summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/add.c148
-rw-r--r--builtin/am.c225
-rw-r--r--builtin/annotate.c10
-rw-r--r--builtin/bisect--helper.c622
-rw-r--r--builtin/blame.c152
-rw-r--r--builtin/branch.c36
-rw-r--r--builtin/bugreport.c194
-rw-r--r--builtin/bundle.c220
-rw-r--r--builtin/cat-file.c27
-rw-r--r--builtin/check-ignore.c41
-rw-r--r--builtin/checkout-index.c20
-rw-r--r--builtin/checkout.c1131
-rw-r--r--builtin/clean.c134
-rw-r--r--builtin/clone.c310
-rw-r--r--builtin/column.c2
-rw-r--r--builtin/commit-graph.c287
-rw-r--r--builtin/commit-tree.c12
-rw-r--r--builtin/commit.c212
-rw-r--r--builtin/config.c77
-rw-r--r--builtin/credential-cache--daemon.c318
-rw-r--r--builtin/credential-cache.c157
-rw-r--r--builtin/credential-store.c195
-rw-r--r--builtin/credential.c3
-rw-r--r--builtin/describe.c105
-rw-r--r--builtin/diff-files.c7
-rw-r--r--builtin/diff-index.c10
-rw-r--r--builtin/diff-tree.c26
-rw-r--r--builtin/diff.c199
-rw-r--r--builtin/difftool.c95
-rw-r--r--builtin/env--helper.c100
-rw-r--r--builtin/fast-export.c309
-rw-r--r--builtin/fast-import.c3636
-rw-r--r--builtin/fetch-pack.c25
-rw-r--r--builtin/fetch.c543
-rw-r--r--builtin/fmt-merge-msg.c656
-rw-r--r--builtin/for-each-ref.c4
-rw-r--r--builtin/for-each-repo.c58
-rw-r--r--builtin/fsck.c143
-rw-r--r--builtin/gc.c1044
-rw-r--r--builtin/grep.c201
-rw-r--r--builtin/hash-object.c2
-rw-r--r--builtin/help.c90
-rw-r--r--builtin/index-pack.c529
-rw-r--r--builtin/init-db.c134
-rw-r--r--builtin/interpret-trailers.c4
-rw-r--r--builtin/log.c505
-rw-r--r--builtin/ls-files.c29
-rw-r--r--builtin/ls-remote.c18
-rw-r--r--builtin/merge-base.c12
-rw-r--r--builtin/merge-recursive.c4
-rw-r--r--builtin/merge-tree.c28
-rw-r--r--builtin/merge.c206
-rw-r--r--builtin/mktag.c7
-rw-r--r--builtin/mktree.c4
-rw-r--r--builtin/multi-pack-index.c26
-rw-r--r--builtin/mv.c7
-rw-r--r--builtin/name-rev.c297
-rw-r--r--builtin/notes.c38
-rw-r--r--builtin/pack-objects.c534
-rw-r--r--builtin/pack-redundant.c6
-rw-r--r--builtin/patch-id.c33
-rw-r--r--builtin/prune-packed.c44
-rw-r--r--builtin/prune.c4
-rw-r--r--builtin/pull.c264
-rw-r--r--builtin/push.c190
-rw-r--r--builtin/range-diff.c9
-rw-r--r--builtin/read-tree.c18
-rw-r--r--builtin/rebase.c916
-rw-r--r--builtin/receive-pack.c768
-rw-r--r--builtin/reflog.c13
-rw-r--r--builtin/remote-ext.c4
-rw-r--r--builtin/remote.c278
-rw-r--r--builtin/repack.c124
-rw-r--r--builtin/replace.c30
-rw-r--r--builtin/reset.c53
-rw-r--r--builtin/rev-list.c164
-rw-r--r--builtin/rev-parse.c133
-rw-r--r--builtin/revert.c16
-rw-r--r--builtin/rm.c36
-rw-r--r--builtin/send-pack.c42
-rw-r--r--builtin/shortlog.c164
-rw-r--r--builtin/show-branch.c27
-rw-r--r--builtin/show-index.c40
-rw-r--r--builtin/show-ref.c12
-rw-r--r--builtin/sparse-checkout.c663
-rw-r--r--builtin/stash.c402
-rw-r--r--builtin/submodule--helper.c736
-rw-r--r--builtin/tag.c43
-rw-r--r--builtin/unpack-objects.c8
-rw-r--r--builtin/update-index.c30
-rw-r--r--builtin/update-ref.c247
-rw-r--r--builtin/upload-archive.c12
-rw-r--r--builtin/upload-pack.c2
-rw-r--r--builtin/verify-commit.c23
-rw-r--r--builtin/verify-pack.c23
-rw-r--r--builtin/worktree.c296
-rw-r--r--builtin/write-tree.c12
97 files changed, 15089 insertions, 4964 deletions
diff --git a/builtin/add.c b/builtin/add.c
index dd18e5c9b6..a825887c50 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -18,8 +18,9 @@
#include "diffcore.h"
#include "revision.h"
#include "bulk-checkin.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "submodule.h"
+#include "add-interactive.h"
static const char * const builtin_add_usage[] = {
N_("git add [<options>] [--] <pathspec>..."),
@@ -28,6 +29,9 @@ static const char * const builtin_add_usage[] = {
static int patch_interactive, add_interactive, edit_interactive;
static int take_worktree_changes;
static int add_renormalize;
+static int pathspec_file_nul;
+static const char *pathspec_from_file;
+static int legacy_stash_p; /* support for the scripted `git stash` */
struct update_callback_data {
int flags;
@@ -184,24 +188,58 @@ int run_add_interactive(const char *revision, const char *patch_mode,
const struct pathspec *pathspec)
{
int status, i;
- struct argv_array argv = ARGV_ARRAY_INIT;
+ struct strvec argv = STRVEC_INIT;
+ int use_builtin_add_i =
+ git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1);
+
+ if (use_builtin_add_i < 0) {
+ int experimental;
+ if (!git_config_get_bool("add.interactive.usebuiltin",
+ &use_builtin_add_i))
+ ; /* ok */
+ else if (!git_config_get_bool("feature.experimental", &experimental) &&
+ experimental)
+ use_builtin_add_i = 1;
+ }
+
+ if (use_builtin_add_i == 1) {
+ enum add_p_mode mode;
+
+ if (!patch_mode)
+ return !!run_add_i(the_repository, pathspec);
+
+ if (!strcmp(patch_mode, "--patch"))
+ mode = ADD_P_ADD;
+ else if (!strcmp(patch_mode, "--patch=stash"))
+ mode = ADD_P_STASH;
+ else if (!strcmp(patch_mode, "--patch=reset"))
+ mode = ADD_P_RESET;
+ else if (!strcmp(patch_mode, "--patch=checkout"))
+ mode = ADD_P_CHECKOUT;
+ else if (!strcmp(patch_mode, "--patch=worktree"))
+ mode = ADD_P_WORKTREE;
+ else
+ die("'%s' not supported", patch_mode);
+
+ return !!run_add_p(the_repository, mode, revision, pathspec);
+ }
- argv_array_push(&argv, "add--interactive");
+ strvec_push(&argv, "add--interactive");
if (patch_mode)
- argv_array_push(&argv, patch_mode);
+ strvec_push(&argv, patch_mode);
if (revision)
- argv_array_push(&argv, revision);
- argv_array_push(&argv, "--");
+ strvec_push(&argv, revision);
+ strvec_push(&argv, "--");
for (i = 0; i < pathspec->nr; i++)
/* pass original pathspec, to be re-parsed */
- argv_array_push(&argv, pathspec->items[i].original);
+ strvec_push(&argv, pathspec->items[i].original);
- status = run_command_v_opt(argv.argv, RUN_GIT_CMD);
- argv_array_clear(&argv);
+ status = run_command_v_opt(argv.v, RUN_GIT_CMD);
+ strvec_clear(&argv);
return status;
}
-int interactive_add(int argc, const char **argv, const char *prefix, int patch)
+int interactive_add(const char **argv, const char *prefix, int patch)
{
struct pathspec pathspec;
@@ -298,10 +336,10 @@ static struct option builtin_add_options[] = {
OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")),
OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
- { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit,
+ OPT_CALLBACK_F(0, "ignore-removal", &addremove_explicit,
NULL /* takes no arguments */,
N_("ignore paths removed in the working tree (same as --no-all)"),
- PARSE_OPT_NOARG, ignore_removal_cb },
+ PARSE_OPT_NOARG, ignore_removal_cb),
OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
@@ -309,6 +347,10 @@ static struct option builtin_add_options[] = {
N_("override the executable bit of the listed files")),
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
N_("warn when adding an embedded repository")),
+ OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p,
+ N_("backend for `git stash -p`")),
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END(),
};
@@ -319,6 +361,7 @@ static int add_config(const char *var, const char *value, void *cb)
ignore_add_errors = git_config_bool(var, value);
return 0;
}
+
return git_default_config(var, value, cb);
}
@@ -369,7 +412,10 @@ static int add_files(struct dir_struct *dir, int flags)
fprintf(stderr, _(ignore_error));
for (i = 0; i < dir->ignored_nr; i++)
fprintf(stderr, "%s\n", dir->ignored[i]->name);
- fprintf(stderr, _("Use -f if you really want to add them.\n"));
+ if (advice_add_ignored_file)
+ advise(_("Use -f if you really want to add them.\n"
+ "Turn this message off by running\n"
+ "\"git config advice.addIgnoredFile false\""));
exit_status = 1;
}
@@ -402,11 +448,28 @@ int cmd_add(int argc, const char **argv, const char *prefix)
builtin_add_usage, PARSE_OPT_KEEP_ARGV0);
if (patch_interactive)
add_interactive = 1;
- if (add_interactive)
- exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive));
+ if (add_interactive) {
+ if (pathspec_from_file)
+ die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
+ exit(interactive_add(argv + 1, prefix, patch_interactive));
+ }
+ if (legacy_stash_p) {
+ struct pathspec pathspec;
- if (edit_interactive)
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_SYMLINK_LEADING_PATH |
+ PATHSPEC_PREFIX_ORIGIN,
+ prefix, argv);
+
+ return run_add_interactive(NULL, "--patch=stash", &pathspec);
+ }
+
+ if (edit_interactive) {
+ if (pathspec_from_file)
+ die(_("--pathspec-from-file is incompatible with --edit"));
return(edit_patch(argc, argv, prefix));
+ }
argc--;
argv++;
@@ -418,10 +481,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (addremove && take_worktree_changes)
die(_("-A and -u are mutually incompatible"));
- if (!take_worktree_changes && addremove_explicit < 0 && argc)
- /* Turn "git add pathspec..." to "git add -A pathspec..." */
- addremove = 1;
-
if (!show_only && ignore_missing)
die(_("Option --ignore-missing can only be used together with --dry-run"));
@@ -434,19 +493,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
- flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
- (show_only ? ADD_CACHE_PRETEND : 0) |
- (intent_to_add ? ADD_CACHE_INTENT : 0) |
- (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
- (!(addremove || take_worktree_changes)
- ? ADD_CACHE_IGNORE_REMOVAL : 0));
-
- if (require_pathspec && argc == 0) {
- fprintf(stderr, _("Nothing specified, nothing added.\n"));
- fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
- return 0;
- }
-
/*
* Check the "pathspec '%s' did not match any files" block
* below before enabling new magic.
@@ -456,17 +502,49 @@ int cmd_add(int argc, const char **argv, const char *prefix)
PATHSPEC_SYMLINK_LEADING_PATH,
prefix, argv);
+ if (pathspec_from_file) {
+ if (pathspec.nr)
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+ parse_pathspec_file(&pathspec, PATHSPEC_ATTR,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_SYMLINK_LEADING_PATH,
+ prefix, pathspec_from_file, pathspec_file_nul);
+ } else if (pathspec_file_nul) {
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
+ }
+
+ if (require_pathspec && pathspec.nr == 0) {
+ fprintf(stderr, _("Nothing specified, nothing added.\n"));
+ if (advice_add_empty_pathspec)
+ advise( _("Maybe you wanted to say 'git add .'?\n"
+ "Turn this message off by running\n"
+ "\"git config advice.addEmptyPathspec false\""));
+ return 0;
+ }
+
+ if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr)
+ /* Turn "git add pathspec..." to "git add -A pathspec..." */
+ addremove = 1;
+
+ flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
+ (show_only ? ADD_CACHE_PRETEND : 0) |
+ (intent_to_add ? ADD_CACHE_INTENT : 0) |
+ (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
+ (!(addremove || take_worktree_changes)
+ ? ADD_CACHE_IGNORE_REMOVAL : 0));
+
if (read_cache_preload(&pathspec) < 0)
die(_("index file corrupt"));
die_in_unpopulated_submodule(&the_index, prefix);
die_path_inside_submodule(&the_index, &pathspec);
+ dir_init(&dir);
if (add_new_files) {
int baselen;
/* Set up the default git porcelain excludes */
- memset(&dir, 0, sizeof(dir));
if (!ignored_too) {
dir.flags |= DIR_COLLECT_IGNORED;
setup_standard_excludes(&dir);
@@ -539,7 +617,7 @@ finish:
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("Unable to write new index file"));
+ dir_clear(&dir);
UNLEAK(pathspec);
- UNLEAK(dir);
return exit_status;
}
diff --git a/builtin/am.c b/builtin/am.c
index 252e37ddf0..f22c73a05b 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -24,7 +24,6 @@
#include "sequencer.h"
#include "revision.h"
#include "merge-recursive.h"
-#include "revision.h"
#include "log-tree.h"
#include "notes-utils.h"
#include "rerere.h"
@@ -82,6 +81,11 @@ enum signoff_type {
SIGNOFF_EXPLICIT /* --signoff was set on the command-line */
};
+enum show_patch_type {
+ SHOW_PATCH_RAW = 0,
+ SHOW_PATCH_DIFF = 1,
+};
+
struct am_state {
/* state directory path */
char *dir;
@@ -112,7 +116,7 @@ struct am_state {
int keep; /* enum keep_type */
int message_id;
int scissors; /* enum scissors_type */
- struct argv_array git_apply_opts;
+ struct strvec git_apply_opts;
const char *resolvemsg;
int committer_date_is_author_date;
int ignore_date;
@@ -142,7 +146,7 @@ static void am_state_init(struct am_state *state)
state->scissors = SCISSORS_UNSET;
- argv_array_init(&state->git_apply_opts);
+ strvec_init(&state->git_apply_opts);
if (!git_config_get_bool("commit.gpgsign", &gpgsign))
state->sign_commit = gpgsign ? "" : NULL;
@@ -158,7 +162,7 @@ static void am_state_release(struct am_state *state)
free(state->author_email);
free(state->author_date);
free(state->msg);
- argv_array_clear(&state->git_apply_opts);
+ strvec_clear(&state->git_apply_opts);
}
/**
@@ -394,8 +398,8 @@ static void am_load(struct am_state *state)
state->scissors = SCISSORS_UNSET;
read_state_file(&sb, state, "apply-opt", 1);
- argv_array_clear(&state->git_apply_opts);
- if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)
+ strvec_clear(&state->git_apply_opts);
+ if (sq_dequote_to_strvec(sb.buf, &state->git_apply_opts) < 0)
die(_("could not parse %s"), am_path(state, "apply-opt"));
state->rebasing = !!file_exists(am_path(state, "rebasing"));
@@ -448,8 +452,8 @@ static int run_post_rewrite_hook(const struct am_state *state)
if (!hook)
return 0;
- argv_array_push(&cp.args, hook);
- argv_array_push(&cp.args, "rebase");
+ strvec_push(&cp.args, hook);
+ strvec_push(&cp.args, "rebase");
cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
cp.stdout_to_stderr = 1;
@@ -647,16 +651,16 @@ static int split_mail_mbox(struct am_state *state, const char **paths,
int ret;
cp.git_cmd = 1;
- argv_array_push(&cp.args, "mailsplit");
- argv_array_pushf(&cp.args, "-d%d", state->prec);
- argv_array_pushf(&cp.args, "-o%s", state->dir);
- argv_array_push(&cp.args, "-b");
+ strvec_push(&cp.args, "mailsplit");
+ strvec_pushf(&cp.args, "-d%d", state->prec);
+ strvec_pushf(&cp.args, "-o%s", state->dir);
+ strvec_push(&cp.args, "-b");
if (keep_cr)
- argv_array_push(&cp.args, "--keep-cr");
+ strvec_push(&cp.args, "--keep-cr");
if (mboxrd)
- argv_array_push(&cp.args, "--mboxrd");
- argv_array_push(&cp.args, "--");
- argv_array_pushv(&cp.args, paths);
+ strvec_push(&cp.args, "--mboxrd");
+ strvec_push(&cp.args, "--");
+ strvec_pushv(&cp.args, paths);
ret = capture_command(&cp, &last, 8);
if (ret)
@@ -783,7 +787,7 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths,
const char *series_dir;
char *series_dir_buf;
FILE *fp;
- struct argv_array patches = ARGV_ARRAY_INIT;
+ struct strvec patches = STRVEC_INIT;
struct strbuf sb = STRBUF_INIT;
int ret;
@@ -801,16 +805,16 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths,
if (*sb.buf == '#')
continue; /* skip comment lines */
- argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf));
+ strvec_push(&patches, mkpath("%s/%s", series_dir, sb.buf));
}
fclose(fp);
strbuf_release(&sb);
free(series_dir_buf);
- ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr);
+ ret = split_mail_conv(stgit_patch_to_mail, state, patches.v, keep_cr);
- argv_array_clear(&patches);
+ strvec_clear(&patches);
return ret;
}
@@ -998,7 +1002,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
}
write_state_text(state, "scissors", str);
- sq_quote_argv(&sb, state->git_apply_opts.argv);
+ sq_quote_argv(&sb, state->git_apply_opts.v);
write_state_text(state, "apply-opt", sb.buf);
if (state->rebasing)
@@ -1072,19 +1076,6 @@ static const char *msgnum(const struct am_state *state)
}
/**
- * Refresh and write index.
- */
-static void refresh_and_write_cache(void)
-{
- struct lock_file lock_file = LOCK_INIT;
-
- hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
- refresh_cache(REFRESH_QUIET);
- if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
- die(_("unable to write index file"));
-}
-
-/**
* Dies with a user-friendly message on how to proceed after resolving the
* problem. This message can be overridden with state->resolvemsg.
*/
@@ -1272,7 +1263,9 @@ static void get_commit_info(struct am_state *state, struct commit *commit)
buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());
ident_line = find_commit_header(buffer, "author", &ident_len);
-
+ if (!ident_line)
+ die(_("missing author line in commit %s"),
+ oid_to_hex(&commit->object.oid));
if (split_ident_line(&id, ident_line, ident_len) < 0)
die(_("invalid ident line: %.*s"), (int)ident_len, ident_line);
@@ -1397,8 +1390,8 @@ static int parse_mail_rebase(struct am_state *state, const char *mail)
*/
static int run_apply(const struct am_state *state, const char *index_file)
{
- struct argv_array apply_paths = ARGV_ARRAY_INIT;
- struct argv_array apply_opts = ARGV_ARRAY_INIT;
+ struct strvec apply_paths = STRVEC_INIT;
+ struct strvec apply_opts = STRVEC_INIT;
struct apply_state apply_state;
int res, opts_left;
int force_apply = 0;
@@ -1407,10 +1400,10 @@ static int run_apply(const struct am_state *state, const char *index_file)
if (init_apply_state(&apply_state, the_repository, NULL))
BUG("init_apply_state() failed");
- argv_array_push(&apply_opts, "apply");
- argv_array_pushv(&apply_opts, state->git_apply_opts.argv);
+ strvec_push(&apply_opts, "apply");
+ strvec_pushv(&apply_opts, state->git_apply_opts.v);
- opts_left = apply_parse_options(apply_opts.argc, apply_opts.argv,
+ opts_left = apply_parse_options(apply_opts.nr, apply_opts.v,
&apply_state, &force_apply, &options,
NULL);
@@ -1433,12 +1426,12 @@ static int run_apply(const struct am_state *state, const char *index_file)
if (check_apply_state(&apply_state, force_apply))
BUG("check_apply_state() failed");
- argv_array_push(&apply_paths, am_path(state, "patch"));
+ strvec_push(&apply_paths, am_path(state, "patch"));
- res = apply_all_patches(&apply_state, apply_paths.argc, apply_paths.argv, options);
+ res = apply_all_patches(&apply_state, apply_paths.nr, apply_paths.v, options);
- argv_array_clear(&apply_paths);
- argv_array_clear(&apply_opts);
+ strvec_clear(&apply_paths);
+ strvec_clear(&apply_opts);
clear_apply_state(&apply_state);
if (res)
@@ -1461,10 +1454,10 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
- argv_array_push(&cp.args, "apply");
- argv_array_pushv(&cp.args, state->git_apply_opts.argv);
- argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
- argv_array_push(&cp.args, am_path(state, "patch"));
+ strvec_push(&cp.args, "apply");
+ strvec_pushv(&cp.args, state->git_apply_opts.v);
+ strvec_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
+ strvec_push(&cp.args, am_path(state, "patch"));
if (run_command(&cp))
return -1;
@@ -1538,7 +1531,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
o.branch1 = "HEAD";
their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
o.branch2 = their_tree_name;
- o.detect_directory_renames = 0;
+ o.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE;
if (state->quiet)
o.verbosity = 0;
@@ -1563,7 +1556,7 @@ static void do_commit(const struct am_state *state)
struct object_id tree, parent, commit;
const struct object_id *old_oid;
struct commit_list *parents = NULL;
- const char *reflog_msg, *author;
+ const char *reflog_msg, *author, *committer = NULL;
struct strbuf sb = STRBUF_INIT;
if (run_hook_le(NULL, "pre-applypatch", NULL))
@@ -1587,11 +1580,16 @@ static void do_commit(const struct am_state *state)
IDENT_STRICT);
if (state->committer_date_is_author_date)
- setenv("GIT_COMMITTER_DATE",
- state->ignore_date ? "" : state->author_date, 1);
-
- if (commit_tree(state->msg, state->msg_len, &tree, parents, &commit,
- author, state->sign_commit))
+ committer = fmt_ident(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL"),
+ WANT_COMMITTER_IDENT,
+ state->ignore_date ? NULL
+ : state->author_date,
+ IDENT_STRICT);
+
+ if (commit_tree_extended(state->msg, state->msg_len, &tree, parents,
+ &commit, author, committer, state->sign_commit,
+ NULL))
die(_("failed to write commit object"));
reflog_msg = getenv("GIT_REFLOG_ACTION");
@@ -1683,7 +1681,7 @@ static int do_interactive(struct am_state *state)
if (!pager)
pager = "cat";
prepare_pager_args(&cp, pager);
- argv_array_push(&cp.args, am_path(state, "patch"));
+ strvec_push(&cp.args, am_path(state, "patch"));
run_command(&cp);
}
}
@@ -1698,12 +1696,12 @@ static int do_interactive(struct am_state *state)
*/
static void am_run(struct am_state *state, int resume)
{
- const char *argv_gc_auto[] = {"gc", "--auto", NULL};
struct strbuf sb = STRBUF_INIT;
unlink(am_path(state, "dirtyindex"));
- refresh_and_write_cache();
+ if (refresh_and_write_cache(REFRESH_QUIET, 0, 0) < 0)
+ die(_("unable to write index file"));
if (repo_index_has_changes(the_repository, NULL, &sb)) {
write_state_bool(state, "dirtyindex", 1);
@@ -1774,7 +1772,7 @@ static void am_run(struct am_state *state, int resume)
linelen(state->msg), state->msg);
if (advice_amworkdir)
- advise(_("Use 'git am --show-current-patch' to see the failed patch"));
+ advise(_("Use 'git am --show-current-patch=diff' to see the failed patch"));
die_user_resolve(state);
}
@@ -1802,7 +1800,7 @@ next:
if (!state->rebasing) {
am_destroy(state);
close_object_store(the_repository->objects);
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ run_auto_maintenance(state->quiet);
}
}
@@ -1956,7 +1954,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem
if (merge_tree(remote_tree))
return -1;
- remove_branch_state(the_repository);
+ remove_branch_state(the_repository, 0);
return 0;
}
@@ -2072,7 +2070,7 @@ static void am_abort(struct am_state *state)
am_destroy(state);
}
-static int show_patch(struct am_state *state)
+static int show_patch(struct am_state *state, enum show_patch_type sub_mode)
{
struct strbuf sb = STRBUF_INIT;
const char *patch_path;
@@ -2089,7 +2087,17 @@ static int show_patch(struct am_state *state)
return ret;
}
- patch_path = am_path(state, msgnum(state));
+ switch (sub_mode) {
+ case SHOW_PATCH_RAW:
+ patch_path = am_path(state, msgnum(state));
+ break;
+ case SHOW_PATCH_DIFF:
+ patch_path = am_path(state, "patch");
+ break;
+ default:
+ BUG("invalid mode for --show-current-patch");
+ }
+
len = strbuf_read_file(&sb, patch_path, 0);
if (len < 0)
die_errno(_("failed to read '%s'"), patch_path);
@@ -2129,7 +2137,7 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
return 0;
}
-enum resume_mode {
+enum resume_type {
RESUME_FALSE = 0,
RESUME_APPLY,
RESUME_RESOLVED,
@@ -2139,6 +2147,47 @@ enum resume_mode {
RESUME_SHOW_PATCH
};
+struct resume_mode {
+ enum resume_type mode;
+ enum show_patch_type sub_mode;
+};
+
+static int parse_opt_show_current_patch(const struct option *opt, const char *arg, int unset)
+{
+ int *opt_value = opt->value;
+ struct resume_mode *resume = container_of(opt_value, struct resume_mode, mode);
+
+ /*
+ * Please update $__git_showcurrentpatch in git-completion.bash
+ * when you add new options
+ */
+ const char *valid_modes[] = {
+ [SHOW_PATCH_DIFF] = "diff",
+ [SHOW_PATCH_RAW] = "raw"
+ };
+ int new_value = SHOW_PATCH_RAW;
+
+ BUG_ON_OPT_NEG(unset);
+
+ if (arg) {
+ for (new_value = 0; new_value < ARRAY_SIZE(valid_modes); new_value++) {
+ if (!strcmp(arg, valid_modes[new_value]))
+ break;
+ }
+ if (new_value >= ARRAY_SIZE(valid_modes))
+ return error(_("Invalid value for --show-current-patch: %s"), arg);
+ }
+
+ if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode)
+ return error(_("--show-current-patch=%s is incompatible with "
+ "--show-current-patch=%s"),
+ arg, valid_modes[resume->sub_mode]);
+
+ resume->mode = RESUME_SHOW_PATCH;
+ resume->sub_mode = new_value;
+ return 0;
+}
+
static int git_am_config(const char *k, const char *v, void *cb)
{
int status;
@@ -2156,7 +2205,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
int binary = -1;
int keep_cr = -1;
int patch_format = PATCH_FORMAT_UNKNOWN;
- enum resume_mode resume = RESUME_FALSE;
+ struct resume_mode resume = { .mode = RESUME_FALSE };
int in_progress;
int ret = 0;
@@ -2175,7 +2224,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
N_("allow fall back on 3way merging if needed")),
OPT__QUIET(&state.quiet, N_("be quiet")),
OPT_SET_INT('s', "signoff", &state.signoff,
- N_("add a Signed-off-by line to the commit message"),
+ N_("add a Signed-off-by trailer to the commit message"),
SIGNOFF_EXPLICIT),
OPT_BOOL('u', "utf8", &state.utf8,
N_("recode into utf8 (default)")),
@@ -2225,24 +2274,26 @@ int cmd_am(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOARG),
OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
N_("override error message when patch failure occurs")),
- OPT_CMDMODE(0, "continue", &resume,
+ OPT_CMDMODE(0, "continue", &resume.mode,
N_("continue applying patches after resolving a conflict"),
RESUME_RESOLVED),
- OPT_CMDMODE('r', "resolved", &resume,
+ OPT_CMDMODE('r', "resolved", &resume.mode,
N_("synonyms for --continue"),
RESUME_RESOLVED),
- OPT_CMDMODE(0, "skip", &resume,
+ OPT_CMDMODE(0, "skip", &resume.mode,
N_("skip the current patch"),
RESUME_SKIP),
- OPT_CMDMODE(0, "abort", &resume,
+ OPT_CMDMODE(0, "abort", &resume.mode,
N_("restore the original branch and abort the patching operation."),
RESUME_ABORT),
- OPT_CMDMODE(0, "quit", &resume,
+ OPT_CMDMODE(0, "quit", &resume.mode,
N_("abort the patching operation but keep HEAD where it is."),
RESUME_QUIT),
- OPT_CMDMODE(0, "show-current-patch", &resume,
- N_("show the patch being applied."),
- RESUME_SHOW_PATCH),
+ { OPTION_CALLBACK, 0, "show-current-patch", &resume.mode,
+ "(diff|raw)",
+ N_("show the patch being applied"),
+ PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+ parse_opt_show_current_patch, RESUME_SHOW_PATCH },
OPT_BOOL(0, "committer-date-is-author-date",
&state.committer_date_is_author_date,
N_("lie about committer date")),
@@ -2292,17 +2343,17 @@ int cmd_am(int argc, const char **argv, const char *prefix)
* intend to feed us a patch but wanted to continue
* unattended.
*/
- if (argc || (resume == RESUME_FALSE && !isatty(0)))
+ if (argc || (resume.mode == RESUME_FALSE && !isatty(0)))
die(_("previous rebase directory %s still exists but mbox given."),
state.dir);
- if (resume == RESUME_FALSE)
- resume = RESUME_APPLY;
+ if (resume.mode == RESUME_FALSE)
+ resume.mode = RESUME_APPLY;
if (state.signoff == SIGNOFF_EXPLICIT)
am_append_signoff(&state);
} else {
- struct argv_array paths = ARGV_ARRAY_INIT;
+ struct strvec paths = STRVEC_INIT;
int i;
/*
@@ -2311,7 +2362,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
* stray directories.
*/
if (file_exists(state.dir) && !state.rebasing) {
- if (resume == RESUME_ABORT || resume == RESUME_QUIT) {
+ if (resume.mode == RESUME_ABORT || resume.mode == RESUME_QUIT) {
am_destroy(&state);
am_state_release(&state);
return 0;
@@ -2322,25 +2373,25 @@ int cmd_am(int argc, const char **argv, const char *prefix)
state.dir);
}
- if (resume)
+ if (resume.mode)
die(_("Resolve operation not in progress, we are not resuming."));
for (i = 0; i < argc; i++) {
if (is_absolute_path(argv[i]) || !prefix)
- argv_array_push(&paths, argv[i]);
+ strvec_push(&paths, argv[i]);
else
- argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
+ strvec_push(&paths, mkpath("%s/%s", prefix, argv[i]));
}
- if (state.interactive && !paths.argc)
+ if (state.interactive && !paths.nr)
die(_("interactive mode requires patches on the command line"));
- am_setup(&state, patch_format, paths.argv, keep_cr);
+ am_setup(&state, patch_format, paths.v, keep_cr);
- argv_array_clear(&paths);
+ strvec_clear(&paths);
}
- switch (resume) {
+ switch (resume.mode) {
case RESUME_FALSE:
am_run(&state, 0);
break;
@@ -2361,7 +2412,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
am_destroy(&state);
break;
case RESUME_SHOW_PATCH:
- ret = show_patch(&state);
+ ret = show_patch(&state, resume.sub_mode);
break;
default:
BUG("invalid resume value");
diff --git a/builtin/annotate.c b/builtin/annotate.c
index da413ae0d1..58ff977a23 100644
--- a/builtin/annotate.c
+++ b/builtin/annotate.c
@@ -5,18 +5,18 @@
*/
#include "git-compat-util.h"
#include "builtin.h"
-#include "argv-array.h"
+#include "strvec.h"
int cmd_annotate(int argc, const char **argv, const char *prefix)
{
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
int i;
- argv_array_pushl(&args, "annotate", "-c", NULL);
+ strvec_pushl(&args, "annotate", "-c", NULL);
for (i = 1; i < argc; i++) {
- argv_array_push(&args, argv[i]);
+ strvec_push(&args, argv[i]);
}
- return cmd_blame(args.argc, args.argv, prefix);
+ return cmd_blame(args.nr, args.v, prefix);
}
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 1fbe156e67..709eb713a3 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -4,34 +4,41 @@
#include "bisect.h"
#include "refs.h"
#include "dir.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "run-command.h"
#include "prompt.h"
#include "quote.h"
+#include "revision.h"
static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS")
static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV")
static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK")
static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START")
-static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD")
static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG")
static GIT_PATH_FUNC(git_path_head_name, "head-name")
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
+static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT")
static const char * const git_bisect_helper_usage[] = {
- N_("git bisect--helper --next-all [--no-checkout]"),
- N_("git bisect--helper --write-terms <bad_term> <good_term>"),
- N_("git bisect--helper --bisect-clean-state"),
N_("git bisect--helper --bisect-reset [<commit>]"),
N_("git bisect--helper --bisect-write [--no-log] <state> <revision> <good_term> <bad_term>"),
N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"),
N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"),
N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"),
- N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]"
- "[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"),
+ N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]"
+ " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"),
+ N_("git bisect--helper --bisect-next"),
+ N_("git bisect--helper --bisect-auto-next"),
+ N_("git bisect--helper --bisect-state (bad|new) [<rev>]"),
+ N_("git bisect--helper --bisect-state (good|old) [<rev>...]"),
NULL
};
+struct add_bisect_ref_data {
+ struct rev_info *revs;
+ unsigned int object_flags;
+};
+
struct bisect_terms {
char *term_good;
char *term_bad;
@@ -52,8 +59,10 @@ static void set_terms(struct bisect_terms *terms, const char *bad,
terms->term_bad = xstrdup(bad);
}
-static const char *vocab_bad = "bad|new";
-static const char *vocab_good = "good|old";
+static const char vocab_bad[] = "bad|new";
+static const char vocab_good[] = "good|old";
+
+static int bisect_autostart(struct bisect_terms *terms);
/*
* Check whether the string `term` belongs to the set of strings
@@ -74,6 +83,65 @@ static int one_of(const char *term, ...)
return res;
}
+/*
+ * return code BISECT_INTERNAL_SUCCESS_MERGE_BASE
+ * and BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND are codes
+ * that indicate special success.
+ */
+
+static int is_bisect_success(enum bisect_error res)
+{
+ return !res ||
+ res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND ||
+ res == BISECT_INTERNAL_SUCCESS_MERGE_BASE;
+}
+
+static int write_in_file(const char *path, const char *mode, const char *format, va_list args)
+{
+ FILE *fp = NULL;
+ int res = 0;
+
+ if (strcmp(mode, "w") && strcmp(mode, "a"))
+ BUG("write-in-file does not support '%s' mode", mode);
+ fp = fopen(path, mode);
+ if (!fp)
+ return error_errno(_("cannot open file '%s' in mode '%s'"), path, mode);
+ res = vfprintf(fp, format, args);
+
+ if (res < 0) {
+ int saved_errno = errno;
+ fclose(fp);
+ errno = saved_errno;
+ return error_errno(_("could not write to file '%s'"), path);
+ }
+
+ return fclose(fp);
+}
+
+static int write_to_file(const char *path, const char *format, ...)
+{
+ int res;
+ va_list args;
+
+ va_start(args, format);
+ res = write_in_file(path, "w", format, args);
+ va_end(args);
+
+ return res;
+}
+
+static int append_to_file(const char *path, const char *format, ...)
+{
+ int res;
+ va_list args;
+
+ va_start(args, format);
+ res = write_in_file(path, "a", format, args);
+ va_end(args);
+
+ return res;
+}
+
static int check_term_format(const char *term, const char *orig_term)
{
int res;
@@ -104,7 +172,6 @@ static int check_term_format(const char *term, const char *orig_term)
static int write_terms(const char *bad, const char *good)
{
- FILE *fp = NULL;
int res;
if (!strcmp(bad, good))
@@ -113,39 +180,11 @@ static int write_terms(const char *bad, const char *good)
if (check_term_format(bad, "bad") || check_term_format(good, "good"))
return -1;
- fp = fopen(git_path_bisect_terms(), "w");
- if (!fp)
- return error_errno(_("could not open the file BISECT_TERMS"));
-
- res = fprintf(fp, "%s\n%s\n", bad, good);
- res |= fclose(fp);
- return (res < 0) ? -1 : 0;
-}
+ res = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good);
-static int is_expected_rev(const char *expected_hex)
-{
- struct strbuf actual_hex = STRBUF_INIT;
- int res = 0;
- if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) {
- strbuf_trim(&actual_hex);
- res = !strcmp(actual_hex.buf, expected_hex);
- }
- strbuf_release(&actual_hex);
return res;
}
-static void check_expected_revs(const char **revs, int rev_nr)
-{
- int i;
-
- for (i = 0; i < rev_nr; i++) {
- if (!is_expected_rev(revs[i])) {
- unlink_or_warn(git_path_bisect_ancestors_ok());
- unlink_or_warn(git_path_bisect_expected_rev());
- }
- }
-}
-
static int bisect_reset(const char *commit)
{
struct strbuf branch = STRBUF_INIT;
@@ -164,18 +203,19 @@ static int bisect_reset(const char *commit)
strbuf_addstr(&branch, commit);
}
- if (!file_exists(git_path_bisect_head())) {
- struct argv_array argv = ARGV_ARRAY_INIT;
+ if (!ref_exists("BISECT_HEAD")) {
+ struct strvec argv = STRVEC_INIT;
- argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL);
- if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
+ strvec_pushl(&argv, "checkout", branch.buf, "--", NULL);
+ if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
+ error(_("could not check out original"
+ " HEAD '%s'. Try 'git bisect"
+ " reset <commit>'."), branch.buf);
strbuf_release(&branch);
- argv_array_clear(&argv);
- return error(_("could not check out original"
- " HEAD '%s'. Try 'git bisect"
- " reset <commit>'."), branch.buf);
+ strvec_clear(&argv);
+ return -1;
}
- argv_array_clear(&argv);
+ strvec_clear(&argv);
}
strbuf_release(&branch);
@@ -205,31 +245,31 @@ static int bisect_write(const char *state, const char *rev,
struct object_id oid;
struct commit *commit;
FILE *fp = NULL;
- int retval = 0;
+ int res = 0;
if (!strcmp(state, terms->term_bad)) {
strbuf_addf(&tag, "refs/bisect/%s", state);
} else if (one_of(state, terms->term_good, "skip", NULL)) {
strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev);
} else {
- retval = error(_("Bad bisect_write argument: %s"), state);
+ res = error(_("Bad bisect_write argument: %s"), state);
goto finish;
}
if (get_oid(rev, &oid)) {
- retval = error(_("couldn't get the oid of the rev '%s'"), rev);
+ res = error(_("couldn't get the oid of the rev '%s'"), rev);
goto finish;
}
if (update_ref(NULL, tag.buf, &oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR)) {
- retval = -1;
+ res = -1;
goto finish;
}
fp = fopen(git_path_bisect_log(), "a");
if (!fp) {
- retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log());
+ res = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log());
goto finish;
}
@@ -243,7 +283,7 @@ finish:
if (fp)
fclose(fp);
strbuf_release(&tag);
- return retval;
+ return res;
}
static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)
@@ -281,35 +321,23 @@ static int mark_good(const char *refname, const struct object_id *oid,
return 1;
}
-static const char *need_bad_and_good_revision_warning =
+static const char need_bad_and_good_revision_warning[] =
N_("You need to give me at least one %s and %s revision.\n"
"You can use \"git bisect %s\" and \"git bisect %s\" for that.");
-static const char *need_bisect_start_warning =
+static const char need_bisect_start_warning[] =
N_("You need to start by \"git bisect start\".\n"
"You then need to give me at least one %s and %s revision.\n"
"You can use \"git bisect %s\" and \"git bisect %s\" for that.");
-static int bisect_next_check(const struct bisect_terms *terms,
- const char *current_term)
+static int decide_next(const struct bisect_terms *terms,
+ const char *current_term, int missing_good,
+ int missing_bad)
{
- int missing_good = 1, missing_bad = 1, retval = 0;
- const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
- const char *good_glob = xstrfmt("%s-*", terms->term_good);
-
- if (ref_exists(bad_ref))
- missing_bad = 0;
-
- for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/",
- (void *) &missing_good);
-
if (!missing_good && !missing_bad)
- goto finish;
-
- if (!current_term) {
- retval = -1;
- goto finish;
- }
+ return 0;
+ if (!current_term)
+ return -1;
if (missing_good && !missing_bad &&
!strcmp(current_term, terms->term_good)) {
@@ -320,7 +348,7 @@ static int bisect_next_check(const struct bisect_terms *terms,
*/
warning(_("bisecting only with a %s commit"), terms->term_bad);
if (!isatty(0))
- goto finish;
+ return 0;
/*
* TRANSLATORS: Make sure to include [Y] and [n] in your
* translation. The program will only accept English input
@@ -328,21 +356,35 @@ static int bisect_next_check(const struct bisect_terms *terms,
*/
yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO);
if (starts_with(yesno, "N") || starts_with(yesno, "n"))
- retval = -1;
- goto finish;
- }
- if (!is_empty_or_missing_file(git_path_bisect_start())) {
- retval = error(_(need_bad_and_good_revision_warning),
- vocab_bad, vocab_good, vocab_bad, vocab_good);
- } else {
- retval = error(_(need_bisect_start_warning),
- vocab_good, vocab_bad, vocab_good, vocab_bad);
+ return -1;
+ return 0;
}
-finish:
- free((void *) good_glob);
- free((void *) bad_ref);
- return retval;
+ if (!is_empty_or_missing_file(git_path_bisect_start()))
+ return error(_(need_bad_and_good_revision_warning),
+ vocab_bad, vocab_good, vocab_bad, vocab_good);
+ else
+ return error(_(need_bisect_start_warning),
+ vocab_good, vocab_bad, vocab_good, vocab_bad);
+}
+
+static int bisect_next_check(const struct bisect_terms *terms,
+ const char *current_term)
+{
+ int missing_good = 1, missing_bad = 1;
+ char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
+ char *good_glob = xstrfmt("%s-*", terms->term_good);
+
+ if (ref_exists(bad_ref))
+ missing_bad = 0;
+
+ for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/",
+ (void *) &missing_good);
+
+ free(good_glob);
+ free(bad_ref);
+
+ return decide_next(terms, current_term, missing_good, missing_bad);
}
static int get_terms(struct bisect_terms *terms)
@@ -396,7 +438,7 @@ static int bisect_terms(struct bisect_terms *terms, const char *option)
static int bisect_append_log_quoted(const char **argv)
{
- int retval = 0;
+ int res = 0;
FILE *fp = fopen(git_path_bisect_log(), "a");
struct strbuf orig_args = STRBUF_INIT;
@@ -404,25 +446,163 @@ static int bisect_append_log_quoted(const char **argv)
return -1;
if (fprintf(fp, "git bisect start") < 1) {
- retval = -1;
+ res = -1;
goto finish;
}
sq_quote_argv(&orig_args, argv);
if (fprintf(fp, "%s\n", orig_args.buf) < 1)
- retval = -1;
+ res = -1;
finish:
fclose(fp);
strbuf_release(&orig_args);
- return retval;
+ return res;
+}
+
+static int add_bisect_ref(const char *refname, const struct object_id *oid,
+ int flags, void *cb)
+{
+ struct add_bisect_ref_data *data = cb;
+
+ add_pending_oid(data->revs, refname, oid, data->object_flags);
+
+ return 0;
}
-static int bisect_start(struct bisect_terms *terms, int no_checkout,
- const char **argv, int argc)
+static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
{
+ int res = 0;
+ struct add_bisect_ref_data cb = { revs };
+ char *good = xstrfmt("%s-*", terms->term_good);
+
+ /*
+ * We cannot use terms->term_bad directly in
+ * for_each_glob_ref_in() and we have to append a '*' to it,
+ * otherwise for_each_glob_ref_in() will append '/' and '*'.
+ */
+ char *bad = xstrfmt("%s*", terms->term_bad);
+
+ /*
+ * It is important to reset the flags used by revision walks
+ * as the previous call to bisect_next_all() in turn
+ * sets up a revision walk.
+ */
+ reset_revision_walk();
+ init_revisions(revs, NULL);
+ setup_revisions(0, NULL, revs, NULL);
+ for_each_glob_ref_in(add_bisect_ref, bad, "refs/bisect/", &cb);
+ cb.object_flags = UNINTERESTING;
+ for_each_glob_ref_in(add_bisect_ref, good, "refs/bisect/", &cb);
+ if (prepare_revision_walk(revs))
+ res = error(_("revision walk setup failed\n"));
+
+ free(good);
+ free(bad);
+ return res;
+}
+
+static int bisect_skipped_commits(struct bisect_terms *terms)
+{
+ int res;
+ FILE *fp = NULL;
+ struct rev_info revs;
+ struct commit *commit;
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_name = STRBUF_INIT;
+
+ res = prepare_revs(terms, &revs);
+ if (res)
+ return res;
+
+ fp = fopen(git_path_bisect_log(), "a");
+ if (!fp)
+ return error_errno(_("could not open '%s' for appending"),
+ git_path_bisect_log());
+
+ if (fprintf(fp, "# only skipped commits left to test\n") < 0)
+ return error_errno(_("failed to write to '%s'"), git_path_bisect_log());
+
+ while ((commit = get_revision(&revs)) != NULL) {
+ strbuf_reset(&commit_name);
+ format_commit_message(commit, "%s",
+ &commit_name, &pp);
+ fprintf(fp, "# possible first %s commit: [%s] %s\n",
+ terms->term_bad, oid_to_hex(&commit->object.oid),
+ commit_name.buf);
+ }
+
+ /*
+ * Reset the flags used by revision walks in case
+ * there is another revision walk after this one.
+ */
+ reset_revision_walk();
+
+ strbuf_release(&commit_name);
+ fclose(fp);
+ return 0;
+}
+
+static int bisect_successful(struct bisect_terms *terms)
+{
+ struct object_id oid;
+ struct commit *commit;
+ struct pretty_print_context pp = {0};
+ struct strbuf commit_name = STRBUF_INIT;
+ char *bad_ref = xstrfmt("refs/bisect/%s",terms->term_bad);
+ int res;
+
+ read_ref(bad_ref, &oid);
+ commit = lookup_commit_reference_by_name(bad_ref);
+ format_commit_message(commit, "%s", &commit_name, &pp);
+
+ res = append_to_file(git_path_bisect_log(), "# first %s commit: [%s] %s\n",
+ terms->term_bad, oid_to_hex(&commit->object.oid),
+ commit_name.buf);
+
+ strbuf_release(&commit_name);
+ free(bad_ref);
+ return res;
+}
+
+static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix)
+{
+ enum bisect_error res;
+
+ if (bisect_autostart(terms))
+ return BISECT_FAILED;
+
+ if (bisect_next_check(terms, terms->term_good))
+ return BISECT_FAILED;
+
+ /* Perform all bisection computation */
+ res = bisect_next_all(the_repository, prefix);
+
+ if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) {
+ res = bisect_successful(terms);
+ return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND;
+ } else if (res == BISECT_ONLY_SKIPPED_LEFT) {
+ res = bisect_skipped_commits(terms);
+ return res ? res : BISECT_ONLY_SKIPPED_LEFT;
+ }
+ return res;
+}
+
+static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix)
+{
+ if (bisect_next_check(terms, NULL))
+ return BISECT_OK;
+
+ return bisect_next(terms, prefix);
+}
+
+static enum bisect_error bisect_start(struct bisect_terms *terms, const char **argv, int argc)
+{
+ int no_checkout = 0;
+ int first_parent_only = 0;
int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0;
- int flags, pathspec_pos, retval = 0;
+ int flags, pathspec_pos;
+ enum bisect_error res = BISECT_OK;
struct string_list revs = STRING_LIST_INIT_DUP;
struct string_list states = STRING_LIST_INIT_DUP;
struct strbuf start_head = STRBUF_INIT;
@@ -450,11 +630,16 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
break;
} else if (!strcmp(arg, "--no-checkout")) {
no_checkout = 1;
+ } else if (!strcmp(arg, "--first-parent")) {
+ first_parent_only = 1;
} else if (!strcmp(arg, "--term-good") ||
!strcmp(arg, "--term-old")) {
+ i++;
+ if (argc <= i)
+ return error(_("'' is not a valid term"));
must_write_terms = 1;
free((void *) terms->term_good);
- terms->term_good = xstrdup(argv[++i]);
+ terms->term_good = xstrdup(argv[i]);
} else if (skip_prefix(arg, "--term-good=", &arg) ||
skip_prefix(arg, "--term-old=", &arg)) {
must_write_terms = 1;
@@ -462,25 +647,26 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
terms->term_good = xstrdup(arg);
} else if (!strcmp(arg, "--term-bad") ||
!strcmp(arg, "--term-new")) {
+ i++;
+ if (argc <= i)
+ return error(_("'' is not a valid term"));
must_write_terms = 1;
free((void *) terms->term_bad);
- terms->term_bad = xstrdup(argv[++i]);
+ terms->term_bad = xstrdup(argv[i]);
} else if (skip_prefix(arg, "--term-bad=", &arg) ||
skip_prefix(arg, "--term-new=", &arg)) {
must_write_terms = 1;
free((void *) terms->term_bad);
terms->term_bad = xstrdup(arg);
- } else if (starts_with(arg, "--") &&
- !one_of(arg, "--term-good", "--term-bad", NULL)) {
+ } else if (starts_with(arg, "--")) {
return error(_("unrecognized option: '%s'"), arg);
- } else {
- char *commit_id = xstrfmt("%s^{commit}", arg);
- if (get_oid(commit_id, &oid) && has_double_dash)
- die(_("'%s' does not appear to be a valid "
- "revision"), arg);
-
+ } else if (!get_oidf(&oid, "%s^{commit}", arg)) {
string_list_append(&revs, oid_to_hex(&oid));
- free(commit_id);
+ } else if (has_double_dash) {
+ die(_("'%s' does not appear to be a valid "
+ "revision"), arg);
+ } else {
+ break;
}
}
pathspec_pos = i;
@@ -518,12 +704,12 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
strbuf_read_file(&start_head, git_path_bisect_start(), 0);
strbuf_trim(&start_head);
if (!no_checkout) {
- struct argv_array argv = ARGV_ARRAY_INIT;
+ struct strvec argv = STRVEC_INIT;
- argv_array_pushl(&argv, "checkout", start_head.buf,
- "--", NULL);
- if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
- retval = error(_("checking out '%s' failed."
+ strvec_pushl(&argv, "checkout", start_head.buf,
+ "--", NULL);
+ if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
+ res = error(_("checking out '%s' failed."
" Try 'git bisect start "
"<valid-branch>'."),
start_head.buf);
@@ -555,28 +741,24 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
* Get rid of any old bisect state.
*/
if (bisect_clean_state())
- return -1;
-
- /*
- * In case of mistaken revs or checkout error, or signals received,
- * "bisect_auto_next" below may exit or misbehave.
- * We have to trap this to be able to clean up using
- * "bisect_clean_state".
- */
+ return BISECT_FAILED;
/*
* Write new start state
*/
write_file(git_path_bisect_start(), "%s\n", start_head.buf);
+ if (first_parent_only)
+ write_file(git_path_bisect_first_parent(), "\n");
+
if (no_checkout) {
if (get_oid(start_head.buf, &oid) < 0) {
- retval = error(_("invalid ref: '%s'"), start_head.buf);
+ res = error(_("invalid ref: '%s'"), start_head.buf);
goto finish;
}
if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR)) {
- retval = -1;
+ res = BISECT_FAILED;
goto finish;
}
}
@@ -588,52 +770,156 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout,
for (i = 0; i < states.nr; i++)
if (bisect_write(states.items[i].string,
revs.items[i].string, terms, 1)) {
- retval = -1;
+ res = BISECT_FAILED;
goto finish;
}
if (must_write_terms && write_terms(terms->term_bad,
terms->term_good)) {
- retval = -1;
+ res = BISECT_FAILED;
goto finish;
}
- retval = bisect_append_log_quoted(argv);
- if (retval)
- retval = -1;
+ res = bisect_append_log_quoted(argv);
+ if (res)
+ res = BISECT_FAILED;
finish:
string_list_clear(&revs, 0);
string_list_clear(&states, 0);
strbuf_release(&start_head);
strbuf_release(&bisect_names);
- return retval;
+ if (res)
+ return res;
+
+ res = bisect_auto_next(terms, NULL);
+ if (!is_bisect_success(res))
+ bisect_clean_state();
+ return res;
+}
+
+static inline int file_is_not_empty(const char *path)
+{
+ return !is_empty_or_missing_file(path);
+}
+
+static int bisect_autostart(struct bisect_terms *terms)
+{
+ int res;
+ const char *yesno;
+
+ if (file_is_not_empty(git_path_bisect_start()))
+ return 0;
+
+ fprintf_ln(stderr, _("You need to start by \"git bisect "
+ "start\"\n"));
+
+ if (!isatty(STDIN_FILENO))
+ return -1;
+
+ /*
+ * TRANSLATORS: Make sure to include [Y] and [n] in your
+ * translation. The program will only accept English input
+ * at this point.
+ */
+ yesno = git_prompt(_("Do you want me to do it for you "
+ "[Y/n]? "), PROMPT_ECHO);
+ res = tolower(*yesno) == 'n' ?
+ -1 : bisect_start(terms, empty_strvec, 0);
+
+ return res;
+}
+
+static enum bisect_error bisect_state(struct bisect_terms *terms, const char **argv,
+ int argc)
+{
+ const char *state;
+ int i, verify_expected = 1;
+ struct object_id oid, expected;
+ struct strbuf buf = STRBUF_INIT;
+ struct oid_array revs = OID_ARRAY_INIT;
+
+ if (!argc)
+ return error(_("Please call `--bisect-state` with at least one argument"));
+
+ if (bisect_autostart(terms))
+ return BISECT_FAILED;
+
+ state = argv[0];
+ if (check_and_set_terms(terms, state) ||
+ !one_of(state, terms->term_good, terms->term_bad, "skip", NULL))
+ return BISECT_FAILED;
+
+ argv++;
+ argc--;
+ if (argc > 1 && !strcmp(state, terms->term_bad))
+ return error(_("'git bisect %s' can take only one argument."), terms->term_bad);
+
+ if (argc == 0) {
+ const char *head = "BISECT_HEAD";
+ enum get_oid_result res_head = get_oid(head, &oid);
+
+ if (res_head == MISSING_OBJECT) {
+ head = "HEAD";
+ res_head = get_oid(head, &oid);
+ }
+
+ if (res_head)
+ error(_("Bad rev input: %s"), head);
+ oid_array_append(&revs, &oid);
+ }
+
+ /*
+ * All input revs must be checked before executing bisect_write()
+ * to discard junk revs.
+ */
+
+ for (; argc; argc--, argv++) {
+ if (get_oid(*argv, &oid)){
+ error(_("Bad rev input: %s"), *argv);
+ oid_array_clear(&revs);
+ return BISECT_FAILED;
+ }
+ oid_array_append(&revs, &oid);
+ }
+
+ if (strbuf_read_file(&buf, git_path_bisect_expected_rev(), 0) < the_hash_algo->hexsz ||
+ get_oid_hex(buf.buf, &expected) < 0)
+ verify_expected = 0; /* Ignore invalid file contents */
+ strbuf_release(&buf);
+
+ for (i = 0; i < revs.nr; i++) {
+ if (bisect_write(state, oid_to_hex(&revs.oid[i]), terms, 0)) {
+ oid_array_clear(&revs);
+ return BISECT_FAILED;
+ }
+ if (verify_expected && !oideq(&revs.oid[i], &expected)) {
+ unlink_or_warn(git_path_bisect_ancestors_ok());
+ unlink_or_warn(git_path_bisect_expected_rev());
+ verify_expected = 0;
+ }
+ }
+
+ oid_array_clear(&revs);
+ return bisect_auto_next(terms, NULL);
}
int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
{
enum {
- NEXT_ALL = 1,
- WRITE_TERMS,
- BISECT_CLEAN_STATE,
- CHECK_EXPECTED_REVS,
- BISECT_RESET,
+ BISECT_RESET = 1,
BISECT_WRITE,
CHECK_AND_SET_TERMS,
BISECT_NEXT_CHECK,
BISECT_TERMS,
- BISECT_START
+ BISECT_START,
+ BISECT_AUTOSTART,
+ BISECT_NEXT,
+ BISECT_AUTO_NEXT,
+ BISECT_STATE
} cmdmode = 0;
- int no_checkout = 0, res = 0, nolog = 0;
+ int res = 0, nolog = 0;
struct option options[] = {
- OPT_CMDMODE(0, "next-all", &cmdmode,
- N_("perform 'git bisect next'"), NEXT_ALL),
- OPT_CMDMODE(0, "write-terms", &cmdmode,
- N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS),
- OPT_CMDMODE(0, "bisect-clean-state", &cmdmode,
- N_("cleanup the bisection state"), BISECT_CLEAN_STATE),
- OPT_CMDMODE(0, "check-expected-revs", &cmdmode,
- N_("check for expected revs"), CHECK_EXPECTED_REVS),
OPT_CMDMODE(0, "bisect-reset", &cmdmode,
N_("reset the bisection state"), BISECT_RESET),
OPT_CMDMODE(0, "bisect-write", &cmdmode,
@@ -646,8 +932,12 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
N_("print out the bisect terms"), BISECT_TERMS),
OPT_CMDMODE(0, "bisect-start", &cmdmode,
N_("start the bisect session"), BISECT_START),
- OPT_BOOL(0, "no-checkout", &no_checkout,
- N_("update BISECT_HEAD instead of checking out the current commit")),
+ OPT_CMDMODE(0, "bisect-next", &cmdmode,
+ N_("find the next bisection commit"), BISECT_NEXT),
+ OPT_CMDMODE(0, "bisect-auto-next", &cmdmode,
+ N_("verify the next bisection state then checkout the next bisection commit"), BISECT_AUTO_NEXT),
+ OPT_CMDMODE(0, "bisect-state", &cmdmode,
+ N_("mark the state of ref (or refs)"), BISECT_STATE),
OPT_BOOL(0, "no-log", &nolog,
N_("no log for BISECT_WRITE")),
OPT_END()
@@ -662,19 +952,6 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
usage_with_options(git_bisect_helper_usage, options);
switch (cmdmode) {
- case NEXT_ALL:
- return bisect_next_all(the_repository, prefix, no_checkout);
- case WRITE_TERMS:
- if (argc != 2)
- return error(_("--write-terms requires two arguments"));
- return write_terms(argv[0], argv[1]);
- case BISECT_CLEAN_STATE:
- if (argc != 0)
- return error(_("--bisect-clean-state requires no arguments"));
- return bisect_clean_state();
- case CHECK_EXPECTED_REVS:
- check_expected_revs(argv, argc);
- return 0;
case BISECT_RESET:
if (argc > 1)
return error(_("--bisect-reset requires either no argument or a commit"));
@@ -704,11 +981,36 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
break;
case BISECT_START:
set_terms(&terms, "bad", "good");
- res = bisect_start(&terms, no_checkout, argv, argc);
+ res = bisect_start(&terms, argv, argc);
+ break;
+ case BISECT_NEXT:
+ if (argc)
+ return error(_("--bisect-next requires 0 arguments"));
+ get_terms(&terms);
+ res = bisect_next(&terms, prefix);
+ break;
+ case BISECT_AUTO_NEXT:
+ if (argc)
+ return error(_("--bisect-auto-next requires 0 arguments"));
+ get_terms(&terms);
+ res = bisect_auto_next(&terms, prefix);
+ break;
+ case BISECT_STATE:
+ set_terms(&terms, "bad", "good");
+ get_terms(&terms);
+ res = bisect_state(&terms, argv, argc);
break;
default:
- return error("BUG: unknown subcommand '%d'", cmdmode);
+ BUG("unknown subcommand %d", cmdmode);
}
free_terms(&terms);
- return !!res;
+
+ /*
+ * Handle early success
+ * From check_merge_bases > check_good_are_ancestors_of_bad > bisect_next_all
+ */
+ if ((res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) || (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND))
+ res = BISECT_OK;
+
+ return -res;
}
diff --git a/builtin/blame.c b/builtin/blame.c
index 21cde57e71..6f7e32411a 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -26,8 +26,8 @@
#include "progress.h"
#include "object-store.h"
#include "blame.h"
-#include "string-list.h"
#include "refs.h"
+#include "tag.h"
static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
@@ -53,14 +53,17 @@ static int no_whole_file_rename;
static int show_progress;
static char repeated_meta_color[COLOR_MAXLEN];
static int coloring_mode;
+static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;
+static int mark_unblamable_lines;
+static int mark_ignored_lines;
static struct date_mode blame_date_mode = { DATE_ISO8601 };
static size_t blame_date_width;
static struct string_list mailmap = STRING_LIST_INIT_NODUP;
-#ifndef DEBUG
-#define DEBUG 0
+#ifndef DEBUG_BLAME
+#define DEBUG_BLAME 0
#endif
static unsigned blame_move_score;
@@ -317,18 +320,18 @@ static const char *format_time(timestamp_t time, const char *tz_str,
return time_buf.buf;
}
-#define OUTPUT_ANNOTATE_COMPAT 001
-#define OUTPUT_LONG_OBJECT_NAME 002
-#define OUTPUT_RAW_TIMESTAMP 004
-#define OUTPUT_PORCELAIN 010
-#define OUTPUT_SHOW_NAME 020
-#define OUTPUT_SHOW_NUMBER 040
-#define OUTPUT_SHOW_SCORE 0100
-#define OUTPUT_NO_AUTHOR 0200
-#define OUTPUT_SHOW_EMAIL 0400
-#define OUTPUT_LINE_PORCELAIN 01000
-#define OUTPUT_COLOR_LINE 02000
-#define OUTPUT_SHOW_AGE_WITH_COLOR 04000
+#define OUTPUT_ANNOTATE_COMPAT (1U<<0)
+#define OUTPUT_LONG_OBJECT_NAME (1U<<1)
+#define OUTPUT_RAW_TIMESTAMP (1U<<2)
+#define OUTPUT_PORCELAIN (1U<<3)
+#define OUTPUT_SHOW_NAME (1U<<4)
+#define OUTPUT_SHOW_NUMBER (1U<<5)
+#define OUTPUT_SHOW_SCORE (1U<<6)
+#define OUTPUT_NO_AUTHOR (1U<<7)
+#define OUTPUT_SHOW_EMAIL (1U<<8)
+#define OUTPUT_LINE_PORCELAIN (1U<<9)
+#define OUTPUT_COLOR_LINE (1U<<10)
+#define OUTPUT_SHOW_AGE_WITH_COLOR (1U<<11)
static void emit_porcelain_details(struct blame_origin *suspect, int repeat)
{
@@ -457,7 +460,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
for (cnt = 0; cnt < ent->num_lines; cnt++) {
char ch;
- int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;
+ int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? the_hash_algo->hexsz : abbrev;
if (opt & OUTPUT_COLOR_LINE) {
if (cnt > 0) {
@@ -480,6 +483,14 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
}
}
+ if (mark_unblamable_lines && ent->unblamable) {
+ length--;
+ putchar('*');
+ }
+ if (mark_ignored_lines && ent->ignored) {
+ length--;
+ putchar('?');
+ }
printf("%.*s", length, hex);
if (opt & OUTPUT_ANNOTATE_COMPAT) {
const char *name;
@@ -696,6 +707,24 @@ static int git_blame_config(const char *var, const char *value, void *cb)
parse_date_format(value, &blame_date_mode);
return 0;
}
+ if (!strcmp(var, "blame.ignorerevsfile")) {
+ const char *str;
+ int ret;
+
+ ret = git_config_pathname(&str, var, value);
+ if (ret)
+ return ret;
+ string_list_insert(&ignore_revs_file_list, str);
+ return 0;
+ }
+ if (!strcmp(var, "blame.markunblamablelines")) {
+ mark_unblamable_lines = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "blame.markignoredlines")) {
+ mark_ignored_lines = git_config_bool(var, value);
+ return 0;
+ }
if (!strcmp(var, "color.blame.repeatedlines")) {
if (color_parse_mem(value, strlen(value), repeated_meta_color))
warning(_("invalid color '%s' in color.blame.repeatedLines"),
@@ -775,6 +804,51 @@ static int is_a_rev(const char *name)
return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
}
+static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata)
+{
+ struct repository *r = ((struct blame_scoreboard *)cbdata)->repo;
+ struct object_id oid;
+
+ oidcpy(&oid, oid_ret);
+ while (1) {
+ struct object *obj;
+ int kind = oid_object_info(r, &oid, NULL);
+ if (kind == OBJ_COMMIT) {
+ oidcpy(oid_ret, &oid);
+ return 0;
+ }
+ if (kind != OBJ_TAG)
+ return -1;
+ obj = deref_tag(r, parse_object(r, &oid), NULL, 0);
+ if (!obj)
+ return -1;
+ oidcpy(&oid, &obj->oid);
+ }
+}
+
+static void build_ignorelist(struct blame_scoreboard *sb,
+ struct string_list *ignore_revs_file_list,
+ struct string_list *ignore_rev_list)
+{
+ struct string_list_item *i;
+ struct object_id oid;
+
+ oidset_init(&sb->ignore_list, 0);
+ for_each_string_list_item(i, ignore_revs_file_list) {
+ if (!strcmp(i->string, ""))
+ oidset_clear(&sb->ignore_list);
+ else
+ oidset_parse_file_carefully(&sb->ignore_list, i->string,
+ peel_to_commit_oid, sb);
+ }
+ for_each_string_list_item(i, ignore_rev_list) {
+ if (get_oid_committish(i->string, &oid) ||
+ peel_to_commit_oid(&oid, sb))
+ die(_("cannot find revision %s to ignore"), i->string);
+ oidset_insert(&sb->ignore_list, &oid);
+ }
+}
+
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -786,13 +860,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
struct progress_info pi = { NULL, 0 };
struct string_list range_list = STRING_LIST_INIT_NODUP;
+ struct string_list ignore_rev_list = STRING_LIST_INIT_NODUP;
int output_option = 0, opt = 0;
int show_stats = 0;
const char *revs_file = NULL;
const char *contents_from = NULL;
const struct option options[] = {
OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
- OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
+ OPT_BOOL('b', NULL, &blank_boundary, N_("Do not show object names of boundary commits (Default: off)")),
OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")),
OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")),
@@ -807,22 +882,17 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
+ OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore <rev> when blaming")),
+ OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")),
OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
-
- /*
- * The following two options are parsed by parse_revision_opt()
- * and are only included here to get included in the "-h"
- * output:
- */
- { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, NULL, 0, parse_opt_unknown_cb },
-
OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),
OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
- { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
- { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
- OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
+ OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback),
+ OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback),
+ OPT_STRING_LIST('L', NULL, &range_list, N_("range"),
+ N_("Process only line range <start>,<end> or function :<funcname>")),
OPT__ABBREV(&abbrev),
OPT_END()
};
@@ -832,6 +902,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
struct range_set ranges;
unsigned int range_i;
long anchor;
+ const int hexsz = the_hash_algo->hexsz;
setup_default_color_by_age();
git_config(git_blame_config, &output_option);
@@ -878,11 +949,11 @@ parse_done:
} else if (show_progress < 0)
show_progress = isatty(2);
- if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ)
+ if (0 < abbrev && abbrev < hexsz)
/* one more abbrev length is needed for the boundary commit */
abbrev++;
else if (!abbrev)
- abbrev = GIT_SHA1_HEXSZ;
+ abbrev = hexsz;
if (revs_file && read_ancestry(revs_file))
die_errno("reading graft file '%s' failed", revs_file);
@@ -1012,7 +1083,19 @@ parse_done:
sb.contents_from = contents_from;
sb.reverse = reverse;
sb.repo = the_repository;
- setup_scoreboard(&sb, path, &o);
+ sb.path = path;
+ build_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list);
+ string_list_clear(&ignore_revs_file_list, 0);
+ string_list_clear(&ignore_rev_list, 0);
+ setup_scoreboard(&sb, &o);
+
+ /*
+ * Changed-path Bloom filters are disabled when looking
+ * for copies.
+ */
+ if (!(opt & PICKAXE_BLAME_COPY))
+ setup_blame_bloom_data(&sb);
+
lno = sb.num_lines;
if (lno && !range_list.nr)
@@ -1030,7 +1113,7 @@ parse_done:
if ((!lno && (top || bottom)) || lno < bottom)
die(Q_("file %s has only %lu line",
"file %s has only %lu lines",
- lno), path, lno);
+ lno), sb.path, lno);
if (bottom < 1)
bottom = 1;
if (top < 1 || lno < top)
@@ -1055,14 +1138,13 @@ parse_done:
string_list_clear(&range_list, 0);
sb.ent = NULL;
- sb.path = path;
if (blame_move_score)
sb.move_score = blame_move_score;
if (blame_copy_score)
sb.copy_score = blame_copy_score;
- sb.debug = DEBUG;
+ sb.debug = DEBUG_BLAME;
sb.on_sanity_fail = &sanity_check_on_fail;
sb.show_root = show_root;
@@ -1116,5 +1198,7 @@ parse_done:
printf("num get patch: %d\n", sb.num_get_patch);
printf("num commits: %d\n", sb.num_commits);
}
+
+ cleanup_scoreboard(&sb);
return 0;
}
diff --git a/builtin/branch.c b/builtin/branch.c
index 8e243cf8c2..efb30b8820 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -26,7 +26,7 @@
#include "commit-reach.h"
static const char * const builtin_branch_usage[] = {
- N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
+ N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
@@ -47,6 +47,7 @@ static char branch_colors[][COLOR_MAXLEN] = {
GIT_COLOR_NORMAL, /* LOCAL */
GIT_COLOR_GREEN, /* CURRENT */
GIT_COLOR_BLUE, /* UPSTREAM */
+ GIT_COLOR_CYAN, /* WORKTREE */
};
enum color_branch {
BRANCH_COLOR_RESET = 0,
@@ -54,7 +55,8 @@ enum color_branch {
BRANCH_COLOR_REMOTE = 2,
BRANCH_COLOR_LOCAL = 3,
BRANCH_COLOR_CURRENT = 4,
- BRANCH_COLOR_UPSTREAM = 5
+ BRANCH_COLOR_UPSTREAM = 5,
+ BRANCH_COLOR_WORKTREE = 6
};
static const char *color_branch_slots[] = {
@@ -64,6 +66,7 @@ static const char *color_branch_slots[] = {
[BRANCH_COLOR_LOCAL] = "local",
[BRANCH_COLOR_CURRENT] = "current",
[BRANCH_COLOR_UPSTREAM] = "upstream",
+ [BRANCH_COLOR_WORKTREE] = "worktree",
};
static struct string_list output = STRING_LIST_INIT_DUP;
@@ -342,9 +345,10 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r
struct strbuf local = STRBUF_INIT;
struct strbuf remote = STRBUF_INIT;
- strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else) %s%%(end)",
- branch_get_color(BRANCH_COLOR_CURRENT),
- branch_get_color(BRANCH_COLOR_LOCAL));
+ strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else)%%(if)%%(worktreepath)%%(then)+ %s%%(else) %s%%(end)%%(end)",
+ branch_get_color(BRANCH_COLOR_CURRENT),
+ branch_get_color(BRANCH_COLOR_WORKTREE),
+ branch_get_color(BRANCH_COLOR_LOCAL));
strbuf_addf(&remote, " %s",
branch_get_color(BRANCH_COLOR_REMOTE));
@@ -363,9 +367,13 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r
strbuf_addf(&local, " %s ", obname.buf);
if (filter->verbose > 1)
+ {
+ strbuf_addf(&local, "%%(if:notequals=*)%%(HEAD)%%(then)%%(if)%%(worktreepath)%%(then)(%s%%(worktreepath)%s) %%(end)%%(end)",
+ branch_get_color(BRANCH_COLOR_WORKTREE), branch_get_color(BRANCH_COLOR_RESET));
strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)"
"%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)",
branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET));
+ }
else
strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
@@ -460,7 +468,7 @@ static void print_current_branch_name(void)
static void reject_rebase_or_bisect_branch(const char *target)
{
- struct worktree **worktrees = get_worktrees(0);
+ struct worktree **worktrees = get_worktrees();
int i;
for (i = 0; worktrees[i]; i++) {
@@ -616,7 +624,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
- OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")),
+ OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("unset the upstream info")),
OPT__COLOR(&branch_use_color, N_("use colored output")),
OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"),
FILTER_REFS_REMOTES),
@@ -645,10 +653,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
OPT_REF_SORT(sorting_tail),
- {
- OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
- N_("print only branches of the object"), 0, parse_opt_object_name
- },
+ OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"),
+ N_("print only branches of the object"), parse_opt_object_name),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
@@ -682,12 +688,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
!show_current && !unset_upstream && argc == 0)
list = 1;
- if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
- filter.no_commit)
+ if (filter.with_commit || filter.no_commit ||
+ filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
list = 1;
if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
- list + unset_upstream > 1)
+ list + edit_description + unset_upstream > 1)
usage_with_options(builtin_branch_usage, options);
if (filter.abbrev == -1)
@@ -731,7 +737,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
*/
if (!sorting)
sorting = ref_default_sorting();
- sorting->ignore_case = icase;
+ ref_sorting_icase_all(sorting, icase);
print_ref_list(&filter, sorting, &format);
print_columns(&output, colopts, NULL);
string_list_clear(&output, 0);
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
new file mode 100644
index 0000000000..3ad4b9b62e
--- /dev/null
+++ b/builtin/bugreport.c
@@ -0,0 +1,194 @@
+#include "builtin.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "help.h"
+#include "compat/compiler.h"
+#include "run-command.h"
+
+
+static void get_system_info(struct strbuf *sys_info)
+{
+ struct utsname uname_info;
+ char *shell = NULL;
+
+ /* get git version from native cmd */
+ strbuf_addstr(sys_info, _("git version:\n"));
+ get_version_info(sys_info, 1);
+
+ /* system call for other version info */
+ strbuf_addstr(sys_info, "uname: ");
+ if (uname(&uname_info))
+ strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"),
+ strerror(errno),
+ errno);
+ else
+ strbuf_addf(sys_info, "%s %s %s %s\n",
+ uname_info.sysname,
+ uname_info.release,
+ uname_info.version,
+ uname_info.machine);
+
+ strbuf_addstr(sys_info, _("compiler info: "));
+ get_compiler_info(sys_info);
+
+ strbuf_addstr(sys_info, _("libc info: "));
+ get_libc_info(sys_info);
+
+ shell = getenv("SHELL");
+ strbuf_addf(sys_info, "$SHELL (typically, interactive shell): %s\n",
+ shell ? shell : "<unset>");
+}
+
+static void get_populated_hooks(struct strbuf *hook_info, int nongit)
+{
+ /*
+ * NEEDSWORK: Doesn't look like there is a list of all possible hooks;
+ * so below is a transcription of `git help hooks`. Later, this should
+ * be replaced with some programmatically generated list (generated from
+ * doc or else taken from some library which tells us about all the
+ * hooks)
+ */
+ static const char *hook[] = {
+ "applypatch-msg",
+ "pre-applypatch",
+ "post-applypatch",
+ "pre-commit",
+ "pre-merge-commit",
+ "prepare-commit-msg",
+ "commit-msg",
+ "post-commit",
+ "pre-rebase",
+ "post-checkout",
+ "post-merge",
+ "pre-push",
+ "pre-receive",
+ "update",
+ "post-receive",
+ "post-update",
+ "push-to-checkout",
+ "pre-auto-gc",
+ "post-rewrite",
+ "sendemail-validate",
+ "fsmonitor-watchman",
+ "p4-pre-submit",
+ "post-index-change",
+ };
+ int i;
+
+ if (nongit) {
+ strbuf_addstr(hook_info,
+ _("not run from a git repository - no hooks to show\n"));
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(hook); i++)
+ if (find_hook(hook[i]))
+ strbuf_addf(hook_info, "%s\n", hook[i]);
+}
+
+static const char * const bugreport_usage[] = {
+ N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>]"),
+ NULL
+};
+
+static int get_bug_template(struct strbuf *template)
+{
+ const char template_text[] = N_(
+"Thank you for filling out a Git bug report!\n"
+"Please answer the following questions to help us understand your issue.\n"
+"\n"
+"What did you do before the bug happened? (Steps to reproduce your issue)\n"
+"\n"
+"What did you expect to happen? (Expected behavior)\n"
+"\n"
+"What happened instead? (Actual behavior)\n"
+"\n"
+"What's different between what you expected and what actually happened?\n"
+"\n"
+"Anything else you want to add:\n"
+"\n"
+"Please review the rest of the bug report below.\n"
+"You can delete any lines you don't wish to share.\n");
+
+ strbuf_addstr(template, _(template_text));
+ return 0;
+}
+
+static void get_header(struct strbuf *buf, const char *title)
+{
+ strbuf_addf(buf, "\n\n[%s]\n", title);
+}
+
+int cmd_bugreport(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf buffer = STRBUF_INIT;
+ struct strbuf report_path = STRBUF_INIT;
+ int report = -1;
+ time_t now = time(NULL);
+ char *option_output = NULL;
+ char *option_suffix = "%Y-%m-%d-%H%M";
+ const char *user_relative_path = NULL;
+
+ const struct option bugreport_options[] = {
+ OPT_STRING('o', "output-directory", &option_output, N_("path"),
+ N_("specify a destination for the bugreport file")),
+ OPT_STRING('s', "suffix", &option_suffix, N_("format"),
+ N_("specify a strftime format suffix for the filename")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, bugreport_options,
+ bugreport_usage, 0);
+
+ /* Prepare the path to put the result */
+ strbuf_addstr(&report_path,
+ prefix_filename(prefix,
+ option_output ? option_output : ""));
+ strbuf_complete(&report_path, '/');
+
+ strbuf_addstr(&report_path, "git-bugreport-");
+ strbuf_addftime(&report_path, option_suffix, localtime(&now), 0, 0);
+ strbuf_addstr(&report_path, ".txt");
+
+ switch (safe_create_leading_directories(report_path.buf)) {
+ case SCLD_OK:
+ case SCLD_EXISTS:
+ break;
+ default:
+ die(_("could not create leading directories for '%s'"),
+ report_path.buf);
+ }
+
+ /* Prepare the report contents */
+ get_bug_template(&buffer);
+
+ get_header(&buffer, _("System Info"));
+ get_system_info(&buffer);
+
+ get_header(&buffer, _("Enabled Hooks"));
+ get_populated_hooks(&buffer, !startup_info->have_repository);
+
+ /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */
+ report = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666);
+
+ if (report < 0)
+ die(_("couldn't create a new file at '%s'"), report_path.buf);
+
+ if (write_in_full(report, buffer.buf, buffer.len) < 0)
+ die_errno(_("unable to write to %s"), report_path.buf);
+
+ close(report);
+
+ /*
+ * We want to print the path relative to the user, but we still need the
+ * path relative to us to give to the editor.
+ */
+ if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path)))
+ user_relative_path = report_path.buf;
+ fprintf(stderr, _("Created new report at '%s'.\n"),
+ user_relative_path);
+
+ UNLEAK(buffer);
+ UNLEAK(report_path);
+ return !!launch_editor(report_path.buf, NULL, NULL);
+}
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 1ea4bfdfc1..ea6948110b 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -1,4 +1,6 @@
#include "builtin.h"
+#include "strvec.h"
+#include "parse-options.h"
#include "cache.h"
#include "bundle.h"
@@ -9,59 +11,187 @@
* bundle supporting "fetch", "pull", and "ls-remote".
*/
-static const char builtin_bundle_usage[] =
- "git bundle create <file> <git-rev-list args>\n"
- " or: git bundle verify <file>\n"
- " or: git bundle list-heads <file> [<refname>...]\n"
- " or: git bundle unbundle <file> [<refname>...]";
+static const char * const builtin_bundle_usage[] = {
+ N_("git bundle create [<options>] <file> <git-rev-list args>"),
+ N_("git bundle verify [<options>] <file>"),
+ N_("git bundle list-heads <file> [<refname>...]"),
+ N_("git bundle unbundle <file> [<refname>...]"),
+ NULL
+};
-int cmd_bundle(int argc, const char **argv, const char *prefix)
-{
+static const char * const builtin_bundle_create_usage[] = {
+ N_("git bundle create [<options>] <file> <git-rev-list args>"),
+ NULL
+};
+
+static const char * const builtin_bundle_verify_usage[] = {
+ N_("git bundle verify [<options>] <file>"),
+ NULL
+};
+
+static const char * const builtin_bundle_list_heads_usage[] = {
+ N_("git bundle list-heads <file> [<refname>...]"),
+ NULL
+};
+
+static const char * const builtin_bundle_unbundle_usage[] = {
+ N_("git bundle unbundle <file> [<refname>...]"),
+ NULL
+};
+
+static int verbose;
+
+static int parse_options_cmd_bundle(int argc,
+ const char **argv,
+ const char* prefix,
+ const char * const usagestr[],
+ const struct option options[],
+ const char **bundle_file) {
+ int newargc;
+ newargc = parse_options(argc, argv, NULL, options, usagestr,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ if (argc < 1)
+ usage_with_options(usagestr, options);
+ *bundle_file = prefix_filename(prefix, argv[0]);
+ return newargc;
+}
+
+static int cmd_bundle_create(int argc, const char **argv, const char *prefix) {
+ int all_progress_implied = 0;
+ int progress = isatty(STDERR_FILENO);
+ struct strvec pack_opts;
+ int version = -1;
+
+ struct option options[] = {
+ OPT_SET_INT('q', "quiet", &progress,
+ N_("do not show progress meter"), 0),
+ OPT_SET_INT(0, "progress", &progress,
+ N_("show progress meter"), 1),
+ OPT_SET_INT(0, "all-progress", &progress,
+ N_("show progress meter during object writing phase"), 2),
+ OPT_BOOL(0, "all-progress-implied",
+ &all_progress_implied,
+ N_("similar to --all-progress when progress meter is shown")),
+ OPT_INTEGER(0, "version", &version,
+ N_("specify bundle format version")),
+ OPT_END()
+ };
+ const char* bundle_file;
+
+ argc = parse_options_cmd_bundle(argc, argv, prefix,
+ builtin_bundle_create_usage, options, &bundle_file);
+ /* bundle internals use argv[1] as further parameters */
+
+ strvec_init(&pack_opts);
+ if (progress == 0)
+ strvec_push(&pack_opts, "--quiet");
+ else if (progress == 1)
+ strvec_push(&pack_opts, "--progress");
+ else if (progress == 2)
+ strvec_push(&pack_opts, "--all-progress");
+ if (progress && all_progress_implied)
+ strvec_push(&pack_opts, "--all-progress-implied");
+
+ if (!startup_info->have_repository)
+ die(_("Need a repository to create a bundle."));
+ return !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version);
+}
+
+static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) {
struct bundle_header header;
- const char *cmd, *bundle_file;
int bundle_fd = -1;
+ int quiet = 0;
- if (argc < 3)
- usage(builtin_bundle_usage);
+ struct option options[] = {
+ OPT_BOOL('q', "quiet", &quiet,
+ N_("do not show bundle details")),
+ OPT_END()
+ };
+ const char* bundle_file;
- cmd = argv[1];
- bundle_file = prefix_filename(prefix, argv[2]);
- argc -= 2;
- argv += 2;
+ argc = parse_options_cmd_bundle(argc, argv, prefix,
+ builtin_bundle_verify_usage, options, &bundle_file);
+ /* bundle internals use argv[1] as further parameters */
memset(&header, 0, sizeof(header));
- if (strcmp(cmd, "create") && (bundle_fd =
- read_bundle_header(bundle_file, &header)) < 0)
+ if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0)
+ return 1;
+ close(bundle_fd);
+ if (verify_bundle(the_repository, &header, !quiet))
return 1;
+ fprintf(stderr, _("%s is okay\n"), bundle_file);
+ return 0;
+}
- if (!strcmp(cmd, "verify")) {
- close(bundle_fd);
- if (argc != 1) {
- usage(builtin_bundle_usage);
- return 1;
- }
- if (verify_bundle(the_repository, &header, 1))
- return 1;
- fprintf(stderr, _("%s is okay\n"), bundle_file);
- return 0;
- }
- if (!strcmp(cmd, "list-heads")) {
- close(bundle_fd);
- return !!list_bundle_refs(&header, argc, argv);
+static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix) {
+ struct bundle_header header;
+ int bundle_fd = -1;
+
+ struct option options[] = {
+ OPT_END()
+ };
+ const char* bundle_file;
+
+ argc = parse_options_cmd_bundle(argc, argv, prefix,
+ builtin_bundle_list_heads_usage, options, &bundle_file);
+ /* bundle internals use argv[1] as further parameters */
+
+ memset(&header, 0, sizeof(header));
+ if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0)
+ return 1;
+ close(bundle_fd);
+ return !!list_bundle_refs(&header, argc, argv);
+}
+
+static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) {
+ struct bundle_header header;
+ int bundle_fd = -1;
+
+ struct option options[] = {
+ OPT_END()
+ };
+ const char* bundle_file;
+
+ argc = parse_options_cmd_bundle(argc, argv, prefix,
+ builtin_bundle_unbundle_usage, options, &bundle_file);
+ /* bundle internals use argv[1] as further parameters */
+
+ memset(&header, 0, sizeof(header));
+ if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0)
+ return 1;
+ if (!startup_info->have_repository)
+ die(_("Need a repository to unbundle."));
+ return !!unbundle(the_repository, &header, bundle_fd, 0) ||
+ list_bundle_refs(&header, argc, argv);
+}
+
+int cmd_bundle(int argc, const char **argv, const char *prefix)
+{
+ struct option options[] = {
+ OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")),
+ OPT_END()
+ };
+ int result;
+
+ argc = parse_options(argc, argv, prefix, options, builtin_bundle_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ packet_trace_identity("bundle");
+
+ if (argc < 2)
+ usage_with_options(builtin_bundle_usage, options);
+
+ else if (!strcmp(argv[0], "create"))
+ result = cmd_bundle_create(argc, argv, prefix);
+ else if (!strcmp(argv[0], "verify"))
+ result = cmd_bundle_verify(argc, argv, prefix);
+ else if (!strcmp(argv[0], "list-heads"))
+ result = cmd_bundle_list_heads(argc, argv, prefix);
+ else if (!strcmp(argv[0], "unbundle"))
+ result = cmd_bundle_unbundle(argc, argv, prefix);
+ else {
+ error(_("Unknown subcommand: %s"), argv[0]);
+ usage_with_options(builtin_bundle_usage, options);
}
- 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(the_repository, bundle_file, argc, argv);
- } else if (!strcmp(cmd, "unbundle")) {
- if (!startup_info->have_repository)
- die(_("Need a repository to unbundle."));
- return !!unbundle(the_repository, &header, bundle_fd, 0) ||
- list_bundle_refs(&header, argc, argv);
- } else
- usage(builtin_bundle_usage);
+ return result ? 1 : 0;
}
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 0f092382e1..5ebf13359e 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -12,9 +12,10 @@
#include "userdiff.h"
#include "streaming.h"
#include "tree-walk.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "packfile.h"
#include "object-store.h"
+#include "promisor-remote.h"
struct batch_options {
int enabled;
@@ -41,7 +42,10 @@ static int filter_object(const char *path, unsigned mode,
oid_to_hex(oid), path);
if ((type == OBJ_BLOB) && S_ISREG(mode)) {
struct strbuf strbuf = STRBUF_INIT;
- if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) {
+ struct checkout_metadata meta;
+
+ init_checkout_metadata(&meta, NULL, NULL, oid);
+ if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) {
free(*buf);
*size = strbuf.len;
*buf = strbuf_detach(&strbuf, NULL);
@@ -172,7 +176,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
* fall-back to the usual case.
*/
}
- buf = read_object_with_reference(&oid, exp_type, &size, NULL);
+ buf = read_object_with_reference(the_repository,
+ &oid, exp_type, &size, NULL);
break;
default:
@@ -260,7 +265,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
strbuf_addstr(sb, data->rest);
} else if (is_atom("deltabase", atom, len)) {
if (data->mark_query)
- data->info.delta_base_sha1 = data->delta_base_oid.hash;
+ data->info.delta_base_oid = &data->delta_base_oid;
else
strbuf_addstr(sb,
oid_to_hex(&data->delta_base_oid));
@@ -523,8 +528,8 @@ static int batch_objects(struct batch_options *opt)
if (opt->all_objects) {
struct object_cb_data cb;
- if (repository_format_partial_clone)
- warning("This repository has extensions.partialClone set. Some objects may not be loaded.");
+ if (has_promisor_remote())
+ warning("This repository uses promisor remotes. Some objects may not be loaded.");
cb.opt = opt;
cb.expand = &data;
@@ -591,7 +596,7 @@ static int batch_objects(struct batch_options *opt)
static const char * const cat_file_usage[] = {
N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"),
- N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"),
+ N_("git cat-file (--batch[=<format>] | --batch-check[=<format>]) [--follow-symlinks] [--textconv | --filters]"),
NULL
};
@@ -645,14 +650,14 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
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",
+ OPT_CALLBACK_F(0, "batch", &batch, "format",
N_("show info and content of objects fed from the standard input"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
- batch_option_callback },
- { OPTION_CALLBACK, 0, "batch-check", &batch, "format",
+ batch_option_callback),
+ OPT_CALLBACK_F(0, "batch-check", &batch, "format",
N_("show info about objects fed from the standard input"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
- batch_option_callback },
+ 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,
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 599097304b..3c652748d5 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -32,19 +32,19 @@ static const struct option check_ignore_options[] = {
OPT_END()
};
-static void output_exclude(const char *path, struct exclude *exclude)
+static void output_pattern(const char *path, struct path_pattern *pattern)
{
- char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : "";
- char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
+ char *bang = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE) ? "!" : "";
+ char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : "";
if (!nul_term_line) {
if (!verbose) {
write_name_quoted(path, stdout, '\n');
} else {
- if (exclude) {
- quote_c_style(exclude->el->src, NULL, stdout, 0);
+ if (pattern) {
+ quote_c_style(pattern->pl->src, NULL, stdout, 0);
printf(":%d:%s%s%s\t",
- exclude->srcpos,
- bang, exclude->pattern, slash);
+ pattern->srcpos,
+ bang, pattern->pattern, slash);
}
else {
printf("::\t");
@@ -56,11 +56,11 @@ static void output_exclude(const char *path, struct exclude *exclude)
if (!verbose) {
printf("%s%c", path, '\0');
} else {
- if (exclude)
+ if (pattern)
printf("%s%c%d%c%s%s%s%c%s%c",
- exclude->el->src, '\0',
- exclude->srcpos, '\0',
- bang, exclude->pattern, slash, '\0',
+ pattern->pl->src, '\0',
+ pattern->srcpos, '\0',
+ bang, pattern->pattern, slash, '\0',
path, '\0');
else
printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0');
@@ -74,7 +74,7 @@ static int check_ignore(struct dir_struct *dir,
const char *full_path;
char *seen;
int num_ignored = 0, i;
- struct exclude *exclude;
+ struct path_pattern *pattern;
struct pathspec pathspec;
if (!argc) {
@@ -103,15 +103,18 @@ static int check_ignore(struct dir_struct *dir,
seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
for (i = 0; i < pathspec.nr; i++) {
full_path = pathspec.items[i].match;
- exclude = NULL;
+ pattern = NULL;
if (!seen[i]) {
int dtype = DT_UNKNOWN;
- exclude = last_exclude_matching(dir, &the_index,
+ pattern = last_matching_pattern(dir, &the_index,
full_path, &dtype);
+ if (!verbose && pattern &&
+ pattern->flags & PATTERN_FLAG_NEGATIVE)
+ pattern = NULL;
}
- if (!quiet && (exclude || show_non_matching))
- output_exclude(pathspec.items[i].original, exclude);
- if (exclude)
+ if (!quiet && (pattern || show_non_matching))
+ output_pattern(pathspec.items[i].original, pattern);
+ if (pattern)
num_ignored++;
}
free(seen);
@@ -177,7 +180,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
if (!no_index && read_cache() < 0)
die(_("index file corrupt"));
- memset(&dir, 0, sizeof(dir));
+ dir_init(&dir);
setup_standard_excludes(&dir);
if (stdin_paths) {
@@ -187,7 +190,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
maybe_flush_or_die(stdout, "ignore to stdout");
}
- clear_directory(&dir);
+ dir_clear(&dir);
return !num_ignored;
}
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index 1ac1cc290e..4bbfc92dce 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -79,6 +79,14 @@ static int checkout_file(const char *name, const char *prefix)
return errs > 0 ? -1 : 0;
}
+ /*
+ * At this point we know we didn't try to check anything out. If it was
+ * because we did find an entry but it was stage 0, that's not an
+ * error.
+ */
+ if (has_same_name && checkout_stage == CHECKOUT_ALL)
+ return 0;
+
if (!state.quiet) {
fprintf(stderr, "git checkout-index: %s ", name);
if (!has_same_name)
@@ -159,6 +167,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
int prefix_length;
int force = 0, quiet = 0, not_new = 0;
int index_opt = 0;
+ int err = 0;
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
@@ -177,9 +186,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
N_("write the content to temporary files")),
OPT_STRING(0, "prefix", &state.base_dir, N_("string"),
N_("when creating files, prepend <string>")),
- { OPTION_CALLBACK, 0, "stage", NULL, "(1|2|3|all)",
+ OPT_CALLBACK_F(0, "stage", NULL, "(1|2|3|all)",
N_("copy out the files from named stage"),
- PARSE_OPT_NONEG, option_parse_stage },
+ PARSE_OPT_NONEG, option_parse_stage),
OPT_END()
};
@@ -223,7 +232,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
if (read_from_stdin)
die("git checkout-index: don't mix '--stdin' and explicit filenames");
p = prefix_path(prefix, prefix_length, arg);
- checkout_file(p, prefix);
+ err |= checkout_file(p, prefix);
free(p);
}
@@ -245,13 +254,16 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
strbuf_swap(&buf, &unquoted);
}
p = prefix_path(prefix, prefix_length, buf.buf);
- checkout_file(p, prefix);
+ err |= checkout_file(p, prefix);
free(p);
}
strbuf_release(&unquoted);
strbuf_release(&buf);
}
+ if (err)
+ return 1;
+
if (all)
checkout_all(prefix, prefix_length);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index ffa776c6e1..9b82119129 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1,32 +1,31 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
-#include "config.h"
+#include "advice.h"
+#include "blob.h"
+#include "branch.h"
+#include "cache-tree.h"
#include "checkout.h"
+#include "commit.h"
+#include "config.h"
+#include "diff.h"
+#include "dir.h"
+#include "ll-merge.h"
#include "lockfile.h"
+#include "merge-recursive.h"
+#include "object-store.h"
#include "parse-options.h"
#include "refs.h"
-#include "object-store.h"
-#include "commit.h"
+#include "remote.h"
+#include "resolve-undo.h"
+#include "revision.h"
+#include "run-command.h"
+#include "submodule.h"
+#include "submodule-config.h"
#include "tree.h"
#include "tree-walk.h"
-#include "cache-tree.h"
#include "unpack-trees.h"
-#include "dir.h"
-#include "run-command.h"
-#include "merge-recursive.h"
-#include "branch.h"
-#include "diff.h"
-#include "revision.h"
-#include "remote.h"
-#include "blob.h"
+#include "wt-status.h"
#include "xdiff-interface.h"
-#include "ll-merge.h"
-#include "resolve-undo.h"
-#include "submodule-config.h"
-#include "submodule.h"
-#include "advice.h"
-
-static int checkout_optimize_new_branch;
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
@@ -34,12 +33,23 @@ static const char * const checkout_usage[] = {
NULL,
};
+static const char * const switch_branch_usage[] = {
+ N_("git switch [<options>] [<branch>]"),
+ NULL,
+};
+
+static const char * const restore_usage[] = {
+ N_("git restore [<options>] [--source=<branch>] <file>..."),
+ NULL,
+};
+
struct checkout_opts {
int patch_mode;
int quiet;
int merge;
int force;
int force_detach;
+ int implicit_detach;
int writeout_stage;
int overwrite_ignore;
int ignore_skipworktree;
@@ -47,10 +57,21 @@ struct checkout_opts {
int show_progress;
int count_checkout_paths;
int overlay_mode;
- /*
- * If new checkout options are added, skip_merge_working_tree
- * should be updated accordingly.
- */
+ int dwim_new_local_branch;
+ int discard_changes;
+ int accept_ref;
+ int accept_pathspec;
+ int switch_branch_doing_nothing_is_ok;
+ int only_merge_on_switching_branches;
+ int can_switch_when_in_progress;
+ int orphan_from_empty_tree;
+ int empty_pathspec_ok;
+ int checkout_index;
+ int checkout_worktree;
+ const char *ignore_unmerged_opt;
+ int ignore_unmerged;
+ int pathspec_file_nul;
+ const char *pathspec_from_file;
const char *new_branch;
const char *new_branch_force;
@@ -58,13 +79,28 @@ struct checkout_opts {
int new_branch_log;
enum branch_track track;
struct diff_options diff_options;
+ char *conflict_style;
int branch_exists;
const char *prefix;
struct pathspec pathspec;
+ const char *from_treeish;
struct tree *source_tree;
};
+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 */
+ char *refname; /* The full name of the ref being checked out. */
+ struct object_id oid; /* The object ID of the commit being checked out. */
+ /*
+ * if not null the branch is detached because it's already
+ * checked out in this checkout
+ */
+ char *checkout;
+};
+
static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
int changed)
{
@@ -105,6 +141,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base,
if (pos >= 0) {
struct cache_entry *old = active_cache[pos];
if (ce->ce_mode == old->ce_mode &&
+ !ce_intent_to_add(old) &&
oideq(&ce->oid, &old->oid)) {
old->ce_flags |= CE_UPDATE;
discard_cache_entry(ce);
@@ -202,6 +239,8 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko
mmbuffer_t result_buf;
struct object_id threeway[3];
unsigned mode = 0;
+ struct ll_merge_options ll_opts;
+ int renormalize = 0;
memset(threeway, 0, sizeof(threeway));
while (pos < active_nr) {
@@ -222,13 +261,12 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko
read_mmblob(&ours, &threeway[1]);
read_mmblob(&theirs, &threeway[2]);
- /*
- * NEEDSWORK: re-create conflicts from merges with
- * merge.renormalize set, too
- */
+ memset(&ll_opts, 0, sizeof(ll_opts));
+ git_config_get_bool("merge.renormalize", &renormalize);
+ ll_opts.renormalize = renormalize;
status = ll_merge(&result_buf, path, &ancestor, "base",
&ours, "ours", &theirs, "theirs",
- state->istate, NULL);
+ state->istate, &ll_opts);
free(ancestor.ptr);
free(ours.ptr);
free(theirs.ptr);
@@ -313,17 +351,79 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce,
}
}
+static int checkout_worktree(const struct checkout_opts *opts,
+ const struct branch_info *info)
+{
+ struct checkout state = CHECKOUT_INIT;
+ int nr_checkouts = 0, nr_unmerged = 0;
+ int errs = 0;
+ int pos;
+
+ state.force = 1;
+ state.refresh_cache = 1;
+ state.istate = &the_index;
+
+ init_checkout_metadata(&state.meta, info->refname,
+ info->commit ? &info->commit->object.oid : &info->oid,
+ NULL);
+
+ enable_delayed_checkout(&state);
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ if (ce->ce_flags & CE_MATCHED) {
+ if (!ce_stage(ce)) {
+ errs |= checkout_entry(ce, &state,
+ NULL, &nr_checkouts);
+ continue;
+ }
+ if (opts->writeout_stage)
+ errs |= checkout_stage(opts->writeout_stage,
+ ce, pos,
+ &state,
+ &nr_checkouts, opts->overlay_mode);
+ else if (opts->merge)
+ errs |= checkout_merged(pos, &state,
+ &nr_unmerged);
+ pos = skip_same_name(ce, pos) - 1;
+ }
+ }
+ remove_marked_cache_entries(&the_index, 1);
+ remove_scheduled_dirs();
+ errs |= finish_delayed_checkout(&state, &nr_checkouts);
+
+ if (opts->count_checkout_paths) {
+ if (nr_unmerged)
+ fprintf_ln(stderr, Q_("Recreated %d merge conflict",
+ "Recreated %d merge conflicts",
+ nr_unmerged),
+ nr_unmerged);
+ if (opts->source_tree)
+ fprintf_ln(stderr, Q_("Updated %d path from %s",
+ "Updated %d paths from %s",
+ nr_checkouts),
+ nr_checkouts,
+ find_unique_abbrev(&opts->source_tree->object.oid,
+ DEFAULT_ABBREV));
+ else if (!nr_unmerged || nr_checkouts)
+ fprintf_ln(stderr, Q_("Updated %d path from the index",
+ "Updated %d paths from the index",
+ nr_checkouts),
+ nr_checkouts);
+ }
+
+ return errs;
+}
+
static int checkout_paths(const struct checkout_opts *opts,
- const char *revision)
+ const struct branch_info *new_branch_info)
{
int pos;
- struct checkout state = CHECKOUT_INIT;
static char *ps_matched;
struct object_id rev;
struct commit *head;
int errs = 0;
struct lock_file lock_file = LOCK_INIT;
- int nr_checkouts = 0, nr_unmerged = 0;
+ int checkout_index;
trace2_cmd_mode(opts->patch_mode ? "patch" : "path");
@@ -333,8 +433,9 @@ static int checkout_paths(const struct checkout_opts *opts,
if (opts->new_branch_log)
die(_("'%s' cannot be used with updating paths"), "-l");
- if (opts->force && opts->patch_mode)
- die(_("'%s' cannot be used with updating paths"), "-f");
+ if (opts->ignore_unmerged && opts->patch_mode)
+ die(_("'%s' cannot be used with updating paths"),
+ opts->ignore_unmerged_opt);
if (opts->force_detach)
die(_("'%s' cannot be used with updating paths"), "--detach");
@@ -342,16 +443,59 @@ static int checkout_paths(const struct checkout_opts *opts,
if (opts->merge && opts->patch_mode)
die(_("'%s' cannot be used with %s"), "--merge", "--patch");
- if (opts->force && opts->merge)
- die(_("'%s' cannot be used with %s"), "-f", "-m");
+ if (opts->ignore_unmerged && opts->merge)
+ die(_("'%s' cannot be used with %s"),
+ opts->ignore_unmerged_opt, "-m");
if (opts->new_branch)
die(_("Cannot update paths and switch to branch '%s' at the same time."),
opts->new_branch);
- if (opts->patch_mode)
- return run_add_interactive(revision, "--patch=checkout",
- &opts->pathspec);
+ if (!opts->checkout_worktree && !opts->checkout_index)
+ die(_("neither '%s' or '%s' is specified"),
+ "--staged", "--worktree");
+
+ if (!opts->checkout_worktree && !opts->from_treeish)
+ die(_("'%s' must be used when '%s' is not specified"),
+ "--worktree", "--source");
+
+ if (opts->checkout_index && !opts->checkout_worktree &&
+ opts->writeout_stage)
+ die(_("'%s' or '%s' cannot be used with %s"),
+ "--ours", "--theirs", "--staged");
+
+ if (opts->checkout_index && !opts->checkout_worktree &&
+ opts->merge)
+ die(_("'%s' or '%s' cannot be used with %s"),
+ "--merge", "--conflict", "--staged");
+
+ if (opts->patch_mode) {
+ const char *patch_mode;
+ const char *rev = new_branch_info->name;
+ char rev_oid[GIT_MAX_HEXSZ + 1];
+
+ /*
+ * Since rev can be in the form of `<a>...<b>` (which is not
+ * recognized by diff-index), we will always replace the name
+ * with the hex of the commit (whether it's in `...` form or
+ * not) for the run_add_interactive() machinery to work
+ * properly. However, there is special logic for the HEAD case
+ * so we mustn't replace that.
+ */
+ if (rev && strcmp(rev, "HEAD"))
+ rev = oid_to_hex_r(rev_oid, &new_branch_info->commit->object.oid);
+
+ if (opts->checkout_index && opts->checkout_worktree)
+ patch_mode = "--patch=checkout";
+ else if (opts->checkout_index && !opts->checkout_worktree)
+ patch_mode = "--patch=reset";
+ else if (!opts->checkout_index && opts->checkout_worktree)
+ patch_mode = "--patch=worktree";
+ else
+ BUG("either flag must have been set, worktree=%d, index=%d",
+ opts->checkout_worktree, opts->checkout_index);
+ return run_add_interactive(rev, patch_mode, &opts->pathspec);
+ }
repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);
if (read_cache_preload(&opts->pathspec) < 0)
@@ -392,8 +536,9 @@ static int checkout_paths(const struct checkout_opts *opts,
if (ce->ce_flags & CE_MATCHED) {
if (!ce_stage(ce))
continue;
- if (opts->force) {
- warning(_("path '%s' is unmerged"), ce->name);
+ if (opts->ignore_unmerged) {
+ if (!opts->quiet)
+ warning(_("path '%s' is unmerged"), ce->name);
} else if (opts->writeout_stage) {
errs |= check_stage(opts->writeout_stage, ce, pos, opts->overlay_mode);
} else if (opts->merge) {
@@ -409,57 +554,33 @@ static int checkout_paths(const struct checkout_opts *opts,
return 1;
/* Now we are committed to check them out */
- state.force = 1;
- state.refresh_cache = 1;
- state.istate = &the_index;
+ if (opts->checkout_worktree)
+ errs |= checkout_worktree(opts, new_branch_info);
+ else
+ remove_marked_cache_entries(&the_index, 1);
- enable_delayed_checkout(&state);
- for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
- if (ce->ce_flags & CE_MATCHED) {
- if (!ce_stage(ce)) {
- errs |= checkout_entry(ce, &state,
- NULL, &nr_checkouts);
- continue;
- }
- if (opts->writeout_stage)
- errs |= checkout_stage(opts->writeout_stage,
- ce, pos,
- &state,
- &nr_checkouts, opts->overlay_mode);
- else if (opts->merge)
- errs |= checkout_merged(pos, &state,
- &nr_unmerged);
- pos = skip_same_name(ce, pos) - 1;
- }
- }
- remove_marked_cache_entries(&the_index, 1);
- remove_scheduled_dirs();
- errs |= finish_delayed_checkout(&state, &nr_checkouts);
+ /*
+ * Allow updating the index when checking out from the index.
+ * This is to save new stat info.
+ */
+ if (opts->checkout_worktree && !opts->checkout_index && !opts->source_tree)
+ checkout_index = 1;
+ else
+ checkout_index = opts->checkout_index;
- if (opts->count_checkout_paths) {
- if (nr_unmerged)
- fprintf_ln(stderr, Q_("Recreated %d merge conflict",
- "Recreated %d merge conflicts",
- nr_unmerged),
- nr_unmerged);
- if (opts->source_tree)
- fprintf_ln(stderr, Q_("Updated %d path from %s",
- "Updated %d paths from %s",
- nr_checkouts),
- nr_checkouts,
- find_unique_abbrev(&opts->source_tree->object.oid,
- DEFAULT_ABBREV));
- else if (!nr_unmerged || nr_checkouts)
- fprintf_ln(stderr, Q_("Updated %d path from the index",
- "Updated %d paths from the index",
- nr_checkouts),
- nr_checkouts);
+ if (checkout_index) {
+ if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+ die(_("unable to write new index file"));
+ } else {
+ /*
+ * NEEDSWORK: if --worktree is not specified, we
+ * should save stat info of checked out files in the
+ * index to avoid the next (potentially costly)
+ * refresh. But it's a bit tricker to do...
+ */
+ rollback_lock_file(&lock_file);
}
- if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
- die(_("unable to write new index file"));
-
read_ref_full("HEAD", 0, &rev, NULL);
head = lookup_commit_reference_gently(the_repository, &rev, 1);
@@ -497,7 +618,8 @@ static void describe_detached_head(const char *msg, struct commit *commit)
}
static int reset_tree(struct tree *tree, const struct checkout_opts *o,
- int worktree, int *writeout_error)
+ int worktree, int *writeout_error,
+ struct branch_info *info)
{
struct unpack_trees_options opts;
struct tree_desc tree_desc;
@@ -512,6 +634,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
opts.verbose_update = o->show_progress;
opts.src_index = &the_index;
opts.dst_index = &the_index;
+ init_checkout_metadata(&opts.meta, info->refname,
+ info->commit ? &info->commit->object.oid : &null_oid,
+ NULL);
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
switch (unpack_trees(1, &tree_desc, &opts)) {
@@ -531,21 +656,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
}
}
-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)
{
struct strbuf buf = STRBUF_INIT;
+ /*
+ * If this is a ref, resolve it; otherwise, look up the OID for our
+ * expression. Failure here is okay.
+ */
+ if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname, 0))
+ repo_get_oid_committish(the_repository, branch->name, &branch->oid);
+
strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
if (strcmp(buf.buf, branch->name))
branch->name = xstrdup(buf.buf);
@@ -553,112 +674,6 @@ static void setup_branch_path(struct branch_info *branch)
branch->path = strbuf_detach(&buf, NULL);
}
-/*
- * Skip merging the trees, updating the index and working directory if and
- * only if we are creating a new branch via "git checkout -b <new_branch>."
- */
-static int skip_merge_working_tree(const struct checkout_opts *opts,
- const struct branch_info *old_branch_info,
- const struct branch_info *new_branch_info)
-{
- /*
- * Do the merge if sparse checkout is on and the user has not opted in
- * to the optimized behavior
- */
- if (core_apply_sparse_checkout && !checkout_optimize_new_branch)
- return 0;
-
- /*
- * We must do the merge if we are actually moving to a new commit.
- */
- if (!old_branch_info->commit || !new_branch_info->commit ||
- !oideq(&old_branch_info->commit->object.oid,
- &new_branch_info->commit->object.oid))
- return 0;
-
- /*
- * opts->patch_mode cannot be used with switching branches so is
- * not tested here
- */
-
- /*
- * opts->quiet only impacts output so doesn't require a merge
- */
-
- /*
- * Honor the explicit request for a three-way merge or to throw away
- * local changes
- */
- if (opts->merge || opts->force)
- return 0;
-
- /*
- * --detach is documented as "updating the index and the files in the
- * working tree" but this optimization skips those steps so fall through
- * to the regular code path.
- */
- if (opts->force_detach)
- return 0;
-
- /*
- * opts->writeout_stage cannot be used with switching branches so is
- * not tested here
- */
-
- /*
- * Honor the explicit ignore requests
- */
- if (!opts->overwrite_ignore || opts->ignore_skipworktree ||
- opts->ignore_other_worktrees)
- return 0;
-
- /*
- * opts->show_progress only impacts output so doesn't require a merge
- */
-
- /*
- * opts->overlay_mode cannot be used with switching branches so is
- * not tested here
- */
-
- /*
- * If we aren't creating a new branch any changes or updates will
- * happen in the existing branch. Since that could only be updating
- * the index and working directory, we don't want to skip those steps
- * or we've defeated any purpose in running the command.
- */
- if (!opts->new_branch)
- return 0;
-
- /*
- * new_branch_force is defined to "create/reset and checkout a branch"
- * so needs to go through the merge to do the reset
- */
- if (opts->new_branch_force)
- return 0;
-
- /*
- * A new orphaned branch requrires the index and the working tree to be
- * adjusted to <start_point>
- */
- if (opts->new_orphan_branch)
- return 0;
-
- /*
- * Remaining variables are not checkout options but used to track state
- */
-
- /*
- * Do the merge if this is the initial checkout. We cannot use
- * is_cache_unborn() here because the index hasn't been loaded yet
- * so cache_nr and timestamp.sec are always zero.
- */
- if (!file_exists(get_index_file()))
- return 0;
-
- return 1;
-}
-
static int merge_working_tree(const struct checkout_opts *opts,
struct branch_info *old_branch_info,
struct branch_info *new_branch_info,
@@ -666,15 +681,21 @@ static int merge_working_tree(const struct checkout_opts *opts,
{
int ret;
struct lock_file lock_file = LOCK_INIT;
+ struct tree *new_tree;
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
if (read_cache_preload(NULL) < 0)
return error(_("index file corrupt"));
resolve_undo_clear();
- if (opts->force) {
- ret = reset_tree(get_commit_tree(new_branch_info->commit),
- opts, 1, writeout_error);
+ if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
+ if (new_branch_info->commit)
+ BUG("'switch --orphan' should never accept a commit as starting point");
+ new_tree = parse_tree_indirect(the_hash_algo->empty_tree);
+ } else
+ new_tree = get_commit_tree(new_branch_info->commit);
+ if (opts->discard_changes) {
+ ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);
if (ret)
return ret;
} else {
@@ -703,6 +724,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
topts.quiet = opts->merge && old_branch_info->commit;
topts.verbose_update = opts->show_progress;
topts.fn = twoway_merge;
+ init_checkout_metadata(&topts.meta, new_branch_info->refname,
+ new_branch_info->commit ?
+ &new_branch_info->commit->object.oid :
+ &new_branch_info->oid, NULL);
if (opts->overwrite_ignore) {
topts.dir = xcalloc(1, sizeof(*topts.dir));
topts.dir->flags |= DIR_SHOW_IGNORED;
@@ -712,7 +737,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
&old_branch_info->commit->object.oid :
the_hash_algo->empty_tree);
init_tree_desc(&trees[0], tree->buffer, tree->size);
- tree = parse_tree_indirect(&new_branch_info->commit->object.oid);
+ parse_tree(new_tree);
+ tree = new_tree;
init_tree_desc(&trees[1], tree->buffer, tree->size);
ret = unpack_trees(2, trees, &topts);
@@ -723,11 +749,11 @@ static int merge_working_tree(const struct checkout_opts *opts,
* give up or do a real merge, depending on
* whether the merge flag was used.
*/
- struct tree *result;
struct tree *work;
struct tree *old_tree;
struct merge_options o;
struct strbuf sb = STRBUF_INIT;
+ struct strbuf old_commit_shortname = STRBUF_INIT;
if (!opts->merge)
return 1;
@@ -745,13 +771,6 @@ static int merge_working_tree(const struct checkout_opts *opts,
"the following files:\n%s"), sb.buf);
strbuf_release(&sb);
- if (repo_index_has_changes(the_repository,
- get_commit_tree(old_branch_info->commit),
- &sb))
- warning(_("staged changes in the following files may be lost: %s"),
- sb.buf);
- strbuf_release(&sb);
-
/* Do more real merge */
/*
@@ -766,36 +785,35 @@ static int merge_working_tree(const struct checkout_opts *opts,
*/
add_files_to_cache(NULL, NULL, 0);
- /*
- * NEEDSWORK: carrying over local changes
- * when branches have different end-of-line
- * normalization (or clean+smudge rules) is
- * a pain; plumb in an option to set
- * o.renormalize?
- */
init_merge_options(&o, the_repository);
o.verbosity = 0;
- work = write_tree_from_memory(&o);
+ work = write_in_core_index_as_tree(the_repository);
- ret = reset_tree(get_commit_tree(new_branch_info->commit),
+ ret = reset_tree(new_tree,
opts, 1,
- writeout_error);
+ writeout_error, new_branch_info);
if (ret)
return ret;
o.ancestor = old_branch_info->name;
+ if (old_branch_info->name == NULL) {
+ strbuf_add_unique_abbrev(&old_commit_shortname,
+ &old_branch_info->commit->object.oid,
+ DEFAULT_ABBREV);
+ o.ancestor = old_commit_shortname.buf;
+ }
o.branch1 = new_branch_info->name;
o.branch2 = "local";
ret = merge_trees(&o,
- get_commit_tree(new_branch_info->commit),
+ new_tree,
work,
- old_tree,
- &result);
+ old_tree);
if (ret < 0)
exit(128);
- ret = reset_tree(get_commit_tree(new_branch_info->commit),
+ ret = reset_tree(new_tree,
opts, 0,
- writeout_error);
+ writeout_error, new_branch_info);
strbuf_release(&o.obuf);
+ strbuf_release(&old_commit_shortname);
if (ret)
return ret;
}
@@ -810,7 +828,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
- if (!opts->force && !opts->quiet)
+ if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
return 0;
@@ -876,7 +894,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
strbuf_addf(&msg, "checkout: moving from %s to %s",
old_desc ? old_desc : "(invalid)", new_branch_info->name);
else
- strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg));
+ strbuf_insertstr(&msg, 0, reflog_msg);
if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) {
/* Nothing to do. */
@@ -915,7 +933,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
delete_reflog(old_branch_info->path);
}
}
- remove_branch_state(the_repository);
+ remove_branch_state(the_repository, !opts->quiet);
strbuf_release(&msg);
if (!opts->quiet &&
(new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD"))))
@@ -1011,7 +1029,10 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
add_pending_object(&revs, object, oid_to_hex(&object->oid));
for_each_ref(add_pending_uninteresting_ref, &revs);
- add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING);
+ if (new_commit)
+ add_pending_oid(&revs, "HEAD",
+ &new_commit->object.oid,
+ UNINTERESTING);
if (prepare_revision_walk(&revs))
die(_("internal error in revision walk"));
@@ -1021,7 +1042,7 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
describe_detached_head(_("Previous HEAD position was"), old_commit);
/* Clean up objects used, as they will be reused. */
- clear_commit_marks_all(ALL_REV_FLAGS);
+ repo_clear_commit_marks(the_repository, ALL_REV_FLAGS);
}
static int switch_branches(const struct checkout_opts *opts,
@@ -1032,6 +1053,7 @@ static int switch_branches(const struct checkout_opts *opts,
void *path_to_free;
struct object_id rev;
int flag, writeout_error = 0;
+ int do_merge = 1;
trace2_cmd_mode("branch");
@@ -1045,22 +1067,26 @@ static int switch_branches(const struct checkout_opts *opts,
if (old_branch_info.path)
skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
+ if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
+ if (new_branch_info->name)
+ BUG("'switch --orphan' should never accept a commit as starting point");
+ new_branch_info->commit = NULL;
+ new_branch_info->name = "(empty)";
+ do_merge = 1;
+ }
+
if (!new_branch_info->name) {
new_branch_info->name = "HEAD";
new_branch_info->commit = old_branch_info.commit;
if (!new_branch_info->commit)
die(_("You are on a branch yet to be born"));
parse_commit_or_die(new_branch_info->commit);
+
+ if (opts->only_merge_on_switching_branches)
+ do_merge = 0;
}
- /* optimize the "checkout -b <new_branch> path */
- if (skip_merge_working_tree(opts, &old_branch_info, new_branch_info)) {
- if (!checkout_optimize_new_branch && !opts->quiet) {
- if (read_cache_preload(NULL) < 0)
- return error(_("index file corrupt"));
- show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
- }
- } else {
+ if (do_merge) {
ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
if (ret) {
free(path_to_free);
@@ -1080,16 +1106,16 @@ static int switch_branches(const struct checkout_opts *opts,
static int git_checkout_config(const char *var, const char *value, void *cb)
{
- if (!strcmp(var, "checkout.optimizenewbranch")) {
- checkout_optimize_new_branch = git_config_bool(var, value);
- return 0;
- }
+ struct checkout_opts *opts = cb;
if (!strcmp(var, "diff.ignoresubmodules")) {
- struct checkout_opts *opts = cb;
handle_ignore_submodules_arg(&opts->diff_options, value);
return 0;
}
+ if (!strcmp(var, "checkout.guess")) {
+ opts->dwim_new_local_branch = git_config_bool(var, value);
+ return 0;
+ }
if (starts_with(var, "submodule."))
return git_default_submodule_config(var, value, NULL);
@@ -1097,17 +1123,76 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
return git_xmerge_config(var, value, NULL);
}
+static void setup_new_branch_info_and_source_tree(
+ struct branch_info *new_branch_info,
+ struct checkout_opts *opts,
+ struct object_id *rev,
+ const char *arg)
+{
+ struct tree **source_tree = &opts->source_tree;
+ struct object_id branch_rev;
+
+ new_branch_info->name = arg;
+ setup_branch_path(new_branch_info);
+
+ if (!check_refname_format(new_branch_info->path, 0) &&
+ !read_ref(new_branch_info->path, &branch_rev))
+ oidcpy(rev, &branch_rev);
+ else {
+ free((char *)new_branch_info->path);
+ new_branch_info->path = NULL; /* not an existing branch */
+ }
+
+ new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
+ if (!new_branch_info->commit) {
+ /* not a commit */
+ *source_tree = parse_tree_indirect(rev);
+ } else {
+ parse_commit_or_die(new_branch_info->commit);
+ *source_tree = get_commit_tree(new_branch_info->commit);
+ }
+}
+
+static const char *parse_remote_branch(const char *arg,
+ struct object_id *rev,
+ int could_be_checkout_paths)
+{
+ int num_matches = 0;
+ const char *remote = unique_tracking_name(arg, rev, &num_matches);
+
+ if (remote && could_be_checkout_paths) {
+ die(_("'%s' could be both a local file and a tracking branch.\n"
+ "Please use -- (and optionally --no-guess) to disambiguate"),
+ arg);
+ }
+
+ if (!remote && num_matches > 1) {
+ if (advice_checkout_ambiguous_remote_branch_name) {
+ advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
+ "you can do so by fully qualifying the name with the --track option:\n"
+ "\n"
+ " git checkout --track origin/<name>\n"
+ "\n"
+ "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
+ "one remote, e.g. the 'origin' remote, consider setting\n"
+ "checkout.defaultRemote=origin in your config."));
+ }
+
+ die(_("'%s' matched multiple (%d) remote tracking branches"),
+ arg, num_matches);
+ }
+
+ return remote;
+}
+
static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
struct branch_info *new_branch_info,
struct checkout_opts *opts,
- struct object_id *rev,
- int *dwim_remotes_matched)
+ struct object_id *rev)
{
- struct tree **source_tree = &opts->source_tree;
const char **new_branch = &opts->new_branch;
int argcount = 0;
- struct object_id branch_rev;
const char *arg;
int dash_dash_pos;
int has_dash_dash = 0;
@@ -1157,10 +1242,16 @@ static int parse_branchname_arg(int argc, const char **argv,
if (!argc)
return 0;
+ if (!opts->accept_pathspec) {
+ if (argc > 1)
+ die(_("only one reference expected"));
+ has_dash_dash = 1; /* helps disambiguate */
+ }
+
arg = argv[0];
dash_dash_pos = -1;
for (i = 0; i < argc; i++) {
- if (!strcmp(argv[i], "--")) {
+ if (opts->accept_pathspec && !strcmp(argv[i], "--")) {
dash_dash_pos = i;
break;
}
@@ -1194,21 +1285,18 @@ static int parse_branchname_arg(int argc, const char **argv,
recover_with_dwim = 0;
/*
- * Accept "git checkout foo" and "git checkout foo --"
- * as candidates for dwim.
+ * Accept "git checkout foo", "git checkout foo --"
+ * and "git switch foo" as candidates for dwim.
*/
if (!(argc == 1 && !has_dash_dash) &&
- !(argc == 2 && has_dash_dash))
+ !(argc == 2 && has_dash_dash) &&
+ opts->accept_pathspec)
recover_with_dwim = 0;
if (recover_with_dwim) {
- const char *remote = unique_tracking_name(arg, rev,
- dwim_remotes_matched);
+ const char *remote = parse_remote_branch(arg, rev,
+ could_be_checkout_paths);
if (remote) {
- if (could_be_checkout_paths)
- die(_("'%s' could be both a local file and a tracking branch.\n"
- "Please use -- (and optionally --no-guess) to disambiguate"),
- arg);
*new_branch = arg;
arg = remote;
/* DWIMmed to create local branch, case (3).(b) */
@@ -1229,26 +1317,11 @@ static int parse_branchname_arg(int argc, const char **argv,
argv++;
argc--;
- new_branch_info->name = arg;
- setup_branch_path(new_branch_info);
-
- if (!check_refname_format(new_branch_info->path, 0) &&
- !read_ref(new_branch_info->path, &branch_rev))
- oidcpy(rev, &branch_rev);
- else
- new_branch_info->path = NULL; /* not an existing branch */
-
- new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
- if (!new_branch_info->commit) {
- /* not a commit */
- *source_tree = parse_tree_indirect(rev);
- } else {
- parse_commit_or_die(new_branch_info->commit);
- *source_tree = get_commit_tree(new_branch_info->commit);
- }
+ setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg);
- if (!*source_tree) /* case (1): want a tree */
+ if (!opts->source_tree) /* case (1): want a tree */
die(_("reference is not a tree: %s"), arg);
+
if (!has_dash_dash) { /* case (3).(d) -> (1) */
/*
* Do not complain the most common case
@@ -1258,7 +1331,7 @@ static int parse_branchname_arg(int argc, const char **argv,
*/
if (argc)
verify_non_filename(opts->prefix, arg);
- } else {
+ } else if (opts->accept_pathspec) {
argcount++;
argv++;
argc--;
@@ -1285,6 +1358,60 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
return status;
}
+static void die_expecting_a_branch(const struct branch_info *branch_info)
+{
+ struct object_id oid;
+ char *to_free;
+
+ if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
+ const char *ref = to_free;
+
+ if (skip_prefix(ref, "refs/tags/", &ref))
+ die(_("a branch is expected, got tag '%s'"), ref);
+ if (skip_prefix(ref, "refs/remotes/", &ref))
+ die(_("a branch is expected, got remote branch '%s'"), ref);
+ die(_("a branch is expected, got '%s'"), ref);
+ }
+ if (branch_info->commit)
+ die(_("a branch is expected, got commit '%s'"), branch_info->name);
+ /*
+ * This case should never happen because we already die() on
+ * non-commit, but just in case.
+ */
+ die(_("a branch is expected, got '%s'"), branch_info->name);
+}
+
+static void die_if_some_operation_in_progress(void)
+{
+ struct wt_status_state state;
+
+ memset(&state, 0, sizeof(state));
+ wt_status_get_state(the_repository, &state, 0);
+
+ if (state.merge_in_progress)
+ die(_("cannot switch branch while merging\n"
+ "Consider \"git merge --quit\" "
+ "or \"git worktree add\"."));
+ if (state.am_in_progress)
+ die(_("cannot switch branch in the middle of an am session\n"
+ "Consider \"git am --quit\" "
+ "or \"git worktree add\"."));
+ if (state.rebase_interactive_in_progress || state.rebase_in_progress)
+ die(_("cannot switch branch while rebasing\n"
+ "Consider \"git rebase --quit\" "
+ "or \"git worktree add\"."));
+ if (state.cherry_pick_in_progress)
+ die(_("cannot switch branch while cherry-picking\n"
+ "Consider \"git cherry-pick --quit\" "
+ "or \"git worktree add\"."));
+ if (state.revert_in_progress)
+ die(_("cannot switch branch while reverting\n"
+ "Consider \"git revert --quit\" "
+ "or \"git worktree add\"."));
+ if (state.bisect_in_progress)
+ warning(_("you are switching branch while bisecting"));
+}
+
static int checkout_branch(struct checkout_opts *opts,
struct branch_info *new_branch_info)
{
@@ -1295,9 +1422,9 @@ static int checkout_branch(struct checkout_opts *opts,
die(_("'%s' cannot be used with switching branches"),
"--patch");
- if (!opts->overlay_mode)
+ if (opts->overlay_mode != -1)
die(_("'%s' cannot be used with switching branches"),
- "--no-overlay");
+ "--[no]-overlay");
if (opts->writeout_stage)
die(_("'%s' cannot be used with switching branches"),
@@ -1306,6 +1433,9 @@ static int checkout_branch(struct checkout_opts *opts,
if (opts->force && opts->merge)
die(_("'%s' cannot be used with '%s'"), "-f", "-m");
+ if (opts->discard_changes && opts->merge)
+ die(_("'%s' cannot be used with '%s'"), "--discard-changes", "--merge");
+
if (opts->force_detach && opts->new_branch)
die(_("'%s' cannot be used with '%s'"),
"--detach", "-b/-B/--orphan");
@@ -1313,6 +1443,8 @@ static int checkout_branch(struct checkout_opts *opts,
if (opts->new_orphan_branch) {
if (opts->track != BRANCH_TRACK_UNSPECIFIED)
die(_("'%s' cannot be used with '%s'"), "--orphan", "-t");
+ if (opts->orphan_from_empty_tree && new_branch_info->name)
+ die(_("'%s' cannot take <start-point>"), "--orphan");
} else if (opts->force_detach) {
if (opts->track != BRANCH_TRACK_UNSPECIFIED)
die(_("'%s' cannot be used with '%s'"), "--detach", "-t");
@@ -1323,6 +1455,23 @@ static int checkout_branch(struct checkout_opts *opts,
die(_("Cannot switch branch to a non-commit '%s'"),
new_branch_info->name);
+ if (!opts->switch_branch_doing_nothing_is_ok &&
+ !new_branch_info->name &&
+ !opts->new_branch &&
+ !opts->force_detach)
+ die(_("missing branch or commit argument"));
+
+ if (!opts->implicit_detach &&
+ !opts->force_detach &&
+ !opts->new_branch &&
+ !opts->new_branch_force &&
+ new_branch_info->name &&
+ !new_branch_info->path)
+ die_expecting_a_branch(new_branch_info);
+
+ if (!opts->can_switch_when_in_progress)
+ die_if_some_operation_in_progress();
+
if (new_branch_info->path && !opts->force_detach && !opts->new_branch &&
!opts->ignore_other_worktrees) {
int flag;
@@ -1344,99 +1493,153 @@ static int checkout_branch(struct checkout_opts *opts,
return switch_branches(opts, new_branch_info);
}
-int cmd_checkout(int argc, const char **argv, const char *prefix)
+static struct option *add_common_options(struct checkout_opts *opts,
+ struct option *prevopts)
{
- struct checkout_opts opts;
- struct branch_info new_branch_info;
- char *conflict_style = NULL;
- int dwim_new_local_branch, no_dwim_new_local_branch = 0;
- int dwim_remotes_matched = 0;
struct option options[] = {
- OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
- OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
- N_("create and checkout a new branch")),
- OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
- N_("create/reset and checkout a branch")),
- OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
- OPT_BOOL(0, "detach", &opts.force_detach, N_("detach HEAD at named commit")),
- OPT_SET_INT('t', "track", &opts.track, N_("set upstream info for new branch"),
+ OPT__QUIET(&opts->quiet, N_("suppress progress reporting")),
+ OPT_CALLBACK_F(0, "recurse-submodules", NULL,
+ "checkout", "control recursive updating of submodules",
+ PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater),
+ OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")),
+ OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")),
+ OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"),
+ N_("conflict style (merge or diff3)")),
+ OPT_END()
+ };
+ struct option *newopts = parse_options_concat(prevopts, options);
+ free(prevopts);
+ return newopts;
+}
+
+static struct option *add_common_switch_branch_options(
+ struct checkout_opts *opts, struct option *prevopts)
+{
+ struct option options[] = {
+ OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
+ OPT_SET_INT('t', "track", &opts->track, N_("set upstream info for new branch"),
BRANCH_TRACK_EXPLICIT),
- OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
- OPT_SET_INT_F('2', "ours", &opts.writeout_stage,
+ OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
+ PARSE_OPT_NOCOMPLETE),
+ OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+ OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore,
+ N_("update ignored files (default)"),
+ PARSE_OPT_NOCOMPLETE),
+ OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees,
+ N_("do not check if another worktree is holding the given ref")),
+ OPT_END()
+ };
+ struct option *newopts = parse_options_concat(prevopts, options);
+ free(prevopts);
+ return newopts;
+}
+
+static struct option *add_checkout_path_options(struct checkout_opts *opts,
+ struct option *prevopts)
+{
+ struct option options[] = {
+ OPT_SET_INT_F('2', "ours", &opts->writeout_stage,
N_("checkout our version for unmerged files"),
2, PARSE_OPT_NONEG),
- OPT_SET_INT_F('3', "theirs", &opts.writeout_stage,
+ OPT_SET_INT_F('3', "theirs", &opts->writeout_stage,
N_("checkout their version for unmerged files"),
3, PARSE_OPT_NONEG),
- OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"),
- PARSE_OPT_NOCOMPLETE),
- OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
- OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore,
- N_("update ignored files (default)"),
- PARSE_OPT_NOCOMPLETE),
- OPT_STRING(0, "conflict", &conflict_style, N_("style"),
- N_("conflict style (merge or diff3)")),
- OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
- OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+ OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")),
+ OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree,
N_("do not limit pathspecs to sparse entries only")),
- OPT_BOOL(0, "no-guess", &no_dwim_new_local_branch,
- N_("do not second guess 'git checkout <no-such-branch>'")),
- OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
- N_("do not check if another worktree is holding the given ref")),
- { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
- "checkout", "control recursive updating of submodules",
- PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
- OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
- OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
- OPT_END(),
+ OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file),
+ OPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul),
+ OPT_END()
};
+ struct option *newopts = parse_options_concat(prevopts, options);
+ free(prevopts);
+ return newopts;
+}
+
+/* create-branch option (either b or c) */
+static char cb_option = 'b';
+
+static int checkout_main(int argc, const char **argv, const char *prefix,
+ struct checkout_opts *opts, struct option *options,
+ const char * const usagestr[])
+{
+ struct branch_info new_branch_info;
+ int parseopt_flags = 0;
- memset(&opts, 0, sizeof(opts));
memset(&new_branch_info, 0, sizeof(new_branch_info));
- opts.overwrite_ignore = 1;
- opts.prefix = prefix;
- opts.show_progress = -1;
- opts.overlay_mode = -1;
+ opts->overwrite_ignore = 1;
+ opts->prefix = prefix;
+ opts->show_progress = -1;
- git_config(git_checkout_config, &opts);
+ git_config(git_checkout_config, opts);
- opts.track = BRANCH_TRACK_UNSPECIFIED;
+ opts->track = BRANCH_TRACK_UNSPECIFIED;
- argc = parse_options(argc, argv, prefix, options, checkout_usage,
- PARSE_OPT_KEEP_DASHDASH);
+ if (!opts->accept_pathspec && !opts->accept_ref)
+ BUG("make up your mind, you need to take _something_");
+ if (opts->accept_pathspec && opts->accept_ref)
+ parseopt_flags = PARSE_OPT_KEEP_DASHDASH;
- dwim_new_local_branch = !no_dwim_new_local_branch;
- if (opts.show_progress < 0) {
- if (opts.quiet)
- opts.show_progress = 0;
+ argc = parse_options(argc, argv, prefix, options,
+ usagestr, parseopt_flags);
+
+ if (opts->show_progress < 0) {
+ if (opts->quiet)
+ opts->show_progress = 0;
else
- opts.show_progress = isatty(2);
+ opts->show_progress = isatty(2);
}
- if (conflict_style) {
- opts.merge = 1; /* implied */
- git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+ if (opts->conflict_style) {
+ opts->merge = 1; /* implied */
+ git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL);
+ }
+ if (opts->force) {
+ opts->discard_changes = 1;
+ opts->ignore_unmerged_opt = "--force";
+ opts->ignore_unmerged = 1;
}
- if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
- die(_("-b, -B and --orphan are mutually exclusive"));
+ if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1)
+ die(_("-%c, -%c and --orphan are mutually exclusive"),
+ cb_option, toupper(cb_option));
- if (opts.overlay_mode == 1 && opts.patch_mode)
+ if (opts->overlay_mode == 1 && opts->patch_mode)
die(_("-p and --overlay are mutually exclusive"));
+ if (opts->checkout_index >= 0 || opts->checkout_worktree >= 0) {
+ if (opts->checkout_index < 0)
+ opts->checkout_index = 0;
+ if (opts->checkout_worktree < 0)
+ opts->checkout_worktree = 0;
+ } else {
+ if (opts->checkout_index < 0)
+ opts->checkout_index = -opts->checkout_index - 1;
+ if (opts->checkout_worktree < 0)
+ opts->checkout_worktree = -opts->checkout_worktree - 1;
+ }
+ if (opts->checkout_index < 0 || opts->checkout_worktree < 0)
+ BUG("these flags should be non-negative by now");
+ /*
+ * convenient shortcut: "git restore --staged [--worktree]" equals
+ * "git restore --staged [--worktree] --source HEAD"
+ */
+ if (!opts->from_treeish && opts->checkout_index)
+ opts->from_treeish = "HEAD";
+
/*
* From here on, new_branch will contain the branch to be checked out,
* and new_branch_force and new_orphan_branch will tell us which one of
- * -b/-B/--orphan is being used.
+ * -b/-B/-c/-C/--orphan is being used.
*/
- if (opts.new_branch_force)
- opts.new_branch = opts.new_branch_force;
+ if (opts->new_branch_force)
+ opts->new_branch = opts->new_branch_force;
- if (opts.new_orphan_branch)
- opts.new_branch = opts.new_orphan_branch;
+ if (opts->new_orphan_branch)
+ opts->new_branch = opts->new_orphan_branch;
- /* --track without -b/-B/--orphan should DWIM */
- if (opts.track != BRANCH_TRACK_UNSPECIFIED && !opts.new_branch) {
+ /* --track without -c/-C/-b/-B/--orphan should DWIM */
+ if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) {
const char *argv0 = argv[0];
if (!argc || !strcmp(argv0, "--"))
die(_("--track needs a branch name"));
@@ -1444,8 +1647,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
skip_prefix(argv0, "remotes/", &argv0);
argv0 = strchr(argv0, '/');
if (!argv0 || !argv0[1])
- die(_("missing branch name; try -b"));
- opts.new_branch = argv0 + 1;
+ die(_("missing branch name; try -%c"), cb_option);
+ opts->new_branch = argv0 + 1;
}
/*
@@ -1461,77 +1664,223 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
* including "last branch" syntax and DWIM-ery for names of
* remote branches, erroring out for invalid or ambiguous cases.
*/
- if (argc) {
+ if (argc && opts->accept_ref) {
struct object_id rev;
int dwim_ok =
- !opts.patch_mode &&
- dwim_new_local_branch &&
- opts.track == BRANCH_TRACK_UNSPECIFIED &&
- !opts.new_branch;
+ !opts->patch_mode &&
+ opts->dwim_new_local_branch &&
+ opts->track == BRANCH_TRACK_UNSPECIFIED &&
+ !opts->new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok,
- &new_branch_info, &opts, &rev,
- &dwim_remotes_matched);
+ &new_branch_info, opts, &rev);
argv += n;
argc -= n;
+ } else if (!opts->accept_ref && opts->from_treeish) {
+ struct object_id rev;
+
+ if (get_oid_mb(opts->from_treeish, &rev))
+ die(_("could not resolve %s"), opts->from_treeish);
+
+ setup_new_branch_info_and_source_tree(&new_branch_info,
+ opts, &rev,
+ opts->from_treeish);
+
+ if (!opts->source_tree)
+ die(_("reference is not a tree: %s"), opts->from_treeish);
}
if (argc) {
- parse_pathspec(&opts.pathspec, 0,
- opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
+ parse_pathspec(&opts->pathspec, 0,
+ opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
prefix, argv);
- if (!opts.pathspec.nr)
+ if (!opts->pathspec.nr)
die(_("invalid path specification"));
/*
* Try to give more helpful suggestion.
* new_branch && argc > 1 will be caught later.
*/
- if (opts.new_branch && argc == 1)
+ if (opts->new_branch && argc == 1 && !new_branch_info.commit)
die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
- argv[0], opts.new_branch);
+ argv[0], opts->new_branch);
- if (opts.force_detach)
+ if (opts->force_detach)
die(_("git checkout: --detach does not take a path argument '%s'"),
argv[0]);
+ }
+
+ if (opts->pathspec_from_file) {
+ if (opts->pathspec.nr)
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+ if (opts->force_detach)
+ die(_("--pathspec-from-file is incompatible with --detach"));
+
+ if (opts->patch_mode)
+ die(_("--pathspec-from-file is incompatible with --patch"));
- if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+ parse_pathspec_file(&opts->pathspec, 0,
+ 0,
+ prefix, opts->pathspec_from_file, opts->pathspec_file_nul);
+ } else if (opts->pathspec_file_nul) {
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
+ }
+
+ opts->pathspec.recursive = 1;
+
+ if (opts->pathspec.nr) {
+ if (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge)
die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
"checking out of the index."));
+ } else {
+ if (opts->accept_pathspec && !opts->empty_pathspec_ok &&
+ !opts->patch_mode) /* patch mode is special */
+ die(_("you must specify path(s) to restore"));
}
- if (opts.new_branch) {
+ if (opts->new_branch) {
struct strbuf buf = STRBUF_INIT;
- if (opts.new_branch_force)
- opts.branch_exists = validate_branchname(opts.new_branch, &buf);
+ if (opts->new_branch_force)
+ opts->branch_exists = validate_branchname(opts->new_branch, &buf);
else
- opts.branch_exists =
- validate_new_branchname(opts.new_branch, &buf, 0);
+ opts->branch_exists =
+ validate_new_branchname(opts->new_branch, &buf, 0);
strbuf_release(&buf);
}
UNLEAK(opts);
- if (opts.patch_mode || opts.pathspec.nr) {
- int ret = checkout_paths(&opts, new_branch_info.name);
- if (ret && dwim_remotes_matched > 1 &&
- advice_checkout_ambiguous_remote_branch_name)
- advise(_("'%s' matched more than one remote tracking branch.\n"
- "We found %d remotes with a reference that matched. So we fell back\n"
- "on trying to resolve the argument as a path, but failed there too!\n"
- "\n"
- "If you meant to check out a remote tracking branch on, e.g. 'origin',\n"
- "you can do so by fully qualifying the name with the --track option:\n"
- "\n"
- " git checkout --track origin/<name>\n"
- "\n"
- "If you'd like to always have checkouts of an ambiguous <name> prefer\n"
- "one remote, e.g. the 'origin' remote, consider setting\n"
- "checkout.defaultRemote=origin in your config."),
- argv[0],
- dwim_remotes_matched);
- return ret;
- } else {
- return checkout_branch(&opts, &new_branch_info);
+ if (opts->patch_mode || opts->pathspec.nr)
+ return checkout_paths(opts, &new_branch_info);
+ else
+ return checkout_branch(opts, &new_branch_info);
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+ struct checkout_opts opts;
+ struct option *options;
+ struct option checkout_options[] = {
+ OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+ N_("create and checkout a new branch")),
+ OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
+ N_("create/reset and checkout a branch")),
+ OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
+ OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
+ N_("second guess 'git checkout <no-such-branch>' (default)")),
+ OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
+ OPT_END()
+ };
+ int ret;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.dwim_new_local_branch = 1;
+ opts.switch_branch_doing_nothing_is_ok = 1;
+ opts.only_merge_on_switching_branches = 0;
+ opts.accept_ref = 1;
+ opts.accept_pathspec = 1;
+ opts.implicit_detach = 1;
+ opts.can_switch_when_in_progress = 1;
+ opts.orphan_from_empty_tree = 0;
+ opts.empty_pathspec_ok = 1;
+ opts.overlay_mode = -1;
+ opts.checkout_index = -2; /* default on */
+ opts.checkout_worktree = -2; /* default on */
+
+ if (argc == 3 && !strcmp(argv[1], "-b")) {
+ /*
+ * User ran 'git checkout -b <branch>' and expects
+ * the same behavior as 'git switch -c <branch>'.
+ */
+ opts.switch_branch_doing_nothing_is_ok = 0;
+ opts.only_merge_on_switching_branches = 1;
}
+
+ options = parse_options_dup(checkout_options);
+ options = add_common_options(&opts, options);
+ options = add_common_switch_branch_options(&opts, options);
+ options = add_checkout_path_options(&opts, options);
+
+ ret = checkout_main(argc, argv, prefix, &opts,
+ options, checkout_usage);
+ FREE_AND_NULL(options);
+ return ret;
+}
+
+int cmd_switch(int argc, const char **argv, const char *prefix)
+{
+ struct checkout_opts opts;
+ struct option *options = NULL;
+ struct option switch_options[] = {
+ OPT_STRING('c', "create", &opts.new_branch, N_("branch"),
+ N_("create and switch to a new branch")),
+ OPT_STRING('C', "force-create", &opts.new_branch_force, N_("branch"),
+ N_("create/reset and switch to a branch")),
+ OPT_BOOL(0, "guess", &opts.dwim_new_local_branch,
+ N_("second guess 'git switch <no-such-branch>'")),
+ OPT_BOOL(0, "discard-changes", &opts.discard_changes,
+ N_("throw away local modifications")),
+ OPT_END()
+ };
+ int ret;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.dwim_new_local_branch = 1;
+ opts.accept_ref = 1;
+ opts.accept_pathspec = 0;
+ opts.switch_branch_doing_nothing_is_ok = 0;
+ opts.only_merge_on_switching_branches = 1;
+ opts.implicit_detach = 0;
+ opts.can_switch_when_in_progress = 0;
+ opts.orphan_from_empty_tree = 1;
+ opts.overlay_mode = -1;
+
+ options = parse_options_dup(switch_options);
+ options = add_common_options(&opts, options);
+ options = add_common_switch_branch_options(&opts, options);
+
+ cb_option = 'c';
+
+ ret = checkout_main(argc, argv, prefix, &opts,
+ options, switch_branch_usage);
+ FREE_AND_NULL(options);
+ return ret;
+}
+
+int cmd_restore(int argc, const char **argv, const char *prefix)
+{
+ struct checkout_opts opts;
+ struct option *options;
+ struct option restore_options[] = {
+ OPT_STRING('s', "source", &opts.from_treeish, "<tree-ish>",
+ N_("which tree-ish to checkout from")),
+ OPT_BOOL('S', "staged", &opts.checkout_index,
+ N_("restore the index")),
+ OPT_BOOL('W', "worktree", &opts.checkout_worktree,
+ N_("restore the working tree (default)")),
+ OPT_BOOL(0, "ignore-unmerged", &opts.ignore_unmerged,
+ N_("ignore unmerged entries")),
+ OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode")),
+ OPT_END()
+ };
+ int ret;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.accept_ref = 0;
+ opts.accept_pathspec = 1;
+ opts.empty_pathspec_ok = 0;
+ opts.overlay_mode = 0;
+ opts.checkout_index = -1; /* default off */
+ opts.checkout_worktree = -2; /* default on */
+ opts.ignore_unmerged_opt = "--ignore-unmerged";
+
+ options = parse_options_dup(restore_options);
+ options = add_common_options(&opts, options);
+ options = add_checkout_path_options(&opts, options);
+
+ ret = checkout_main(argc, argv, prefix, &opts,
+ options, restore_usage);
+ FREE_AND_NULL(options);
+ return ret;
}
diff --git a/builtin/clean.c b/builtin/clean.c
index d5579da716..687ab473c2 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -18,6 +18,7 @@
#include "color.h"
#include "pathspec.h"
#include "help.h"
+#include "prompt.h"
static int force = -1; /* unset */
static int interactive;
@@ -158,9 +159,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
*dir_gone = 1;
- if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) {
+ if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
+ is_nonbare_repository_dir(path)) {
if (!quiet) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
quoted.buf);
}
@@ -175,7 +177,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
res = dry_run ? 0 : rmdir(path->buf);
if (res) {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -200,7 +202,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
ret = 1;
if (gone) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
string_list_append(&dels, quoted.buf);
} else
*dir_gone = 0;
@@ -208,11 +210,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
} else {
res = dry_run ? 0 : unlink(path->buf);
if (!res) {
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
string_list_append(&dels, quoted.buf);
} else {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -236,7 +238,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
*dir_gone = 1;
else {
int saved_errno = errno;
- quote_path_relative(path->buf, prefix, &quoted);
+ quote_path(path->buf, prefix, &quoted, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
@@ -264,7 +266,7 @@ static void pretty_print_dels(void)
struct column_options copts;
for_each_string_list_item(item, &del_list) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
string_list_append(&list, qname);
}
@@ -419,7 +421,6 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
return found;
}
-
/*
* Parse user input, and return choice(s) for menu (menu_stuff).
*
@@ -579,9 +580,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
clean_get_color(CLEAN_COLOR_RESET));
}
- if (strbuf_getline_lf(&choice, stdin) != EOF) {
- strbuf_trim(&choice);
- } else {
+ if (git_read_line_interactively(&choice) == EOF) {
eof = 1;
break;
}
@@ -648,7 +647,7 @@ static int filter_by_patterns_cmd(void)
struct strbuf confirm = STRBUF_INIT;
struct strbuf **ignore_list;
struct string_list_item *item;
- struct exclude_list *el;
+ struct pattern_list *pl;
int changed = -1, i;
for (;;) {
@@ -661,17 +660,15 @@ static int filter_by_patterns_cmd(void)
clean_print_color(CLEAN_COLOR_PROMPT);
printf(_("Input ignore patterns>> "));
clean_print_color(CLEAN_COLOR_RESET);
- if (strbuf_getline_lf(&confirm, stdin) != EOF)
- strbuf_trim(&confirm);
- else
+ if (git_read_line_interactively(&confirm) == EOF)
putchar('\n');
/* quit filter_by_pattern mode if press ENTER or Ctrl-D */
if (!confirm.len)
break;
- memset(&dir, 0, sizeof(dir));
- el = add_exclude_list(&dir, EXC_CMDL, "manual exclude");
+ dir_init(&dir);
+ pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude");
ignore_list = strbuf_split_max(&confirm, ' ', 0);
for (i = 0; ignore_list[i]; i++) {
@@ -679,7 +676,7 @@ static int filter_by_patterns_cmd(void)
if (!ignore_list[i]->len)
continue;
- add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1));
+ add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1));
}
changed = 0;
@@ -701,7 +698,7 @@ static int filter_by_patterns_cmd(void)
}
strbuf_list_free(ignore_list);
- clear_directory(&dir);
+ dir_clear(&dir);
}
strbuf_release(&confirm);
@@ -756,12 +753,10 @@ static int ask_each_cmd(void)
for_each_string_list_item(item, &del_list) {
/* Ctrl-D should stop removing files */
if (!eof) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
/* TRANSLATORS: Make sure to keep [y/N] as is */
printf(_("Remove %s [y/N]? "), qname);
- if (strbuf_getline_lf(&confirm, stdin) != EOF) {
- strbuf_trim(&confirm);
- } else {
+ if (git_read_line_interactively(&confirm) == EOF) {
putchar('\n');
eof = 1;
}
@@ -901,7 +896,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
struct pathspec pathspec;
struct strbuf buf = STRBUF_INIT;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
- struct exclude_list *el;
+ struct pattern_list *pl;
struct string_list_item *item;
const char *qname;
struct option options[] = {
@@ -911,8 +906,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
OPT_BOOL('d', NULL, &remove_directories,
N_("remove whole directories")),
- { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
- N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb },
+ OPT_CALLBACK_F('e', "exclude", &exclude_list, N_("pattern"),
+ N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb),
OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
OPT_BOOL('X', NULL, &ignored_only,
N_("remove only ignored files")),
@@ -928,13 +923,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
0);
- memset(&dir, 0, sizeof(dir));
- if (ignored_only)
- dir.flags |= DIR_SHOW_IGNORED;
-
- if (ignored && ignored_only)
- die(_("-x and -X cannot be used together"));
-
+ dir_init(&dir);
if (!interactive && !dry_run && !force) {
if (config_set)
die(_("clean.requireForce set to true and neither -i, -n, nor -f given; "
@@ -946,21 +935,64 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (force > 1)
rm_flags = 0;
+ else
+ dir.flags |= DIR_SKIP_NESTED_GIT;
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
- if (remove_directories)
- dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS;
+ if (ignored && ignored_only)
+ die(_("-x and -X cannot be used together"));
+ if (!ignored)
+ setup_standard_excludes(&dir);
+ if (ignored_only)
+ dir.flags |= DIR_SHOW_IGNORED;
+
+ if (argc) {
+ /*
+ * Remaining args implies pathspecs specified, and we should
+ * recurse within those.
+ */
+ remove_directories = 1;
+ }
+
+ if (remove_directories && !ignored_only) {
+ /*
+ * We need to know about ignored files too:
+ *
+ * If (ignored), then we will delete ignored files as well.
+ *
+ * If (!ignored), then even though we not are doing
+ * anything with ignored files, we need to know about them
+ * so that we can avoid deleting a directory of untracked
+ * files that also contains an ignored file within it.
+ *
+ * For the (!ignored) case, since we only need to avoid
+ * deleting ignored files, we can set
+ * DIR_SHOW_IGNORED_TOO_MODE_MATCHING in order to avoid
+ * recursing into a directory which is itself ignored.
+ */
+ dir.flags |= DIR_SHOW_IGNORED_TOO;
+ if (!ignored)
+ dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING;
+
+ /*
+ * Let the fill_directory() machinery know that we aren't
+ * just recursing to collect the ignored files; we want all
+ * the untracked ones so that we can delete them. (Note:
+ * we could also set DIR_KEEP_UNTRACKED_CONTENTS when
+ * ignored_only is true, since DIR_KEEP_UNTRACKED_CONTENTS
+ * only has effect in combination with DIR_SHOW_IGNORED_TOO. It makes
+ * the code clearer to exclude it, though.
+ */
+ dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS;
+ }
if (read_cache() < 0)
die(_("index file corrupt"));
- if (!ignored)
- setup_standard_excludes(&dir);
-
- el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
+ pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option");
for (i = 0; i < exclude_list.nr; i++)
- add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
+ add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1));
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD,
@@ -978,12 +1010,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (!cache_name_is_other(ent->name, ent->len))
continue;
- if (pathspec.nr)
- matches = dir_path_match(&the_index, ent, &pathspec, 0, NULL);
-
- if (pathspec.nr && !matches)
- continue;
-
if (lstat(ent->name, &st))
die_errno("Cannot lstat '%s'", ent->name);
@@ -995,11 +1021,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
string_list_append(&del_list, rel);
}
- for (i = 0; i < dir.nr; i++)
- free(dir.entries[i]);
-
- for (i = 0; i < dir.ignored_nr; i++)
- free(dir.ignored[i]);
+ dir_clear(&dir);
if (interactive && del_list.nr > 0)
interactive_main_loop();
@@ -1007,6 +1029,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
for_each_string_list_item(item, &del_list) {
struct stat st;
+ strbuf_reset(&abs_path);
if (prefix)
strbuf_addstr(&abs_path, prefix);
@@ -1024,23 +1047,22 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
errors++;
if (gone && !quiet) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
} else {
res = dry_run ? 0 : unlink(abs_path.buf);
if (res) {
int saved_errno = errno;
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
errno = saved_errno;
warning_errno(_(msg_warn_remove_failed), qname);
errors++;
} else if (!quiet) {
- qname = quote_path_relative(item->string, NULL, &buf);
+ qname = quote_path(item->string, NULL, &buf, 0);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
}
- strbuf_reset(&abs_path);
}
strbuf_release(&abs_path);
diff --git a/builtin/clone.c b/builtin/clone.c
index 6b285111d5..a0841923cf 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -23,6 +23,8 @@
#include "transport.h"
#include "strbuf.h"
#include "dir.h"
+#include "dir-iterator.h"
+#include "iterator.h"
#include "sigchain.h"
#include "branch.h"
#include "remote.h"
@@ -30,7 +32,6 @@
#include "connected.h"
#include "packfile.h"
#include "list-objects-filter-options.h"
-#include "object-store.h"
/*
* Overall FIXMEs:
@@ -52,12 +53,14 @@ static int option_shallow_submodules;
static int deepen;
static char *option_template, *option_depth, *option_since;
static char *option_origin = NULL;
+static char *remote_name = NULL;
static char *option_branch = NULL;
static struct string_list option_not = STRING_LIST_INIT_NODUP;
static const char *real_git_dir;
static char *option_upload_pack = "git-upload-pack";
static int option_verbosity;
static int option_progress = -1;
+static int option_sparse_checkout;
static enum transport_family family;
static struct string_list option_config = STRING_LIST_INIT_NODUP;
static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
@@ -67,6 +70,7 @@ static int max_jobs = -1;
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
static struct list_objects_filter_options filter_options;
static struct string_list server_options = STRING_LIST_INIT_NODUP;
+static int option_remote_submodules;
static int recurse_submodules_cb(const struct option *opt,
const char *arg, int unset)
@@ -99,10 +103,10 @@ static struct option builtin_clone_options[] = {
N_("don't use local hardlinks, always copy")),
OPT_BOOL('s', "shared", &option_shared,
N_("setup as shared repository")),
- OPT_ALIAS(0, "recursive", "recurse-submodules"),
{ OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules,
N_("pathspec"), N_("initialize submodules in the clone"),
PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." },
+ OPT_ALIAS(0, "recursive", "recurse-submodules"),
OPT_INTEGER('j', "jobs", &max_jobs,
N_("number of submodules cloned in parallel")),
OPT_STRING(0, "template", &option_template, N_("template-directory"),
@@ -142,6 +146,10 @@ static struct option builtin_clone_options[] = {
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
TRANSPORT_FAMILY_IPV6),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+ OPT_BOOL(0, "remote-submodules", &option_remote_submodules,
+ N_("any cloned submodules will use their remote-tracking branch")),
+ OPT_BOOL(0, "sparse", &option_sparse_checkout,
+ N_("initialize sparse-checkout file to include only files at root")),
OPT_END()
};
@@ -354,8 +362,7 @@ static void setup_reference(void)
add_one_reference, &required);
}
-static void copy_alternates(struct strbuf *src, struct strbuf *dst,
- const char *src_repo)
+static void copy_alternates(struct strbuf *src, const char *src_repo)
{
/*
* Read from the source objects/info/alternates file
@@ -392,58 +399,65 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst,
fclose(in);
}
+static void mkdir_if_missing(const char *pathname, mode_t mode)
+{
+ struct stat st;
+
+ if (!mkdir(pathname, mode))
+ return;
+
+ if (errno != EEXIST)
+ die_errno(_("failed to create directory '%s'"), pathname);
+ else if (stat(pathname, &st))
+ die_errno(_("failed to stat '%s'"), pathname);
+ else if (!S_ISDIR(st.st_mode))
+ die(_("%s exists and is not a directory"), pathname);
+}
+
static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
- const char *src_repo, int src_baselen)
+ const char *src_repo)
{
- struct dirent *de;
- struct stat buf;
int src_len, dest_len;
- DIR *dir;
-
- dir = opendir(src->buf);
- if (!dir)
- die_errno(_("failed to open '%s'"), src->buf);
-
- if (mkdir(dest->buf, 0777)) {
- if (errno != EEXIST)
- die_errno(_("failed to create directory '%s'"), dest->buf);
- else if (stat(dest->buf, &buf))
- die_errno(_("failed to stat '%s'"), dest->buf);
- else if (!S_ISDIR(buf.st_mode))
- die(_("%s exists and is not a directory"), dest->buf);
- }
+ struct dir_iterator *iter;
+ int iter_status;
+ unsigned int flags;
+ struct strbuf realpath = STRBUF_INIT;
+
+ mkdir_if_missing(dest->buf, 0777);
+
+ flags = DIR_ITERATOR_PEDANTIC | DIR_ITERATOR_FOLLOW_SYMLINKS;
+ iter = dir_iterator_begin(src->buf, flags);
+
+ if (!iter)
+ die_errno(_("failed to start iterator over '%s'"), src->buf);
strbuf_addch(src, '/');
src_len = src->len;
strbuf_addch(dest, '/');
dest_len = dest->len;
- while ((de = readdir(dir)) != NULL) {
+ while ((iter_status = dir_iterator_advance(iter)) == ITER_OK) {
strbuf_setlen(src, src_len);
- strbuf_addstr(src, de->d_name);
+ strbuf_addstr(src, iter->relative_path);
strbuf_setlen(dest, dest_len);
- strbuf_addstr(dest, de->d_name);
- if (stat(src->buf, &buf)) {
- warning (_("failed to stat %s\n"), src->buf);
- continue;
- }
- if (S_ISDIR(buf.st_mode)) {
- if (de->d_name[0] != '.')
- copy_or_link_directory(src, dest,
- src_repo, src_baselen);
+ strbuf_addstr(dest, iter->relative_path);
+
+ if (S_ISDIR(iter->st.st_mode)) {
+ mkdir_if_missing(dest->buf, 0777);
continue;
}
/* Files that cannot be copied bit-for-bit... */
- if (!strcmp(src->buf + src_baselen, "/info/alternates")) {
- copy_alternates(src, dest, src_repo);
+ if (!fspathcmp(iter->relative_path, "info/alternates")) {
+ copy_alternates(src, src_repo);
continue;
}
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
if (!option_no_hardlinks) {
- if (!link(src->buf, dest->buf))
+ strbuf_realpath(&realpath, src->buf, 1);
+ if (!link(realpath.buf, dest->buf))
continue;
if (option_local > 0)
die_errno(_("failed to create link '%s'"), dest->buf);
@@ -452,7 +466,13 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
if (copy_file_with_time(dest->buf, src->buf, 0666))
die_errno(_("failed to copy file to '%s'"), dest->buf);
}
- closedir(dir);
+
+ if (iter_status != ITER_DONE) {
+ strbuf_setlen(src, src_len);
+ die(_("failed to iterate over '%s'"), src->buf);
+ }
+
+ strbuf_release(&realpath);
}
static void clone_local(const char *src_repo, const char *dest_repo)
@@ -470,7 +490,7 @@ static void clone_local(const char *src_repo, const char *dest_repo)
get_common_dir(&dest, dest_repo);
strbuf_addstr(&src, "/objects");
strbuf_addstr(&dest, "/objects");
- copy_or_link_directory(&src, &dest, src_repo, src.len);
+ copy_or_link_directory(&src, &dest, src_repo);
strbuf_release(&src);
strbuf_release(&dest);
}
@@ -492,7 +512,7 @@ static enum {
static const char junk_leave_repo_msg[] =
N_("Clone succeeded, but checkout failed.\n"
"You can inspect what was checked out with 'git status'\n"
- "and retry the checkout with 'git checkout -f HEAD'\n");
+ "and retry with 'git restore --source=HEAD :/'\n");
static void remove_junk(void)
{
@@ -624,7 +644,9 @@ static void write_followtags(const struct ref *refs, const char *msg)
continue;
if (ends_with(ref->name, "^{}"))
continue;
- if (!has_object_file(&ref->old_oid))
+ if (!has_object_file_with_flags(&ref->old_oid,
+ OBJECT_INFO_QUICK |
+ OBJECT_INFO_SKIP_FETCH_OBJECT))
continue;
update_ref(msg, ref->name, &ref->old_oid, NULL, 0,
UPDATE_REFS_DIE_ON_ERR);
@@ -657,8 +679,7 @@ static void update_remote_refs(const struct ref *refs,
const char *branch_top,
const char *msg,
struct transport *transport,
- int check_connectivity,
- int check_refs_only)
+ int check_connectivity)
{
const struct ref *rm = mapped_refs;
@@ -667,7 +688,6 @@ static void update_remote_refs(const struct ref *refs,
opt.transport = transport;
opt.progress = transport->progress;
- opt.check_refs_only = !!check_refs_only;
if (check_connected(iterate_ref_map, &rm, &opt))
die(_("remote did not send all necessary objects"));
@@ -702,7 +722,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
if (!option_bare) {
update_ref(msg, "HEAD", &our->old_oid, NULL, 0,
UPDATE_REFS_DIE_ON_ERR);
- install_branch_config(0, head, option_origin, our->name);
+ install_branch_config(0, head, remote_name, our->name);
}
} else if (our) {
struct commit *c = lookup_commit_reference(the_repository,
@@ -721,6 +741,27 @@ static void update_head(const struct ref *our, const struct ref *remote,
}
}
+static int git_sparse_checkout_init(const char *repo)
+{
+ struct strvec argv = STRVEC_INIT;
+ int result = 0;
+ strvec_pushl(&argv, "-C", repo, "sparse-checkout", "init", NULL);
+
+ /*
+ * We must apply the setting in the current process
+ * for the later checkout to use the sparse-checkout file.
+ */
+ core_apply_sparse_checkout = 1;
+
+ if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
+ error(_("failed to initialize sparse-checkout"));
+ result = 1;
+ }
+
+ strvec_clear(&argv);
+ return result;
+}
+
static int checkout(int submodule_progress)
{
struct object_id oid;
@@ -743,11 +784,11 @@ static int checkout(int submodule_progress)
if (!strcmp(head, "HEAD")) {
if (advice_detached_head)
detach_advice(oid_to_hex(&oid));
+ FREE_AND_NULL(head);
} else {
if (!starts_with(head, "refs/heads/"))
die(_("HEAD not found below refs/heads!"));
}
- free(head);
/* We need to be in the new work tree for the checkout */
setup_work_tree();
@@ -762,6 +803,7 @@ static int checkout(int submodule_progress)
opts.verbose_update = (option_verbosity >= 0);
opts.src_index = &the_index;
opts.dst_index = &the_index;
+ init_checkout_metadata(&opts.meta, head, &oid, NULL);
tree = parse_tree_indirect(&oid);
parse_tree(tree);
@@ -769,37 +811,67 @@ static int checkout(int submodule_progress)
if (unpack_trees(1, &t, &opts) < 0)
die(_("unable to checkout working tree"));
+ free(head);
+
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
- err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
+ err |= run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid),
oid_to_hex(&oid), "1", NULL);
if (!err && (option_recurse_submodules.nr > 0)) {
- struct argv_array args = ARGV_ARRAY_INIT;
- argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL);
+ struct strvec args = STRVEC_INIT;
+ strvec_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL);
if (option_shallow_submodules == 1)
- argv_array_push(&args, "--depth=1");
+ strvec_push(&args, "--depth=1");
if (max_jobs != -1)
- argv_array_pushf(&args, "--jobs=%d", max_jobs);
+ strvec_pushf(&args, "--jobs=%d", max_jobs);
if (submodule_progress)
- argv_array_push(&args, "--progress");
+ strvec_push(&args, "--progress");
if (option_verbosity < 0)
- argv_array_push(&args, "--quiet");
+ strvec_push(&args, "--quiet");
+
+ if (option_remote_submodules) {
+ strvec_push(&args, "--remote");
+ strvec_push(&args, "--no-fetch");
+ }
+
+ if (option_single_branch >= 0)
+ strvec_push(&args, option_single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
- err = run_command_v_opt(args.argv, RUN_GIT_CMD);
- argv_array_clear(&args);
+ err = run_command_v_opt(args.v, RUN_GIT_CMD);
+ strvec_clear(&args);
}
return err;
}
+static int git_clone_config(const char *k, const char *v, void *cb)
+{
+ if (!strcmp(k, "clone.defaultremotename")) {
+ free(remote_name);
+ remote_name = xstrdup(v);
+ }
+ return git_default_config(k, v, cb);
+}
+
static int write_one_config(const char *key, const char *value, void *data)
{
+ /*
+ * give git_clone_config a chance to write config values back to the
+ * environment, since git_config_set_multivar_gently only deals with
+ * config-file writes
+ */
+ int apply_failed = git_clone_config(key, value, data);
+ if (apply_failed)
+ return apply_failed;
+
return git_config_set_multivar_gently(key,
value ? value : "true",
CONFIG_REGEX_NONE, 0);
@@ -852,12 +924,12 @@ static void write_refspec_config(const char *src_ref_prefix,
}
/* Configure the remote */
if (value.len) {
- strbuf_addf(&key, "remote.%s.fetch", option_origin);
+ strbuf_addf(&key, "remote.%s.fetch", remote_name);
git_config_set_multivar(key.buf, value.buf, "^$", 0);
strbuf_reset(&key);
if (option_mirror) {
- strbuf_addf(&key, "remote.%s.mirror", option_origin);
+ strbuf_addf(&key, "remote.%s.mirror", remote_name);
git_config_set(key.buf, "true");
strbuf_reset(&key);
}
@@ -882,7 +954,7 @@ static void dissociate_from_references(void)
free(alternates);
}
-static int dir_exists(const char *path)
+static int path_exists(const char *path)
{
struct stat sb;
return !stat(path, &sb);
@@ -892,15 +964,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
{
int is_bundle = 0, is_local;
const char *repo_name, *repo, *work_tree, *git_dir;
- char *path, *dir;
- int dest_exists;
+ char *path, *dir, *display_repo = NULL;
+ int dest_exists, real_dest_exists = 0;
const struct ref *refs, *remote_head;
const struct ref *remote_head_points_at;
const struct ref *our_head_points_at;
struct ref *mapped_refs;
const struct ref *ref;
struct strbuf key = STRBUF_INIT;
- struct strbuf default_refspec = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;
const char *src_ref_prefix = "refs/heads/";
@@ -908,11 +979,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
int err = 0, complete_refs_before_fetch = 1;
int submodule_progress;
- struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
-
- fetch_if_missing = 0;
+ struct strvec ref_prefixes = STRVEC_INIT;
packet_trace_identity("clone");
+
+ git_config(git_clone_config, NULL);
+
argc = parse_options(argc, argv, prefix, builtin_clone_options,
builtin_clone_usage, 0);
@@ -941,18 +1013,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
option_no_checkout = 1;
}
- if (!option_origin)
- option_origin = "origin";
-
repo_name = argv[0];
path = get_repo_path(repo_name, &is_bundle);
if (path)
repo = absolute_pathdup(repo_name);
- else if (!strchr(repo_name, ':'))
- die(_("repository '%s' does not exist"), repo_name);
- else
+ else if (strchr(repo_name, ':')) {
repo = repo_name;
+ display_repo = transport_anonymize_url(repo);
+ } else
+ die(_("repository '%s' does not exist"), repo_name);
/* no need to be strict, transport_set_option() will validate it again */
if (option_depth && atoi(option_depth) < 1)
@@ -964,18 +1034,28 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
dir = guess_dir_name(repo_name, is_bundle, option_bare);
strip_trailing_slashes(dir);
- dest_exists = dir_exists(dir);
+ dest_exists = path_exists(dir);
if (dest_exists && !is_empty_dir(dir))
die(_("destination path '%s' already exists and is not "
"an empty directory."), dir);
- strbuf_addf(&reflog_msg, "clone: from %s", repo);
+ if (real_git_dir) {
+ real_dest_exists = path_exists(real_git_dir);
+ if (real_dest_exists && !is_empty_dir(real_git_dir))
+ die(_("repository path '%s' already exists and is not "
+ "an empty directory."), real_git_dir);
+ }
+
+
+ strbuf_addf(&reflog_msg, "clone: from %s",
+ display_repo ? display_repo : repo);
+ free(display_repo);
if (option_bare)
work_tree = NULL;
else {
work_tree = getenv("GIT_WORK_TREE");
- if (work_tree && dir_exists(work_tree))
+ if (work_tree && path_exists(work_tree))
die(_("working tree '%s' already exists."), work_tree);
}
@@ -1003,7 +1083,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
if (real_git_dir) {
- if (dir_exists(real_git_dir))
+ if (real_dest_exists)
junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL;
junk_git_dir = real_git_dir;
} else {
@@ -1057,14 +1137,36 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
}
- init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET);
+ init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL,
+ INIT_DB_QUIET);
if (real_git_dir)
git_dir = real_git_dir;
+ /*
+ * additional config can be injected with -c, make sure it's included
+ * after init_db, which clears the entire config environment.
+ */
write_config(&option_config);
- git_config(git_default_config, NULL);
+ /*
+ * re-read config after init_db and write_config to pick up any config
+ * injected by --template and --config, respectively.
+ */
+ git_config(git_clone_config, NULL);
+
+ /*
+ * apply the remote name provided by --origin only after this second
+ * call to git_config, to ensure it overrides all config-based values.
+ */
+ if (option_origin != NULL)
+ remote_name = xstrdup(option_origin);
+
+ if (remote_name == NULL)
+ remote_name = xstrdup("origin");
+
+ if (!valid_remote_name(remote_name))
+ die(_("'%s' is not a valid remote name"), remote_name);
if (option_bare) {
if (option_mirror)
@@ -1073,15 +1175,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
git_config_set("core.bare", "true");
} else {
- strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
+ strbuf_addf(&branch_top, "refs/remotes/%s/", remote_name);
}
- strbuf_addf(&key, "remote.%s.url", option_origin);
+ strbuf_addf(&key, "remote.%s.url", remote_name);
git_config_set(key.buf, repo);
strbuf_reset(&key);
if (option_no_tags) {
- strbuf_addf(&key, "remote.%s.tagOpt", option_origin);
+ strbuf_addf(&key, "remote.%s.tagOpt", remote_name);
git_config_set(key.buf, "--no-tags");
strbuf_reset(&key);
}
@@ -1089,11 +1191,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_required_reference.nr || option_optional_reference.nr)
setup_reference();
- remote = remote_get(option_origin);
+ if (option_sparse_checkout && git_sparse_checkout_init(dir))
+ return 1;
- strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix,
- branch_top.buf);
- refspec_append(&remote->fetch, default_refspec.buf);
+ remote = remote_get(remote_name);
+
+ refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix,
+ branch_top.buf);
transport = transport_get(remote, remote->url[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1142,29 +1246,36 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport->server_options = &server_options;
if (filter_options.choice) {
- struct strbuf expanded_filter_spec = STRBUF_INIT;
- expand_list_objects_filter_spec(&filter_options,
- &expanded_filter_spec);
+ const char *spec =
+ expand_list_objects_filter_spec(&filter_options);
transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
- expanded_filter_spec.buf);
+ spec);
transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
- strbuf_release(&expanded_filter_spec);
}
if (transport->smart_options && !deepen && !filter_options.choice)
transport->smart_options->check_self_contained_and_connected = 1;
- argv_array_push(&ref_prefixes, "HEAD");
+ strvec_push(&ref_prefixes, "HEAD");
refspec_ref_prefixes(&remote->fetch, &ref_prefixes);
if (option_branch)
expand_ref_prefix(&ref_prefixes, option_branch);
if (!option_no_tags)
- argv_array_push(&ref_prefixes, "refs/tags/");
+ strvec_push(&ref_prefixes, "refs/tags/");
refs = transport_get_remote_refs(transport, &ref_prefixes);
if (refs) {
+ int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
+
+ /*
+ * Now that we know what algorithm the remote side is using,
+ * let's set ours to the same thing.
+ */
+ initialize_repository_version(hash_algo, 1);
+ repo_set_hash_algo(the_repository, hash_algo);
+
mapped_refs = wanted_peer_refs(refs, &remote->fetch);
/*
* transport_get_remote_refs() may return refs with null sha-1
@@ -1195,7 +1306,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (!our_head_points_at)
die(_("Remote branch %s not found in upstream %s"),
- option_branch, option_origin);
+ option_branch, remote_name);
}
else
our_head_points_at = remote_head_points_at;
@@ -1203,7 +1314,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
else {
if (option_branch)
die(_("Remote branch %s not found in upstream %s"),
- option_branch, option_origin);
+ option_branch, remote_name);
warning(_("You appear to have cloned an empty repository."));
mapped_refs = NULL;
@@ -1211,16 +1322,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
remote_head_points_at = NULL;
remote_head = NULL;
option_no_checkout = 1;
- if (!option_bare)
- install_branch_config(0, "master", option_origin,
- "refs/heads/master");
+ if (!option_bare) {
+ const char *branch = git_default_branch_name();
+ char *ref = xstrfmt("refs/heads/%s", branch);
+
+ install_branch_config(0, branch, remote_name, ref);
+ free(ref);
+ }
}
write_refspec_config(src_ref_prefix, our_head_points_at,
remote_head_points_at, &branch_top);
if (filter_options.choice)
- partial_clone_register(option_origin, &filter_options);
+ partial_clone_register(remote_name, &filter_options);
if (is_local)
clone_local(path, git_dir);
@@ -1229,7 +1344,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
update_remote_refs(refs, mapped_refs, remote_head_points_at,
branch_top.buf, reflog_msg.buf, transport,
- !is_local, filter_options.choice);
+ !is_local);
update_head(our_head_points_at, remote_head, reflog_msg.buf);
@@ -1250,15 +1365,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
junk_mode = JUNK_LEAVE_REPO;
- fetch_if_missing = 1;
err = checkout(submodule_progress);
+ free(remote_name);
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
strbuf_release(&key);
- strbuf_release(&default_refspec);
junk_mode = JUNK_LEAVE_ALL;
- argv_array_clear(&ref_prefixes);
+ strvec_clear(&ref_prefixes);
return err;
}
diff --git a/builtin/column.c b/builtin/column.c
index 5228ccf37a..e815e148aa 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -43,7 +43,7 @@ int cmd_column(int argc, const char **argv, const char *prefix)
memset(&copts, 0, sizeof(copts));
copts.padding = 1;
- argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
+ argc = parse_options(argc, argv, prefix, options, builtin_column_usage, 0);
if (argc)
usage_with_options(builtin_column_usage, options);
if (real_command || command) {
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index d8efa5bab2..78fa08f43a 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -5,27 +5,29 @@
#include "parse-options.h"
#include "repository.h"
#include "commit-graph.h"
+#include "object-store.h"
+#include "progress.h"
+#include "tag.h"
static char const * const builtin_commit_graph_usage[] = {
- N_("git commit-graph [--object-dir <objdir>]"),
- N_("git commit-graph read [--object-dir <objdir>]"),
- N_("git commit-graph verify [--object-dir <objdir>]"),
- N_("git commit-graph write [--object-dir <objdir>] [--append] [--reachable|--stdin-packs|--stdin-commits]"),
+ N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
+ N_("git commit-graph write [--object-dir <objdir>] [--append] "
+ "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
+ "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
+ "<split options>"),
NULL
};
static const char * const builtin_commit_graph_verify_usage[] = {
- N_("git commit-graph verify [--object-dir <objdir>]"),
- NULL
-};
-
-static const char * const builtin_commit_graph_read_usage[] = {
- N_("git commit-graph read [--object-dir <objdir>]"),
+ N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"),
NULL
};
static const char * const builtin_commit_graph_write_usage[] = {
- N_("git commit-graph write [--object-dir <objdir>] [--append] [--reachable|--stdin-packs|--stdin-commits]"),
+ N_("git commit-graph write [--object-dir <objdir>] [--append] "
+ "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] "
+ "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] "
+ "<split options>"),
NULL
};
@@ -35,114 +37,173 @@ static struct opts_commit_graph {
int stdin_packs;
int stdin_commits;
int append;
+ int split;
+ int shallow;
+ int progress;
+ int enable_changed_paths;
} opts;
+static struct object_directory *find_odb(struct repository *r,
+ const char *obj_dir)
+{
+ struct object_directory *odb;
+ char *obj_dir_real = real_pathdup(obj_dir, 1);
+ struct strbuf odb_path_real = STRBUF_INIT;
+
+ prepare_alt_odb(r);
+ for (odb = r->objects->odb; odb; odb = odb->next) {
+ strbuf_realpath(&odb_path_real, odb->path, 1);
+ if (!strcmp(obj_dir_real, odb_path_real.buf))
+ break;
+ }
+
+ free(obj_dir_real);
+ strbuf_release(&odb_path_real);
+
+ if (!odb)
+ die(_("could not find object directory matching %s"), obj_dir);
+ return odb;
+}
static int graph_verify(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
+ struct object_directory *odb = NULL;
char *graph_name;
int open_ok;
int fd;
struct stat st;
+ int flags = 0;
static struct option builtin_commit_graph_verify_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
N_("The object directory to store the graph")),
+ OPT_BOOL(0, "shallow", &opts.shallow,
+ N_("if the commit-graph is split, only verify the tip file")),
+ OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
OPT_END(),
};
+ trace2_cmd_mode("verify");
+
+ opts.progress = isatty(2);
argc = parse_options(argc, argv, NULL,
builtin_commit_graph_verify_options,
builtin_commit_graph_verify_usage, 0);
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
+ if (opts.shallow)
+ flags |= COMMIT_GRAPH_VERIFY_SHALLOW;
+ if (opts.progress)
+ flags |= COMMIT_GRAPH_WRITE_PROGRESS;
- graph_name = get_commit_graph_filename(opts.obj_dir);
+ odb = find_odb(the_repository, opts.obj_dir);
+ graph_name = get_commit_graph_filename(odb);
open_ok = open_commit_graph(graph_name, &fd, &st);
- if (!open_ok && errno == ENOENT)
- return 0;
- if (!open_ok)
+ if (!open_ok && errno != ENOENT)
die_errno(_("Could not open commit-graph '%s'"), graph_name);
- graph = load_commit_graph_one_fd_st(fd, &st);
+
FREE_AND_NULL(graph_name);
+ if (open_ok)
+ graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb);
+ else
+ graph = read_commit_graph_one(the_repository, odb);
+
+ /* Return failure if open_ok predicted success */
if (!graph)
- return 1;
+ return !!open_ok;
UNLEAK(graph);
- return verify_commit_graph(the_repository, graph);
+ return verify_commit_graph(the_repository, graph, flags);
}
-static int graph_read(int argc, const char **argv)
+extern int read_replace_refs;
+static struct commit_graph_opts write_opts;
+
+static int write_option_parse_split(const struct option *opt, const char *arg,
+ int unset)
{
- struct commit_graph *graph = NULL;
- char *graph_name;
- int open_ok;
- int fd;
- struct stat st;
+ enum commit_graph_split_flags *flags = opt->value;
- static struct option builtin_commit_graph_read_options[] = {
- OPT_STRING(0, "object-dir", &opts.obj_dir,
- N_("dir"),
- N_("The object directory to store the graph")),
- OPT_END(),
- };
+ BUG_ON_OPT_NEG(unset);
- argc = parse_options(argc, argv, NULL,
- builtin_commit_graph_read_options,
- builtin_commit_graph_read_usage, 0);
+ opts.split = 1;
+ if (!arg)
+ return 0;
- if (!opts.obj_dir)
- opts.obj_dir = get_object_directory();
+ if (!strcmp(arg, "no-merge"))
+ *flags = COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED;
+ else if (!strcmp(arg, "replace"))
+ *flags = COMMIT_GRAPH_SPLIT_REPLACE;
+ else
+ die(_("unrecognized --split argument, %s"), arg);
- graph_name = get_commit_graph_filename(opts.obj_dir);
+ return 0;
+}
- open_ok = open_commit_graph(graph_name, &fd, &st);
- if (!open_ok)
- die_errno(_("Could not open commit-graph '%s'"), graph_name);
+static int read_one_commit(struct oidset *commits, struct progress *progress,
+ const char *hash)
+{
+ struct object *result;
+ struct object_id oid;
+ const char *end;
- graph = load_commit_graph_one_fd_st(fd, &st);
- if (!graph)
- return 1;
+ if (parse_oid_hex(hash, &oid, &end))
+ return error(_("unexpected non-hex object ID: %s"), hash);
- FREE_AND_NULL(graph_name);
+ result = deref_tag(the_repository, parse_object(the_repository, &oid),
+ NULL, 0);
+ if (!result)
+ return error(_("invalid object: %s"), hash);
+ else if (object_as_type(result, OBJ_COMMIT, 1))
+ oidset_insert(commits, &result->oid);
- printf("header: %08x %d %d %d %d\n",
- ntohl(*(uint32_t*)graph->data),
- *(unsigned char*)(graph->data + 4),
- *(unsigned char*)(graph->data + 5),
- *(unsigned char*)(graph->data + 6),
- *(unsigned char*)(graph->data + 7));
- printf("num_commits: %u\n", graph->num_commits);
- printf("chunks:");
-
- if (graph->chunk_oid_fanout)
- printf(" oid_fanout");
- if (graph->chunk_oid_lookup)
- printf(" oid_lookup");
- if (graph->chunk_commit_data)
- printf(" commit_metadata");
- if (graph->chunk_extra_edges)
- printf(" extra_edges");
- printf("\n");
+ display_progress(progress, oidset_size(commits));
- UNLEAK(graph);
+ return 0;
+}
+static int write_option_max_new_filters(const struct option *opt,
+ const char *arg,
+ int unset)
+{
+ int *to = opt->value;
+ if (unset)
+ *to = -1;
+ else {
+ const char *s;
+ *to = strtol(arg, (char **)&s, 10);
+ if (*s)
+ return error(_("%s expects a numerical value"),
+ optname(opt, opt->flags));
+ }
return 0;
}
-extern int read_replace_refs;
+static int git_commit_graph_write_config(const char *var, const char *value,
+ void *cb)
+{
+ if (!strcmp(var, "commitgraph.maxnewfilters"))
+ write_opts.max_new_filters = git_config_int(var, value);
+ /*
+ * No need to fall-back to 'git_default_config', since this was already
+ * called in 'cmd_commit_graph()'.
+ */
+ return 0;
+}
static int graph_write(int argc, const char **argv)
{
- struct string_list *pack_indexes = NULL;
- struct string_list *commit_hex = NULL;
- struct string_list lines;
+ struct string_list pack_indexes = STRING_LIST_INIT_NODUP;
+ struct strbuf buf = STRBUF_INIT;
+ struct oidset commits = OIDSET_INIT;
+ struct object_directory *odb = NULL;
int result = 0;
- unsigned int flags = COMMIT_GRAPH_PROGRESS;
+ enum commit_graph_write_flags flags = 0;
+ struct progress *progress = NULL;
static struct option builtin_commit_graph_write_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
@@ -156,9 +217,36 @@ static int graph_write(int argc, const char **argv)
N_("start walk at commits listed by stdin")),
OPT_BOOL(0, "append", &opts.append,
N_("include all commits already in the commit-graph file")),
+ OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths,
+ N_("enable computation for changed paths")),
+ OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
+ OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL,
+ N_("allow writing an incremental commit-graph file"),
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
+ write_option_parse_split),
+ OPT_INTEGER(0, "max-commits", &write_opts.max_commits,
+ N_("maximum number of commits in a non-base split commit-graph")),
+ OPT_INTEGER(0, "size-multiple", &write_opts.size_multiple,
+ N_("maximum ratio between two levels of a split commit-graph")),
+ OPT_EXPIRY_DATE(0, "expire-time", &write_opts.expire_time,
+ N_("only expire files older than a given date-time")),
+ OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters,
+ NULL, N_("maximum number of changed-path Bloom filters to compute"),
+ 0, write_option_max_new_filters),
OPT_END(),
};
+ opts.progress = isatty(2);
+ opts.enable_changed_paths = -1;
+ write_opts.size_multiple = 2;
+ write_opts.max_commits = 0;
+ write_opts.expire_time = 0;
+ write_opts.max_new_filters = -1;
+
+ trace2_cmd_mode("write");
+
+ git_config(git_commit_graph_write_config, &opts);
+
argc = parse_options(argc, argv, NULL,
builtin_commit_graph_write_options,
builtin_commit_graph_write_usage, 0);
@@ -168,35 +256,56 @@ static int graph_write(int argc, const char **argv)
if (!opts.obj_dir)
opts.obj_dir = get_object_directory();
if (opts.append)
- flags |= COMMIT_GRAPH_APPEND;
+ flags |= COMMIT_GRAPH_WRITE_APPEND;
+ if (opts.split)
+ flags |= COMMIT_GRAPH_WRITE_SPLIT;
+ if (opts.progress)
+ flags |= COMMIT_GRAPH_WRITE_PROGRESS;
+ if (!opts.enable_changed_paths)
+ flags |= COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS;
+ if (opts.enable_changed_paths == 1 ||
+ git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0))
+ flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS;
read_replace_refs = 0;
+ odb = find_odb(the_repository, opts.obj_dir);
- if (opts.reachable)
- return write_commit_graph_reachable(opts.obj_dir, flags);
-
- string_list_init(&lines, 0);
- if (opts.stdin_packs || opts.stdin_commits) {
- struct strbuf buf = STRBUF_INIT;
+ if (opts.reachable) {
+ if (write_commit_graph_reachable(odb, flags, &write_opts))
+ return 1;
+ return 0;
+ }
+ if (opts.stdin_packs) {
while (strbuf_getline(&buf, stdin) != EOF)
- string_list_append(&lines, strbuf_detach(&buf, NULL));
-
- if (opts.stdin_packs)
- pack_indexes = &lines;
- if (opts.stdin_commits)
- commit_hex = &lines;
-
- UNLEAK(buf);
+ string_list_append(&pack_indexes,
+ strbuf_detach(&buf, NULL));
+ } else if (opts.stdin_commits) {
+ oidset_init(&commits, 0);
+ if (opts.progress)
+ progress = start_delayed_progress(
+ _("Collecting commits from input"), 0);
+
+ while (strbuf_getline(&buf, stdin) != EOF) {
+ if (read_one_commit(&commits, progress, buf.buf)) {
+ result = 1;
+ goto cleanup;
+ }
+ }
+
+ stop_progress(&progress);
}
- if (write_commit_graph(opts.obj_dir,
- pack_indexes,
- commit_hex,
- flags))
+ if (write_commit_graph(odb,
+ opts.stdin_packs ? &pack_indexes : NULL,
+ opts.stdin_commits ? &commits : NULL,
+ flags,
+ &write_opts))
result = 1;
- UNLEAK(lines);
+cleanup:
+ string_list_clear(&pack_indexes, 0);
+ strbuf_release(&buf);
return result;
}
@@ -219,9 +328,9 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix)
builtin_commit_graph_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
+ save_commit_buffer = 0;
+
if (argc > 0) {
- if (!strcmp(argv[0], "read"))
- return graph_read(argc, argv);
if (!strcmp(argv[0], "verify"))
return graph_verify(argc, argv);
if (!strcmp(argv[0], "write"))
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index b866d83951..1031b9a491 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -108,15 +108,15 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
struct object_id commit_oid;
struct option options[] = {
- { OPTION_CALLBACK, 'p', NULL, &parents, N_("parent"),
+ OPT_CALLBACK_F('p', NULL, &parents, N_("parent"),
N_("id of a parent commit object"), PARSE_OPT_NONEG,
- parse_parent_arg_callback },
- { OPTION_CALLBACK, 'm', NULL, &buffer, N_("message"),
+ parse_parent_arg_callback),
+ OPT_CALLBACK_F('m', NULL, &buffer, N_("message"),
N_("commit message"), PARSE_OPT_NONEG,
- parse_message_arg_callback },
- { OPTION_CALLBACK, 'F', NULL, &buffer, N_("file"),
+ parse_message_arg_callback),
+ OPT_CALLBACK_F('F', NULL, &buffer, N_("file"),
N_("read commit log message from file"), PARSE_OPT_NONEG,
- parse_file_arg_callback },
+ parse_file_arg_callback),
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
OPT_END()
diff --git a/builtin/commit.c b/builtin/commit.c
index 1921401117..505fe60956 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -59,16 +59,22 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\
" git commit --allow-empty\n"
"\n");
+static const char empty_rebase_pick_advice[] =
+N_("Otherwise, please use 'git rebase --skip'\n");
+
static const char empty_cherry_pick_advice_single[] =
-N_("Otherwise, please use 'git reset'\n");
+N_("Otherwise, please use 'git cherry-pick --skip'\n");
static const char empty_cherry_pick_advice_multi[] =
-N_("If you wish to skip this commit, use:\n"
+N_("and then use:\n"
+"\n"
+" git cherry-pick --continue\n"
"\n"
-" git reset\n"
+"to resume cherry-picking the remaining commits.\n"
+"If you wish to skip this commit, use:\n"
"\n"
-"Then \"git cherry-pick --continue\" will resume cherry-picking\n"
-"the remaining commits.\n");
+" git cherry-pick --skip\n"
+"\n");
static const char *color_status_slots[] = {
[WT_STATUS_HEADER] = "header",
@@ -104,9 +110,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff;
static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int config_commit_verbose = -1; /* unspecified */
-static int no_post_rewrite, allow_empty_message;
+static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit;
+static char *sign_commit, *pathspec_from_file;
/*
* The default commit message cleanup mode will remove the lines
@@ -119,7 +125,6 @@ static enum commit_msg_cleanup_mode cleanup_mode;
static const char *cleanup_arg;
static enum commit_whence whence;
-static int sequencer_in_use;
static int use_editor = 1, include_status = 1;
static int have_option_m;
static struct strbuf message = STRBUF_INIT;
@@ -176,12 +181,7 @@ static void determine_whence(struct wt_status *s)
{
if (file_exists(git_path_merge_head(the_repository)))
whence = FROM_MERGE;
- else if (file_exists(git_path_cherry_pick_head(the_repository))) {
- whence = FROM_CHERRY_PICK;
- if (file_exists(git_path_seq_dir()))
- sequencer_in_use = 1;
- }
- else
+ else if (!sequencer_determine_whence(the_repository, &whence))
whence = FROM_COMMIT;
if (s)
s->whence = whence;
@@ -326,7 +326,7 @@ static void refresh_cache_or_die(int refresh_flags)
die_resolve_conflict("commit");
}
-static const char *prepare_index(int argc, const char **argv, const char *prefix,
+static const char *prepare_index(const char **argv, const char *prefix,
const struct commit *current_head, int is_status)
{
struct string_list partial = STRING_LIST_INIT_DUP;
@@ -340,11 +340,31 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
PATHSPEC_PREFER_FULL,
prefix, argv);
+ if (pathspec_from_file) {
+ if (interactive)
+ die(_("--pathspec-from-file is incompatible with --interactive/--patch"));
+
+ if (all)
+ die(_("--pathspec-from-file with -a does not make sense"));
+
+ if (pathspec.nr)
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+ parse_pathspec_file(&pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ prefix, pathspec_from_file, pathspec_file_nul);
+ } else if (pathspec_file_nul) {
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
+ }
+
+ if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
+ die(_("No paths with --include/--only does not make sense."));
+
if (read_cache_preload(&pathspec) < 0)
die(_("index file corrupt"));
if (interactive) {
- char *old_index_env = NULL;
+ char *old_index_env = NULL, *old_repo_index_file;
hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
refresh_cache_or_die(refresh_flags);
@@ -352,12 +372,16 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
if (write_locked_index(&the_index, &index_lock, 0))
die(_("unable to create temporary index"));
+ old_repo_index_file = the_repository->index_file;
+ the_repository->index_file =
+ (char *)get_lock_file_path(&index_lock);
old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
- setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1);
+ setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
- if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
+ if (interactive_add(argv, prefix, patch_interactive) != 0)
die(_("interactive add failed"));
+ the_repository->index_file = old_repo_index_file;
if (old_index_env && *old_index_env)
setenv(INDEX_ENVIRONMENT, old_index_env, 1);
else
@@ -450,8 +474,10 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
if (whence != FROM_COMMIT) {
if (whence == FROM_MERGE)
die(_("cannot do a partial commit during a merge."));
- else if (whence == FROM_CHERRY_PICK)
+ else if (is_from_cherry_pick(whence))
die(_("cannot do a partial commit during a cherry-pick."));
+ else if (is_from_rebase(whence))
+ die(_("cannot do a partial commit during a rebase."));
}
if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec))
@@ -507,7 +533,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
s->nowarn = nowarn;
s->is_initial = get_oid(s->reference, &oid) ? 1 : 0;
if (!s->is_initial)
- hashcpy(s->sha1_commit, oid.hash);
+ oidcpy(&s->oid_commit, &oid);
s->status_format = status_format;
s->ignore_submodule_arg = ignore_submodule_arg;
@@ -534,7 +560,7 @@ static void export_one(const char *var, const char *s, const char *e, int hack)
struct strbuf buf = STRBUF_INIT;
if (hack)
strbuf_addch(&buf, hack);
- strbuf_addf(&buf, "%.*s", (int)(e - s), s);
+ strbuf_add(&buf, s, e - s);
setenv(var, buf.buf, 1);
strbuf_release(&buf);
}
@@ -768,7 +794,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
else if (whence == FROM_MERGE)
hook_arg1 = "merge";
- else if (whence == FROM_CHERRY_PICK) {
+ else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) {
hook_arg1 = "commit";
hook_arg2 = "CHERRY_PICK_HEAD";
}
@@ -821,21 +847,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
!merge_contains_scissors)
wt_status_add_cut_line(s->fp);
- status_printf_ln(s, GIT_COLOR_NORMAL,
- whence == FROM_MERGE
- ? _("\n"
- "It looks like you may be committing a merge.\n"
- "If this is not correct, please remove the file\n"
- " %s\n"
- "and try again.\n")
- : _("\n"
- "It looks like you may be committing a cherry-pick.\n"
- "If this is not correct, please remove the file\n"
- " %s\n"
- "and try again.\n"),
+ status_printf_ln(
+ s, GIT_COLOR_NORMAL,
whence == FROM_MERGE ?
- git_path_merge_head(the_repository) :
- git_path_cherry_pick_head(the_repository));
+ _("\n"
+ "It looks like you may be committing a merge.\n"
+ "If this is not correct, please run\n"
+ " git update-ref -d MERGE_HEAD\n"
+ "and try again.\n") :
+ _("\n"
+ "It looks like you may be committing a cherry-pick.\n"
+ "If this is not correct, please run\n"
+ " git update-ref -d CHERRY_PICK_HEAD\n"
+ "and try again.\n"));
}
fprintf(s->fp, "\n");
@@ -941,16 +965,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
if (!committable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
+ s->hints = advice_status_hints;
s->display_comment_prefix = old_display_comment_prefix;
run_status(stdout, index_file, prefix, 0, s);
if (amend)
fputs(_(empty_amend_advice), stderr);
- else if (whence == FROM_CHERRY_PICK) {
+ else if (is_from_cherry_pick(whence) ||
+ whence == FROM_REBASE_PICK) {
fputs(_(empty_cherry_pick_advice), stderr);
- if (!sequencer_in_use)
+ if (whence == FROM_CHERRY_PICK_SINGLE)
fputs(_(empty_cherry_pick_advice_single), stderr);
- else
+ else if (whence == FROM_CHERRY_PICK_MULTI)
fputs(_(empty_cherry_pick_advice_multi), stderr);
+ else
+ fputs(_(empty_rebase_pick_advice), stderr);
}
return 0;
}
@@ -975,15 +1003,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
return 0;
if (use_editor) {
- struct argv_array env = ARGV_ARRAY_INIT;
+ struct strvec env = STRVEC_INIT;
- argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file);
- if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) {
+ strvec_pushf(&env, "GIT_INDEX_FILE=%s", index_file);
+ if (launch_editor(git_path_commit_editmsg(), NULL, env.v)) {
fprintf(stderr,
_("Please supply the message using either -m or -F option.\n"));
exit(1);
}
- argv_array_clear(&env);
+ strvec_clear(&env);
}
if (!no_verify &&
@@ -1078,9 +1106,11 @@ static const char *read_commit_message(const char *name)
static struct status_deferred_config {
enum wt_status_format status_format;
int show_branch;
+ enum ahead_behind_flags ahead_behind;
} status_deferred_config = {
STATUS_FORMAT_UNSPECIFIED,
- -1 /* unspecified */
+ -1, /* unspecified */
+ AHEAD_BEHIND_UNSPECIFIED,
};
static void finalize_deferred_config(struct wt_status *s)
@@ -1107,6 +1137,17 @@ static void finalize_deferred_config(struct wt_status *s)
if (s->show_branch < 0)
s->show_branch = 0;
+ /*
+ * If the user did not give a "--[no]-ahead-behind" command
+ * line argument *AND* we will print in a human-readable format
+ * (short, long etc.) then we inherit from the status.aheadbehind
+ * config setting. In all other cases (and porcelain V[12] formats
+ * in particular), we inherit _FULL for backwards compatibility.
+ */
+ if (use_deferred_config &&
+ s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
+ s->ahead_behind_flags = status_deferred_config.ahead_behind;
+
if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
s->ahead_behind_flags = AHEAD_BEHIND_FULL;
}
@@ -1140,8 +1181,10 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (amend && whence != FROM_COMMIT) {
if (whence == FROM_MERGE)
die(_("You are in the middle of a merge -- cannot amend."));
- else if (whence == FROM_CHERRY_PICK)
+ else if (is_from_cherry_pick(whence))
die(_("You are in the middle of a cherry-pick -- cannot amend."));
+ else if (whence == FROM_REBASE_PICK)
+ die(_("You are in the middle of a rebase -- cannot amend."));
}
if (fixup_message && squash_message)
die(_("Options --squash and --fixup cannot be used together"));
@@ -1163,7 +1206,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
use_message = edit_message;
if (amend && !use_message && !fixup_message)
use_message = "HEAD";
- if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
+ if (!use_message && !is_from_cherry_pick(whence) &&
+ !is_from_rebase(whence) && renew_authorship)
die(_("--reset-author can be used only with -C, -c or --amend."));
if (use_message) {
use_message_buffer = read_commit_message(use_message);
@@ -1172,7 +1216,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
author_message_buffer = use_message_buffer;
}
}
- if (whence == FROM_CHERRY_PICK && !renew_authorship) {
+ if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) &&
+ !renew_authorship) {
author_message = "CHERRY_PICK_HEAD";
author_message_buffer = read_commit_message(author_message);
}
@@ -1182,8 +1227,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (also + only + all + interactive > 1)
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
- if (argc == 0 && (also || (only && !amend && !allow_empty)))
- die(_("No paths with --include/--only does not make sense."));
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
handle_untracked_files_arg(s);
@@ -1198,13 +1241,13 @@ static int parse_and_validate_options(int argc, const char *argv[],
return argc;
}
-static int dry_run_commit(int argc, const char **argv, const char *prefix,
+static int dry_run_commit(const char **argv, const char *prefix,
const struct commit *current_head, struct wt_status *s)
{
int committable;
const char *index_file;
- index_file = prepare_index(argc, argv, prefix, current_head, 1);
+ index_file = prepare_index(argv, prefix, current_head, 1);
committable = run_status(stdout, index_file, prefix, 0, s);
rollback_index_files();
@@ -1246,6 +1289,10 @@ static int git_status_config(const char *k, const char *v, void *cb)
status_deferred_config.show_branch = git_config_bool(k, v);
return 0;
}
+ if (!strcmp(k, "status.aheadbehind")) {
+ status_deferred_config.ahead_behind = git_config_bool(k, v);
+ return 0;
+ }
if (!strcmp(k, "status.showstash")) {
s->show_stash = git_config_bool(k, v);
return 0;
@@ -1323,9 +1370,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
N_("show stash information")),
OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
N_("compute full ahead/behind values")),
- { OPTION_CALLBACK, 0, "porcelain", &status_format,
+ OPT_CALLBACK_F(0, "porcelain", &status_format,
N_("version"), N_("machine-readable output"),
- PARSE_OPT_OPTARG, opt_parse_porcelain },
+ PARSE_OPT_OPTARG, opt_parse_porcelain),
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
@@ -1344,9 +1391,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")),
OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")),
- { OPTION_CALLBACK, 'M', "find-renames", &rename_score_arg,
+ OPT_CALLBACK_F('M', "find-renames", &rename_score_arg,
N_("n"), N_("detect renames, optionally set similarity index"),
- PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score },
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score),
OPT_END(),
};
@@ -1386,7 +1433,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
s.is_initial = get_oid(s.reference, &oid) ? 1 : 0;
if (!s.is_initial)
- hashcpy(s.sha1_commit, oid.hash);
+ oidcpy(&s.oid_commit, &oid);
s.ignore_submodule_arg = ignore_submodule_arg;
s.status_format = status_format;
@@ -1443,31 +1490,8 @@ static int git_commit_config(const char *k, const char *v, void *cb)
return git_status_config(k, v, s);
}
-int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
-{
- struct argv_array hook_env = ARGV_ARRAY_INIT;
- va_list args;
- int ret;
-
- argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
-
- /*
- * Let the hook know that no editor will be launched.
- */
- if (!editor_is_used)
- argv_array_push(&hook_env, "GIT_EDITOR=:");
-
- va_start(args, name);
- ret = run_hook_ve(hook_env.argv,name, args);
- va_end(args);
- argv_array_clear(&hook_env);
-
- return ret;
-}
-
int cmd_commit(int argc, const char **argv, const char *prefix)
{
- const char *argv_gc_auto[] = {"gc", "--auto", NULL};
static struct wt_status s;
static struct option builtin_commit_options[] = {
OPT__QUIET(&quiet, N_("suppress summary after successful commit")),
@@ -1483,7 +1507,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
- OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")),
+ OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
OPT_CLEANUP(&cleanup_arg),
@@ -1515,6 +1539,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
/* end commit contents options */
OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
@@ -1558,8 +1584,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose;
if (dry_run)
- return dry_run_commit(argc, argv, prefix, current_head, &s);
- index_file = prepare_index(argc, argv, prefix, current_head, 0);
+ return dry_run_commit(argv, prefix, current_head, &s);
+ index_file = prepare_index(argv, prefix, current_head, 0);
/* Set up everything for writing the commit object. This includes
running hooks, writing the trees, and interacting with the user. */
@@ -1608,8 +1634,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
reduce_heads_replace(&parents);
} else {
if (!reflog_msg)
- reflog_msg = (whence == FROM_CHERRY_PICK)
+ reflog_msg = is_from_cherry_pick(whence)
? "commit (cherry-pick)"
+ : is_from_rebase(whence)
+ ? "commit (rebase)"
: "commit";
commit_list_insert(current_head, &parents);
}
@@ -1636,7 +1664,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
}
if (amend) {
- const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+ const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL };
extra = read_commit_extra_headers(current_head, exclude_gpgsig);
} else {
struct commit_extra_header **tail = &extra;
@@ -1644,8 +1672,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
}
if (commit_tree_extended(sb.buf, sb.len, &active_cache_tree->oid,
- parents, &oid, author_ident.buf, sign_commit,
- extra)) {
+ parents, &oid, author_ident.buf, NULL,
+ sign_commit, extra)) {
rollback_index_files();
die(_("failed to write commit object"));
}
@@ -1658,7 +1686,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
die("%s", err.buf);
}
- sequencer_post_commit_cleanup(the_repository);
+ sequencer_post_commit_cleanup(the_repository, 0);
unlink(git_path_merge_head(the_repository));
unlink(git_path_merge_msg(the_repository));
unlink(git_path_merge_mode(the_repository));
@@ -1667,14 +1695,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (commit_index_files())
die(_("repository has been updated, but unable to write\n"
"new_index file. Check that disk is not full and quota is\n"
- "not exceeded, and then \"git reset HEAD\" to recover."));
+ "not exceeded, and then \"git restore --staged :/\" to recover."));
- if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) &&
- write_commit_graph_reachable(get_object_directory(), 0))
- return 1;
+ git_test_write_commit_graph_or_die();
repo_rerere(the_repository, 0);
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ run_auto_maintenance(quiet);
run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
if (amend && !no_post_rewrite) {
commit_post_rewrite(the_repository, current_head, &oid);
@@ -1690,6 +1716,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
&oid, flags);
}
+ apply_autostash(git_path_merge_autostash(the_repository));
+
UNLEAK(err);
UNLEAK(sb);
return 0;
diff --git a/builtin/config.c b/builtin/config.c
index 98d65bc0ad..963d65fd3f 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -29,10 +29,11 @@ static int use_worktree_config;
static struct git_config_source given_config_source;
static int actions, type;
static char *default_value;
-static int end_null;
+static int end_nul;
static int respect_includes_opt = -1;
static struct config_options config_options;
static int show_origin;
+static int show_scope;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
@@ -64,6 +65,7 @@ static int show_origin;
#define TYPE_PATH 4
#define TYPE_EXPIRY_DATE 5
#define TYPE_COLOR 6
+#define TYPE_BOOL_OR_STR 7
#define OPT_CALLBACK_VALUE(s, l, v, h, i) \
{ OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \
@@ -93,6 +95,8 @@ static int option_parse_type(const struct option *opt, const char *arg,
new_type = TYPE_INT;
else if (!strcmp(arg, "bool-or-int"))
new_type = TYPE_BOOL_OR_INT;
+ else if (!strcmp(arg, "bool-or-str"))
+ new_type = TYPE_BOOL_OR_STR;
else if (!strcmp(arg, "path"))
new_type = TYPE_PATH;
else if (!strcmp(arg, "expiry-date"))
@@ -148,13 +152,15 @@ static struct option builtin_config_options[] = {
OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL),
OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT),
OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
+ OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR),
OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH),
OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
OPT_GROUP(N_("Other")),
- OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
+ OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")),
OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
+ OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")),
OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
OPT_END(),
};
@@ -178,22 +184,34 @@ static void check_argc(int argc, int min, int max)
static void show_config_origin(struct strbuf *buf)
{
- const char term = end_null ? '\0' : '\t';
+ const char term = end_nul ? '\0' : '\t';
strbuf_addstr(buf, current_config_origin_type());
strbuf_addch(buf, ':');
- if (end_null)
+ if (end_nul)
strbuf_addstr(buf, current_config_name());
else
quote_c_style(current_config_name(), buf, NULL, 0);
strbuf_addch(buf, term);
}
+static void show_config_scope(struct strbuf *buf)
+{
+ const char term = end_nul ? '\0' : '\t';
+ const char *scope = config_scope_name(current_config_scope());
+
+ strbuf_addstr(buf, N_(scope));
+ strbuf_addch(buf, term);
+}
+
static int show_all_config(const char *key_, const char *value_, void *cb)
{
- if (show_origin) {
+ if (show_origin || show_scope) {
struct strbuf buf = STRBUF_INIT;
- show_config_origin(&buf);
+ if (show_scope)
+ show_config_scope(&buf);
+ if (show_origin)
+ show_config_origin(&buf);
/* Use fwrite as "buf" can contain \0's if "end_null" is set. */
fwrite(buf.buf, 1, buf.len, stdout);
strbuf_release(&buf);
@@ -213,6 +231,8 @@ struct strbuf_list {
static int format_config(struct strbuf *buf, const char *key_, const char *value_)
{
+ if (show_scope)
+ show_config_scope(buf);
if (show_origin)
show_config_origin(buf);
if (show_keys)
@@ -234,6 +254,12 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value
strbuf_addstr(buf, v ? "true" : "false");
else
strbuf_addf(buf, "%d", v);
+ } else if (type == TYPE_BOOL_OR_STR) {
+ int v = git_parse_maybe_bool(value_);
+ if (v < 0)
+ strbuf_addstr(buf, value_);
+ else
+ strbuf_addstr(buf, v ? "true" : "false");
} else if (type == TYPE_PATH) {
const char *v;
if (git_config_pathname(&v, key_, value_) < 0)
@@ -395,6 +421,13 @@ static char *normalize_value(const char *key, const char *value)
else
return xstrdup(v ? "true" : "false");
}
+ if (type == TYPE_BOOL_OR_STR) {
+ int v = git_parse_maybe_bool(value);
+ if (v < 0)
+ return xstrdup(value);
+ else
+ return xstrdup(v ? "true" : "false");
+ }
if (type == TYPE_COLOR) {
char v[COLOR_MAXLEN];
if (git_config_color(v, key, value))
@@ -612,16 +645,21 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config();
}
- if (use_local_config && nongit)
- die(_("--local can only be used inside a git repository"));
+ if (nongit) {
+ if (use_local_config)
+ die(_("--local can only be used inside a git repository"));
+ if (given_config_source.blob)
+ die(_("--blob can only be used inside a git repository"));
+ if (use_worktree_config)
+ die(_("--worktree can only be used inside a git repository"));
- if (given_config_source.blob && nongit)
- die(_("--blob can only be used inside a git repository"));
+ }
if (given_config_source.file &&
!strcmp(given_config_source.file, "-")) {
given_config_source.file = NULL;
given_config_source.use_stdin = 1;
+ given_config_source.scope = CONFIG_SCOPE_COMMAND;
}
if (use_global_config) {
@@ -637,6 +675,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
*/
die(_("$HOME not set"));
+ given_config_source.scope = CONFIG_SCOPE_GLOBAL;
+
if (access_or_warn(user_config, R_OK, 0) &&
xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
given_config_source.file = xdg_config;
@@ -646,12 +686,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
free(xdg_config);
}
}
- else if (use_system_config)
+ else if (use_system_config) {
given_config_source.file = git_etc_gitconfig();
- else if (use_local_config)
+ given_config_source.scope = CONFIG_SCOPE_SYSTEM;
+ } else if (use_local_config) {
given_config_source.file = git_pathdup("config");
- else if (use_worktree_config) {
- struct worktree **worktrees = get_worktrees(0);
+ given_config_source.scope = CONFIG_SCOPE_LOCAL;
+ } else if (use_worktree_config) {
+ struct worktree **worktrees = get_worktrees();
if (repository_format_worktree_config)
given_config_source.file = git_pathdup("config.worktree");
else if (worktrees[0] && worktrees[1])
@@ -662,13 +704,18 @@ int cmd_config(int argc, const char **argv, const char *prefix)
"section in \"git help worktree\" for details"));
else
given_config_source.file = git_pathdup("config");
+ given_config_source.scope = CONFIG_SCOPE_LOCAL;
free_worktrees(worktrees);
} else if (given_config_source.file) {
if (!is_absolute_path(given_config_source.file) && prefix)
given_config_source.file =
prefix_filename(prefix, given_config_source.file);
+ given_config_source.scope = CONFIG_SCOPE_COMMAND;
+ } else if (given_config_source.blob) {
+ given_config_source.scope = CONFIG_SCOPE_COMMAND;
}
+
if (respect_includes_opt == -1)
config_options.respect_includes = !given_config_source.file;
else
@@ -678,7 +725,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
config_options.git_dir = get_git_dir();
}
- if (end_null) {
+ if (end_nul) {
term = '\0';
delim = '\n';
key_delim = '\n';
diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c
new file mode 100644
index 0000000000..c61f123a3b
--- /dev/null
+++ b/builtin/credential-cache--daemon.c
@@ -0,0 +1,318 @@
+#include "builtin.h"
+#include "parse-options.h"
+
+#ifndef NO_UNIX_SOCKETS
+
+#include "config.h"
+#include "tempfile.h"
+#include "credential.h"
+#include "unix-socket.h"
+
+struct credential_cache_entry {
+ struct credential item;
+ timestamp_t expiration;
+};
+static struct credential_cache_entry *entries;
+static int entries_nr;
+static int entries_alloc;
+
+static void cache_credential(struct credential *c, int timeout)
+{
+ struct credential_cache_entry *e;
+
+ ALLOC_GROW(entries, entries_nr + 1, entries_alloc);
+ e = &entries[entries_nr++];
+
+ /* take ownership of pointers */
+ memcpy(&e->item, c, sizeof(*c));
+ memset(c, 0, sizeof(*c));
+ e->expiration = time(NULL) + timeout;
+}
+
+static struct credential_cache_entry *lookup_credential(const struct credential *c)
+{
+ int i;
+ for (i = 0; i < entries_nr; i++) {
+ struct credential *e = &entries[i].item;
+ if (credential_match(c, e))
+ return &entries[i];
+ }
+ return NULL;
+}
+
+static void remove_credential(const struct credential *c)
+{
+ struct credential_cache_entry *e;
+
+ e = lookup_credential(c);
+ if (e)
+ e->expiration = 0;
+}
+
+static timestamp_t check_expirations(void)
+{
+ static timestamp_t wait_for_entry_until;
+ int i = 0;
+ timestamp_t now = time(NULL);
+ timestamp_t next = TIME_MAX;
+
+ /*
+ * Initially give the client 30 seconds to actually contact us
+ * and store a credential before we decide there's no point in
+ * keeping the daemon around.
+ */
+ if (!wait_for_entry_until)
+ wait_for_entry_until = now + 30;
+
+ while (i < entries_nr) {
+ if (entries[i].expiration <= now) {
+ entries_nr--;
+ credential_clear(&entries[i].item);
+ if (i != entries_nr)
+ memcpy(&entries[i], &entries[entries_nr], sizeof(*entries));
+ /*
+ * Stick around 30 seconds in case a new credential
+ * shows up (e.g., because we just removed a failed
+ * one, and we will soon get the correct one).
+ */
+ wait_for_entry_until = now + 30;
+ }
+ else {
+ if (entries[i].expiration < next)
+ next = entries[i].expiration;
+ i++;
+ }
+ }
+
+ if (!entries_nr) {
+ if (wait_for_entry_until <= now)
+ return 0;
+ next = wait_for_entry_until;
+ }
+
+ return next - now;
+}
+
+static int read_request(FILE *fh, struct credential *c,
+ struct strbuf *action, int *timeout)
+{
+ static struct strbuf item = STRBUF_INIT;
+ const char *p;
+
+ strbuf_getline_lf(&item, fh);
+ if (!skip_prefix(item.buf, "action=", &p))
+ return error("client sent bogus action line: %s", item.buf);
+ strbuf_addstr(action, p);
+
+ strbuf_getline_lf(&item, fh);
+ if (!skip_prefix(item.buf, "timeout=", &p))
+ return error("client sent bogus timeout line: %s", item.buf);
+ *timeout = atoi(p);
+
+ if (credential_read(c, fh) < 0)
+ return -1;
+ return 0;
+}
+
+static void serve_one_client(FILE *in, FILE *out)
+{
+ struct credential c = CREDENTIAL_INIT;
+ struct strbuf action = STRBUF_INIT;
+ int timeout = -1;
+
+ if (read_request(in, &c, &action, &timeout) < 0)
+ /* ignore error */ ;
+ else if (!strcmp(action.buf, "get")) {
+ struct credential_cache_entry *e = lookup_credential(&c);
+ if (e) {
+ fprintf(out, "username=%s\n", e->item.username);
+ fprintf(out, "password=%s\n", e->item.password);
+ }
+ }
+ else if (!strcmp(action.buf, "exit")) {
+ /*
+ * It's important that we clean up our socket first, and then
+ * signal the client only once we have finished the cleanup.
+ * Calling exit() directly does this, because we clean up in
+ * our atexit() handler, and then signal the client when our
+ * process actually ends, which closes the socket and gives
+ * them EOF.
+ */
+ exit(0);
+ }
+ else if (!strcmp(action.buf, "erase"))
+ remove_credential(&c);
+ else if (!strcmp(action.buf, "store")) {
+ if (timeout < 0)
+ warning("cache client didn't specify a timeout");
+ else if (!c.username || !c.password)
+ warning("cache client gave us a partial credential");
+ else {
+ remove_credential(&c);
+ cache_credential(&c, timeout);
+ }
+ }
+ else
+ warning("cache client sent unknown action: %s", action.buf);
+
+ credential_clear(&c);
+ strbuf_release(&action);
+}
+
+static int serve_cache_loop(int fd)
+{
+ struct pollfd pfd;
+ timestamp_t wakeup;
+
+ wakeup = check_expirations();
+ if (!wakeup)
+ return 0;
+
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+ if (poll(&pfd, 1, 1000 * wakeup) < 0) {
+ if (errno != EINTR)
+ die_errno("poll failed");
+ return 1;
+ }
+
+ if (pfd.revents & POLLIN) {
+ int client, client2;
+ FILE *in, *out;
+
+ client = accept(fd, NULL, NULL);
+ if (client < 0) {
+ warning_errno("accept failed");
+ return 1;
+ }
+ client2 = dup(client);
+ if (client2 < 0) {
+ warning_errno("dup failed");
+ close(client);
+ return 1;
+ }
+
+ in = xfdopen(client, "r");
+ out = xfdopen(client2, "w");
+ serve_one_client(in, out);
+ fclose(in);
+ fclose(out);
+ }
+ return 1;
+}
+
+static void serve_cache(const char *socket_path, int debug)
+{
+ int fd;
+
+ fd = unix_stream_listen(socket_path);
+ if (fd < 0)
+ die_errno("unable to bind to '%s'", socket_path);
+
+ printf("ok\n");
+ fclose(stdout);
+ if (!debug) {
+ if (!freopen("/dev/null", "w", stderr))
+ die_errno("unable to point stderr to /dev/null");
+ }
+
+ while (serve_cache_loop(fd))
+ ; /* nothing */
+
+ close(fd);
+}
+
+static const char permissions_advice[] = N_(
+"The permissions on your socket directory are too loose; other\n"
+"users may be able to read your cached credentials. Consider running:\n"
+"\n"
+" chmod 0700 %s");
+static void init_socket_directory(const char *path)
+{
+ struct stat st;
+ char *path_copy = xstrdup(path);
+ char *dir = dirname(path_copy);
+
+ if (!stat(dir, &st)) {
+ if (st.st_mode & 077)
+ die(_(permissions_advice), dir);
+ } else {
+ /*
+ * We must be sure to create the directory with the correct mode,
+ * not just chmod it after the fact; otherwise, there is a race
+ * condition in which somebody can chdir to it, sleep, then try to open
+ * our protected socket.
+ */
+ if (safe_create_leading_directories_const(dir) < 0)
+ die_errno("unable to create directories for '%s'", dir);
+ if (mkdir(dir, 0700) < 0)
+ die_errno("unable to mkdir '%s'", dir);
+ }
+
+ if (chdir(dir))
+ /*
+ * We don't actually care what our cwd is; we chdir here just to
+ * be a friendly daemon and avoid tying up our original cwd.
+ * If this fails, it's OK to just continue without that benefit.
+ */
+ ;
+
+ free(path_copy);
+}
+
+int cmd_credential_cache_daemon(int argc, const char **argv, const char *prefix)
+{
+ struct tempfile *socket_file;
+ const char *socket_path;
+ int ignore_sighup = 0;
+ static const char *usage[] = {
+ "git-credential-cache--daemon [opts] <socket_path>",
+ NULL
+ };
+ int debug = 0;
+ const struct option options[] = {
+ OPT_BOOL(0, "debug", &debug,
+ N_("print debugging messages to stderr")),
+ OPT_END()
+ };
+
+ git_config_get_bool("credentialcache.ignoresighup", &ignore_sighup);
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+ socket_path = argv[0];
+
+ if (!socket_path)
+ usage_with_options(usage, options);
+
+ if (!is_absolute_path(socket_path))
+ die("socket directory must be an absolute path");
+
+ init_socket_directory(socket_path);
+ socket_file = register_tempfile(socket_path);
+
+ if (ignore_sighup)
+ signal(SIGHUP, SIG_IGN);
+
+ serve_cache(socket_path, debug);
+ delete_tempfile(&socket_file);
+
+ return 0;
+}
+
+#else
+
+int cmd_credential_cache_daemon(int argc, const char **argv, const char *prefix)
+{
+ const char * const usage[] = {
+ "git credential-cache--daemon [options] <action>",
+ "",
+ "credential-cache--daemon is disabled in this build of Git",
+ NULL
+ };
+ struct option options[] = { OPT_END() };
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+ die(_("credential-cache--daemon unavailable; no unix socket support"));
+}
+
+#endif /* NO_UNIX_SOCKET */
diff --git a/builtin/credential-cache.c b/builtin/credential-cache.c
new file mode 100644
index 0000000000..9b3f709905
--- /dev/null
+++ b/builtin/credential-cache.c
@@ -0,0 +1,157 @@
+#include "builtin.h"
+#include "parse-options.h"
+
+#ifndef NO_UNIX_SOCKETS
+
+#include "credential.h"
+#include "string-list.h"
+#include "unix-socket.h"
+#include "run-command.h"
+
+#define FLAG_SPAWN 0x1
+#define FLAG_RELAY 0x2
+
+static int send_request(const char *socket, const struct strbuf *out)
+{
+ int got_data = 0;
+ int fd = unix_stream_connect(socket);
+
+ if (fd < 0)
+ return -1;
+
+ if (write_in_full(fd, out->buf, out->len) < 0)
+ die_errno("unable to write to cache daemon");
+ shutdown(fd, SHUT_WR);
+
+ while (1) {
+ char in[1024];
+ int r;
+
+ r = read_in_full(fd, in, sizeof(in));
+ if (r == 0 || (r < 0 && errno == ECONNRESET))
+ break;
+ if (r < 0)
+ die_errno("read error from cache daemon");
+ write_or_die(1, in, r);
+ got_data = 1;
+ }
+ close(fd);
+ return got_data;
+}
+
+static void spawn_daemon(const char *socket)
+{
+ struct child_process daemon = CHILD_PROCESS_INIT;
+ char buf[128];
+ int r;
+
+ strvec_pushl(&daemon.args,
+ "credential-cache--daemon", socket,
+ NULL);
+ daemon.git_cmd = 1;
+ daemon.no_stdin = 1;
+ daemon.out = -1;
+
+ if (start_command(&daemon))
+ die_errno("unable to start cache daemon");
+ r = read_in_full(daemon.out, buf, sizeof(buf));
+ if (r < 0)
+ die_errno("unable to read result code from cache daemon");
+ if (r != 3 || memcmp(buf, "ok\n", 3))
+ die("cache daemon did not start: %.*s", r, buf);
+ close(daemon.out);
+}
+
+static void do_cache(const char *socket, const char *action, int timeout,
+ int flags)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "action=%s\n", action);
+ strbuf_addf(&buf, "timeout=%d\n", timeout);
+ if (flags & FLAG_RELAY) {
+ if (strbuf_read(&buf, 0, 0) < 0)
+ die_errno("unable to relay credential");
+ }
+
+ if (send_request(socket, &buf) < 0) {
+ if (errno != ENOENT && errno != ECONNREFUSED)
+ die_errno("unable to connect to cache daemon");
+ if (flags & FLAG_SPAWN) {
+ spawn_daemon(socket);
+ if (send_request(socket, &buf) < 0)
+ die_errno("unable to connect to cache daemon");
+ }
+ }
+ strbuf_release(&buf);
+}
+
+static char *get_socket_path(void)
+{
+ struct stat sb;
+ char *old_dir, *socket;
+ old_dir = expand_user_path("~/.git-credential-cache", 0);
+ if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode))
+ socket = xstrfmt("%s/socket", old_dir);
+ else
+ socket = xdg_cache_home("credential/socket");
+ free(old_dir);
+ return socket;
+}
+
+int cmd_credential_cache(int argc, const char **argv, const char *prefix)
+{
+ char *socket_path = NULL;
+ int timeout = 900;
+ const char *op;
+ const char * const usage[] = {
+ "git credential-cache [<options>] <action>",
+ NULL
+ };
+ struct option options[] = {
+ OPT_INTEGER(0, "timeout", &timeout,
+ "number of seconds to cache credentials"),
+ OPT_STRING(0, "socket", &socket_path, "path",
+ "path of cache-daemon socket"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+ if (!argc)
+ usage_with_options(usage, options);
+ op = argv[0];
+
+ if (!socket_path)
+ socket_path = get_socket_path();
+ if (!socket_path)
+ die("unable to find a suitable socket path; use --socket");
+
+ if (!strcmp(op, "exit"))
+ do_cache(socket_path, op, timeout, 0);
+ else if (!strcmp(op, "get") || !strcmp(op, "erase"))
+ do_cache(socket_path, op, timeout, FLAG_RELAY);
+ else if (!strcmp(op, "store"))
+ do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);
+ else
+ ; /* ignore unknown operation */
+
+ return 0;
+}
+
+#else
+
+int cmd_credential_cache(int argc, const char **argv, const char *prefix)
+{
+ const char * const usage[] = {
+ "git credential-cache [options] <action>",
+ "",
+ "credential-cache is disabled in this build of Git",
+ NULL
+ };
+ struct option options[] = { OPT_END() };
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+ die(_("credential-cache unavailable; no unix socket support"));
+}
+
+#endif /* NO_UNIX_SOCKETS */
diff --git a/builtin/credential-store.c b/builtin/credential-store.c
new file mode 100644
index 0000000000..5331ab151a
--- /dev/null
+++ b/builtin/credential-store.c
@@ -0,0 +1,195 @@
+#include "builtin.h"
+#include "lockfile.h"
+#include "credential.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static struct lock_file credential_lock;
+
+static int parse_credential_file(const char *fn,
+ struct credential *c,
+ void (*match_cb)(struct credential *),
+ void (*other_cb)(struct strbuf *))
+{
+ FILE *fh;
+ struct strbuf line = STRBUF_INIT;
+ struct credential entry = CREDENTIAL_INIT;
+ int found_credential = 0;
+
+ fh = fopen(fn, "r");
+ if (!fh) {
+ if (errno != ENOENT && errno != EACCES)
+ die_errno("unable to open %s", fn);
+ return found_credential;
+ }
+
+ while (strbuf_getline_lf(&line, fh) != EOF) {
+ if (!credential_from_url_gently(&entry, line.buf, 1) &&
+ entry.username && entry.password &&
+ credential_match(c, &entry)) {
+ found_credential = 1;
+ if (match_cb) {
+ match_cb(&entry);
+ break;
+ }
+ }
+ else if (other_cb)
+ other_cb(&line);
+ }
+
+ credential_clear(&entry);
+ strbuf_release(&line);
+ fclose(fh);
+ return found_credential;
+}
+
+static void print_entry(struct credential *c)
+{
+ printf("username=%s\n", c->username);
+ printf("password=%s\n", c->password);
+}
+
+static void print_line(struct strbuf *buf)
+{
+ strbuf_addch(buf, '\n');
+ write_or_die(get_lock_file_fd(&credential_lock), buf->buf, buf->len);
+}
+
+static void rewrite_credential_file(const char *fn, struct credential *c,
+ struct strbuf *extra)
+{
+ if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0)
+ die_errno("unable to get credential storage lock");
+ if (extra)
+ print_line(extra);
+ parse_credential_file(fn, c, NULL, print_line);
+ if (commit_lock_file(&credential_lock) < 0)
+ die_errno("unable to write credential store");
+}
+
+static void store_credential_file(const char *fn, struct credential *c)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "%s://", c->protocol);
+ strbuf_addstr_urlencode(&buf, c->username, is_rfc3986_unreserved);
+ strbuf_addch(&buf, ':');
+ strbuf_addstr_urlencode(&buf, c->password, is_rfc3986_unreserved);
+ strbuf_addch(&buf, '@');
+ if (c->host)
+ strbuf_addstr_urlencode(&buf, c->host, is_rfc3986_unreserved);
+ if (c->path) {
+ strbuf_addch(&buf, '/');
+ strbuf_addstr_urlencode(&buf, c->path,
+ is_rfc3986_reserved_or_unreserved);
+ }
+
+ rewrite_credential_file(fn, c, &buf);
+ strbuf_release(&buf);
+}
+
+static void store_credential(const struct string_list *fns, struct credential *c)
+{
+ struct string_list_item *fn;
+
+ /*
+ * Sanity check that what we are storing is actually sensible.
+ * In particular, we can't make a URL without a protocol field.
+ * Without either a host or pathname (depending on the scheme),
+ * we have no primary key. And without a username and password,
+ * we are not actually storing a credential.
+ */
+ if (!c->protocol || !(c->host || c->path) || !c->username || !c->password)
+ return;
+
+ for_each_string_list_item(fn, fns)
+ if (!access(fn->string, F_OK)) {
+ store_credential_file(fn->string, c);
+ return;
+ }
+ /*
+ * Write credential to the filename specified by fns->items[0], thus
+ * creating it
+ */
+ if (fns->nr)
+ store_credential_file(fns->items[0].string, c);
+}
+
+static void remove_credential(const struct string_list *fns, struct credential *c)
+{
+ struct string_list_item *fn;
+
+ /*
+ * Sanity check that we actually have something to match
+ * against. The input we get is a restrictive pattern,
+ * so technically a blank credential means "erase everything".
+ * But it is too easy to accidentally send this, since it is equivalent
+ * to empty input. So explicitly disallow it, and require that the
+ * pattern have some actual content to match.
+ */
+ if (!c->protocol && !c->host && !c->path && !c->username)
+ return;
+ for_each_string_list_item(fn, fns)
+ if (!access(fn->string, F_OK))
+ rewrite_credential_file(fn->string, c, NULL);
+}
+
+static void lookup_credential(const struct string_list *fns, struct credential *c)
+{
+ struct string_list_item *fn;
+
+ for_each_string_list_item(fn, fns)
+ if (parse_credential_file(fn->string, c, print_entry, NULL))
+ return; /* Found credential */
+}
+
+int cmd_credential_store(int argc, const char **argv, const char *prefix)
+{
+ const char * const usage[] = {
+ "git credential-store [<options>] <action>",
+ NULL
+ };
+ const char *op;
+ struct credential c = CREDENTIAL_INIT;
+ struct string_list fns = STRING_LIST_INIT_DUP;
+ char *file = NULL;
+ struct option options[] = {
+ OPT_STRING(0, "file", &file, "path",
+ "fetch and store credentials in <path>"),
+ OPT_END()
+ };
+
+ umask(077);
+
+ argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);
+ if (argc != 1)
+ usage_with_options(usage, options);
+ op = argv[0];
+
+ if (file) {
+ string_list_append(&fns, file);
+ } else {
+ if ((file = expand_user_path("~/.git-credentials", 0)))
+ string_list_append_nodup(&fns, file);
+ file = xdg_config_home("credentials");
+ if (file)
+ string_list_append_nodup(&fns, file);
+ }
+ if (!fns.nr)
+ die("unable to set up default path; use --file");
+
+ if (credential_read(&c, stdin) < 0)
+ die("unable to read credential");
+
+ if (!strcmp(op, "get"))
+ lookup_credential(&fns, &c);
+ else if (!strcmp(op, "erase"))
+ remove_credential(&fns, &c);
+ else if (!strcmp(op, "store"))
+ store_credential(&fns, &c);
+ else
+ ; /* Ignore unknown operation. */
+
+ string_list_clear(&fns, 0);
+ return 0;
+}
diff --git a/builtin/credential.c b/builtin/credential.c
index 879acfbcda..d75dcdc64a 100644
--- a/builtin/credential.c
+++ b/builtin/credential.c
@@ -1,6 +1,7 @@
#include "git-compat-util.h"
#include "credential.h"
#include "builtin.h"
+#include "config.h"
static const char usage_msg[] =
"git credential [fill|approve|reject]";
@@ -10,6 +11,8 @@ int cmd_credential(int argc, const char **argv, const char *prefix)
const char *op;
struct credential c = CREDENTIAL_INIT;
+ git_config(git_default_config, NULL);
+
if (argc != 2 || !strcmp(argv[1], "-h"))
usage(usage_msg);
op = argv[1];
diff --git a/builtin/describe.c b/builtin/describe.c
index 1409cedce2..7668591d57 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -12,10 +12,9 @@
#include "revision.h"
#include "diff.h"
#include "hashmap.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "run-command.h"
#include "object-store.h"
-#include "revision.h"
#include "list-objects.h"
#include "commit-slab.h"
@@ -55,6 +54,7 @@ struct commit_name {
struct tag *tag;
unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
unsigned name_checked:1;
+ unsigned misnamed:1;
struct object_id oid;
char *path;
};
@@ -64,19 +64,22 @@ static const char *prio_names[] = {
};
static int commit_name_neq(const void *unused_cmp_data,
- const void *entry,
- const void *entry_or_key,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
const void *peeled)
{
- const struct commit_name *cn1 = entry;
- const struct commit_name *cn2 = entry_or_key;
+ const struct commit_name *cn1, *cn2;
+
+ cn1 = container_of(eptr, const struct commit_name, entry);
+ cn2 = container_of(entry_or_key, const struct commit_name, entry);
return !oideq(&cn1->peeled, peeled ? peeled : &cn2->peeled);
}
static inline struct commit_name *find_commit_name(const struct object_id *peeled)
{
- return hashmap_get_from_hash(&names, sha1hash(peeled->hash), peeled->hash);
+ return hashmap_get_entry_from_hash(&names, oidhash(peeled), peeled,
+ struct commit_name, entry);
}
static int replace_name(struct commit_name *e,
@@ -123,13 +126,14 @@ static void add_to_known_names(const char *path,
if (!e) {
e = xmalloc(sizeof(struct commit_name));
oidcpy(&e->peeled, peeled);
- hashmap_entry_init(e, sha1hash(peeled->hash));
- hashmap_add(&names, e);
+ hashmap_entry_init(&e->entry, oidhash(peeled));
+ hashmap_add(&names, &e->entry);
e->path = NULL;
}
e->tag = tag;
e->prio = prio;
e->name_checked = 0;
+ e->misnamed = 0;
oidcpy(&e->oid, oid);
free(e->path);
e->path = xstrdup(path);
@@ -273,10 +277,11 @@ static void append_name(struct commit_name *n, struct strbuf *dst)
die(_("annotated tag %s not available"), n->path);
}
if (n->tag && !n->name_checked) {
- if (!n->tag->tag)
- die(_("annotated tag %s has no embedded name"), n->path);
- if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
- warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
+ if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) {
+ warning(_("tag '%s' is externally known as '%s'"),
+ n->path, n->tag->tag);
+ n->misnamed = 1;
+ }
n->name_checked = 1;
}
@@ -312,8 +317,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
* Exact match to an existing ref.
*/
append_name(n, dst);
- if (longformat)
- append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst);
+ if (n->misnamed || longformat)
+ append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst);
if (suffix)
strbuf_addstr(dst, suffix);
return;
@@ -330,8 +335,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
struct commit_name *n;
init_commit_names(&commit_names);
- n = hashmap_iter_first(&names, &iter);
- for (; n; n = hashmap_iter_next(&iter)) {
+ hashmap_for_each_entry(&names, &iter, n,
+ entry /* member name */) {
c = lookup_commit_reference_gently(the_repository,
&n->peeled, 1);
if (c)
@@ -374,11 +379,25 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
if (!(c->object.flags & t->flag_within))
t->depth++;
}
+ /* Stop if last remaining path already covered by best candidate(s) */
if (annotated_cnt && !list) {
- if (debug)
- fprintf(stderr, _("finished search at %s\n"),
- oid_to_hex(&c->object.oid));
- break;
+ int best_depth = INT_MAX;
+ unsigned best_within = 0;
+ for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+ struct possible_tag *t = &all_matches[cur_match];
+ if (t->depth < best_depth) {
+ best_depth = t->depth;
+ best_within = t->flag_within;
+ } else if (t->depth == best_depth) {
+ best_within |= t->flag_within;
+ }
+ }
+ if ((c->object.flags & best_within) == best_within) {
+ if (debug)
+ fprintf(stderr, _("finished search at %s\n"),
+ oid_to_hex(&c->object.oid));
+ break;
+ }
}
while (parents) {
struct commit *p = parents->item;
@@ -447,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
}
append_name(all_matches[0].name, dst);
- if (abbrev)
+ if (all_matches[0].name->misnamed || abbrev)
append_suffix(all_matches[0].depth, &cmit->object.oid, dst);
if (suffix)
strbuf_addstr(dst, suffix);
@@ -482,15 +501,15 @@ static void process_object(struct object *obj, const char *path, void *data)
static void describe_blob(struct object_id oid, struct strbuf *dst)
{
struct rev_info revs;
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
struct process_commit_data pcd = { null_oid, oid, dst, &revs};
- argv_array_pushl(&args, "internal: The first arg is not parsed",
- "--objects", "--in-commit-order", "--reverse", "HEAD",
- NULL);
+ strvec_pushl(&args, "internal: The first arg is not parsed",
+ "--objects", "--in-commit-order", "--reverse", "HEAD",
+ NULL);
repo_init_revisions(the_repository, &revs, NULL);
- if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1)
+ if (setup_revisions(args.nr, args.v, &revs, NULL) > 1)
BUG("setup_revisions could not handle all args?");
if (prepare_revision_walk(&revs))
@@ -575,26 +594,26 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
if (contains) {
struct string_list_item *item;
- struct argv_array args;
+ struct strvec args;
- argv_array_init(&args);
- argv_array_pushl(&args, "name-rev",
- "--peel-tag", "--name-only", "--no-undefined",
- NULL);
+ strvec_init(&args);
+ strvec_pushl(&args, "name-rev",
+ "--peel-tag", "--name-only", "--no-undefined",
+ NULL);
if (always)
- argv_array_push(&args, "--always");
+ strvec_push(&args, "--always");
if (!all) {
- argv_array_push(&args, "--tags");
+ strvec_push(&args, "--tags");
for_each_string_list_item(item, &patterns)
- argv_array_pushf(&args, "--refs=refs/tags/%s", item->string);
+ strvec_pushf(&args, "--refs=refs/tags/%s", item->string);
for_each_string_list_item(item, &exclude_patterns)
- argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string);
+ strvec_pushf(&args, "--exclude=refs/tags/%s", item->string);
}
if (argc)
- argv_array_pushv(&args, argv);
+ strvec_pushv(&args, argv);
else
- argv_array_push(&args, "HEAD");
- return cmd_name_rev(args.argc, args.argv, prefix);
+ strvec_push(&args, "HEAD");
+ return cmd_name_rev(args.nr, args.v, prefix);
}
hashmap_init(&names, commit_name_neq, NULL, 0);
@@ -605,7 +624,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
if (argc == 0) {
if (broken) {
struct child_process cp = CHILD_PROCESS_INIT;
- argv_array_pushv(&cp.args, diff_index_args);
+ strvec_pushv(&cp.args, diff_index_args);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.no_stdout = 1;
@@ -627,7 +646,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
} else if (dirty) {
struct lock_file index_lock = LOCK_INIT;
struct rev_info revs;
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
int fd, result;
setup_work_tree();
@@ -639,8 +658,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
repo_update_index_if_able(the_repository, &index_lock);
repo_init_revisions(the_repository, &revs, prefix);
- argv_array_pushv(&args, diff_index_args);
- if (setup_revisions(args.argc, args.argv, &revs, NULL) != 1)
+ strvec_pushv(&args, diff_index_args);
+ if (setup_revisions(args.nr, args.v, &revs, NULL) != 1)
BUG("malformed internal diff-index command line");
result = run_diff_index(&revs, 0);
diff --git a/builtin/diff-files.c b/builtin/diff-files.c
index 86ae474fbf..1e352dd8f7 100644
--- a/builtin/diff-files.c
+++ b/builtin/diff-files.c
@@ -28,6 +28,13 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
repo_init_revisions(the_repository, &rev, prefix);
rev.abbrev = 0;
+
+ /*
+ * Consider "intent-to-add" files as new by default, unless
+ * explicitly specified in the command line or anywhere else.
+ */
+ rev.diffopt.ita_invisible_in_index = 1;
+
precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, &rev, NULL);
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index 93ec642423..7f5281c461 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -15,7 +15,7 @@ COMMON_DIFF_OPTIONS_HELP;
int cmd_diff_index(int argc, const char **argv, const char *prefix)
{
struct rev_info rev;
- int cached = 0;
+ unsigned int option = 0;
int i;
int result;
@@ -32,7 +32,9 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
const char *arg = argv[i];
if (!strcmp(arg, "--cached"))
- cached = 1;
+ option |= DIFF_INDEX_CACHED;
+ else if (!strcmp(arg, "--merge-base"))
+ option |= DIFF_INDEX_MERGE_BASE;
else
usage(diff_cache_usage);
}
@@ -46,7 +48,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
if (rev.pending.nr != 1 ||
rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
usage(diff_cache_usage);
- if (!cached) {
+ if (!(option & DIFF_INDEX_CACHED)) {
setup_work_tree();
if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
@@ -56,7 +58,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
perror("read_cache");
return -1;
}
- result = run_diff_index(&rev, cached);
+ result = run_diff_index(&rev, option);
UNLEAK(rev);
return diff_result_code(&rev.diffopt, result);
}
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
index cb9ea79367..9fc95e959f 100644
--- a/builtin/diff-tree.c
+++ b/builtin/diff-tree.c
@@ -109,7 +109,9 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
struct object *tree1, *tree2;
static struct rev_info *opt = &log_tree_opt;
struct setup_revision_opt s_r_opt;
+ struct userformat_want w;
int read_stdin = 0;
+ int merge_base = 0;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(diff_tree_usage);
@@ -127,6 +129,14 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, opt, &s_r_opt);
+ memset(&w, 0, sizeof(w));
+ userformat_find_requirements(NULL, &w);
+
+ if (!opt->show_notes_given && w.notes)
+ opt->show_notes = 1;
+ if (opt->show_notes)
+ load_display_notes(&opt->notes_opt);
+
while (--argc > 0) {
const char *arg = *++argv;
@@ -134,9 +144,18 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
read_stdin = 1;
continue;
}
+ if (!strcmp(arg, "--merge-base")) {
+ merge_base = 1;
+ continue;
+ }
usage(diff_tree_usage);
}
+ if (read_stdin && merge_base)
+ die(_("--stdin and --merge-base are mutually exclusive"));
+ if (merge_base && opt->pending.nr != 2)
+ die(_("--merge-base only works with two commits"));
+
/*
* NOTE! We expect "a..b" to expand to "^a b" but it is
* perfectly valid for revision range parser to yield "b ^a",
@@ -156,7 +175,12 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
case 2:
tree1 = opt->pending.objects[0].item;
tree2 = opt->pending.objects[1].item;
- if (tree2->flags & UNINTERESTING) {
+ if (merge_base) {
+ struct object_id oid;
+
+ diff_get_merge_base(opt, &oid);
+ tree1 = lookup_object(the_repository, &oid);
+ } else if (tree2->flags & UNINTERESTING) {
SWAP(tree2, tree1);
}
diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt);
diff --git a/builtin/diff.c b/builtin/diff.c
index 42ac803091..780c33877f 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -6,6 +6,7 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
#include "config.h"
+#include "ewah/ewok.h"
#include "lockfile.h"
#include "color.h"
#include "commit.h"
@@ -17,13 +18,19 @@
#include "log-tree.h"
#include "builtin.h"
#include "submodule.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#define DIFF_NO_INDEX_EXPLICIT 1
#define DIFF_NO_INDEX_IMPLICIT 2
static const char builtin_diff_usage[] =
-"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]";
+"git diff [<options>] [<commit>] [--] [<path>...]\n"
+" or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n"
+" or: git diff [<options>] <commit> [--merge-base] [<commit>...] <commit> [--] [<path>...]\n"
+" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n"
+" or: git diff [<options>] <blob> <blob>]\n"
+" or: git diff [<options>] --no-index [--] <path> <path>]\n"
+COMMON_DIFF_OPTIONS_HELP;
static const char *blob_path(struct object_array_entry *entry)
{
@@ -127,11 +134,13 @@ static int builtin_diff_blobs(struct rev_info *revs,
static int builtin_diff_index(struct rev_info *revs,
int argc, const char **argv)
{
- int cached = 0;
+ unsigned int option = 0;
while (1 < argc) {
const char *arg = argv[1];
if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged"))
- cached = 1;
+ option |= DIFF_INDEX_CACHED;
+ else if (!strcmp(arg, "--merge-base"))
+ option |= DIFF_INDEX_MERGE_BASE;
else
usage(builtin_diff_usage);
argv++; argc--;
@@ -144,7 +153,7 @@ static int builtin_diff_index(struct rev_info *revs,
revs->max_count != -1 || revs->min_age != -1 ||
revs->max_age != -1)
usage(builtin_diff_usage);
- if (!cached) {
+ if (!(option & DIFF_INDEX_CACHED)) {
setup_work_tree();
if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
@@ -154,7 +163,7 @@ static int builtin_diff_index(struct rev_info *revs,
perror("read_cache");
return -1;
}
- return run_diff_index(revs, cached);
+ return run_diff_index(revs, option);
}
static int builtin_diff_tree(struct rev_info *revs,
@@ -163,19 +172,34 @@ static int builtin_diff_tree(struct rev_info *revs,
struct object_array_entry *ent1)
{
const struct object_id *(oid[2]);
- int swap = 0;
+ struct object_id mb_oid;
+ int merge_base = 0;
- if (argc > 1)
- usage(builtin_diff_usage);
+ while (1 < argc) {
+ const char *arg = argv[1];
+ if (!strcmp(arg, "--merge-base"))
+ merge_base = 1;
+ else
+ usage(builtin_diff_usage);
+ argv++; argc--;
+ }
- /*
- * We saw two trees, ent0 and ent1. If ent1 is uninteresting,
- * swap them.
- */
- if (ent1->item->flags & UNINTERESTING)
- swap = 1;
- oid[swap] = &ent0->item->oid;
- oid[1 - swap] = &ent1->item->oid;
+ if (merge_base) {
+ diff_get_merge_base(revs, &mb_oid);
+ oid[0] = &mb_oid;
+ oid[1] = &revs->pending.objects[1].item->oid;
+ } else {
+ int swap = 0;
+
+ /*
+ * We saw two trees, ent0 and ent1. If ent1 is uninteresting,
+ * swap them.
+ */
+ if (ent1->item->flags & UNINTERESTING)
+ swap = 1;
+ oid[swap] = &ent0->item->oid;
+ oid[1 - swap] = &ent1->item->oid;
+ }
diff_tree_oid(oid[0], oid[1], "", &revs->diffopt);
log_tree_diff_flush(revs);
return 0;
@@ -196,8 +220,7 @@ static int builtin_diff_combined(struct rev_info *revs,
revs->dense_combined_merges = revs->combine_merges = 1;
for (i = 1; i < ents; i++)
oid_array_append(&parents, &ent[i].item->oid);
- diff_tree_combined(&ent[0].item->oid, &parents,
- revs->dense_combined_merges, revs);
+ diff_tree_combined(&ent[0].item->oid, &parents, revs);
oid_array_clear(&parents);
return 0;
}
@@ -254,6 +277,108 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
return run_diff_files(revs, options);
}
+struct symdiff {
+ struct bitmap *skip;
+ int warn;
+ const char *base, *left, *right;
+};
+
+/*
+ * Check for symmetric-difference arguments, and if present, arrange
+ * everything we need to know to handle them correctly. As a bonus,
+ * weed out all bogus range-based revision specifications, e.g.,
+ * "git diff A..B C..D" or "git diff A..B C" get rejected.
+ *
+ * For an actual symmetric diff, *symdiff is set this way:
+ *
+ * - its skip is non-NULL and marks *all* rev->pending.objects[i]
+ * indices that the caller should ignore (extra merge bases, of
+ * which there might be many, and A in A...B). Note that the
+ * chosen merge base and right side are NOT marked.
+ * - warn is set if there are multiple merge bases.
+ * - base, left, and right point to the names to use in a
+ * warning about multiple merge bases.
+ *
+ * If there is no symmetric diff argument, sym->skip is NULL and
+ * sym->warn is cleared. The remaining fields are not set.
+ */
+static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)
+{
+ int i, is_symdiff = 0, basecount = 0, othercount = 0;
+ int lpos = -1, rpos = -1, basepos = -1;
+ struct bitmap *map = NULL;
+
+ /*
+ * Use the whence fields to find merge bases and left and
+ * right parts of symmetric difference, so that we do not
+ * depend on the order that revisions are parsed. If there
+ * are any revs that aren't from these sources, we have a
+ * "git diff C A...B" or "git diff A...B C" case. Or we
+ * could even get "git diff A...B C...E", for instance.
+ *
+ * If we don't have just one merge base, we pick one
+ * at random.
+ *
+ * NB: REV_CMD_LEFT, REV_CMD_RIGHT are also used for A..B,
+ * so we must check for SYMMETRIC_LEFT too. The two arrays
+ * rev->pending.objects and rev->cmdline.rev are parallel.
+ */
+ for (i = 0; i < rev->cmdline.nr; i++) {
+ struct object *obj = rev->pending.objects[i].item;
+ switch (rev->cmdline.rev[i].whence) {
+ case REV_CMD_MERGE_BASE:
+ if (basepos < 0)
+ basepos = i;
+ basecount++;
+ break; /* do mark all bases */
+ case REV_CMD_LEFT:
+ if (lpos >= 0)
+ usage(builtin_diff_usage);
+ lpos = i;
+ if (obj->flags & SYMMETRIC_LEFT) {
+ is_symdiff = 1;
+ break; /* do mark A */
+ }
+ continue;
+ case REV_CMD_RIGHT:
+ if (rpos >= 0)
+ usage(builtin_diff_usage);
+ rpos = i;
+ continue; /* don't mark B */
+ case REV_CMD_PARENTS_ONLY:
+ case REV_CMD_REF:
+ case REV_CMD_REV:
+ othercount++;
+ continue;
+ }
+ if (map == NULL)
+ map = bitmap_new();
+ bitmap_set(map, i);
+ }
+
+ /*
+ * Forbid any additional revs for both A...B and A..B.
+ */
+ if (lpos >= 0 && othercount > 0)
+ usage(builtin_diff_usage);
+
+ if (!is_symdiff) {
+ bitmap_free(map);
+ sym->warn = 0;
+ sym->skip = NULL;
+ return;
+ }
+
+ sym->left = rev->pending.objects[lpos].name;
+ sym->right = rev->pending.objects[rpos].name;
+ if (basecount == 0)
+ die(_("%s...%s: no merge base"), sym->left, sym->right);
+ sym->base = rev->pending.objects[basepos].name;
+ bitmap_unset(map, basepos); /* unmark the base we want */
+ sym->warn = basecount > 1;
+ sym->skip = map;
+}
+
int cmd_diff(int argc, const char **argv, const char *prefix)
{
int i;
@@ -263,19 +388,29 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
struct object_array_entry *blob[2];
int nongit = 0, no_index = 0;
int result = 0;
+ struct symdiff sdiff;
/*
* We could get N tree-ish in the rev.pending_objects list.
- * Also there could be M blobs there, and P pathspecs.
+ * Also there could be M blobs there, and P pathspecs. --cached may
+ * also be present.
*
* N=0, M=0:
- * cache vs files (diff-files)
+ * cache vs files (diff-files)
+ *
+ * N=0, M=0, --cached:
+ * HEAD vs cache (diff-index --cached)
+ *
* N=0, M=2:
* compare two random blobs. P must be zero.
+ *
* N=0, M=1, P=1:
- * compare a blob with a working tree file.
+ * compare a blob with a working tree file.
*
* N=1, M=0:
+ * tree vs files (diff-index)
+ *
+ * N=1, M=0, --cached:
* tree vs cache (diff-index --cached)
*
* N=2, M=0:
@@ -382,6 +517,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
}
+ symdiff_prepare(&rev, &sdiff);
for (i = 0; i < rev.pending.nr; i++) {
struct object_array_entry *entry = &rev.pending.objects[i];
struct object *obj = entry->item;
@@ -396,6 +532,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
obj = &get_commit_tree(((struct commit *)obj))->object;
if (obj->type == OBJ_TREE) {
+ if (sdiff.skip && bitmap_get(sdiff.skip, i))
+ continue;
obj->flags |= flags;
add_object_array(obj, name, &ent);
} else if (obj->type == OBJ_BLOB) {
@@ -437,21 +575,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
usage(builtin_diff_usage);
else if (ent.nr == 1)
result = builtin_diff_index(&rev, argc, argv);
- else if (ent.nr == 2)
+ else if (ent.nr == 2) {
+ if (sdiff.warn)
+ warning(_("%s...%s: multiple merge bases, using %s"),
+ sdiff.left, sdiff.right, sdiff.base);
result = builtin_diff_tree(&rev, argc, argv,
&ent.objects[0], &ent.objects[1]);
- else if (ent.objects[0].item->flags & UNINTERESTING) {
- /*
- * diff A...B where there is at least one merge base
- * between A and B. We have ent.objects[0] ==
- * merge-base, ent.objects[ents-2] == A, and
- * ent.objects[ents-1] == B. Show diff between the
- * base and B. Note that we pick one merge base at
- * random if there are more than one.
- */
- result = builtin_diff_tree(&rev, argc, argv,
- &ent.objects[0],
- &ent.objects[ent.nr-1]);
} else
result = builtin_diff_combined(&rev, argc, argv,
ent.objects, ent.nr);
diff --git a/builtin/difftool.c b/builtin/difftool.c
index 16eb8b70ea..6e18e623fd 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -18,7 +18,7 @@
#include "run-command.h"
#include "exec-cmd.h"
#include "parse-options.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "strbuf.h"
#include "lockfile.h"
#include "object-store.h"
@@ -125,12 +125,15 @@ struct working_tree_entry {
};
static int working_tree_entry_cmp(const void *unused_cmp_data,
- const void *entry,
- const void *entry_or_key,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
const void *unused_keydata)
{
- const struct working_tree_entry *a = entry;
- const struct working_tree_entry *b = entry_or_key;
+ const struct working_tree_entry *a, *b;
+
+ a = container_of(eptr, const struct working_tree_entry, entry);
+ b = container_of(entry_or_key, const struct working_tree_entry, entry);
+
return strcmp(a->path, b->path);
}
@@ -145,12 +148,14 @@ struct pair_entry {
};
static int pair_cmp(const void *unused_cmp_data,
- const void *entry,
- const void *entry_or_key,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
const void *unused_keydata)
{
- const struct pair_entry *a = entry;
- const struct pair_entry *b = entry_or_key;
+ const struct pair_entry *a, *b;
+
+ a = container_of(eptr, const struct pair_entry, entry);
+ b = container_of(entry_or_key, const struct pair_entry, entry);
return strcmp(a->path, b->path);
}
@@ -161,14 +166,14 @@ static void add_left_or_right(struct hashmap *map, const char *path,
struct pair_entry *e, *existing;
FLEX_ALLOC_STR(e, path, path);
- hashmap_entry_init(e, strhash(path));
- existing = hashmap_get(map, e, NULL);
+ hashmap_entry_init(&e->entry, strhash(path));
+ existing = hashmap_get_entry(map, e, entry, NULL);
if (existing) {
free(e);
e = existing;
} else {
e->left[0] = e->right[0] = '\0';
- hashmap_add(map, e);
+ hashmap_add(map, &e->entry);
}
strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
}
@@ -179,12 +184,14 @@ struct path_entry {
};
static int path_entry_cmp(const void *unused_cmp_data,
- const void *entry,
- const void *entry_or_key,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
const void *key)
{
- const struct path_entry *a = entry;
- const struct path_entry *b = entry_or_key;
+ const struct path_entry *a, *b;
+
+ a = container_of(eptr, const struct path_entry, entry);
+ b = container_of(entry_or_key, const struct path_entry, entry);
return strcmp(a->path, key ? key : b->path);
}
@@ -203,10 +210,10 @@ static void changed_files(struct hashmap *result, const char *index_path,
strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
env[0] = index_env.buf;
- argv_array_pushl(&update_index.args,
- "--git-dir", git_dir, "--work-tree", workdir,
- "update-index", "--really-refresh", "-q",
- "--unmerged", NULL);
+ strvec_pushl(&update_index.args,
+ "--git-dir", git_dir, "--work-tree", workdir,
+ "update-index", "--really-refresh", "-q",
+ "--unmerged", NULL);
update_index.no_stdin = 1;
update_index.no_stdout = 1;
update_index.no_stderr = 1;
@@ -218,9 +225,9 @@ static void changed_files(struct hashmap *result, const char *index_path,
/* Ignore any errors of update-index */
run_command(&update_index);
- argv_array_pushl(&diff_files.args,
- "--git-dir", git_dir, "--work-tree", workdir,
- "diff-files", "--name-only", "-z", NULL);
+ strvec_pushl(&diff_files.args,
+ "--git-dir", git_dir, "--work-tree", workdir,
+ "diff-files", "--name-only", "-z", NULL);
diff_files.no_stdin = 1;
diff_files.git_cmd = 1;
diff_files.use_shell = 0;
@@ -234,8 +241,8 @@ static void changed_files(struct hashmap *result, const char *index_path,
while (!strbuf_getline_nul(&buf, fp)) {
struct path_entry *entry;
FLEX_ALLOC_STR(entry, path, buf.buf);
- hashmap_entry_init(entry, strhash(buf.buf));
- hashmap_add(result, entry);
+ hashmap_entry_init(&entry->entry, strhash(buf.buf));
+ hashmap_add(result, &entry->entry);
}
fclose(fp);
if (finish_command(&diff_files))
@@ -335,7 +342,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
const char *workdir, *tmp;
int ret = 0, i;
FILE *fp;
- struct hashmap working_tree_dups, submodules, symlinks2;
+ struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp,
+ NULL);
+ struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL);
+ struct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL);
struct hashmap_iter iter;
struct pair_entry *entry;
struct index_state wtindex;
@@ -376,20 +386,16 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
rdir_len = rdir.len;
wtdir_len = wtdir.len;
- hashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0);
- hashmap_init(&submodules, pair_cmp, NULL, 0);
- hashmap_init(&symlinks2, pair_cmp, NULL, 0);
-
child.no_stdin = 1;
child.git_cmd = 1;
child.use_shell = 0;
child.clean_on_exit = 1;
child.dir = prefix;
child.out = -1;
- argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
- NULL);
+ strvec_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+ NULL);
for (i = 0; i < argc; i++)
- argv_array_push(&child.args, argv[i]);
+ strvec_push(&child.args, argv[i]);
if (start_command(&child))
die("could not obtain raw diff");
fp = xfdopen(child.out, "r");
@@ -461,12 +467,13 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
/* Avoid duplicate working_tree entries */
FLEX_ALLOC_STR(entry, path, dst_path);
- hashmap_entry_init(entry, strhash(dst_path));
- if (hashmap_get(&working_tree_dups, entry, NULL)) {
+ hashmap_entry_init(&entry->entry, strhash(dst_path));
+ if (hashmap_get(&working_tree_dups, &entry->entry,
+ NULL)) {
free(entry);
continue;
}
- hashmap_add(&working_tree_dups, entry);
+ hashmap_add(&working_tree_dups, &entry->entry);
if (!use_wt_file(workdir, dst_path, &roid)) {
if (checkout_path(rmode, &roid, dst_path,
@@ -530,8 +537,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
* temporary file to both the left and right directories to show the
* change in the recorded SHA1 for the submodule.
*/
- hashmap_iter_init(&submodules, &iter);
- while ((entry = hashmap_iter_next(&iter))) {
+ hashmap_for_each_entry(&submodules, &iter, entry,
+ entry /* member name */) {
if (*entry->left) {
add_path(&ldir, ldir_len, entry->path);
ensure_leading_directories(ldir.buf);
@@ -549,8 +556,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
* shows only the link itself, not the contents of the link target.
* This loop replicates that behavior.
*/
- hashmap_iter_init(&symlinks2, &iter);
- while ((entry = hashmap_iter_next(&iter))) {
+ hashmap_for_each_entry(&symlinks2, &iter, entry,
+ entry /* member name */) {
if (*entry->left) {
add_path(&ldir, ldir_len, entry->path);
ensure_leading_directories(ldir.buf);
@@ -659,7 +666,7 @@ finish:
static int run_file_diff(int prompt, const char *prefix,
int argc, const char **argv)
{
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
const char *env[] = {
"GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
NULL
@@ -672,10 +679,10 @@ static int run_file_diff(int prompt, const char *prefix,
env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
- argv_array_push(&args, "diff");
+ strvec_push(&args, "diff");
for (i = 0; i < argc; i++)
- argv_array_push(&args, argv[i]);
- ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
+ strvec_push(&args, argv[i]);
+ ret = run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env);
exit(ret);
}
diff --git a/builtin/env--helper.c b/builtin/env--helper.c
new file mode 100644
index 0000000000..27349098b0
--- /dev/null
+++ b/builtin/env--helper.c
@@ -0,0 +1,100 @@
+#include "builtin.h"
+#include "config.h"
+#include "parse-options.h"
+
+static char const * const env__helper_usage[] = {
+ N_("git env--helper --type=[bool|ulong] <options> <env-var>"),
+ NULL
+};
+
+enum cmdmode {
+ ENV_HELPER_TYPE_BOOL = 1,
+ ENV_HELPER_TYPE_ULONG
+};
+
+static int option_parse_type(const struct option *opt, const char *arg,
+ int unset)
+{
+ enum cmdmode *cmdmode = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+
+ if (!strcmp(arg, "bool"))
+ *cmdmode = ENV_HELPER_TYPE_BOOL;
+ else if (!strcmp(arg, "ulong"))
+ *cmdmode = ENV_HELPER_TYPE_ULONG;
+ else
+ die(_("unrecognized --type argument, %s"), arg);
+
+ return 0;
+}
+
+int cmd_env__helper(int argc, const char **argv, const char *prefix)
+{
+ int exit_code = 0;
+ const char *env_variable = NULL;
+ const char *env_default = NULL;
+ int ret;
+ int ret_int, default_int;
+ unsigned long ret_ulong, default_ulong;
+ enum cmdmode cmdmode = 0;
+ struct option opts[] = {
+ OPT_CALLBACK_F(0, "type", &cmdmode, N_("type"),
+ N_("value is given this type"), PARSE_OPT_NONEG,
+ option_parse_type),
+ OPT_STRING(0, "default", &env_default, N_("value"),
+ N_("default for git_env_*(...) to fall back on")),
+ OPT_BOOL(0, "exit-code", &exit_code,
+ N_("be quiet only use git_env_*() value as exit code")),
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, opts, env__helper_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (env_default && !*env_default)
+ usage_with_options(env__helper_usage, opts);
+ if (!cmdmode)
+ usage_with_options(env__helper_usage, opts);
+ if (argc != 1)
+ usage_with_options(env__helper_usage, opts);
+ env_variable = argv[0];
+
+ switch (cmdmode) {
+ case ENV_HELPER_TYPE_BOOL:
+ if (env_default) {
+ default_int = git_parse_maybe_bool(env_default);
+ if (default_int == -1) {
+ error(_("option `--default' expects a boolean value with `--type=bool`, not `%s`"),
+ env_default);
+ usage_with_options(env__helper_usage, opts);
+ }
+ } else {
+ default_int = 0;
+ }
+ ret_int = git_env_bool(env_variable, default_int);
+ if (!exit_code)
+ puts(ret_int ? "true" : "false");
+ ret = ret_int;
+ break;
+ case ENV_HELPER_TYPE_ULONG:
+ if (env_default) {
+ if (!git_parse_ulong(env_default, &default_ulong)) {
+ error(_("option `--default' expects an unsigned long value with `--type=ulong`, not `%s`"),
+ env_default);
+ usage_with_options(env__helper_usage, opts);
+ }
+ } else {
+ default_ulong = 0;
+ }
+ ret_ulong = git_env_ulong(env_variable, default_ulong);
+ if (!exit_code)
+ printf("%lu\n", ret_ulong);
+ ret = ret_ulong;
+ break;
+ default:
+ BUG("unknown <type> value");
+ break;
+ }
+
+ return !ret;
+}
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 9e283482ef..d2e33f5005 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -33,16 +33,19 @@ static const char *fast_export_usage[] = {
static int progress;
static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT;
static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT;
+static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT;
static int fake_missing_tagger;
static int use_done_feature;
static int no_data;
static int full_tree;
static int reference_excluded_commits;
static int show_original_ids;
+static int mark_tags;
static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
static struct string_list tag_refs = STRING_LIST_INIT_NODUP;
static struct refspec refspecs = REFSPEC_INIT_FETCH;
static int anonymize;
+static struct hashmap anonymized_seeds;
static struct revision_sources revision_sources;
static int parse_opt_signed_tag_mode(const struct option *opt,
@@ -77,6 +80,31 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt,
return 0;
}
+static int parse_opt_reencode_mode(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset) {
+ reencode_mode = REENCODE_ABORT;
+ return 0;
+ }
+
+ switch (git_parse_maybe_bool(arg)) {
+ case 0:
+ reencode_mode = REENCODE_NO;
+ break;
+ case 1:
+ reencode_mode = REENCODE_YES;
+ break;
+ default:
+ if (!strcasecmp(arg, "abort"))
+ reencode_mode = REENCODE_ABORT;
+ else
+ return error("Unknown reencoding mode: %s", arg);
+ }
+
+ return 0;
+}
+
static struct decoration idnums;
static uint32_t last_idnum;
@@ -93,19 +121,33 @@ static int has_unshown_parent(struct commit *commit)
struct anonymized_entry {
struct hashmap_entry hash;
+ const char *anon;
+ const char orig[FLEX_ARRAY];
+};
+
+struct anonymized_entry_key {
+ struct hashmap_entry hash;
const char *orig;
size_t orig_len;
- const char *anon;
- size_t anon_len;
};
static int anonymized_entry_cmp(const void *unused_cmp_data,
- const void *va, const void *vb,
- const void *unused_keydata)
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
+ const void *keydata)
{
- const struct anonymized_entry *a = va, *b = vb;
- return a->orig_len != b->orig_len ||
- memcmp(a->orig, b->orig, a->orig_len);
+ const struct anonymized_entry *a, *b;
+
+ a = container_of(eptr, const struct anonymized_entry, hash);
+ if (keydata) {
+ const struct anonymized_entry_key *key = keydata;
+ int equal = !strncmp(a->orig, key->orig, key->orig_len) &&
+ !a->orig[key->orig_len];
+ return !equal;
+ }
+
+ b = container_of(entry_or_key, const struct anonymized_entry, hash);
+ return strcmp(a->orig, b->orig);
}
/*
@@ -113,31 +155,39 @@ static int anonymized_entry_cmp(const void *unused_cmp_data,
* the same anonymized string with another. The actual generation
* is farmed out to the generate function.
*/
-static const void *anonymize_mem(struct hashmap *map,
- void *(*generate)(const void *, size_t *),
- const void *orig, size_t *len)
+static const char *anonymize_str(struct hashmap *map,
+ char *(*generate)(void *),
+ const char *orig, size_t len,
+ void *data)
{
- struct anonymized_entry key, *ret;
+ struct anonymized_entry_key key;
+ struct anonymized_entry *ret;
if (!map->cmpfn)
hashmap_init(map, anonymized_entry_cmp, NULL, 0);
- hashmap_entry_init(&key, memhash(orig, *len));
+ hashmap_entry_init(&key.hash, memhash(orig, len));
key.orig = orig;
- key.orig_len = *len;
- ret = hashmap_get(map, &key, NULL);
+ key.orig_len = len;
+
+ /* First check if it's a token the user configured manually... */
+ if (anonymized_seeds.cmpfn)
+ ret = hashmap_get_entry(&anonymized_seeds, &key, hash, &key);
+ else
+ ret = NULL;
+
+ /* ...otherwise check if we've already seen it in this context... */
+ if (!ret)
+ ret = hashmap_get_entry(map, &key, hash, &key);
+ /* ...and finally generate a new mapping if necessary */
if (!ret) {
- ret = xmalloc(sizeof(*ret));
+ FLEX_ALLOC_MEM(ret, orig, orig, len);
hashmap_entry_init(&ret->hash, key.hash.hash);
- ret->orig = xstrdup(orig);
- ret->orig_len = *len;
- ret->anon = generate(orig, len);
- ret->anon_len = *len;
- hashmap_put(map, ret);
+ ret->anon = generate(data);
+ hashmap_put(map, &ret->hash);
}
- *len = ret->anon_len;
return ret->anon;
}
@@ -149,13 +199,13 @@ static const void *anonymize_mem(struct hashmap *map,
*/
static void anonymize_path(struct strbuf *out, const char *path,
struct hashmap *map,
- void *(*generate)(const void *, size_t *))
+ char *(*generate)(void *))
{
while (*path) {
const char *end_of_component = strchrnul(path, '/');
size_t len = end_of_component - path;
- const char *c = anonymize_mem(map, generate, path, &len);
- strbuf_add(out, c, len);
+ const char *c = anonymize_str(map, generate, path, len, NULL);
+ strbuf_addstr(out, c);
path = end_of_component;
if (*path)
strbuf_addch(out, *path++);
@@ -249,7 +299,7 @@ static void export_blob(const struct object_id *oid)
if (is_null_oid(oid))
return;
- object = lookup_object(the_repository, oid->hash);
+ object = lookup_object(the_repository, oid);
if (object && object->flags & SHOWN)
return;
@@ -261,7 +311,8 @@ static void export_blob(const struct object_id *oid)
buf = read_object_file(oid, &type, &size);
if (!buf)
die("could not read blob %s", oid_to_hex(oid));
- if (check_object_signature(oid, buf, size, type_name(type)) < 0)
+ if (check_object_signature(the_repository, oid, buf, size,
+ type_name(type)) < 0)
die("oid mismatch in blob %s", oid_to_hex(oid));
object = parse_object_buffer(the_repository, oid, type,
size, buf, &eaten);
@@ -328,12 +379,12 @@ static void print_path_1(const char *path)
printf("%s", path);
}
-static void *anonymize_path_component(const void *path, size_t *len)
+static char *anonymize_path_component(void *data)
{
static int counter;
struct strbuf out = STRBUF_INIT;
strbuf_addf(&out, "path%d", counter++);
- return strbuf_detach(&out, len);
+ return strbuf_detach(&out, NULL);
}
static void print_path(const char *path)
@@ -350,20 +401,23 @@ static void print_path(const char *path)
}
}
-static void *generate_fake_oid(const void *old, size_t *len)
+static char *generate_fake_oid(void *data)
{
static uint32_t counter = 1; /* avoid null oid */
const unsigned hashsz = the_hash_algo->rawsz;
- unsigned char *out = xcalloc(hashsz, 1);
- put_be32(out + hashsz - 4, counter++);
- return out;
+ struct object_id oid;
+ char *hex = xmallocz(GIT_MAX_HEXSZ);
+
+ oidclr(&oid);
+ put_be32(oid.hash + hashsz - 4, counter++);
+ return oid_to_hex_r(hex, &oid);
}
-static const struct object_id *anonymize_oid(const struct object_id *oid)
+static const char *anonymize_oid(const char *oid_hex)
{
static struct hashmap objs;
- size_t len = the_hash_algo->rawsz;
- return anonymize_mem(&objs, generate_fake_oid, oid, &len);
+ size_t len = strlen(oid_hex);
+ return anonymize_str(&objs, generate_fake_oid, oid_hex, len, NULL);
}
static void show_filemodify(struct diff_queue_struct *q,
@@ -422,12 +476,12 @@ static void show_filemodify(struct diff_queue_struct *q,
*/
if (no_data || S_ISGITLINK(spec->mode))
printf("M %06o %s ", spec->mode,
- oid_to_hex(anonymize ?
- anonymize_oid(&spec->oid) :
- &spec->oid));
+ anonymize ?
+ anonymize_oid(oid_to_hex(&spec->oid)) :
+ oid_to_hex(&spec->oid));
else {
struct object *object = lookup_object(the_repository,
- spec->oid.hash);
+ &spec->oid);
printf("M %06o :%d ", spec->mode,
get_object_mark(object));
}
@@ -453,19 +507,19 @@ static const char *find_encoding(const char *begin, const char *end)
bol = memmem(begin, end ? end - begin : strlen(begin),
needle, strlen(needle));
if (!bol)
- return git_commit_encoding;
+ return NULL;
bol += strlen(needle);
eol = strchrnul(bol, '\n');
*eol = '\0';
return bol;
}
-static void *anonymize_ref_component(const void *old, size_t *len)
+static char *anonymize_ref_component(void *data)
{
static int counter;
struct strbuf out = STRBUF_INIT;
strbuf_addf(&out, "ref%d", counter++);
- return strbuf_detach(&out, len);
+ return strbuf_detach(&out, NULL);
}
static const char *anonymize_refname(const char *refname)
@@ -484,13 +538,6 @@ static const char *anonymize_refname(const char *refname)
static struct strbuf anon = STRBUF_INIT;
int i;
- /*
- * We also leave "master" as a special case, since it does not reveal
- * anything interesting.
- */
- if (!strcmp(refname, "refs/heads/master"))
- return refname;
-
strbuf_reset(&anon);
for (i = 0; i < ARRAY_SIZE(prefixes); i++) {
if (skip_prefix(refname, prefixes[i], &refname)) {
@@ -513,14 +560,13 @@ static char *anonymize_commit_message(const char *old)
return xstrfmt("subject %d\n\nbody\n", counter++);
}
-static struct hashmap idents;
-static void *anonymize_ident(const void *old, size_t *len)
+static char *anonymize_ident(void *data)
{
static int counter;
struct strbuf out = STRBUF_INIT;
strbuf_addf(&out, "User %d <user%d@example.com>", counter, counter);
counter++;
- return strbuf_detach(&out, len);
+ return strbuf_detach(&out, NULL);
}
/*
@@ -530,6 +576,7 @@ static void *anonymize_ident(const void *old, size_t *len)
*/
static void anonymize_ident_line(const char **beg, const char **end)
{
+ static struct hashmap idents;
static struct strbuf buffers[] = { STRBUF_INIT, STRBUF_INIT };
static unsigned which_buffer;
@@ -555,9 +602,9 @@ static void anonymize_ident_line(const char **beg, const char **end)
size_t len;
len = split.mail_end - split.name_begin;
- ident = anonymize_mem(&idents, anonymize_ident,
- split.name_begin, &len);
- strbuf_add(out, ident, len);
+ ident = anonymize_str(&idents, anonymize_ident,
+ split.name_begin, len, NULL);
+ strbuf_addstr(out, ident);
strbuf_addch(out, ' ');
strbuf_add(out, split.date_begin, split.tz_end - split.date_begin);
} else {
@@ -633,18 +680,32 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
}
mark_next_object(&commit->object);
- if (anonymize)
+ if (anonymize) {
reencoded = anonymize_commit_message(message);
- else if (!is_encoding_utf8(encoding))
- reencoded = reencode_string(message, "UTF-8", encoding);
+ } else if (encoding) {
+ switch(reencode_mode) {
+ case REENCODE_YES:
+ reencoded = reencode_string(message, "UTF-8", encoding);
+ break;
+ case REENCODE_NO:
+ break;
+ case REENCODE_ABORT:
+ die("Encountered commit-specific encoding %s in commit "
+ "%s; use --reencode=[yes|no] to handle it",
+ encoding, oid_to_hex(&commit->object.oid));
+ }
+ }
if (!commit->parents)
printf("reset %s\n", refname);
printf("commit %s\nmark :%"PRIu32"\n", refname, last_idnum);
if (show_original_ids)
printf("original-oid %s\n", oid_to_hex(&commit->object.oid));
- printf("%.*s\n%.*s\ndata %u\n%s",
+ printf("%.*s\n%.*s\n",
(int)(author_end - author), author,
- (int)(committer_end - committer), committer,
+ (int)(committer_end - committer), committer);
+ if (!reencoded && encoding)
+ printf("encoding %s\n", encoding);
+ printf("data %u\n%s",
(unsigned)(reencoded
? strlen(reencoded) : message
? strlen(message) : 0),
@@ -665,9 +726,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
if (mark)
printf(":%d\n", mark);
else
- printf("%s\n", oid_to_hex(anonymize ?
- anonymize_oid(&obj->oid) :
- &obj->oid));
+ printf("%s\n",
+ anonymize ?
+ anonymize_oid(oid_to_hex(&obj->oid)) :
+ oid_to_hex(&obj->oid));
i++;
}
@@ -682,12 +744,12 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
show_progress();
}
-static void *anonymize_tag(const void *old, size_t *len)
+static char *anonymize_tag(void *data)
{
static int counter;
struct strbuf out = STRBUF_INIT;
strbuf_addf(&out, "tag message %d", counter++);
- return strbuf_detach(&out, len);
+ return strbuf_detach(&out, NULL);
}
static void handle_tail(struct object_array *commits, struct rev_info *revs,
@@ -757,8 +819,8 @@ static void handle_tag(const char *name, struct tag *tag)
name = anonymize_refname(name);
if (message) {
static struct hashmap tags;
- message = anonymize_mem(&tags, anonymize_tag,
- message, &message_size);
+ message = anonymize_str(&tags, anonymize_tag,
+ message, message_size, NULL);
}
}
@@ -802,25 +864,39 @@ static void handle_tag(const char *name, struct tag *tag)
free(buf);
return;
case REWRITE:
- if (tagged->type != OBJ_COMMIT) {
- die("tag %s tags unexported %s!",
- oid_to_hex(&tag->object.oid),
- type_name(tagged->type));
- }
- p = rewrite_commit((struct commit *)tagged);
- if (!p) {
- printf("reset %s\nfrom %s\n\n",
- name, oid_to_hex(&null_oid));
- free(buf);
- return;
+ if (tagged->type == OBJ_TAG && !mark_tags) {
+ die(_("Error: Cannot export nested tags unless --mark-tags is specified."));
+ } else if (tagged->type == OBJ_COMMIT) {
+ p = rewrite_commit((struct commit *)tagged);
+ if (!p) {
+ printf("reset %s\nfrom %s\n\n",
+ name, oid_to_hex(&null_oid));
+ free(buf);
+ return;
+ }
+ tagged_mark = get_object_mark(&p->object);
+ } else {
+ /* tagged->type is either OBJ_BLOB or OBJ_TAG */
+ tagged_mark = get_object_mark(tagged);
}
- tagged_mark = get_object_mark(&p->object);
}
}
- if (starts_with(name, "refs/tags/"))
- name += 10;
- printf("tag %s\nfrom :%d\n", name, tagged_mark);
+ if (tagged->type == OBJ_TAG) {
+ printf("reset %s\nfrom %s\n\n",
+ name, oid_to_hex(&null_oid));
+ }
+ skip_prefix(name, "refs/tags/", &name);
+ printf("tag %s\n", name);
+ if (mark_tags) {
+ mark_next_object(&tag->object);
+ printf("mark :%"PRIu32"\n", last_idnum);
+ }
+ if (tagged_mark)
+ printf("from :%d\n", tagged_mark);
+ else
+ printf("from %s\n", oid_to_hex(&tagged->oid));
+
if (show_original_ids)
printf("original-oid %s\n", oid_to_hex(&tag->object.oid));
printf("%.*s%sdata %d\n%.*s\n",
@@ -867,7 +943,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
if (e->flags & UNINTERESTING)
continue;
- if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1)
+ if (dwim_ref(e->name, strlen(e->name), &oid, &full_name, 0) != 1)
continue;
if (refspecs.nr) {
@@ -950,7 +1026,7 @@ static void handle_tags_and_duplicates(struct string_list *extras)
/*
* Getting here means we have a commit which
* was excluded by a negative refspec (e.g.
- * fast-export ^master master). If we are
+ * fast-export ^HEAD HEAD). If we are
* referencing excluded commits, set the ref
* to the exact commit. Otherwise, the user
* wants the branch exported but every commit
@@ -1007,11 +1083,16 @@ static void export_marks(char *file)
error("Unable to write marks file %s.", file);
}
-static void import_marks(char *input_file)
+static void import_marks(char *input_file, int check_exists)
{
char line[512];
- FILE *f = xfopen(input_file, "r");
+ FILE *f;
+ struct stat sb;
+ if (check_exists && stat(input_file, &sb))
+ return;
+
+ f = xfopen(input_file, "r");
while (fgets(line, sizeof(line), f)) {
uint32_t mark;
char *line_end, *mark_end;
@@ -1070,12 +1151,45 @@ static void handle_deletes(void)
}
}
+static char *anonymize_seed(void *data)
+{
+ return xstrdup(data);
+}
+
+static int parse_opt_anonymize_map(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct hashmap *map = opt->value;
+ const char *delim, *value;
+ size_t keylen;
+
+ BUG_ON_OPT_NEG(unset);
+
+ delim = strchr(arg, ':');
+ if (delim) {
+ keylen = delim - arg;
+ value = delim + 1;
+ } else {
+ keylen = strlen(arg);
+ value = arg;
+ }
+
+ if (!keylen || !*value)
+ return error(_("--anonymize-map token cannot be empty"));
+
+ anonymize_str(map, anonymize_seed, arg, keylen, (void *)value);
+
+ return 0;
+}
+
int cmd_fast_export(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
struct object_array commits = OBJECT_ARRAY_INIT;
struct commit *commit;
- char *export_filename = NULL, *import_filename = NULL;
+ char *export_filename = NULL,
+ *import_filename = NULL,
+ *import_filename_if_exists = NULL;
uint32_t lastimportid;
struct string_list refspecs_list = STRING_LIST_INIT_NODUP;
struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP;
@@ -1088,10 +1202,17 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, N_("mode"),
N_("select handling of tags that tag filtered objects"),
parse_opt_tag_of_filtered_mode),
+ OPT_CALLBACK(0, "reencode", &reencode_mode, N_("mode"),
+ N_("select handling of commit messages in an alternate encoding"),
+ parse_opt_reencode_mode),
OPT_STRING(0, "export-marks", &export_filename, N_("file"),
N_("Dump marks to this file")),
OPT_STRING(0, "import-marks", &import_filename, N_("file"),
N_("Import marks from this file")),
+ OPT_STRING(0, "import-marks-if-exists",
+ &import_filename_if_exists,
+ N_("file"),
+ N_("Import marks from this file if it exists")),
OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger,
N_("Fake a tagger when tags lack one")),
OPT_BOOL(0, "full-tree", &full_tree,
@@ -1102,10 +1223,15 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"),
N_("Apply refspec to exported refs")),
OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")),
+ OPT_CALLBACK_F(0, "anonymize-map", &anonymized_seeds, N_("from:to"),
+ N_("convert <from> to <to> in anonymized output"),
+ PARSE_OPT_NONEG, parse_opt_anonymize_map),
OPT_BOOL(0, "reference-excluded-parents",
&reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")),
OPT_BOOL(0, "show-original-ids", &show_original_ids,
N_("Show original object ids of blobs/commits")),
+ OPT_BOOL(0, "mark-tags", &mark_tags,
+ N_("Label tags with mark ids")),
OPT_END()
};
@@ -1127,6 +1253,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (argc > 1)
usage_with_options (fast_export_usage, options);
+ if (anonymized_seeds.cmpfn && !anonymize)
+ die(_("--anonymize-map without --anonymize does not make sense"));
+
if (refspecs_list.nr) {
int i;
@@ -1139,8 +1268,12 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (use_done_feature)
printf("feature done\n");
+ if (import_filename && import_filename_if_exists)
+ die(_("Cannot pass both --import-marks and --import-marks-if-exists"));
if (import_filename)
- import_marks(import_filename);
+ import_marks(import_filename, 0);
+ else if (import_filename_if_exists)
+ import_marks(import_filename_if_exists, 1);
lastimportid = last_idnum;
if (import_filename && revs.prune_data.nr)
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
new file mode 100644
index 0000000000..dd4d09cece
--- /dev/null
+++ b/builtin/fast-import.c
@@ -0,0 +1,3636 @@
+#include "builtin.h"
+#include "cache.h"
+#include "repository.h"
+#include "config.h"
+#include "lockfile.h"
+#include "object.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "delta.h"
+#include "pack.h"
+#include "refs.h"
+#include "csum-file.h"
+#include "quote.h"
+#include "dir.h"
+#include "run-command.h"
+#include "packfile.h"
+#include "object-store.h"
+#include "mem-pool.h"
+#include "commit-reach.h"
+#include "khash.h"
+
+#define PACK_ID_BITS 16
+#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
+#define DEPTH_BITS 13
+#define MAX_DEPTH ((1<<DEPTH_BITS)-1)
+
+/*
+ * We abuse the setuid bit on directories to mean "do not delta".
+ */
+#define NO_DELTA S_ISUID
+
+/*
+ * The amount of additional space required in order to write an object into the
+ * current pack. This is the hash lengths at the end of the pack, plus the
+ * length of one object ID.
+ */
+#define PACK_SIZE_THRESHOLD (the_hash_algo->rawsz * 3)
+
+struct object_entry {
+ struct pack_idx_entry idx;
+ struct hashmap_entry ent;
+ uint32_t type : TYPE_BITS,
+ pack_id : PACK_ID_BITS,
+ depth : DEPTH_BITS;
+};
+
+static int object_entry_hashcmp(const void *map_data,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
+ const void *keydata)
+{
+ const struct object_id *oid = keydata;
+ const struct object_entry *e1, *e2;
+
+ e1 = container_of(eptr, const struct object_entry, ent);
+ if (oid)
+ return oidcmp(&e1->idx.oid, oid);
+
+ e2 = container_of(entry_or_key, const struct object_entry, ent);
+ return oidcmp(&e1->idx.oid, &e2->idx.oid);
+}
+
+struct object_entry_pool {
+ struct object_entry_pool *next_pool;
+ struct object_entry *next_free;
+ struct object_entry *end;
+ struct object_entry entries[FLEX_ARRAY]; /* more */
+};
+
+struct mark_set {
+ union {
+ struct object_id *oids[1024];
+ struct object_entry *marked[1024];
+ struct mark_set *sets[1024];
+ } data;
+ unsigned int shift;
+};
+
+struct last_object {
+ struct strbuf data;
+ off_t offset;
+ unsigned int depth;
+ unsigned no_swap : 1;
+};
+
+struct atom_str {
+ struct atom_str *next_atom;
+ unsigned short str_len;
+ char str_dat[FLEX_ARRAY]; /* more */
+};
+
+struct tree_content;
+struct tree_entry {
+ struct tree_content *tree;
+ struct atom_str *name;
+ struct tree_entry_ms {
+ uint16_t mode;
+ struct object_id oid;
+ } versions[2];
+};
+
+struct tree_content {
+ unsigned int entry_capacity; /* must match avail_tree_content */
+ unsigned int entry_count;
+ unsigned int delta_depth;
+ struct tree_entry *entries[FLEX_ARRAY]; /* more */
+};
+
+struct avail_tree_content {
+ unsigned int entry_capacity; /* must match tree_content */
+ struct avail_tree_content *next_avail;
+};
+
+struct branch {
+ struct branch *table_next_branch;
+ struct branch *active_next_branch;
+ const char *name;
+ struct tree_entry branch_tree;
+ uintmax_t last_commit;
+ uintmax_t num_notes;
+ unsigned active : 1;
+ unsigned delete : 1;
+ unsigned pack_id : PACK_ID_BITS;
+ struct object_id oid;
+};
+
+struct tag {
+ struct tag *next_tag;
+ const char *name;
+ unsigned int pack_id;
+ struct object_id oid;
+};
+
+struct hash_list {
+ struct hash_list *next;
+ struct object_id oid;
+};
+
+typedef enum {
+ WHENSPEC_RAW = 1,
+ WHENSPEC_RAW_PERMISSIVE,
+ WHENSPEC_RFC2822,
+ WHENSPEC_NOW
+} whenspec_type;
+
+struct recent_command {
+ struct recent_command *prev;
+ struct recent_command *next;
+ char *buf;
+};
+
+typedef void (*mark_set_inserter_t)(struct mark_set **s, struct object_id *oid, uintmax_t mark);
+typedef void (*each_mark_fn_t)(uintmax_t mark, void *obj, void *cbp);
+
+/* Configured limits on output */
+static unsigned long max_depth = 50;
+static off_t max_packsize;
+static int unpack_limit = 100;
+static int force_update;
+
+/* Stats and misc. counters */
+static uintmax_t alloc_count;
+static uintmax_t marks_set_count;
+static uintmax_t object_count_by_type[1 << TYPE_BITS];
+static uintmax_t duplicate_count_by_type[1 << TYPE_BITS];
+static uintmax_t delta_count_by_type[1 << TYPE_BITS];
+static uintmax_t delta_count_attempts_by_type[1 << TYPE_BITS];
+static unsigned long object_count;
+static unsigned long branch_count;
+static unsigned long branch_load_count;
+static int failure;
+static FILE *pack_edges;
+static unsigned int show_stats = 1;
+static int global_argc;
+static const char **global_argv;
+
+/* Memory pools */
+static struct mem_pool fi_mem_pool = {NULL, 2*1024*1024 -
+ sizeof(struct mp_block), 0 };
+
+/* Atom management */
+static unsigned int atom_table_sz = 4451;
+static unsigned int atom_cnt;
+static struct atom_str **atom_table;
+
+/* The .pack file being generated */
+static struct pack_idx_option pack_idx_opts;
+static unsigned int pack_id;
+static struct hashfile *pack_file;
+static struct packed_git *pack_data;
+static struct packed_git **all_packs;
+static off_t pack_size;
+
+/* Table of objects we've written. */
+static unsigned int object_entry_alloc = 5000;
+static struct object_entry_pool *blocks;
+static struct hashmap object_table;
+static struct mark_set *marks;
+static const char *export_marks_file;
+static const char *import_marks_file;
+static int import_marks_file_from_stream;
+static int import_marks_file_ignore_missing;
+static int import_marks_file_done;
+static int relative_marks_paths;
+
+/* Our last blob */
+static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 };
+
+/* Tree management */
+static unsigned int tree_entry_alloc = 1000;
+static void *avail_tree_entry;
+static unsigned int avail_tree_table_sz = 100;
+static struct avail_tree_content **avail_tree_table;
+static size_t tree_entry_allocd;
+static struct strbuf old_tree = STRBUF_INIT;
+static struct strbuf new_tree = STRBUF_INIT;
+
+/* Branch data */
+static unsigned long max_active_branches = 5;
+static unsigned long cur_active_branches;
+static unsigned long branch_table_sz = 1039;
+static struct branch **branch_table;
+static struct branch *active_branches;
+
+/* Tag data */
+static struct tag *first_tag;
+static struct tag *last_tag;
+
+/* Input stream parsing */
+static whenspec_type whenspec = WHENSPEC_RAW;
+static struct strbuf command_buf = STRBUF_INIT;
+static int unread_command_buf;
+static struct recent_command cmd_hist = {&cmd_hist, &cmd_hist, NULL};
+static struct recent_command *cmd_tail = &cmd_hist;
+static struct recent_command *rc_free;
+static unsigned int cmd_save = 100;
+static uintmax_t next_mark;
+static struct strbuf new_data = STRBUF_INIT;
+static int seen_data_command;
+static int require_explicit_termination;
+static int allow_unsafe_features;
+
+/* Signal handling */
+static volatile sig_atomic_t checkpoint_requested;
+
+/* Submodule marks */
+static struct string_list sub_marks_from = STRING_LIST_INIT_DUP;
+static struct string_list sub_marks_to = STRING_LIST_INIT_DUP;
+static kh_oid_map_t *sub_oid_map;
+
+/* Where to write output of cat-blob commands */
+static int cat_blob_fd = STDOUT_FILENO;
+
+static void parse_argv(void);
+static void parse_get_mark(const char *p);
+static void parse_cat_blob(const char *p);
+static void parse_ls(const char *p, struct branch *b);
+
+static void for_each_mark(struct mark_set *m, uintmax_t base, each_mark_fn_t callback, void *p)
+{
+ uintmax_t k;
+ if (m->shift) {
+ for (k = 0; k < 1024; k++) {
+ if (m->data.sets[k])
+ for_each_mark(m->data.sets[k], base + (k << m->shift), callback, p);
+ }
+ } else {
+ for (k = 0; k < 1024; k++) {
+ if (m->data.marked[k])
+ callback(base + k, m->data.marked[k], p);
+ }
+ }
+}
+
+static void dump_marks_fn(uintmax_t mark, void *object, void *cbp) {
+ struct object_entry *e = object;
+ FILE *f = cbp;
+
+ fprintf(f, ":%" PRIuMAX " %s\n", mark, oid_to_hex(&e->idx.oid));
+}
+
+static void write_branch_report(FILE *rpt, struct branch *b)
+{
+ fprintf(rpt, "%s:\n", b->name);
+
+ fprintf(rpt, " status :");
+ if (b->active)
+ fputs(" active", rpt);
+ if (b->branch_tree.tree)
+ fputs(" loaded", rpt);
+ if (is_null_oid(&b->branch_tree.versions[1].oid))
+ fputs(" dirty", rpt);
+ fputc('\n', rpt);
+
+ fprintf(rpt, " tip commit : %s\n", oid_to_hex(&b->oid));
+ fprintf(rpt, " old tree : %s\n",
+ oid_to_hex(&b->branch_tree.versions[0].oid));
+ fprintf(rpt, " cur tree : %s\n",
+ oid_to_hex(&b->branch_tree.versions[1].oid));
+ fprintf(rpt, " commit clock: %" PRIuMAX "\n", b->last_commit);
+
+ fputs(" last pack : ", rpt);
+ if (b->pack_id < MAX_PACK_ID)
+ fprintf(rpt, "%u", b->pack_id);
+ fputc('\n', rpt);
+
+ fputc('\n', rpt);
+}
+
+static void write_crash_report(const char *err)
+{
+ char *loc = git_pathdup("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
+ FILE *rpt = fopen(loc, "w");
+ struct branch *b;
+ unsigned long lu;
+ struct recent_command *rc;
+
+ if (!rpt) {
+ error_errno("can't write crash report %s", loc);
+ free(loc);
+ return;
+ }
+
+ fprintf(stderr, "fast-import: dumping crash report to %s\n", loc);
+
+ fprintf(rpt, "fast-import crash report:\n");
+ fprintf(rpt, " fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
+ fprintf(rpt, " parent process : %"PRIuMAX"\n", (uintmax_t) getppid());
+ fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_MODE(ISO8601)));
+ fputc('\n', rpt);
+
+ fputs("fatal: ", rpt);
+ fputs(err, rpt);
+ fputc('\n', rpt);
+
+ fputc('\n', rpt);
+ fputs("Most Recent Commands Before Crash\n", rpt);
+ fputs("---------------------------------\n", rpt);
+ for (rc = cmd_hist.next; rc != &cmd_hist; rc = rc->next) {
+ if (rc->next == &cmd_hist)
+ fputs("* ", rpt);
+ else
+ fputs(" ", rpt);
+ fputs(rc->buf, rpt);
+ fputc('\n', rpt);
+ }
+
+ fputc('\n', rpt);
+ fputs("Active Branch LRU\n", rpt);
+ fputs("-----------------\n", rpt);
+ fprintf(rpt, " active_branches = %lu cur, %lu max\n",
+ cur_active_branches,
+ max_active_branches);
+ fputc('\n', rpt);
+ fputs(" pos clock name\n", rpt);
+ fputs(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", rpt);
+ for (b = active_branches, lu = 0; b; b = b->active_next_branch)
+ fprintf(rpt, " %2lu) %6" PRIuMAX" %s\n",
+ ++lu, b->last_commit, b->name);
+
+ fputc('\n', rpt);
+ fputs("Inactive Branches\n", rpt);
+ fputs("-----------------\n", rpt);
+ for (lu = 0; lu < branch_table_sz; lu++) {
+ for (b = branch_table[lu]; b; b = b->table_next_branch)
+ write_branch_report(rpt, b);
+ }
+
+ if (first_tag) {
+ struct tag *tg;
+ fputc('\n', rpt);
+ fputs("Annotated Tags\n", rpt);
+ fputs("--------------\n", rpt);
+ for (tg = first_tag; tg; tg = tg->next_tag) {
+ fputs(oid_to_hex(&tg->oid), rpt);
+ fputc(' ', rpt);
+ fputs(tg->name, rpt);
+ fputc('\n', rpt);
+ }
+ }
+
+ fputc('\n', rpt);
+ fputs("Marks\n", rpt);
+ fputs("-----\n", rpt);
+ if (export_marks_file)
+ fprintf(rpt, " exported to %s\n", export_marks_file);
+ else
+ for_each_mark(marks, 0, dump_marks_fn, rpt);
+
+ fputc('\n', rpt);
+ fputs("-------------------\n", rpt);
+ fputs("END OF CRASH REPORT\n", rpt);
+ fclose(rpt);
+ free(loc);
+}
+
+static void end_packfile(void);
+static void unkeep_all_packs(void);
+static void dump_marks(void);
+
+static NORETURN void die_nicely(const char *err, va_list params)
+{
+ static int zombie;
+ char message[2 * PATH_MAX];
+
+ vsnprintf(message, sizeof(message), err, params);
+ fputs("fatal: ", stderr);
+ fputs(message, stderr);
+ fputc('\n', stderr);
+
+ if (!zombie) {
+ zombie = 1;
+ write_crash_report(message);
+ end_packfile();
+ unkeep_all_packs();
+ dump_marks();
+ }
+ exit(128);
+}
+
+#ifndef SIGUSR1 /* Windows, for example */
+
+static void set_checkpoint_signal(void)
+{
+}
+
+#else
+
+static void checkpoint_signal(int signo)
+{
+ checkpoint_requested = 1;
+}
+
+static void set_checkpoint_signal(void)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = checkpoint_signal;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGUSR1, &sa, NULL);
+}
+
+#endif
+
+static void alloc_objects(unsigned int cnt)
+{
+ struct object_entry_pool *b;
+
+ b = xmalloc(sizeof(struct object_entry_pool)
+ + cnt * sizeof(struct object_entry));
+ b->next_pool = blocks;
+ b->next_free = b->entries;
+ b->end = b->entries + cnt;
+ blocks = b;
+ alloc_count += cnt;
+}
+
+static struct object_entry *new_object(struct object_id *oid)
+{
+ struct object_entry *e;
+
+ if (blocks->next_free == blocks->end)
+ alloc_objects(object_entry_alloc);
+
+ e = blocks->next_free++;
+ oidcpy(&e->idx.oid, oid);
+ return e;
+}
+
+static struct object_entry *find_object(struct object_id *oid)
+{
+ return hashmap_get_entry_from_hash(&object_table, oidhash(oid), oid,
+ struct object_entry, ent);
+}
+
+static struct object_entry *insert_object(struct object_id *oid)
+{
+ struct object_entry *e;
+ unsigned int hash = oidhash(oid);
+
+ e = hashmap_get_entry_from_hash(&object_table, hash, oid,
+ struct object_entry, ent);
+ if (!e) {
+ e = new_object(oid);
+ e->idx.offset = 0;
+ hashmap_entry_init(&e->ent, hash);
+ hashmap_add(&object_table, &e->ent);
+ }
+
+ return e;
+}
+
+static void invalidate_pack_id(unsigned int id)
+{
+ unsigned long lu;
+ struct tag *t;
+ struct hashmap_iter iter;
+ struct object_entry *e;
+
+ hashmap_for_each_entry(&object_table, &iter, e, ent) {
+ if (e->pack_id == id)
+ e->pack_id = MAX_PACK_ID;
+ }
+
+ for (lu = 0; lu < branch_table_sz; lu++) {
+ struct branch *b;
+
+ for (b = branch_table[lu]; b; b = b->table_next_branch)
+ if (b->pack_id == id)
+ b->pack_id = MAX_PACK_ID;
+ }
+
+ for (t = first_tag; t; t = t->next_tag)
+ if (t->pack_id == id)
+ t->pack_id = MAX_PACK_ID;
+}
+
+static unsigned int hc_str(const char *s, size_t len)
+{
+ unsigned int r = 0;
+ while (len-- > 0)
+ r = r * 31 + *s++;
+ return r;
+}
+
+static void insert_mark(struct mark_set **top, uintmax_t idnum, struct object_entry *oe)
+{
+ struct mark_set *s = *top;
+
+ while ((idnum >> s->shift) >= 1024) {
+ s = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
+ s->shift = (*top)->shift + 10;
+ s->data.sets[0] = *top;
+ *top = s;
+ }
+ while (s->shift) {
+ uintmax_t i = idnum >> s->shift;
+ idnum -= i << s->shift;
+ if (!s->data.sets[i]) {
+ s->data.sets[i] = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
+ s->data.sets[i]->shift = s->shift - 10;
+ }
+ s = s->data.sets[i];
+ }
+ if (!s->data.marked[idnum])
+ marks_set_count++;
+ s->data.marked[idnum] = oe;
+}
+
+static void *find_mark(struct mark_set *s, uintmax_t idnum)
+{
+ uintmax_t orig_idnum = idnum;
+ struct object_entry *oe = NULL;
+ if ((idnum >> s->shift) < 1024) {
+ while (s && s->shift) {
+ uintmax_t i = idnum >> s->shift;
+ idnum -= i << s->shift;
+ s = s->data.sets[i];
+ }
+ if (s)
+ oe = s->data.marked[idnum];
+ }
+ if (!oe)
+ die("mark :%" PRIuMAX " not declared", orig_idnum);
+ return oe;
+}
+
+static struct atom_str *to_atom(const char *s, unsigned short len)
+{
+ unsigned int hc = hc_str(s, len) % atom_table_sz;
+ struct atom_str *c;
+
+ for (c = atom_table[hc]; c; c = c->next_atom)
+ if (c->str_len == len && !strncmp(s, c->str_dat, len))
+ return c;
+
+ c = mem_pool_alloc(&fi_mem_pool, sizeof(struct atom_str) + len + 1);
+ c->str_len = len;
+ memcpy(c->str_dat, s, len);
+ c->str_dat[len] = 0;
+ c->next_atom = atom_table[hc];
+ atom_table[hc] = c;
+ atom_cnt++;
+ return c;
+}
+
+static struct branch *lookup_branch(const char *name)
+{
+ unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz;
+ struct branch *b;
+
+ for (b = branch_table[hc]; b; b = b->table_next_branch)
+ if (!strcmp(name, b->name))
+ return b;
+ return NULL;
+}
+
+static struct branch *new_branch(const char *name)
+{
+ unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz;
+ struct branch *b = lookup_branch(name);
+
+ if (b)
+ die("Invalid attempt to create duplicate branch: %s", name);
+ if (check_refname_format(name, REFNAME_ALLOW_ONELEVEL))
+ die("Branch name doesn't conform to GIT standards: %s", name);
+
+ b = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct branch));
+ b->name = mem_pool_strdup(&fi_mem_pool, name);
+ b->table_next_branch = branch_table[hc];
+ b->branch_tree.versions[0].mode = S_IFDIR;
+ b->branch_tree.versions[1].mode = S_IFDIR;
+ b->num_notes = 0;
+ b->active = 0;
+ b->pack_id = MAX_PACK_ID;
+ branch_table[hc] = b;
+ branch_count++;
+ return b;
+}
+
+static unsigned int hc_entries(unsigned int cnt)
+{
+ cnt = cnt & 7 ? (cnt / 8) + 1 : cnt / 8;
+ return cnt < avail_tree_table_sz ? cnt : avail_tree_table_sz - 1;
+}
+
+static struct tree_content *new_tree_content(unsigned int cnt)
+{
+ struct avail_tree_content *f, *l = NULL;
+ struct tree_content *t;
+ unsigned int hc = hc_entries(cnt);
+
+ for (f = avail_tree_table[hc]; f; l = f, f = f->next_avail)
+ if (f->entry_capacity >= cnt)
+ break;
+
+ if (f) {
+ if (l)
+ l->next_avail = f->next_avail;
+ else
+ avail_tree_table[hc] = f->next_avail;
+ } else {
+ cnt = cnt & 7 ? ((cnt / 8) + 1) * 8 : cnt;
+ f = mem_pool_alloc(&fi_mem_pool, sizeof(*t) + sizeof(t->entries[0]) * cnt);
+ f->entry_capacity = cnt;
+ }
+
+ t = (struct tree_content*)f;
+ t->entry_count = 0;
+ t->delta_depth = 0;
+ return t;
+}
+
+static void release_tree_entry(struct tree_entry *e);
+static void release_tree_content(struct tree_content *t)
+{
+ struct avail_tree_content *f = (struct avail_tree_content*)t;
+ unsigned int hc = hc_entries(f->entry_capacity);
+ f->next_avail = avail_tree_table[hc];
+ avail_tree_table[hc] = f;
+}
+
+static void release_tree_content_recursive(struct tree_content *t)
+{
+ unsigned int i;
+ for (i = 0; i < t->entry_count; i++)
+ release_tree_entry(t->entries[i]);
+ release_tree_content(t);
+}
+
+static struct tree_content *grow_tree_content(
+ struct tree_content *t,
+ int amt)
+{
+ struct tree_content *r = new_tree_content(t->entry_count + amt);
+ r->entry_count = t->entry_count;
+ r->delta_depth = t->delta_depth;
+ COPY_ARRAY(r->entries, t->entries, t->entry_count);
+ release_tree_content(t);
+ return r;
+}
+
+static struct tree_entry *new_tree_entry(void)
+{
+ struct tree_entry *e;
+
+ if (!avail_tree_entry) {
+ unsigned int n = tree_entry_alloc;
+ tree_entry_allocd += n * sizeof(struct tree_entry);
+ ALLOC_ARRAY(e, n);
+ avail_tree_entry = e;
+ while (n-- > 1) {
+ *((void**)e) = e + 1;
+ e++;
+ }
+ *((void**)e) = NULL;
+ }
+
+ e = avail_tree_entry;
+ avail_tree_entry = *((void**)e);
+ return e;
+}
+
+static void release_tree_entry(struct tree_entry *e)
+{
+ if (e->tree)
+ release_tree_content_recursive(e->tree);
+ *((void**)e) = avail_tree_entry;
+ avail_tree_entry = e;
+}
+
+static struct tree_content *dup_tree_content(struct tree_content *s)
+{
+ struct tree_content *d;
+ struct tree_entry *a, *b;
+ unsigned int i;
+
+ if (!s)
+ return NULL;
+ d = new_tree_content(s->entry_count);
+ for (i = 0; i < s->entry_count; i++) {
+ a = s->entries[i];
+ b = new_tree_entry();
+ memcpy(b, a, sizeof(*a));
+ if (a->tree && is_null_oid(&b->versions[1].oid))
+ b->tree = dup_tree_content(a->tree);
+ else
+ b->tree = NULL;
+ d->entries[i] = b;
+ }
+ d->entry_count = s->entry_count;
+ d->delta_depth = s->delta_depth;
+
+ return d;
+}
+
+static void start_packfile(void)
+{
+ struct strbuf tmp_file = STRBUF_INIT;
+ struct packed_git *p;
+ int pack_fd;
+
+ pack_fd = odb_mkstemp(&tmp_file, "pack/tmp_pack_XXXXXX");
+ FLEX_ALLOC_STR(p, pack_name, tmp_file.buf);
+ strbuf_release(&tmp_file);
+
+ p->pack_fd = pack_fd;
+ p->do_not_close = 1;
+ pack_file = hashfd(pack_fd, p->pack_name);
+
+ pack_data = p;
+ pack_size = write_pack_header(pack_file, 0);
+ object_count = 0;
+
+ REALLOC_ARRAY(all_packs, pack_id + 1);
+ all_packs[pack_id] = p;
+}
+
+static const char *create_index(void)
+{
+ const char *tmpfile;
+ struct pack_idx_entry **idx, **c, **last;
+ struct object_entry *e;
+ struct object_entry_pool *o;
+
+ /* Build the table of object IDs. */
+ ALLOC_ARRAY(idx, object_count);
+ c = idx;
+ for (o = blocks; o; o = o->next_pool)
+ for (e = o->next_free; e-- != o->entries;)
+ if (pack_id == e->pack_id)
+ *c++ = &e->idx;
+ last = idx + object_count;
+ if (c != last)
+ die("internal consistency error creating the index");
+
+ tmpfile = write_idx_file(NULL, idx, object_count, &pack_idx_opts,
+ pack_data->hash);
+ free(idx);
+ return tmpfile;
+}
+
+static char *keep_pack(const char *curr_index_name)
+{
+ static const char *keep_msg = "fast-import";
+ struct strbuf name = STRBUF_INIT;
+ int keep_fd;
+
+ odb_pack_name(&name, pack_data->hash, "keep");
+ keep_fd = odb_pack_keep(name.buf);
+ if (keep_fd < 0)
+ die_errno("cannot create keep file");
+ write_or_die(keep_fd, keep_msg, strlen(keep_msg));
+ if (close(keep_fd))
+ die_errno("failed to write keep file");
+
+ odb_pack_name(&name, pack_data->hash, "pack");
+ if (finalize_object_file(pack_data->pack_name, name.buf))
+ die("cannot store pack file");
+
+ odb_pack_name(&name, pack_data->hash, "idx");
+ if (finalize_object_file(curr_index_name, name.buf))
+ die("cannot store index file");
+ free((void *)curr_index_name);
+ return strbuf_detach(&name, NULL);
+}
+
+static void unkeep_all_packs(void)
+{
+ struct strbuf name = STRBUF_INIT;
+ int k;
+
+ for (k = 0; k < pack_id; k++) {
+ struct packed_git *p = all_packs[k];
+ odb_pack_name(&name, p->hash, "keep");
+ unlink_or_warn(name.buf);
+ }
+ strbuf_release(&name);
+}
+
+static int loosen_small_pack(const struct packed_git *p)
+{
+ struct child_process unpack = CHILD_PROCESS_INIT;
+
+ if (lseek(p->pack_fd, 0, SEEK_SET) < 0)
+ die_errno("Failed seeking to start of '%s'", p->pack_name);
+
+ unpack.in = p->pack_fd;
+ unpack.git_cmd = 1;
+ unpack.stdout_to_stderr = 1;
+ strvec_push(&unpack.args, "unpack-objects");
+ if (!show_stats)
+ strvec_push(&unpack.args, "-q");
+
+ return run_command(&unpack);
+}
+
+static void end_packfile(void)
+{
+ static int running;
+
+ if (running || !pack_data)
+ return;
+
+ running = 1;
+ clear_delta_base_cache();
+ if (object_count) {
+ struct packed_git *new_p;
+ struct object_id cur_pack_oid;
+ char *idx_name;
+ int i;
+ struct branch *b;
+ struct tag *t;
+
+ close_pack_windows(pack_data);
+ finalize_hashfile(pack_file, cur_pack_oid.hash, 0);
+ fixup_pack_header_footer(pack_data->pack_fd, pack_data->hash,
+ pack_data->pack_name, object_count,
+ cur_pack_oid.hash, pack_size);
+
+ if (object_count <= unpack_limit) {
+ if (!loosen_small_pack(pack_data)) {
+ invalidate_pack_id(pack_id);
+ goto discard_pack;
+ }
+ }
+
+ close(pack_data->pack_fd);
+ idx_name = keep_pack(create_index());
+
+ /* Register the packfile with core git's machinery. */
+ new_p = add_packed_git(idx_name, strlen(idx_name), 1);
+ if (!new_p)
+ die("core git rejected index %s", idx_name);
+ all_packs[pack_id] = new_p;
+ install_packed_git(the_repository, new_p);
+ free(idx_name);
+
+ /* Print the boundary */
+ if (pack_edges) {
+ fprintf(pack_edges, "%s:", new_p->pack_name);
+ for (i = 0; i < branch_table_sz; i++) {
+ for (b = branch_table[i]; b; b = b->table_next_branch) {
+ if (b->pack_id == pack_id)
+ fprintf(pack_edges, " %s",
+ oid_to_hex(&b->oid));
+ }
+ }
+ for (t = first_tag; t; t = t->next_tag) {
+ if (t->pack_id == pack_id)
+ fprintf(pack_edges, " %s",
+ oid_to_hex(&t->oid));
+ }
+ fputc('\n', pack_edges);
+ fflush(pack_edges);
+ }
+
+ pack_id++;
+ }
+ else {
+discard_pack:
+ close(pack_data->pack_fd);
+ unlink_or_warn(pack_data->pack_name);
+ }
+ FREE_AND_NULL(pack_data);
+ running = 0;
+
+ /* We can't carry a delta across packfiles. */
+ strbuf_release(&last_blob.data);
+ last_blob.offset = 0;
+ last_blob.depth = 0;
+}
+
+static void cycle_packfile(void)
+{
+ end_packfile();
+ start_packfile();
+}
+
+static int store_object(
+ enum object_type type,
+ struct strbuf *dat,
+ struct last_object *last,
+ struct object_id *oidout,
+ uintmax_t mark)
+{
+ void *out, *delta;
+ struct object_entry *e;
+ unsigned char hdr[96];
+ struct object_id oid;
+ unsigned long hdrlen, deltalen;
+ git_hash_ctx c;
+ git_zstream s;
+
+ hdrlen = xsnprintf((char *)hdr, sizeof(hdr), "%s %lu",
+ type_name(type), (unsigned long)dat->len) + 1;
+ the_hash_algo->init_fn(&c);
+ the_hash_algo->update_fn(&c, hdr, hdrlen);
+ the_hash_algo->update_fn(&c, dat->buf, dat->len);
+ the_hash_algo->final_fn(oid.hash, &c);
+ if (oidout)
+ oidcpy(oidout, &oid);
+
+ e = insert_object(&oid);
+ if (mark)
+ insert_mark(&marks, mark, e);
+ if (e->idx.offset) {
+ duplicate_count_by_type[type]++;
+ return 1;
+ } else if (find_sha1_pack(oid.hash,
+ get_all_packs(the_repository))) {
+ e->type = type;
+ e->pack_id = MAX_PACK_ID;
+ e->idx.offset = 1; /* just not zero! */
+ duplicate_count_by_type[type]++;
+ return 1;
+ }
+
+ if (last && last->data.len && last->data.buf && last->depth < max_depth
+ && dat->len > the_hash_algo->rawsz) {
+
+ delta_count_attempts_by_type[type]++;
+ delta = diff_delta(last->data.buf, last->data.len,
+ dat->buf, dat->len,
+ &deltalen, dat->len - the_hash_algo->rawsz);
+ } else
+ delta = NULL;
+
+ git_deflate_init(&s, pack_compression_level);
+ if (delta) {
+ s.next_in = delta;
+ s.avail_in = deltalen;
+ } else {
+ s.next_in = (void *)dat->buf;
+ s.avail_in = dat->len;
+ }
+ s.avail_out = git_deflate_bound(&s, s.avail_in);
+ s.next_out = out = xmalloc(s.avail_out);
+ while (git_deflate(&s, Z_FINISH) == Z_OK)
+ ; /* nothing */
+ git_deflate_end(&s);
+
+ /* Determine if we should auto-checkpoint. */
+ if ((max_packsize
+ && (pack_size + PACK_SIZE_THRESHOLD + s.total_out) > max_packsize)
+ || (pack_size + PACK_SIZE_THRESHOLD + s.total_out) < pack_size) {
+
+ /* This new object needs to *not* have the current pack_id. */
+ e->pack_id = pack_id + 1;
+ cycle_packfile();
+
+ /* We cannot carry a delta into the new pack. */
+ if (delta) {
+ FREE_AND_NULL(delta);
+
+ git_deflate_init(&s, pack_compression_level);
+ s.next_in = (void *)dat->buf;
+ s.avail_in = dat->len;
+ s.avail_out = git_deflate_bound(&s, s.avail_in);
+ s.next_out = out = xrealloc(out, s.avail_out);
+ while (git_deflate(&s, Z_FINISH) == Z_OK)
+ ; /* nothing */
+ git_deflate_end(&s);
+ }
+ }
+
+ e->type = type;
+ e->pack_id = pack_id;
+ e->idx.offset = pack_size;
+ object_count++;
+ object_count_by_type[type]++;
+
+ crc32_begin(pack_file);
+
+ if (delta) {
+ off_t ofs = e->idx.offset - last->offset;
+ unsigned pos = sizeof(hdr) - 1;
+
+ delta_count_by_type[type]++;
+ e->depth = last->depth + 1;
+
+ hdrlen = encode_in_pack_object_header(hdr, sizeof(hdr),
+ OBJ_OFS_DELTA, deltalen);
+ hashwrite(pack_file, hdr, hdrlen);
+ pack_size += hdrlen;
+
+ hdr[pos] = ofs & 127;
+ while (ofs >>= 7)
+ hdr[--pos] = 128 | (--ofs & 127);
+ hashwrite(pack_file, hdr + pos, sizeof(hdr) - pos);
+ pack_size += sizeof(hdr) - pos;
+ } else {
+ e->depth = 0;
+ hdrlen = encode_in_pack_object_header(hdr, sizeof(hdr),
+ type, dat->len);
+ hashwrite(pack_file, hdr, hdrlen);
+ pack_size += hdrlen;
+ }
+
+ hashwrite(pack_file, out, s.total_out);
+ pack_size += s.total_out;
+
+ e->idx.crc32 = crc32_end(pack_file);
+
+ free(out);
+ free(delta);
+ if (last) {
+ if (last->no_swap) {
+ last->data = *dat;
+ } else {
+ strbuf_swap(&last->data, dat);
+ }
+ last->offset = e->idx.offset;
+ last->depth = e->depth;
+ }
+ return 0;
+}
+
+static void truncate_pack(struct hashfile_checkpoint *checkpoint)
+{
+ if (hashfile_truncate(pack_file, checkpoint))
+ die_errno("cannot truncate pack to skip duplicate");
+ pack_size = checkpoint->offset;
+}
+
+static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
+{
+ size_t in_sz = 64 * 1024, out_sz = 64 * 1024;
+ unsigned char *in_buf = xmalloc(in_sz);
+ unsigned char *out_buf = xmalloc(out_sz);
+ struct object_entry *e;
+ struct object_id oid;
+ unsigned long hdrlen;
+ off_t offset;
+ git_hash_ctx c;
+ git_zstream s;
+ struct hashfile_checkpoint checkpoint;
+ int status = Z_OK;
+
+ /* Determine if we should auto-checkpoint. */
+ if ((max_packsize
+ && (pack_size + PACK_SIZE_THRESHOLD + len) > max_packsize)
+ || (pack_size + PACK_SIZE_THRESHOLD + len) < pack_size)
+ cycle_packfile();
+
+ hashfile_checkpoint(pack_file, &checkpoint);
+ offset = checkpoint.offset;
+
+ hdrlen = xsnprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1;
+
+ the_hash_algo->init_fn(&c);
+ the_hash_algo->update_fn(&c, out_buf, hdrlen);
+
+ crc32_begin(pack_file);
+
+ git_deflate_init(&s, pack_compression_level);
+
+ hdrlen = encode_in_pack_object_header(out_buf, out_sz, OBJ_BLOB, len);
+
+ s.next_out = out_buf + hdrlen;
+ s.avail_out = out_sz - hdrlen;
+
+ while (status != Z_STREAM_END) {
+ if (0 < len && !s.avail_in) {
+ size_t cnt = in_sz < len ? in_sz : (size_t)len;
+ size_t n = fread(in_buf, 1, cnt, stdin);
+ if (!n && feof(stdin))
+ die("EOF in data (%" PRIuMAX " bytes remaining)", len);
+
+ the_hash_algo->update_fn(&c, in_buf, n);
+ s.next_in = in_buf;
+ s.avail_in = n;
+ len -= n;
+ }
+
+ status = git_deflate(&s, len ? 0 : Z_FINISH);
+
+ if (!s.avail_out || status == Z_STREAM_END) {
+ size_t n = s.next_out - out_buf;
+ hashwrite(pack_file, out_buf, n);
+ pack_size += n;
+ s.next_out = out_buf;
+ s.avail_out = out_sz;
+ }
+
+ switch (status) {
+ case Z_OK:
+ case Z_BUF_ERROR:
+ case Z_STREAM_END:
+ continue;
+ default:
+ die("unexpected deflate failure: %d", status);
+ }
+ }
+ git_deflate_end(&s);
+ the_hash_algo->final_fn(oid.hash, &c);
+
+ if (oidout)
+ oidcpy(oidout, &oid);
+
+ e = insert_object(&oid);
+
+ if (mark)
+ insert_mark(&marks, mark, e);
+
+ if (e->idx.offset) {
+ duplicate_count_by_type[OBJ_BLOB]++;
+ truncate_pack(&checkpoint);
+
+ } else if (find_sha1_pack(oid.hash,
+ get_all_packs(the_repository))) {
+ e->type = OBJ_BLOB;
+ e->pack_id = MAX_PACK_ID;
+ e->idx.offset = 1; /* just not zero! */
+ duplicate_count_by_type[OBJ_BLOB]++;
+ truncate_pack(&checkpoint);
+
+ } else {
+ e->depth = 0;
+ e->type = OBJ_BLOB;
+ e->pack_id = pack_id;
+ e->idx.offset = offset;
+ e->idx.crc32 = crc32_end(pack_file);
+ object_count++;
+ object_count_by_type[OBJ_BLOB]++;
+ }
+
+ free(in_buf);
+ free(out_buf);
+}
+
+/* All calls must be guarded by find_object() or find_mark() to
+ * ensure the 'struct object_entry' passed was written by this
+ * process instance. We unpack the entry by the offset, avoiding
+ * the need for the corresponding .idx file. This unpacking rule
+ * works because we only use OBJ_REF_DELTA within the packfiles
+ * created by fast-import.
+ *
+ * oe must not be NULL. Such an oe usually comes from giving
+ * an unknown SHA-1 to find_object() or an undefined mark to
+ * find_mark(). Callers must test for this condition and use
+ * the standard read_sha1_file() when it happens.
+ *
+ * oe->pack_id must not be MAX_PACK_ID. Such an oe is usually from
+ * find_mark(), where the mark was reloaded from an existing marks
+ * file and is referencing an object that this fast-import process
+ * instance did not write out to a packfile. Callers must test for
+ * this condition and use read_sha1_file() instead.
+ */
+static void *gfi_unpack_entry(
+ struct object_entry *oe,
+ unsigned long *sizep)
+{
+ enum object_type type;
+ struct packed_git *p = all_packs[oe->pack_id];
+ if (p == pack_data && p->pack_size < (pack_size + the_hash_algo->rawsz)) {
+ /* The object is stored in the packfile we are writing to
+ * and we have modified it since the last time we scanned
+ * back to read a previously written object. If an old
+ * window covered [p->pack_size, p->pack_size + rawsz) its
+ * data is stale and is not valid. Closing all windows
+ * and updating the packfile length ensures we can read
+ * the newly written data.
+ */
+ close_pack_windows(p);
+ hashflush(pack_file);
+
+ /* We have to offer rawsz bytes additional on the end of
+ * the packfile as the core unpacker code assumes the
+ * footer is present at the file end and must promise
+ * at least rawsz bytes within any window it maps. But
+ * we don't actually create the footer here.
+ */
+ p->pack_size = pack_size + the_hash_algo->rawsz;
+ }
+ return unpack_entry(the_repository, p, oe->idx.offset, &type, sizep);
+}
+
+static const char *get_mode(const char *str, uint16_t *modep)
+{
+ unsigned char c;
+ uint16_t mode = 0;
+
+ while ((c = *str++) != ' ') {
+ if (c < '0' || c > '7')
+ return NULL;
+ mode = (mode << 3) + (c - '0');
+ }
+ *modep = mode;
+ return str;
+}
+
+static void load_tree(struct tree_entry *root)
+{
+ struct object_id *oid = &root->versions[1].oid;
+ struct object_entry *myoe;
+ struct tree_content *t;
+ unsigned long size;
+ char *buf;
+ const char *c;
+
+ root->tree = t = new_tree_content(8);
+ if (is_null_oid(oid))
+ return;
+
+ myoe = find_object(oid);
+ if (myoe && myoe->pack_id != MAX_PACK_ID) {
+ if (myoe->type != OBJ_TREE)
+ die("Not a tree: %s", oid_to_hex(oid));
+ t->delta_depth = myoe->depth;
+ buf = gfi_unpack_entry(myoe, &size);
+ if (!buf)
+ die("Can't load tree %s", oid_to_hex(oid));
+ } else {
+ enum object_type type;
+ buf = read_object_file(oid, &type, &size);
+ if (!buf || type != OBJ_TREE)
+ die("Can't load tree %s", oid_to_hex(oid));
+ }
+
+ c = buf;
+ while (c != (buf + size)) {
+ struct tree_entry *e = new_tree_entry();
+
+ if (t->entry_count == t->entry_capacity)
+ root->tree = t = grow_tree_content(t, t->entry_count);
+ t->entries[t->entry_count++] = e;
+
+ e->tree = NULL;
+ c = get_mode(c, &e->versions[1].mode);
+ if (!c)
+ die("Corrupt mode in %s", oid_to_hex(oid));
+ e->versions[0].mode = e->versions[1].mode;
+ e->name = to_atom(c, strlen(c));
+ c += e->name->str_len + 1;
+ hashcpy(e->versions[0].oid.hash, (unsigned char *)c);
+ hashcpy(e->versions[1].oid.hash, (unsigned char *)c);
+ c += the_hash_algo->rawsz;
+ }
+ free(buf);
+}
+
+static int tecmp0 (const void *_a, const void *_b)
+{
+ struct tree_entry *a = *((struct tree_entry**)_a);
+ struct tree_entry *b = *((struct tree_entry**)_b);
+ return base_name_compare(
+ a->name->str_dat, a->name->str_len, a->versions[0].mode,
+ b->name->str_dat, b->name->str_len, b->versions[0].mode);
+}
+
+static int tecmp1 (const void *_a, const void *_b)
+{
+ struct tree_entry *a = *((struct tree_entry**)_a);
+ struct tree_entry *b = *((struct tree_entry**)_b);
+ return base_name_compare(
+ a->name->str_dat, a->name->str_len, a->versions[1].mode,
+ b->name->str_dat, b->name->str_len, b->versions[1].mode);
+}
+
+static void mktree(struct tree_content *t, int v, struct strbuf *b)
+{
+ size_t maxlen = 0;
+ unsigned int i;
+
+ if (!v)
+ QSORT(t->entries, t->entry_count, tecmp0);
+ else
+ QSORT(t->entries, t->entry_count, tecmp1);
+
+ for (i = 0; i < t->entry_count; i++) {
+ if (t->entries[i]->versions[v].mode)
+ maxlen += t->entries[i]->name->str_len + 34;
+ }
+
+ strbuf_reset(b);
+ strbuf_grow(b, maxlen);
+ for (i = 0; i < t->entry_count; i++) {
+ struct tree_entry *e = t->entries[i];
+ if (!e->versions[v].mode)
+ continue;
+ strbuf_addf(b, "%o %s%c",
+ (unsigned int)(e->versions[v].mode & ~NO_DELTA),
+ e->name->str_dat, '\0');
+ strbuf_add(b, e->versions[v].oid.hash, the_hash_algo->rawsz);
+ }
+}
+
+static void store_tree(struct tree_entry *root)
+{
+ struct tree_content *t;
+ unsigned int i, j, del;
+ struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 };
+ struct object_entry *le = NULL;
+
+ if (!is_null_oid(&root->versions[1].oid))
+ return;
+
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
+
+ for (i = 0; i < t->entry_count; i++) {
+ if (t->entries[i]->tree)
+ store_tree(t->entries[i]);
+ }
+
+ if (!(root->versions[0].mode & NO_DELTA))
+ le = find_object(&root->versions[0].oid);
+ if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) {
+ mktree(t, 0, &old_tree);
+ lo.data = old_tree;
+ lo.offset = le->idx.offset;
+ lo.depth = t->delta_depth;
+ }
+
+ mktree(t, 1, &new_tree);
+ store_object(OBJ_TREE, &new_tree, &lo, &root->versions[1].oid, 0);
+
+ t->delta_depth = lo.depth;
+ for (i = 0, j = 0, del = 0; i < t->entry_count; i++) {
+ struct tree_entry *e = t->entries[i];
+ if (e->versions[1].mode) {
+ e->versions[0].mode = e->versions[1].mode;
+ oidcpy(&e->versions[0].oid, &e->versions[1].oid);
+ t->entries[j++] = e;
+ } else {
+ release_tree_entry(e);
+ del++;
+ }
+ }
+ t->entry_count -= del;
+}
+
+static void tree_content_replace(
+ struct tree_entry *root,
+ const struct object_id *oid,
+ const uint16_t mode,
+ struct tree_content *newtree)
+{
+ if (!S_ISDIR(mode))
+ die("Root cannot be a non-directory");
+ oidclr(&root->versions[0].oid);
+ oidcpy(&root->versions[1].oid, oid);
+ if (root->tree)
+ release_tree_content_recursive(root->tree);
+ root->tree = newtree;
+}
+
+static int tree_content_set(
+ struct tree_entry *root,
+ const char *p,
+ const struct object_id *oid,
+ const uint16_t mode,
+ struct tree_content *subtree)
+{
+ struct tree_content *t;
+ const char *slash1;
+ unsigned int i, n;
+ struct tree_entry *e;
+
+ slash1 = strchrnul(p, '/');
+ n = slash1 - p;
+ if (!n)
+ die("Empty path component found in input");
+ if (!*slash1 && !S_ISDIR(mode) && subtree)
+ die("Non-directories cannot have subtrees");
+
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
+ for (i = 0; i < t->entry_count; i++) {
+ e = t->entries[i];
+ if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
+ if (!*slash1) {
+ if (!S_ISDIR(mode)
+ && e->versions[1].mode == mode
+ && oideq(&e->versions[1].oid, oid))
+ return 0;
+ e->versions[1].mode = mode;
+ oidcpy(&e->versions[1].oid, oid);
+ if (e->tree)
+ release_tree_content_recursive(e->tree);
+ e->tree = subtree;
+
+ /*
+ * We need to leave e->versions[0].sha1 alone
+ * to avoid modifying the preimage tree used
+ * when writing out the parent directory.
+ * But after replacing the subdir with a
+ * completely different one, it's not a good
+ * delta base any more, and besides, we've
+ * thrown away the tree entries needed to
+ * make a delta against it.
+ *
+ * So let's just explicitly disable deltas
+ * for the subtree.
+ */
+ if (S_ISDIR(e->versions[0].mode))
+ e->versions[0].mode |= NO_DELTA;
+
+ oidclr(&root->versions[1].oid);
+ return 1;
+ }
+ if (!S_ISDIR(e->versions[1].mode)) {
+ e->tree = new_tree_content(8);
+ e->versions[1].mode = S_IFDIR;
+ }
+ if (!e->tree)
+ load_tree(e);
+ if (tree_content_set(e, slash1 + 1, oid, mode, subtree)) {
+ oidclr(&root->versions[1].oid);
+ return 1;
+ }
+ return 0;
+ }
+ }
+
+ if (t->entry_count == t->entry_capacity)
+ root->tree = t = grow_tree_content(t, t->entry_count);
+ e = new_tree_entry();
+ e->name = to_atom(p, n);
+ e->versions[0].mode = 0;
+ oidclr(&e->versions[0].oid);
+ t->entries[t->entry_count++] = e;
+ if (*slash1) {
+ e->tree = new_tree_content(8);
+ e->versions[1].mode = S_IFDIR;
+ tree_content_set(e, slash1 + 1, oid, mode, subtree);
+ } else {
+ e->tree = subtree;
+ e->versions[1].mode = mode;
+ oidcpy(&e->versions[1].oid, oid);
+ }
+ oidclr(&root->versions[1].oid);
+ return 1;
+}
+
+static int tree_content_remove(
+ struct tree_entry *root,
+ const char *p,
+ struct tree_entry *backup_leaf,
+ int allow_root)
+{
+ struct tree_content *t;
+ const char *slash1;
+ unsigned int i, n;
+ struct tree_entry *e;
+
+ slash1 = strchrnul(p, '/');
+ n = slash1 - p;
+
+ if (!root->tree)
+ load_tree(root);
+
+ if (!*p && allow_root) {
+ e = root;
+ goto del_entry;
+ }
+
+ t = root->tree;
+ for (i = 0; i < t->entry_count; i++) {
+ e = t->entries[i];
+ if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
+ if (*slash1 && !S_ISDIR(e->versions[1].mode))
+ /*
+ * If p names a file in some subdirectory, and a
+ * file or symlink matching the name of the
+ * parent directory of p exists, then p cannot
+ * exist and need not be deleted.
+ */
+ return 1;
+ if (!*slash1 || !S_ISDIR(e->versions[1].mode))
+ goto del_entry;
+ if (!e->tree)
+ load_tree(e);
+ if (tree_content_remove(e, slash1 + 1, backup_leaf, 0)) {
+ for (n = 0; n < e->tree->entry_count; n++) {
+ if (e->tree->entries[n]->versions[1].mode) {
+ oidclr(&root->versions[1].oid);
+ return 1;
+ }
+ }
+ backup_leaf = NULL;
+ goto del_entry;
+ }
+ return 0;
+ }
+ }
+ return 0;
+
+del_entry:
+ if (backup_leaf)
+ memcpy(backup_leaf, e, sizeof(*backup_leaf));
+ else if (e->tree)
+ release_tree_content_recursive(e->tree);
+ e->tree = NULL;
+ e->versions[1].mode = 0;
+ oidclr(&e->versions[1].oid);
+ oidclr(&root->versions[1].oid);
+ return 1;
+}
+
+static int tree_content_get(
+ struct tree_entry *root,
+ const char *p,
+ struct tree_entry *leaf,
+ int allow_root)
+{
+ struct tree_content *t;
+ const char *slash1;
+ unsigned int i, n;
+ struct tree_entry *e;
+
+ slash1 = strchrnul(p, '/');
+ n = slash1 - p;
+ if (!n && !allow_root)
+ die("Empty path component found in input");
+
+ if (!root->tree)
+ load_tree(root);
+
+ if (!n) {
+ e = root;
+ goto found_entry;
+ }
+
+ t = root->tree;
+ for (i = 0; i < t->entry_count; i++) {
+ e = t->entries[i];
+ if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
+ if (!*slash1)
+ goto found_entry;
+ if (!S_ISDIR(e->versions[1].mode))
+ return 0;
+ if (!e->tree)
+ load_tree(e);
+ return tree_content_get(e, slash1 + 1, leaf, 0);
+ }
+ }
+ return 0;
+
+found_entry:
+ memcpy(leaf, e, sizeof(*leaf));
+ if (e->tree && is_null_oid(&e->versions[1].oid))
+ leaf->tree = dup_tree_content(e->tree);
+ else
+ leaf->tree = NULL;
+ return 1;
+}
+
+static int update_branch(struct branch *b)
+{
+ static const char *msg = "fast-import";
+ struct ref_transaction *transaction;
+ struct object_id old_oid;
+ struct strbuf err = STRBUF_INIT;
+
+ if (is_null_oid(&b->oid)) {
+ if (b->delete)
+ delete_ref(NULL, b->name, NULL, 0);
+ return 0;
+ }
+ if (read_ref(b->name, &old_oid))
+ oidclr(&old_oid);
+ if (!force_update && !is_null_oid(&old_oid)) {
+ struct commit *old_cmit, *new_cmit;
+
+ old_cmit = lookup_commit_reference_gently(the_repository,
+ &old_oid, 0);
+ new_cmit = lookup_commit_reference_gently(the_repository,
+ &b->oid, 0);
+ if (!old_cmit || !new_cmit)
+ return error("Branch %s is missing commits.", b->name);
+
+ if (!in_merge_bases(old_cmit, new_cmit)) {
+ warning("Not updating %s"
+ " (new tip %s does not contain %s)",
+ b->name, oid_to_hex(&b->oid),
+ oid_to_hex(&old_oid));
+ return -1;
+ }
+ }
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
+ 0, msg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ return -1;
+ }
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ return 0;
+}
+
+static void dump_branches(void)
+{
+ unsigned int i;
+ struct branch *b;
+
+ for (i = 0; i < branch_table_sz; i++) {
+ for (b = branch_table[i]; b; b = b->table_next_branch)
+ failure |= update_branch(b);
+ }
+}
+
+static void dump_tags(void)
+{
+ static const char *msg = "fast-import";
+ struct tag *t;
+ struct strbuf ref_name = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ failure |= error("%s", err.buf);
+ goto cleanup;
+ }
+ for (t = first_tag; t; t = t->next_tag) {
+ strbuf_reset(&ref_name);
+ strbuf_addf(&ref_name, "refs/tags/%s", t->name);
+
+ if (ref_transaction_update(transaction, ref_name.buf,
+ &t->oid, NULL, 0, msg, &err)) {
+ failure |= error("%s", err.buf);
+ goto cleanup;
+ }
+ }
+ if (ref_transaction_commit(transaction, &err))
+ failure |= error("%s", err.buf);
+
+ cleanup:
+ ref_transaction_free(transaction);
+ strbuf_release(&ref_name);
+ strbuf_release(&err);
+}
+
+static void dump_marks(void)
+{
+ struct lock_file mark_lock = LOCK_INIT;
+ FILE *f;
+
+ if (!export_marks_file || (import_marks_file && !import_marks_file_done))
+ return;
+
+ if (safe_create_leading_directories_const(export_marks_file)) {
+ failure |= error_errno("unable to create leading directories of %s",
+ export_marks_file);
+ return;
+ }
+
+ if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) {
+ failure |= error_errno("Unable to write marks file %s",
+ export_marks_file);
+ return;
+ }
+
+ f = fdopen_lock_file(&mark_lock, "w");
+ if (!f) {
+ int saved_errno = errno;
+ rollback_lock_file(&mark_lock);
+ failure |= error("Unable to write marks file %s: %s",
+ export_marks_file, strerror(saved_errno));
+ return;
+ }
+
+ for_each_mark(marks, 0, dump_marks_fn, f);
+ if (commit_lock_file(&mark_lock)) {
+ failure |= error_errno("Unable to write file %s",
+ export_marks_file);
+ return;
+ }
+}
+
+static void insert_object_entry(struct mark_set **s, struct object_id *oid, uintmax_t mark)
+{
+ struct object_entry *e;
+ e = find_object(oid);
+ if (!e) {
+ enum object_type type = oid_object_info(the_repository,
+ oid, NULL);
+ if (type < 0)
+ die("object not found: %s", oid_to_hex(oid));
+ e = insert_object(oid);
+ e->type = type;
+ e->pack_id = MAX_PACK_ID;
+ e->idx.offset = 1; /* just not zero! */
+ }
+ insert_mark(s, mark, e);
+}
+
+static void insert_oid_entry(struct mark_set **s, struct object_id *oid, uintmax_t mark)
+{
+ insert_mark(s, mark, xmemdupz(oid, sizeof(*oid)));
+}
+
+static void read_mark_file(struct mark_set **s, FILE *f, mark_set_inserter_t inserter)
+{
+ char line[512];
+ while (fgets(line, sizeof(line), f)) {
+ uintmax_t mark;
+ char *end;
+ struct object_id oid;
+
+ /* Ensure SHA-1 objects are padded with zeros. */
+ memset(oid.hash, 0, sizeof(oid.hash));
+
+ end = strchr(line, '\n');
+ if (line[0] != ':' || !end)
+ die("corrupt mark line: %s", line);
+ *end = 0;
+ mark = strtoumax(line + 1, &end, 10);
+ if (!mark || end == line + 1
+ || *end != ' '
+ || get_oid_hex_any(end + 1, &oid) == GIT_HASH_UNKNOWN)
+ die("corrupt mark line: %s", line);
+ inserter(s, &oid, mark);
+ }
+}
+
+static void read_marks(void)
+{
+ FILE *f = fopen(import_marks_file, "r");
+ if (f)
+ ;
+ else if (import_marks_file_ignore_missing && errno == ENOENT)
+ goto done; /* Marks file does not exist */
+ else
+ die_errno("cannot read '%s'", import_marks_file);
+ read_mark_file(&marks, f, insert_object_entry);
+ fclose(f);
+done:
+ import_marks_file_done = 1;
+}
+
+
+static int read_next_command(void)
+{
+ static int stdin_eof = 0;
+
+ if (stdin_eof) {
+ unread_command_buf = 0;
+ return EOF;
+ }
+
+ for (;;) {
+ if (unread_command_buf) {
+ unread_command_buf = 0;
+ } else {
+ struct recent_command *rc;
+
+ stdin_eof = strbuf_getline_lf(&command_buf, stdin);
+ if (stdin_eof)
+ return EOF;
+
+ if (!seen_data_command
+ && !starts_with(command_buf.buf, "feature ")
+ && !starts_with(command_buf.buf, "option ")) {
+ parse_argv();
+ }
+
+ rc = rc_free;
+ if (rc)
+ rc_free = rc->next;
+ else {
+ rc = cmd_hist.next;
+ cmd_hist.next = rc->next;
+ cmd_hist.next->prev = &cmd_hist;
+ free(rc->buf);
+ }
+
+ rc->buf = xstrdup(command_buf.buf);
+ rc->prev = cmd_tail;
+ rc->next = cmd_hist.prev;
+ rc->prev->next = rc;
+ cmd_tail = rc;
+ }
+ if (command_buf.buf[0] == '#')
+ continue;
+ return 0;
+ }
+}
+
+static void skip_optional_lf(void)
+{
+ int term_char = fgetc(stdin);
+ if (term_char != '\n' && term_char != EOF)
+ ungetc(term_char, stdin);
+}
+
+static void parse_mark(void)
+{
+ const char *v;
+ if (skip_prefix(command_buf.buf, "mark :", &v)) {
+ next_mark = strtoumax(v, NULL, 10);
+ read_next_command();
+ }
+ else
+ next_mark = 0;
+}
+
+static void parse_original_identifier(void)
+{
+ const char *v;
+ if (skip_prefix(command_buf.buf, "original-oid ", &v))
+ read_next_command();
+}
+
+static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res)
+{
+ const char *data;
+ strbuf_reset(sb);
+
+ if (!skip_prefix(command_buf.buf, "data ", &data))
+ die("Expected 'data n' command, found: %s", command_buf.buf);
+
+ if (skip_prefix(data, "<<", &data)) {
+ char *term = xstrdup(data);
+ size_t term_len = command_buf.len - (data - command_buf.buf);
+
+ for (;;) {
+ if (strbuf_getline_lf(&command_buf, stdin) == EOF)
+ die("EOF in data (terminator '%s' not found)", term);
+ if (term_len == command_buf.len
+ && !strcmp(term, command_buf.buf))
+ break;
+ strbuf_addbuf(sb, &command_buf);
+ strbuf_addch(sb, '\n');
+ }
+ free(term);
+ }
+ else {
+ uintmax_t len = strtoumax(data, NULL, 10);
+ size_t n = 0, length = (size_t)len;
+
+ if (limit && limit < len) {
+ *len_res = len;
+ return 0;
+ }
+ if (length < len)
+ die("data is too large to use in this context");
+
+ while (n < length) {
+ size_t s = strbuf_fread(sb, length - n, stdin);
+ if (!s && feof(stdin))
+ die("EOF in data (%lu bytes remaining)",
+ (unsigned long)(length - n));
+ n += s;
+ }
+ }
+
+ skip_optional_lf();
+ return 1;
+}
+
+static int validate_raw_date(const char *src, struct strbuf *result, int strict)
+{
+ const char *orig_src = src;
+ char *endp;
+ unsigned long num;
+
+ errno = 0;
+
+ num = strtoul(src, &endp, 10);
+ /*
+ * NEEDSWORK: perhaps check for reasonable values? For example, we
+ * could error on values representing times more than a
+ * day in the future.
+ */
+ if (errno || endp == src || *endp != ' ')
+ return -1;
+
+ src = endp + 1;
+ if (*src != '-' && *src != '+')
+ return -1;
+
+ num = strtoul(src + 1, &endp, 10);
+ /*
+ * NEEDSWORK: check for brokenness other than num > 1400, such as
+ * (num % 100) >= 60, or ((num % 100) % 15) != 0 ?
+ */
+ if (errno || endp == src + 1 || *endp || /* did not parse */
+ (strict && (1400 < num)) /* parsed a broken timezone */
+ )
+ return -1;
+
+ strbuf_addstr(result, orig_src);
+ return 0;
+}
+
+static char *parse_ident(const char *buf)
+{
+ const char *ltgt;
+ size_t name_len;
+ struct strbuf ident = STRBUF_INIT;
+
+ /* ensure there is a space delimiter even if there is no name */
+ if (*buf == '<')
+ --buf;
+
+ ltgt = buf + strcspn(buf, "<>");
+ if (*ltgt != '<')
+ die("Missing < in ident string: %s", buf);
+ if (ltgt != buf && ltgt[-1] != ' ')
+ die("Missing space before < in ident string: %s", buf);
+ ltgt = ltgt + 1 + strcspn(ltgt + 1, "<>");
+ if (*ltgt != '>')
+ die("Missing > in ident string: %s", buf);
+ ltgt++;
+ if (*ltgt != ' ')
+ die("Missing space after > in ident string: %s", buf);
+ ltgt++;
+ name_len = ltgt - buf;
+ strbuf_add(&ident, buf, name_len);
+
+ switch (whenspec) {
+ case WHENSPEC_RAW:
+ if (validate_raw_date(ltgt, &ident, 1) < 0)
+ die("Invalid raw date \"%s\" in ident: %s", ltgt, buf);
+ break;
+ case WHENSPEC_RAW_PERMISSIVE:
+ if (validate_raw_date(ltgt, &ident, 0) < 0)
+ die("Invalid raw date \"%s\" in ident: %s", ltgt, buf);
+ break;
+ case WHENSPEC_RFC2822:
+ if (parse_date(ltgt, &ident) < 0)
+ die("Invalid rfc2822 date \"%s\" in ident: %s", ltgt, buf);
+ break;
+ case WHENSPEC_NOW:
+ if (strcmp("now", ltgt))
+ die("Date in ident must be 'now': %s", buf);
+ datestamp(&ident);
+ break;
+ }
+
+ return strbuf_detach(&ident, NULL);
+}
+
+static void parse_and_store_blob(
+ struct last_object *last,
+ struct object_id *oidout,
+ uintmax_t mark)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ uintmax_t len;
+
+ if (parse_data(&buf, big_file_threshold, &len))
+ store_object(OBJ_BLOB, &buf, last, oidout, mark);
+ else {
+ if (last) {
+ strbuf_release(&last->data);
+ last->offset = 0;
+ last->depth = 0;
+ }
+ stream_blob(len, oidout, mark);
+ skip_optional_lf();
+ }
+}
+
+static void parse_new_blob(void)
+{
+ read_next_command();
+ parse_mark();
+ parse_original_identifier();
+ parse_and_store_blob(&last_blob, NULL, next_mark);
+}
+
+static void unload_one_branch(void)
+{
+ while (cur_active_branches
+ && cur_active_branches >= max_active_branches) {
+ uintmax_t min_commit = ULONG_MAX;
+ struct branch *e, *l = NULL, *p = NULL;
+
+ for (e = active_branches; e; e = e->active_next_branch) {
+ if (e->last_commit < min_commit) {
+ p = l;
+ min_commit = e->last_commit;
+ }
+ l = e;
+ }
+
+ if (p) {
+ e = p->active_next_branch;
+ p->active_next_branch = e->active_next_branch;
+ } else {
+ e = active_branches;
+ active_branches = e->active_next_branch;
+ }
+ e->active = 0;
+ e->active_next_branch = NULL;
+ if (e->branch_tree.tree) {
+ release_tree_content_recursive(e->branch_tree.tree);
+ e->branch_tree.tree = NULL;
+ }
+ cur_active_branches--;
+ }
+}
+
+static void load_branch(struct branch *b)
+{
+ load_tree(&b->branch_tree);
+ if (!b->active) {
+ b->active = 1;
+ b->active_next_branch = active_branches;
+ active_branches = b;
+ cur_active_branches++;
+ branch_load_count++;
+ }
+}
+
+static unsigned char convert_num_notes_to_fanout(uintmax_t num_notes)
+{
+ unsigned char fanout = 0;
+ while ((num_notes >>= 8))
+ fanout++;
+ return fanout;
+}
+
+static void construct_path_with_fanout(const char *hex_sha1,
+ unsigned char fanout, char *path)
+{
+ unsigned int i = 0, j = 0;
+ if (fanout >= the_hash_algo->rawsz)
+ die("Too large fanout (%u)", fanout);
+ while (fanout) {
+ path[i++] = hex_sha1[j++];
+ path[i++] = hex_sha1[j++];
+ path[i++] = '/';
+ fanout--;
+ }
+ memcpy(path + i, hex_sha1 + j, the_hash_algo->hexsz - j);
+ path[i + the_hash_algo->hexsz - j] = '\0';
+}
+
+static uintmax_t do_change_note_fanout(
+ struct tree_entry *orig_root, struct tree_entry *root,
+ char *hex_oid, unsigned int hex_oid_len,
+ char *fullpath, unsigned int fullpath_len,
+ unsigned char fanout)
+{
+ struct tree_content *t;
+ struct tree_entry *e, leaf;
+ unsigned int i, tmp_hex_oid_len, tmp_fullpath_len;
+ uintmax_t num_notes = 0;
+ struct object_id oid;
+ /* hex oid + '/' between each pair of hex digits + NUL */
+ char realpath[GIT_MAX_HEXSZ + ((GIT_MAX_HEXSZ / 2) - 1) + 1];
+ const unsigned hexsz = the_hash_algo->hexsz;
+
+ if (!root->tree)
+ load_tree(root);
+ t = root->tree;
+
+ for (i = 0; t && i < t->entry_count; i++) {
+ e = t->entries[i];
+ tmp_hex_oid_len = hex_oid_len + e->name->str_len;
+ tmp_fullpath_len = fullpath_len;
+
+ /*
+ * We're interested in EITHER existing note entries (entries
+ * with exactly 40 hex chars in path, not including directory
+ * separators), OR directory entries that may contain note
+ * entries (with < 40 hex chars in path).
+ * Also, each path component in a note entry must be a multiple
+ * of 2 chars.
+ */
+ if (!e->versions[1].mode ||
+ tmp_hex_oid_len > hexsz ||
+ e->name->str_len % 2)
+ continue;
+
+ /* This _may_ be a note entry, or a subdir containing notes */
+ memcpy(hex_oid + hex_oid_len, e->name->str_dat,
+ e->name->str_len);
+ if (tmp_fullpath_len)
+ fullpath[tmp_fullpath_len++] = '/';
+ memcpy(fullpath + tmp_fullpath_len, e->name->str_dat,
+ e->name->str_len);
+ tmp_fullpath_len += e->name->str_len;
+ fullpath[tmp_fullpath_len] = '\0';
+
+ if (tmp_hex_oid_len == hexsz && !get_oid_hex(hex_oid, &oid)) {
+ /* This is a note entry */
+ if (fanout == 0xff) {
+ /* Counting mode, no rename */
+ num_notes++;
+ continue;
+ }
+ construct_path_with_fanout(hex_oid, fanout, realpath);
+ if (!strcmp(fullpath, realpath)) {
+ /* Note entry is in correct location */
+ num_notes++;
+ continue;
+ }
+
+ /* Rename fullpath to realpath */
+ if (!tree_content_remove(orig_root, fullpath, &leaf, 0))
+ die("Failed to remove path %s", fullpath);
+ tree_content_set(orig_root, realpath,
+ &leaf.versions[1].oid,
+ leaf.versions[1].mode,
+ leaf.tree);
+ } else if (S_ISDIR(e->versions[1].mode)) {
+ /* This is a subdir that may contain note entries */
+ num_notes += do_change_note_fanout(orig_root, e,
+ hex_oid, tmp_hex_oid_len,
+ fullpath, tmp_fullpath_len, fanout);
+ }
+
+ /* The above may have reallocated the current tree_content */
+ t = root->tree;
+ }
+ return num_notes;
+}
+
+static uintmax_t change_note_fanout(struct tree_entry *root,
+ unsigned char fanout)
+{
+ /*
+ * The size of path is due to one slash between every two hex digits,
+ * plus the terminating NUL. Note that there is no slash at the end, so
+ * the number of slashes is one less than half the number of hex
+ * characters.
+ */
+ char hex_oid[GIT_MAX_HEXSZ], path[GIT_MAX_HEXSZ + (GIT_MAX_HEXSZ / 2) - 1 + 1];
+ return do_change_note_fanout(root, root, hex_oid, 0, path, 0, fanout);
+}
+
+static int parse_mapped_oid_hex(const char *hex, struct object_id *oid, const char **end)
+{
+ int algo;
+ khiter_t it;
+
+ /* Make SHA-1 object IDs have all-zero padding. */
+ memset(oid->hash, 0, sizeof(oid->hash));
+
+ algo = parse_oid_hex_any(hex, oid, end);
+ if (algo == GIT_HASH_UNKNOWN)
+ return -1;
+
+ it = kh_get_oid_map(sub_oid_map, *oid);
+ /* No such object? */
+ if (it == kh_end(sub_oid_map)) {
+ /* If we're using the same algorithm, pass it through. */
+ if (hash_algos[algo].format_id == the_hash_algo->format_id)
+ return 0;
+ return -1;
+ }
+ oidcpy(oid, kh_value(sub_oid_map, it));
+ return 0;
+}
+
+/*
+ * Given a pointer into a string, parse a mark reference:
+ *
+ * idnum ::= ':' bigint;
+ *
+ * Return the first character after the value in *endptr.
+ *
+ * Complain if the following character is not what is expected,
+ * either a space or end of the string.
+ */
+static uintmax_t parse_mark_ref(const char *p, char **endptr)
+{
+ uintmax_t mark;
+
+ assert(*p == ':');
+ p++;
+ mark = strtoumax(p, endptr, 10);
+ if (*endptr == p)
+ die("No value after ':' in mark: %s", command_buf.buf);
+ return mark;
+}
+
+/*
+ * Parse the mark reference, and complain if this is not the end of
+ * the string.
+ */
+static uintmax_t parse_mark_ref_eol(const char *p)
+{
+ char *end;
+ uintmax_t mark;
+
+ mark = parse_mark_ref(p, &end);
+ if (*end != '\0')
+ die("Garbage after mark: %s", command_buf.buf);
+ return mark;
+}
+
+/*
+ * Parse the mark reference, demanding a trailing space. Return a
+ * pointer to the space.
+ */
+static uintmax_t parse_mark_ref_space(const char **p)
+{
+ uintmax_t mark;
+ char *end;
+
+ mark = parse_mark_ref(*p, &end);
+ if (*end++ != ' ')
+ die("Missing space after mark: %s", command_buf.buf);
+ *p = end;
+ return mark;
+}
+
+static void file_change_m(const char *p, struct branch *b)
+{
+ static struct strbuf uq = STRBUF_INIT;
+ const char *endp;
+ struct object_entry *oe;
+ struct object_id oid;
+ uint16_t mode, inline_data = 0;
+
+ p = get_mode(p, &mode);
+ if (!p)
+ die("Corrupt mode: %s", command_buf.buf);
+ switch (mode) {
+ case 0644:
+ case 0755:
+ mode |= S_IFREG;
+ case S_IFREG | 0644:
+ case S_IFREG | 0755:
+ case S_IFLNK:
+ case S_IFDIR:
+ case S_IFGITLINK:
+ /* ok */
+ break;
+ default:
+ die("Corrupt mode: %s", command_buf.buf);
+ }
+
+ if (*p == ':') {
+ oe = find_mark(marks, parse_mark_ref_space(&p));
+ oidcpy(&oid, &oe->idx.oid);
+ } else if (skip_prefix(p, "inline ", &p)) {
+ inline_data = 1;
+ oe = NULL; /* not used with inline_data, but makes gcc happy */
+ } else {
+ if (parse_mapped_oid_hex(p, &oid, &p))
+ die("Invalid dataref: %s", command_buf.buf);
+ oe = find_object(&oid);
+ if (*p++ != ' ')
+ die("Missing space after SHA1: %s", command_buf.buf);
+ }
+
+ strbuf_reset(&uq);
+ if (!unquote_c_style(&uq, p, &endp)) {
+ if (*endp)
+ die("Garbage after path in: %s", command_buf.buf);
+ p = uq.buf;
+ }
+
+ /* Git does not track empty, non-toplevel directories. */
+ if (S_ISDIR(mode) && is_empty_tree_oid(&oid) && *p) {
+ tree_content_remove(&b->branch_tree, p, NULL, 0);
+ return;
+ }
+
+ if (S_ISGITLINK(mode)) {
+ if (inline_data)
+ die("Git links cannot be specified 'inline': %s",
+ command_buf.buf);
+ else if (oe) {
+ if (oe->type != OBJ_COMMIT)
+ die("Not a commit (actually a %s): %s",
+ type_name(oe->type), command_buf.buf);
+ }
+ /*
+ * Accept the sha1 without checking; it expected to be in
+ * another repository.
+ */
+ } else if (inline_data) {
+ if (S_ISDIR(mode))
+ die("Directories cannot be specified 'inline': %s",
+ command_buf.buf);
+ if (p != uq.buf) {
+ strbuf_addstr(&uq, p);
+ p = uq.buf;
+ }
+ while (read_next_command() != EOF) {
+ const char *v;
+ if (skip_prefix(command_buf.buf, "cat-blob ", &v))
+ parse_cat_blob(v);
+ else {
+ parse_and_store_blob(&last_blob, &oid, 0);
+ break;
+ }
+ }
+ } else {
+ enum object_type expected = S_ISDIR(mode) ?
+ OBJ_TREE: OBJ_BLOB;
+ enum object_type type = oe ? oe->type :
+ oid_object_info(the_repository, &oid,
+ NULL);
+ if (type < 0)
+ die("%s not found: %s",
+ S_ISDIR(mode) ? "Tree" : "Blob",
+ command_buf.buf);
+ if (type != expected)
+ die("Not a %s (actually a %s): %s",
+ type_name(expected), type_name(type),
+ command_buf.buf);
+ }
+
+ if (!*p) {
+ tree_content_replace(&b->branch_tree, &oid, mode, NULL);
+ return;
+ }
+ tree_content_set(&b->branch_tree, p, &oid, mode, NULL);
+}
+
+static void file_change_d(const char *p, struct branch *b)
+{
+ static struct strbuf uq = STRBUF_INIT;
+ const char *endp;
+
+ strbuf_reset(&uq);
+ if (!unquote_c_style(&uq, p, &endp)) {
+ if (*endp)
+ die("Garbage after path in: %s", command_buf.buf);
+ p = uq.buf;
+ }
+ tree_content_remove(&b->branch_tree, p, NULL, 1);
+}
+
+static void file_change_cr(const char *s, struct branch *b, int rename)
+{
+ const char *d;
+ static struct strbuf s_uq = STRBUF_INIT;
+ static struct strbuf d_uq = STRBUF_INIT;
+ const char *endp;
+ struct tree_entry leaf;
+
+ strbuf_reset(&s_uq);
+ if (!unquote_c_style(&s_uq, s, &endp)) {
+ if (*endp != ' ')
+ die("Missing space after source: %s", command_buf.buf);
+ } else {
+ endp = strchr(s, ' ');
+ if (!endp)
+ die("Missing space after source: %s", command_buf.buf);
+ strbuf_add(&s_uq, s, endp - s);
+ }
+ s = s_uq.buf;
+
+ endp++;
+ if (!*endp)
+ die("Missing dest: %s", command_buf.buf);
+
+ d = endp;
+ strbuf_reset(&d_uq);
+ if (!unquote_c_style(&d_uq, d, &endp)) {
+ if (*endp)
+ die("Garbage after dest in: %s", command_buf.buf);
+ d = d_uq.buf;
+ }
+
+ memset(&leaf, 0, sizeof(leaf));
+ if (rename)
+ tree_content_remove(&b->branch_tree, s, &leaf, 1);
+ else
+ tree_content_get(&b->branch_tree, s, &leaf, 1);
+ if (!leaf.versions[1].mode)
+ die("Path %s not in branch", s);
+ if (!*d) { /* C "path/to/subdir" "" */
+ tree_content_replace(&b->branch_tree,
+ &leaf.versions[1].oid,
+ leaf.versions[1].mode,
+ leaf.tree);
+ return;
+ }
+ tree_content_set(&b->branch_tree, d,
+ &leaf.versions[1].oid,
+ leaf.versions[1].mode,
+ leaf.tree);
+}
+
+static void note_change_n(const char *p, struct branch *b, unsigned char *old_fanout)
+{
+ static struct strbuf uq = STRBUF_INIT;
+ struct object_entry *oe;
+ struct branch *s;
+ struct object_id oid, commit_oid;
+ char path[GIT_MAX_RAWSZ * 3];
+ uint16_t inline_data = 0;
+ unsigned char new_fanout;
+
+ /*
+ * When loading a branch, we don't traverse its tree to count the real
+ * number of notes (too expensive to do this for all non-note refs).
+ * This means that recently loaded notes refs might incorrectly have
+ * b->num_notes == 0, and consequently, old_fanout might be wrong.
+ *
+ * Fix this by traversing the tree and counting the number of notes
+ * when b->num_notes == 0. If the notes tree is truly empty, the
+ * calculation should not take long.
+ */
+ if (b->num_notes == 0 && *old_fanout == 0) {
+ /* Invoke change_note_fanout() in "counting mode". */
+ b->num_notes = change_note_fanout(&b->branch_tree, 0xff);
+ *old_fanout = convert_num_notes_to_fanout(b->num_notes);
+ }
+
+ /* Now parse the notemodify command. */
+ /* <dataref> or 'inline' */
+ if (*p == ':') {
+ oe = find_mark(marks, parse_mark_ref_space(&p));
+ oidcpy(&oid, &oe->idx.oid);
+ } else if (skip_prefix(p, "inline ", &p)) {
+ inline_data = 1;
+ oe = NULL; /* not used with inline_data, but makes gcc happy */
+ } else {
+ if (parse_mapped_oid_hex(p, &oid, &p))
+ die("Invalid dataref: %s", command_buf.buf);
+ oe = find_object(&oid);
+ if (*p++ != ' ')
+ die("Missing space after SHA1: %s", command_buf.buf);
+ }
+
+ /* <commit-ish> */
+ s = lookup_branch(p);
+ if (s) {
+ if (is_null_oid(&s->oid))
+ die("Can't add a note on empty branch.");
+ oidcpy(&commit_oid, &s->oid);
+ } else if (*p == ':') {
+ uintmax_t commit_mark = parse_mark_ref_eol(p);
+ struct object_entry *commit_oe = find_mark(marks, commit_mark);
+ if (commit_oe->type != OBJ_COMMIT)
+ die("Mark :%" PRIuMAX " not a commit", commit_mark);
+ oidcpy(&commit_oid, &commit_oe->idx.oid);
+ } else if (!get_oid(p, &commit_oid)) {
+ unsigned long size;
+ char *buf = read_object_with_reference(the_repository,
+ &commit_oid,
+ commit_type, &size,
+ &commit_oid);
+ if (!buf || size < the_hash_algo->hexsz + 6)
+ die("Not a valid commit: %s", p);
+ free(buf);
+ } else
+ die("Invalid ref name or SHA1 expression: %s", p);
+
+ if (inline_data) {
+ if (p != uq.buf) {
+ strbuf_addstr(&uq, p);
+ p = uq.buf;
+ }
+ read_next_command();
+ parse_and_store_blob(&last_blob, &oid, 0);
+ } else if (oe) {
+ if (oe->type != OBJ_BLOB)
+ die("Not a blob (actually a %s): %s",
+ type_name(oe->type), command_buf.buf);
+ } else if (!is_null_oid(&oid)) {
+ enum object_type type = oid_object_info(the_repository, &oid,
+ NULL);
+ if (type < 0)
+ die("Blob not found: %s", command_buf.buf);
+ if (type != OBJ_BLOB)
+ die("Not a blob (actually a %s): %s",
+ type_name(type), command_buf.buf);
+ }
+
+ construct_path_with_fanout(oid_to_hex(&commit_oid), *old_fanout, path);
+ if (tree_content_remove(&b->branch_tree, path, NULL, 0))
+ b->num_notes--;
+
+ if (is_null_oid(&oid))
+ return; /* nothing to insert */
+
+ b->num_notes++;
+ new_fanout = convert_num_notes_to_fanout(b->num_notes);
+ construct_path_with_fanout(oid_to_hex(&commit_oid), new_fanout, path);
+ tree_content_set(&b->branch_tree, path, &oid, S_IFREG | 0644, NULL);
+}
+
+static void file_change_deleteall(struct branch *b)
+{
+ release_tree_content_recursive(b->branch_tree.tree);
+ oidclr(&b->branch_tree.versions[0].oid);
+ oidclr(&b->branch_tree.versions[1].oid);
+ load_tree(&b->branch_tree);
+ b->num_notes = 0;
+}
+
+static void parse_from_commit(struct branch *b, char *buf, unsigned long size)
+{
+ if (!buf || size < the_hash_algo->hexsz + 6)
+ die("Not a valid commit: %s", oid_to_hex(&b->oid));
+ if (memcmp("tree ", buf, 5)
+ || get_oid_hex(buf + 5, &b->branch_tree.versions[1].oid))
+ die("The commit %s is corrupt", oid_to_hex(&b->oid));
+ oidcpy(&b->branch_tree.versions[0].oid,
+ &b->branch_tree.versions[1].oid);
+}
+
+static void parse_from_existing(struct branch *b)
+{
+ if (is_null_oid(&b->oid)) {
+ oidclr(&b->branch_tree.versions[0].oid);
+ oidclr(&b->branch_tree.versions[1].oid);
+ } else {
+ unsigned long size;
+ char *buf;
+
+ buf = read_object_with_reference(the_repository,
+ &b->oid, commit_type, &size,
+ &b->oid);
+ parse_from_commit(b, buf, size);
+ free(buf);
+ }
+}
+
+static int parse_objectish(struct branch *b, const char *objectish)
+{
+ struct branch *s;
+ struct object_id oid;
+
+ oidcpy(&oid, &b->branch_tree.versions[1].oid);
+
+ s = lookup_branch(objectish);
+ if (b == s)
+ die("Can't create a branch from itself: %s", b->name);
+ else if (s) {
+ struct object_id *t = &s->branch_tree.versions[1].oid;
+ oidcpy(&b->oid, &s->oid);
+ oidcpy(&b->branch_tree.versions[0].oid, t);
+ oidcpy(&b->branch_tree.versions[1].oid, t);
+ } else if (*objectish == ':') {
+ uintmax_t idnum = parse_mark_ref_eol(objectish);
+ struct object_entry *oe = find_mark(marks, idnum);
+ if (oe->type != OBJ_COMMIT)
+ die("Mark :%" PRIuMAX " not a commit", idnum);
+ if (!oideq(&b->oid, &oe->idx.oid)) {
+ oidcpy(&b->oid, &oe->idx.oid);
+ if (oe->pack_id != MAX_PACK_ID) {
+ unsigned long size;
+ char *buf = gfi_unpack_entry(oe, &size);
+ parse_from_commit(b, buf, size);
+ free(buf);
+ } else
+ parse_from_existing(b);
+ }
+ } else if (!get_oid(objectish, &b->oid)) {
+ parse_from_existing(b);
+ if (is_null_oid(&b->oid))
+ b->delete = 1;
+ }
+ else
+ die("Invalid ref name or SHA1 expression: %s", objectish);
+
+ if (b->branch_tree.tree && !oideq(&oid, &b->branch_tree.versions[1].oid)) {
+ release_tree_content_recursive(b->branch_tree.tree);
+ b->branch_tree.tree = NULL;
+ }
+
+ read_next_command();
+ return 1;
+}
+
+static int parse_from(struct branch *b)
+{
+ const char *from;
+
+ if (!skip_prefix(command_buf.buf, "from ", &from))
+ return 0;
+
+ return parse_objectish(b, from);
+}
+
+static int parse_objectish_with_prefix(struct branch *b, const char *prefix)
+{
+ const char *base;
+
+ if (!skip_prefix(command_buf.buf, prefix, &base))
+ return 0;
+
+ return parse_objectish(b, base);
+}
+
+static struct hash_list *parse_merge(unsigned int *count)
+{
+ struct hash_list *list = NULL, **tail = &list, *n;
+ const char *from;
+ struct branch *s;
+
+ *count = 0;
+ while (skip_prefix(command_buf.buf, "merge ", &from)) {
+ n = xmalloc(sizeof(*n));
+ s = lookup_branch(from);
+ if (s)
+ oidcpy(&n->oid, &s->oid);
+ else if (*from == ':') {
+ uintmax_t idnum = parse_mark_ref_eol(from);
+ struct object_entry *oe = find_mark(marks, idnum);
+ if (oe->type != OBJ_COMMIT)
+ die("Mark :%" PRIuMAX " not a commit", idnum);
+ oidcpy(&n->oid, &oe->idx.oid);
+ } else if (!get_oid(from, &n->oid)) {
+ unsigned long size;
+ char *buf = read_object_with_reference(the_repository,
+ &n->oid,
+ commit_type,
+ &size, &n->oid);
+ if (!buf || size < the_hash_algo->hexsz + 6)
+ die("Not a valid commit: %s", from);
+ free(buf);
+ } else
+ die("Invalid ref name or SHA1 expression: %s", from);
+
+ n->next = NULL;
+ *tail = n;
+ tail = &n->next;
+
+ (*count)++;
+ read_next_command();
+ }
+ return list;
+}
+
+static void parse_new_commit(const char *arg)
+{
+ static struct strbuf msg = STRBUF_INIT;
+ struct branch *b;
+ char *author = NULL;
+ char *committer = NULL;
+ char *encoding = NULL;
+ struct hash_list *merge_list = NULL;
+ unsigned int merge_count;
+ unsigned char prev_fanout, new_fanout;
+ const char *v;
+
+ b = lookup_branch(arg);
+ if (!b)
+ b = new_branch(arg);
+
+ read_next_command();
+ parse_mark();
+ parse_original_identifier();
+ if (skip_prefix(command_buf.buf, "author ", &v)) {
+ author = parse_ident(v);
+ read_next_command();
+ }
+ if (skip_prefix(command_buf.buf, "committer ", &v)) {
+ committer = parse_ident(v);
+ read_next_command();
+ }
+ if (!committer)
+ die("Expected committer but didn't get one");
+ if (skip_prefix(command_buf.buf, "encoding ", &v)) {
+ encoding = xstrdup(v);
+ read_next_command();
+ }
+ parse_data(&msg, 0, NULL);
+ read_next_command();
+ parse_from(b);
+ merge_list = parse_merge(&merge_count);
+
+ /* ensure the branch is active/loaded */
+ if (!b->branch_tree.tree || !max_active_branches) {
+ unload_one_branch();
+ load_branch(b);
+ }
+
+ prev_fanout = convert_num_notes_to_fanout(b->num_notes);
+
+ /* file_change* */
+ while (command_buf.len > 0) {
+ if (skip_prefix(command_buf.buf, "M ", &v))
+ file_change_m(v, b);
+ else if (skip_prefix(command_buf.buf, "D ", &v))
+ file_change_d(v, b);
+ else if (skip_prefix(command_buf.buf, "R ", &v))
+ file_change_cr(v, b, 1);
+ else if (skip_prefix(command_buf.buf, "C ", &v))
+ file_change_cr(v, b, 0);
+ else if (skip_prefix(command_buf.buf, "N ", &v))
+ note_change_n(v, b, &prev_fanout);
+ else if (!strcmp("deleteall", command_buf.buf))
+ file_change_deleteall(b);
+ else if (skip_prefix(command_buf.buf, "ls ", &v))
+ parse_ls(v, b);
+ else if (skip_prefix(command_buf.buf, "cat-blob ", &v))
+ parse_cat_blob(v);
+ else {
+ unread_command_buf = 1;
+ break;
+ }
+ if (read_next_command() == EOF)
+ break;
+ }
+
+ new_fanout = convert_num_notes_to_fanout(b->num_notes);
+ if (new_fanout != prev_fanout)
+ b->num_notes = change_note_fanout(&b->branch_tree, new_fanout);
+
+ /* build the tree and the commit */
+ store_tree(&b->branch_tree);
+ oidcpy(&b->branch_tree.versions[0].oid,
+ &b->branch_tree.versions[1].oid);
+
+ strbuf_reset(&new_data);
+ strbuf_addf(&new_data, "tree %s\n",
+ oid_to_hex(&b->branch_tree.versions[1].oid));
+ if (!is_null_oid(&b->oid))
+ strbuf_addf(&new_data, "parent %s\n",
+ oid_to_hex(&b->oid));
+ while (merge_list) {
+ struct hash_list *next = merge_list->next;
+ strbuf_addf(&new_data, "parent %s\n",
+ oid_to_hex(&merge_list->oid));
+ free(merge_list);
+ merge_list = next;
+ }
+ strbuf_addf(&new_data,
+ "author %s\n"
+ "committer %s\n",
+ author ? author : committer, committer);
+ if (encoding)
+ strbuf_addf(&new_data,
+ "encoding %s\n",
+ encoding);
+ strbuf_addch(&new_data, '\n');
+ strbuf_addbuf(&new_data, &msg);
+ free(author);
+ free(committer);
+ free(encoding);
+
+ if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))
+ b->pack_id = pack_id;
+ b->last_commit = object_count_by_type[OBJ_COMMIT];
+}
+
+static void parse_new_tag(const char *arg)
+{
+ static struct strbuf msg = STRBUF_INIT;
+ const char *from;
+ char *tagger;
+ struct branch *s;
+ struct tag *t;
+ uintmax_t from_mark = 0;
+ struct object_id oid;
+ enum object_type type;
+ const char *v;
+
+ t = mem_pool_alloc(&fi_mem_pool, sizeof(struct tag));
+ memset(t, 0, sizeof(struct tag));
+ t->name = mem_pool_strdup(&fi_mem_pool, arg);
+ if (last_tag)
+ last_tag->next_tag = t;
+ else
+ first_tag = t;
+ last_tag = t;
+ read_next_command();
+ parse_mark();
+
+ /* from ... */
+ if (!skip_prefix(command_buf.buf, "from ", &from))
+ die("Expected from command, got %s", command_buf.buf);
+ s = lookup_branch(from);
+ if (s) {
+ if (is_null_oid(&s->oid))
+ die("Can't tag an empty branch.");
+ oidcpy(&oid, &s->oid);
+ type = OBJ_COMMIT;
+ } else if (*from == ':') {
+ struct object_entry *oe;
+ from_mark = parse_mark_ref_eol(from);
+ oe = find_mark(marks, from_mark);
+ type = oe->type;
+ oidcpy(&oid, &oe->idx.oid);
+ } else if (!get_oid(from, &oid)) {
+ struct object_entry *oe = find_object(&oid);
+ if (!oe) {
+ type = oid_object_info(the_repository, &oid, NULL);
+ if (type < 0)
+ die("Not a valid object: %s", from);
+ } else
+ type = oe->type;
+ } else
+ die("Invalid ref name or SHA1 expression: %s", from);
+ read_next_command();
+
+ /* original-oid ... */
+ parse_original_identifier();
+
+ /* tagger ... */
+ if (skip_prefix(command_buf.buf, "tagger ", &v)) {
+ tagger = parse_ident(v);
+ read_next_command();
+ } else
+ tagger = NULL;
+
+ /* tag payload/message */
+ parse_data(&msg, 0, NULL);
+
+ /* build the tag object */
+ strbuf_reset(&new_data);
+
+ strbuf_addf(&new_data,
+ "object %s\n"
+ "type %s\n"
+ "tag %s\n",
+ oid_to_hex(&oid), type_name(type), t->name);
+ if (tagger)
+ strbuf_addf(&new_data,
+ "tagger %s\n", tagger);
+ strbuf_addch(&new_data, '\n');
+ strbuf_addbuf(&new_data, &msg);
+ free(tagger);
+
+ if (store_object(OBJ_TAG, &new_data, NULL, &t->oid, next_mark))
+ t->pack_id = MAX_PACK_ID;
+ else
+ t->pack_id = pack_id;
+}
+
+static void parse_reset_branch(const char *arg)
+{
+ struct branch *b;
+ const char *tag_name;
+
+ b = lookup_branch(arg);
+ if (b) {
+ oidclr(&b->oid);
+ oidclr(&b->branch_tree.versions[0].oid);
+ oidclr(&b->branch_tree.versions[1].oid);
+ if (b->branch_tree.tree) {
+ release_tree_content_recursive(b->branch_tree.tree);
+ b->branch_tree.tree = NULL;
+ }
+ }
+ else
+ b = new_branch(arg);
+ read_next_command();
+ parse_from(b);
+ if (b->delete && skip_prefix(b->name, "refs/tags/", &tag_name)) {
+ /*
+ * Elsewhere, we call dump_branches() before dump_tags(),
+ * and dump_branches() will handle ref deletions first, so
+ * in order to make sure the deletion actually takes effect,
+ * we need to remove the tag from our list of tags to update.
+ *
+ * NEEDSWORK: replace list of tags with hashmap for faster
+ * deletion?
+ */
+ struct tag *t, *prev = NULL;
+ for (t = first_tag; t; t = t->next_tag) {
+ if (!strcmp(t->name, tag_name))
+ break;
+ prev = t;
+ }
+ if (t) {
+ if (prev)
+ prev->next_tag = t->next_tag;
+ else
+ first_tag = t->next_tag;
+ if (!t->next_tag)
+ last_tag = prev;
+ /* There is no mem_pool_free(t) function to call. */
+ }
+ }
+ if (command_buf.len > 0)
+ unread_command_buf = 1;
+}
+
+static void cat_blob_write(const char *buf, unsigned long size)
+{
+ if (write_in_full(cat_blob_fd, buf, size) < 0)
+ die_errno("Write to frontend failed");
+}
+
+static void cat_blob(struct object_entry *oe, struct object_id *oid)
+{
+ struct strbuf line = STRBUF_INIT;
+ unsigned long size;
+ enum object_type type = 0;
+ char *buf;
+
+ if (!oe || oe->pack_id == MAX_PACK_ID) {
+ buf = read_object_file(oid, &type, &size);
+ } else {
+ type = oe->type;
+ buf = gfi_unpack_entry(oe, &size);
+ }
+
+ /*
+ * Output based on batch_one_object() from cat-file.c.
+ */
+ if (type <= 0) {
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%s missing\n", oid_to_hex(oid));
+ cat_blob_write(line.buf, line.len);
+ strbuf_release(&line);
+ free(buf);
+ return;
+ }
+ if (!buf)
+ die("Can't read object %s", oid_to_hex(oid));
+ if (type != OBJ_BLOB)
+ die("Object %s is a %s but a blob was expected.",
+ oid_to_hex(oid), type_name(type));
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%s %s %"PRIuMAX"\n", oid_to_hex(oid),
+ type_name(type), (uintmax_t)size);
+ cat_blob_write(line.buf, line.len);
+ strbuf_release(&line);
+ cat_blob_write(buf, size);
+ cat_blob_write("\n", 1);
+ if (oe && oe->pack_id == pack_id) {
+ last_blob.offset = oe->idx.offset;
+ strbuf_attach(&last_blob.data, buf, size, size);
+ last_blob.depth = oe->depth;
+ } else
+ free(buf);
+}
+
+static void parse_get_mark(const char *p)
+{
+ struct object_entry *oe;
+ char output[GIT_MAX_HEXSZ + 2];
+
+ /* get-mark SP <object> LF */
+ if (*p != ':')
+ die("Not a mark: %s", p);
+
+ oe = find_mark(marks, parse_mark_ref_eol(p));
+ if (!oe)
+ die("Unknown mark: %s", command_buf.buf);
+
+ xsnprintf(output, sizeof(output), "%s\n", oid_to_hex(&oe->idx.oid));
+ cat_blob_write(output, the_hash_algo->hexsz + 1);
+}
+
+static void parse_cat_blob(const char *p)
+{
+ struct object_entry *oe;
+ struct object_id oid;
+
+ /* cat-blob SP <object> LF */
+ if (*p == ':') {
+ oe = find_mark(marks, parse_mark_ref_eol(p));
+ if (!oe)
+ die("Unknown mark: %s", command_buf.buf);
+ oidcpy(&oid, &oe->idx.oid);
+ } else {
+ if (parse_mapped_oid_hex(p, &oid, &p))
+ die("Invalid dataref: %s", command_buf.buf);
+ if (*p)
+ die("Garbage after SHA1: %s", command_buf.buf);
+ oe = find_object(&oid);
+ }
+
+ cat_blob(oe, &oid);
+}
+
+static struct object_entry *dereference(struct object_entry *oe,
+ struct object_id *oid)
+{
+ unsigned long size;
+ char *buf = NULL;
+ const unsigned hexsz = the_hash_algo->hexsz;
+
+ if (!oe) {
+ enum object_type type = oid_object_info(the_repository, oid,
+ NULL);
+ if (type < 0)
+ die("object not found: %s", oid_to_hex(oid));
+ /* cache it! */
+ oe = insert_object(oid);
+ oe->type = type;
+ oe->pack_id = MAX_PACK_ID;
+ oe->idx.offset = 1;
+ }
+ switch (oe->type) {
+ case OBJ_TREE: /* easy case. */
+ return oe;
+ case OBJ_COMMIT:
+ case OBJ_TAG:
+ break;
+ default:
+ die("Not a tree-ish: %s", command_buf.buf);
+ }
+
+ if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */
+ buf = gfi_unpack_entry(oe, &size);
+ } else {
+ enum object_type unused;
+ buf = read_object_file(oid, &unused, &size);
+ }
+ if (!buf)
+ die("Can't load object %s", oid_to_hex(oid));
+
+ /* Peel one layer. */
+ switch (oe->type) {
+ case OBJ_TAG:
+ if (size < hexsz + strlen("object ") ||
+ get_oid_hex(buf + strlen("object "), oid))
+ die("Invalid SHA1 in tag: %s", command_buf.buf);
+ break;
+ case OBJ_COMMIT:
+ if (size < hexsz + strlen("tree ") ||
+ get_oid_hex(buf + strlen("tree "), oid))
+ die("Invalid SHA1 in commit: %s", command_buf.buf);
+ }
+
+ free(buf);
+ return find_object(oid);
+}
+
+static void insert_mapped_mark(uintmax_t mark, void *object, void *cbp)
+{
+ struct object_id *fromoid = object;
+ struct object_id *tooid = find_mark(cbp, mark);
+ int ret;
+ khiter_t it;
+
+ it = kh_put_oid_map(sub_oid_map, *fromoid, &ret);
+ /* We've already seen this object. */
+ if (ret == 0)
+ return;
+ kh_value(sub_oid_map, it) = tooid;
+}
+
+static void build_mark_map_one(struct mark_set *from, struct mark_set *to)
+{
+ for_each_mark(from, 0, insert_mapped_mark, to);
+}
+
+static void build_mark_map(struct string_list *from, struct string_list *to)
+{
+ struct string_list_item *fromp, *top;
+
+ sub_oid_map = kh_init_oid_map();
+
+ for_each_string_list_item(fromp, from) {
+ top = string_list_lookup(to, fromp->string);
+ if (!fromp->util) {
+ die(_("Missing from marks for submodule '%s'"), fromp->string);
+ } else if (!top || !top->util) {
+ die(_("Missing to marks for submodule '%s'"), fromp->string);
+ }
+ build_mark_map_one(fromp->util, top->util);
+ }
+}
+
+static struct object_entry *parse_treeish_dataref(const char **p)
+{
+ struct object_id oid;
+ struct object_entry *e;
+
+ if (**p == ':') { /* <mark> */
+ e = find_mark(marks, parse_mark_ref_space(p));
+ if (!e)
+ die("Unknown mark: %s", command_buf.buf);
+ oidcpy(&oid, &e->idx.oid);
+ } else { /* <sha1> */
+ if (parse_mapped_oid_hex(*p, &oid, p))
+ die("Invalid dataref: %s", command_buf.buf);
+ e = find_object(&oid);
+ if (*(*p)++ != ' ')
+ die("Missing space after tree-ish: %s", command_buf.buf);
+ }
+
+ while (!e || e->type != OBJ_TREE)
+ e = dereference(e, &oid);
+ return e;
+}
+
+static void print_ls(int mode, const unsigned char *hash, const char *path)
+{
+ static struct strbuf line = STRBUF_INIT;
+
+ /* See show_tree(). */
+ const char *type =
+ S_ISGITLINK(mode) ? commit_type :
+ S_ISDIR(mode) ? tree_type :
+ blob_type;
+
+ if (!mode) {
+ /* missing SP path LF */
+ strbuf_reset(&line);
+ strbuf_addstr(&line, "missing ");
+ quote_c_style(path, &line, NULL, 0);
+ strbuf_addch(&line, '\n');
+ } else {
+ /* mode SP type SP object_name TAB path LF */
+ strbuf_reset(&line);
+ strbuf_addf(&line, "%06o %s %s\t",
+ mode & ~NO_DELTA, type, hash_to_hex(hash));
+ quote_c_style(path, &line, NULL, 0);
+ strbuf_addch(&line, '\n');
+ }
+ cat_blob_write(line.buf, line.len);
+}
+
+static void parse_ls(const char *p, struct branch *b)
+{
+ struct tree_entry *root = NULL;
+ struct tree_entry leaf = {NULL};
+
+ /* ls SP (<tree-ish> SP)? <path> */
+ if (*p == '"') {
+ if (!b)
+ die("Not in a commit: %s", command_buf.buf);
+ root = &b->branch_tree;
+ } else {
+ struct object_entry *e = parse_treeish_dataref(&p);
+ root = new_tree_entry();
+ oidcpy(&root->versions[1].oid, &e->idx.oid);
+ if (!is_null_oid(&root->versions[1].oid))
+ root->versions[1].mode = S_IFDIR;
+ load_tree(root);
+ }
+ if (*p == '"') {
+ static struct strbuf uq = STRBUF_INIT;
+ const char *endp;
+ strbuf_reset(&uq);
+ if (unquote_c_style(&uq, p, &endp))
+ die("Invalid path: %s", command_buf.buf);
+ if (*endp)
+ die("Garbage after path in: %s", command_buf.buf);
+ p = uq.buf;
+ }
+ tree_content_get(root, p, &leaf, 1);
+ /*
+ * A directory in preparation would have a sha1 of zero
+ * until it is saved. Save, for simplicity.
+ */
+ if (S_ISDIR(leaf.versions[1].mode))
+ store_tree(&leaf);
+
+ print_ls(leaf.versions[1].mode, leaf.versions[1].oid.hash, p);
+ if (leaf.tree)
+ release_tree_content_recursive(leaf.tree);
+ if (!b || root != &b->branch_tree)
+ release_tree_entry(root);
+}
+
+static void checkpoint(void)
+{
+ checkpoint_requested = 0;
+ if (object_count) {
+ cycle_packfile();
+ }
+ dump_branches();
+ dump_tags();
+ dump_marks();
+}
+
+static void parse_checkpoint(void)
+{
+ checkpoint_requested = 1;
+ skip_optional_lf();
+}
+
+static void parse_progress(void)
+{
+ fwrite(command_buf.buf, 1, command_buf.len, stdout);
+ fputc('\n', stdout);
+ fflush(stdout);
+ skip_optional_lf();
+}
+
+static void parse_alias(void)
+{
+ struct object_entry *e;
+ struct branch b;
+
+ skip_optional_lf();
+ read_next_command();
+
+ /* mark ... */
+ parse_mark();
+ if (!next_mark)
+ die(_("Expected 'mark' command, got %s"), command_buf.buf);
+
+ /* to ... */
+ memset(&b, 0, sizeof(b));
+ if (!parse_objectish_with_prefix(&b, "to "))
+ die(_("Expected 'to' command, got %s"), command_buf.buf);
+ e = find_object(&b.oid);
+ assert(e);
+ insert_mark(&marks, next_mark, e);
+}
+
+static char* make_fast_import_path(const char *path)
+{
+ if (!relative_marks_paths || is_absolute_path(path))
+ return xstrdup(path);
+ return git_pathdup("info/fast-import/%s", path);
+}
+
+static void option_import_marks(const char *marks,
+ int from_stream, int ignore_missing)
+{
+ if (import_marks_file) {
+ if (from_stream)
+ die("Only one import-marks command allowed per stream");
+
+ /* read previous mark file */
+ if(!import_marks_file_from_stream)
+ read_marks();
+ }
+
+ import_marks_file = make_fast_import_path(marks);
+ import_marks_file_from_stream = from_stream;
+ import_marks_file_ignore_missing = ignore_missing;
+}
+
+static void option_date_format(const char *fmt)
+{
+ if (!strcmp(fmt, "raw"))
+ whenspec = WHENSPEC_RAW;
+ else if (!strcmp(fmt, "raw-permissive"))
+ whenspec = WHENSPEC_RAW_PERMISSIVE;
+ else if (!strcmp(fmt, "rfc2822"))
+ whenspec = WHENSPEC_RFC2822;
+ else if (!strcmp(fmt, "now"))
+ whenspec = WHENSPEC_NOW;
+ else
+ die("unknown --date-format argument %s", fmt);
+}
+
+static unsigned long ulong_arg(const char *option, const char *arg)
+{
+ char *endptr;
+ unsigned long rv = strtoul(arg, &endptr, 0);
+ if (strchr(arg, '-') || endptr == arg || *endptr)
+ die("%s: argument must be a non-negative integer", option);
+ return rv;
+}
+
+static void option_depth(const char *depth)
+{
+ max_depth = ulong_arg("--depth", depth);
+ if (max_depth > MAX_DEPTH)
+ die("--depth cannot exceed %u", MAX_DEPTH);
+}
+
+static void option_active_branches(const char *branches)
+{
+ max_active_branches = ulong_arg("--active-branches", branches);
+}
+
+static void option_export_marks(const char *marks)
+{
+ export_marks_file = make_fast_import_path(marks);
+}
+
+static void option_cat_blob_fd(const char *fd)
+{
+ unsigned long n = ulong_arg("--cat-blob-fd", fd);
+ if (n > (unsigned long) INT_MAX)
+ die("--cat-blob-fd cannot exceed %d", INT_MAX);
+ cat_blob_fd = (int) n;
+}
+
+static void option_export_pack_edges(const char *edges)
+{
+ if (pack_edges)
+ fclose(pack_edges);
+ pack_edges = xfopen(edges, "a");
+}
+
+static void option_rewrite_submodules(const char *arg, struct string_list *list)
+{
+ struct mark_set *ms;
+ FILE *fp;
+ char *s = xstrdup(arg);
+ char *f = strchr(s, ':');
+ if (!f)
+ die(_("Expected format name:filename for submodule rewrite option"));
+ *f = '\0';
+ f++;
+ ms = xcalloc(1, sizeof(*ms));
+
+ fp = fopen(f, "r");
+ if (!fp)
+ die_errno("cannot read '%s'", f);
+ read_mark_file(&ms, fp, insert_oid_entry);
+ fclose(fp);
+
+ string_list_insert(list, s)->util = ms;
+}
+
+static int parse_one_option(const char *option)
+{
+ if (skip_prefix(option, "max-pack-size=", &option)) {
+ unsigned long v;
+ if (!git_parse_ulong(option, &v))
+ return 0;
+ if (v < 8192) {
+ warning("max-pack-size is now in bytes, assuming --max-pack-size=%lum", v);
+ v *= 1024 * 1024;
+ } else if (v < 1024 * 1024) {
+ warning("minimum max-pack-size is 1 MiB");
+ v = 1024 * 1024;
+ }
+ max_packsize = v;
+ } else if (skip_prefix(option, "big-file-threshold=", &option)) {
+ unsigned long v;
+ if (!git_parse_ulong(option, &v))
+ return 0;
+ big_file_threshold = v;
+ } else if (skip_prefix(option, "depth=", &option)) {
+ option_depth(option);
+ } else if (skip_prefix(option, "active-branches=", &option)) {
+ option_active_branches(option);
+ } else if (skip_prefix(option, "export-pack-edges=", &option)) {
+ option_export_pack_edges(option);
+ } else if (!strcmp(option, "quiet")) {
+ show_stats = 0;
+ } else if (!strcmp(option, "stats")) {
+ show_stats = 1;
+ } else if (!strcmp(option, "allow-unsafe-features")) {
+ ; /* already handled during early option parsing */
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+static void check_unsafe_feature(const char *feature, int from_stream)
+{
+ if (from_stream && !allow_unsafe_features)
+ die(_("feature '%s' forbidden in input without --allow-unsafe-features"),
+ feature);
+}
+
+static int parse_one_feature(const char *feature, int from_stream)
+{
+ const char *arg;
+
+ if (skip_prefix(feature, "date-format=", &arg)) {
+ option_date_format(arg);
+ } else if (skip_prefix(feature, "import-marks=", &arg)) {
+ check_unsafe_feature("import-marks", from_stream);
+ option_import_marks(arg, from_stream, 0);
+ } else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) {
+ check_unsafe_feature("import-marks-if-exists", from_stream);
+ option_import_marks(arg, from_stream, 1);
+ } else if (skip_prefix(feature, "export-marks=", &arg)) {
+ check_unsafe_feature(feature, from_stream);
+ option_export_marks(arg);
+ } else if (!strcmp(feature, "alias")) {
+ ; /* Don't die - this feature is supported */
+ } else if (skip_prefix(feature, "rewrite-submodules-to=", &arg)) {
+ option_rewrite_submodules(arg, &sub_marks_to);
+ } else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) {
+ option_rewrite_submodules(arg, &sub_marks_from);
+ } else if (!strcmp(feature, "get-mark")) {
+ ; /* Don't die - this feature is supported */
+ } else if (!strcmp(feature, "cat-blob")) {
+ ; /* Don't die - this feature is supported */
+ } else if (!strcmp(feature, "relative-marks")) {
+ relative_marks_paths = 1;
+ } else if (!strcmp(feature, "no-relative-marks")) {
+ relative_marks_paths = 0;
+ } else if (!strcmp(feature, "done")) {
+ require_explicit_termination = 1;
+ } else if (!strcmp(feature, "force")) {
+ force_update = 1;
+ } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
+ ; /* do nothing; we have the feature */
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+static void parse_feature(const char *feature)
+{
+ if (seen_data_command)
+ die("Got feature command '%s' after data command", feature);
+
+ if (parse_one_feature(feature, 1))
+ return;
+
+ die("This version of fast-import does not support feature %s.", feature);
+}
+
+static void parse_option(const char *option)
+{
+ if (seen_data_command)
+ die("Got option command '%s' after data command", option);
+
+ if (parse_one_option(option))
+ return;
+
+ die("This version of fast-import does not support option: %s", option);
+}
+
+static void git_pack_config(void)
+{
+ int indexversion_value;
+ int limit;
+ unsigned long packsizelimit_value;
+
+ if (!git_config_get_ulong("pack.depth", &max_depth)) {
+ if (max_depth > MAX_DEPTH)
+ max_depth = MAX_DEPTH;
+ }
+ if (!git_config_get_int("pack.indexversion", &indexversion_value)) {
+ pack_idx_opts.version = indexversion_value;
+ if (pack_idx_opts.version > 2)
+ git_die_config("pack.indexversion",
+ "bad pack.indexversion=%"PRIu32, pack_idx_opts.version);
+ }
+ if (!git_config_get_ulong("pack.packsizelimit", &packsizelimit_value))
+ max_packsize = packsizelimit_value;
+
+ if (!git_config_get_int("fastimport.unpacklimit", &limit))
+ unpack_limit = limit;
+ else if (!git_config_get_int("transfer.unpacklimit", &limit))
+ unpack_limit = limit;
+
+ git_config(git_default_config, NULL);
+}
+
+static const char fast_import_usage[] =
+"git fast-import [--date-format=<f>] [--max-pack-size=<n>] [--big-file-threshold=<n>] [--depth=<n>] [--active-branches=<n>] [--export-marks=<marks.file>]";
+
+static void parse_argv(void)
+{
+ unsigned int i;
+
+ for (i = 1; i < global_argc; i++) {
+ const char *a = global_argv[i];
+
+ if (*a != '-' || !strcmp(a, "--"))
+ break;
+
+ if (!skip_prefix(a, "--", &a))
+ die("unknown option %s", a);
+
+ if (parse_one_option(a))
+ continue;
+
+ if (parse_one_feature(a, 0))
+ continue;
+
+ if (skip_prefix(a, "cat-blob-fd=", &a)) {
+ option_cat_blob_fd(a);
+ continue;
+ }
+
+ die("unknown option --%s", a);
+ }
+ if (i != global_argc)
+ usage(fast_import_usage);
+
+ seen_data_command = 1;
+ if (import_marks_file)
+ read_marks();
+ build_mark_map(&sub_marks_from, &sub_marks_to);
+}
+
+int cmd_fast_import(int argc, const char **argv, const char *prefix)
+{
+ unsigned int i;
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(fast_import_usage);
+
+ reset_pack_idx_option(&pack_idx_opts);
+ git_pack_config();
+
+ alloc_objects(object_entry_alloc);
+ strbuf_init(&command_buf, 0);
+ atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*));
+ branch_table = xcalloc(branch_table_sz, sizeof(struct branch*));
+ avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
+ marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set));
+
+ hashmap_init(&object_table, object_entry_hashcmp, NULL, 0);
+
+ /*
+ * We don't parse most options until after we've seen the set of
+ * "feature" lines at the start of the stream (which allows the command
+ * line to override stream data). But we must do an early parse of any
+ * command-line options that impact how we interpret the feature lines.
+ */
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (*arg != '-' || !strcmp(arg, "--"))
+ break;
+ if (!strcmp(arg, "--allow-unsafe-features"))
+ allow_unsafe_features = 1;
+ }
+
+ global_argc = argc;
+ global_argv = argv;
+
+ rc_free = mem_pool_alloc(&fi_mem_pool, cmd_save * sizeof(*rc_free));
+ for (i = 0; i < (cmd_save - 1); i++)
+ rc_free[i].next = &rc_free[i + 1];
+ rc_free[cmd_save - 1].next = NULL;
+
+ start_packfile();
+ set_die_routine(die_nicely);
+ set_checkpoint_signal();
+ while (read_next_command() != EOF) {
+ const char *v;
+ if (!strcmp("blob", command_buf.buf))
+ parse_new_blob();
+ else if (skip_prefix(command_buf.buf, "commit ", &v))
+ parse_new_commit(v);
+ else if (skip_prefix(command_buf.buf, "tag ", &v))
+ parse_new_tag(v);
+ else if (skip_prefix(command_buf.buf, "reset ", &v))
+ parse_reset_branch(v);
+ else if (skip_prefix(command_buf.buf, "ls ", &v))
+ parse_ls(v, NULL);
+ else if (skip_prefix(command_buf.buf, "cat-blob ", &v))
+ parse_cat_blob(v);
+ else if (skip_prefix(command_buf.buf, "get-mark ", &v))
+ parse_get_mark(v);
+ else if (!strcmp("checkpoint", command_buf.buf))
+ parse_checkpoint();
+ else if (!strcmp("done", command_buf.buf))
+ break;
+ else if (!strcmp("alias", command_buf.buf))
+ parse_alias();
+ else if (starts_with(command_buf.buf, "progress "))
+ parse_progress();
+ else if (skip_prefix(command_buf.buf, "feature ", &v))
+ parse_feature(v);
+ else if (skip_prefix(command_buf.buf, "option git ", &v))
+ parse_option(v);
+ else if (starts_with(command_buf.buf, "option "))
+ /* ignore non-git options*/;
+ else
+ die("Unsupported command: %s", command_buf.buf);
+
+ if (checkpoint_requested)
+ checkpoint();
+ }
+
+ /* argv hasn't been parsed yet, do so */
+ if (!seen_data_command)
+ parse_argv();
+
+ if (require_explicit_termination && feof(stdin))
+ die("stream ends early");
+
+ end_packfile();
+
+ dump_branches();
+ dump_tags();
+ unkeep_all_packs();
+ dump_marks();
+
+ if (pack_edges)
+ fclose(pack_edges);
+
+ if (show_stats) {
+ uintmax_t total_count = 0, duplicate_count = 0;
+ for (i = 0; i < ARRAY_SIZE(object_count_by_type); i++)
+ total_count += object_count_by_type[i];
+ for (i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++)
+ duplicate_count += duplicate_count_by_type[i];
+
+ fprintf(stderr, "%s statistics:\n", argv[0]);
+ fprintf(stderr, "---------------------------------------------------------------------\n");
+ fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count);
+ fprintf(stderr, "Total objects: %10" PRIuMAX " (%10" PRIuMAX " duplicates )\n", total_count, duplicate_count);
+ fprintf(stderr, " blobs : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB], delta_count_attempts_by_type[OBJ_BLOB]);
+ fprintf(stderr, " trees : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE], delta_count_attempts_by_type[OBJ_TREE]);
+ fprintf(stderr, " commits: %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT], delta_count_attempts_by_type[OBJ_COMMIT]);
+ fprintf(stderr, " tags : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG], delta_count_attempts_by_type[OBJ_TAG]);
+ fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count);
+ fprintf(stderr, " marks: %10" PRIuMAX " (%10" PRIuMAX " unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count);
+ fprintf(stderr, " atoms: %10u\n", atom_cnt);
+ fprintf(stderr, "Memory total: %10" PRIuMAX " KiB\n", (tree_entry_allocd + fi_mem_pool.pool_alloc + alloc_count*sizeof(struct object_entry))/1024);
+ fprintf(stderr, " pools: %10lu KiB\n", (unsigned long)((tree_entry_allocd + fi_mem_pool.pool_alloc) /1024));
+ fprintf(stderr, " objects: %10" PRIuMAX " KiB\n", (alloc_count*sizeof(struct object_entry))/1024);
+ fprintf(stderr, "---------------------------------------------------------------------\n");
+ pack_report();
+ fprintf(stderr, "---------------------------------------------------------------------\n");
+ fprintf(stderr, "\n");
+ }
+
+ return failure ? 1 : 0;
+}
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index dc1485c8aa..58b7c1fbdc 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -3,7 +3,7 @@
#include "fetch-pack.h"
#include "remote.h"
#include "connect.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "protocol.h"
static const char fetch_pack_usage[] =
@@ -48,8 +48,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
struct ref **sought = NULL;
int nr_sought = 0, alloc_sought = 0;
int fd[2];
- char *pack_lockfile = NULL;
- char **pack_lockfile_ptr = NULL;
+ struct string_list pack_lockfiles = STRING_LIST_INIT_DUP;
+ struct string_list *pack_lockfiles_ptr = NULL;
struct child_process *conn;
struct fetch_pack_args args;
struct oid_array shallow = OID_ARRAY_INIT;
@@ -134,7 +134,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
}
if (!strcmp("--lock-pack", arg)) {
args.lock_pack = 1;
- pack_lockfile_ptr = &pack_lockfile;
+ pack_lockfiles_ptr = &pack_lockfiles;
continue;
}
if (!strcmp("--check-self-contained-and-connected", arg)) {
@@ -153,10 +153,6 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
args.from_promisor = 1;
continue;
}
- if (!strcmp("--no-dependents", arg)) {
- args.no_dependents = 1;
- continue;
- }
if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) {
parse_list_objects_filter(&args.filter_options, arg);
continue;
@@ -224,7 +220,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
version = discover_version(&reader);
switch (version) {
case protocol_v2:
- get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL);
+ get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);
break;
case protocol_v1:
case protocol_v0:
@@ -235,10 +231,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
}
ref = fetch_pack(&args, fd, ref, sought, nr_sought,
- &shallow, pack_lockfile_ptr, version);
- if (pack_lockfile) {
- printf("lock %s\n", pack_lockfile);
+ &shallow, pack_lockfiles_ptr, version);
+ if (pack_lockfiles.nr) {
+ int i;
+
+ printf("lock %s\n", pack_lockfiles.items[0].string);
fflush(stdout);
+ for (i = 1; i < pack_lockfiles.nr; i++)
+ warning(_("Lockfile created but not reported: %s"),
+ pack_lockfiles.items[i].string);
}
if (args.check_self_contained_and_connected &&
args.self_contained_and_connected) {
diff --git a/builtin/fetch.c b/builtin/fetch.c
index c9b92b1e52..ecf8537605 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -7,6 +7,7 @@
#include "refs.h"
#include "refspec.h"
#include "object-store.h"
+#include "oidset.h"
#include "commit.h"
#include "builtin.h"
#include "string-list.h"
@@ -18,11 +19,17 @@
#include "submodule-config.h"
#include "submodule.h"
#include "connected.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "utf8.h"
#include "packfile.h"
#include "list-objects-filter-options.h"
#include "commit-reach.h"
+#include "branch.h"
+#include "promisor-remote.h"
+#include "commit-graph.h"
+#include "shallow.h"
+
+#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
static const char * const builtin_fetch_usage[] = {
N_("git fetch [<options>] [<repository> [<refspec>...]]"),
@@ -39,6 +46,8 @@ enum {
};
static int fetch_prune_config = -1; /* unspecified */
+static int fetch_show_forced_updates = 1;
+static uint64_t forced_updates_ms = 0;
static int prune = -1; /* unspecified */
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
@@ -46,10 +55,14 @@ static int fetch_prune_tags_config = -1; /* unspecified */
static int prune_tags = -1; /* unspecified */
#define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
-static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
+static int all, append, dry_run, force, keep, multiple, update_head_ok;
+static int write_fetch_head = 1;
+static int verbosity, deepen_relative, set_upstream;
static int progress = -1;
+static int enable_auto_gc = 1;
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
-static int max_children = 1;
+static int max_jobs = -1, submodule_fetch_jobs_config = -1;
+static int fetch_parallel_config = 1;
static enum transport_family family;
static const char *depth;
static const char *deepen_since;
@@ -66,6 +79,8 @@ static struct refspec refmap = REFSPEC_INIT_FETCH;
static struct list_objects_filter_options filter_options;
static struct string_list server_options = STRING_LIST_INIT_DUP;
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
+static int fetch_write_commit_graph = -1;
+static int stdin_refspecs = 0;
static int git_fetch_config(const char *k, const char *v, void *cb)
{
@@ -79,6 +94,11 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
return 0;
}
+ if (!strcmp(k, "fetch.showforcedupdates")) {
+ fetch_show_forced_updates = git_config_bool(k, v);
+ return 0;
+ }
+
if (!strcmp(k, "submodule.recurse")) {
int r = git_config_bool(k, v) ?
RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
@@ -86,13 +106,20 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
}
if (!strcmp(k, "submodule.fetchjobs")) {
- max_children = parse_submodule_fetchjobs(k, v);
+ submodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v);
return 0;
} else if (!strcmp(k, "fetch.recursesubmodules")) {
recurse_submodules = parse_fetch_recurse_submodules_arg(k, v);
return 0;
}
+ if (!strcmp(k, "fetch.parallel")) {
+ fetch_parallel_config = git_config_int(k, v);
+ if (fetch_parallel_config < 0)
+ die(_("fetch.parallel cannot be negative"));
+ return 0;
+ }
+
return git_default_config(k, v, cb);
}
@@ -113,6 +140,8 @@ static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
OPT_BOOL(0, "all", &all,
N_("fetch from all remotes")),
+ OPT_BOOL(0, "set-upstream", &set_upstream,
+ N_("set upstream for git pull/fetch")),
OPT_BOOL('a', "append", &append,
N_("append to .git/FETCH_HEAD instead of overwriting")),
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
@@ -124,17 +153,19 @@ static struct option builtin_fetch_options[] = {
N_("fetch all tags and associated objects"), TAGS_SET),
OPT_SET_INT('n', NULL, &tags,
N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
- OPT_INTEGER('j', "jobs", &max_children,
+ OPT_INTEGER('j', "jobs", &max_jobs,
N_("number of submodules fetched in parallel")),
OPT_BOOL('p', "prune", &prune,
N_("prune remote-tracking branches no longer on remote")),
OPT_BOOL('P', "prune-tags", &prune_tags,
N_("prune local tags no longer on remote and clobber changed tags")),
- { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
+ OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"),
N_("control recursive fetching of submodules"),
- PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
+ PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
OPT_BOOL(0, "dry-run", &dry_run,
N_("dry run")),
+ OPT_BOOL(0, "write-fetch-head", &write_fetch_head,
+ N_("write fetched references to the FETCH_HEAD file")),
OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")),
OPT_BOOL('u', "update-head-ok", &update_head_ok,
N_("allow updating of HEAD ref")),
@@ -152,15 +183,15 @@ static struct option builtin_fetch_options[] = {
1, PARSE_OPT_NONEG),
{ OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"),
N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN },
- { OPTION_CALLBACK, 0, "recurse-submodules-default",
+ OPT_CALLBACK_F(0, "recurse-submodules-default",
&recurse_submodules_default, N_("on-demand"),
N_("default for recursive fetching of submodules "
"(lower priority than config files)"),
- PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules },
+ PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules),
OPT_BOOL(0, "update-shallow", &update_shallow,
N_("accept refs that update .git/shallow")),
- { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"),
- N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg },
+ OPT_CALLBACK_F(0, "refmap", NULL, N_("refmap"),
+ N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg),
OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
@@ -169,6 +200,16 @@ static struct option builtin_fetch_options[] = {
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
N_("report that we have only objects reachable from this object")),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
+ OPT_BOOL(0, "auto-maintenance", &enable_auto_gc,
+ N_("run 'maintenance --auto' after fetching")),
+ OPT_BOOL(0, "auto-gc", &enable_auto_gc,
+ N_("run 'maintenance --auto' after fetching")),
+ OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates,
+ N_("check for forced-updates on all updated branches")),
+ OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph,
+ N_("write the commit-graph after fetching")),
+ OPT_BOOL(0, "stdin", &stdin_refspecs,
+ N_("accept refspecs from stdin")),
OPT_END()
};
@@ -225,32 +266,31 @@ static void add_merge_config(struct ref **head,
}
}
-static int will_fetch(struct ref **head, const unsigned char *sha1)
+static void create_fetch_oidset(struct ref **head, struct oidset *out)
{
struct ref *rm = *head;
while (rm) {
- if (hasheq(rm->old_oid.hash, sha1))
- return 1;
+ oidset_insert(out, &rm->old_oid);
rm = rm->next;
}
- return 0;
}
struct refname_hash_entry {
- struct hashmap_entry ent; /* must be the first member */
+ struct hashmap_entry ent;
struct object_id oid;
int ignore;
char refname[FLEX_ARRAY];
};
static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data,
- const void *e1_,
- const void *e2_,
+ const struct hashmap_entry *eptr,
+ const struct hashmap_entry *entry_or_key,
const void *keydata)
{
- const struct refname_hash_entry *e1 = e1_;
- const struct refname_hash_entry *e2 = e2_;
+ const struct refname_hash_entry *e1, *e2;
+ e1 = container_of(eptr, const struct refname_hash_entry, ent);
+ e2 = container_of(entry_or_key, const struct refname_hash_entry, ent);
return strcmp(e1->refname, keydata ? keydata : e2->refname);
}
@@ -262,9 +302,9 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map,
size_t len = strlen(refname);
FLEX_ALLOC_MEM(ent, refname, refname, len);
- hashmap_entry_init(ent, strhash(refname));
+ hashmap_entry_init(&ent->ent, strhash(refname));
oidcpy(&ent->oid, oid);
- hashmap_add(map, ent);
+ hashmap_add(map, &ent->ent);
return ent;
}
@@ -299,13 +339,16 @@ static void find_non_local_tags(const struct ref *refs,
{
struct hashmap existing_refs;
struct hashmap remote_refs;
+ struct oidset fetch_oids = OIDSET_INIT;
struct string_list remote_refs_list = STRING_LIST_INIT_NODUP;
struct string_list_item *remote_ref_item;
const struct ref *ref;
struct refname_hash_entry *item = NULL;
+ const int quick_flags = OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT;
refname_hash_init(&existing_refs);
refname_hash_init(&remote_refs);
+ create_fetch_oidset(head, &fetch_oids);
for_each_ref(add_one_refname, &existing_refs);
for (ref = refs; ref; ref = ref->next) {
@@ -320,11 +363,10 @@ static void find_non_local_tags(const struct ref *refs,
*/
if (ends_with(ref->name, "^{}")) {
if (item &&
- !has_object_file_with_flags(&ref->old_oid,
- OBJECT_INFO_QUICK) &&
- !will_fetch(head, ref->old_oid.hash) &&
- !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
- !will_fetch(head, item->oid.hash))
+ !has_object_file_with_flags(&ref->old_oid, quick_flags) &&
+ !oidset_contains(&fetch_oids, &ref->old_oid) &&
+ !has_object_file_with_flags(&item->oid, quick_flags) &&
+ !oidset_contains(&fetch_oids, &item->oid))
clear_item(item);
item = NULL;
continue;
@@ -337,8 +379,8 @@ static void find_non_local_tags(const struct ref *refs,
* fetch.
*/
if (item &&
- !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
- !will_fetch(head, item->oid.hash))
+ !has_object_file_with_flags(&item->oid, quick_flags) &&
+ !oidset_contains(&fetch_oids, &item->oid))
clear_item(item);
item = NULL;
@@ -351,15 +393,15 @@ static void find_non_local_tags(const struct ref *refs,
item = refname_hash_add(&remote_refs, ref->name, &ref->old_oid);
string_list_insert(&remote_refs_list, ref->name);
}
- hashmap_free(&existing_refs, 1);
+ hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);
/*
* We may have a final lightweight tag that needs to be
* checked to see if it needs fetching.
*/
if (item &&
- !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) &&
- !will_fetch(head, item->oid.hash))
+ !has_object_file_with_flags(&item->oid, quick_flags) &&
+ !oidset_contains(&fetch_oids, &item->oid))
clear_item(item);
/*
@@ -369,8 +411,10 @@ static void find_non_local_tags(const struct ref *refs,
for_each_string_list_item(remote_ref_item, &remote_refs_list) {
const char *refname = remote_ref_item->string;
struct ref *rm;
+ unsigned int hash = strhash(refname);
- item = hashmap_get_from_hash(&remote_refs, strhash(refname), refname);
+ item = hashmap_get_entry_from_hash(&remote_refs, hash, refname,
+ struct refname_hash_entry, ent);
if (!item)
BUG("unseen remote ref?");
@@ -384,8 +428,9 @@ static void find_non_local_tags(const struct ref *refs,
**tail = rm;
*tail = &rm->next;
}
- hashmap_free(&remote_refs, 1);
+ hashmap_clear_and_free(&remote_refs, struct refname_hash_entry, ent);
string_list_clear(&remote_refs_list, 0);
+ oidset_clear(&fetch_oids);
}
static struct ref *get_ref_map(struct remote *remote,
@@ -402,6 +447,7 @@ static struct ref *get_ref_map(struct remote *remote,
struct ref *orefs = NULL, **oref_tail = &orefs;
struct hashmap existing_refs;
+ int existing_refs_populated = 0;
if (rs->nr) {
struct refspec *fetch_refspec;
@@ -493,26 +539,41 @@ static struct ref *get_ref_map(struct remote *remote,
tail = &rm->next;
}
- ref_map = ref_remove_duplicates(ref_map);
+ /*
+ * apply negative refspecs first, before we remove duplicates. This is
+ * necessary as negative refspecs might remove an otherwise conflicting
+ * duplicate.
+ */
+ if (rs->nr)
+ ref_map = apply_negative_refspecs(ref_map, rs);
+ else
+ ref_map = apply_negative_refspecs(ref_map, &remote->fetch);
- refname_hash_init(&existing_refs);
- for_each_ref(add_one_refname, &existing_refs);
+ ref_map = ref_remove_duplicates(ref_map);
for (rm = ref_map; rm; rm = rm->next) {
if (rm->peer_ref) {
const char *refname = rm->peer_ref->name;
struct refname_hash_entry *peer_item;
+ unsigned int hash = strhash(refname);
- peer_item = hashmap_get_from_hash(&existing_refs,
- strhash(refname),
- refname);
+ if (!existing_refs_populated) {
+ refname_hash_init(&existing_refs);
+ for_each_ref(add_one_refname, &existing_refs);
+ existing_refs_populated = 1;
+ }
+
+ peer_item = hashmap_get_entry_from_hash(&existing_refs,
+ hash, refname,
+ struct refname_hash_entry, ent);
if (peer_item) {
struct object_id *old_oid = &peer_item->oid;
oidcpy(&rm->peer_ref->old_oid, old_oid);
}
}
}
- hashmap_free(&existing_refs, 1);
+ if (existing_refs_populated)
+ hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);
return ref_map;
}
@@ -607,7 +668,7 @@ static void prepare_format_display(struct ref *ref_map)
struct ref *rm;
const char *format = "full";
- git_config_get_string_const("fetch.output", &format);
+ git_config_get_string_tmp("fetch.output", &format);
if (!strcasecmp(format, "full"))
compact_format = 0;
else if (!strcasecmp(format, "compact"))
@@ -707,6 +768,7 @@ static int update_local_ref(struct ref *ref,
enum object_type type;
struct branch *current_branch = branch_get(NULL);
const char *pretty_ref = prettify_refname(ref->name);
+ int fast_forward = 0;
type = oid_object_info(the_repository, &ref->new_oid, NULL);
if (type < 0)
@@ -781,9 +843,18 @@ static int update_local_ref(struct ref *ref,
return r;
}
- if (in_merge_bases(current, updated)) {
+ if (fetch_show_forced_updates) {
+ uint64_t t_before = getnanotime();
+ fast_forward = in_merge_bases(current, updated);
+ forced_updates_ms += (getnanotime() - t_before) / 1000000;
+ } else {
+ fast_forward = 1;
+ }
+
+ if (fast_forward) {
struct strbuf quickref = STRBUF_INIT;
int r;
+
strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
strbuf_addstr(&quickref, "..");
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
@@ -826,6 +897,15 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
return 0;
}
+static const char warn_show_forced_updates[] =
+N_("Fetch normally indicates which branches had a forced update,\n"
+ "but that check has been disabled. To re-enable, use '--show-forced-updates'\n"
+ "flag or run 'git config fetch.showForcedUpdates true'.");
+static const char warn_time_show_forced_updates[] =
+N_("It took %.2f seconds to check forced updates. You can use\n"
+ "'--no-show-forced-updates' or run 'git config fetch.showForcedUpdates false'\n"
+ " to avoid this check.\n");
+
static int store_updated_refs(const char *raw_url, const char *remote_name,
int connectivity_checked, struct ref *ref_map)
{
@@ -836,7 +916,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
const char *what, *kind;
struct ref *rm;
char *url;
- const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(the_repository);
+ const char *filename = (!write_fetch_head
+ ? "/dev/null"
+ : git_path_fetch_head(the_repository));
int want_status;
int summary_width = transport_summary_width(ref_map);
@@ -850,8 +932,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
url = xstrdup("foreign");
if (!connectivity_checked) {
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
+
rm = ref_map;
- if (check_connected(iterate_ref_map, &rm, NULL)) {
+ if (check_connected(iterate_ref_map, &rm, &opt)) {
rc = error(_("%s did not send all necessary objects\n"), url);
goto abort;
}
@@ -894,25 +978,21 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
ref->force = rm->peer_ref->force;
}
- if (recurse_submodules != RECURSE_SUBMODULES_OFF)
+ if (recurse_submodules != RECURSE_SUBMODULES_OFF &&
+ (!rm->peer_ref || !oideq(&ref->old_oid, &ref->new_oid))) {
check_for_new_submodule_commits(&rm->old_oid);
+ }
if (!strcmp(rm->name, "HEAD")) {
kind = "";
what = "";
}
- else if (starts_with(rm->name, "refs/heads/")) {
+ else if (skip_prefix(rm->name, "refs/heads/", &what))
kind = "branch";
- what = rm->name + 11;
- }
- else if (starts_with(rm->name, "refs/tags/")) {
+ else if (skip_prefix(rm->name, "refs/tags/", &what))
kind = "tag";
- what = rm->name + 10;
- }
- else if (starts_with(rm->name, "refs/remotes/")) {
+ else if (skip_prefix(rm->name, "refs/remotes/", &what))
kind = "remote-tracking branch";
- what = rm->name + 13;
- }
else {
kind = "";
what = rm->name;
@@ -957,11 +1037,17 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
rc |= update_local_ref(ref, what, rm, &note,
summary_width);
free(ref);
- } else
+ } else if (write_fetch_head || dry_run) {
+ /*
+ * Display fetches written to FETCH_HEAD (or
+ * would be written to FETCH_HEAD, if --dry-run
+ * is set).
+ */
format_display(&note, '*',
*kind ? kind : "branch", NULL,
*what ? what : "HEAD",
"FETCH_HEAD", summary_width);
+ }
if (note.len) {
if (verbosity >= 0 && !shown_url) {
fprintf(stderr, _("From %.*s\n"),
@@ -979,6 +1065,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
" 'git remote prune %s' to remove any old, conflicting "
"branches"), remote_name);
+ if (advice_fetch_show_forced_updates) {
+ if (!fetch_show_forced_updates) {
+ warning(_(warn_show_forced_updates));
+ } else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) {
+ warning(_(warn_time_show_forced_updates),
+ forced_updates_ms / 1000.0);
+ }
+ }
+
abort:
strbuf_release(&note);
free(url);
@@ -1012,7 +1107,8 @@ static int check_exist_and_connected(struct ref *ref_map)
* we need all direct targets to exist.
*/
for (r = rm; r; r = r->next) {
- if (!has_object_file(&r->old_oid))
+ if (!has_object_file_with_flags(&r->old_oid,
+ OBJECT_INFO_SKIP_FETCH_OBJECT))
return -1;
}
@@ -1023,8 +1119,11 @@ static int check_exist_and_connected(struct ref *ref_map)
static int fetch_refs(struct transport *transport, struct ref *ref_map)
{
int ret = check_exist_and_connected(ref_map);
- if (ret)
+ if (ret) {
+ trace2_region_enter("fetch", "fetch_refs", the_repository);
ret = transport_fetch_refs(transport, ref_map);
+ trace2_region_leave("fetch", "fetch_refs", the_repository);
+ }
if (!ret)
/*
* Keep the new pack's ".keep" file around to allow the caller
@@ -1040,11 +1139,14 @@ static int consume_refs(struct transport *transport, struct ref *ref_map)
{
int connectivity_checked = transport->smart_options
? transport->smart_options->connectivity_checked : 0;
- int ret = store_updated_refs(transport->url,
- transport->remote->name,
- connectivity_checked,
- ref_map);
+ int ret;
+ trace2_region_enter("fetch", "consume_refs", the_repository);
+ ret = store_updated_refs(transport->url,
+ transport->remote->name,
+ connectivity_checked,
+ ref_map);
transport_unlock_pack(transport);
+ trace2_region_leave("fetch", "consume_refs", the_repository);
return ret;
}
@@ -1196,13 +1298,10 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
if (update_shallow)
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
if (filter_options.choice) {
- struct strbuf expanded_filter_spec = STRBUF_INIT;
- expand_list_objects_filter_spec(&filter_options,
- &expanded_filter_spec);
- set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER,
- expanded_filter_spec.buf);
+ const char *spec =
+ expand_list_objects_filter_spec(&filter_options);
+ set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec);
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
- strbuf_release(&expanded_filter_spec);
}
if (negotiation_tip.nr) {
if (transport->smart_options)
@@ -1250,7 +1349,7 @@ static int do_fetch(struct transport *transport,
int autotags = (transport->remote->fetch_tags == 1);
int retcode = 0;
const struct ref *remote_refs;
- struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
+ struct strvec ref_prefixes = STRVEC_INIT;
int must_list_refs = 1;
if (tags == TAGS_DEFAULT) {
@@ -1261,7 +1360,7 @@ static int do_fetch(struct transport *transport,
}
/* if not appending, truncate FETCH_HEAD */
- if (!append && !dry_run) {
+ if (!append && write_fetch_head) {
retcode = truncate_fetch_head();
if (retcode)
goto cleanup;
@@ -1288,16 +1387,18 @@ static int do_fetch(struct transport *transport,
if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
must_list_refs = 1;
- if (ref_prefixes.argc)
- argv_array_push(&ref_prefixes, "refs/tags/");
+ if (ref_prefixes.nr)
+ strvec_push(&ref_prefixes, "refs/tags/");
}
- if (must_list_refs)
+ if (must_list_refs) {
+ trace2_region_enter("fetch", "remote_refs", the_repository);
remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
- else
+ trace2_region_leave("fetch", "remote_refs", the_repository);
+ } else
remote_refs = NULL;
- argv_array_clear(&ref_prefixes);
+ strvec_clear(&ref_prefixes);
ref_map = get_ref_map(transport->remote, remote_refs, rs,
tags, &autotags);
@@ -1325,6 +1426,51 @@ static int do_fetch(struct transport *transport,
retcode = 1;
goto cleanup;
}
+
+ if (set_upstream) {
+ struct branch *branch = branch_get("HEAD");
+ struct ref *rm;
+ struct ref *source_ref = NULL;
+
+ /*
+ * We're setting the upstream configuration for the
+ * current branch. The relevant upstream is the
+ * fetched branch that is meant to be merged with the
+ * current one, i.e. the one fetched to FETCH_HEAD.
+ *
+ * When there are several such branches, consider the
+ * request ambiguous and err on the safe side by doing
+ * nothing and just emit a warning.
+ */
+ for (rm = ref_map; rm; rm = rm->next) {
+ if (!rm->peer_ref) {
+ if (source_ref) {
+ warning(_("multiple branches detected, incompatible with --set-upstream"));
+ goto skip;
+ } else {
+ source_ref = rm;
+ }
+ }
+ }
+ if (source_ref) {
+ if (!strcmp(source_ref->name, "HEAD") ||
+ starts_with(source_ref->name, "refs/heads/"))
+ install_branch_config(0,
+ branch->name,
+ transport->remote->name,
+ source_ref->name);
+ else if (starts_with(source_ref->name, "refs/remotes/"))
+ warning(_("not setting upstream for a remote remote-tracking branch"));
+ else if (starts_with(source_ref->name, "refs/tags/"))
+ warning(_("not setting upstream for a remote tag"));
+ else
+ warning(_("unknown branch type"));
+ } else {
+ warning(_("no source branch found.\n"
+ "you need to specify exactly one branch with the --set-upstream option."));
+ }
+ }
+ skip:
free_refs(ref_map);
/* if neither --no-tags nor --tags was specified, do automated tag
@@ -1390,65 +1536,138 @@ static int add_remote_or_group(const char *name, struct string_list *list)
return 1;
}
-static void add_options_to_argv(struct argv_array *argv)
+static void add_options_to_argv(struct strvec *argv)
{
if (dry_run)
- argv_array_push(argv, "--dry-run");
+ strvec_push(argv, "--dry-run");
if (prune != -1)
- argv_array_push(argv, prune ? "--prune" : "--no-prune");
+ strvec_push(argv, prune ? "--prune" : "--no-prune");
if (prune_tags != -1)
- argv_array_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags");
+ strvec_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags");
if (update_head_ok)
- argv_array_push(argv, "--update-head-ok");
+ strvec_push(argv, "--update-head-ok");
if (force)
- argv_array_push(argv, "--force");
+ strvec_push(argv, "--force");
if (keep)
- argv_array_push(argv, "--keep");
+ strvec_push(argv, "--keep");
if (recurse_submodules == RECURSE_SUBMODULES_ON)
- argv_array_push(argv, "--recurse-submodules");
+ strvec_push(argv, "--recurse-submodules");
else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
- argv_array_push(argv, "--recurse-submodules=on-demand");
+ strvec_push(argv, "--recurse-submodules=on-demand");
if (tags == TAGS_SET)
- argv_array_push(argv, "--tags");
+ strvec_push(argv, "--tags");
else if (tags == TAGS_UNSET)
- argv_array_push(argv, "--no-tags");
+ strvec_push(argv, "--no-tags");
if (verbosity >= 2)
- argv_array_push(argv, "-v");
+ strvec_push(argv, "-v");
if (verbosity >= 1)
- argv_array_push(argv, "-v");
+ strvec_push(argv, "-v");
else if (verbosity < 0)
- argv_array_push(argv, "-q");
+ strvec_push(argv, "-q");
+ if (family == TRANSPORT_FAMILY_IPV4)
+ strvec_push(argv, "--ipv4");
+ else if (family == TRANSPORT_FAMILY_IPV6)
+ strvec_push(argv, "--ipv6");
+}
+
+/* Fetch multiple remotes in parallel */
+struct parallel_fetch_state {
+ const char **argv;
+ struct string_list *remotes;
+ int next, result;
+};
+
+static int fetch_next_remote(struct child_process *cp, struct strbuf *out,
+ void *cb, void **task_cb)
+{
+ struct parallel_fetch_state *state = cb;
+ char *remote;
+
+ if (state->next < 0 || state->next >= state->remotes->nr)
+ return 0;
+
+ remote = state->remotes->items[state->next++].string;
+ *task_cb = remote;
+
+ strvec_pushv(&cp->args, state->argv);
+ strvec_push(&cp->args, remote);
+ cp->git_cmd = 1;
+
+ if (verbosity >= 0)
+ printf(_("Fetching %s\n"), remote);
+
+ return 1;
}
-static int fetch_multiple(struct string_list *list)
+static int fetch_failed_to_start(struct strbuf *out, void *cb, void *task_cb)
+{
+ struct parallel_fetch_state *state = cb;
+ const char *remote = task_cb;
+
+ state->result = error(_("Could not fetch %s"), remote);
+
+ return 0;
+}
+
+static int fetch_finished(int result, struct strbuf *out,
+ void *cb, void *task_cb)
+{
+ struct parallel_fetch_state *state = cb;
+ const char *remote = task_cb;
+
+ if (result) {
+ strbuf_addf(out, _("could not fetch '%s' (exit code: %d)\n"),
+ remote, result);
+ state->result = -1;
+ }
+
+ return 0;
+}
+
+static int fetch_multiple(struct string_list *list, int max_children)
{
int i, result = 0;
- struct argv_array argv = ARGV_ARRAY_INIT;
+ struct strvec argv = STRVEC_INIT;
- if (!append && !dry_run) {
+ if (!append && write_fetch_head) {
int errcode = truncate_fetch_head();
if (errcode)
return errcode;
}
- argv_array_pushl(&argv, "fetch", "--append", NULL);
+ strvec_pushl(&argv, "fetch", "--append", "--no-auto-gc",
+ "--no-write-commit-graph", NULL);
add_options_to_argv(&argv);
- for (i = 0; i < list->nr; i++) {
- const char *name = list->items[i].string;
- argv_array_push(&argv, name);
- if (verbosity >= 0)
- printf(_("Fetching %s\n"), name);
- if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
- error(_("Could not fetch %s"), name);
- result = 1;
+ if (max_children != 1 && list->nr != 1) {
+ struct parallel_fetch_state state = { argv.v, list, 0, 0 };
+
+ strvec_push(&argv, "--end-of-options");
+ result = run_processes_parallel_tr2(max_children,
+ &fetch_next_remote,
+ &fetch_failed_to_start,
+ &fetch_finished,
+ &state,
+ "fetch", "parallel/fetch");
+
+ if (!result)
+ result = state.result;
+ } else
+ for (i = 0; i < list->nr; i++) {
+ const char *name = list->items[i].string;
+ strvec_push(&argv, name);
+ if (verbosity >= 0)
+ printf(_("Fetching %s\n"), name);
+ if (run_command_v_opt(argv.v, RUN_GIT_CMD)) {
+ error(_("Could not fetch %s"), name);
+ result = 1;
+ }
+ strvec_pop(&argv);
}
- argv_array_pop(&argv);
- }
- argv_array_clear(&argv);
- return result;
+ strvec_clear(&argv);
+ return !!result;
}
/*
@@ -1468,41 +1687,32 @@ static inline void fetch_one_setup_partial(struct remote *remote)
* If no prior partial clone/fetch and the current fetch DID NOT
* request a partial-fetch, do a normal fetch.
*/
- if (!repository_format_partial_clone && !filter_options.choice)
+ if (!has_promisor_remote() && !filter_options.choice)
return;
/*
- * If this is the FIRST partial-fetch request, we enable partial
- * on this repo and remember the given filter-spec as the default
- * for subsequent fetches to this remote.
+ * If this is a partial-fetch request, we enable partial on
+ * this repo if not already enabled and remember the given
+ * filter-spec as the default for subsequent fetches to this
+ * remote if there is currently no default filter-spec.
*/
- if (!repository_format_partial_clone && filter_options.choice) {
+ if (filter_options.choice) {
partial_clone_register(remote->name, &filter_options);
return;
}
/*
- * We are currently limited to only ONE promisor remote and only
- * allow partial-fetches from the promisor remote.
- */
- if (strcmp(remote->name, repository_format_partial_clone)) {
- if (filter_options.choice)
- die(_("--filter can only be used with the remote "
- "configured in extensions.partialClone"));
- return;
- }
-
- /*
* Do a partial-fetch from the promisor remote using either the
* explicitly given filter-spec or inherit the filter-spec from
* the config.
*/
if (!filter_options.choice)
- partial_clone_get_default_filter_spec(&filter_options);
+ partial_clone_get_default_filter_spec(&filter_options, remote->name);
return;
}
-static int fetch_one(struct remote *remote, int argc, const char **argv, int prune_tags_ok)
+static int fetch_one(struct remote *remote, int argc, const char **argv,
+ int prune_tags_ok, int use_stdin_refspecs)
{
struct refspec rs = REFSPEC_INIT_FETCH;
int i;
@@ -1545,20 +1755,24 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int pru
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "tag")) {
- char *tag;
i++;
if (i >= argc)
die(_("You need to specify a tag name."));
- tag = xstrfmt("refs/tags/%s:refs/tags/%s",
- argv[i], argv[i]);
- refspec_append(&rs, tag);
- free(tag);
+ refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s",
+ argv[i], argv[i]);
} else {
refspec_append(&rs, argv[i]);
}
}
+ if (use_stdin_refspecs) {
+ struct strbuf line = STRBUF_INIT;
+ while (strbuf_getline_lf(&line, stdin) != EOF)
+ refspec_append(&rs, line.buf);
+ strbuf_release(&line);
+ }
+
if (server_options.nr)
gtransport->server_options = &server_options;
@@ -1580,22 +1794,31 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
struct remote *remote = NULL;
int result = 0;
int prune_tags_ok = 1;
- struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
packet_trace_identity("fetch");
- fetch_if_missing = 0;
-
/* Record the command line for the reflog */
strbuf_addstr(&default_rla, "fetch");
- for (i = 1; i < argc; i++)
- strbuf_addf(&default_rla, " %s", argv[i]);
+ for (i = 1; i < argc; i++) {
+ /* This handles non-URLs gracefully */
+ char *anon = transport_anonymize_url(argv[i]);
+
+ strbuf_addf(&default_rla, " %s", anon);
+ free(anon);
+ }
- fetch_config_from_gitmodules(&max_children, &recurse_submodules);
git_config(git_fetch_config, NULL);
argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);
+ if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
+ int *sfjc = submodule_fetch_jobs_config == -1
+ ? &submodule_fetch_jobs_config : NULL;
+ int *rs = recurse_submodules == RECURSE_SUBMODULES_DEFAULT
+ ? &recurse_submodules : NULL;
+
+ fetch_config_from_gitmodules(sfjc, rs);
+ }
if (deepen_relative) {
if (deepen_relative < 0)
@@ -1619,8 +1842,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
if (depth || deepen_since || deepen_not.nr)
deepen = 1;
- if (filter_options.choice && !repository_format_partial_clone)
- die("--filter can only be used when extensions.partialClone is set");
+ /* FETCH_HEAD never gets updated in --dry-run mode */
+ if (dry_run)
+ write_fetch_head = 0;
if (all) {
if (argc == 1)
@@ -1653,19 +1877,35 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
if (remote) {
- if (filter_options.choice || repository_format_partial_clone)
+ if (filter_options.choice || has_promisor_remote())
fetch_one_setup_partial(remote);
- result = fetch_one(remote, argc, argv, prune_tags_ok);
+ result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs);
} else {
+ int max_children = max_jobs;
+
if (filter_options.choice)
die(_("--filter can only be used with the remote "
"configured in extensions.partialclone"));
+
+ if (stdin_refspecs)
+ die(_("--stdin can only be used when fetching "
+ "from one remote"));
+
+ if (max_children < 0)
+ max_children = fetch_parallel_config;
+
/* TODO should this also die if we have a previous partial-clone? */
- result = fetch_multiple(&list);
+ result = fetch_multiple(&list, max_children);
}
if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
- struct argv_array options = ARGV_ARRAY_INIT;
+ struct strvec options = STRVEC_INIT;
+ int max_children = max_jobs;
+
+ if (max_children < 0)
+ max_children = submodule_fetch_jobs_config;
+ if (max_children < 0)
+ max_children = fetch_parallel_config;
add_options_to_argv(&options);
result = fetch_populated_submodules(the_repository,
@@ -1675,18 +1915,29 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
recurse_submodules_default,
verbosity < 0,
max_children);
- argv_array_clear(&options);
+ strvec_clear(&options);
}
string_list_clear(&list, 0);
+ prepare_repo_settings(the_repository);
+ if (fetch_write_commit_graph > 0 ||
+ (fetch_write_commit_graph < 0 &&
+ the_repository->settings.fetch_write_commit_graph)) {
+ int commit_graph_flags = COMMIT_GRAPH_WRITE_SPLIT;
+
+ if (progress)
+ commit_graph_flags |= COMMIT_GRAPH_WRITE_PROGRESS;
+
+ write_commit_graph_reachable(the_repository->objects->odb,
+ commit_graph_flags,
+ NULL);
+ }
+
close_object_store(the_repository->objects);
- argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL);
- if (verbosity < 0)
- argv_array_push(&argv_gc_auto, "--quiet");
- run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD);
- argv_array_clear(&argv_gc_auto);
+ if (enable_auto_gc)
+ run_auto_maintenance(verbosity < 0);
return result;
}
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index a4615587fd..48a8699de7 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -1,667 +1,13 @@
#include "builtin.h"
-#include "cache.h"
#include "config.h"
-#include "refs.h"
-#include "object-store.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "tag.h"
-#include "string-list.h"
-#include "branch.h"
#include "fmt-merge-msg.h"
-#include "gpg-interface.h"
-#include "repository.h"
-#include "commit-reach.h"
+#include "parse-options.h"
static const char * const fmt_merge_msg_usage[] = {
N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"),
NULL
};
-static int use_branch_desc;
-
-int fmt_merge_msg_config(const char *key, const char *value, void *cb)
-{
- if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
- int is_bool;
- merge_log_config = git_config_bool_or_int(key, value, &is_bool);
- if (!is_bool && merge_log_config < 0)
- return error("%s: negative length %s", key, value);
- if (is_bool && merge_log_config)
- merge_log_config = DEFAULT_MERGE_LOG_LEN;
- } else if (!strcmp(key, "merge.branchdesc")) {
- use_branch_desc = git_config_bool(key, value);
- } else {
- return git_default_config(key, value, cb);
- }
- return 0;
-}
-
-/* merge data per repository where the merged tips came from */
-struct src_data {
- struct string_list branch, tag, r_branch, generic;
- int head_status;
-};
-
-struct origin_data {
- struct object_id oid;
- unsigned is_local_branch:1;
-};
-
-static void init_src_data(struct src_data *data)
-{
- data->branch.strdup_strings = 1;
- data->tag.strdup_strings = 1;
- data->r_branch.strdup_strings = 1;
- data->generic.strdup_strings = 1;
-}
-
-static struct string_list srcs = STRING_LIST_INIT_DUP;
-static struct string_list origins = STRING_LIST_INIT_DUP;
-
-struct merge_parents {
- int alloc, nr;
- struct merge_parent {
- struct object_id given;
- struct object_id commit;
- unsigned char used;
- } *item;
-};
-
-/*
- * I know, I know, this is inefficient, but you won't be pulling and merging
- * hundreds of heads at a time anyway.
- */
-static struct merge_parent *find_merge_parent(struct merge_parents *table,
- struct object_id *given,
- struct object_id *commit)
-{
- int i;
- for (i = 0; i < table->nr; i++) {
- if (given && !oideq(&table->item[i].given, given))
- continue;
- if (commit && !oideq(&table->item[i].commit, commit))
- continue;
- return &table->item[i];
- }
- return NULL;
-}
-
-static void add_merge_parent(struct merge_parents *table,
- struct object_id *given,
- struct object_id *commit)
-{
- if (table->nr && find_merge_parent(table, given, commit))
- return;
- ALLOC_GROW(table->item, table->nr + 1, table->alloc);
- oidcpy(&table->item[table->nr].given, given);
- oidcpy(&table->item[table->nr].commit, commit);
- table->item[table->nr].used = 0;
- table->nr++;
-}
-
-static int handle_line(char *line, struct merge_parents *merge_parents)
-{
- int i, len = strlen(line);
- struct origin_data *origin_data;
- char *src;
- const char *origin;
- struct src_data *src_data;
- struct string_list_item *item;
- int pulling_head = 0;
- struct object_id oid;
- const unsigned hexsz = the_hash_algo->hexsz;
-
- if (len < hexsz + 3 || line[hexsz] != '\t')
- return 1;
-
- if (starts_with(line + hexsz + 1, "not-for-merge"))
- return 0;
-
- if (line[hexsz + 1] != '\t')
- return 2;
-
- i = get_oid_hex(line, &oid);
- if (i)
- return 3;
-
- if (!find_merge_parent(merge_parents, &oid, NULL))
- return 0; /* subsumed by other parents */
-
- origin_data = xcalloc(1, sizeof(struct origin_data));
- oidcpy(&origin_data->oid, &oid);
-
- if (line[len - 1] == '\n')
- line[len - 1] = 0;
- line += hexsz + 2;
-
- /*
- * At this point, line points at the beginning of comment e.g.
- * "branch 'frotz' of git://that/repository.git".
- * Find the repository name and point it with src.
- */
- src = strstr(line, " of ");
- if (src) {
- *src = 0;
- src += 4;
- pulling_head = 0;
- } else {
- src = line;
- pulling_head = 1;
- }
-
- item = unsorted_string_list_lookup(&srcs, src);
- if (!item) {
- item = string_list_append(&srcs, src);
- item->util = xcalloc(1, sizeof(struct src_data));
- init_src_data(item->util);
- }
- src_data = item->util;
-
- if (pulling_head) {
- origin = src;
- src_data->head_status |= 1;
- } else if (starts_with(line, "branch ")) {
- origin_data->is_local_branch = 1;
- origin = line + 7;
- string_list_append(&src_data->branch, origin);
- src_data->head_status |= 2;
- } else if (starts_with(line, "tag ")) {
- origin = line;
- string_list_append(&src_data->tag, origin + 4);
- src_data->head_status |= 2;
- } else if (skip_prefix(line, "remote-tracking branch ", &origin)) {
- string_list_append(&src_data->r_branch, origin);
- src_data->head_status |= 2;
- } else {
- origin = src;
- string_list_append(&src_data->generic, line);
- src_data->head_status |= 2;
- }
-
- if (!strcmp(".", src) || !strcmp(src, origin)) {
- int len = strlen(origin);
- if (origin[0] == '\'' && origin[len - 1] == '\'')
- origin = xmemdupz(origin + 1, len - 2);
- } else
- origin = xstrfmt("%s of %s", origin, src);
- if (strcmp(".", src))
- origin_data->is_local_branch = 0;
- string_list_append(&origins, origin)->util = origin_data;
- return 0;
-}
-
-static void print_joined(const char *singular, const char *plural,
- struct string_list *list, struct strbuf *out)
-{
- if (list->nr == 0)
- return;
- if (list->nr == 1) {
- strbuf_addf(out, "%s%s", singular, list->items[0].string);
- } else {
- int i;
- strbuf_addstr(out, plural);
- for (i = 0; i < list->nr - 1; i++)
- strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
- list->items[i].string);
- strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
- }
-}
-
-static void add_branch_desc(struct strbuf *out, const char *name)
-{
- struct strbuf desc = STRBUF_INIT;
-
- if (!read_branch_desc(&desc, name)) {
- const char *bp = desc.buf;
- while (*bp) {
- const char *ep = strchrnul(bp, '\n');
- if (*ep)
- ep++;
- strbuf_addf(out, " : %.*s", (int)(ep - bp), bp);
- bp = ep;
- }
- strbuf_complete_line(out);
- }
- strbuf_release(&desc);
-}
-
-#define util_as_integral(elem) ((intptr_t)((elem)->util))
-
-static void record_person_from_buf(int which, struct string_list *people,
- const char *buffer)
-{
- char *name_buf, *name, *name_end;
- struct string_list_item *elem;
- const char *field;
-
- field = (which == 'a') ? "\nauthor " : "\ncommitter ";
- name = strstr(buffer, field);
- if (!name)
- return;
- name += strlen(field);
- name_end = strchrnul(name, '<');
- if (*name_end)
- name_end--;
- while (isspace(*name_end) && name <= name_end)
- name_end--;
- if (name_end < name)
- return;
- name_buf = xmemdupz(name, name_end - name + 1);
-
- elem = string_list_lookup(people, name_buf);
- if (!elem) {
- elem = string_list_insert(people, name_buf);
- elem->util = (void *)0;
- }
- elem->util = (void*)(util_as_integral(elem) + 1);
- 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_;
- return util_as_integral(b) - util_as_integral(a);
-}
-
-static void add_people_count(struct strbuf *out, struct string_list *people)
-{
- if (people->nr == 1)
- strbuf_addstr(out, people->items[0].string);
- else if (people->nr == 2)
- strbuf_addf(out, "%s (%d) and %s (%d)",
- people->items[0].string,
- (int)util_as_integral(&people->items[0]),
- people->items[1].string,
- (int)util_as_integral(&people->items[1]));
- else if (people->nr)
- strbuf_addf(out, "%s (%d) and others",
- people->items[0].string,
- (int)util_as_integral(&people->items[0]));
-}
-
-static void credit_people(struct strbuf *out,
- struct string_list *them,
- int kind)
-{
- const char *label;
- const char *me;
-
- if (kind == 'a') {
- label = "By";
- me = git_author_info(IDENT_NO_DATE);
- } else {
- label = "Via";
- me = git_committer_info(IDENT_NO_DATE);
- }
-
- if (!them->nr ||
- (them->nr == 1 &&
- me &&
- skip_prefix(me, them->items->string, &me) &&
- starts_with(me, " <")))
- return;
- strbuf_addf(out, "\n%c %s ", comment_line_char, label);
- add_people_count(out, them);
-}
-
-static void add_people_info(struct strbuf *out,
- struct string_list *authors,
- struct string_list *committers)
-{
- QSORT(authors->items, authors->nr,
- cmp_string_list_util_as_integral);
- QSORT(committers->items, committers->nr,
- cmp_string_list_util_as_integral);
-
- credit_people(out, authors, 'a');
- credit_people(out, committers, 'c');
-}
-
-static void shortlog(const char *name,
- struct origin_data *origin_data,
- struct commit *head,
- struct rev_info *rev,
- struct fmt_merge_msg_opts *opts,
- struct strbuf *out)
-{
- int i, count = 0;
- struct commit *commit;
- struct object *branch;
- struct string_list subjects = STRING_LIST_INIT_DUP;
- struct string_list authors = STRING_LIST_INIT_DUP;
- struct string_list committers = STRING_LIST_INIT_DUP;
- int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
- struct strbuf sb = STRBUF_INIT;
- const struct object_id *oid = &origin_data->oid;
- int limit = opts->shortlog_len;
-
- branch = deref_tag(the_repository, parse_object(the_repository, oid),
- oid_to_hex(oid),
- the_hash_algo->hexsz);
- if (!branch || branch->type != OBJ_COMMIT)
- return;
-
- setup_revisions(0, NULL, rev, NULL);
- add_pending_object(rev, branch, name);
- add_pending_object(rev, &head->object, "^HEAD");
- head->object.flags |= UNINTERESTING;
- if (prepare_revision_walk(rev))
- die("revision walk setup failed");
- while ((commit = get_revision(rev)) != NULL) {
- struct pretty_print_context ctx = {0};
-
- if (commit->parents && commit->parents->next) {
- /* do not list a merge but count committer */
- if (opts->credit_people)
- record_person('c', &committers, commit);
- continue;
- }
- if (!count && opts->credit_people)
- /* the 'tip' committer */
- record_person('c', &committers, commit);
- if (opts->credit_people)
- record_person('a', &authors, commit);
- count++;
- if (subjects.nr > limit)
- continue;
-
- format_commit_message(commit, "%s", &sb, &ctx);
- strbuf_ltrim(&sb);
-
- if (!sb.len)
- string_list_append(&subjects,
- oid_to_hex(&commit->object.oid));
- else
- string_list_append_nodup(&subjects,
- strbuf_detach(&sb, NULL));
- }
-
- if (opts->credit_people)
- add_people_info(out, &authors, &committers);
- if (count > limit)
- strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
- else
- strbuf_addf(out, "\n* %s:\n", name);
-
- if (origin_data->is_local_branch && use_branch_desc)
- add_branch_desc(out, name);
-
- for (i = 0; i < subjects.nr; i++)
- if (i >= limit)
- strbuf_addstr(out, " ...\n");
- else
- strbuf_addf(out, " %s\n", subjects.items[i].string);
-
- clear_commit_marks((struct commit *)branch, flags);
- clear_commit_marks(head, flags);
- free_commit_list(rev->commits);
- rev->commits = NULL;
- rev->pending.nr = 0;
-
- string_list_clear(&authors, 0);
- string_list_clear(&committers, 0);
- string_list_clear(&subjects, 0);
-}
-
-static void fmt_merge_msg_title(struct strbuf *out,
- const char *current_branch)
-{
- int i = 0;
- char *sep = "";
-
- strbuf_addstr(out, "Merge ");
- for (i = 0; i < srcs.nr; i++) {
- struct src_data *src_data = srcs.items[i].util;
- const char *subsep = "";
-
- strbuf_addstr(out, sep);
- sep = "; ";
-
- if (src_data->head_status == 1) {
- strbuf_addstr(out, srcs.items[i].string);
- continue;
- }
- if (src_data->head_status == 3) {
- subsep = ", ";
- strbuf_addstr(out, "HEAD");
- }
- if (src_data->branch.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("branch ", "branches ", &src_data->branch,
- out);
- }
- if (src_data->r_branch.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("remote-tracking branch ", "remote-tracking branches ",
- &src_data->r_branch, out);
- }
- if (src_data->tag.nr) {
- strbuf_addstr(out, subsep);
- subsep = ", ";
- print_joined("tag ", "tags ", &src_data->tag, out);
- }
- if (src_data->generic.nr) {
- strbuf_addstr(out, subsep);
- print_joined("commit ", "commits ", &src_data->generic,
- out);
- }
- if (strcmp(".", srcs.items[i].string))
- strbuf_addf(out, " of %s", srcs.items[i].string);
- }
-
- if (!strcmp("master", current_branch))
- strbuf_addch(out, '\n');
- else
- strbuf_addf(out, " into %s\n", current_branch);
-}
-
-static void fmt_tag_signature(struct strbuf *tagbuf,
- struct strbuf *sig,
- const char *buf,
- unsigned long len)
-{
- const char *tag_body = strstr(buf, "\n\n");
- if (tag_body) {
- tag_body += 2;
- strbuf_add(tagbuf, tag_body, buf + len - tag_body);
- }
- strbuf_complete_line(tagbuf);
- if (sig->len) {
- strbuf_addch(tagbuf, '\n');
- strbuf_add_commented_lines(tagbuf, sig->buf, sig->len);
- }
-}
-
-static void fmt_merge_msg_sigs(struct strbuf *out)
-{
- int i, tag_number = 0, first_tag = 0;
- struct strbuf tagbuf = STRBUF_INIT;
-
- for (i = 0; i < origins.nr; i++) {
- struct object_id *oid = origins.items[i].util;
- enum object_type type;
- unsigned long size, len;
- char *buf = read_object_file(oid, &type, &size);
- struct strbuf sig = STRBUF_INIT;
-
- if (!buf || type != OBJ_TAG)
- goto next;
- len = parse_signature(buf, size);
-
- if (size == len)
- ; /* merely annotated */
- else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) {
- if (!sig.len)
- strbuf_addstr(&sig, "gpg verification failed.\n");
- }
-
- if (!tag_number++) {
- fmt_tag_signature(&tagbuf, &sig, buf, len);
- first_tag = i;
- } else {
- if (tag_number == 2) {
- struct strbuf tagline = STRBUF_INIT;
- strbuf_addch(&tagline, '\n');
- strbuf_add_commented_lines(&tagline,
- origins.items[first_tag].string,
- strlen(origins.items[first_tag].string));
- strbuf_insert(&tagbuf, 0, tagline.buf,
- tagline.len);
- strbuf_release(&tagline);
- }
- strbuf_addch(&tagbuf, '\n');
- strbuf_add_commented_lines(&tagbuf,
- origins.items[i].string,
- strlen(origins.items[i].string));
- fmt_tag_signature(&tagbuf, &sig, buf, len);
- }
- strbuf_release(&sig);
- next:
- free(buf);
- }
- if (tagbuf.len) {
- strbuf_addch(out, '\n');
- strbuf_addbuf(out, &tagbuf);
- }
- strbuf_release(&tagbuf);
-}
-
-static void find_merge_parents(struct merge_parents *result,
- struct strbuf *in, struct object_id *head)
-{
- struct commit_list *parents;
- struct commit *head_commit;
- int pos = 0, i, j;
-
- parents = NULL;
- while (pos < in->len) {
- int len;
- char *p = in->buf + pos;
- char *newline = strchr(p, '\n');
- const char *q;
- struct object_id oid;
- struct commit *parent;
- struct object *obj;
-
- len = newline ? newline - p : strlen(p);
- pos += len + !!newline;
-
- if (parse_oid_hex(p, &oid, &q) ||
- q[0] != '\t' ||
- q[1] != '\t')
- continue; /* skip not-for-merge */
- /*
- * Do not use get_merge_parent() here; we do not have
- * "name" here and we do not want to contaminate its
- * util field yet.
- */
- obj = parse_object(the_repository, &oid);
- parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
- if (!parent)
- continue;
- commit_list_insert(parent, &parents);
- add_merge_parent(result, &obj->oid, &parent->object.oid);
- }
- head_commit = lookup_commit(the_repository, head);
- if (head_commit)
- commit_list_insert(head_commit, &parents);
- reduce_heads_replace(&parents);
-
- while (parents) {
- struct commit *cmit = pop_commit(&parents);
- for (i = 0; i < result->nr; i++)
- if (oideq(&result->item[i].commit, &cmit->object.oid))
- result->item[i].used = 1;
- }
-
- for (i = j = 0; i < result->nr; i++) {
- if (result->item[i].used) {
- if (i != j)
- result->item[j] = result->item[i];
- j++;
- }
- }
- result->nr = j;
-}
-
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
- struct fmt_merge_msg_opts *opts)
-{
- int i = 0, pos = 0;
- struct object_id head_oid;
- const char *current_branch;
- void *current_branch_to_free;
- struct merge_parents merge_parents;
-
- memset(&merge_parents, 0, sizeof(merge_parents));
-
- /* get current branch */
- current_branch = current_branch_to_free =
- resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
- if (!current_branch)
- die("No current branch");
- if (starts_with(current_branch, "refs/heads/"))
- current_branch += 11;
-
- find_merge_parents(&merge_parents, in, &head_oid);
-
- /* get a line */
- while (pos < in->len) {
- int len;
- char *newline, *p = in->buf + pos;
-
- newline = strchr(p, '\n');
- len = newline ? newline - p : strlen(p);
- pos += len + !!newline;
- i++;
- p[len] = 0;
- if (handle_line(p, &merge_parents))
- die("error in line %d: %.*s", i, len, p);
- }
-
- if (opts->add_title && srcs.nr)
- fmt_merge_msg_title(out, current_branch);
-
- if (origins.nr)
- fmt_merge_msg_sigs(out);
-
- if (opts->shortlog_len) {
- struct commit *head;
- struct rev_info rev;
-
- head = lookup_commit_or_die(&head_oid, "HEAD");
- repo_init_revisions(the_repository, &rev, NULL);
- rev.commit_format = CMIT_FMT_ONELINE;
- rev.ignore_merges = 1;
- rev.limited = 1;
-
- strbuf_complete_line(out);
-
- for (i = 0; i < origins.nr; i++)
- shortlog(origins.items[i].string,
- origins.items[i].util,
- head, &rev, opts, out);
- }
-
- strbuf_complete_line(out);
- free(current_branch_to_free);
- free(merge_parents.item);
- return 0;
-}
-
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
const char *inpath = NULL;
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 465153e853..9d1ecda2b8 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -9,7 +9,7 @@
static char const * const for_each_ref_usage[] = {
N_("git for-each-ref [<options>] [<pattern>]"),
N_("git for-each-ref [--points-at <object>]"),
- N_("git for-each-ref [(--merged | --no-merged) [<commit>]]"),
+ N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
NULL
};
@@ -70,7 +70,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
if (!sorting)
sorting = ref_default_sorting();
- sorting->ignore_case = icase;
+ ref_sorting_icase_all(sorting, icase);
filter.ignore_case = icase;
filter.name_patterns = argv;
diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c
new file mode 100644
index 0000000000..5bba623ff1
--- /dev/null
+++ b/builtin/for-each-repo.c
@@ -0,0 +1,58 @@
+#include "cache.h"
+#include "config.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "string-list.h"
+
+static const char * const for_each_repo_usage[] = {
+ N_("git for-each-repo --config=<config> <command-args>"),
+ NULL
+};
+
+static int run_command_on_repo(const char *path,
+ void *cbdata)
+{
+ int i;
+ struct child_process child = CHILD_PROCESS_INIT;
+ struct strvec *args = (struct strvec *)cbdata;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "-C", path, NULL);
+
+ for (i = 0; i < args->nr; i++)
+ strvec_push(&child.args, args->v[i]);
+
+ return run_command(&child);
+}
+
+int cmd_for_each_repo(int argc, const char **argv, const char *prefix)
+{
+ static const char *config_key = NULL;
+ int i, result = 0;
+ const struct string_list *values;
+ struct strvec args = STRVEC_INIT;
+
+ const struct option options[] = {
+ OPT_STRING(0, "config", &config_key, N_("config"),
+ N_("config key storing a list of repository paths")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, for_each_repo_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (!config_key)
+ die(_("missing --config=<config>"));
+
+ for (i = 0; i < argc; i++)
+ strvec_push(&args, argv[i]);
+
+ values = repo_config_get_value_multi(the_repository,
+ config_key);
+
+ for (i = 0; !result && i < values->nr; i++)
+ result = run_command_on_repo(values->items[i].string, &args);
+
+ return result;
+}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index d26fb0a044..fbf26cafcf 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -49,41 +49,22 @@ static int name_objects;
#define ERROR_PACK 04
#define ERROR_REFS 010
#define ERROR_COMMIT_GRAPH 020
+#define ERROR_MULTI_PACK_INDEX 040
-static const char *describe_object(struct object *obj)
+static const char *describe_object(const struct object_id *oid)
{
- static struct strbuf bufs[] = {
- STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
- };
- static int b = 0;
- struct strbuf *buf;
- char *name = NULL;
-
- if (name_objects)
- name = lookup_decoration(fsck_walk_options.object_names, obj);
-
- buf = bufs + b;
- b = (b + 1) % ARRAY_SIZE(bufs);
- strbuf_reset(buf);
- strbuf_addstr(buf, oid_to_hex(&obj->oid));
- if (name)
- strbuf_addf(buf, " (%s)", name);
-
- return buf->buf;
+ return fsck_describe_object(&fsck_walk_options, oid);
}
-static const char *printable_type(struct object *obj)
+static const char *printable_type(const struct object_id *oid,
+ enum object_type type)
{
const char *ret;
- if (obj->type == OBJ_NONE) {
- enum object_type type = oid_object_info(the_repository,
- &obj->oid, NULL);
- if (type > 0)
- object_as_type(the_repository, obj, type, 0);
- }
+ if (type == OBJ_NONE)
+ type = oid_object_info(the_repository, oid, NULL);
- ret = type_name(obj->type);
+ ret = type_name(type);
if (!ret)
ret = _("unknown");
@@ -118,26 +99,32 @@ static int objerror(struct object *obj, const char *err)
errors_found |= ERROR_OBJECT;
/* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */
fprintf_ln(stderr, _("error in %s %s: %s"),
- printable_type(obj), describe_object(obj), err);
+ printable_type(&obj->oid, obj->type),
+ describe_object(&obj->oid), err);
return -1;
}
static int fsck_error_func(struct fsck_options *o,
- struct object *obj, int type, const char *message)
+ const struct object_id *oid,
+ enum object_type object_type,
+ int msg_type, const char *message)
{
- switch (type) {
+ switch (msg_type) {
case FSCK_WARN:
/* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */
fprintf_ln(stderr, _("warning in %s %s: %s"),
- printable_type(obj), describe_object(obj), message);
+ printable_type(oid, object_type),
+ describe_object(oid), message);
return 0;
case FSCK_ERROR:
/* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */
fprintf_ln(stderr, _("error in %s %s: %s"),
- printable_type(obj), describe_object(obj), message);
+ printable_type(oid, object_type),
+ describe_object(oid), message);
return 1;
default:
- BUG("%d (FSCK_IGNORE?) should never trigger this callback", type);
+ BUG("%d (FSCK_IGNORE?) should never trigger this callback",
+ msg_type);
}
}
@@ -155,7 +142,8 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt
if (!obj) {
/* ... these references to parent->fld are safe here */
printf_ln(_("broken link from %7s %s"),
- printable_type(parent), describe_object(parent));
+ printable_type(&parent->oid, parent->type),
+ describe_object(&parent->oid));
printf_ln(_("broken link from %7s %s"),
(type == OBJ_ANY ? _("unknown") : type_name(type)),
_("unknown"));
@@ -180,13 +168,13 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt
return 0;
if (!(obj->flags & HAS_OBJ)) {
- if (parent && !has_object_file(&obj->oid)) {
+ if (parent && !has_object(the_repository, &obj->oid, 1)) {
printf_ln(_("broken link from %7s %s\n"
" to %7s %s"),
- printable_type(parent),
- describe_object(parent),
- printable_type(obj),
- describe_object(obj));
+ printable_type(&parent->oid, parent->type),
+ describe_object(&parent->oid),
+ printable_type(&obj->oid, obj->type),
+ describe_object(&obj->oid));
errors_found |= ERROR_REACHABLE;
}
return 1;
@@ -238,7 +226,7 @@ static int mark_used(struct object *obj, int type, void *data, struct fsck_optio
static void mark_unreachable_referents(const struct object_id *oid)
{
struct fsck_options options = FSCK_OPTIONS_DEFAULT;
- struct object *obj = lookup_object(the_repository, oid->hash);
+ struct object *obj = lookup_object(the_repository, oid);
if (!obj || !(obj->flags & HAS_OBJ))
return; /* not part of our original set */
@@ -253,7 +241,7 @@ static void mark_unreachable_referents(const struct object_id *oid)
enum object_type type = oid_object_info(the_repository,
&obj->oid, NULL);
if (type > 0)
- object_as_type(the_repository, obj, type, 0);
+ object_as_type(obj, type, 0);
}
options.walk = mark_used;
@@ -292,8 +280,9 @@ static void check_reachable_object(struct object *obj)
return;
if (has_object_pack(&obj->oid))
return; /* it is in pack - forget about it */
- printf_ln(_("missing %s %s"), printable_type(obj),
- describe_object(obj));
+ printf_ln(_("missing %s %s"),
+ printable_type(&obj->oid, obj->type),
+ describe_object(&obj->oid));
errors_found |= ERROR_REACHABLE;
return;
}
@@ -318,8 +307,9 @@ static void check_unreachable_object(struct object *obj)
* since this is something that is prunable.
*/
if (show_unreachable) {
- printf_ln(_("unreachable %s %s"), printable_type(obj),
- describe_object(obj));
+ printf_ln(_("unreachable %s %s"),
+ printable_type(&obj->oid, obj->type),
+ describe_object(&obj->oid));
return;
}
@@ -337,12 +327,13 @@ static void check_unreachable_object(struct object *obj)
*/
if (!(obj->flags & USED)) {
if (show_dangling)
- printf_ln(_("dangling %s %s"), printable_type(obj),
- describe_object(obj));
+ printf_ln(_("dangling %s %s"),
+ printable_type(&obj->oid, obj->type),
+ describe_object(&obj->oid));
if (write_lost_and_found) {
char *filename = git_pathdup("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
- describe_object(obj));
+ describe_object(&obj->oid));
FILE *f;
if (safe_create_leading_directories_const(filename)) {
@@ -355,7 +346,7 @@ static void check_unreachable_object(struct object *obj)
if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1))
die_errno(_("could not write '%s'"), filename);
} else
- fprintf(f, "%s\n", describe_object(obj));
+ fprintf(f, "%s\n", describe_object(&obj->oid));
if (fclose(f))
die_errno(_("could not finish '%s'"),
filename);
@@ -374,7 +365,7 @@ static void check_unreachable_object(struct object *obj)
static void check_object(struct object *obj)
{
if (verbose)
- fprintf_ln(stderr, _("Checking %s"), describe_object(obj));
+ fprintf_ln(stderr, _("Checking %s"), describe_object(&obj->oid));
if (obj->flags & REACHABLE)
check_reachable_object(obj);
@@ -432,7 +423,8 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
if (verbose)
fprintf_ln(stderr, _("Checking %s %s"),
- printable_type(obj), describe_object(obj));
+ printable_type(&obj->oid, obj->type),
+ describe_object(&obj->oid));
if (fsck_walk(obj, NULL, &fsck_obj_options))
objerror(obj, _("broken links"));
@@ -445,7 +437,7 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
if (!commit->parents && show_root)
printf_ln(_("root %s"),
- describe_object(&commit->object));
+ describe_object(&commit->object.oid));
}
if (obj->type == OBJ_TAG) {
@@ -453,10 +445,10 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size)
if (show_tags && tag->tagged) {
printf_ln(_("tagged %s %s (%s) in %s"),
- printable_type(tag->tagged),
- describe_object(tag->tagged),
+ printable_type(&tag->tagged->oid, tag->tagged->type),
+ describe_object(&tag->tagged->oid),
tag->tag,
- describe_object(&tag->object));
+ describe_object(&tag->object.oid));
}
}
@@ -497,12 +489,12 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid,
struct object *obj;
if (!is_null_oid(oid)) {
- obj = lookup_object(the_repository, oid->hash);
+ obj = lookup_object(the_repository, oid);
if (obj && (obj->flags & HAS_OBJ)) {
- if (timestamp && name_objects)
- add_decoration(fsck_walk_options.object_names,
- obj,
- xstrfmt("%s@{%"PRItime"}", refname, timestamp));
+ if (timestamp)
+ fsck_put_object_name(&fsck_walk_options, oid,
+ "%s@{%"PRItime"}",
+ refname, timestamp);
obj->flags |= USED;
mark_object_reachable(obj);
} else if (!is_promisor_object(oid)) {
@@ -566,9 +558,8 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid,
}
default_refs++;
obj->flags |= USED;
- if (name_objects)
- add_decoration(fsck_walk_options.object_names,
- obj, xstrdup(refname));
+ fsck_put_object_name(&fsck_walk_options,
+ oid, "%s", refname);
mark_object_reachable(obj);
return 0;
@@ -586,7 +577,7 @@ static void get_default_heads(void)
for_each_rawref(fsck_handle_ref, NULL);
- worktrees = get_worktrees(0);
+ worktrees = get_worktrees();
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
struct strbuf ref = STRBUF_INIT;
@@ -742,9 +733,7 @@ static int fsck_cache_tree(struct cache_tree *it)
return 1;
}
obj->flags |= USED;
- if (name_objects)
- add_decoration(fsck_walk_options.object_names,
- obj, xstrdup(":"));
+ fsck_put_object_name(&fsck_walk_options, &it->oid, ":");
mark_object_reachable(obj);
if (obj->type != OBJ_TREE)
err |= objerror(obj, _("non-tree in cache-tree"));
@@ -756,7 +745,7 @@ static int fsck_cache_tree(struct cache_tree *it)
static void mark_object_for_connectivity(const struct object_id *oid)
{
- struct object *obj = lookup_unknown_object(oid->hash);
+ struct object *obj = lookup_unknown_object(oid);
obj->flags |= HAS_OBJ;
}
@@ -830,8 +819,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
}
if (name_objects)
- fsck_walk_options.object_names =
- xcalloc(1, sizeof(struct decoration));
+ fsck_enable_object_names(&fsck_walk_options);
git_config(fsck_config, NULL);
@@ -879,7 +867,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
struct object_id oid;
if (!get_oid(arg, &oid)) {
struct object *obj = lookup_object(the_repository,
- oid.hash);
+ &oid);
if (!obj || !(obj->flags & HAS_OBJ)) {
if (is_promisor_object(&oid))
@@ -890,9 +878,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
}
obj->flags |= USED;
- if (name_objects)
- add_decoration(fsck_walk_options.object_names,
- obj, xstrdup(arg));
+ fsck_put_object_name(&fsck_walk_options, &oid,
+ "%s", arg);
mark_object_reachable(obj);
continue;
}
@@ -928,10 +915,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
continue;
obj = &blob->object;
obj->flags |= USED;
- if (name_objects)
- add_decoration(fsck_walk_options.object_names,
- obj,
- xstrfmt(":%s", active_cache[i]->name));
+ fsck_put_object_name(&fsck_walk_options, &obj->oid,
+ ":%s", active_cache[i]->name);
mark_object_reachable(obj);
}
if (active_cache_tree)
@@ -968,7 +953,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
midx_argv[2] = "--object-dir";
midx_argv[3] = odb->path;
if (run_command(&midx_verify))
- errors_found |= ERROR_COMMIT_GRAPH;
+ errors_found |= ERROR_MULTI_PACK_INDEX;
}
}
diff --git a/builtin/gc.c b/builtin/gc.c
index be8e0bfcbe..3e8d76fd5a 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -18,7 +18,7 @@
#include "parse-options.h"
#include "run-command.h"
#include "sigchain.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "commit.h"
#include "commit-graph.h"
#include "packfile.h"
@@ -27,6 +27,11 @@
#include "pack-objects.h"
#include "blob.h"
#include "tree.h"
+#include "promisor-remote.h"
+#include "refs.h"
+#include "remote.h"
+#include "object-store.h"
+#include "exec-cmd.h"
#define FAILED_RUN "failed to run %s"
@@ -41,7 +46,6 @@ static int aggressive_depth = 50;
static int aggressive_window = 250;
static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
-static int gc_write_commit_graph;
static int detach_auto = 1;
static timestamp_t gc_log_expire_time;
static const char *gc_log_expire = "1.day.ago";
@@ -50,12 +54,12 @@ static const char *prune_worktrees_expire = "3.months.ago";
static unsigned long big_pack_threshold;
static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE;
-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 struct strvec pack_refs_cmd = STRVEC_INIT;
+static struct strvec reflog = STRVEC_INIT;
+static struct strvec repack = STRVEC_INIT;
+static struct strvec prune = STRVEC_INIT;
+static struct strvec prune_worktrees = STRVEC_INIT;
+static struct strvec rerere = STRVEC_INIT;
static struct tempfile *pidfile;
static struct lock_file log_lock;
@@ -148,7 +152,6 @@ static void gc_config(void)
git_config_get_int("gc.aggressivedepth", &aggressive_depth);
git_config_get_int("gc.auto", &gc_auto_threshold);
git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
- git_config_get_bool("gc.writecommitgraph", &gc_write_commit_graph);
git_config_get_bool("gc.autodetach", &detach_auto);
git_config_get_expiry("gc.pruneexpire", &prune_expire);
git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
@@ -312,18 +315,18 @@ static uint64_t estimate_repack_memory(struct packed_git *pack)
static int keep_one_pack(struct string_list_item *item, void *data)
{
- argv_array_pushf(&repack, "--keep-pack=%s", basename(item->string));
+ strvec_pushf(&repack, "--keep-pack=%s", basename(item->string));
return 0;
}
static void add_repack_all_option(struct string_list *keep_pack)
{
if (prune_expire && !strcmp(prune_expire, "now"))
- argv_array_push(&repack, "-a");
+ strvec_push(&repack, "-a");
else {
- argv_array_push(&repack, "-A");
+ strvec_push(&repack, "-A");
if (prune_expire)
- argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
+ strvec_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
}
if (keep_pack)
@@ -332,7 +335,7 @@ static void add_repack_all_option(struct string_list *keep_pack)
static void add_repack_incremental_option(void)
{
- argv_array_push(&repack, "--no-write-bitmap-index");
+ strvec_push(&repack, "--no-write-bitmap-index");
}
static int need_to_gc(void)
@@ -459,7 +462,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
/*
* Returns 0 if there was no previous error and gc can proceed, 1 if
* gc should not proceed due to an error in the last run. Prints a
- * message and returns -1 if an error occured while reading gc.log
+ * message and returns -1 if an error occurred while reading gc.log
*/
static int report_last_gc_error(void)
{
@@ -515,11 +518,11 @@ static void gc_before_repack(void)
if (done++)
return;
- if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
- die(FAILED_RUN, pack_refs_cmd.argv[0]);
+ if (pack_refs && run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD))
+ die(FAILED_RUN, pack_refs_cmd.v[0]);
- if (prune_reflogs && run_command_v_opt(reflog.argv, RUN_GIT_CMD))
- die(FAILED_RUN, reflog.argv[0]);
+ if (prune_reflogs && run_command_v_opt(reflog.v, RUN_GIT_CMD))
+ die(FAILED_RUN, reflog.v[0]);
}
int cmd_gc(int argc, const char **argv, const char *prefix)
@@ -531,7 +534,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
const char *name;
pid_t pid;
int daemonized = 0;
- int keep_base_pack = -1;
+ int keep_largest_pack = -1;
timestamp_t dummy;
struct option builtin_gc_options[] = {
@@ -545,7 +548,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
OPT_BOOL_F(0, "force", &force,
N_("force running gc even if there may be another gc running"),
PARSE_OPT_NOCOMPLETE),
- OPT_BOOL(0, "keep-largest-pack", &keep_base_pack,
+ OPT_BOOL(0, "keep-largest-pack", &keep_largest_pack,
N_("repack all other packs except the largest pack")),
OPT_END()
};
@@ -553,12 +556,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_gc_usage, builtin_gc_options);
- 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_worktrees, "worktree", "prune", "--expire", NULL);
- argv_array_pushl(&rerere, "rerere", "gc", NULL);
+ strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
+ strvec_pushl(&reflog, "reflog", "expire", "--all", NULL);
+ strvec_pushl(&repack, "repack", "-d", "-l", NULL);
+ strvec_pushl(&prune, "prune", "--expire", NULL);
+ strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
+ strvec_pushl(&rerere, "rerere", "gc", NULL);
/* default expiry time, overwritten in gc_config */
gc_config();
@@ -577,14 +580,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
die(_("failed to parse prune expiry value %s"), prune_expire);
if (aggressive) {
- argv_array_push(&repack, "-f");
+ strvec_push(&repack, "-f");
if (aggressive_depth > 0)
- argv_array_pushf(&repack, "--depth=%d", aggressive_depth);
+ strvec_pushf(&repack, "--depth=%d", aggressive_depth);
if (aggressive_window > 0)
- argv_array_pushf(&repack, "--window=%d", aggressive_window);
+ strvec_pushf(&repack, "--window=%d", aggressive_window);
}
if (quiet)
- argv_array_push(&repack, "-q");
+ strvec_push(&repack, "-q");
if (auto_gc) {
/*
@@ -602,7 +605,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (detach_auto) {
int ret = report_last_gc_error();
if (ret < 0)
- /* an I/O error occured, already reported */
+ /* an I/O error occurred, already reported */
exit(128);
if (ret == 1)
/* Last gc --auto failed. Skip this one. */
@@ -622,8 +625,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
} else {
struct string_list keep_pack = STRING_LIST_INIT_NODUP;
- if (keep_base_pack != -1) {
- if (keep_base_pack)
+ if (keep_largest_pack != -1) {
+ if (keep_largest_pack)
find_base_packs(&keep_pack, 0);
} else if (big_pack_threshold) {
find_base_packs(&keep_pack, big_pack_threshold);
@@ -654,29 +657,29 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (!repository_format_precious_objects) {
close_object_store(the_repository->objects);
- if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
- die(FAILED_RUN, repack.argv[0]);
+ if (run_command_v_opt(repack.v, RUN_GIT_CMD))
+ die(FAILED_RUN, repack.v[0]);
if (prune_expire) {
- argv_array_push(&prune, prune_expire);
+ strvec_push(&prune, prune_expire);
if (quiet)
- argv_array_push(&prune, "--no-progress");
- if (repository_format_partial_clone)
- argv_array_push(&prune,
- "--exclude-promisor-objects");
- if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
- die(FAILED_RUN, prune.argv[0]);
+ strvec_push(&prune, "--no-progress");
+ if (has_promisor_remote())
+ strvec_push(&prune,
+ "--exclude-promisor-objects");
+ if (run_command_v_opt(prune.v, RUN_GIT_CMD))
+ die(FAILED_RUN, prune.v[0]);
}
}
if (prune_worktrees_expire) {
- argv_array_push(&prune_worktrees, prune_worktrees_expire);
- if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD))
- die(FAILED_RUN, prune_worktrees.argv[0]);
+ strvec_push(&prune_worktrees, prune_worktrees_expire);
+ if (run_command_v_opt(prune_worktrees.v, RUN_GIT_CMD))
+ die(FAILED_RUN, prune_worktrees.v[0]);
}
- if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
- die(FAILED_RUN, rerere.argv[0]);
+ if (run_command_v_opt(rerere.v, RUN_GIT_CMD))
+ die(FAILED_RUN, rerere.v[0]);
report_garbage = report_pack_garbage;
reprepare_packed_git(the_repository);
@@ -685,10 +688,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
clean_pack_garbage();
}
- if (gc_write_commit_graph &&
- write_commit_graph_reachable(get_object_directory(),
- !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0))
- return 1;
+ prepare_repo_settings(the_repository);
+ if (the_repository->settings.gc_write_commit_graph == 1)
+ write_commit_graph_reachable(the_repository->objects->odb,
+ !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0,
+ NULL);
if (auto_gc && too_many_loose_objects())
warning(_("There are too many unreachable loose objects; "
@@ -699,3 +703,939 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
return 0;
}
+
+static const char *const builtin_maintenance_run_usage[] = {
+ N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>] [--schedule]"),
+ NULL
+};
+
+enum schedule_priority {
+ SCHEDULE_NONE = 0,
+ SCHEDULE_WEEKLY = 1,
+ SCHEDULE_DAILY = 2,
+ SCHEDULE_HOURLY = 3,
+};
+
+static enum schedule_priority parse_schedule(const char *value)
+{
+ if (!value)
+ return SCHEDULE_NONE;
+ if (!strcasecmp(value, "hourly"))
+ return SCHEDULE_HOURLY;
+ if (!strcasecmp(value, "daily"))
+ return SCHEDULE_DAILY;
+ if (!strcasecmp(value, "weekly"))
+ return SCHEDULE_WEEKLY;
+ return SCHEDULE_NONE;
+}
+
+static int maintenance_opt_schedule(const struct option *opt, const char *arg,
+ int unset)
+{
+ enum schedule_priority *priority = opt->value;
+
+ if (unset)
+ die(_("--no-schedule is not allowed"));
+
+ *priority = parse_schedule(arg);
+
+ if (!*priority)
+ die(_("unrecognized --schedule argument '%s'"), arg);
+
+ return 0;
+}
+
+struct maintenance_run_opts {
+ int auto_flag;
+ int quiet;
+ enum schedule_priority schedule;
+};
+
+/* Remember to update object flag allocation in object.h */
+#define SEEN (1u<<0)
+
+struct cg_auto_data {
+ int num_not_in_graph;
+ int limit;
+};
+
+static int dfs_on_ref(const char *refname,
+ const struct object_id *oid, int flags,
+ void *cb_data)
+{
+ struct cg_auto_data *data = (struct cg_auto_data *)cb_data;
+ int result = 0;
+ struct object_id peeled;
+ struct commit_list *stack = NULL;
+ struct commit *commit;
+
+ if (!peel_ref(refname, &peeled))
+ oid = &peeled;
+ if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)
+ return 0;
+
+ commit = lookup_commit(the_repository, oid);
+ if (!commit)
+ return 0;
+ if (parse_commit(commit) ||
+ commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
+ return 0;
+
+ data->num_not_in_graph++;
+
+ if (data->num_not_in_graph >= data->limit)
+ return 1;
+
+ commit_list_append(commit, &stack);
+
+ while (!result && stack) {
+ struct commit_list *parent;
+
+ commit = pop_commit(&stack);
+
+ for (parent = commit->parents; parent; parent = parent->next) {
+ if (parse_commit(parent->item) ||
+ commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH ||
+ parent->item->object.flags & SEEN)
+ continue;
+
+ parent->item->object.flags |= SEEN;
+ data->num_not_in_graph++;
+
+ if (data->num_not_in_graph >= data->limit) {
+ result = 1;
+ break;
+ }
+
+ commit_list_append(parent->item, &stack);
+ }
+ }
+
+ free_commit_list(stack);
+ return result;
+}
+
+static int should_write_commit_graph(void)
+{
+ int result;
+ struct cg_auto_data data;
+
+ data.num_not_in_graph = 0;
+ data.limit = 100;
+ git_config_get_int("maintenance.commit-graph.auto",
+ &data.limit);
+
+ if (!data.limit)
+ return 0;
+ if (data.limit < 0)
+ return 1;
+
+ result = for_each_ref(dfs_on_ref, &data);
+
+ repo_clear_commit_marks(the_repository, SEEN);
+
+ return result;
+}
+
+static int run_write_commit_graph(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "commit-graph", "write",
+ "--split", "--reachable", NULL);
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--no-progress");
+
+ return !!run_command(&child);
+}
+
+static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
+{
+ prepare_repo_settings(the_repository);
+ if (!the_repository->settings.core_commit_graph)
+ return 0;
+
+ close_object_store(the_repository->objects);
+ if (run_write_commit_graph(opts)) {
+ error(_("failed to write commit-graph"));
+ return 1;
+ }
+
+ return 0;
+}
+
+static int fetch_remote(const char *remote, struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "fetch", remote, "--prune", "--no-tags",
+ "--no-write-fetch-head", "--recurse-submodules=no",
+ "--refmap=", NULL);
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--quiet");
+
+ strvec_pushf(&child.args, "+refs/heads/*:refs/prefetch/%s/*", remote);
+
+ return !!run_command(&child);
+}
+
+static int append_remote(struct remote *remote, void *cbdata)
+{
+ struct string_list *remotes = (struct string_list *)cbdata;
+
+ string_list_append(remotes, remote->name);
+ return 0;
+}
+
+static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
+{
+ int result = 0;
+ struct string_list_item *item;
+ struct string_list remotes = STRING_LIST_INIT_DUP;
+
+ if (for_each_remote(append_remote, &remotes)) {
+ error(_("failed to fill remotes"));
+ result = 1;
+ goto cleanup;
+ }
+
+ for_each_string_list_item(item, &remotes)
+ result |= fetch_remote(item->string, opts);
+
+cleanup:
+ string_list_clear(&remotes, 0);
+ return result;
+}
+
+static int maintenance_task_gc(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_push(&child.args, "gc");
+
+ if (opts->auto_flag)
+ strvec_push(&child.args, "--auto");
+ if (opts->quiet)
+ strvec_push(&child.args, "--quiet");
+ else
+ strvec_push(&child.args, "--no-quiet");
+
+ close_object_store(the_repository->objects);
+ return run_command(&child);
+}
+
+static int prune_packed(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_push(&child.args, "prune-packed");
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--quiet");
+
+ return !!run_command(&child);
+}
+
+struct write_loose_object_data {
+ FILE *in;
+ int count;
+ int batch_size;
+};
+
+static int loose_object_auto_limit = 100;
+
+static int loose_object_count(const struct object_id *oid,
+ const char *path,
+ void *data)
+{
+ int *count = (int*)data;
+ if (++(*count) >= loose_object_auto_limit)
+ return 1;
+ return 0;
+}
+
+static int loose_object_auto_condition(void)
+{
+ int count = 0;
+
+ git_config_get_int("maintenance.loose-objects.auto",
+ &loose_object_auto_limit);
+
+ if (!loose_object_auto_limit)
+ return 0;
+ if (loose_object_auto_limit < 0)
+ return 1;
+
+ return for_each_loose_file_in_objdir(the_repository->objects->odb->path,
+ loose_object_count,
+ NULL, NULL, &count);
+}
+
+static int bail_on_loose(const struct object_id *oid,
+ const char *path,
+ void *data)
+{
+ return 1;
+}
+
+static int write_loose_object_to_stdin(const struct object_id *oid,
+ const char *path,
+ void *data)
+{
+ struct write_loose_object_data *d = (struct write_loose_object_data *)data;
+
+ fprintf(d->in, "%s\n", oid_to_hex(oid));
+
+ return ++(d->count) > d->batch_size;
+}
+
+static int pack_loose(struct maintenance_run_opts *opts)
+{
+ struct repository *r = the_repository;
+ int result = 0;
+ struct write_loose_object_data data;
+ struct child_process pack_proc = CHILD_PROCESS_INIT;
+
+ /*
+ * Do not start pack-objects process
+ * if there are no loose objects.
+ */
+ if (!for_each_loose_file_in_objdir(r->objects->odb->path,
+ bail_on_loose,
+ NULL, NULL, NULL))
+ return 0;
+
+ pack_proc.git_cmd = 1;
+
+ strvec_push(&pack_proc.args, "pack-objects");
+ if (opts->quiet)
+ strvec_push(&pack_proc.args, "--quiet");
+ strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->odb->path);
+
+ pack_proc.in = -1;
+
+ if (start_command(&pack_proc)) {
+ error(_("failed to start 'git pack-objects' process"));
+ return 1;
+ }
+
+ data.in = xfdopen(pack_proc.in, "w");
+ data.count = 0;
+ data.batch_size = 50000;
+
+ for_each_loose_file_in_objdir(r->objects->odb->path,
+ write_loose_object_to_stdin,
+ NULL,
+ NULL,
+ &data);
+
+ fclose(data.in);
+
+ if (finish_command(&pack_proc)) {
+ error(_("failed to finish 'git pack-objects' process"));
+ result = 1;
+ }
+
+ return result;
+}
+
+static int maintenance_task_loose_objects(struct maintenance_run_opts *opts)
+{
+ return prune_packed(opts) || pack_loose(opts);
+}
+
+static int incremental_repack_auto_condition(void)
+{
+ struct packed_git *p;
+ int enabled;
+ int incremental_repack_auto_limit = 10;
+ int count = 0;
+
+ if (git_config_get_bool("core.multiPackIndex", &enabled) ||
+ !enabled)
+ return 0;
+
+ git_config_get_int("maintenance.incremental-repack.auto",
+ &incremental_repack_auto_limit);
+
+ if (!incremental_repack_auto_limit)
+ return 0;
+ if (incremental_repack_auto_limit < 0)
+ return 1;
+
+ for (p = get_packed_git(the_repository);
+ count < incremental_repack_auto_limit && p;
+ p = p->next) {
+ if (!p->multi_pack_index)
+ count++;
+ }
+
+ return count >= incremental_repack_auto_limit;
+}
+
+static int multi_pack_index_write(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "multi-pack-index", "write", NULL);
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--no-progress");
+
+ if (run_command(&child))
+ return error(_("failed to write multi-pack-index"));
+
+ return 0;
+}
+
+static int multi_pack_index_expire(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "multi-pack-index", "expire", NULL);
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--no-progress");
+
+ close_object_store(the_repository->objects);
+
+ if (run_command(&child))
+ return error(_("'git multi-pack-index expire' failed"));
+
+ return 0;
+}
+
+#define TWO_GIGABYTES (INT32_MAX)
+
+static off_t get_auto_pack_size(void)
+{
+ /*
+ * The "auto" value is special: we optimize for
+ * one large pack-file (i.e. from a clone) and
+ * expect the rest to be small and they can be
+ * repacked quickly.
+ *
+ * The strategy we select here is to select a
+ * size that is one more than the second largest
+ * pack-file. This ensures that we will repack
+ * at least two packs if there are three or more
+ * packs.
+ */
+ off_t max_size = 0;
+ off_t second_largest_size = 0;
+ off_t result_size;
+ struct packed_git *p;
+ struct repository *r = the_repository;
+
+ reprepare_packed_git(r);
+ for (p = get_all_packs(r); p; p = p->next) {
+ if (p->pack_size > max_size) {
+ second_largest_size = max_size;
+ max_size = p->pack_size;
+ } else if (p->pack_size > second_largest_size)
+ second_largest_size = p->pack_size;
+ }
+
+ result_size = second_largest_size + 1;
+
+ /* But limit ourselves to a batch size of 2g */
+ if (result_size > TWO_GIGABYTES)
+ result_size = TWO_GIGABYTES;
+
+ return result_size;
+}
+
+static int multi_pack_index_repack(struct maintenance_run_opts *opts)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "multi-pack-index", "repack", NULL);
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--no-progress");
+
+ strvec_pushf(&child.args, "--batch-size=%"PRIuMAX,
+ (uintmax_t)get_auto_pack_size());
+
+ close_object_store(the_repository->objects);
+
+ if (run_command(&child))
+ return error(_("'git multi-pack-index repack' failed"));
+
+ return 0;
+}
+
+static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts)
+{
+ prepare_repo_settings(the_repository);
+ if (!the_repository->settings.core_multi_pack_index) {
+ warning(_("skipping incremental-repack task because core.multiPackIndex is disabled"));
+ return 0;
+ }
+
+ if (multi_pack_index_write(opts))
+ return 1;
+ if (multi_pack_index_expire(opts))
+ return 1;
+ if (multi_pack_index_repack(opts))
+ return 1;
+ return 0;
+}
+
+typedef int maintenance_task_fn(struct maintenance_run_opts *opts);
+
+/*
+ * An auto condition function returns 1 if the task should run
+ * and 0 if the task should NOT run. See needs_to_gc() for an
+ * example.
+ */
+typedef int maintenance_auto_fn(void);
+
+struct maintenance_task {
+ const char *name;
+ maintenance_task_fn *fn;
+ maintenance_auto_fn *auto_condition;
+ unsigned enabled:1;
+
+ enum schedule_priority schedule;
+
+ /* -1 if not selected. */
+ int selected_order;
+};
+
+enum maintenance_task_label {
+ TASK_PREFETCH,
+ TASK_LOOSE_OBJECTS,
+ TASK_INCREMENTAL_REPACK,
+ TASK_GC,
+ TASK_COMMIT_GRAPH,
+
+ /* Leave as final value */
+ TASK__COUNT
+};
+
+static struct maintenance_task tasks[] = {
+ [TASK_PREFETCH] = {
+ "prefetch",
+ maintenance_task_prefetch,
+ },
+ [TASK_LOOSE_OBJECTS] = {
+ "loose-objects",
+ maintenance_task_loose_objects,
+ loose_object_auto_condition,
+ },
+ [TASK_INCREMENTAL_REPACK] = {
+ "incremental-repack",
+ maintenance_task_incremental_repack,
+ incremental_repack_auto_condition,
+ },
+ [TASK_GC] = {
+ "gc",
+ maintenance_task_gc,
+ need_to_gc,
+ 1,
+ },
+ [TASK_COMMIT_GRAPH] = {
+ "commit-graph",
+ maintenance_task_commit_graph,
+ should_write_commit_graph,
+ },
+};
+
+static int compare_tasks_by_selection(const void *a_, const void *b_)
+{
+ const struct maintenance_task *a = a_;
+ const struct maintenance_task *b = b_;
+
+ return b->selected_order - a->selected_order;
+}
+
+static int maintenance_run_tasks(struct maintenance_run_opts *opts)
+{
+ int i, found_selected = 0;
+ int result = 0;
+ struct lock_file lk;
+ struct repository *r = the_repository;
+ char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path);
+
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ /*
+ * Another maintenance command is running.
+ *
+ * If --auto was provided, then it is likely due to a
+ * recursive process stack. Do not report an error in
+ * that case.
+ */
+ if (!opts->auto_flag && !opts->quiet)
+ warning(_("lock file '%s' exists, skipping maintenance"),
+ lock_path);
+ free(lock_path);
+ return 0;
+ }
+ free(lock_path);
+
+ for (i = 0; !found_selected && i < TASK__COUNT; i++)
+ found_selected = tasks[i].selected_order >= 0;
+
+ if (found_selected)
+ QSORT(tasks, TASK__COUNT, compare_tasks_by_selection);
+
+ for (i = 0; i < TASK__COUNT; i++) {
+ if (found_selected && tasks[i].selected_order < 0)
+ continue;
+
+ if (!found_selected && !tasks[i].enabled)
+ continue;
+
+ if (opts->auto_flag &&
+ (!tasks[i].auto_condition ||
+ !tasks[i].auto_condition()))
+ continue;
+
+ if (opts->schedule && tasks[i].schedule < opts->schedule)
+ continue;
+
+ trace2_region_enter("maintenance", tasks[i].name, r);
+ if (tasks[i].fn(opts)) {
+ error(_("task '%s' failed"), tasks[i].name);
+ result = 1;
+ }
+ trace2_region_leave("maintenance", tasks[i].name, r);
+ }
+
+ rollback_lock_file(&lk);
+ return result;
+}
+
+static void initialize_maintenance_strategy(void)
+{
+ char *config_str;
+
+ if (git_config_get_string("maintenance.strategy", &config_str))
+ return;
+
+ if (!strcasecmp(config_str, "incremental")) {
+ tasks[TASK_GC].schedule = SCHEDULE_NONE;
+ tasks[TASK_COMMIT_GRAPH].enabled = 1;
+ tasks[TASK_COMMIT_GRAPH].schedule = SCHEDULE_HOURLY;
+ tasks[TASK_PREFETCH].enabled = 1;
+ tasks[TASK_PREFETCH].schedule = SCHEDULE_HOURLY;
+ tasks[TASK_INCREMENTAL_REPACK].enabled = 1;
+ tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY;
+ tasks[TASK_LOOSE_OBJECTS].enabled = 1;
+ tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY;
+ }
+}
+
+static void initialize_task_config(int schedule)
+{
+ int i;
+ struct strbuf config_name = STRBUF_INIT;
+ gc_config();
+
+ if (schedule)
+ initialize_maintenance_strategy();
+
+ for (i = 0; i < TASK__COUNT; i++) {
+ int config_value;
+ char *config_str;
+
+ strbuf_reset(&config_name);
+ strbuf_addf(&config_name, "maintenance.%s.enabled",
+ tasks[i].name);
+
+ if (!git_config_get_bool(config_name.buf, &config_value))
+ tasks[i].enabled = config_value;
+
+ strbuf_reset(&config_name);
+ strbuf_addf(&config_name, "maintenance.%s.schedule",
+ tasks[i].name);
+
+ if (!git_config_get_string(config_name.buf, &config_str)) {
+ tasks[i].schedule = parse_schedule(config_str);
+ free(config_str);
+ }
+ }
+
+ strbuf_release(&config_name);
+}
+
+static int task_option_parse(const struct option *opt,
+ const char *arg, int unset)
+{
+ int i, num_selected = 0;
+ struct maintenance_task *task = NULL;
+
+ BUG_ON_OPT_NEG(unset);
+
+ for (i = 0; i < TASK__COUNT; i++) {
+ if (tasks[i].selected_order >= 0)
+ num_selected++;
+ if (!strcasecmp(tasks[i].name, arg)) {
+ task = &tasks[i];
+ }
+ }
+
+ if (!task) {
+ error(_("'%s' is not a valid task"), arg);
+ return 1;
+ }
+
+ if (task->selected_order >= 0) {
+ error(_("task '%s' cannot be selected multiple times"), arg);
+ return 1;
+ }
+
+ task->selected_order = num_selected + 1;
+
+ return 0;
+}
+
+static int maintenance_run(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct maintenance_run_opts opts;
+ struct option builtin_maintenance_run_options[] = {
+ OPT_BOOL(0, "auto", &opts.auto_flag,
+ N_("run tasks based on the state of the repository")),
+ OPT_CALLBACK(0, "schedule", &opts.schedule, N_("frequency"),
+ N_("run tasks based on frequency"),
+ maintenance_opt_schedule),
+ OPT_BOOL(0, "quiet", &opts.quiet,
+ N_("do not report progress or other information over stderr")),
+ OPT_CALLBACK_F(0, "task", NULL, N_("task"),
+ N_("run a specific task"),
+ PARSE_OPT_NONEG, task_option_parse),
+ OPT_END()
+ };
+ memset(&opts, 0, sizeof(opts));
+
+ opts.quiet = !isatty(2);
+
+ for (i = 0; i < TASK__COUNT; i++)
+ tasks[i].selected_order = -1;
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_maintenance_run_options,
+ builtin_maintenance_run_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (opts.auto_flag && opts.schedule)
+ die(_("use at most one of --auto and --schedule=<frequency>"));
+
+ initialize_task_config(opts.schedule);
+
+ if (argc != 0)
+ usage_with_options(builtin_maintenance_run_usage,
+ builtin_maintenance_run_options);
+ return maintenance_run_tasks(&opts);
+}
+
+static int maintenance_register(void)
+{
+ char *config_value;
+ struct child_process config_set = CHILD_PROCESS_INIT;
+ struct child_process config_get = CHILD_PROCESS_INIT;
+
+ /* There is no current repository, so skip registering it */
+ if (!the_repository || !the_repository->gitdir)
+ return 0;
+
+ /* Disable foreground maintenance */
+ git_config_set("maintenance.auto", "false");
+
+ /* Set maintenance strategy, if unset */
+ if (!git_config_get_string("maintenance.strategy", &config_value))
+ free(config_value);
+ else
+ git_config_set("maintenance.strategy", "incremental");
+
+ config_get.git_cmd = 1;
+ strvec_pushl(&config_get.args, "config", "--global", "--get", "maintenance.repo",
+ the_repository->worktree ? the_repository->worktree
+ : the_repository->gitdir,
+ NULL);
+ config_get.out = -1;
+
+ if (start_command(&config_get))
+ return error(_("failed to run 'git config'"));
+
+ /* We already have this value in our config! */
+ if (!finish_command(&config_get))
+ return 0;
+
+ config_set.git_cmd = 1;
+ strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo",
+ the_repository->worktree ? the_repository->worktree
+ : the_repository->gitdir,
+ NULL);
+
+ return run_command(&config_set);
+}
+
+static int maintenance_unregister(void)
+{
+ struct child_process config_unset = CHILD_PROCESS_INIT;
+
+ if (!the_repository || !the_repository->gitdir)
+ return error(_("no current repository to unregister"));
+
+ config_unset.git_cmd = 1;
+ strvec_pushl(&config_unset.args, "config", "--global", "--unset",
+ "maintenance.repo",
+ the_repository->worktree ? the_repository->worktree
+ : the_repository->gitdir,
+ NULL);
+
+ return run_command(&config_unset);
+}
+
+#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
+#define END_LINE "# END GIT MAINTENANCE SCHEDULE"
+
+static int update_background_schedule(int run_maintenance)
+{
+ int result = 0;
+ int in_old_region = 0;
+ struct child_process crontab_list = CHILD_PROCESS_INIT;
+ struct child_process crontab_edit = CHILD_PROCESS_INIT;
+ FILE *cron_list, *cron_in;
+ const char *crontab_name;
+ struct strbuf line = STRBUF_INIT;
+ struct lock_file lk;
+ char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
+
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0)
+ return error(_("another process is scheduling background maintenance"));
+
+ crontab_name = getenv("GIT_TEST_CRONTAB");
+ if (!crontab_name)
+ crontab_name = "crontab";
+
+ strvec_split(&crontab_list.args, crontab_name);
+ strvec_push(&crontab_list.args, "-l");
+ crontab_list.in = -1;
+ crontab_list.out = dup(lk.tempfile->fd);
+ crontab_list.git_cmd = 0;
+
+ if (start_command(&crontab_list)) {
+ result = error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
+ goto cleanup;
+ }
+
+ /* Ignore exit code, as an empty crontab will return error. */
+ finish_command(&crontab_list);
+
+ /*
+ * Read from the .lock file, filtering out the old
+ * schedule while appending the new schedule.
+ */
+ cron_list = fdopen(lk.tempfile->fd, "r");
+ rewind(cron_list);
+
+ strvec_split(&crontab_edit.args, crontab_name);
+ crontab_edit.in = -1;
+ crontab_edit.git_cmd = 0;
+
+ if (start_command(&crontab_edit)) {
+ result = error(_("failed to run 'crontab'; your system might not support 'cron'"));
+ goto cleanup;
+ }
+
+ cron_in = fdopen(crontab_edit.in, "w");
+ if (!cron_in) {
+ result = error(_("failed to open stdin of 'crontab'"));
+ goto done_editing;
+ }
+
+ while (!strbuf_getline_lf(&line, cron_list)) {
+ if (!in_old_region && !strcmp(line.buf, BEGIN_LINE))
+ in_old_region = 1;
+ if (in_old_region)
+ continue;
+ fprintf(cron_in, "%s\n", line.buf);
+ if (in_old_region && !strcmp(line.buf, END_LINE))
+ in_old_region = 0;
+ }
+
+ if (run_maintenance) {
+ struct strbuf line_format = STRBUF_INIT;
+ const char *exec_path = git_exec_path();
+
+ fprintf(cron_in, "%s\n", BEGIN_LINE);
+ fprintf(cron_in,
+ "# The following schedule was created by Git\n");
+ fprintf(cron_in, "# Any edits made in this region might be\n");
+ fprintf(cron_in,
+ "# replaced in the future by a Git command.\n\n");
+
+ strbuf_addf(&line_format,
+ "%%s %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%s\n",
+ exec_path, exec_path);
+ fprintf(cron_in, line_format.buf, "0", "1-23", "*", "hourly");
+ fprintf(cron_in, line_format.buf, "0", "0", "1-6", "daily");
+ fprintf(cron_in, line_format.buf, "0", "0", "0", "weekly");
+ strbuf_release(&line_format);
+
+ fprintf(cron_in, "\n%s\n", END_LINE);
+ }
+
+ fflush(cron_in);
+ fclose(cron_in);
+ close(crontab_edit.in);
+
+done_editing:
+ if (finish_command(&crontab_edit)) {
+ result = error(_("'crontab' died"));
+ goto cleanup;
+ }
+ fclose(cron_list);
+
+cleanup:
+ rollback_lock_file(&lk);
+ return result;
+}
+
+static int maintenance_start(void)
+{
+ if (maintenance_register())
+ warning(_("failed to add repo to global config"));
+
+ return update_background_schedule(1);
+}
+
+static int maintenance_stop(void)
+{
+ return update_background_schedule(0);
+}
+
+static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]");
+
+int cmd_maintenance(int argc, const char **argv, const char *prefix)
+{
+ if (argc < 2 ||
+ (argc == 2 && !strcmp(argv[1], "-h")))
+ usage(builtin_maintenance_usage);
+
+ if (!strcmp(argv[1], "run"))
+ return maintenance_run(argc - 1, argv + 1, prefix);
+ if (!strcmp(argv[1], "start"))
+ return maintenance_start();
+ if (!strcmp(argv[1], "stop"))
+ return maintenance_stop();
+ if (!strcmp(argv[1], "register"))
+ return maintenance_register();
+ if (!strcmp(argv[1], "unregister"))
+ return maintenance_unregister();
+
+ die(_("invalid subcommand: %s"), argv[1]);
+}
diff --git a/builtin/grep.c b/builtin/grep.c
index 580fd38f41..e58e57504c 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -24,6 +24,7 @@
#include "submodule.h"
#include "submodule-config.h"
#include "object-store.h"
+#include "packfile.h"
static char const * const grep_usage[] = {
N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@ -32,7 +33,6 @@ static char const * const grep_usage[] = {
static int recurse_submodules;
-#define GREP_NUM_THREADS_DEFAULT 8
static int num_threads;
static pthread_t *threads;
@@ -91,8 +91,11 @@ static pthread_cond_t cond_result;
static int skip_first_line;
-static void add_work(struct grep_opt *opt, const struct grep_source *gs)
+static void add_work(struct grep_opt *opt, struct grep_source *gs)
{
+ if (opt->binary != GREP_BINARY_TEXT)
+ grep_source_load_driver(gs, opt->repo->index);
+
grep_lock();
while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
@@ -100,9 +103,6 @@ static void add_work(struct grep_opt *opt, const struct grep_source *gs)
}
todo[todo_end].source = *gs;
- if (opt->binary != GREP_BINARY_TEXT)
- grep_source_load_driver(&todo[todo_end].source,
- opt->repo->index);
todo[todo_end].done = 0;
strbuf_reset(&todo[todo_end].out);
todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
@@ -200,12 +200,12 @@ static void start_threads(struct grep_opt *opt)
int i;
pthread_mutex_init(&grep_mutex, NULL);
- pthread_mutex_init(&grep_read_mutex, NULL);
pthread_mutex_init(&grep_attr_mutex, NULL);
pthread_cond_init(&cond_add, NULL);
pthread_cond_init(&cond_write, NULL);
pthread_cond_init(&cond_result, NULL);
grep_use_locks = 1;
+ enable_obj_read_lock();
for (i = 0; i < ARRAY_SIZE(todo); i++) {
strbuf_init(&todo[i].out, 0);
@@ -257,12 +257,12 @@ static int wait_all(void)
free(threads);
pthread_mutex_destroy(&grep_mutex);
- pthread_mutex_destroy(&grep_read_mutex);
pthread_mutex_destroy(&grep_attr_mutex);
pthread_cond_destroy(&cond_add);
pthread_cond_destroy(&cond_write);
pthread_cond_destroy(&cond_result);
grep_use_locks = 0;
+ disable_obj_read_lock();
return hit;
}
@@ -295,14 +295,36 @@ static int grep_cmd_config(const char *var, const char *value, void *cb)
return st;
}
-static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size)
+static void grep_source_name(struct grep_opt *opt, const char *filename,
+ int tree_name_len, struct strbuf *out)
{
- void *data;
+ strbuf_reset(out);
+
+ if (opt->null_following_name) {
+ if (opt->relative && opt->prefix_length) {
+ struct strbuf rel_buf = STRBUF_INIT;
+ const char *rel_name =
+ relative_path(filename + tree_name_len,
+ opt->prefix, &rel_buf);
+
+ if (tree_name_len)
+ strbuf_add(out, filename, tree_name_len);
+
+ strbuf_addstr(out, rel_name);
+ strbuf_release(&rel_buf);
+ } else {
+ strbuf_addstr(out, filename);
+ }
+ return;
+ }
+
+ if (opt->relative && opt->prefix_length)
+ quote_path(filename + tree_name_len, opt->prefix, out, 0);
+ else
+ quote_c_style(filename + tree_name_len, out, NULL, 0);
- grep_read_lock();
- data = read_object_file(oid, type, size);
- grep_read_unlock();
- return data;
+ if (tree_name_len)
+ strbuf_insert(out, 0, filename, tree_name_len);
}
static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
@@ -312,13 +334,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
struct strbuf pathbuf = STRBUF_INIT;
struct grep_source gs;
- if (opt->relative && opt->prefix_length) {
- quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
- strbuf_insert(&pathbuf, 0, filename, tree_name_len);
- } else {
- strbuf_addstr(&pathbuf, filename);
- }
-
+ grep_source_name(opt, filename, tree_name_len, &pathbuf);
grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid);
strbuf_release(&pathbuf);
@@ -344,11 +360,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
struct strbuf buf = STRBUF_INIT;
struct grep_source gs;
- if (opt->relative && opt->prefix_length)
- quote_path_relative(filename, opt->prefix, &buf);
- else
- strbuf_addstr(&buf, filename);
-
+ grep_source_name(opt, filename, 0, &buf);
grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename);
strbuf_release(&buf);
@@ -385,7 +397,7 @@ static void run_pager(struct grep_opt *opt, const char *prefix)
int i, status;
for (i = 0; i < path_list->nr; i++)
- argv_array_push(&child.args, path_list->items[i].string);
+ strvec_push(&child.args, path_list->items[i].string);
child.dir = prefix;
child.use_shell = 1;
@@ -403,34 +415,32 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
static int grep_submodule(struct grep_opt *opt,
const struct pathspec *pathspec,
const struct object_id *oid,
- const char *filename, const char *path)
+ const char *filename, const char *path, int cached)
{
struct repository subrepo;
struct repository *superproject = opt->repo;
- const struct submodule *sub = submodule_from_path(superproject,
- &null_oid, path);
+ const struct submodule *sub;
struct grep_opt subopt;
int hit;
- /*
- * NEEDSWORK: submodules functions need to be protected because they
- * access the object store via config_from_gitmodules(): the latter
- * uses get_oid() which, for now, relies on the global the_repository
- * object.
- */
- grep_read_lock();
+ sub = submodule_from_path(superproject, &null_oid, path);
- if (!is_submodule_active(superproject, path)) {
- grep_read_unlock();
+ if (!is_submodule_active(superproject, path))
return 0;
- }
- if (repo_submodule_init(&subrepo, superproject, sub)) {
- grep_read_unlock();
+ if (repo_submodule_init(&subrepo, superproject, sub))
return 0;
- }
- repo_read_gitmodules(&subrepo);
+ /*
+ * NEEDSWORK: repo_read_gitmodules() might call
+ * add_to_alternates_memory() via config_from_gitmodules(). This
+ * operation causes a race condition with concurrent object readings
+ * performed by the worker threads. That's why we need obj_read_lock()
+ * here. It should be removed once it's no longer necessary to add the
+ * subrepo's odbs to the in-memory alternates list.
+ */
+ obj_read_lock();
+ repo_read_gitmodules(&subrepo, 0);
/*
* NEEDSWORK: This adds the submodule's object directory to the list of
@@ -443,7 +453,7 @@ static int grep_submodule(struct grep_opt *opt,
* object.
*/
add_to_alternates_memory(subrepo.objects->odb->path);
- grep_read_unlock();
+ obj_read_unlock();
memcpy(&subopt, opt, sizeof(subopt));
subopt.repo = &subrepo;
@@ -455,13 +465,12 @@ static int grep_submodule(struct grep_opt *opt,
unsigned long size;
struct strbuf base = STRBUF_INIT;
- object = parse_object_or_die(oid, oid_to_hex(oid));
-
- grep_read_lock();
- data = read_object_with_reference(&object->oid, tree_type,
+ obj_read_lock();
+ object = parse_object_or_die(oid, NULL);
+ obj_read_unlock();
+ data = read_object_with_reference(&subrepo,
+ &object->oid, tree_type,
&size, NULL);
- grep_read_unlock();
-
if (!data)
die(_("unable to read tree (%s)"), oid_to_hex(&object->oid));
@@ -474,7 +483,7 @@ static int grep_submodule(struct grep_opt *opt,
strbuf_release(&base);
free(data);
} else {
- hit = grep_cache(&subopt, pathspec, 1);
+ hit = grep_cache(&subopt, pathspec, cached);
}
repo_clear(&subrepo);
@@ -522,7 +531,8 @@ static int grep_cache(struct grep_opt *opt,
}
} else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
submodule_path_match(repo->index, pathspec, name.buf, NULL)) {
- hit |= grep_submodule(opt, pathspec, NULL, ce->name, ce->name);
+ hit |= grep_submodule(opt, pathspec, NULL, ce->name,
+ ce->name, cached);
} else {
continue;
}
@@ -585,7 +595,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
void *data;
unsigned long size;
- data = lock_and_read_oid_file(&entry.oid, &type, &size);
+ data = read_object_file(&entry.oid, &type, &size);
if (!data)
die(_("unable to read tree (%s)"),
oid_to_hex(&entry.oid));
@@ -597,7 +607,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
free(data);
} else if (recurse_submodules && S_ISGITLINK(entry.mode)) {
hit |= grep_submodule(opt, pathspec, &entry.oid,
- base->buf, base->buf + tn_len);
+ base->buf, base->buf + tn_len,
+ 1); /* ignored */
}
strbuf_setlen(base, old_baselen);
@@ -622,11 +633,9 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
struct strbuf base;
int hit, len;
- grep_read_lock();
- data = read_object_with_reference(&obj->oid, tree_type,
+ data = read_object_with_reference(opt->repo,
+ &obj->oid, tree_type,
&size, NULL);
- grep_read_unlock();
-
if (!data)
die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid));
@@ -655,13 +664,29 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
for (i = 0; i < nr; i++) {
struct object *real_obj;
+
+ obj_read_lock();
real_obj = deref_tag(opt->repo, list->objects[i].item,
NULL, 0);
+ obj_read_unlock();
+
+ if (!real_obj) {
+ char hex[GIT_MAX_HEXSZ + 1];
+ const char *name = list->objects[i].name;
+
+ if (!name) {
+ oid_to_hex_r(hex, &list->objects[i].item->oid);
+ name = hex;
+ }
+ die(_("invalid object '%s' given."), name);
+ }
/* load the gitmodules file for this rev */
if (recurse_submodules) {
submodule_free(opt->repo);
+ obj_read_lock();
gitmodules_config_oid(&real_obj->oid);
+ obj_read_unlock();
}
if (grep_object(opt, pathspec, real_obj, list->objects[i].name,
list->objects[i].path)) {
@@ -679,7 +704,7 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
struct dir_struct dir;
int i, hit = 0;
- memset(&dir, 0, sizeof(dir));
+ dir_init(&dir);
if (!use_index)
dir.flags |= DIR_NO_GITLINKS;
if (exc_std)
@@ -687,12 +712,11 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
fill_directory(&dir, opt->repo->index, pathspec);
for (i = 0; i < dir.nr; i++) {
- if (!dir_path_match(opt->repo->index, dir.entries[i], pathspec, 0, NULL))
- continue;
hit |= grep_file(opt, dir.entries[i]->name);
if (hit && opt->status_only)
break;
}
+ dir_clear(&dir);
return hit;
}
@@ -894,20 +918,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_GROUP(""),
OPT_CALLBACK('f', NULL, &opt, N_("file"),
N_("read patterns from file"), file_callback),
- { OPTION_CALLBACK, 'e', NULL, &opt, N_("pattern"),
- N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback },
- { OPTION_CALLBACK, 0, "and", &opt, NULL,
- N_("combine patterns specified with -e"),
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
+ OPT_CALLBACK_F('e', NULL, &opt, N_("pattern"),
+ N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback),
+ OPT_CALLBACK_F(0, "and", &opt, NULL,
+ N_("combine patterns specified with -e"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback),
OPT_BOOL(0, "or", &dummy, ""),
- { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
- { OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
- open_callback },
- { OPTION_CALLBACK, ')', NULL, &opt, NULL, "",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
- close_callback },
+ OPT_CALLBACK_F(0, "not", &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback),
+ OPT_CALLBACK_F('(', NULL, &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+ open_callback),
+ OPT_CALLBACK_F(')', NULL, &opt, NULL, "",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
+ close_callback),
OPT__QUIET(&opt.status_only,
N_("indicate hit with exit status without output")),
OPT_BOOL(0, "all-match", &opt.all_match,
@@ -954,6 +978,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
/* die the same way as if we did it at the beginning */
setup_git_directory();
}
+ /* Ignore --recurse-submodules if --no-index is given or implied */
+ if (!use_index)
+ recurse_submodules = 0;
/*
* skip a -- separator; we know it cannot be
@@ -1058,7 +1085,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
pathspec.recursive = 1;
pathspec.recurse_submodules = !!recurse_submodules;
- if (list.nr || cached || show_in_pager) {
+ if (recurse_submodules && untracked)
+ die(_("--untracked not supported with --recurse-submodules"));
+
+ if (show_in_pager) {
if (num_threads > 1)
warning(_("invalid option combination, ignoring --threads"));
num_threads = 1;
@@ -1068,7 +1098,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
} else if (num_threads < 0)
die(_("invalid number of threads specified (%d)"), num_threads);
else if (num_threads == 0)
- num_threads = HAVE_THREADS ? GREP_NUM_THREADS_DEFAULT : 1;
+ num_threads = HAVE_THREADS ? online_cpus() : 1;
if (num_threads > 1) {
if (!HAVE_THREADS)
@@ -1077,6 +1107,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
&& (opt.pre_context || opt.post_context ||
opt.file_break || opt.funcbody))
skip_first_line = 1;
+
+ /*
+ * Pre-read gitmodules (if not read already) and force eager
+ * initialization of packed_git to prevent racy lazy
+ * reading/initialization once worker threads are started.
+ */
+ if (recurse_submodules)
+ repo_read_gitmodules(the_repository, 1);
+ if (startup_info->have_repository)
+ (void)get_packed_git(the_repository);
+
start_threads(&opt);
} else {
/*
@@ -1106,14 +1147,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
strbuf_addf(&buf, "+/%s%s",
strcmp("less", pager) ? "" : "*",
opt.pattern_list->pattern);
- string_list_append(&path_list, buf.buf);
- strbuf_detach(&buf, NULL);
+ string_list_append(&path_list,
+ strbuf_detach(&buf, NULL));
}
}
- if (recurse_submodules && (!use_index || untracked))
- die(_("option not supported with --recurse-submodules"));
-
if (!show_in_pager && !opt.status_only)
setup_pager();
@@ -1143,5 +1181,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
run_pager(&opt, prefix);
clear_pathspec(&pathspec);
free_grep_patterns(&opt);
+ grep_destroy();
return !hit;
}
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index e055c11103..640ef4ded5 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -108,7 +108,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
int i;
const char *errstr = NULL;
- argc = parse_options(argc, argv, NULL, hash_object_options,
+ argc = parse_options(argc, argv, prefix, hash_object_options,
hash_object_usage, 0);
if (flags & HASH_WRITE_OBJECT)
diff --git a/builtin/help.c b/builtin/help.c
index e5590d7787..bb339f0fc8 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -8,6 +8,7 @@
#include "parse-options.h"
#include "run-command.h"
#include "column.h"
+#include "config-list.h"
#include "help.h"
#include "alias.h"
@@ -62,6 +63,91 @@ static const char * const builtin_help_usage[] = {
NULL
};
+struct slot_expansion {
+ const char *prefix;
+ const char *placeholder;
+ void (*fn)(struct string_list *list, const char *prefix);
+ int found;
+};
+
+static void list_config_help(int for_human)
+{
+ struct slot_expansion slot_expansions[] = {
+ { "advice", "*", list_config_advices },
+ { "color.branch", "<slot>", list_config_color_branch_slots },
+ { "color.decorate", "<slot>", list_config_color_decorate_slots },
+ { "color.diff", "<slot>", list_config_color_diff_slots },
+ { "color.grep", "<slot>", list_config_color_grep_slots },
+ { "color.interactive", "<slot>", list_config_color_interactive_slots },
+ { "color.remote", "<slot>", list_config_color_sideband_slots },
+ { "color.status", "<slot>", list_config_color_status_slots },
+ { "fsck", "<msg-id>", list_config_fsck_msg_ids },
+ { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids },
+ { NULL, NULL, NULL }
+ };
+ const char **p;
+ struct slot_expansion *e;
+ struct string_list keys = STRING_LIST_INIT_DUP;
+ int i;
+
+ for (p = config_name_list; *p; p++) {
+ const char *var = *p;
+ struct strbuf sb = STRBUF_INIT;
+
+ for (e = slot_expansions; e->prefix; e++) {
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder);
+ if (!strcasecmp(var, sb.buf)) {
+ e->fn(&keys, e->prefix);
+ e->found++;
+ break;
+ }
+ }
+ strbuf_release(&sb);
+ if (!e->prefix)
+ string_list_append(&keys, var);
+ }
+
+ for (e = slot_expansions; e->prefix; e++)
+ if (!e->found)
+ BUG("slot_expansion %s.%s is not used",
+ e->prefix, e->placeholder);
+
+ string_list_sort(&keys);
+ for (i = 0; i < keys.nr; i++) {
+ const char *var = keys.items[i].string;
+ const char *wildcard, *tag, *cut;
+
+ if (for_human) {
+ puts(var);
+ continue;
+ }
+
+ wildcard = strchr(var, '*');
+ tag = strchr(var, '<');
+
+ if (!wildcard && !tag) {
+ puts(var);
+ continue;
+ }
+
+ if (wildcard && !tag)
+ cut = wildcard;
+ else if (!wildcard && tag)
+ cut = tag;
+ else
+ cut = wildcard < tag ? wildcard : tag;
+
+ /*
+ * We may produce duplicates, but that's up to
+ * git-completion.bash to handle
+ */
+ printf("%.*s\n", (int)(cut - var), var);
+ }
+ string_list_clear(&keys, 0);
+}
+
static enum help_format parse_help_format(const char *format)
{
if (!strcmp(format, "man"))
@@ -242,7 +328,7 @@ static int add_man_viewer_cmd(const char *name,
static int add_man_viewer_info(const char *var, const char *value)
{
const char *name, *subkey;
- int namelen;
+ size_t namelen;
if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name)
return 0;
@@ -493,7 +579,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
}
if (show_guides)
- list_common_guides_help();
+ list_guides_help();
if (show_all || show_guides) {
printf("%s\n", _(git_more_info_string));
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 0d55f73b0b..4b8d86e0ad 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -14,7 +14,7 @@
#include "thread-utils.h"
#include "packfile.h"
#include "object-store.h"
-#include "fetch-object.h"
+#include "promisor-remote.h"
static const char index_pack_usage[] =
"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
@@ -33,19 +33,61 @@ struct object_stat {
};
struct base_data {
+ /* Initialized by make_base(). */
struct base_data *base;
- struct base_data *child;
struct object_entry *obj;
- void *data;
- unsigned long size;
int ref_first, ref_last;
int ofs_first, ofs_last;
+ /*
+ * Threads should increment retain_data if they are about to call
+ * patch_delta() using this struct's data as a base, and decrement this
+ * when they are done. While retain_data is nonzero, this struct's data
+ * will not be freed even if the delta base cache limit is exceeded.
+ */
+ int retain_data;
+ /*
+ * The number of direct children that have not been fully processed
+ * (entered work_head, entered done_head, left done_head). When this
+ * number reaches zero, this struct base_data can be freed.
+ */
+ int children_remaining;
+
+ /* Not initialized by make_base(). */
+ struct list_head list;
+ void *data;
+ unsigned long size;
};
+/*
+ * Stack of struct base_data that have unprocessed children.
+ * threaded_second_pass() uses this as a source of work (the other being the
+ * objects array).
+ *
+ * Guarded by work_mutex.
+ */
+static LIST_HEAD(work_head);
+
+/*
+ * Stack of struct base_data that have children, all of whom have been
+ * processed or are being processed, and at least one child is being processed.
+ * These struct base_data must be kept around until the last child is
+ * processed.
+ *
+ * Guarded by work_mutex.
+ */
+static LIST_HEAD(done_head);
+
+/*
+ * All threads share one delta base cache.
+ *
+ * base_cache_used is guarded by work_mutex, and base_cache_limit is read-only
+ * in a thread.
+ */
+static size_t base_cache_used;
+static size_t base_cache_limit;
+
struct thread_local {
pthread_t thread;
- struct base_data *base_cache;
- size_t base_cache_used;
int pack_fd;
};
@@ -117,10 +159,6 @@ static pthread_mutex_t deepest_delta_mutex;
#define deepest_delta_lock() lock_mutex(&deepest_delta_mutex)
#define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex)
-static pthread_mutex_t type_cas_mutex;
-#define type_cas_lock() lock_mutex(&type_cas_mutex)
-#define type_cas_unlock() unlock_mutex(&type_cas_mutex)
-
static pthread_key_t key;
static inline void lock_mutex(pthread_mutex_t *mutex)
@@ -144,7 +182,6 @@ static void init_thread(void)
init_recursive_mutex(&read_mutex);
pthread_mutex_init(&counter_mutex, NULL);
pthread_mutex_init(&work_mutex, NULL);
- pthread_mutex_init(&type_cas_mutex, NULL);
if (show_stat)
pthread_mutex_init(&deepest_delta_mutex, NULL);
pthread_key_create(&key, NULL);
@@ -167,7 +204,6 @@ static void cleanup_thread(void)
pthread_mutex_destroy(&read_mutex);
pthread_mutex_destroy(&counter_mutex);
pthread_mutex_destroy(&work_mutex);
- pthread_mutex_destroy(&type_cas_mutex);
if (show_stat)
pthread_mutex_destroy(&deepest_delta_mutex);
for (i = 0; i < nr_threads; i++)
@@ -364,56 +400,42 @@ static void set_thread_data(struct thread_local *data)
pthread_setspecific(key, data);
}
-static struct base_data *alloc_base_data(void)
-{
- struct base_data *base = xcalloc(1, sizeof(struct base_data));
- base->ref_last = -1;
- base->ofs_last = -1;
- return base;
-}
-
static void free_base_data(struct base_data *c)
{
if (c->data) {
FREE_AND_NULL(c->data);
- get_thread_data()->base_cache_used -= c->size;
+ base_cache_used -= c->size;
}
}
static void prune_base_data(struct base_data *retain)
{
- struct base_data *b;
- struct thread_local *data = get_thread_data();
- for (b = data->base_cache;
- data->base_cache_used > delta_base_cache_limit && b;
- b = b->child) {
- if (b->data && b != retain)
- free_base_data(b);
- }
-}
+ struct list_head *pos;
-static void link_base_data(struct base_data *base, struct base_data *c)
-{
- if (base)
- base->child = c;
- else
- get_thread_data()->base_cache = c;
+ if (base_cache_used <= base_cache_limit)
+ return;
- c->base = base;
- c->child = NULL;
- if (c->data)
- get_thread_data()->base_cache_used += c->size;
- prune_base_data(c);
-}
+ list_for_each_prev(pos, &done_head) {
+ struct base_data *b = list_entry(pos, struct base_data, list);
+ if (b->retain_data || b == retain)
+ continue;
+ if (b->data) {
+ free_base_data(b);
+ if (base_cache_used <= base_cache_limit)
+ return;
+ }
+ }
-static void unlink_base_data(struct base_data *c)
-{
- struct base_data *base = c->base;
- if (base)
- base->child = NULL;
- else
- get_thread_data()->base_cache = NULL;
- free_base_data(c);
+ list_for_each_prev(pos, &work_head) {
+ struct base_data *b = list_entry(pos, struct base_data, list);
+ if (b->retain_data || b == retain)
+ continue;
+ if (b->data) {
+ free_base_data(b);
+ if (base_cache_used <= base_cache_limit)
+ return;
+ }
+ }
}
static int is_delta_type(enum object_type type)
@@ -614,7 +636,7 @@ static int compare_ofs_delta_bases(off_t offset1, off_t offset2,
0;
}
-static int find_ofs_delta(const off_t offset, enum object_type type)
+static int find_ofs_delta(const off_t offset)
{
int first = 0, last = nr_ofs_deltas;
@@ -624,7 +646,8 @@ static int find_ofs_delta(const off_t offset, enum object_type type)
int cmp;
cmp = compare_ofs_delta_bases(offset, delta->offset,
- type, objects[delta->obj_no].type);
+ OBJ_OFS_DELTA,
+ objects[delta->obj_no].type);
if (!cmp)
return next;
if (cmp < 0) {
@@ -637,10 +660,9 @@ static int find_ofs_delta(const off_t offset, 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_index, int *last_index)
{
- int first = find_ofs_delta(offset, type);
+ int first = find_ofs_delta(offset);
int last = first;
int end = nr_ofs_deltas - 1;
@@ -668,7 +690,7 @@ static int compare_ref_delta_bases(const struct object_id *oid1,
return oidcmp(oid1, oid2);
}
-static int find_ref_delta(const struct object_id *oid, enum object_type type)
+static int find_ref_delta(const struct object_id *oid)
{
int first = 0, last = nr_ref_deltas;
@@ -678,7 +700,8 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type)
int cmp;
cmp = compare_ref_delta_bases(oid, &delta->oid,
- type, objects[delta->obj_no].type);
+ OBJ_REF_DELTA,
+ objects[delta->obj_no].type);
if (!cmp)
return next;
if (cmp < 0) {
@@ -691,10 +714,9 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type)
}
static void find_ref_delta_children(const struct object_id *oid,
- int *first_index, int *last_index,
- enum object_type type)
+ int *first_index, int *last_index)
{
- int first = find_ref_delta(oid, type);
+ int first = find_ref_delta(oid);
int last = first;
int end = nr_ref_deltas - 1;
@@ -757,7 +779,8 @@ static int check_collison(struct object_entry *entry)
memset(&data, 0, sizeof(data));
data.entry = entry;
- data.st = open_istream(&entry->idx.oid, &type, &size, NULL);
+ data.st = open_istream(the_repository, &entry->idx.oid, &type, &size,
+ NULL);
if (!data.st)
return -1;
if (size != entry->size || type != entry->type)
@@ -865,26 +888,15 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
}
/*
- * This function is part of find_unresolved_deltas(). There are two
- * walkers going in the opposite ways.
- *
- * The first one in find_unresolved_deltas() traverses down from
- * parent node to children, deflating nodes along the way. However,
- * memory for deflated nodes is limited by delta_base_cache_limit, so
- * at some point parent node's deflated content may be freed.
- *
- * The second walker is this function, which goes from current node up
- * to top parent if necessary to deflate the node. In normal
- * situation, its parent node would be already deflated, so it just
- * needs to apply delta.
- *
- * In the worst case scenario, parent node is no longer deflated because
- * we're running out of delta_base_cache_limit; we need to re-deflate
- * parents, possibly up to the top base.
+ * Ensure that this node has been reconstructed and return its contents.
*
- * All deflated objects here are subject to be freed if we exceed
- * delta_base_cache_limit, just like in find_unresolved_deltas(), we
- * just need to make sure the last node is not freed.
+ * In the typical and best case, this node would already be reconstructed
+ * (through the invocation to resolve_delta() in threaded_second_pass()) and it
+ * would not be pruned. However, if pruning of this node was necessary due to
+ * reaching delta_base_cache_limit, this function will find the closest
+ * ancestor with reconstructed data that has not been pruned (or if there is
+ * none, the ultimate base object), and reconstruct each node in the delta
+ * chain in order to generate the reconstructed data for this node.
*/
static void *get_base_data(struct base_data *c)
{
@@ -901,7 +913,7 @@ static void *get_base_data(struct base_data *c)
if (!delta_nr) {
c->data = get_data_from_pack(obj);
c->size = obj->size;
- get_thread_data()->base_cache_used += c->size;
+ base_cache_used += c->size;
prune_base_data(c);
}
for (; delta_nr > 0; delta_nr--) {
@@ -917,7 +929,7 @@ static void *get_base_data(struct base_data *c)
free(raw);
if (!c->data)
bad_object(obj->idx.offset, _("failed to apply delta"));
- get_thread_data()->base_cache_used += c->size;
+ base_cache_used += c->size;
prune_base_data(c);
}
free(delta);
@@ -925,10 +937,27 @@ static void *get_base_data(struct base_data *c)
return c->data;
}
-static void resolve_delta(struct object_entry *delta_obj,
- struct base_data *base, struct base_data *result)
+static struct base_data *make_base(struct object_entry *obj,
+ struct base_data *parent)
{
- void *base_data, *delta_data;
+ struct base_data *base = xcalloc(1, sizeof(struct base_data));
+ base->base = parent;
+ base->obj = obj;
+ find_ref_delta_children(&obj->idx.oid,
+ &base->ref_first, &base->ref_last);
+ find_ofs_delta_children(obj->idx.offset,
+ &base->ofs_first, &base->ofs_last);
+ base->children_remaining = base->ref_last - base->ref_first +
+ base->ofs_last - base->ofs_first + 2;
+ return base;
+}
+
+static struct base_data *resolve_delta(struct object_entry *delta_obj,
+ struct base_data *base)
+{
+ void *delta_data, *result_data;
+ struct base_data *result;
+ unsigned long result_size;
if (show_stat) {
int i = delta_obj - objects;
@@ -941,113 +970,26 @@ static void resolve_delta(struct object_entry *delta_obj,
obj_stat[i].base_object_no = j;
}
delta_data = get_data_from_pack(delta_obj);
- base_data = get_base_data(base);
- result->obj = delta_obj;
- result->data = patch_delta(base_data, base->size,
- delta_data, delta_obj->size, &result->size);
+ assert(base->data);
+ result_data = patch_delta(base->data, base->size,
+ delta_data, delta_obj->size, &result_size);
free(delta_data);
- if (!result->data)
+ if (!result_data)
bad_object(delta_obj->idx.offset, _("failed to apply delta"));
- hash_object_file(result->data, result->size,
+ hash_object_file(the_hash_algo, result_data, result_size,
type_name(delta_obj->real_type), &delta_obj->idx.oid);
- sha1_object(result->data, NULL, result->size, delta_obj->real_type,
+ sha1_object(result_data, NULL, result_size, delta_obj->real_type,
&delta_obj->idx.oid);
+
+ result = make_base(delta_obj, base);
+ result->data = result_data;
+ result->size = result_size;
+
counter_lock();
nr_resolved_deltas++;
counter_unlock();
-}
-
-/*
- * Standard boolean compare-and-swap: atomically check whether "*type" is
- * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched
- * and return false.
- */
-static int compare_and_swap_type(signed char *type,
- enum object_type want,
- enum object_type set)
-{
- enum object_type old;
-
- type_cas_lock();
- old = *type;
- if (old == want)
- *type = set;
- type_cas_unlock();
- return old == want;
-}
-
-static struct base_data *find_unresolved_deltas_1(struct base_data *base,
- struct base_data *prev_base)
-{
- if (base->ref_last == -1 && base->ofs_last == -1) {
- find_ref_delta_children(&base->obj->idx.oid,
- &base->ref_first, &base->ref_last,
- OBJ_REF_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);
- return NULL;
- }
-
- link_base_data(prev_base, base);
- }
-
- if (base->ref_first <= base->ref_last) {
- 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,
- base->obj->real_type))
- BUG("child->real_type != OBJ_REF_DELTA");
-
- resolve_delta(child, base, result);
- if (base->ref_first == base->ref_last && base->ofs_last == -1)
- free_base_data(base);
-
- base->ref_first++;
- return result;
- }
-
- if (base->ofs_first <= base->ofs_last) {
- struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no;
- struct base_data *result = alloc_base_data();
-
- assert(child->real_type == OBJ_OFS_DELTA);
- child->real_type = base->obj->real_type;
- resolve_delta(child, base, result);
- if (base->ofs_first == base->ofs_last)
- free_base_data(base);
-
- base->ofs_first++;
- return result;
- }
-
- unlink_base_data(base);
- return NULL;
-}
-
-static void find_unresolved_deltas(struct base_data *base)
-{
- struct base_data *new_base, *prev_base = NULL;
- for (;;) {
- new_base = find_unresolved_deltas_1(base, prev_base);
-
- if (new_base) {
- prev_base = base;
- base = new_base;
- } else {
- free(base);
- base = prev_base;
- if (!base)
- return;
- prev_base = base->base;
- }
- }
+ return result;
}
static int compare_ofs_delta_entry(const void *a, const void *b)
@@ -1068,34 +1010,135 @@ static int compare_ref_delta_entry(const void *a, const void *b)
return oidcmp(&delta_a->oid, &delta_b->oid);
}
-static void resolve_base(struct object_entry *obj)
-{
- struct base_data *base_obj = alloc_base_data();
- base_obj->obj = obj;
- base_obj->data = NULL;
- find_unresolved_deltas(base_obj);
-}
-
static void *threaded_second_pass(void *data)
{
- set_thread_data(data);
+ if (data)
+ set_thread_data(data);
for (;;) {
- int i;
+ struct base_data *parent = NULL;
+ struct object_entry *child_obj;
+ struct base_data *child;
+
counter_lock();
display_progress(progress, nr_resolved_deltas);
counter_unlock();
+
work_lock();
- while (nr_dispatched < nr_objects &&
- is_delta_type(objects[nr_dispatched].type))
- nr_dispatched++;
- if (nr_dispatched >= nr_objects) {
- work_unlock();
- break;
+ if (list_empty(&work_head)) {
+ /*
+ * Take an object from the object array.
+ */
+ while (nr_dispatched < nr_objects &&
+ is_delta_type(objects[nr_dispatched].type))
+ nr_dispatched++;
+ if (nr_dispatched >= nr_objects) {
+ work_unlock();
+ break;
+ }
+ child_obj = &objects[nr_dispatched++];
+ } else {
+ /*
+ * Peek at the top of the stack, and take a child from
+ * it.
+ */
+ parent = list_first_entry(&work_head, struct base_data,
+ list);
+
+ if (parent->ref_first <= parent->ref_last) {
+ int offset = ref_deltas[parent->ref_first++].obj_no;
+ child_obj = objects + offset;
+ if (child_obj->real_type != OBJ_REF_DELTA)
+ die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)",
+ (uintmax_t) child_obj->idx.offset,
+ oid_to_hex(&parent->obj->idx.oid));
+ child_obj->real_type = parent->obj->real_type;
+ } else {
+ child_obj = objects +
+ ofs_deltas[parent->ofs_first++].obj_no;
+ assert(child_obj->real_type == OBJ_OFS_DELTA);
+ child_obj->real_type = parent->obj->real_type;
+ }
+
+ if (parent->ref_first > parent->ref_last &&
+ parent->ofs_first > parent->ofs_last) {
+ /*
+ * This parent has run out of children, so move
+ * it to done_head.
+ */
+ list_del(&parent->list);
+ list_add(&parent->list, &done_head);
+ }
+
+ /*
+ * Ensure that the parent has data, since we will need
+ * it later.
+ *
+ * NEEDSWORK: If parent data needs to be reloaded, this
+ * prolongs the time that the current thread spends in
+ * the mutex. A mitigating factor is that parent data
+ * needs to be reloaded only if the delta base cache
+ * limit is exceeded, so in the typical case, this does
+ * not happen.
+ */
+ get_base_data(parent);
+ parent->retain_data++;
}
- i = nr_dispatched++;
work_unlock();
- resolve_base(&objects[i]);
+ if (parent) {
+ child = resolve_delta(child_obj, parent);
+ if (!child->children_remaining)
+ FREE_AND_NULL(child->data);
+ } else {
+ child = make_base(child_obj, NULL);
+ if (child->children_remaining) {
+ /*
+ * Since this child has its own delta children,
+ * we will need this data in the future.
+ * Inflate now so that future iterations will
+ * have access to this object's data while
+ * outside the work mutex.
+ */
+ child->data = get_data_from_pack(child_obj);
+ child->size = child_obj->size;
+ }
+ }
+
+ work_lock();
+ if (parent)
+ parent->retain_data--;
+ if (child->data) {
+ /*
+ * This child has its own children, so add it to
+ * work_head.
+ */
+ list_add(&child->list, &work_head);
+ base_cache_used += child->size;
+ prune_base_data(NULL);
+ } else {
+ /*
+ * This child does not have its own children. It may be
+ * the last descendant of its ancestors; free those
+ * that we can.
+ */
+ struct base_data *p = parent;
+
+ while (p) {
+ struct base_data *next_p;
+
+ p->children_remaining--;
+ if (p->children_remaining)
+ break;
+
+ next_p = p->base;
+ free_base_data(p);
+ list_del(&p->list);
+ free(p);
+
+ p = next_p;
+ }
+ }
+ work_unlock();
}
return NULL;
}
@@ -1196,6 +1239,7 @@ static void resolve_deltas(void)
nr_ref_deltas + nr_ofs_deltas);
nr_dispatched = 0;
+ base_cache_limit = delta_base_cache_limit * nr_threads;
if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) {
init_thread();
for (i = 0; i < nr_threads; i++) {
@@ -1210,15 +1254,7 @@ static void resolve_deltas(void)
cleanup_thread();
return;
}
-
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = &objects[i];
-
- if (is_delta_type(obj->type))
- continue;
- resolve_base(obj);
- display_progress(progress, nr_resolved_deltas);
- }
+ threaded_second_pass(&nothread_data);
}
/*
@@ -1352,7 +1388,7 @@ static void fix_unresolved_deltas(struct hashfile *f)
sorted_by_pos[i] = &ref_deltas[i];
QSORT(sorted_by_pos, nr_ref_deltas, delta_pos_compare);
- if (repository_format_partial_clone) {
+ if (has_promisor_remote()) {
/*
* Prefetch the delta bases.
*/
@@ -1365,30 +1401,36 @@ static void fix_unresolved_deltas(struct hashfile *f)
continue;
oid_array_append(&to_fetch, &d->oid);
}
- if (to_fetch.nr)
- fetch_objects(repository_format_partial_clone,
- to_fetch.oid, to_fetch.nr);
+ promisor_remote_get_direct(the_repository,
+ to_fetch.oid, to_fetch.nr);
oid_array_clear(&to_fetch);
}
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();
+ void *data;
+ unsigned long size;
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- base_obj->data = read_object_file(&d->oid, &type,
- &base_obj->size);
- if (!base_obj->data)
+ data = read_object_file(&d->oid, &type, &size);
+ if (!data)
continue;
- if (check_object_signature(&d->oid, base_obj->data,
- base_obj->size, type_name(type)))
+ if (check_object_signature(the_repository, &d->oid,
+ data, size,
+ type_name(type)))
die(_("local object %s is corrupt"), oid_to_hex(&d->oid));
- base_obj->obj = append_obj_to_pack(f, d->oid.hash,
- base_obj->data, base_obj->size, type);
- find_unresolved_deltas(base_obj);
+
+ /*
+ * Add this as an object to the objects array and call
+ * threaded_second_pass() (which will pick up the added
+ * object).
+ */
+ append_obj_to_pack(f, d->oid.hash, data, size, type);
+ threaded_second_pass(NULL);
+
display_progress(progress, nr_resolved_deltas);
}
free(sorted_by_pos);
@@ -1490,11 +1532,11 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
}
if (!from_stdin) {
- printf("%s\n", sha1_to_hex(hash));
+ printf("%s\n", hash_to_hex(hash));
} else {
struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%s\t%s\n", report, sha1_to_hex(hash));
+ strbuf_addf(&buf, "%s\t%s\n", report, hash_to_hex(hash));
write_or_die(1, buf.buf, buf.len);
strbuf_release(&buf);
@@ -1552,14 +1594,10 @@ static void read_v2_anomalous_offsets(struct packed_git *p,
{
const uint32_t *idx1, *idx2;
uint32_t i;
- const uint32_t hashwords = the_hash_algo->rawsz / sizeof(uint32_t);
/* The address of the 4-byte offset table */
- idx1 = (((const uint32_t *)p->index_data)
- + 2 /* 8-byte header */
- + 256 /* fan out */
- + hashwords * p->num_objects /* object ID table */
- + p->num_objects /* CRC32 table */
+ idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset))
+ + (size_t)p->num_objects /* CRC32 table */
);
/* The address of the 8-byte offset table */
@@ -1668,6 +1706,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
unsigned char pack_hash[GIT_MAX_RAWSZ];
unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */
int report_end_of_input = 0;
+ int hash_algo = 0;
/*
* index-pack never needs to fetch missing objects except when
@@ -1761,6 +1800,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
die(_("bad %s"), arg);
} else if (skip_prefix(arg, "--max-input-size=", &arg)) {
max_input_size = strtoumax(arg, NULL, 10);
+ } else if (skip_prefix(arg, "--object-format=", &arg)) {
+ hash_algo = hash_algo_by_name(arg);
+ if (hash_algo == GIT_HASH_UNKNOWN)
+ die(_("unknown hash algorithm '%s'"), arg);
+ repo_set_hash_algo(the_repository, hash_algo);
} else
usage(index_pack_usage);
continue;
@@ -1777,6 +1821,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
die(_("--fix-thin cannot be used without --stdin"));
if (from_stdin && !startup_info->have_repository)
die(_("--stdin requires a git repository"));
+ if (from_stdin && hash_algo)
+ die(_("--object-format cannot be used with --stdin"));
if (!index_name && pack_name)
index_name = derive_filename(pack_name, "idx", &index_name_buf);
@@ -1791,9 +1837,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (HAVE_THREADS && !nr_threads) {
nr_threads = online_cpus();
- /* An experiment showed that more threads does not mean faster */
- if (nr_threads > 3)
- nr_threads = 3;
+ /*
+ * Experiments show that going above 20 threads doesn't help,
+ * no matter how many cores you have. Below that, we tend to
+ * max at half the number of online_cpus(), presumably because
+ * half of those are hyperthreads rather than full cores. We'll
+ * never reduce the level below "3", though, to match a
+ * historical value that nobody complained about.
+ */
+ if (nr_threads < 4)
+ ; /* too few cores to consider capping */
+ else if (nr_threads < 6)
+ nr_threads = 3; /* historic cap */
+ else if (nr_threads < 40)
+ nr_threads /= 2;
+ else
+ nr_threads = 20; /* hard cap */
}
curr_pack = open_pack_file(pack_name);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 944ec77fe1..01bc648d41 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -9,6 +9,7 @@
#include "builtin.h"
#include "exec-cmd.h"
#include "parse-options.h"
+#include "worktree.h"
#ifndef DEFAULT_GIT_TEMPLATE_DIR
#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
@@ -20,6 +21,8 @@
#define TEST_FILEMODE 1
#endif
+#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH"
+
static int init_is_bare_repository = 0;
static int init_shared_repository = -1;
static const char *init_db_template_dir;
@@ -176,13 +179,34 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
return 1;
}
+void initialize_repository_version(int hash_algo, int reinit)
+{
+ char repo_version_string[10];
+ int repo_version = GIT_REPO_VERSION;
+
+ if (hash_algo != GIT_HASH_SHA1)
+ repo_version = GIT_REPO_VERSION_READ;
+
+ /* This forces creation of new config file */
+ xsnprintf(repo_version_string, sizeof(repo_version_string),
+ "%d", repo_version);
+ git_config_set("core.repositoryformatversion", repo_version_string);
+
+ if (hash_algo != GIT_HASH_SHA1)
+ git_config_set("extensions.objectformat",
+ hash_algos[hash_algo].name);
+ else if (reinit)
+ git_config_set_gently("extensions.objectformat", NULL);
+}
+
static int create_default_files(const char *template_path,
- const char *original_git_dir)
+ const char *original_git_dir,
+ const char *initial_branch,
+ const struct repository_format *fmt)
{
struct stat st1;
struct strbuf buf = STRBUF_INIT;
char *path;
- char repo_version_string[10];
char junk[2];
int reinit;
int filemode;
@@ -233,21 +257,29 @@ static int create_default_files(const char *template_path,
die("failed to set up refs db: %s", err.buf);
/*
- * Create the default symlink from ".git/HEAD" to the "master"
- * branch, if it does not exist yet.
+ * Point the HEAD symref to the initial branch with if HEAD does
+ * not yet exist.
*/
path = git_path_buf(&buf, "HEAD");
reinit = (!access(path, R_OK)
|| readlink(path, junk, sizeof(junk)-1) != -1);
if (!reinit) {
- if (create_symref("HEAD", "refs/heads/master", NULL) < 0)
+ char *ref;
+
+ if (!initial_branch)
+ initial_branch = git_default_branch_name();
+
+ ref = xstrfmt("refs/heads/%s", initial_branch);
+ if (check_refname_format(ref, 0) < 0)
+ die(_("invalid initial branch name: '%s'"),
+ initial_branch);
+
+ if (create_symref("HEAD", ref, NULL) < 0)
exit(1);
+ free(ref);
}
- /* This forces creation of new config file */
- xsnprintf(repo_version_string, sizeof(repo_version_string),
- "%d", GIT_REPO_VERSION);
- git_config_set("core.repositoryformatversion", repo_version_string);
+ initialize_repository_version(fmt->hash_algo, 0);
/* Check filemode trustability */
path = git_path_buf(&buf, "config");
@@ -335,17 +367,40 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
if (rename(src, git_dir))
die_errno(_("unable to move %s to %s"), src, git_dir);
+ repair_worktrees(NULL, NULL);
}
write_file(git_link, "gitdir: %s", git_dir);
}
+static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash)
+{
+ const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT);
+ /*
+ * If we already have an initialized repo, don't allow the user to
+ * specify a different algorithm, as that could cause corruption.
+ * Otherwise, if the user has specified one on the command line, use it.
+ */
+ if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo)
+ die(_("attempt to reinitialize repository with different hash"));
+ else if (hash != GIT_HASH_UNKNOWN)
+ repo_fmt->hash_algo = hash;
+ else if (env) {
+ int env_algo = hash_algo_by_name(env);
+ if (env_algo == GIT_HASH_UNKNOWN)
+ die(_("unknown hash algorithm '%s'"), env);
+ repo_fmt->hash_algo = env_algo;
+ }
+}
+
int init_db(const char *git_dir, const char *real_git_dir,
- const char *template_dir, unsigned int flags)
+ const char *template_dir, int hash, const char *initial_branch,
+ unsigned int flags)
{
int reinit;
int exist_ok = flags & INIT_DB_EXIST_OK;
char *original_git_dir = real_pathdup(git_dir, 1);
+ struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
if (real_git_dir) {
struct stat st;
@@ -356,12 +411,12 @@ int init_db(const char *git_dir, const char *real_git_dir,
if (!exist_ok && !stat(real_git_dir, &st))
die(_("%s already exists"), real_git_dir);
- set_git_dir(real_path(real_git_dir));
+ set_git_dir(real_git_dir, 1);
git_dir = get_git_dir();
separate_git_dir(git_dir, original_git_dir);
}
else {
- set_git_dir(real_path(git_dir));
+ set_git_dir(git_dir, 1);
git_dir = get_git_dir();
}
startup_info->have_repository = 1;
@@ -378,9 +433,15 @@ int init_db(const char *git_dir, const char *real_git_dir,
* config file, so this will not fail. What we are catching
* is an attempt to reinitialize new repository with an old tool.
*/
- check_repository_format();
+ check_repository_format(&repo_fmt);
- reinit = create_default_files(template_dir, original_git_dir);
+ validate_hash_algorithm(&repo_fmt, hash);
+
+ reinit = create_default_files(template_dir, original_git_dir,
+ initial_branch, &repo_fmt);
+ if (reinit && initial_branch)
+ warning(_("re-init: ignored --initial-branch=%s"),
+ initial_branch);
create_object_directory();
@@ -482,6 +543,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
const char *work_tree;
const char *template_dir = NULL;
unsigned int flags = 0;
+ const char *object_format = NULL;
+ const char *initial_branch = NULL;
+ int hash_algo = GIT_HASH_UNKNOWN;
const struct option init_db_options[] = {
OPT_STRING(0, "template", &template_dir, N_("template-directory"),
N_("directory from which templates will be used")),
@@ -494,11 +558,18 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
+ OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
+ N_("override the name of the initial branch")),
+ OPT_STRING(0, "object-format", &object_format, N_("hash"),
+ N_("specify the hash algorithm to use")),
OPT_END()
};
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
+ if (real_git_dir && is_bare_repository_cfg == 1)
+ die(_("--separate-git-dir and --bare are mutually exclusive"));
+
if (real_git_dir && !is_absolute_path(real_git_dir))
real_git_dir = real_pathdup(real_git_dir, 1);
@@ -546,6 +617,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
free(cwd);
}
+ if (object_format) {
+ hash_algo = hash_algo_by_name(object_format);
+ if (hash_algo == GIT_HASH_UNKNOWN)
+ die(_("unknown hash algorithm '%s'"), object_format);
+ }
+
if (init_shared_repository != -1)
set_shared_repository(init_shared_repository);
@@ -567,6 +644,30 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
if (!git_dir)
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+ /*
+ * When --separate-git-dir is used inside a linked worktree, take
+ * care to ensure that the common .git/ directory is relocated, not
+ * the worktree-specific .git/worktrees/<id>/ directory.
+ */
+ if (real_git_dir) {
+ int err;
+ const char *p;
+ struct strbuf sb = STRBUF_INIT;
+
+ p = read_gitfile_gently(git_dir, &err);
+ if (p && get_common_dir(&sb, p)) {
+ struct strbuf mainwt = STRBUF_INIT;
+
+ strbuf_addbuf(&mainwt, &sb);
+ strbuf_strip_suffix(&mainwt, "/.git");
+ if (chdir(mainwt.buf) < 0)
+ die_errno(_("cannot chdir to %s"), mainwt.buf);
+ strbuf_release(&mainwt);
+ git_dir = strbuf_detach(&sb, NULL);
+ }
+ strbuf_release(&sb);
+ }
+
if (is_bare_repository_cfg < 0)
is_bare_repository_cfg = guess_repository_type(git_dir);
@@ -588,6 +689,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
get_git_work_tree());
}
else {
+ if (real_git_dir)
+ die(_("--separate-git-dir incompatible with bare repository"));
if (work_tree)
set_git_work_tree(work_tree);
}
@@ -597,5 +700,6 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
UNLEAK(work_tree);
flags |= INIT_DB_EXIST_OK;
- return init_db(git_dir, real_git_dir, template_dir, flags);
+ return init_db(git_dir, real_git_dir, template_dir, hash_algo,
+ initial_branch, flags);
}
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index f101d092b8..84748eafc0 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
@@ -105,8 +105,8 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")),
OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply config rules")),
OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")),
- { OPTION_CALLBACK, 0, "parse", &opts, NULL, N_("set parsing options"),
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse },
+ OPT_CALLBACK_F(0, "parse", &opts, NULL, N_("set parsing options"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),
OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat --- specially")),
OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"),
N_("trailer(s) to add"), option_parse_trailer),
diff --git a/builtin/log.c b/builtin/log.c
index e43ee12fb1..08204e3196 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -33,10 +33,11 @@
#include "commit-slab.h"
#include "repository.h"
#include "commit-reach.h"
-#include "interdiff.h"
#include "range-diff.h"
#define MAIL_DEFAULT_WRAP 72
+#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
+#define FORMAT_PATCH_NAME_MAX_DEFAULT 64
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
@@ -45,10 +46,12 @@ static int default_abbrev_commit;
static int default_show_root = 1;
static int default_follow;
static int default_show_signature;
+static int default_encode_email_headers = 1;
static int decoration_style;
static int decoration_given;
-static int use_mailmap_config;
+static int use_mailmap_config = 1;
static const char *fmt_patch_subject_prefix = "PATCH";
+static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT;
static const char *fmt_pretty;
static const char * const builtin_log_usage[] = {
@@ -63,9 +66,14 @@ struct line_opt_callback_data {
struct string_list args;
};
+static int session_is_interactive(void)
+{
+ return isatty(1) || pager_in_use();
+}
+
static int auto_decoration_style(void)
{
- return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0;
+ return session_is_interactive() ? DECORATE_SHORT_REFS : 0;
}
static int parse_decoration_style(const char *value)
@@ -144,7 +152,9 @@ static void cmd_log_init_defaults(struct rev_info *rev)
rev->abbrev_commit = default_abbrev_commit;
rev->show_root_diff = default_show_root;
rev->subject_prefix = fmt_patch_subject_prefix;
+ rev->patch_name_max = fmt_patch_name_max;
rev->show_signature = default_show_signature;
+ rev->encode_email_headers = default_encode_email_headers;
rev->diffopt.flags.allow_textconv = 1;
if (default_date_mode)
@@ -155,26 +165,29 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
struct rev_info *rev, struct setup_revision_opt *opt)
{
struct userformat_want w;
- int quiet = 0, source = 0, mailmap = 0;
+ int quiet = 0, source = 0, mailmap;
static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;
+ static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;
static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;
struct decoration_filter decoration_filter = {&decorate_refs_include,
- &decorate_refs_exclude};
+ &decorate_refs_exclude,
+ &decorate_refs_exclude_config};
static struct revision_sources revision_sources;
const struct option builtin_log_options[] = {
OPT__QUIET(&quiet, N_("suppress diff output")),
OPT_BOOL(0, "source", &source, N_("show source")),
OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
+ OPT_ALIAS(0, "mailmap", "use-mailmap"),
OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include,
N_("pattern"), N_("only decorate refs that match <pattern>")),
OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude,
N_("pattern"), N_("do not decorate refs that match <pattern>")),
- { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
- PARSE_OPT_OPTARG, decorate_callback},
- OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
- N_("Process line range n,m in file, counting from 1"),
+ OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"),
+ PARSE_OPT_OPTARG, decorate_callback),
+ OPT_CALLBACK('L', NULL, &line_cb, "range:file",
+ N_("Trace the evolution of line range <start>,<end> or function :<funcname> in <file>"),
log_line_range_callback),
OPT_END()
};
@@ -196,13 +209,16 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
if (argc > 1)
die(_("unrecognized argument: %s"), argv[1]);
+ if (rev->line_level_traverse && rev->prune_data.nr)
+ die(_("-L<range>:<file> cannot be used with pathspec"));
+
memset(&w, 0, sizeof(w));
userformat_find_requirements(NULL, &w);
if (!rev->show_notes_given && (!rev->pretty_given || w.notes))
rev->show_notes = 1;
if (rev->show_notes)
- init_display_notes(&rev->notes_opt);
+ load_display_notes(&rev->notes_opt);
if ((rev->diffopt.pickaxe_opts & DIFF_PICKAXE_KINDS_MASK) ||
rev->diffopt.filter || rev->diffopt.flags.follow_renames)
@@ -230,7 +246,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
}
if (decoration_style) {
+ const struct string_list *config_exclude =
+ repo_config_get_value_multi(the_repository,
+ "log.excludeDecoration");
+
+ if (config_exclude) {
+ struct string_list_item *item;
+ for_each_string_list_item(item, config_exclude)
+ string_list_append(&decorate_refs_exclude_config,
+ item->string);
+ }
+
rev->show_decorations = 1;
+
load_ref_decorations(&decoration_filter, decoration_style);
}
@@ -432,6 +460,14 @@ static int git_log_config(const char *var, const char *value, void *cb)
return git_config_string(&fmt_pretty, var, value);
if (!strcmp(var, "format.subjectprefix"))
return git_config_string(&fmt_patch_subject_prefix, var, value);
+ if (!strcmp(var, "format.filenamemaxlength")) {
+ fmt_patch_name_max = git_config_int(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "format.encodeemailheaders")) {
+ default_encode_email_headers = git_config_bool(var, value);
+ return 0;
+ }
if (!strcmp(var, "log.abbrevcommit")) {
default_abbrev_commit = git_config_bool(var, value);
return 0;
@@ -572,8 +608,8 @@ static int show_tree_object(const struct object_id *oid,
static void show_setup_revisions_tweak(struct rev_info *rev,
struct setup_revision_opt *opt)
{
- if (rev->ignore_merges) {
- /* There was no "-m" on the command line */
+ if (rev->ignore_merges < 0) {
+ /* There was no "-m" variant on the command line */
rev->ignore_merges = 0;
if (!rev->first_parent_only && !rev->combine_merges) {
/* No "--first-parent", "-c", or "--cc" */
@@ -622,6 +658,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
break;
case OBJ_TAG: {
struct tag *t = (struct tag *)o;
+ struct object_id *oid = get_tagged_oid(t);
if (rev.shown_one)
putchar('\n');
@@ -633,10 +670,10 @@ int cmd_show(int argc, const char **argv, const char *prefix)
rev.shown_one = 1;
if (ret)
break;
- o = parse_object(the_repository, &t->tagged->oid);
+ o = parse_object(the_repository, oid);
if (!o)
ret = error(_("could not read object %s"),
- oid_to_hex(&t->tagged->oid));
+ oid_to_hex(oid));
objects[i].item = o;
i--;
break;
@@ -704,8 +741,7 @@ static void log_setup_revisions_tweak(struct rev_info *rev,
if (!rev->diffopt.output_format && rev->combine_merges)
rev->diffopt.output_format = DIFF_FORMAT_PATCH;
- /* Turn -m on when --cc/-c was given */
- if (rev->combine_merges)
+ if (rev->first_parent_only && rev->ignore_merges < 0)
rev->ignore_merges = 0;
}
@@ -759,23 +795,59 @@ static void add_header(const char *value)
item->string[len] = '\0';
}
-#define THREAD_SHALLOW 1
-#define THREAD_DEEP 2
-static int thread;
+enum cover_setting {
+ COVER_UNSET,
+ COVER_OFF,
+ COVER_ON,
+ COVER_AUTO
+};
+
+enum thread_level {
+ THREAD_UNSET,
+ THREAD_SHALLOW,
+ THREAD_DEEP
+};
+
+enum cover_from_description {
+ COVER_FROM_NONE,
+ COVER_FROM_MESSAGE,
+ COVER_FROM_SUBJECT,
+ COVER_FROM_AUTO
+};
+
+enum auto_base_setting {
+ AUTO_BASE_NEVER,
+ AUTO_BASE_ALWAYS,
+ AUTO_BASE_WHEN_ABLE
+};
+
+static enum thread_level thread;
static int do_signoff;
-static int base_auto;
+static enum auto_base_setting auto_base;
static char *from;
static const char *signature = git_version_string;
static const char *signature_file;
-static int config_cover_letter;
+static enum cover_setting config_cover_letter;
static const char *config_output_directory;
+static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE;
+static int show_notes;
+static struct display_notes_opt notes_opt;
-enum {
- COVER_UNSET,
- COVER_OFF,
- COVER_ON,
- COVER_AUTO
-};
+static enum cover_from_description parse_cover_from_description(const char *arg)
+{
+ if (!arg || !strcmp(arg, "default"))
+ return COVER_FROM_MESSAGE;
+ else if (!strcmp(arg, "none"))
+ return COVER_FROM_NONE;
+ else if (!strcmp(arg, "message"))
+ return COVER_FROM_MESSAGE;
+ else if (!strcmp(arg, "subject"))
+ return COVER_FROM_SUBJECT;
+ else if (!strcmp(arg, "auto"))
+ return COVER_FROM_AUTO;
+ else
+ die(_("%s: invalid cover from description mode"), arg);
+}
static int git_format_config(const char *var, const char *value, void *cb)
{
@@ -828,7 +900,7 @@ static int git_format_config(const char *var, const char *value, void *cb)
thread = THREAD_SHALLOW;
return 0;
}
- thread = git_config_bool(var, value) && THREAD_SHALLOW;
+ thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET;
return 0;
}
if (!strcmp(var, "format.signoff")) {
@@ -850,7 +922,11 @@ static int git_format_config(const char *var, const char *value, void *cb)
if (!strcmp(var, "format.outputdirectory"))
return git_config_string(&config_output_directory, var, value);
if (!strcmp(var, "format.useautobase")) {
- base_auto = git_config_bool(var, value);
+ if (value && !strcasecmp(value, "whenAble")) {
+ auto_base = AUTO_BASE_WHEN_ABLE;
+ return 0;
+ }
+ auto_base = git_config_bool(var, value) ? AUTO_BASE_ALWAYS : AUTO_BASE_NEVER;
return 0;
}
if (!strcmp(var, "format.from")) {
@@ -864,6 +940,20 @@ static int git_format_config(const char *var, const char *value, void *cb)
from = NULL;
return 0;
}
+ if (!strcmp(var, "format.notes")) {
+ int b = git_parse_maybe_bool(value);
+ if (b < 0)
+ enable_ref_display_notes(&notes_opt, &show_notes, value);
+ else if (b)
+ enable_default_display_notes(&notes_opt, &show_notes);
+ else
+ disable_display_notes(&notes_opt, &show_notes);
+ return 0;
+ }
+ if (!strcmp(var, "format.coverfromdescription")) {
+ cover_from_description_mode = parse_cover_from_description(value);
+ return 0;
+ }
return git_log_config(var, value, cb);
}
@@ -875,15 +965,9 @@ static int open_next_file(struct commit *commit, const char *subject,
struct rev_info *rev, int quiet)
{
struct strbuf filename = STRBUF_INIT;
- int suffix_len = strlen(rev->patch_suffix) + 1;
if (output_directory) {
strbuf_addstr(&filename, output_directory);
- if (filename.len >=
- PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) {
- strbuf_release(&filename);
- return error(_("name of output directory is too long"));
- }
strbuf_complete(&filename, '/');
}
@@ -970,20 +1054,6 @@ static void print_signature(FILE *file)
putc('\n', file);
}
-static void add_branch_description(struct strbuf *buf, const char *branch_name)
-{
- struct strbuf desc = STRBUF_INIT;
- if (!branch_name || !*branch_name)
- return;
- read_branch_desc(&desc, branch_name);
- if (desc.len) {
- strbuf_addch(buf, '\n');
- strbuf_addbuf(buf, &desc);
- strbuf_addch(buf, '\n');
- }
- strbuf_release(&desc);
-}
-
static char *find_branch_name(struct rev_info *rev)
{
int i, positive = -1;
@@ -1004,7 +1074,7 @@ static char *find_branch_name(struct rev_info *rev)
return NULL;
ref = rev->cmdline.rev[positive].name;
tip_oid = &rev->cmdline.rev[positive].item->oid;
- if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) &&
+ if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref, 0) &&
skip_prefix(full_ref, "refs/heads/", &v) &&
oideq(tip_oid, &branch_oid))
branch = xstrdup(v);
@@ -1030,15 +1100,70 @@ static void show_diffstat(struct rev_info *rev,
fprintf(rev->diffopt.file, "\n");
}
-static void make_cover_letter(struct rev_info *rev, int use_stdout,
+static void prepare_cover_text(struct pretty_print_context *pp,
+ const char *branch_name,
+ struct strbuf *sb,
+ const char *encoding,
+ int need_8bit_cte)
+{
+ const char *subject = "*** SUBJECT HERE ***";
+ const char *body = "*** BLURB HERE ***";
+ struct strbuf description_sb = STRBUF_INIT;
+ struct strbuf subject_sb = STRBUF_INIT;
+
+ if (cover_from_description_mode == COVER_FROM_NONE)
+ goto do_pp;
+
+ if (branch_name && *branch_name)
+ read_branch_desc(&description_sb, branch_name);
+ if (!description_sb.len)
+ goto do_pp;
+
+ if (cover_from_description_mode == COVER_FROM_SUBJECT ||
+ cover_from_description_mode == COVER_FROM_AUTO)
+ body = format_subject(&subject_sb, description_sb.buf, " ");
+
+ if (cover_from_description_mode == COVER_FROM_MESSAGE ||
+ (cover_from_description_mode == COVER_FROM_AUTO &&
+ subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN))
+ body = description_sb.buf;
+ else
+ subject = subject_sb.buf;
+
+do_pp:
+ pp_title_line(pp, &subject, sb, encoding, need_8bit_cte);
+ pp_remainder(pp, &body, sb, 0);
+
+ strbuf_release(&description_sb);
+ strbuf_release(&subject_sb);
+}
+
+static int get_notes_refs(struct string_list_item *item, void *arg)
+{
+ strvec_pushf(arg, "--notes=%s", item->string);
+ return 0;
+}
+
+static void get_notes_args(struct strvec *arg, struct rev_info *rev)
+{
+ if (!rev->show_notes) {
+ strvec_push(arg, "--no-notes");
+ } else if (rev->notes_opt.use_default_notes > 0 ||
+ (rev->notes_opt.use_default_notes == -1 &&
+ !rev->notes_opt.extra_notes_refs.nr)) {
+ strvec_push(arg, "--notes");
+ } else {
+ for_each_string_list(&rev->notes_opt.extra_notes_refs, get_notes_refs, arg);
+ }
+}
+
+static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *branch_name,
int quiet)
{
const char *committer;
- const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
- const char *msg;
struct shortlog log;
struct strbuf sb = STRBUF_INIT;
int i;
@@ -1052,7 +1177,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
committer = git_committer_info(0);
- if (!use_stdout &&
+ if (use_separate_file &&
open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
die(_("failed to create cover-letter file"));
@@ -1068,15 +1193,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
if (!branch_name)
branch_name = find_branch_name(rev);
- msg = body;
pp.fmt = CMIT_FMT_EMAIL;
pp.date_mode.type = DATE_RFC2822;
pp.rev = rev;
pp.print_email_subject = 1;
pp_user_info(&pp, NULL, &sb, committer, encoding);
- pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
- pp_remainder(&pp, &msg, &sb, 0);
- add_branch_description(&sb, branch_name);
+ prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte);
fprintf(rev->diffopt.file, "%s\n", sb.buf);
strbuf_release(&sb);
@@ -1087,6 +1209,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
log.in1 = 2;
log.in2 = 4;
log.file = rev->diffopt.file;
+ log.groups = SHORTLOG_GROUP_AUTHOR;
for (i = 0; i < nr; i++)
shortlog_add_commit(&log, list[i]);
@@ -1098,7 +1221,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
if (rev->idiff_oid1) {
fprintf_ln(rev->diffopt.file, "%s", rev->idiff_title);
- show_interdiff(rev, 0);
+ show_interdiff(rev->idiff_oid1, rev->idiff_oid2, 0,
+ &rev->diffopt);
}
if (rev->rdiff1) {
@@ -1107,13 +1231,16 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
* can be added later if deemed desirable.
*/
struct diff_options opts;
+ struct strvec other_arg = STRVEC_INIT;
diff_setup(&opts);
opts.file = rev->diffopt.file;
opts.use_color = rev->diffopt.use_color;
diff_setup_done(&opts);
fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title);
+ get_notes_args(&other_arg, rev);
show_range_diff(rev->rdiff1, rev->rdiff2,
- rev->creation_factor, 1, &opts);
+ rev->creation_factor, 1, &opts, &other_arg);
+ strvec_clear(&other_arg);
}
}
@@ -1225,9 +1352,9 @@ static int output_directory_callback(const struct option *opt, const char *arg,
static int thread_callback(const struct option *opt, const char *arg, int unset)
{
- int *thread = (int *)opt->value;
+ enum thread_level *thread = (enum thread_level *)opt->value;
if (unset)
- *thread = 0;
+ *thread = THREAD_UNSET;
else if (!arg || !strcmp(arg, "shallow"))
*thread = THREAD_SHALLOW;
else if (!strcmp(arg, "deep"))
@@ -1274,7 +1401,7 @@ static int header_callback(const struct option *opt, const char *arg, int unset)
string_list_clear(&extra_to, 0);
string_list_clear(&extra_cc, 0);
} else {
- add_header(arg);
+ add_header(arg);
}
return 0;
}
@@ -1312,6 +1439,23 @@ static int from_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int base_callback(const struct option *opt, const char *arg, int unset)
+{
+ const char **base_commit = opt->value;
+
+ if (unset) {
+ auto_base = AUTO_BASE_NEVER;
+ *base_commit = NULL;
+ } else if (!strcmp(arg, "auto")) {
+ auto_base = AUTO_BASE_ALWAYS;
+ *base_commit = NULL;
+ } else {
+ auto_base = AUTO_BASE_NEVER;
+ *base_commit = arg;
+ }
+ return 0;
+}
+
struct base_tree_info {
struct object_id base_commit;
int nr_patch_id, alloc_patch_id;
@@ -1324,13 +1468,36 @@ static struct commit *get_base_commit(const char *base_commit,
{
struct commit *base = NULL;
struct commit **rev;
- int i = 0, rev_nr = 0;
+ int i = 0, rev_nr = 0, auto_select, die_on_failure;
- if (base_commit && strcmp(base_commit, "auto")) {
+ switch (auto_base) {
+ case AUTO_BASE_NEVER:
+ if (base_commit) {
+ auto_select = 0;
+ die_on_failure = 1;
+ } else {
+ /* no base information is requested */
+ return NULL;
+ }
+ break;
+ case AUTO_BASE_ALWAYS:
+ case AUTO_BASE_WHEN_ABLE:
+ if (base_commit) {
+ BUG("requested automatic base selection but a commit was provided");
+ } else {
+ auto_select = 1;
+ die_on_failure = auto_base == AUTO_BASE_ALWAYS;
+ }
+ break;
+ default:
+ BUG("unexpected automatic base selection method");
+ }
+
+ if (!auto_select) {
base = lookup_commit_reference_by_name(base_commit);
if (!base)
die(_("unknown commit %s"), base_commit);
- } else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) {
+ } else {
struct branch *curr_branch = branch_get(NULL);
const char *upstream = branch_get_upstream(curr_branch, NULL);
if (upstream) {
@@ -1338,19 +1505,32 @@ static struct commit *get_base_commit(const char *base_commit,
struct commit *commit;
struct object_id oid;
- if (get_oid(upstream, &oid))
- die(_("failed to resolve '%s' as a valid ref"), upstream);
+ if (get_oid(upstream, &oid)) {
+ if (die_on_failure)
+ die(_("failed to resolve '%s' as a valid ref"), upstream);
+ else
+ return NULL;
+ }
commit = lookup_commit_or_die(&oid, "upstream base");
base_list = get_merge_bases_many(commit, total, list);
/* There should be one and only one merge base. */
- if (!base_list || base_list->next)
- die(_("could not find exact merge base"));
+ if (!base_list || base_list->next) {
+ if (die_on_failure) {
+ die(_("could not find exact merge base"));
+ } else {
+ free_commit_list(base_list);
+ return NULL;
+ }
+ }
base = base_list->item;
free_commit_list(base_list);
} else {
- die(_("failed to get upstream, if you want to record base commit automatically,\n"
- "please use git branch --set-upstream-to to track a remote branch.\n"
- "Or you could specify base commit by --base=<base-commit-id> manually"));
+ if (die_on_failure)
+ die(_("failed to get upstream, if you want to record base commit automatically,\n"
+ "please use git branch --set-upstream-to to track a remote branch.\n"
+ "Or you could specify base commit by --base=<base-commit-id> manually"));
+ else
+ return NULL;
}
}
@@ -1367,8 +1547,14 @@ static struct commit *get_base_commit(const char *base_commit,
for (i = 0; i < rev_nr / 2; i++) {
struct commit_list *merge_base;
merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]);
- if (!merge_base || merge_base->next)
- die(_("failed to find exact merge base"));
+ if (!merge_base || merge_base->next) {
+ if (die_on_failure) {
+ die(_("failed to find exact merge base"));
+ } else {
+ free(rev);
+ return NULL;
+ }
+ }
rev[i] = merge_base->item;
}
@@ -1378,12 +1564,24 @@ static struct commit *get_base_commit(const char *base_commit,
rev_nr = DIV_ROUND_UP(rev_nr, 2);
}
- if (!in_merge_bases(base, rev[0]))
- die(_("base commit should be the ancestor of revision list"));
+ if (!in_merge_bases(base, rev[0])) {
+ if (die_on_failure) {
+ die(_("base commit should be the ancestor of revision list"));
+ } else {
+ free(rev);
+ return NULL;
+ }
+ }
for (i = 0; i < total; i++) {
- if (base == list[i])
- die(_("base commit shouldn't be in revision list"));
+ if (base == list[i]) {
+ if (die_on_failure) {
+ die(_("base commit shouldn't be in revision list"));
+ } else {
+ free(rev);
+ return NULL;
+ }
+ }
}
free(rev);
@@ -1435,7 +1633,7 @@ static void prepare_bases(struct base_tree_info *bases,
struct object_id *patch_id;
if (*commit_base_at(&commit_base, commit))
continue;
- if (commit_patch_id(commit, &diffopt, &oid, 0))
+ if (commit_patch_id(commit, &diffopt, &oid, 0, 1))
die(_("cannot get patch id"));
ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id);
patch_id = bases->patch_id + bases->nr_patch_id;
@@ -1483,16 +1681,20 @@ static void infer_range_diff_ranges(struct strbuf *r1,
struct commit *head)
{
const char *head_oid = oid_to_hex(&head->object.oid);
+ int prev_is_range = !!strstr(prev, "..");
- if (!strstr(prev, "..")) {
+ if (prev_is_range)
+ strbuf_addstr(r1, prev);
+ else
strbuf_addf(r1, "%s..%s", head_oid, prev);
+
+ if (origin)
+ strbuf_addf(r2, "%s..%s", oid_to_hex(&origin->object.oid), head_oid);
+ else if (prev_is_range)
+ die(_("failed to infer range-diff origin of current series"));
+ else {
+ warning(_("using '%s' as range-diff origin of current series"), prev);
strbuf_addf(r2, "%s..%s", prev, head_oid);
- } else if (!origin) {
- die(_("failed to infer range-diff ranges"));
- } else {
- strbuf_addstr(r1, prev);
- strbuf_addf(r2, "%s..%s",
- oid_to_hex(&origin->object.oid), head_oid);
}
}
@@ -1518,9 +1720,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int use_patch_format = 0;
int quiet = 0;
int reroll_count = -1;
+ char *cover_from_description_arg = NULL;
char *branch_name = NULL;
char *base_commit = NULL;
struct base_tree_info bases;
+ struct commit *base;
int show_progress = 0;
struct progress *progress = NULL;
struct oid_array idiff_prev = OID_ARRAY_INIT;
@@ -1532,13 +1736,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int creation_factor = -1;
const struct option builtin_format_patch_options[] = {
- { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
+ OPT_CALLBACK_F('n', "numbered", &numbered, NULL,
N_("use [PATCH n/m] even with a single patch"),
- PARSE_OPT_NOARG, numbered_callback },
- { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
+ PARSE_OPT_NOARG, numbered_callback),
+ OPT_CALLBACK_F('N', "no-numbered", &numbered, NULL,
N_("use [PATCH] even with multiple patches"),
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback },
- OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback),
+ OPT_BOOL('s', "signoff", &do_signoff, N_("add a Signed-off-by trailer")),
OPT_BOOL(0, "stdout", &use_stdout,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
@@ -1551,18 +1755,23 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
N_("start numbering patches at <n> instead of 1")),
OPT_INTEGER('v', "reroll-count", &reroll_count,
N_("mark the series as Nth re-roll")),
- { OPTION_CALLBACK, 0, "rfc", &rev, NULL,
+ OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max,
+ N_("max length of output filename")),
+ OPT_CALLBACK_F(0, "rfc", &rev, NULL,
N_("Use [RFC PATCH] instead of [PATCH]"),
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
- { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback),
+ OPT_STRING(0, "cover-from-description", &cover_from_description_arg,
+ N_("cover-from-description-mode"),
+ N_("generate parts of a cover letter based on a branch's description")),
+ OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"),
N_("Use [<prefix>] instead of [PATCH]"),
- PARSE_OPT_NONEG, subject_prefix_callback },
- { OPTION_CALLBACK, 'o', "output-directory", &output_directory,
+ PARSE_OPT_NONEG, subject_prefix_callback),
+ OPT_CALLBACK_F('o', "output-directory", &output_directory,
N_("dir"), N_("store resulting files in <dir>"),
- PARSE_OPT_NONEG, output_directory_callback },
- { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
+ PARSE_OPT_NONEG, output_directory_callback),
+ OPT_CALLBACK_F('k', "keep-subject", &rev, NULL,
N_("don't strip/add [PATCH]"),
- PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback),
OPT_BOOL(0, "no-binary", &no_binary_diff,
N_("don't output binary diffs")),
OPT_BOOL(0, "zero-commit", &zero_commit,
@@ -1573,31 +1782,30 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
N_("show patch format instead of default (patch + stat)"),
1, PARSE_OPT_NONEG),
OPT_GROUP(N_("Messaging")),
- { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"),
- N_("add email header"), 0, header_callback },
- { OPTION_CALLBACK, 0, "to", NULL, N_("email"), N_("add To: header"),
- 0, to_callback },
- { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
- 0, cc_callback },
- { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
+ OPT_CALLBACK(0, "add-header", NULL, N_("header"),
+ N_("add email header"), header_callback),
+ OPT_CALLBACK(0, "to", NULL, N_("email"), N_("add To: header"), to_callback),
+ OPT_CALLBACK(0, "cc", NULL, N_("email"), N_("add Cc: header"), cc_callback),
+ OPT_CALLBACK_F(0, "from", &from, N_("ident"),
N_("set From address to <ident> (or committer ident if absent)"),
- PARSE_OPT_OPTARG, from_callback },
+ PARSE_OPT_OPTARG, from_callback),
OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
N_("make first mail a reply to <message-id>")),
- { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
+ OPT_CALLBACK_F(0, "attach", &rev, N_("boundary"),
N_("attach the patch"), PARSE_OPT_OPTARG,
- attach_callback },
- { OPTION_CALLBACK, 0, "inline", &rev, N_("boundary"),
+ attach_callback),
+ OPT_CALLBACK_F(0, "inline", &rev, N_("boundary"),
N_("inline the patch"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
- inline_callback },
- { OPTION_CALLBACK, 0, "thread", &thread, N_("style"),
+ inline_callback),
+ OPT_CALLBACK_F(0, "thread", &thread, N_("style"),
N_("enable message threading, styles: shallow, deep"),
- PARSE_OPT_OPTARG, thread_callback },
+ PARSE_OPT_OPTARG, thread_callback),
OPT_STRING(0, "signature", &signature, N_("signature"),
N_("add a signature")),
- OPT_STRING(0, "base", &base_commit, N_("base-commit"),
- N_("add prerequisite tree info to the patch series")),
+ OPT_CALLBACK_F(0, "base", &base_commit, N_("base-commit"),
+ N_("add prerequisite tree info to the patch series"),
+ 0, base_callback),
OPT_FILENAME(0, "signature-file", &signature_file,
N_("add a signature from a file")),
OPT__QUIET(&quiet, N_("don't print the patch filenames")),
@@ -1617,9 +1825,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
extra_to.strdup_strings = 1;
extra_cc.strdup_strings = 1;
init_log_defaults();
+ init_display_notes(&notes_opt);
git_config(git_format_config, NULL);
repo_init_revisions(the_repository, &rev, prefix);
+ rev.show_notes = show_notes;
+ memcpy(&rev.notes_opt, &notes_opt, sizeof(notes_opt));
rev.commit_format = CMIT_FMT_EMAIL;
+ rev.encode_email_headers = default_encode_email_headers;
rev.expand_tabs_in_log_default = 0;
rev.verbose_header = 1;
rev.diff = 1;
@@ -1645,6 +1857,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
PARSE_OPT_KEEP_DASHDASH);
+ /* Make sure "0000-$sub.patch" gives non-negative length for $sub */
+ if (fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix))
+ fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix);
+
+ if (cover_from_description_arg)
+ cover_from_description_mode = parse_cover_from_description(cover_from_description_arg);
+
if (0 < reroll_count) {
struct strbuf sprefix = STRBUF_INIT;
strbuf_addf(&sprefix, "%s v%d",
@@ -1726,26 +1945,50 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
rev.zero_commit = zero_commit;
+ rev.patch_name_max = fmt_patch_name_max;
if (!rev.diffopt.flags.text && !no_binary_diff)
rev.diffopt.flags.binary = 1;
if (rev.show_notes)
- init_display_notes(&rev.notes_opt);
+ load_display_notes(&rev.notes_opt);
- if (!output_directory && !use_stdout)
- output_directory = config_output_directory;
+ if (use_stdout + rev.diffopt.close_file + !!output_directory > 1)
+ die(_("--stdout, --output, and --output-directory are mutually exclusive"));
- if (!use_stdout)
- output_directory = set_outdir(prefix, output_directory);
- else
+ if (use_stdout) {
setup_pager();
+ } else if (rev.diffopt.close_file) {
+ /*
+ * The diff code parsed --output; it has already opened the
+ * file, but but we must instruct it not to close after each
+ * diff.
+ */
+ rev.diffopt.close_file = 0;
+ } else {
+ int saved;
+
+ if (!output_directory)
+ output_directory = config_output_directory;
+ output_directory = set_outdir(prefix, output_directory);
- if (output_directory) {
if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
rev.diffopt.use_color = GIT_COLOR_NEVER;
- if (use_stdout)
- die(_("standard output, or directory, which one?"));
+ /*
+ * We consider <outdir> as 'outside of gitdir', therefore avoid
+ * applying adjust_shared_perm in s-c-l-d.
+ */
+ saved = get_shared_repository();
+ set_shared_repository(0);
+ switch (safe_create_leading_directories_const(output_directory)) {
+ case SCLD_OK:
+ case SCLD_EXISTS:
+ break;
+ default:
+ die(_("could not create leading directories "
+ "of '%s'"), output_directory);
+ }
+ set_shared_repository(saved);
if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
die_errno(_("could not create directory '%s'"),
output_directory);
@@ -1874,8 +2117,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
}
memset(&bases, 0, sizeof(bases));
- if (base_commit || base_auto) {
- struct commit *base = get_base_commit(base_commit, list, nr);
+ base = get_base_commit(base_commit, list, nr);
+ if (base) {
reset_revision_walk();
clear_object_flags(UNINTERESTING);
prepare_bases(&bases, base, list, nr);
@@ -1892,7 +2135,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (cover_letter) {
if (thread)
gen_message_id(&rev, "cover");
- make_cover_letter(&rev, use_stdout,
+ make_cover_letter(&rev, !!output_directory,
origin, nr, list, branch_name, quiet);
print_bases(&bases, rev.diffopt.file);
print_signature(rev.diffopt.file);
@@ -1947,7 +2190,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
gen_message_id(&rev, oid_to_hex(&commit->object.oid));
}
- if (!use_stdout &&
+ if (output_directory &&
open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet))
die(_("failed to create output files"));
shown = log_tree_commit(&rev, commit);
@@ -1960,7 +2203,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
* the log; when using one file per patch, we do
* not want the extra blank line.
*/
- if (!use_stdout)
+ if (output_directory)
rev.shown_one = 0;
if (shown) {
print_bases(&bases, rev.diffopt.file);
@@ -1971,7 +2214,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
else
print_signature(rev.diffopt.file);
}
- if (!use_stdout)
+ if (output_directory)
fclose(rev.diffopt.file);
}
stop_progress(&progress);
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 670e8fb93c..c8eae899b8 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -128,8 +128,9 @@ static void show_dir_entry(const struct index_state *istate,
if (len > ent->len)
die("git ls-files: internal error - directory entry not superset of prefix");
- if (!dir_path_match(istate, ent, &pathspec, len, ps_matched))
- return;
+ /* If ps_matches is non-NULL, figure out which pathspec(s) match. */
+ if (ps_matched)
+ dir_path_match(istate, ent, &pathspec, len, ps_matched);
fputs(tag, stdout);
write_eolinfo(istate, NULL, ent->name);
@@ -492,7 +493,7 @@ static int option_parse_exclude_from(const struct option *opt,
BUG_ON_OPT_NEG(unset);
exc_given = 1;
- add_excludes_from_file(dir, arg);
+ add_patterns_from_file(dir, arg);
return 0;
}
@@ -516,7 +517,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
int require_work_tree = 0, show_tag = 0, i;
const char *max_prefix;
struct dir_struct dir;
- struct exclude_list *el;
+ struct pattern_list *pl;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
struct option builtin_ls_files_options[] = {
/* Think twice before adding "--nul" synonym to this */
@@ -554,18 +555,18 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
N_("show unmerged files in the output")),
OPT_BOOL(0, "resolve-undo", &show_resolve_undo,
N_("show resolve-undo information")),
- { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"),
+ OPT_CALLBACK_F('x', "exclude", &exclude_list, N_("pattern"),
N_("skip files matching pattern"),
- PARSE_OPT_NONEG, option_parse_exclude },
- { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"),
+ PARSE_OPT_NONEG, option_parse_exclude),
+ OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"),
N_("exclude patterns are read from <file>"),
- PARSE_OPT_NONEG, option_parse_exclude_from },
+ PARSE_OPT_NONEG, option_parse_exclude_from),
OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"),
N_("read additional per-directory exclude patterns in <file>")),
- { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
+ OPT_CALLBACK_F(0, "exclude-standard", &dir, NULL,
N_("add the standard git exclusions"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
- option_parse_exclude_standard },
+ option_parse_exclude_standard),
OPT_SET_INT_F(0, "full-name", &prefix_len,
N_("make the output relative to the project top directory"),
0, PARSE_OPT_NONEG),
@@ -583,7 +584,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(ls_files_usage, builtin_ls_files_options);
- memset(&dir, 0, sizeof(dir));
+ dir_init(&dir);
prefix = cmd_prefix;
if (prefix)
prefix_len = strlen(prefix);
@@ -594,9 +595,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
ls_files_usage, 0);
- el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
+ pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option");
for (i = 0; i < exclude_list.nr; i++) {
- add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args);
+ add_pattern(exclude_list.items[i].string, "", 0, pl, --exclude_args);
}
if (show_tag || show_valid_bit || show_fsmonitor_bit) {
tag_cached = "H ";
@@ -687,6 +688,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
return bad ? 1 : 0;
}
- UNLEAK(dir);
+ dir_clear(&dir);
return 0;
}
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 6ef519514b..092917eca2 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -45,7 +45,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
int show_symref_target = 0;
const char *uploadpack = NULL;
const char **pattern = NULL;
- struct argv_array ref_prefixes = ARGV_ARRAY_INIT;
+ struct strvec ref_prefixes = STRVEC_INIT;
int i;
struct string_list server_options = STRING_LIST_INIT_DUP;
@@ -83,6 +83,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
PARSE_OPT_STOP_AT_NON_OPTION);
dest = argv[0];
+ UNLEAK(sorting);
+
if (argc > 1) {
int i;
pattern = xcalloc(argc, sizeof(const char *));
@@ -92,9 +94,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
}
if (flags & REF_TAGS)
- argv_array_push(&ref_prefixes, "refs/tags/");
+ strvec_push(&ref_prefixes, "refs/tags/");
if (flags & REF_HEADS)
- argv_array_push(&ref_prefixes, "refs/heads/");
+ strvec_push(&ref_prefixes, "refs/heads/");
remote = remote_get(dest);
if (!remote) {
@@ -107,7 +109,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
if (get_url) {
printf("%s\n", *remote->url);
- UNLEAK(sorting);
return 0;
}
@@ -118,10 +119,12 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
transport->server_options = &server_options;
ref = transport_get_remote_refs(transport, &ref_prefixes);
- if (transport_disconnect(transport)) {
- UNLEAK(sorting);
- return 1;
+ if (ref) {
+ int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
+ repo_set_hash_algo(the_repository, hash_algo);
}
+ if (transport_disconnect(transport))
+ return 1;
if (!dest && !quiet)
fprintf(stderr, "From %s\n", *remote->url);
@@ -146,7 +149,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
status = 0; /* we found something */
}
- UNLEAK(sorting);
ref_array_clear(&ref_array);
return status;
}
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index e3f8da13b6..6719ac198d 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -114,26 +114,16 @@ static int handle_is_ancestor(int argc, const char **argv)
static int handle_fork_point(int argc, const char **argv)
{
struct object_id oid;
- char *refname;
struct commit *derived, *fork_point;
const char *commitname;
- switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) {
- case 0:
- die("No such ref: '%s'", argv[0]);
- case 1:
- break; /* good */
- default:
- die("Ambiguous refname: '%s'", argv[0]);
- }
-
commitname = (argc == 2) ? argv[1] : "HEAD";
if (get_oid(commitname, &oid))
die("Not a valid object name: '%s'", commitname);
derived = lookup_commit_reference(the_repository, &oid);
- fork_point = get_fork_point(refname, derived);
+ fork_point = get_fork_point(argv[0], derived);
if (!fork_point)
return 1;
diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
index 5b910e351e..a4bfd8fc51 100644
--- a/builtin/merge-recursive.c
+++ b/builtin/merge-recursive.c
@@ -1,3 +1,4 @@
+#include "cache.h"
#include "builtin.h"
#include "commit.h"
#include "tag.h"
@@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
if (argc - i != 3) /* "--" "<head>" "<remote>" */
die(_("not handling anything other than two heads merge."));
+ if (repo_read_index_unmerged(the_repository))
+ die_resolve_conflict("merge");
+
o.branch1 = argv[++i];
o.branch2 = argv[++i];
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index 34ca0258b1..de8520778d 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -109,6 +109,7 @@ static void show_diff(struct merge_list *entry)
xdemitconf_t xecfg;
xdemitcb_t ecb;
+ memset(&xpp, 0, sizeof(xpp));
xpp.flags = 0;
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
@@ -180,8 +181,9 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const stru
static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
{
- char *path = xmallocz(traverse_path_len(info, n) + the_hash_algo->rawsz);
- return make_traverse_path(path, info, n);
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_make_traverse_path(&buf, info, n->path, n->pathlen);
+ return strbuf_detach(&buf, NULL);
}
static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result)
@@ -205,6 +207,7 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s
static void unresolved_directory(const struct traverse_info *info,
struct name_entry n[3])
{
+ struct repository *r = the_repository;
char *newbase;
struct name_entry *p;
struct tree_desc t[3];
@@ -220,9 +223,9 @@ static void unresolved_directory(const struct traverse_info *info,
newbase = traverse_path(info, p);
#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? &(e)->oid : NULL)
- buf0 = fill_tree_descriptor(t + 0, ENTRY_OID(n + 0));
- buf1 = fill_tree_descriptor(t + 1, ENTRY_OID(n + 1));
- buf2 = fill_tree_descriptor(t + 2, ENTRY_OID(n + 2));
+ buf0 = fill_tree_descriptor(r, t + 0, ENTRY_OID(n + 0));
+ buf1 = fill_tree_descriptor(r, t + 1, ENTRY_OID(n + 1));
+ buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));
#undef ENTRY_OID
merge_trees(t, newbase);
@@ -351,14 +354,16 @@ static void merge_trees(struct tree_desc t[3], const char *base)
traverse_trees(&the_index, 3, t, &info);
}
-static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
+static void *get_tree_descriptor(struct repository *r,
+ struct tree_desc *desc,
+ const char *rev)
{
struct object_id oid;
void *buf;
- if (get_oid(rev, &oid))
+ if (repo_get_oid(r, rev, &oid))
die("unknown rev %s", rev);
- buf = fill_tree_descriptor(desc, &oid);
+ buf = fill_tree_descriptor(r, desc, &oid);
if (!buf)
die("%s is not a tree", rev);
return buf;
@@ -366,15 +371,16 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
int cmd_merge_tree(int argc, const char **argv, const char *prefix)
{
+ struct repository *r = the_repository;
struct tree_desc t[3];
void *buf1, *buf2, *buf3;
if (argc != 4)
usage(merge_tree_usage);
- buf1 = get_tree_descriptor(t+0, argv[1]);
- buf2 = get_tree_descriptor(t+1, argv[2]);
- buf3 = get_tree_descriptor(t+2, argv[3]);
+ buf1 = get_tree_descriptor(r, t+0, argv[1]);
+ buf2 = get_tree_descriptor(r, t+1, argv[2]);
+ buf3 = get_tree_descriptor(r, t+2, argv[3]);
merge_trees(t, "");
free(buf1);
free(buf2);
diff --git a/builtin/merge.c b/builtin/merge.c
index 29988e54c5..1cff730715 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -28,6 +28,7 @@
#include "rerere.h"
#include "help.h"
#include "merge-recursive.h"
+#include "merge-ort-wrappers.h"
#include "resolve-undo.h"
#include "remote.h"
#include "fmt-merge-msg.h"
@@ -37,8 +38,10 @@
#include "packfile.h"
#include "tag.h"
#include "alias.h"
+#include "branch.h"
#include "commit-reach.h"
#include "wt-status.h"
+#include "commit-graph.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -61,6 +64,7 @@ static int show_diffstat = 1, shortlog_len = -1, squash;
static int option_commit = -1;
static int option_edit = -1;
static int allow_trivial = 1, have_message, verify_signatures;
+static int check_trust_level = 1;
static int overwrite_ignore = 1;
static struct strbuf merge_msg = STRBUF_INIT;
static struct strategy **use_strategies;
@@ -69,21 +73,23 @@ static const char **xopts;
static size_t xopts_nr, xopts_alloc;
static const char *branch;
static char *branch_mergeoptions;
-static int option_renormalize;
static int verbosity;
static int allow_rerere_auto;
static int abort_current_merge;
+static int quit_current_merge;
static int continue_current_merge;
static int allow_unrelated_histories;
static int show_progress = -1;
static int default_to_upstream = 1;
static int signoff;
static const char *sign_commit;
-static int verify_msg = 1;
+static int autostash;
+static int no_verify;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
{ "octopus", DEFAULT_OCTOPUS },
+ { "ort", NO_TRIVIAL },
{ "resolve", 0 },
{ "ours", NO_FAST_FORWARD | NO_TRIVIAL },
{ "subtree", NO_FAST_FORWARD | NO_TRIVIAL },
@@ -155,10 +161,17 @@ static struct strategy *get_strategy(const char *name)
struct strategy *ret;
static struct cmdnames main_cmds, other_cmds;
static int loaded;
+ char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
if (!name)
return NULL;
+ if (default_strategy &&
+ !strcmp(default_strategy, "ort") &&
+ !strcmp(name, "recursive")) {
+ name = "ort";
+ }
+
for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
if (!strcmp(name, all_strategy[i].name))
return &all_strategy[i];
@@ -238,9 +251,9 @@ static int option_parse_n(const struct option *opt,
}
static struct option builtin_merge_options[] = {
- { OPTION_CALLBACK, 'n', NULL, NULL, NULL,
+ OPT_CALLBACK_F('n', NULL, NULL, NULL,
N_("do not show a diffstat at the end of the merge"),
- PARSE_OPT_NOARG, option_parse_n },
+ PARSE_OPT_NOARG, option_parse_n),
OPT_BOOL(0, "stat", &show_diffstat,
N_("show a diffstat at the end of the merge")),
OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
@@ -274,6 +287,8 @@ static struct option builtin_merge_options[] = {
OPT__VERBOSITY(&verbosity),
OPT_BOOL(0, "abort", &abort_current_merge,
N_("abort the current in-progress merge")),
+ OPT_BOOL(0, "quit", &quit_current_merge,
+ N_("--abort but leave index and working tree alone")),
OPT_BOOL(0, "continue", &continue_current_merge,
N_("continue the current in-progress merge")),
OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories,
@@ -281,20 +296,13 @@ static struct option builtin_merge_options[] = {
OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ OPT_AUTOSTASH(&autostash),
OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
- OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")),
- OPT_BOOL(0, "verify", &verify_msg, N_("verify commit-msg hook")),
+ OPT_BOOL(0, "signoff", &signoff, N_("add a Signed-off-by trailer")),
+ OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")),
OPT_END()
};
-/* Cleans up metadata that is uninteresting after a succeeded merge. */
-static void drop_save(void)
-{
- unlink(git_path_merge_head(the_repository));
- unlink(git_path_merge_msg(the_repository));
- unlink(git_path_merge_mode(the_repository));
-}
-
static int save_state(struct object_id *stash)
{
int len;
@@ -388,7 +396,7 @@ static void finish_up_to_date(const char *msg)
{
if (verbosity >= 0)
printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg);
- drop_save();
+ remove_merge_branch_state(the_repository);
}
static void squash_message(struct commit *commit, struct commit_list *remoteheads)
@@ -450,7 +458,6 @@ static void finish(struct commit *head_commit,
if (verbosity >= 0 && !merge_msg.len)
printf(_("No merge message -- not updating HEAD\n"));
else {
- const char *argv_gc_auto[] = { "gc", "--auto", NULL };
update_ref(reflog_message.buf, "HEAD", new_head, head,
0, UPDATE_REFS_DIE_ON_ERR);
/*
@@ -458,7 +465,7 @@ static void finish(struct commit *head_commit,
* user should see them.
*/
close_object_store(the_repository->objects);
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ run_auto_maintenance(verbosity < 0);
}
}
if (new_head && show_diffstat) {
@@ -478,6 +485,7 @@ static void finish(struct commit *head_commit,
/* Run a post-merge hook */
run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+ apply_autostash(git_path_merge_autostash(the_repository));
strbuf_release(&reflog_message);
}
@@ -501,7 +509,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
if (!remote_head)
die(_("'%s' does not point to a commit"), remote);
- if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref) > 0) {
+ if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref, 0) > 0) {
if (starts_with(found_ref, "refs/heads/")) {
strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
oid_to_hex(&branch_head), remote);
@@ -600,10 +608,12 @@ static void parse_branch_merge_options(char *bmo)
static int git_merge_config(const char *k, const char *v, void *cb)
{
int status;
+ const char *str;
- if (branch && starts_with(k, "branch.") &&
- starts_with(k + 7, branch) &&
- !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+ if (branch &&
+ skip_prefix(k, "branch.", &str) &&
+ skip_prefix(str, branch, &str) &&
+ !strcmp(str, ".mergeoptions")) {
free(branch_mergeoptions);
branch_mergeoptions = xstrdup(v);
return 0;
@@ -619,8 +629,6 @@ static int git_merge_config(const char *k, const char *v, void *cb)
return git_config_string(&pull_octopus, k, v);
else if (!strcmp(k, "commit.cleanup"))
return git_config_string(&cleanup_arg, k, v);
- else if (!strcmp(k, "merge.renormalize"))
- option_renormalize = git_config_bool(k, v);
else if (!strcmp(k, "merge.ff")) {
int boolval = git_parse_maybe_bool(v);
if (0 <= boolval) {
@@ -635,6 +643,11 @@ static int git_merge_config(const char *k, const char *v, void *cb)
} else if (!strcmp(k, "commit.gpgsign")) {
sign_commit = git_config_bool(k, v) ? "" : NULL;
return 0;
+ } else if (!strcmp(k, "gpg.mintrustlevel")) {
+ check_trust_level = 0;
+ } else if (!strcmp(k, "merge.autostash")) {
+ autostash = git_config_bool(k, v);
+ return 0;
}
status = fmt_merge_msg_config(k, v, cb);
@@ -692,16 +705,14 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
struct commit_list *remoteheads,
struct commit *head)
{
- struct lock_file lock = LOCK_INIT;
const char *head_arg = "HEAD";
- hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
- refresh_cache(REFRESH_QUIET);
- if (write_locked_index(&the_index, &lock,
- COMMIT_LOCK | SKIP_IF_UNCHANGED))
+ if (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0)
return error(_("Unable to write index."));
- if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
+ if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree") ||
+ !strcmp(strategy, "ort")) {
+ struct lock_file lock = LOCK_INIT;
int clean, x;
struct commit *result;
struct commit_list *reversed = NULL;
@@ -717,7 +728,6 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
if (!strcmp(strategy, "subtree"))
o.subtree_shift = "";
- o.renormalize = option_renormalize;
o.show_rename_progress =
show_progress == -1 ? isatty(2) : show_progress;
@@ -732,8 +742,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
commit_list_insert(j->item, &reversed);
hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
- clean = merge_recursive(&o, head,
- remoteheads->item, reversed, &result);
+ if (!strcmp(strategy, "ort"))
+ clean = merge_ort_recursive(&o, head, remoteheads->item,
+ reversed, &result);
+ else
+ clean = merge_recursive(&o, head, remoteheads->item,
+ reversed, &result);
if (clean < 0)
exit(128);
if (write_locked_index(&the_index, &lock,
@@ -820,6 +834,18 @@ static void write_merge_heads(struct commit_list *);
static void prepare_to_commit(struct commit_list *remoteheads)
{
struct strbuf msg = STRBUF_INIT;
+ const char *index_file = get_index_file();
+
+ if (!no_verify && run_commit_hook(0 < option_edit, index_file, "pre-merge-commit", NULL))
+ abort_commit(remoteheads, NULL);
+ /*
+ * Re-read the index as pre-merge-commit hook could have updated it,
+ * and write it out as a tree. We must do this before we invoke
+ * the editor and after we invoke run_status above.
+ */
+ if (find_hook("pre-merge-commit"))
+ discard_cache();
+ read_cache_from(index_file);
strbuf_addbuf(&msg, &merge_msg);
if (squash)
BUG("the control must not reach here under --squash");
@@ -846,7 +872,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
abort_commit(remoteheads, NULL);
}
- if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(),
+ if (!no_verify && run_commit_hook(0 < option_edit, get_index_file(),
"commit-msg",
git_path_merge_msg(the_repository), NULL))
abort_commit(remoteheads, NULL);
@@ -864,12 +890,8 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
{
struct object_id result_tree, result_commit;
struct commit_list *parents, **pptr = &parents;
- struct lock_file lock = LOCK_INIT;
- hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
- refresh_cache(REFRESH_QUIET);
- if (write_locked_index(&the_index, &lock,
- COMMIT_LOCK | SKIP_IF_UNCHANGED))
+ if (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0)
return error(_("Unable to write index."));
write_tree_trivial(&result_tree);
@@ -881,7 +903,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
&result_commit, NULL, sign_commit))
die(_("failed to write commit object"));
finish(head, remoteheads, &result_commit, "In-index merge");
- drop_save();
+ remove_merge_branch_state(the_repository);
return 0;
}
@@ -896,6 +918,7 @@ static int finish_automerge(struct commit *head,
struct strbuf buf = STRBUF_INIT;
struct object_id result_commit;
+ write_tree_trivial(result_tree);
free_commit_list(common);
parents = remoteheads;
if (!head_subsumed || fast_forward == FF_NO)
@@ -907,7 +930,7 @@ static int finish_automerge(struct commit *head,
strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
finish(head, remoteheads, &result_commit, buf.buf);
strbuf_release(&buf);
- drop_save();
+ remove_merge_branch_state(the_repository);
return 0;
}
@@ -1255,6 +1278,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (branch)
skip_prefix(branch, "refs/heads/", &branch);
+ if (!pull_twohead) {
+ char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
+ if (default_strategy && !strcmp(default_strategy, "ort"))
+ pull_twohead = "ort";
+ }
+
init_diff_ui_defaults();
git_config(git_merge_config, NULL);
@@ -1276,6 +1305,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (abort_current_merge) {
int nargc = 2;
const char *nargv[] = {"reset", "--merge", NULL};
+ struct strbuf stash_oid = STRBUF_INIT;
if (orig_argc != 2)
usage_msg_opt(_("--abort expects no arguments"),
@@ -1284,8 +1314,27 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (!file_exists(git_path_merge_head(the_repository)))
die(_("There is no merge to abort (MERGE_HEAD missing)."));
+ if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository),
+ READ_ONELINER_SKIP_IF_EMPTY))
+ unlink(git_path_merge_autostash(the_repository));
+
/* Invoke 'git reset --merge' */
ret = cmd_reset(nargc, nargv, prefix);
+
+ if (stash_oid.len)
+ apply_autostash_oid(stash_oid.buf);
+
+ strbuf_release(&stash_oid);
+ goto done;
+ }
+
+ if (quit_current_merge) {
+ if (orig_argc != 2)
+ usage_msg_opt(_("--quit expects no arguments"),
+ builtin_merge_usage,
+ builtin_merge_options);
+
+ remove_merge_branch_state(the_repository);
goto done;
}
@@ -1319,7 +1368,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else
die(_("You have not concluded your merge (MERGE_HEAD exists)."));
}
- if (file_exists(git_path_cherry_pick_head(the_repository))) {
+ if (ref_exists("CHERRY_PICK_HEAD")) {
if (advice_resolve_conflict)
die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
"Please, commit your changes before you merge."));
@@ -1385,7 +1434,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die(_("Can merge only exactly one commit into empty head"));
if (verify_signatures)
- verify_merge_signature(remoteheads->item, verbosity);
+ verify_merge_signature(remoteheads->item, verbosity,
+ check_trust_level);
remote_head_oid = &remoteheads->item->object.oid;
read_empty(remote_head_oid, 0);
@@ -1408,7 +1458,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (verify_signatures) {
for (p = remoteheads; p; p = p->next) {
- verify_merge_signature(p->item, verbosity);
+ verify_merge_signature(p->item, verbosity,
+ check_trust_level);
}
}
@@ -1496,6 +1547,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
goto done;
}
+ if (autostash)
+ create_autostash(the_repository,
+ git_path_merge_autostash(the_repository),
+ "merge");
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
@@ -1505,7 +1560,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
finish(head_commit, remoteheads, &commit->object.oid, msg.buf);
- drop_save();
+ remove_merge_branch_state(the_repository);
goto done;
} else if (!remoteheads->next && common->next)
;
@@ -1562,6 +1617,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (fast_forward == FF_ONLY)
die(_("Not possible to fast-forward, aborting."));
+ if (autostash)
+ create_autostash(the_repository,
+ git_path_merge_autostash(the_repository),
+ "merge");
+
/* We are going to make a new commit. */
git_committer_info(IDENT_STRICT);
@@ -1580,8 +1640,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
save_state(&stash))
oidclr(&stash);
- for (i = 0; i < use_strategies_nr; i++) {
- int ret;
+ for (i = 0; !merge_was_ok && i < use_strategies_nr; i++) {
+ int ret, cnt;
if (i) {
printf(_("Rewinding the tree to pristine...\n"));
restore_state(&head_commit->object.oid, &stash);
@@ -1598,40 +1658,26 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
ret = try_merge_strategy(use_strategies[i]->name,
common, remoteheads,
head_commit);
- if (!option_commit && !ret) {
- merge_was_ok = 1;
- /*
- * This is necessary here just to avoid writing
- * the tree, but later we will *not* exit with
- * status code 1 because merge_was_ok is set.
- */
- ret = 1;
- }
-
- if (ret) {
- /*
- * The backend exits with 1 when conflicts are
- * left to be resolved, with 2 when it does not
- * handle the given merge at all.
- */
- if (ret == 1) {
- int cnt = evaluate_result();
-
- if (best_cnt <= 0 || cnt <= best_cnt) {
- best_strategy = use_strategies[i]->name;
- best_cnt = cnt;
+ /*
+ * The backend exits with 1 when conflicts are
+ * left to be resolved, with 2 when it does not
+ * handle the given merge at all.
+ */
+ if (ret < 2) {
+ if (!ret) {
+ if (option_commit) {
+ /* Automerge succeeded. */
+ automerge_was_ok = 1;
+ break;
}
+ merge_was_ok = 1;
+ }
+ cnt = (use_strategies_nr > 1) ? evaluate_result() : 0;
+ if (best_cnt <= 0 || cnt <= best_cnt) {
+ best_strategy = use_strategies[i]->name;
+ best_cnt = cnt;
}
- if (merge_was_ok)
- break;
- else
- continue;
}
-
- /* Automerge succeeded. */
- write_tree_trivial(&result_tree);
- automerge_was_ok = 1;
- break;
}
/*
@@ -1670,9 +1716,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
head_commit);
}
- if (squash)
+ if (squash) {
finish(head_commit, remoteheads, NULL, NULL);
- else
+
+ git_test_write_commit_graph_or_die();
+ } else
write_merge_state(remoteheads);
if (merge_was_ok)
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 6fb7dc8578..4982d3a93e 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -29,8 +29,11 @@ static int verify_object(const struct object_id *oid, const char *expected_type)
const struct object_id *repl = lookup_replace_object(the_repository, oid);
if (buffer) {
- if (type == type_from_string(expected_type))
- ret = check_object_signature(repl, buffer, size, expected_type);
+ if (type == type_from_string(expected_type)) {
+ ret = check_object_signature(the_repository, repl,
+ buffer, size,
+ expected_type);
+ }
free(buffer);
}
return ret;
diff --git a/builtin/mktree.c b/builtin/mktree.c
index 94e82b8504..891991b00d 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -67,7 +67,7 @@ static const char *mktree_usage[] = {
NULL
};
-static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_missing)
+static void mktree_line(char *buf, int nul_term_line, int allow_missing)
{
char *ptr, *ntr;
const char *p;
@@ -172,7 +172,7 @@ int cmd_mktree(int ac, const char **av, const char *prefix)
break;
die("input format error: (blank line only valid in batch mode)");
}
- mktree_line(sb.buf, sb.len, nul_term_line, allow_missing);
+ mktree_line(sb.buf, nul_term_line, allow_missing);
}
if (is_batch_mode && got_eof && used < 1) {
/*
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 72dfd3dadc..5bf88cd2a8 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -6,31 +6,41 @@
#include "trace2.h"
static char const * const builtin_multi_pack_index_usage[] = {
- N_("git multi-pack-index [--object-dir=<dir>] (write|verify)"),
+ N_("git multi-pack-index [<options>] (write|verify|expire|repack --batch-size=<size>)"),
NULL
};
static struct opts_multi_pack_index {
const char *object_dir;
+ unsigned long batch_size;
+ int progress;
} opts;
int cmd_multi_pack_index(int argc, const char **argv,
const char *prefix)
{
+ unsigned flags = 0;
+
static struct option builtin_multi_pack_index_options[] = {
OPT_FILENAME(0, "object-dir", &opts.object_dir,
N_("object directory containing set of packfile and pack-index pairs")),
+ OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
+ OPT_MAGNITUDE(0, "batch-size", &opts.batch_size,
+ N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")),
OPT_END(),
};
git_config(git_default_config, NULL);
+ opts.progress = isatty(2);
argc = parse_options(argc, argv, prefix,
builtin_multi_pack_index_options,
builtin_multi_pack_index_usage, 0);
if (!opts.object_dir)
opts.object_dir = get_object_directory();
+ if (opts.progress)
+ flags |= MIDX_PROGRESS;
if (argc == 0)
usage_with_options(builtin_multi_pack_index_usage,
@@ -43,10 +53,18 @@ int cmd_multi_pack_index(int argc, const char **argv,
trace2_cmd_mode(argv[0]);
+ if (!strcmp(argv[0], "repack"))
+ return midx_repack(the_repository, opts.object_dir,
+ (size_t)opts.batch_size, flags);
+ if (opts.batch_size)
+ die(_("--batch-size option is only for 'repack' subcommand"));
+
if (!strcmp(argv[0], "write"))
- return write_midx_file(opts.object_dir);
+ return write_midx_file(opts.object_dir, flags);
if (!strcmp(argv[0], "verify"))
- return verify_midx_file(the_repository, opts.object_dir);
+ return verify_midx_file(the_repository, opts.object_dir, flags);
+ if (!strcmp(argv[0], "expire"))
+ return expire_midx_packs(the_repository, opts.object_dir, flags);
- die(_("unrecognized verb: %s"), argv[0]);
+ die(_("unrecognized subcommand: %s"), argv[0]);
}
diff --git a/builtin/mv.c b/builtin/mv.c
index be15ba7044..7dac714af9 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -132,6 +132,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
struct stat st;
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
struct lock_file lock_file = LOCK_INIT;
+ struct cache_entry *ce;
git_config(git_default_config, NULL);
@@ -220,9 +221,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
}
argc += last - first;
}
- } else if (cache_name_pos(src, length) < 0)
+ } else if (!(ce = cache_file_exists(src, length, ignore_case))) {
bad = _("not under version control");
- else if (lstat(dst, &st) == 0 &&
+ } else if (ce_stage(ce)) {
+ bad = _("conflicted");
+ } else if (lstat(dst, &st) == 0 &&
(!ignore_case || strcasecmp(src, dst))) {
bad = _("destination exists");
if (force) {
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 05ccf53e00..725dd04519 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -6,20 +6,25 @@
#include "tag.h"
#include "refs.h"
#include "parse-options.h"
+#include "prio-queue.h"
#include "sha1-lookup.h"
#include "commit-slab.h"
-#define CUTOFF_DATE_SLOP 86400 /* one day */
+/*
+ * One day. See the 'name a rev shortly after epoch' test in t6120 when
+ * changing this value
+ */
+#define CUTOFF_DATE_SLOP 86400
-typedef struct rev_name {
- const char *tip_name;
+struct rev_name {
+ char *tip_name;
timestamp_t taggerdate;
int generation;
int distance;
int from_tag;
-} rev_name;
+};
-define_commit_slab(commit_rev_name, struct rev_name *);
+define_commit_slab(commit_rev_name, struct rev_name);
static timestamp_t cutoff = TIME_MAX;
static struct commit_rev_name rev_names;
@@ -27,22 +32,20 @@ static struct commit_rev_name rev_names;
/* How many generations are maximally preferred over _one_ merge traversal? */
#define MERGE_TRAVERSAL_WEIGHT 65535
-static struct rev_name *get_commit_rev_name(struct commit *commit)
+static int is_valid_rev_name(const struct rev_name *name)
{
- struct rev_name **slot = commit_rev_name_peek(&rev_names, commit);
-
- return slot ? *slot : NULL;
+ return name && (name->generation || name->tip_name);
}
-static void set_commit_rev_name(struct commit *commit, struct rev_name *name)
+static struct rev_name *get_commit_rev_name(const struct commit *commit)
{
- *commit_rev_name_at(&rev_names, commit) = name;
+ struct rev_name *name = commit_rev_name_peek(&rev_names, commit);
+
+ return is_valid_rev_name(name) ? name : NULL;
}
static int is_better_name(struct rev_name *name,
- const char *tip_name,
timestamp_t taggerdate,
- int generation,
int distance,
int from_tag)
{
@@ -77,69 +80,135 @@ static int is_better_name(struct rev_name *name,
return 0;
}
-static void name_rev(struct commit *commit,
- const char *tip_name, timestamp_t taggerdate,
- int generation, int distance, int from_tag,
- int deref)
+static struct rev_name *create_or_update_name(struct commit *commit,
+ timestamp_t taggerdate,
+ int generation, int distance,
+ int from_tag)
{
- struct rev_name *name = get_commit_rev_name(commit);
- struct commit_list *parents;
- int parent_number = 1;
- char *to_free = NULL;
-
- parse_commit(commit);
+ struct rev_name *name = commit_rev_name_at(&rev_names, commit);
+
+ if (is_valid_rev_name(name)) {
+ if (!is_better_name(name, taggerdate, distance, from_tag))
+ return NULL;
+
+ /*
+ * This string might still be shared with ancestors
+ * (generation > 0). We can release it here regardless,
+ * because the new name that has just won will be better
+ * for them as well, so name_rev() will replace these
+ * stale pointers when it processes the parents.
+ */
+ if (!name->generation)
+ free(name->tip_name);
+ }
- if (commit->date < cutoff)
- return;
+ name->taggerdate = taggerdate;
+ name->generation = generation;
+ name->distance = distance;
+ name->from_tag = from_tag;
- if (deref) {
- tip_name = to_free = xstrfmt("%s^0", tip_name);
+ return name;
+}
- if (generation)
- die("generation: %d, but deref?", generation);
+static char *get_parent_name(const struct rev_name *name, int parent_number)
+{
+ struct strbuf sb = STRBUF_INIT;
+ size_t len;
+
+ strip_suffix(name->tip_name, "^0", &len);
+ if (name->generation > 0) {
+ strbuf_grow(&sb, len +
+ 1 + decimal_width(name->generation) +
+ 1 + decimal_width(parent_number));
+ strbuf_addf(&sb, "%.*s~%d^%d", (int)len, name->tip_name,
+ name->generation, parent_number);
+ } else {
+ strbuf_grow(&sb, len +
+ 1 + decimal_width(parent_number));
+ strbuf_addf(&sb, "%.*s^%d", (int)len, name->tip_name,
+ parent_number);
}
+ return strbuf_detach(&sb, NULL);
+}
- if (name == NULL) {
- name = xmalloc(sizeof(rev_name));
- set_commit_rev_name(commit, name);
- goto copy_data;
- } else if (is_better_name(name, tip_name, taggerdate,
- generation, distance, from_tag)) {
-copy_data:
- name->tip_name = tip_name;
- name->taggerdate = taggerdate;
- name->generation = generation;
- name->distance = distance;
- name->from_tag = from_tag;
- } else {
- free(to_free);
+static void name_rev(struct commit *start_commit,
+ const char *tip_name, timestamp_t taggerdate,
+ int from_tag, int deref)
+{
+ struct prio_queue queue;
+ struct commit *commit;
+ struct commit **parents_to_queue = NULL;
+ size_t parents_to_queue_nr, parents_to_queue_alloc = 0;
+ struct rev_name *start_name;
+
+ parse_commit(start_commit);
+ if (start_commit->date < cutoff)
return;
- }
- for (parents = commit->parents;
- parents;
- parents = parents->next, parent_number++) {
- if (parent_number > 1) {
- size_t len;
- char *new_name;
-
- strip_suffix(tip_name, "^0", &len);
- if (generation > 0)
- new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name,
- generation, parent_number);
- else
- new_name = xstrfmt("%.*s^%d", (int)len, tip_name,
- parent_number);
-
- name_rev(parents->item, new_name, taggerdate, 0,
- distance + MERGE_TRAVERSAL_WEIGHT,
- from_tag, 0);
- } else {
- name_rev(parents->item, tip_name, taggerdate,
- generation + 1, distance + 1,
- from_tag, 0);
+ start_name = create_or_update_name(start_commit, taggerdate, 0, 0,
+ from_tag);
+ if (!start_name)
+ return;
+ if (deref)
+ start_name->tip_name = xstrfmt("%s^0", tip_name);
+ else
+ start_name->tip_name = xstrdup(tip_name);
+
+ memset(&queue, 0, sizeof(queue)); /* Use the prio_queue as LIFO */
+ prio_queue_put(&queue, start_commit);
+
+ while ((commit = prio_queue_get(&queue))) {
+ struct rev_name *name = get_commit_rev_name(commit);
+ struct commit_list *parents;
+ int parent_number = 1;
+
+ parents_to_queue_nr = 0;
+
+ for (parents = commit->parents;
+ parents;
+ parents = parents->next, parent_number++) {
+ struct commit *parent = parents->item;
+ struct rev_name *parent_name;
+ int generation, distance;
+
+ parse_commit(parent);
+ if (parent->date < cutoff)
+ continue;
+
+ if (parent_number > 1) {
+ generation = 0;
+ distance = name->distance + MERGE_TRAVERSAL_WEIGHT;
+ } else {
+ generation = name->generation + 1;
+ distance = name->distance + 1;
+ }
+
+ parent_name = create_or_update_name(parent, taggerdate,
+ generation,
+ distance, from_tag);
+ if (parent_name) {
+ if (parent_number > 1)
+ parent_name->tip_name =
+ get_parent_name(name,
+ parent_number);
+ else
+ parent_name->tip_name = name->tip_name;
+ ALLOC_GROW(parents_to_queue,
+ parents_to_queue_nr + 1,
+ parents_to_queue_alloc);
+ parents_to_queue[parents_to_queue_nr] = parent;
+ parents_to_queue_nr++;
+ }
}
+
+ /* The first parent must come out first from the prio_queue */
+ while (parents_to_queue_nr)
+ prio_queue_put(&queue,
+ parents_to_queue[--parents_to_queue_nr]);
}
+
+ clear_prio_queue(&queue);
+ free(parents_to_queue);
}
static int subpath_matches(const char *path, const char *filter)
@@ -160,10 +229,10 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
{
if (shorten_unambiguous)
refname = shorten_unambiguous_ref(refname, 0);
- else if (starts_with(refname, "refs/heads/"))
- refname = refname + 11;
- else if (starts_with(refname, "refs/"))
- refname = refname + 5;
+ else if (skip_prefix(refname, "refs/heads/", &refname))
+ ; /* refname already advanced */
+ else
+ skip_prefix(refname, "refs/", &refname);
return refname;
}
@@ -178,6 +247,10 @@ static struct tip_table {
struct tip_table_entry {
struct object_id oid;
const char *refname;
+ struct commit *commit;
+ timestamp_t taggerdate;
+ unsigned int from_tag:1;
+ unsigned int deref:1;
} *table;
int nr;
int alloc;
@@ -185,13 +258,18 @@ static struct tip_table {
} tip_table;
static void add_to_tip_table(const struct object_id *oid, const char *refname,
- int shorten_unambiguous)
+ int shorten_unambiguous, struct commit *commit,
+ timestamp_t taggerdate, int from_tag, int deref)
{
refname = name_ref_abbrev(refname, shorten_unambiguous);
ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc);
oidcpy(&tip_table.table[tip_table.nr].oid, oid);
tip_table.table[tip_table.nr].refname = xstrdup(refname);
+ tip_table.table[tip_table.nr].commit = commit;
+ tip_table.table[tip_table.nr].taggerdate = taggerdate;
+ tip_table.table[tip_table.nr].from_tag = from_tag;
+ tip_table.table[tip_table.nr].deref = deref;
tip_table.nr++;
tip_table.sorted = 0;
}
@@ -202,12 +280,30 @@ static int tipcmp(const void *a_, const void *b_)
return oidcmp(&a->oid, &b->oid);
}
+static int cmp_by_tag_and_age(const void *a_, const void *b_)
+{
+ const struct tip_table_entry *a = a_, *b = b_;
+ int cmp;
+
+ /* Prefer tags. */
+ cmp = b->from_tag - a->from_tag;
+ if (cmp)
+ return cmp;
+
+ /* Older is better. */
+ if (a->taggerdate < b->taggerdate)
+ return -1;
+ return a->taggerdate != b->taggerdate;
+}
+
static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data)
{
struct object *o = parse_object(the_repository, oid);
struct name_ref_data *data = cb_data;
int can_abbreviate_output = data->tags_only && data->name_only;
int deref = 0;
+ int from_tag = 0;
+ struct commit *commit = NULL;
timestamp_t taggerdate = TIME_MAX;
if (data->tags_only && !starts_with(path, "refs/tags/"))
@@ -256,8 +352,6 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo
return 0;
}
- add_to_tip_table(oid, path, can_abbreviate_output);
-
while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
if (!t->tagged)
@@ -267,18 +361,35 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo
taggerdate = t->date;
}
if (o && o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
- int from_tag = starts_with(path, "refs/tags/");
-
+ commit = (struct commit *)o;
+ from_tag = starts_with(path, "refs/tags/");
if (taggerdate == TIME_MAX)
- taggerdate = ((struct commit *)o)->date;
- path = name_ref_abbrev(path, can_abbreviate_output);
- name_rev(commit, xstrdup(path), taggerdate, 0, 0,
- from_tag, deref);
+ taggerdate = commit->date;
}
+
+ add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate,
+ from_tag, deref);
return 0;
}
+static void name_tips(void)
+{
+ int i;
+
+ /*
+ * Try to set better names first, so that worse ones spread
+ * less.
+ */
+ QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age);
+ for (i = 0; i < tip_table.nr; i++) {
+ struct tip_table_entry *e = &tip_table.table[i];
+ if (e->commit) {
+ name_rev(e->commit, e->refname, e->taggerdate,
+ e->from_tag, e->deref);
+ }
+ }
+}
+
static const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
{
struct tip_table_entry *table = table_;
@@ -308,11 +419,11 @@ static const char *get_exact_ref_match(const struct object *o)
static const char *get_rev_name(const struct object *o, struct strbuf *buf)
{
struct rev_name *n;
- struct commit *c;
+ const struct commit *c;
if (o->type != OBJ_COMMIT)
return get_exact_ref_match(o);
- c = (struct commit *) o;
+ c = (const struct commit *) o;
n = get_commit_rev_name(c);
if (!n)
return NULL;
@@ -320,11 +431,10 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf)
if (!n->generation)
return n->tip_name;
else {
- int len = strlen(n->tip_name);
- if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
- len -= 2;
strbuf_reset(buf);
- strbuf_addf(buf, "%.*s~%d", len, n->tip_name, n->generation);
+ strbuf_addstr(buf, n->tip_name);
+ strbuf_strip_suffix(buf, "^0");
+ strbuf_addf(buf, "~%d", n->generation);
return buf->buf;
}
}
@@ -381,8 +491,7 @@ static void name_rev_line(char *p, struct name_ref_data *data)
*(p+1) = 0;
if (!get_oid(p - (hexsz - 1), &oid)) {
struct object *o =
- lookup_object(the_repository,
- oid.hash);
+ lookup_object(the_repository, &oid);
if (o)
name = get_rev_name(o, &buf);
}
@@ -412,7 +521,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
struct option opts[] = {
- OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
+ OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")),
OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"),
N_("only use refs matching <pattern>")),
@@ -485,9 +594,15 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
add_object_array(object, *argv, &revs);
}
- if (cutoff)
- cutoff = cutoff - CUTOFF_DATE_SLOP;
+ if (cutoff) {
+ /* check for undeflow */
+ if (cutoff > TIME_MIN + CUTOFF_DATE_SLOP)
+ cutoff = cutoff - CUTOFF_DATE_SLOP;
+ else
+ cutoff = TIME_MIN;
+ }
for_each_ref(name_ref, &data);
+ name_tips();
if (transform_stdin) {
char buffer[2048];
diff --git a/builtin/notes.c b/builtin/notes.c
index 02e97f55c5..2987c08a2e 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -406,18 +406,18 @@ static int add(int argc, const char **argv, const char *prefix)
const struct object_id *note;
struct note_data d = { 0, 0, NULL, STRBUF_INIT };
struct option options[] = {
- { OPTION_CALLBACK, 'm', "message", &d, N_("message"),
+ OPT_CALLBACK_F('m', "message", &d, N_("message"),
N_("note contents as a string"), PARSE_OPT_NONEG,
- parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &d, N_("file"),
+ parse_msg_arg),
+ OPT_CALLBACK_F('F', "file", &d, N_("file"),
N_("note contents in a file"), PARSE_OPT_NONEG,
- parse_file_arg},
- { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"),
+ parse_file_arg),
+ OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
N_("reuse and edit specified note object"), PARSE_OPT_NONEG,
- parse_reedit_arg},
- { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"),
+ parse_reedit_arg),
+ OPT_CALLBACK_F('C', "reuse-message", &d, N_("object"),
N_("reuse specified note object"), PARSE_OPT_NONEG,
- parse_reuse_arg},
+ parse_reuse_arg),
OPT_BOOL(0, "allow-empty", &allow_empty,
N_("allow storing empty note")),
OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE),
@@ -513,7 +513,7 @@ static int copy(int argc, const char **argv, const char *prefix)
}
}
- if (argc < 2) {
+ if (argc < 1) {
error(_("too few parameters"));
usage_with_options(git_notes_copy_usage, options);
}
@@ -572,18 +572,18 @@ static int append_edit(int argc, const char **argv, const char *prefix)
const char * const *usage;
struct note_data d = { 0, 0, NULL, STRBUF_INIT };
struct option options[] = {
- { OPTION_CALLBACK, 'm', "message", &d, N_("message"),
+ OPT_CALLBACK_F('m', "message", &d, N_("message"),
N_("note contents as a string"), PARSE_OPT_NONEG,
- parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &d, N_("file"),
+ parse_msg_arg),
+ OPT_CALLBACK_F('F', "file", &d, N_("file"),
N_("note contents in a file"), PARSE_OPT_NONEG,
- parse_file_arg},
- { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"),
+ parse_file_arg),
+ OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"),
N_("reuse and edit specified note object"), PARSE_OPT_NONEG,
- parse_reedit_arg},
- { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"),
+ parse_reedit_arg),
+ OPT_CALLBACK_F('C', "reuse-message", &d, N_("object"),
N_("reuse specified note object"), PARSE_OPT_NONEG,
- parse_reuse_arg},
+ parse_reuse_arg),
OPT_BOOL(0, "allow-empty", &allow_empty,
N_("allow storing empty note")),
OPT_END()
@@ -622,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
strbuf_grow(&d.buf, size + 1);
if (d.buf.len && prev_buf && size)
- strbuf_insert(&d.buf, 0, "\n", 1);
+ strbuf_insertstr(&d.buf, 0, "\n");
if (prev_buf && size)
strbuf_insert(&d.buf, 0, prev_buf, size);
free(prev_buf);
@@ -745,7 +745,7 @@ static int merge_commit(struct notes_merge_options *o)
memset(&pretty_ctx, 0, sizeof(pretty_ctx));
format_commit_message(partial, "%s", &msg, &pretty_ctx);
strbuf_trim(&msg);
- strbuf_insert(&msg, 0, "notes: ", 7);
+ strbuf_insertstr(&msg, 0, "notes: ");
update_ref(msg.buf, o->local_ref, &oid,
is_null_oid(&parent_oid) ? NULL : &parent_oid,
0, UPDATE_REFS_DIE_ON_ERR);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 787ae10288..5617c01b5a 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -26,14 +26,16 @@
#include "pack-bitmap.h"
#include "delta-islands.h"
#include "reachable.h"
-#include "sha1-array.h"
-#include "argv-array.h"
+#include "oid-array.h"
+#include "strvec.h"
#include "list.h"
#include "packfile.h"
#include "object-store.h"
#include "dir.h"
#include "midx.h"
#include "trace2.h"
+#include "shallow.h"
+#include "promisor-remote.h"
#define IN_PACK(obj) oe_in_pack(&to_pack, obj)
#define SIZE(obj) oe_size(&to_pack, obj)
@@ -92,11 +94,16 @@ static struct progress *progress_state;
static struct packed_git *reuse_packfile;
static uint32_t reuse_packfile_objects;
-static off_t reuse_packfile_offset;
+static struct bitmap *reuse_packfile_bitmap;
static int use_bitmap_index_default = 1;
static int use_bitmap_index = -1;
-static int write_bitmap_index;
+static int allow_pack_reuse = 1;
+static enum {
+ WRITE_BITMAP_FALSE = 0,
+ WRITE_BITMAP_QUIET,
+ WRITE_BITMAP_TRUE,
+} write_bitmap_index;
static uint16_t write_bitmap_options = BITMAP_OPT_HASH_CACHE;
static int exclude_promisor_objects;
@@ -111,6 +118,8 @@ static unsigned long window_memory_limit = 0;
static struct list_objects_filter_options filter_options;
+static struct string_list uri_protocols = STRING_LIST_INIT_NODUP;
+
enum missing_action {
MA_ERROR = 0, /* fail if any missing objects are encountered */
MA_ALLOW_ANY, /* silently allow ALL missing objects */
@@ -119,6 +128,15 @@ enum missing_action {
static enum missing_action arg_missing_action;
static show_object_fn fn_show_object;
+struct configured_exclusion {
+ struct oidmap_entry e;
+ char *pack_hash_hex;
+ char *uri;
+};
+static struct oidmap configured_exclusions;
+
+static struct oidset excluded_by_config;
+
/*
* stats
*/
@@ -159,7 +177,7 @@ static void *get_delta(struct object_entry *entry)
delta_buf = diff_delta(base_buf, base_size,
buf, size, &delta_size, 0);
/*
- * We succesfully computed this delta once but dropped it for
+ * We successfully computed this delta once but dropped it for
* memory reasons. Something is very wrong if this time we
* recompute and create a different delta.
*/
@@ -299,7 +317,8 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent
if (!usable_delta) {
if (oe_type(entry) == OBJ_BLOB &&
oe_size_greater_than(&to_pack, entry, big_file_threshold) &&
- (st = open_istream(&entry->idx.oid, &type, &size, NULL)) != NULL)
+ (st = open_istream(the_repository, &entry->idx.oid, &type,
+ &size, NULL)) != NULL)
buf = NULL;
else {
buf = read_object_file(&entry->idx.oid, &type, &size);
@@ -606,12 +625,12 @@ static int mark_tagged(const char *path, const struct object_id *oid, int flag,
void *cb_data)
{
struct object_id peeled;
- struct object_entry *entry = packlist_find(&to_pack, oid->hash, NULL);
+ struct object_entry *entry = packlist_find(&to_pack, oid);
if (entry)
entry->tagged = 1;
if (!peel_ref(path, &peeled)) {
- entry = packlist_find(&to_pack, peeled.hash, NULL);
+ entry = packlist_find(&to_pack, &peeled);
if (entry)
entry->tagged = 1;
}
@@ -780,57 +799,205 @@ static struct object_entry **compute_write_order(void)
return wo;
}
-static off_t write_reused_pack(struct hashfile *f)
+
+/*
+ * A reused set of objects. All objects in a chunk have the same
+ * relative position in the original packfile and the generated
+ * packfile.
+ */
+
+static struct reused_chunk {
+ /* The offset of the first object of this chunk in the original
+ * packfile. */
+ off_t original;
+ /* The offset of the first object of this chunk in the generated
+ * packfile minus "original". */
+ off_t difference;
+} *reused_chunks;
+static int reused_chunks_nr;
+static int reused_chunks_alloc;
+
+static void record_reused_object(off_t where, off_t offset)
{
- unsigned char buffer[8192];
- off_t to_write, total;
- int fd;
+ if (reused_chunks_nr && reused_chunks[reused_chunks_nr-1].difference == offset)
+ return;
- if (!is_pack_valid(reuse_packfile))
- die(_("packfile is invalid: %s"), reuse_packfile->pack_name);
+ ALLOC_GROW(reused_chunks, reused_chunks_nr + 1,
+ reused_chunks_alloc);
+ reused_chunks[reused_chunks_nr].original = where;
+ reused_chunks[reused_chunks_nr].difference = offset;
+ reused_chunks_nr++;
+}
- fd = git_open(reuse_packfile->pack_name);
- if (fd < 0)
- die_errno(_("unable to open packfile for reuse: %s"),
- reuse_packfile->pack_name);
+/*
+ * Binary search to find the chunk that "where" is in. Note
+ * that we're not looking for an exact match, just the first
+ * chunk that contains it (which implicitly ends at the start
+ * of the next chunk.
+ */
+static off_t find_reused_offset(off_t where)
+{
+ int lo = 0, hi = reused_chunks_nr;
+ while (lo < hi) {
+ int mi = lo + ((hi - lo) / 2);
+ if (where == reused_chunks[mi].original)
+ return reused_chunks[mi].difference;
+ if (where < reused_chunks[mi].original)
+ hi = mi;
+ else
+ lo = mi + 1;
+ }
+
+ /*
+ * The first chunk starts at zero, so we can't have gone below
+ * there.
+ */
+ assert(lo);
+ return reused_chunks[lo-1].difference;
+}
- if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1)
- die_errno(_("unable to seek in reused packfile"));
+static void write_reused_pack_one(size_t pos, struct hashfile *out,
+ struct pack_window **w_curs)
+{
+ off_t offset, next, cur;
+ enum object_type type;
+ unsigned long size;
- if (reuse_packfile_offset < 0)
- reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz;
+ offset = reuse_packfile->revindex[pos].offset;
+ next = reuse_packfile->revindex[pos + 1].offset;
- total = to_write = reuse_packfile_offset - sizeof(struct pack_header);
+ record_reused_object(offset, offset - hashfile_total(out));
- while (to_write) {
- int read_pack = xread(fd, buffer, sizeof(buffer));
+ cur = offset;
+ type = unpack_object_header(reuse_packfile, w_curs, &cur, &size);
+ assert(type >= 0);
- if (read_pack <= 0)
- die_errno(_("unable to read from reused packfile"));
+ if (type == OBJ_OFS_DELTA) {
+ off_t base_offset;
+ off_t fixup;
- if (read_pack > to_write)
- read_pack = to_write;
+ unsigned char header[MAX_PACK_OBJECT_HEADER];
+ unsigned len;
- hashwrite(f, buffer, read_pack);
- to_write -= read_pack;
+ base_offset = get_delta_base(reuse_packfile, w_curs, &cur, type, offset);
+ assert(base_offset != 0);
+
+ /* Convert to REF_DELTA if we must... */
+ if (!allow_ofs_delta) {
+ int base_pos = find_revindex_position(reuse_packfile, base_offset);
+ struct object_id base_oid;
+
+ nth_packed_object_id(&base_oid, reuse_packfile,
+ reuse_packfile->revindex[base_pos].nr);
+
+ len = encode_in_pack_object_header(header, sizeof(header),
+ OBJ_REF_DELTA, size);
+ hashwrite(out, header, len);
+ hashwrite(out, base_oid.hash, the_hash_algo->rawsz);
+ copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
+ return;
+ }
+
+ /* Otherwise see if we need to rewrite the offset... */
+ fixup = find_reused_offset(offset) -
+ find_reused_offset(base_offset);
+ if (fixup) {
+ unsigned char ofs_header[10];
+ unsigned i, ofs_len;
+ off_t ofs = offset - base_offset - fixup;
+
+ len = encode_in_pack_object_header(header, sizeof(header),
+ OBJ_OFS_DELTA, size);
+
+ i = sizeof(ofs_header) - 1;
+ ofs_header[i] = ofs & 127;
+ while (ofs >>= 7)
+ ofs_header[--i] = 128 | (--ofs & 127);
+
+ ofs_len = sizeof(ofs_header) - i;
+
+ hashwrite(out, header, len);
+ hashwrite(out, ofs_header + sizeof(ofs_header) - ofs_len, ofs_len);
+ copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur);
+ return;
+ }
+
+ /* ...otherwise we have no fixup, and can write it verbatim */
+ }
+
+ copy_pack_data(out, reuse_packfile, w_curs, offset, next - offset);
+}
+
+static size_t write_reused_pack_verbatim(struct hashfile *out,
+ struct pack_window **w_curs)
+{
+ size_t pos = 0;
+
+ while (pos < reuse_packfile_bitmap->word_alloc &&
+ reuse_packfile_bitmap->words[pos] == (eword_t)~0)
+ pos++;
+
+ if (pos) {
+ off_t to_write;
+
+ written = (pos * BITS_IN_EWORD);
+ to_write = reuse_packfile->revindex[written].offset
+ - sizeof(struct pack_header);
+
+ /* We're recording one chunk, not one object. */
+ record_reused_object(sizeof(struct pack_header), 0);
+ hashflush(out);
+ copy_pack_data(out, reuse_packfile, w_curs,
+ sizeof(struct pack_header), to_write);
- /*
- * We don't know the actual number of objects written,
- * only how many bytes written, how many bytes total, and
- * how many objects total. So we can fake it by pretending all
- * objects we are writing are the same size. This gives us a
- * smooth progress meter, and at the end it matches the true
- * answer.
- */
- written = reuse_packfile_objects *
- (((double)(total - to_write)) / total);
display_progress(progress_state, written);
}
+ return pos;
+}
+
+static void write_reused_pack(struct hashfile *f)
+{
+ size_t i = 0;
+ uint32_t offset;
+ struct pack_window *w_curs = NULL;
+
+ if (allow_ofs_delta)
+ i = write_reused_pack_verbatim(f, &w_curs);
+
+ for (; i < reuse_packfile_bitmap->word_alloc; ++i) {
+ eword_t word = reuse_packfile_bitmap->words[i];
+ size_t pos = (i * BITS_IN_EWORD);
+
+ for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
+ if ((word >> offset) == 0)
+ break;
+
+ offset += ewah_bit_ctz64(word >> offset);
+ write_reused_pack_one(pos + offset, f, &w_curs);
+ display_progress(progress_state, ++written);
+ }
+ }
- close(fd);
- written = reuse_packfile_objects;
- display_progress(progress_state, written);
- return reuse_packfile_offset - sizeof(struct pack_header);
+ unuse_pack(&w_curs);
+}
+
+static void write_excluded_by_configs(void)
+{
+ struct oidset_iter iter;
+ const struct object_id *oid;
+
+ oidset_iter_init(&excluded_by_config, &iter);
+ while ((oid = oidset_iter_next(&iter))) {
+ struct configured_exclusion *ex =
+ oidmap_get(&configured_exclusions, oid);
+
+ if (!ex)
+ BUG("configured exclusion wasn't configured");
+ write_in_full(1, ex->pack_hash_hex, strlen(ex->pack_hash_hex));
+ write_in_full(1, " ", 1);
+ write_in_full(1, ex->uri, strlen(ex->uri));
+ write_in_full(1, "\n", 1);
+ }
}
static const char no_split_warning[] = N_(
@@ -863,11 +1030,9 @@ static void write_pack_file(void)
offset = write_pack_header(f, nr_remaining);
if (reuse_packfile) {
- off_t packfile_size;
assert(pack_to_stdout);
-
- packfile_size = write_reused_pack(f);
- offset += packfile_size;
+ write_reused_pack(f);
+ offset = hashfile_total(f);
}
nr_written = 0;
@@ -892,7 +1057,8 @@ static void write_pack_file(void)
nr_written, oid.hash, offset);
close(fd);
if (write_bitmap_index) {
- warning(_(no_split_warning));
+ if (write_bitmap_index != WRITE_BITMAP_QUIET)
+ warning(_(no_split_warning));
write_bitmap_index = 0;
}
}
@@ -991,12 +1157,15 @@ static int no_try_delta(const char *path)
* few lines later when we want to add the new entry.
*/
static int have_duplicate_entry(const struct object_id *oid,
- int exclude,
- uint32_t *index_pos)
+ int exclude)
{
struct object_entry *entry;
- entry = packlist_find(&to_pack, oid->hash, index_pos);
+ if (reuse_packfile_bitmap &&
+ bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid))
+ return 1;
+
+ entry = packlist_find(&to_pack, oid);
if (!entry)
return 0;
@@ -1128,6 +1297,25 @@ static int want_object_in_pack(const struct object_id *oid,
}
}
+ if (uri_protocols.nr) {
+ struct configured_exclusion *ex =
+ oidmap_get(&configured_exclusions, oid);
+ int i;
+ const char *p;
+
+ if (ex) {
+ for (i = 0; i < uri_protocols.nr; i++) {
+ if (skip_prefix(ex->uri,
+ uri_protocols.items[i].string,
+ &p) &&
+ *p == ':') {
+ oidset_insert(&excluded_by_config, oid);
+ return 0;
+ }
+ }
+ }
+ }
+
return 1;
}
@@ -1136,13 +1324,12 @@ static void create_object_entry(const struct object_id *oid,
uint32_t hash,
int exclude,
int no_try_delta,
- uint32_t index_pos,
struct packed_git *found_pack,
off_t found_offset)
{
struct object_entry *entry;
- entry = packlist_alloc(&to_pack, oid->hash, index_pos);
+ entry = packlist_alloc(&to_pack, oid);
entry->hash = hash;
oe_set_type(entry, type);
if (exclude)
@@ -1166,17 +1353,17 @@ static int add_object_entry(const struct object_id *oid, enum object_type type,
{
struct packed_git *found_pack = NULL;
off_t found_offset = 0;
- uint32_t index_pos;
display_progress(progress_state, ++nr_seen);
- if (have_duplicate_entry(oid, exclude, &index_pos))
+ if (have_duplicate_entry(oid, exclude))
return 0;
if (!want_object_in_pack(oid, exclude, &found_pack, &found_offset)) {
/* The pack is missing an object, so it will not have closure */
if (write_bitmap_index) {
- warning(_(no_closure_warning));
+ if (write_bitmap_index != WRITE_BITMAP_QUIET)
+ warning(_(no_closure_warning));
write_bitmap_index = 0;
}
return 0;
@@ -1184,7 +1371,7 @@ static int add_object_entry(const struct object_id *oid, enum object_type type,
create_object_entry(oid, type, pack_name_hash(name),
exclude, name && no_try_delta(name),
- index_pos, found_pack, found_offset);
+ found_pack, found_offset);
return 1;
}
@@ -1193,17 +1380,15 @@ static int add_object_entry_from_bitmap(const struct object_id *oid,
int flags, uint32_t name_hash,
struct packed_git *pack, off_t offset)
{
- uint32_t index_pos;
-
display_progress(progress_state, ++nr_seen);
- if (have_duplicate_entry(oid, 0, &index_pos))
+ if (have_duplicate_entry(oid, 0))
return 0;
if (!want_object_in_pack(oid, 0, &pack, &offset))
return 0;
- create_object_entry(oid, type, name_hash, 0, 0, index_pos, pack, offset);
+ create_object_entry(oid, type, name_hash, 0, 0, pack, offset);
return 1;
}
@@ -1428,7 +1613,8 @@ static void add_preferred_base(struct object_id *oid)
if (window <= num_preferred_base++)
return;
- data = read_object_with_reference(oid, tree_type, &size, &tree_oid);
+ data = read_object_with_reference(the_repository, oid,
+ tree_type, &size, &tree_oid);
if (!data)
return;
@@ -1484,21 +1670,17 @@ static void cleanup_preferred_base(void)
* deltify other objects against, in order to avoid
* circular deltas.
*/
-static int can_reuse_delta(const unsigned char *base_sha1,
+static int can_reuse_delta(const struct object_id *base_oid,
struct object_entry *delta,
struct object_entry **base_out)
{
struct object_entry *base;
- struct object_id base_oid;
-
- if (!base_sha1)
- return 0;
/*
* First see if we're already sending the base (or it's explicitly in
* our "excluded" list).
*/
- base = packlist_find(&to_pack, base_sha1, NULL);
+ base = packlist_find(&to_pack, base_oid);
if (base) {
if (!in_same_island(&delta->idx.oid, &base->idx.oid))
return 0;
@@ -1511,10 +1693,9 @@ static int can_reuse_delta(const unsigned char *base_sha1,
* even if it was buried too deep in history to make it into the
* packing list.
*/
- oidread(&base_oid, base_sha1);
- if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, &base_oid)) {
+ if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, base_oid)) {
if (use_delta_islands) {
- if (!in_same_island(&delta->idx.oid, &base_oid))
+ if (!in_same_island(&delta->idx.oid, base_oid))
return 0;
}
*base_out = NULL;
@@ -1524,14 +1705,36 @@ static int can_reuse_delta(const unsigned char *base_sha1,
return 0;
}
-static void check_object(struct object_entry *entry)
+static void prefetch_to_pack(uint32_t object_index_start) {
+ struct oid_array to_fetch = OID_ARRAY_INIT;
+ uint32_t i;
+
+ for (i = object_index_start; i < to_pack.nr_objects; i++) {
+ struct object_entry *entry = to_pack.objects + i;
+
+ if (!oid_object_info_extended(the_repository,
+ &entry->idx.oid,
+ NULL,
+ OBJECT_INFO_FOR_PREFETCH))
+ continue;
+ oid_array_append(&to_fetch, &entry->idx.oid);
+ }
+ promisor_remote_get_direct(the_repository,
+ to_fetch.oid, to_fetch.nr);
+ oid_array_clear(&to_fetch);
+}
+
+static void check_object(struct object_entry *entry, uint32_t object_index)
{
unsigned long canonical_size;
+ enum object_type type;
+ struct object_info oi = {.typep = &type, .sizep = &canonical_size};
if (IN_PACK(entry)) {
struct packed_git *p = IN_PACK(entry);
struct pack_window *w_curs = NULL;
- const unsigned char *base_ref = NULL;
+ int have_base = 0;
+ struct object_id base_ref;
struct object_entry *base_entry;
unsigned long used, used_0;
unsigned long avail;
@@ -1572,9 +1775,13 @@ static void check_object(struct object_entry *entry)
unuse_pack(&w_curs);
return;
case OBJ_REF_DELTA:
- if (reuse_delta && !entry->preferred_base)
- base_ref = use_pack(p, &w_curs,
- entry->in_pack_offset + used, NULL);
+ if (reuse_delta && !entry->preferred_base) {
+ oidread(&base_ref,
+ use_pack(p, &w_curs,
+ entry->in_pack_offset + used,
+ NULL));
+ have_base = 1;
+ }
entry->in_pack_header_size = used + the_hash_algo->rawsz;
break;
case OBJ_OFS_DELTA:
@@ -1604,13 +1811,15 @@ static void check_object(struct object_entry *entry)
revidx = find_pack_revindex(p, ofs);
if (!revidx)
goto give_up;
- base_ref = nth_packed_object_sha1(p, revidx->nr);
+ if (!nth_packed_object_id(&base_ref, p, revidx->nr))
+ have_base = 1;
}
entry->in_pack_header_size = used + used_0;
break;
}
- if (can_reuse_delta(base_ref, entry, &base_entry)) {
+ if (have_base &&
+ can_reuse_delta(&base_ref, entry, &base_entry)) {
oe_set_type(entry, entry->in_pack_type);
SET_SIZE(entry, in_pack_size); /* delta size */
SET_DELTA_SIZE(entry, in_pack_size);
@@ -1620,7 +1829,7 @@ static void check_object(struct object_entry *entry)
entry->delta_sibling_idx = base_entry->delta_child_idx;
SET_DELTA_CHILD(base_entry, entry);
} else {
- SET_DELTA_EXT(entry, base_ref);
+ SET_DELTA_EXT(entry, &base_ref);
}
unuse_pack(&w_curs);
@@ -1653,8 +1862,18 @@ static void check_object(struct object_entry *entry)
unuse_pack(&w_curs);
}
- oe_set_type(entry,
- oid_object_info(the_repository, &entry->idx.oid, &canonical_size));
+ if (oid_object_info_extended(the_repository, &entry->idx.oid, &oi,
+ OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_LOOKUP_REPLACE) < 0) {
+ if (has_promisor_remote()) {
+ prefetch_to_pack(object_index);
+ if (oid_object_info_extended(the_repository, &entry->idx.oid, &oi,
+ OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_LOOKUP_REPLACE) < 0)
+ type = -1;
+ } else {
+ type = -1;
+ }
+ }
+ oe_set_type(entry, type);
if (entry->type_valid) {
SET_SIZE(entry, canonical_size);
} else {
@@ -1874,7 +2093,7 @@ static void get_object_details(void)
for (i = 0; i < to_pack.nr_objects; i++) {
struct object_entry *entry = sorted_by_offset[i];
- check_object(entry);
+ check_object(entry, i);
if (entry->type_valid &&
oe_size_greater_than(&to_pack, entry, big_file_threshold))
entry->no_try_delta = 1;
@@ -2334,15 +2553,6 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
free(array);
}
-static void try_to_free_from_threads(size_t size)
-{
- packing_data_lock(&to_pack);
- release_pack_memory(size);
- packing_data_unlock(&to_pack);
-}
-
-static try_to_free_t old_try_to_free_routine;
-
/*
* The main object list is split into smaller lists, each is handed to
* one worker.
@@ -2383,12 +2593,10 @@ static void init_threaded_search(void)
pthread_mutex_init(&cache_mutex, NULL);
pthread_mutex_init(&progress_mutex, NULL);
pthread_cond_init(&progress_cond, NULL);
- old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads);
}
static void cleanup_threaded_search(void)
{
- set_try_to_free_routine(old_try_to_free_routine);
pthread_cond_destroy(&progress_cond);
pthread_mutex_destroy(&cache_mutex);
pthread_mutex_destroy(&progress_mutex);
@@ -2560,6 +2768,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
free(p);
}
+static int obj_is_packed(const struct object_id *oid)
+{
+ return packlist_find(&to_pack, oid) ||
+ (reuse_packfile_bitmap &&
+ bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid));
+}
+
static void add_tag_chain(const struct object_id *oid)
{
struct tag *tag;
@@ -2571,7 +2786,7 @@ static void add_tag_chain(const struct object_id *oid)
* it was included via bitmaps, we would not have parsed it
* previously).
*/
- if (packlist_find(&to_pack, oid->hash, NULL))
+ if (obj_is_packed(oid))
return;
tag = lookup_tag(the_repository, oid);
@@ -2595,7 +2810,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag,
if (starts_with(path, "refs/tags/") && /* is a tag? */
!peel_ref(path, &peeled) && /* peelable? */
- packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */
+ obj_is_packed(&peeled)) /* object packed? */
add_tag_chain(oid);
return 0;
}
@@ -2663,6 +2878,7 @@ static void prepare_pack(int window, int depth)
if (nr_deltas && n > 1) {
unsigned nr_done = 0;
+
if (progress)
progress_state = start_progress(_("Compressing objects"),
nr_deltas);
@@ -2707,8 +2923,8 @@ static int git_pack_config(const char *k, const char *v, void *cb)
use_bitmap_index_default = git_config_bool(k, v);
return 0;
}
- if (!strcmp(k, "pack.usesparse")) {
- sparse = git_config_bool(k, v);
+ if (!strcmp(k, "pack.allowpackreuse")) {
+ allow_pack_reuse = git_config_bool(k, v);
return 0;
}
if (!strcmp(k, "pack.threads")) {
@@ -2729,6 +2945,29 @@ static int git_pack_config(const char *k, const char *v, void *cb)
pack_idx_opts.version);
return 0;
}
+ if (!strcmp(k, "uploadpack.blobpackfileuri")) {
+ struct configured_exclusion *ex = xmalloc(sizeof(*ex));
+ const char *oid_end, *pack_end;
+ /*
+ * Stores the pack hash. This is not a true object ID, but is
+ * of the same form.
+ */
+ struct object_id pack_hash;
+
+ if (parse_oid_hex(v, &ex->e.oid, &oid_end) ||
+ *oid_end != ' ' ||
+ parse_oid_hex(oid_end + 1, &pack_hash, &pack_end) ||
+ *pack_end != ' ')
+ die(_("value of uploadpack.blobpackfileuri must be "
+ "of the form '<object-hash> <pack-hash> <uri>' (got '%s')"), v);
+ if (oidmap_get(&configured_exclusions, &ex->e.oid))
+ die(_("object already configured in another "
+ "uploadpack.blobpackfileuri (got '%s')"), v);
+ ex->pack_hash_hex = xcalloc(1, pack_end - oid_end);
+ memcpy(ex->pack_hash_hex, oid_end + 1, pack_end - oid_end - 1);
+ ex->uri = xstrdup(pack_end + 1);
+ oidmap_put(&configured_exclusions, ex);
+ }
return git_default_config(k, v, cb);
}
@@ -2795,7 +3034,7 @@ static void show_object(struct object *obj, const char *name, void *data)
for (p = strchr(name, '/'); p; p = strchr(p + 1, '/'))
depth++;
- ent = packlist_find(&to_pack, obj->oid.hash, NULL);
+ ent = packlist_find(&to_pack, &obj->oid);
if (ent && depth > oe_tree_depth(&to_pack, ent))
oe_set_tree_depth(&to_pack, ent, depth);
}
@@ -2809,7 +3048,7 @@ static void show_object__ma_allow_any(struct object *obj, const char *name, void
* Quietly ignore ALL missing objects. This avoids problems with
* staging them now and getting an odd error later.
*/
- if (!has_object_file(&obj->oid))
+ if (!has_object(the_repository, &obj->oid, 0))
return;
show_object(obj, name, data);
@@ -2823,7 +3062,7 @@ static void show_object__ma_allow_promisor(struct object *obj, const char *name,
* Quietly ignore EXPECTED missing objects. This avoids problems with
* staging them now and getting an odd error later.
*/
- if (!has_object_file(&obj->oid) && is_promisor_object(&obj->oid))
+ if (!has_object(the_repository, &obj->oid, 0) && is_promisor_object(&obj->oid))
return;
show_object(obj, name, data);
@@ -2899,7 +3138,7 @@ static int ofscmp(const void *a_, const void *b_)
return oidcmp(&a->object->oid, &b->object->oid);
}
-static void add_objects_in_unpacked_packs(struct rev_info *revs)
+static void add_objects_in_unpacked_packs(void)
{
struct packed_git *p;
struct in_pack in_pack;
@@ -2921,8 +3160,8 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
in_pack.alloc);
for (i = 0; i < p->num_objects; i++) {
- nth_packed_object_oid(&oid, p, i);
- o = lookup_unknown_object(oid.hash);
+ nth_packed_object_id(&oid, p, i);
+ o = lookup_unknown_object(&oid);
if (!(o->flags & OBJECT_ADDED))
mark_in_pack_object(o, p, &in_pack);
o->flags |= OBJECT_ADDED;
@@ -3011,7 +3250,7 @@ static int loosened_object_can_be_discarded(const struct object_id *oid,
return 1;
}
-static void loosen_unused_packed_objects(struct rev_info *revs)
+static void loosen_unused_packed_objects(void)
{
struct packed_git *p;
uint32_t i;
@@ -3025,8 +3264,8 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
die(_("cannot open pack index"));
for (i = 0; i < p->num_objects; i++) {
- nth_packed_object_oid(&oid, p, i);
- if (!packlist_find(&to_pack, oid.hash, NULL) &&
+ nth_packed_object_id(&oid, p, i);
+ if (!packlist_find(&to_pack, &oid) &&
!has_sha1_pack_kept_or_nonlocal(&oid) &&
!loosened_object_can_be_discarded(&oid, p->mtime))
if (force_object_loose(&oid, p->mtime))
@@ -3042,8 +3281,8 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
*/
static int pack_options_allow_reuse(void)
{
- return pack_to_stdout &&
- allow_ofs_delta &&
+ return allow_pack_reuse &&
+ pack_to_stdout &&
!ignore_packed_keep_on_disk &&
!ignore_packed_keep_in_core &&
(!local || !have_non_local_packs) &&
@@ -3052,7 +3291,7 @@ static int pack_options_allow_reuse(void)
static int get_object_list_from_bitmap(struct rev_info *revs)
{
- if (!(bitmap_git = prepare_bitmap_walk(revs)))
+ if (!(bitmap_git = prepare_bitmap_walk(revs, &filter_options)))
return -1;
if (pack_options_allow_reuse() &&
@@ -3060,13 +3299,14 @@ static int get_object_list_from_bitmap(struct rev_info *revs)
bitmap_git,
&reuse_packfile,
&reuse_packfile_objects,
- &reuse_packfile_offset)) {
+ &reuse_packfile_bitmap)) {
assert(reuse_packfile_objects);
nr_result += reuse_packfile_objects;
display_progress(progress_state, nr_result);
}
- traverse_bitmap_commit_list(bitmap_git, &add_object_entry_from_bitmap);
+ traverse_bitmap_commit_list(bitmap_git, revs,
+ &add_object_entry_from_bitmap);
return 0;
}
@@ -3117,7 +3357,7 @@ static void get_object_list(int ac, const char **av)
if (starts_with(line, "--shallow ")) {
struct object_id oid;
if (get_oid_hex(line + 10, &oid))
- die("not an SHA-1 '%s'", line + 10);
+ die("not an object name '%s'", line + 10);
register_shallow(the_repository, &oid);
use_bitmap_index = 0;
continue;
@@ -3158,11 +3398,11 @@ static void get_object_list(int ac, const char **av)
}
if (keep_unreachable)
- add_objects_in_unpacked_packs(&revs);
+ add_objects_in_unpacked_packs();
if (pack_loose_unreachable)
add_unreachable_loose_objects();
if (unpack_unreachable)
- loosen_unused_packed_objects(&revs);
+ loosen_unused_packed_objects();
oid_array_clear(&recent_objects);
}
@@ -3231,7 +3471,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
int use_internal_rev_list = 0;
int shallow = 0;
int all_progress_implied = 0;
- struct argv_array rp = ARGV_ARRAY_INIT;
+ struct strvec rp = STRVEC_INIT;
int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
int rev_list_index = 0;
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
@@ -3245,9 +3485,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "all-progress-implied",
&all_progress_implied,
N_("similar to --all-progress when progress meter is shown")),
- { OPTION_CALLBACK, 0, "index-version", NULL, N_("<version>[,<offset>]"),
+ OPT_CALLBACK_F(0, "index-version", NULL, N_("<version>[,<offset>]"),
N_("write the pack index file in the specified idx format version"),
- PARSE_OPT_NONEG, option_parse_index_version },
+ PARSE_OPT_NONEG, option_parse_index_version),
OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit,
N_("maximum size of each output pack file")),
OPT_BOOL(0, "local", &local,
@@ -3292,9 +3532,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
N_("keep unreachable objects")),
OPT_BOOL(0, "pack-loose-unreachable", &pack_loose_unreachable,
N_("pack loose unreachable objects")),
- { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"),
+ OPT_CALLBACK_F(0, "unpack-unreachable", NULL, N_("time"),
N_("unpack unreachable objects newer than <time>"),
- PARSE_OPT_OPTARG, option_parse_unpack_unreachable },
+ PARSE_OPT_OPTARG, option_parse_unpack_unreachable),
OPT_BOOL(0, "sparse", &sparse,
N_("use the sparse reachability algorithm")),
OPT_BOOL(0, "thin", &thin,
@@ -3311,16 +3551,24 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
N_("do not hide commits by grafts"), 0),
OPT_BOOL(0, "use-bitmap-index", &use_bitmap_index,
N_("use a bitmap index if available to speed up counting objects")),
- OPT_BOOL(0, "write-bitmap-index", &write_bitmap_index,
- N_("write a bitmap index together with the pack index")),
+ OPT_SET_INT(0, "write-bitmap-index", &write_bitmap_index,
+ N_("write a bitmap index together with the pack index"),
+ WRITE_BITMAP_TRUE),
+ OPT_SET_INT_F(0, "write-bitmap-index-quiet",
+ &write_bitmap_index,
+ N_("write a bitmap index if possible"),
+ WRITE_BITMAP_QUIET, PARSE_OPT_HIDDEN),
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
- { OPTION_CALLBACK, 0, "missing", NULL, N_("action"),
+ OPT_CALLBACK_F(0, "missing", NULL, N_("action"),
N_("handling for missing objects"), PARSE_OPT_NONEG,
- option_parse_missing_action },
+ option_parse_missing_action),
OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects,
N_("do not pack objects in promisor packfiles")),
OPT_BOOL(0, "delta-islands", &use_delta_islands,
N_("respect islands during delta compression")),
+ OPT_STRING_LIST(0, "uri-protocol", &uri_protocols,
+ N_("protocol"),
+ N_("exclude any configured uploadpack.blobpackfileuri with this protocol")),
OPT_END(),
};
@@ -3329,7 +3577,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
read_replace_refs = 0;
- sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0);
+ sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
+ prepare_repo_settings(the_repository);
+ if (sparse < 0)
+ sparse = the_repository->settings.pack_use_sparse;
+
reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
@@ -3355,36 +3607,36 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
cache_max_small_delta_size = (1U << OE_Z_DELTA_BITS) - 1;
}
- argv_array_push(&rp, "pack-objects");
+ strvec_push(&rp, "pack-objects");
if (thin) {
use_internal_rev_list = 1;
- argv_array_push(&rp, shallow
+ strvec_push(&rp, shallow
? "--objects-edge-aggressive"
: "--objects-edge");
} else
- argv_array_push(&rp, "--objects");
+ strvec_push(&rp, "--objects");
if (rev_list_all) {
use_internal_rev_list = 1;
- argv_array_push(&rp, "--all");
+ strvec_push(&rp, "--all");
}
if (rev_list_reflog) {
use_internal_rev_list = 1;
- argv_array_push(&rp, "--reflog");
+ strvec_push(&rp, "--reflog");
}
if (rev_list_index) {
use_internal_rev_list = 1;
- argv_array_push(&rp, "--indexed-objects");
+ strvec_push(&rp, "--indexed-objects");
}
if (rev_list_unpacked) {
use_internal_rev_list = 1;
- argv_array_push(&rp, "--unpacked");
+ strvec_push(&rp, "--unpacked");
}
if (exclude_promisor_objects) {
use_internal_rev_list = 1;
fetch_if_missing = 0;
- argv_array_push(&rp, "--exclude-promisor-objects");
+ strvec_push(&rp, "--exclude-promisor-objects");
}
if (unpack_unreachable || keep_unreachable || pack_loose_unreachable)
use_internal_rev_list = 1;
@@ -3421,7 +3673,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (filter_options.choice) {
if (!pack_to_stdout)
die(_("cannot use --filter without --stdout"));
- use_bitmap_index = 0;
}
/*
@@ -3447,7 +3698,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
write_bitmap_index = 0;
if (use_delta_islands)
- argv_array_push(&rp, "--topo-order");
+ strvec_push(&rp, "--topo-order");
if (progress && all_progress_implied)
progress = 2;
@@ -3485,8 +3736,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (!use_internal_rev_list)
read_object_list_from_stdin();
else {
- get_object_list(rp.argc, rp.argv);
- argv_array_clear(&rp);
+ get_object_list(rp.nr, rp.v);
+ strvec_clear(&rp);
}
cleanup_preferred_base();
if (include_tag && nr_result)
@@ -3506,13 +3757,16 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
}
trace2_region_enter("pack-objects", "write-pack-file", the_repository);
+ write_excluded_by_configs();
write_pack_file();
trace2_region_leave("pack-objects", "write-pack-file", the_repository);
if (progress)
fprintf_ln(stderr,
_("Total %"PRIu32" (delta %"PRIu32"),"
- " reused %"PRIu32" (delta %"PRIu32")"),
- written, written_delta, reused, reused_delta);
+ " reused %"PRIu32" (delta %"PRIu32"),"
+ " pack-reused %"PRIu32),
+ written, written_delta, reused, reused_delta,
+ reuse_packfile_objects);
return 0;
}
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c
index 178e3409b7..3e70f2a4c1 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -236,7 +236,7 @@ static struct pack_list * pack_list_difference(const struct pack_list *A,
static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
{
- unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
+ size_t p1_off = 0, p2_off = 0, p1_step, p2_step;
const unsigned char *p1_base, *p2_base;
struct llist_item *p1_hint = NULL, *p2_hint = NULL;
const unsigned int hashsz = the_hash_algo->rawsz;
@@ -280,7 +280,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
{
size_t ret = 0;
- unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step;
+ size_t p1_off = 0, p2_off = 0, p1_step, p2_step;
const unsigned char *p1_base, *p2_base;
const unsigned int hashsz = the_hash_algo->rawsz;
@@ -499,7 +499,7 @@ static void scan_alt_odb_packs(void)
static struct pack_list * add_pack(struct packed_git *p)
{
struct pack_list l;
- unsigned long off = 0, step;
+ size_t off = 0, step;
const unsigned char *base;
if (!p->pack_local && !(alt_odb || verbose))
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index 970d0d30b4..822ffff51f 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -1,15 +1,12 @@
+#include "cache.h"
#include "builtin.h"
#include "config.h"
+#include "diff.h"
static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result)
{
- char name[50];
-
- if (!patchlen)
- return;
-
- memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1);
- printf("%s %s\n", oid_to_hex(result), name);
+ if (patchlen)
+ printf("%s %s\n", oid_to_hex(result), oid_to_hex(id));
}
static int remove_space(char *line)
@@ -54,30 +51,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
return 1;
}
-static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx)
-{
- unsigned char hash[GIT_MAX_RAWSZ];
- unsigned short carry = 0;
- int i;
-
- git_SHA1_Final(hash, ctx);
- git_SHA1_Init(ctx);
- /* 20-byte sum, with carry */
- for (i = 0; i < GIT_SHA1_RAWSZ; ++i) {
- carry += result->hash[i] + hash[i];
- result->hash[i] = carry;
- carry >>= 8;
- }
-}
-
static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
struct strbuf *line_buf, int stable)
{
int patchlen = 0, found_next = 0;
int before = -1, after = -1;
- git_SHA_CTX ctx;
+ git_hash_ctx ctx;
- git_SHA1_Init(&ctx);
+ the_hash_algo->init_fn(&ctx);
oidclr(result);
while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
@@ -137,7 +118,7 @@ static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
/* Compute the sha without whitespace */
len = remove_space(line);
patchlen += len;
- git_SHA1_Update(&ctx, line, len);
+ the_hash_algo->update_fn(&ctx, line, len);
}
if (!found_next)
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
index 48c5e78e33..b7b9281a8c 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -1,54 +1,12 @@
#include "builtin.h"
-#include "cache.h"
-#include "progress.h"
#include "parse-options.h"
-#include "packfile.h"
-#include "object-store.h"
+#include "prune-packed.h"
static const char * const prune_packed_usage[] = {
N_("git prune-packed [-n | --dry-run] [-q | --quiet]"),
NULL
};
-static struct progress *progress;
-
-static int prune_subdir(unsigned int nr, const char *path, void *data)
-{
- int *opts = data;
- display_progress(progress, nr + 1);
- if (!(*opts & PRUNE_PACKED_DRY_RUN))
- rmdir(path);
- return 0;
-}
-
-static int prune_object(const struct object_id *oid, const char *path,
- void *data)
-{
- int *opts = data;
-
- if (!has_object_pack(oid))
- return 0;
-
- if (*opts & PRUNE_PACKED_DRY_RUN)
- printf("rm -f %s\n", path);
- else
- unlink_or_warn(path);
- return 0;
-}
-
-void prune_packed_objects(int opts)
-{
- if (opts & PRUNE_PACKED_VERBOSE)
- progress = start_delayed_progress(_("Removing duplicate objects"), 256);
-
- for_each_loose_file_in_objdir(get_object_directory(),
- prune_object, NULL, prune_subdir, &opts);
-
- /* Ensure we show 100% before finishing progress */
- display_progress(progress, 256);
- stop_progress(&progress);
-}
-
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0;
diff --git a/builtin/prune.c b/builtin/prune.c
index 97613eccb5..02c6ab7cba 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -6,7 +6,9 @@
#include "reachable.h"
#include "parse-options.h"
#include "progress.h"
+#include "prune-packed.h"
#include "object-store.h"
+#include "shallow.h"
static const char * const prune_usage[] = {
N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"),
@@ -53,7 +55,7 @@ static int is_object_reachable(const struct object_id *oid,
perform_reachability_traversal(revs);
- obj = lookup_object(the_repository, oid->hash);
+ obj = lookup_object(the_repository, oid);
return obj && (obj->flags & SEEN);
}
diff --git a/builtin/pull.c b/builtin/pull.c
index 9dd32a115b..1034372f8b 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -12,9 +12,10 @@
#include "parse-options.h"
#include "exec-cmd.h"
#include "run-command.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "remote.h"
#include "dir.h"
+#include "rebase.h"
#include "refs.h"
#include "refspec.h"
#include "revision.h"
@@ -26,15 +27,6 @@
#include "commit-reach.h"
#include "sequencer.h"
-enum rebase_type {
- REBASE_INVALID = -1,
- REBASE_FALSE = 0,
- REBASE_TRUE,
- REBASE_PRESERVE,
- REBASE_MERGES,
- REBASE_INTERACTIVE
-};
-
/**
* 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
@@ -45,22 +37,9 @@ enum rebase_type {
static enum rebase_type parse_config_rebase(const char *key, const char *value,
int fatal)
{
- int v = git_parse_maybe_bool(value);
-
- if (!v)
- return REBASE_FALSE;
- else if (v > 0)
- return REBASE_TRUE;
- else if (!strcmp(value, "preserve") || !strcmp(value, "p"))
- return REBASE_PRESERVE;
- else if (!strcmp(value, "merges") || !strcmp(value, "m"))
- return REBASE_MERGES;
- else if (!strcmp(value, "interactive") || !strcmp(value, "i"))
- return REBASE_INTERACTIVE;
- /*
- * Please update _git_config() in git-completion.bash when you
- * add new rebase modes.
- */
+ enum rebase_type v = rebase_parse_value(value);
+ if (v != REBASE_INVALID)
+ return v;
if (fatal)
die(_("Invalid value for %s: %s"), key, value);
@@ -107,8 +86,9 @@ static char *opt_ff;
static char *opt_verify_signatures;
static int opt_autostash = -1;
static int config_autostash;
-static struct argv_array opt_strategies = ARGV_ARRAY_INIT;
-static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT;
+static int check_trust_level = 1;
+static struct strvec opt_strategies = STRVEC_INIT;
+static struct strvec opt_strategy_opts = STRVEC_INIT;
static char *opt_gpg_sign;
static int opt_allow_unrelated_histories;
@@ -128,6 +108,9 @@ static char *opt_update_shallow;
static char *opt_refmap;
static char *opt_ipv4;
static char *opt_ipv6;
+static int opt_show_forced_updates = -1;
+static char *set_upstream;
+static struct strvec opt_fetch = STRVEC_INIT;
static struct option pull_options[] = {
/* Shared options */
@@ -135,17 +118,17 @@ static struct option pull_options[] = {
OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
N_("force progress reporting"),
PARSE_OPT_NOARG),
- { OPTION_CALLBACK, 0, "recurse-submodules",
+ OPT_CALLBACK_F(0, "recurse-submodules",
&recurse_submodules, N_("on-demand"),
N_("control for recursive fetching of submodules"),
- PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules },
+ PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
/* Options passed to git-merge or git-rebase */
OPT_GROUP(N_("Options related to merging")),
- { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
+ OPT_CALLBACK_F('r', "rebase", &opt_rebase,
"(false|true|merges|preserve|interactive)",
N_("incorporate changes by rebasing rather than merging"),
- PARSE_OPT_OPTARG, parse_opt_rebase },
+ 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),
@@ -159,7 +142,7 @@ static struct option pull_options[] = {
N_("add (at most <n>) entries from shortlog to merge commit message"),
PARSE_OPT_OPTARG),
OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL,
- N_("add Signed-off-by:"),
+ N_("add a Signed-off-by trailer"),
PARSE_OPT_OPTARG),
OPT_PASSTHRU(0, "squash", &opt_squash, NULL,
N_("create a single commit instead of doing a merge"),
@@ -181,7 +164,7 @@ static struct option pull_options[] = {
N_("verify that the named commit has a valid GPG signature"),
PARSE_OPT_NOARG),
OPT_BOOL(0, "autostash", &opt_autostash,
- N_("automatically stash/stash pop before and after rebase")),
+ N_("automatically stash/stash pop before and after")),
OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"),
N_("merge strategy to use"),
0),
@@ -225,6 +208,15 @@ static struct option pull_options[] = {
OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
N_("deepen history of shallow clone"),
0),
+ OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"),
+ N_("deepen history of shallow repository based on time"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("revision"),
+ N_("deepen history of shallow clone, excluding rev"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"),
+ 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),
@@ -234,12 +226,24 @@ static struct option pull_options[] = {
OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
N_("specify fetch refmap"),
PARSE_OPT_NONEG),
+ OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch,
+ N_("server-specific"),
+ N_("option to transmit"),
+ 0),
OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL,
N_("use IPv4 addresses only"),
PARSE_OPT_NOARG),
OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL,
N_("use IPv6 addresses only"),
PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"),
+ N_("report that we have only objects reachable from this object"),
+ 0),
+ OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
+ N_("check for forced-updates on all updated branches")),
+ OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
+ N_("set upstream for git pull/fetch"),
+ PARSE_OPT_NOARG),
OPT_END()
};
@@ -247,25 +251,25 @@ static struct option pull_options[] = {
/**
* Pushes "-q" or "-v" switches into arr to match the opt_verbosity level.
*/
-static void argv_push_verbosity(struct argv_array *arr)
+static void argv_push_verbosity(struct strvec *arr)
{
int verbosity;
for (verbosity = opt_verbosity; verbosity > 0; verbosity--)
- argv_array_push(arr, "-v");
+ strvec_push(arr, "-v");
for (verbosity = opt_verbosity; verbosity < 0; verbosity++)
- argv_array_push(arr, "-q");
+ strvec_push(arr, "-q");
}
/**
* Pushes "-f" switches into arr to match the opt_force level.
*/
-static void argv_push_force(struct argv_array *arr)
+static void argv_push_force(struct strvec *arr)
{
int force = opt_force;
while (force-- > 0)
- argv_array_push(arr, "-f");
+ strvec_push(arr, "-f");
}
/**
@@ -340,6 +344,21 @@ static enum rebase_type config_get_rebase(void)
if (!git_config_get_value("pull.rebase", &value))
return parse_config_rebase("pull.rebase", value, 1);
+ if (opt_verbosity >= 0 && !opt_ff) {
+ advise(_("Pulling without specifying how to reconcile divergent branches is\n"
+ "discouraged. You can squelch this message by running one of the following\n"
+ "commands sometime before your next pull:\n"
+ "\n"
+ " git config pull.rebase false # merge (the default strategy)\n"
+ " git config pull.rebase true # rebase\n"
+ " git config pull.ff only # fast-forward only\n"
+ "\n"
+ "You can replace \"git config\" with \"git config --global\" to set a default\n"
+ "preference for all repositories. You can also pass --rebase, --no-rebase,\n"
+ "or --ff-only on the command line to override the configured default per\n"
+ "invocation.\n"));
+ }
+
return REBASE_FALSE;
}
@@ -348,6 +367,8 @@ static enum rebase_type config_get_rebase(void)
*/
static int git_pull_config(const char *var, const char *value, void *cb)
{
+ int status;
+
if (!strcmp(var, "rebase.autostash")) {
config_autostash = git_config_bool(var, value);
return 0;
@@ -355,7 +376,14 @@ static int git_pull_config(const char *var, const char *value, void *cb)
recurse_submodules = git_config_bool(var, value) ?
RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF;
return 0;
+ } else if (!strcmp(var, "gpg.mintrustlevel")) {
+ check_trust_level = 0;
}
+
+ status = git_gpg_config(var, value, cb);
+ if (status)
+ return status;
+
return git_default_config(var, value, cb);
}
@@ -495,68 +523,75 @@ static void parse_repo_refspecs(int argc, const char **argv, const char **repo,
*/
static int run_fetch(const char *repo, const char **refspecs)
{
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
int ret;
- argv_array_pushl(&args, "fetch", "--update-head-ok", NULL);
+ strvec_pushl(&args, "fetch", "--update-head-ok", NULL);
/* Shared options */
argv_push_verbosity(&args);
if (opt_progress)
- argv_array_push(&args, opt_progress);
+ strvec_push(&args, opt_progress);
/* Options passed to git-fetch */
if (opt_all)
- argv_array_push(&args, opt_all);
+ strvec_push(&args, opt_all);
if (opt_append)
- argv_array_push(&args, opt_append);
+ strvec_push(&args, opt_append);
if (opt_upload_pack)
- argv_array_push(&args, opt_upload_pack);
+ strvec_push(&args, opt_upload_pack);
argv_push_force(&args);
if (opt_tags)
- argv_array_push(&args, opt_tags);
+ strvec_push(&args, opt_tags);
if (opt_prune)
- argv_array_push(&args, opt_prune);
+ strvec_push(&args, opt_prune);
if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT)
switch (recurse_submodules) {
case RECURSE_SUBMODULES_ON:
- argv_array_push(&args, "--recurse-submodules=on");
+ strvec_push(&args, "--recurse-submodules=on");
break;
case RECURSE_SUBMODULES_OFF:
- argv_array_push(&args, "--recurse-submodules=no");
+ strvec_push(&args, "--recurse-submodules=no");
break;
case RECURSE_SUBMODULES_ON_DEMAND:
- argv_array_push(&args, "--recurse-submodules=on-demand");
+ strvec_push(&args, "--recurse-submodules=on-demand");
break;
default:
BUG("submodule recursion option not understood");
}
if (max_children)
- argv_array_push(&args, max_children);
+ strvec_push(&args, max_children);
if (opt_dry_run)
- argv_array_push(&args, "--dry-run");
+ strvec_push(&args, "--dry-run");
if (opt_keep)
- argv_array_push(&args, opt_keep);
+ strvec_push(&args, opt_keep);
if (opt_depth)
- argv_array_push(&args, opt_depth);
+ strvec_push(&args, opt_depth);
if (opt_unshallow)
- argv_array_push(&args, opt_unshallow);
+ strvec_push(&args, opt_unshallow);
if (opt_update_shallow)
- argv_array_push(&args, opt_update_shallow);
+ strvec_push(&args, opt_update_shallow);
if (opt_refmap)
- argv_array_push(&args, opt_refmap);
+ strvec_push(&args, opt_refmap);
if (opt_ipv4)
- argv_array_push(&args, opt_ipv4);
+ strvec_push(&args, opt_ipv4);
if (opt_ipv6)
- argv_array_push(&args, opt_ipv6);
+ strvec_push(&args, opt_ipv6);
+ if (opt_show_forced_updates > 0)
+ strvec_push(&args, "--show-forced-updates");
+ else if (opt_show_forced_updates == 0)
+ strvec_push(&args, "--no-show-forced-updates");
+ if (set_upstream)
+ strvec_push(&args, set_upstream);
+ strvec_pushv(&args, opt_fetch.v);
if (repo) {
- argv_array_push(&args, repo);
- argv_array_pushv(&args, refspecs);
+ strvec_push(&args, repo);
+ strvec_pushv(&args, refspecs);
} else if (*refspecs)
BUG("refspecs without repo?");
- ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
- argv_array_clear(&args);
+ ret = run_command_v_opt(args.v, RUN_GIT_CMD);
+ strvec_clear(&args);
return ret;
}
@@ -574,7 +609,8 @@ static int pull_into_void(const struct object_id *merge_head,
die(_("unable to access commit %s"),
oid_to_hex(merge_head));
- verify_merge_signature(commit, opt_verbosity);
+ verify_merge_signature(commit, opt_verbosity,
+ check_trust_level);
}
/*
@@ -600,8 +636,8 @@ static int rebase_submodules(void)
cp.git_cmd = 1;
cp.no_stdin = 1;
- argv_array_pushl(&cp.args, "submodule", "update",
- "--recursive", "--rebase", NULL);
+ strvec_pushl(&cp.args, "submodule", "update",
+ "--recursive", "--rebase", NULL);
argv_push_verbosity(&cp.args);
return run_command(&cp);
@@ -613,8 +649,8 @@ static int update_submodules(void)
cp.git_cmd = 1;
cp.no_stdin = 1;
- argv_array_pushl(&cp.args, "submodule", "update",
- "--recursive", "--checkout", NULL);
+ strvec_pushl(&cp.args, "submodule", "update",
+ "--recursive", "--checkout", NULL);
argv_push_verbosity(&cp.args);
return run_command(&cp);
@@ -626,44 +662,48 @@ static int update_submodules(void)
static int run_merge(void)
{
int ret;
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
- argv_array_pushl(&args, "merge", NULL);
+ strvec_pushl(&args, "merge", NULL);
/* Shared options */
argv_push_verbosity(&args);
if (opt_progress)
- argv_array_push(&args, opt_progress);
+ strvec_push(&args, opt_progress);
/* Options passed to git-merge */
if (opt_diffstat)
- argv_array_push(&args, opt_diffstat);
+ strvec_push(&args, opt_diffstat);
if (opt_log)
- argv_array_push(&args, opt_log);
+ strvec_push(&args, opt_log);
if (opt_signoff)
- argv_array_push(&args, opt_signoff);
+ strvec_push(&args, opt_signoff);
if (opt_squash)
- argv_array_push(&args, opt_squash);
+ strvec_push(&args, opt_squash);
if (opt_commit)
- argv_array_push(&args, opt_commit);
+ strvec_push(&args, opt_commit);
if (opt_edit)
- argv_array_push(&args, opt_edit);
+ strvec_push(&args, opt_edit);
if (cleanup_arg)
- argv_array_pushf(&args, "--cleanup=%s", cleanup_arg);
+ strvec_pushf(&args, "--cleanup=%s", cleanup_arg);
if (opt_ff)
- argv_array_push(&args, opt_ff);
+ strvec_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);
+ strvec_push(&args, opt_verify_signatures);
+ strvec_pushv(&args, opt_strategies.v);
+ strvec_pushv(&args, opt_strategy_opts.v);
if (opt_gpg_sign)
- argv_array_push(&args, opt_gpg_sign);
+ strvec_push(&args, opt_gpg_sign);
+ if (opt_autostash == 0)
+ strvec_push(&args, "--no-autostash");
+ else if (opt_autostash == 1)
+ strvec_push(&args, "--autostash");
if (opt_allow_unrelated_histories > 0)
- argv_array_push(&args, "--allow-unrelated-histories");
+ strvec_push(&args, "--allow-unrelated-histories");
- argv_array_push(&args, "FETCH_HEAD");
- ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
- argv_array_clear(&args);
+ strvec_push(&args, "FETCH_HEAD");
+ ret = run_command_v_opt(args.v, RUN_GIT_CMD);
+ strvec_clear(&args);
return ret;
}
@@ -760,8 +800,8 @@ static int get_rebase_fork_point(struct object_id *fork_point, const char *repo,
if (!remote_branch)
return -1;
- argv_array_pushl(&cp.args, "merge-base", "--fork-point",
- remote_branch, curr_branch->name, NULL);
+ strvec_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;
@@ -821,48 +861,48 @@ static int run_rebase(const struct object_id *curr_head,
{
int ret;
struct object_id oct_merge_base;
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
if (!get_octopus_merge_base(&oct_merge_base, curr_head, merge_head, fork_point))
if (!is_null_oid(fork_point) && oideq(&oct_merge_base, fork_point))
fork_point = NULL;
- argv_array_push(&args, "rebase");
+ strvec_push(&args, "rebase");
/* Shared options */
argv_push_verbosity(&args);
/* Options passed to git-rebase */
if (opt_rebase == REBASE_MERGES)
- argv_array_push(&args, "--rebase-merges");
+ strvec_push(&args, "--rebase-merges");
else if (opt_rebase == REBASE_PRESERVE)
- argv_array_push(&args, "--preserve-merges");
+ strvec_push(&args, "--preserve-merges");
else if (opt_rebase == REBASE_INTERACTIVE)
- argv_array_push(&args, "--interactive");
+ strvec_push(&args, "--interactive");
if (opt_diffstat)
- argv_array_push(&args, opt_diffstat);
- argv_array_pushv(&args, opt_strategies.argv);
- argv_array_pushv(&args, opt_strategy_opts.argv);
+ strvec_push(&args, opt_diffstat);
+ strvec_pushv(&args, opt_strategies.v);
+ strvec_pushv(&args, opt_strategy_opts.v);
if (opt_gpg_sign)
- argv_array_push(&args, opt_gpg_sign);
+ strvec_push(&args, opt_gpg_sign);
if (opt_autostash == 0)
- argv_array_push(&args, "--no-autostash");
+ strvec_push(&args, "--no-autostash");
else if (opt_autostash == 1)
- argv_array_push(&args, "--autostash");
+ strvec_push(&args, "--autostash");
if (opt_verify_signatures &&
!strcmp(opt_verify_signatures, "--verify-signatures"))
warning(_("ignoring --verify-signatures for rebase"));
- argv_array_push(&args, "--onto");
- argv_array_push(&args, oid_to_hex(merge_head));
+ strvec_push(&args, "--onto");
+ strvec_push(&args, oid_to_hex(merge_head));
if (fork_point && !is_null_oid(fork_point))
- argv_array_push(&args, oid_to_hex(fork_point));
+ strvec_push(&args, oid_to_hex(fork_point));
else
- argv_array_push(&args, oid_to_hex(merge_head));
+ strvec_push(&args, oid_to_hex(merge_head));
- ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
- argv_array_clear(&args);
+ ret = run_command_v_opt(args.v, RUN_GIT_CMD);
+ strvec_clear(&args);
return ret;
}
@@ -905,9 +945,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (get_oid("HEAD", &orig_head))
oidclr(&orig_head);
- if (!opt_rebase && opt_autostash != -1)
- die(_("--[no-]autostash option is only valid with --rebase."));
-
autostash = config_autostash;
if (opt_rebase) {
if (opt_autostash != -1)
@@ -973,6 +1010,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (opt_rebase) {
int ret = 0;
+ int ran_ff = 0;
if ((recurse_submodules == RECURSE_SUBMODULES_ON ||
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&
submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head))
@@ -986,13 +1024,17 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
commit_list_insert(head, &list);
merge_head = lookup_commit_reference(the_repository,
&merge_heads.oid[0]);
- if (is_descendant_of(merge_head, list)) {
+ if (repo_is_descendant_of(the_repository,
+ merge_head, list)) {
/* we can fast-forward this without invoking rebase */
opt_ff = "--ff-only";
+ ran_ff = 1;
ret = run_merge();
}
+ free_commit_list(list);
}
- ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point);
+ if (!ran_ff)
+ ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point);
if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))
diff --git a/builtin/push.c b/builtin/push.c
index 021dd3b1e4..03adb58602 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -61,40 +61,41 @@ static struct refspec rs = REFSPEC_INIT_PUSH;
static struct string_list push_options_config = STRING_LIST_INIT_DUP;
-static const char *map_refspec(const char *ref,
- struct remote *remote, struct ref *local_refs)
+static void refspec_append_mapped(struct refspec *refspec, const char *ref,
+ struct remote *remote, struct ref *local_refs)
{
+ const char *branch_name;
struct ref *matched = NULL;
/* Does "ref" uniquely name our ref? */
- if (count_refspec_match(ref, local_refs, &matched) != 1)
- return ref;
+ if (count_refspec_match(ref, local_refs, &matched) != 1) {
+ refspec_append(refspec, ref);
+ return;
+ }
if (remote->push.nr) {
struct refspec_item query;
memset(&query, 0, sizeof(struct refspec_item));
query.src = matched->name;
if (!query_refspecs(&remote->push, &query) && query.dst) {
- struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%s%s:%s",
- query.force ? "+" : "",
- query.src, query.dst);
- return strbuf_detach(&buf, NULL);
+ refspec_appendf(refspec, "%s%s:%s",
+ query.force ? "+" : "",
+ query.src, query.dst);
+ return;
}
}
if (push_default == PUSH_DEFAULT_UPSTREAM &&
- starts_with(matched->name, "refs/heads/")) {
- struct branch *branch = branch_get(matched->name + 11);
+ skip_prefix(matched->name, "refs/heads/", &branch_name)) {
+ struct branch *branch = branch_get(branch_name);
if (branch->merge_nr == 1 && branch->merge[0]->src) {
- struct strbuf buf = STRBUF_INIT;
- strbuf_addf(&buf, "%s:%s",
- ref, branch->merge[0]->src);
- return strbuf_detach(&buf, NULL);
+ refspec_appendf(refspec, "%s:%s",
+ ref, branch->merge[0]->src);
+ return;
}
}
- return ref;
+ refspec_append(refspec, ref);
}
static void set_refspecs(const char **refs, int nr, const char *repo)
@@ -106,30 +107,26 @@ static void set_refspecs(const char **refs, int nr, const char *repo)
for (i = 0; i < nr; i++) {
const char *ref = refs[i];
if (!strcmp("tag", ref)) {
- struct strbuf tagref = STRBUF_INIT;
if (nr <= ++i)
die(_("tag shorthand without <tag>"));
ref = refs[i];
if (deleterefs)
- strbuf_addf(&tagref, ":refs/tags/%s", ref);
+ refspec_appendf(&rs, ":refs/tags/%s", ref);
else
- strbuf_addf(&tagref, "refs/tags/%s", ref);
- ref = strbuf_detach(&tagref, NULL);
+ refspec_appendf(&rs, "refs/tags/%s", ref);
} else if (deleterefs) {
- struct strbuf delref = STRBUF_INIT;
if (strchr(ref, ':'))
die(_("--delete only accepts plain target ref names"));
- strbuf_addf(&delref, ":%s", ref);
- ref = strbuf_detach(&delref, NULL);
+ refspec_appendf(&rs, ":%s", ref);
} else if (!strchr(ref, ':')) {
if (!remote) {
/* lazily grab remote and local_refs */
remote = remote_get(repo);
local_refs = get_local_heads();
}
- ref = map_refspec(ref, remote, local_refs);
- }
- refspec_append(&rs, ref);
+ refspec_append_mapped(&rs, ref, remote, local_refs);
+ } else
+ refspec_append(&rs, ref);
}
}
@@ -143,8 +140,8 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p)
return remote->url_nr;
}
-static NORETURN int die_push_simple(struct branch *branch,
- struct remote *remote)
+static NORETURN void die_push_simple(struct branch *branch,
+ struct remote *remote)
{
/*
* There's no point in using shorten_unambiguous_ref here,
@@ -191,8 +188,6 @@ static const char message_detached_head_die[] =
static void setup_push_upstream(struct remote *remote, struct branch *branch,
int triangular, int simple)
{
- struct strbuf refspec = STRBUF_INIT;
-
if (!branch)
die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
@@ -218,18 +213,14 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch,
die_push_simple(branch, remote);
}
- strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src);
- refspec_append(&rs, refspec.buf);
+ refspec_appendf(&rs, "%s:%s", branch->refname, branch->merge[0]->src);
}
static void setup_push_current(struct remote *remote, struct branch *branch)
{
- struct strbuf refspec = STRBUF_INIT;
-
if (!branch)
die(_(message_detached_head_die), remote->name);
- strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname);
- refspec_append(&rs, refspec.buf);
+ refspec_appendf(&rs, "%s:%s", branch->refname, branch->refname);
}
static int is_workflow_triangular(struct remote *remote)
@@ -299,6 +290,12 @@ static const char message_advice_ref_needs_force[] =
"or update a remote ref to make it point at a non-commit object,\n"
"without using the '--force' option.\n");
+static const char message_advice_ref_needs_update[] =
+ N_("Updates were rejected because the tip of the remote-tracking\n"
+ "branch has been updated since the last checkout. You may want\n"
+ "to integrate those changes locally (e.g., 'git pull ...')\n"
+ "before forcing an update.\n");
+
static void advise_pull_before_push(void)
{
if (!advice_push_non_ff_current || !advice_push_update_rejected)
@@ -334,11 +331,19 @@ static void advise_ref_needs_force(void)
advise(_(message_advice_ref_needs_force));
}
+static void advise_ref_needs_update(void)
+{
+ if (!advice_push_ref_needs_update || !advice_push_update_rejected)
+ return;
+ advise(_(message_advice_ref_needs_update));
+}
+
static int push_with_options(struct transport *transport, struct refspec *rs,
int flags)
{
int err;
unsigned int reject_reasons;
+ char *anon_url = transport_anonymize_url(transport->url);
transport_set_verbosity(transport, verbosity, progress);
transport->family = family;
@@ -356,16 +361,19 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
}
if (verbosity > 0)
- fprintf(stderr, _("Pushing to %s\n"), transport->url);
+ fprintf(stderr, _("Pushing to %s\n"), anon_url);
+ trace2_region_enter("push", "transport_push", the_repository);
err = transport_push(the_repository, transport,
rs, flags, &reject_reasons);
+ trace2_region_leave("push", "transport_push", the_repository);
if (err != 0) {
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR));
- error(_("failed to push some refs to '%s'"), transport->url);
+ error(_("failed to push some refs to '%s'"), anon_url);
fprintf(stderr, "%s", push_get_color(PUSH_COLOR_RESET));
}
err |= transport_disconnect(transport);
+ free(anon_url);
if (!err)
return 0;
@@ -379,36 +387,22 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
advise_ref_fetch_first();
} else if (reject_reasons & REJECT_NEEDS_FORCE) {
advise_ref_needs_force();
+ } else if (reject_reasons & REJECT_REF_NEEDS_UPDATE) {
+ advise_ref_needs_update();
}
return 1;
}
-static int do_push(const char *repo, int flags,
- const struct string_list *push_options)
+static int do_push(int flags,
+ const struct string_list *push_options,
+ struct remote *remote)
{
int i, errs;
- struct remote *remote = pushremote_get(repo);
const char **url;
int url_nr;
struct refspec *push_refspec = &rs;
- if (!remote) {
- if (repo)
- die(_("bad repository '%s'"), repo);
- die(_("No configured push destination.\n"
- "Either specify the URL from the command-line or configure a remote repository using\n"
- "\n"
- " git remote add <name> <url>\n"
- "\n"
- "and then push using the remote name\n"
- "\n"
- " git push <name>\n"));
- }
-
- if (remote->mirror)
- flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
-
if (push_options->nr)
flags |= TRANSPORT_PUSH_OPTIONS;
@@ -447,10 +441,8 @@ static int option_parse_recurse_submodules(const struct option *opt,
if (unset)
*recurse_submodules = RECURSE_SUBMODULES_OFF;
- else if (arg)
- *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg);
else
- die("%s missing parameter", opt->long_name);
+ *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg);
return 0;
}
@@ -533,6 +525,12 @@ static int git_push_config(const char *k, const char *v, void *cb)
if (!v)
return config_error_nonbool(k);
return color_parse(v, push_colors[slot]);
+ } else if (!strcmp(k, "push.useforceifincludes")) {
+ if (git_config_bool(k, v))
+ *flags |= TRANSPORT_PUSH_FORCE_IF_INCLUDES;
+ else
+ *flags &= ~TRANSPORT_PUSH_FORCE_IF_INCLUDES;
+ return 0;
}
return git_default_config(k, v, NULL);
@@ -548,6 +546,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
struct string_list push_options_cmdline = STRING_LIST_INIT_DUP;
struct string_list *push_options;
const struct string_list_item *item;
+ struct remote *remote;
struct option options[] = {
OPT__VERBOSITY(&verbosity),
@@ -560,13 +559,14 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN),
OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN),
OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE),
- { OPTION_CALLBACK,
- 0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
- N_("require old value of ref to be at this value"),
- PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option },
- { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)",
- N_("control recursive pushing of submodules"),
- PARSE_OPT_OPTARG, option_parse_recurse_submodules },
+ OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
+ N_("require old value of ref to be at this value"),
+ PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option),
+ OPT_BIT(0, TRANS_OPT_FORCE_IF_INCLUDES, &flags,
+ N_("require remote updates to be integrated locally"),
+ TRANSPORT_PUSH_FORCE_IF_INCLUDES),
+ OPT_CALLBACK(0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)",
+ N_("control recursive pushing of submodules"), option_parse_recurse_submodules),
OPT_BOOL_F( 0 , "thin", &thin, N_("use thin pack"), PARSE_OPT_NOCOMPLETE),
OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")),
OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")),
@@ -578,9 +578,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
TRANSPORT_PUSH_FOLLOW_TAGS),
- { OPTION_CALLBACK,
- 0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
- PARSE_OPT_OPTARG, option_parse_push_signed },
+ OPT_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
+ PARSE_OPT_OPTARG, option_parse_push_signed),
OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
OPT_STRING_LIST('o', "push-option", &push_options_cmdline, N_("server-specific"), N_("option to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
@@ -602,20 +601,6 @@ int cmd_push(int argc, const char **argv, const char *prefix)
die(_("--delete is incompatible with --all, --mirror and --tags"));
if (deleterefs && argc < 2)
die(_("--delete doesn't make sense without any refs"));
- if (flags & TRANSPORT_PUSH_ALL) {
- if (tags)
- die(_("--all and --tags are incompatible"));
- if (argc >= 2)
- die(_("--all can't be combined with refspecs"));
- }
- if (flags & TRANSPORT_PUSH_MIRROR) {
- if (tags)
- die(_("--mirror and --tags are incompatible"));
- if (argc >= 2)
- die(_("--mirror can't be combined with refspecs"));
- }
- if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR))
- die(_("--all and --mirror are incompatible"));
if (recurse_submodules == RECURSE_SUBMODULES_CHECK)
flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
@@ -632,11 +617,46 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
+ remote = pushremote_get(repo);
+ if (!remote) {
+ if (repo)
+ die(_("bad repository '%s'"), repo);
+ die(_("No configured push destination.\n"
+ "Either specify the URL from the command-line or configure a remote repository using\n"
+ "\n"
+ " git remote add <name> <url>\n"
+ "\n"
+ "and then push using the remote name\n"
+ "\n"
+ " git push <name>\n"));
+ }
+
+ if (remote->mirror)
+ flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+
+ if (flags & TRANSPORT_PUSH_ALL) {
+ if (tags)
+ die(_("--all and --tags are incompatible"));
+ if (argc >= 2)
+ die(_("--all can't be combined with refspecs"));
+ }
+ if (flags & TRANSPORT_PUSH_MIRROR) {
+ if (tags)
+ die(_("--mirror and --tags are incompatible"));
+ if (argc >= 2)
+ die(_("--mirror can't be combined with refspecs"));
+ }
+ if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR))
+ die(_("--all and --mirror are incompatible"));
+
+ if (!is_empty_cas(&cas) && (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES))
+ cas.use_force_if_includes = 1;
+
for_each_string_list_item(item, push_options)
if (strchr(item->string, '\n'))
die(_("push options must not have new line characters"));
- rc = do_push(repo, flags, push_options);
+ rc = do_push(flags, push_options, remote);
string_list_clear(&push_options_cmdline, 0);
string_list_clear(&push_options_config, 0);
if (rc == -1)
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index 784bd19321..24c4162f74 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -15,12 +15,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
{
int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT;
struct diff_options diffopt = { NULL };
+ struct strvec other_arg = STRVEC_INIT;
int simple_color = -1;
struct option range_diff_options[] = {
OPT_INTEGER(0, "creation-factor", &creation_factor,
N_("Percentage by which creation is weighted")),
OPT_BOOL(0, "no-dual-color", &simple_color,
N_("use simple diff colors")),
+ OPT_PASSTHRU_ARGV(0, "notes", &other_arg,
+ N_("notes"), N_("passed to 'git log'"),
+ PARSE_OPT_OPTARG),
OPT_END()
};
struct option *options;
@@ -32,7 +36,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
repo_diff_setup(the_repository, &diffopt);
options = parse_options_concat(range_diff_options, diffopt.parseopts);
- argc = parse_options(argc, argv, NULL, options,
+ argc = parse_options(argc, argv, prefix, options,
builtin_range_diff_usage, 0);
diff_setup_done(&diffopt);
@@ -78,8 +82,9 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
FREE_AND_NULL(options);
res = show_range_diff(range1.buf, range2.buf, creation_factor,
- simple_color < 1, &diffopt);
+ simple_color < 1, &diffopt, &other_arg);
+ strvec_clear(&other_arg);
strbuf_release(&range1);
strbuf_release(&range2);
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 5c9c082595..485e7b0479 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -111,7 +111,7 @@ static int git_read_tree_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
-int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
+int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)
{
int i, stage = 0;
struct object_id oid;
@@ -120,9 +120,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
int prefix_set = 0;
struct lock_file lock_file = LOCK_INIT;
const struct option read_tree_options[] = {
- { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"),
+ OPT_CALLBACK_F(0, "index-output", NULL, N_("file"),
N_("write resulting index to <file>"),
- PARSE_OPT_NONEG, index_output_cb },
+ PARSE_OPT_NONEG, index_output_cb),
OPT_BOOL(0, "empty", &read_empty,
N_("only empty the index")),
OPT__VERBOSE(&opts.verbose_update, N_("be verbose")),
@@ -140,10 +140,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
PARSE_OPT_NONEG },
OPT_BOOL('u', NULL, &opts.update,
N_("update working tree with merge result")),
- { OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
+ OPT_CALLBACK_F(0, "exclude-per-directory", &opts,
N_("gitignore"),
N_("allow explicitly ignored files to be overwritten"),
- PARSE_OPT_NONEG, exclude_per_directory_cb },
+ PARSE_OPT_NONEG, exclude_per_directory_cb),
OPT_BOOL('i', NULL, &opts.index_only,
N_("don't check the working tree after merging")),
OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")),
@@ -151,9 +151,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
N_("skip applying sparse checkout filter")),
OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
N_("debug unpack-trees")),
- { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+ OPT_CALLBACK_F(0, "recurse-submodules", NULL,
"checkout", "control recursive updating of submodules",
- PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+ PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater),
OPT__QUIET(&opts.quiet, N_("suppress feedback messages")),
OPT_END()
};
@@ -165,7 +165,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
git_config(git_read_tree_config, NULL);
- argc = parse_options(argc, argv, unused_prefix, read_tree_options,
+ argc = parse_options(argc, argv, cmd_prefix, read_tree_options,
read_tree_usage, 0);
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
@@ -185,7 +185,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
if (opts.reset || opts.merge || opts.prefix) {
if (read_cache_unmerged() && (opts.prefix || opts.merge))
- die("You need to resolve your current index first");
+ die(_("You need to resolve your current index first"));
stage = opts.merge = 1;
}
resolve_undo_clear();
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 2748fa6f2e..19c7b377aa 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -8,7 +8,7 @@
#include "builtin.h"
#include "run-command.h"
#include "exec-cmd.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "dir.h"
#include "packfile.h"
#include "refs.h"
@@ -27,10 +27,13 @@
#include "branch.h"
#include "sequencer.h"
#include "rebase-interactive.h"
+#include "reset.h"
+
+#define DEFAULT_REFLOG_ACTION "rebase"
static char const * const builtin_rebase_usage[] = {
- N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
- "[<upstream>] [<branch>]"),
+ N_("git rebase [-i] [options] [--exec <cmd>] "
+ "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
"--root [<branch>]"),
N_("git rebase --continue | --abort | --skip | --edit-todo"),
@@ -44,14 +47,22 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge")
enum rebase_type {
REBASE_UNSPECIFIED = -1,
- REBASE_AM,
+ REBASE_APPLY,
REBASE_MERGE,
- REBASE_INTERACTIVE,
REBASE_PRESERVE_MERGES
};
+enum empty_type {
+ EMPTY_UNSPECIFIED = -1,
+ EMPTY_DROP,
+ EMPTY_KEEP,
+ EMPTY_ASK
+};
+
struct rebase_options {
enum rebase_type type;
+ enum empty_type empty;
+ const char *default_backend;
const char *state_dir;
struct commit *upstream;
const char *upstream_name;
@@ -62,7 +73,7 @@ struct rebase_options {
const char *onto_name;
const char *revisions;
const char *switch_to;
- int root;
+ int root, root_with_onto;
struct object_id *squash_onto;
struct commit *restrict_revision;
int dont_finish_rebase;
@@ -73,7 +84,7 @@ struct rebase_options {
REBASE_FORCE = 1<<3,
REBASE_INTERACTIVE_EXPLICIT = 1<<4,
} flags;
- struct argv_array git_am_opts;
+ struct strvec git_am_opts;
const char *action;
int signoff;
int allow_rerere_autoupdate;
@@ -81,6 +92,8 @@ struct rebase_options {
int autosquash;
char *gpg_sign_opt;
int autostash;
+ int committer_date_is_author_date;
+ int ignore_date;
char *cmd;
int allow_empty_message;
int rebase_merges, rebase_cousins;
@@ -88,12 +101,16 @@ struct rebase_options {
struct strbuf git_format_patch_opt;
int reschedule_failed_exec;
int use_legacy_rebase;
+ int reapply_cherry_picks;
};
#define REBASE_OPTIONS_INIT { \
.type = REBASE_UNSPECIFIED, \
+ .empty = EMPTY_UNSPECIFIED, \
+ .keep_empty = 1, \
+ .default_backend = "merge", \
.flags = REBASE_NO_QUIET, \
- .git_am_opts = ARGV_ARRAY_INIT, \
+ .git_am_opts = STRVEC_INIT, \
.git_format_patch_opt = STRBUF_INIT \
}
@@ -102,6 +119,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
struct replay_opts replay = REPLAY_OPTS_INIT;
replay.action = REPLAY_INTERACTIVE_REBASE;
+ replay.strategy = NULL;
sequencer_init_config(&replay);
replay.signoff = opts->signoff;
@@ -110,13 +128,30 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
replay.allow_empty = 1;
replay.allow_empty_message = opts->allow_empty_message;
+ replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
+ replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
+ replay.quiet = !(opts->flags & REBASE_NO_QUIET);
replay.verbose = opts->flags & REBASE_VERBOSE;
replay.reschedule_failed_exec = opts->reschedule_failed_exec;
+ replay.committer_date_is_author_date =
+ opts->committer_date_is_author_date;
+ replay.ignore_date = opts->ignore_date;
replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
- replay.strategy = opts->strategy;
+ if (opts->strategy)
+ replay.strategy = opts->strategy;
+ else if (!replay.strategy && replay.default_strategy) {
+ replay.strategy = replay.default_strategy;
+ replay.default_strategy = NULL;
+ }
+
if (opts->strategy_opts)
parse_strategy_opts(&replay, opts->strategy_opts);
+ if (opts->squash_onto) {
+ oidcpy(&replay.squash_onto, opts->squash_onto);
+ replay.have_squash_onto = 1;
+ }
+
return replay;
}
@@ -241,21 +276,16 @@ static int edit_todo_file(unsigned flags)
}
static int get_revision_ranges(struct commit *upstream, struct commit *onto,
- const char **head_hash,
- char **revisions, char **shortrevisions)
+ struct object_id *orig_head, char **revisions,
+ char **shortrevisions)
{
struct commit *base_rev = upstream ? upstream : onto;
const char *shorthead;
- struct object_id orig_head;
- if (get_oid("HEAD", &orig_head))
- return error(_("no HEAD?"));
-
- *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ);
*revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
- *head_hash);
+ oid_to_hex(orig_head));
- shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
+ shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV);
if (upstream) {
const char *shortrev;
@@ -271,7 +301,8 @@ static int get_revision_ranges(struct commit *upstream, struct commit *onto,
}
static int init_basic_state(struct replay_opts *opts, const char *head_name,
- struct commit *onto, const char *orig_head)
+ struct commit *onto,
+ const struct object_id *orig_head)
{
FILE *interactive;
@@ -302,24 +333,19 @@ static void split_exec_commands(const char *cmd, struct string_list *commands)
static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
{
int ret;
- const char *head_hash = NULL;
char *revisions = NULL, *shortrevisions = NULL;
- struct argv_array make_script_args = ARGV_ARRAY_INIT;
+ struct strvec make_script_args = STRVEC_INIT;
struct todo_list todo_list = TODO_LIST_INIT;
struct replay_opts replay = get_replay_opts(opts);
struct string_list commands = STRING_LIST_INIT_DUP;
- if (prepare_branch_to_be_rebased(the_repository, &replay,
- opts->switch_to))
- return -1;
-
- if (get_revision_ranges(opts->upstream, opts->onto, &head_hash,
+ if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head,
&revisions, &shortrevisions))
return -1;
if (init_basic_state(&replay,
opts->head_name ? opts->head_name : "detached HEAD",
- opts->onto, head_hash)) {
+ opts->onto, &opts->orig_head)) {
free(revisions);
free(shortrevisions);
@@ -330,13 +356,13 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
write_file(path_squash_onto(), "%s\n",
oid_to_hex(opts->squash_onto));
- argv_array_pushl(&make_script_args, "", revisions, NULL);
+ strvec_pushl(&make_script_args, "", revisions, NULL);
if (opts->restrict_revision)
- argv_array_push(&make_script_args,
- oid_to_hex(&opts->restrict_revision->object.oid));
+ strvec_pushf(&make_script_args, "^%s",
+ oid_to_hex(&opts->restrict_revision->object.oid));
ret = sequencer_make_script(the_repository, &todo_list.buf,
- make_script_args.argc, make_script_args.argv,
+ make_script_args.nr, make_script_args.v,
flags);
if (ret)
@@ -349,20 +375,21 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
split_exec_commands(opts->cmd, &commands);
ret = complete_action(the_repository, &replay, flags,
- shortrevisions, opts->onto_name, opts->onto, head_hash,
- &commands, opts->autosquash, &todo_list);
+ shortrevisions, opts->onto_name, opts->onto,
+ &opts->orig_head, &commands, opts->autosquash,
+ &todo_list);
}
string_list_clear(&commands, 0);
free(revisions);
free(shortrevisions);
todo_list_release(&todo_list);
- argv_array_clear(&make_script_args);
+ strvec_clear(&make_script_args);
return ret;
}
-static int run_rebase_interactive(struct rebase_options *opts,
+static int run_sequencer_rebase(struct rebase_options *opts,
enum action command)
{
unsigned flags = 0;
@@ -374,7 +401,9 @@ static int run_rebase_interactive(struct rebase_options *opts,
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
+ flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
+ flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0;
switch (command) {
case ACTION_NONE: {
@@ -403,7 +432,7 @@ static int run_rebase_interactive(struct rebase_options *opts,
struct child_process cmd = CHILD_PROCESS_INIT;
cmd.git_cmd = 1;
- argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL);
+ strvec_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL);
ret = run_command(&cmd);
break;
@@ -433,6 +462,20 @@ static int run_rebase_interactive(struct rebase_options *opts,
return ret;
}
+static void imply_merge(struct rebase_options *opts, const char *option);
+static int parse_opt_keep_empty(const struct option *opt, const char *arg,
+ int unset)
+{
+ struct rebase_options *opts = opt->value;
+
+ BUG_ON_OPT_ARG(arg);
+
+ imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty");
+ opts->keep_empty = !unset;
+ opts->type = REBASE_MERGE;
+ return 0;
+}
+
static const char * const builtin_rebase_interactive_usage[] = {
N_("git rebase--interactive [<options>]"),
NULL
@@ -446,9 +489,13 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
REBASE_FORCE),
- OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
- OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
- N_("allow commits with empty messages")),
+ OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
+ N_("keep commits which start empty"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
+ parse_opt_keep_empty),
+ OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
+ N_("allow commits with empty messages"),
+ PARSE_OPT_HIDDEN),
OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
N_("keep original branch points of cousins")),
@@ -508,7 +555,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
if (argc == 1)
usage_with_options(builtin_rebase_interactive_usage, options);
- argc = parse_options(argc, argv, NULL, options,
+ argc = parse_options(argc, argv, prefix, options,
builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
if (!is_null_oid(&squash_onto))
@@ -518,28 +565,26 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
warning(_("--[no-]rebase-cousins has no effect without "
"--rebase-merges"));
- return !!run_rebase_interactive(&opts, command);
+ return !!run_sequencer_rebase(&opts, command);
}
-static int is_interactive(struct rebase_options *opts)
+static int is_merge(struct rebase_options *opts)
{
- return opts->type == REBASE_INTERACTIVE ||
+ return opts->type == REBASE_MERGE ||
opts->type == REBASE_PRESERVE_MERGES;
}
-static void imply_interactive(struct rebase_options *opts, const char *option)
+static void imply_merge(struct rebase_options *opts, const char *option)
{
switch (opts->type) {
- case REBASE_AM:
- die(_("%s requires an interactive rebase"), option);
+ case REBASE_APPLY:
+ die(_("%s requires the merge backend"), option);
break;
- case REBASE_INTERACTIVE:
+ case REBASE_MERGE:
case REBASE_PRESERVE_MERGES:
break;
- case REBASE_MERGE:
- /* we now implement --merge via --interactive */
default:
- opts->type = REBASE_INTERACTIVE; /* implied */
+ opts->type = REBASE_MERGE; /* implied */
break;
}
}
@@ -560,15 +605,6 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o
return path.buf;
}
-/* Read one file, then strip line endings */
-static int read_one(const char *path, struct strbuf *buf)
-{
- if (strbuf_read_file(buf, path, 0) < 0)
- return error_errno(_("could not read '%s'"), path);
- strbuf_trim_trailing_newline(buf);
- return 0;
-}
-
/* Initialize the rebase options from the state directory. */
static int read_basic_state(struct rebase_options *opts)
{
@@ -576,8 +612,10 @@ static int read_basic_state(struct rebase_options *opts)
struct strbuf buf = STRBUF_INIT;
struct object_id oid;
- if (read_one(state_dir_path("head-name", opts), &head_name) ||
- read_one(state_dir_path("onto", opts), &buf))
+ if (!read_oneliner(&head_name, state_dir_path("head-name", opts),
+ READ_ONELINER_WARN_MISSING) ||
+ !read_oneliner(&buf, state_dir_path("onto", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
opts->head_name = starts_with(head_name.buf, "refs/") ?
xstrdup(head_name.buf) : NULL;
@@ -593,9 +631,11 @@ static int read_basic_state(struct rebase_options *opts)
*/
strbuf_reset(&buf);
if (file_exists(state_dir_path("orig-head", opts))) {
- if (read_one(state_dir_path("orig-head", opts), &buf))
+ if (!read_oneliner(&buf, state_dir_path("orig-head", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
- } else if (read_one(state_dir_path("head", opts), &buf))
+ } else if (!read_oneliner(&buf, state_dir_path("head", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
if (get_oid(buf.buf, &opts->orig_head))
return error(_("invalid orig-head: '%s'"), buf.buf);
@@ -615,8 +655,8 @@ static int read_basic_state(struct rebase_options *opts)
if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) {
strbuf_reset(&buf);
- if (read_one(state_dir_path("allow_rerere_autoupdate", opts),
- &buf))
+ if (!read_oneliner(&buf, state_dir_path("allow_rerere_autoupdate", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
if (!strcmp(buf.buf, "--rerere-autoupdate"))
opts->allow_rerere_autoupdate = RERERE_AUTOUPDATE;
@@ -629,8 +669,8 @@ static int read_basic_state(struct rebase_options *opts)
if (file_exists(state_dir_path("gpg_sign_opt", opts))) {
strbuf_reset(&buf);
- if (read_one(state_dir_path("gpg_sign_opt", opts),
- &buf))
+ if (!read_oneliner(&buf, state_dir_path("gpg_sign_opt", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
free(opts->gpg_sign_opt);
opts->gpg_sign_opt = xstrdup(buf.buf);
@@ -638,7 +678,8 @@ static int read_basic_state(struct rebase_options *opts)
if (file_exists(state_dir_path("strategy", opts))) {
strbuf_reset(&buf);
- if (read_one(state_dir_path("strategy", opts), &buf))
+ if (!read_oneliner(&buf, state_dir_path("strategy", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
free(opts->strategy);
opts->strategy = xstrdup(buf.buf);
@@ -646,7 +687,8 @@ static int read_basic_state(struct rebase_options *opts)
if (file_exists(state_dir_path("strategy_opts", opts))) {
strbuf_reset(&buf);
- if (read_one(state_dir_path("strategy_opts", opts), &buf))
+ if (!read_oneliner(&buf, state_dir_path("strategy_opts", opts),
+ READ_ONELINER_WARN_MISSING))
return -1;
free(opts->strategy_opts);
opts->strategy_opts = xstrdup(buf.buf);
@@ -665,8 +707,8 @@ static int rebase_write_basic_state(struct rebase_options *opts)
opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
write_file(state_dir_path("orig-head", opts), "%s",
oid_to_hex(&opts->orig_head));
- write_file(state_dir_path("quiet", opts), "%s",
- opts->flags & REBASE_NO_QUIET ? "" : "t");
+ if (!(opts->flags & REBASE_NO_QUIET))
+ write_file(state_dir_path("quiet", opts), "%s", "");
if (opts->flags & REBASE_VERBOSE)
write_file(state_dir_path("verbose", opts), "%s", "");
if (opts->strategy)
@@ -684,71 +726,25 @@ static int rebase_write_basic_state(struct rebase_options *opts)
write_file(state_dir_path("gpg_sign_opt", opts), "%s",
opts->gpg_sign_opt);
if (opts->signoff)
- write_file(state_dir_path("strategy", opts), "--signoff");
-
- return 0;
-}
+ write_file(state_dir_path("signoff", opts), "--signoff");
-static int apply_autostash(struct rebase_options *opts)
-{
- const char *path = state_dir_path("autostash", opts);
- struct strbuf autostash = STRBUF_INIT;
- struct child_process stash_apply = CHILD_PROCESS_INIT;
-
- if (!file_exists(path))
- return 0;
-
- if (read_one(path, &autostash))
- return error(_("Could not read '%s'"), path);
- /* Ensure that the hash is not mistaken for a number */
- strbuf_addstr(&autostash, "^0");
- argv_array_pushl(&stash_apply.args,
- "stash", "apply", autostash.buf, NULL);
- stash_apply.git_cmd = 1;
- stash_apply.no_stderr = stash_apply.no_stdout =
- stash_apply.no_stdin = 1;
- if (!run_command(&stash_apply))
- printf(_("Applied autostash.\n"));
- else {
- struct argv_array args = ARGV_ARRAY_INIT;
- int res = 0;
-
- argv_array_pushl(&args,
- "stash", "store", "-m", "autostash", "-q",
- autostash.buf, NULL);
- if (run_command_v_opt(args.argv, RUN_GIT_CMD))
- res = error(_("Cannot store %s"), autostash.buf);
- argv_array_clear(&args);
- strbuf_release(&autostash);
- if (res)
- return res;
-
- fprintf(stderr,
- _("Applying autostash resulted in conflicts.\n"
- "Your changes are safe in the stash.\n"
- "You can run \"git stash pop\" or \"git stash drop\" "
- "at any time.\n"));
- }
-
- strbuf_release(&autostash);
return 0;
}
static int finish_rebase(struct rebase_options *opts)
{
struct strbuf dir = STRBUF_INIT;
- const char *argv_gc_auto[] = { "gc", "--auto", NULL };
int ret = 0;
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
- apply_autostash(opts);
+ apply_autostash(state_dir_path("autostash", opts));
close_object_store(the_repository->objects);
/*
- * We ignore errors in 'gc --auto', since the
+ * We ignore errors in 'git maintenance run --auto', since the
* user should see them.
*/
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
- if (opts->type == REBASE_INTERACTIVE) {
+ run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));
+ if (opts->type == REBASE_MERGE) {
struct replay_opts replay = REPLAY_OPTS_INIT;
replay.action = REPLAY_INTERACTIVE_REBASE;
@@ -786,143 +782,6 @@ static void add_var(struct strbuf *buf, const char *name, const char *value)
}
}
-#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
-
-#define RESET_HEAD_DETACH (1<<0)
-#define RESET_HEAD_HARD (1<<1)
-#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
-#define RESET_HEAD_REFS_ONLY (1<<3)
-#define RESET_ORIG_HEAD (1<<4)
-
-static int reset_head(struct object_id *oid, const char *action,
- const char *switch_to_branch, unsigned flags,
- const char *reflog_orig_head, const char *reflog_head)
-{
- unsigned detach_head = flags & RESET_HEAD_DETACH;
- unsigned reset_hard = flags & RESET_HEAD_HARD;
- unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
- unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
- unsigned update_orig_head = flags & RESET_ORIG_HEAD;
- struct object_id head_oid;
- struct tree_desc desc[2] = { { NULL }, { NULL } };
- struct lock_file lock = LOCK_INIT;
- struct unpack_trees_options unpack_tree_opts;
- struct tree *tree;
- const char *reflog_action;
- struct strbuf msg = STRBUF_INIT;
- size_t prefix_len;
- struct object_id *orig = NULL, oid_orig,
- *old_orig = NULL, oid_old_orig;
- int ret = 0, nr = 0;
-
- if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
- BUG("Not a fully qualified branch: '%s'", switch_to_branch);
-
- if (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) {
- ret = -1;
- goto leave_reset_head;
- }
-
- if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
- ret = error(_("could not determine HEAD revision"));
- goto leave_reset_head;
- }
-
- if (!oid)
- oid = &head_oid;
-
- if (refs_only)
- goto reset_head_refs;
-
- memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
- setup_unpack_trees_porcelain(&unpack_tree_opts, action);
- unpack_tree_opts.head_idx = 1;
- unpack_tree_opts.src_index = the_repository->index;
- unpack_tree_opts.dst_index = the_repository->index;
- unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
- unpack_tree_opts.update = 1;
- unpack_tree_opts.merge = 1;
- if (!detach_head)
- unpack_tree_opts.reset = 1;
-
- if (repo_read_index_unmerged(the_repository) < 0) {
- ret = error(_("could not read index"));
- goto leave_reset_head;
- }
-
- if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) {
- ret = error(_("failed to find tree of %s"),
- oid_to_hex(&head_oid));
- goto leave_reset_head;
- }
-
- if (!fill_tree_descriptor(&desc[nr++], oid)) {
- ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
- goto leave_reset_head;
- }
-
- if (unpack_trees(nr, desc, &unpack_tree_opts)) {
- ret = -1;
- goto leave_reset_head;
- }
-
- tree = parse_tree_indirect(oid);
- prime_cache_tree(the_repository, the_repository->index, tree);
-
- if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) {
- ret = error(_("could not write index"));
- goto leave_reset_head;
- }
-
-reset_head_refs:
- reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
- strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase");
- prefix_len = msg.len;
-
- if (update_orig_head) {
- if (!get_oid("ORIG_HEAD", &oid_old_orig))
- old_orig = &oid_old_orig;
- if (!get_oid("HEAD", &oid_orig)) {
- orig = &oid_orig;
- if (!reflog_orig_head) {
- strbuf_addstr(&msg, "updating ORIG_HEAD");
- reflog_orig_head = msg.buf;
- }
- update_ref(reflog_orig_head, "ORIG_HEAD", orig,
- old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
- } else if (old_orig)
- delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
- }
-
- if (!reflog_head) {
- strbuf_setlen(&msg, prefix_len);
- strbuf_addstr(&msg, "updating HEAD");
- reflog_head = msg.buf;
- }
- if (!switch_to_branch)
- ret = update_ref(reflog_head, "HEAD", oid, orig,
- detach_head ? REF_NO_DEREF : 0,
- UPDATE_REFS_MSG_ON_ERR);
- else {
- ret = update_ref(reflog_head, switch_to_branch, oid,
- NULL, 0, UPDATE_REFS_MSG_ON_ERR);
- if (!ret)
- ret = create_symref("HEAD", switch_to_branch,
- reflog_head);
- }
- if (run_hook)
- run_hook_le(NULL, "post-checkout",
- oid_to_hex(orig ? orig : &null_oid),
- oid_to_hex(oid), "1", NULL);
-
-leave_reset_head:
- strbuf_release(&msg);
- rollback_lock_file(&lock);
- while (nr)
- free((void *)desc[--nr].buffer);
- return ret;
-}
-
static int move_to_original_branch(struct rebase_options *opts)
{
struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
@@ -938,8 +797,10 @@ static int move_to_original_branch(struct rebase_options *opts)
opts->head_name, oid_to_hex(&opts->onto->object.oid));
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
opts->head_name);
- ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY,
- orig_head_reflog.buf, head_reflog.buf);
+ ret = reset_head(the_repository, NULL, "", opts->head_name,
+ RESET_HEAD_REFS_ONLY,
+ orig_head_reflog.buf, head_reflog.buf,
+ DEFAULT_REFLOG_ACTION);
strbuf_release(&orig_head_reflog);
strbuf_release(&head_reflog);
@@ -962,13 +823,13 @@ static int run_am(struct rebase_options *opts)
char *rebased_patches;
am.git_cmd = 1;
- argv_array_push(&am.args, "am");
+ strvec_push(&am.args, "am");
if (opts->action && !strcmp("continue", opts->action)) {
- argv_array_push(&am.args, "--resolved");
- argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+ strvec_push(&am.args, "--resolved");
+ strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
if (opts->gpg_sign_opt)
- argv_array_push(&am.args, opts->gpg_sign_opt);
+ strvec_push(&am.args, opts->gpg_sign_opt);
status = run_command(&am);
if (status)
return status;
@@ -976,8 +837,8 @@ static int run_am(struct rebase_options *opts)
return move_to_original_branch(opts);
}
if (opts->action && !strcmp("skip", opts->action)) {
- argv_array_push(&am.args, "--skip");
- argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+ strvec_push(&am.args, "--skip");
+ strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
status = run_command(&am);
if (status)
return status;
@@ -985,7 +846,7 @@ static int run_am(struct rebase_options *opts)
return move_to_original_branch(opts);
}
if (opts->action && !strcmp("show-current-patch", opts->action)) {
- argv_array_push(&am.args, "--show-current-patch");
+ strvec_push(&am.args, "--show-current-patch");
return run_command(&am);
}
@@ -1003,31 +864,33 @@ static int run_am(struct rebase_options *opts)
status = error_errno(_("could not open '%s' for writing"),
rebased_patches);
free(rebased_patches);
- argv_array_clear(&am.args);
+ strvec_clear(&am.args);
return status;
}
format_patch.git_cmd = 1;
- argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
- "--full-index", "--cherry-pick", "--right-only",
- "--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
- "--no-cover-letter", "--pretty=mboxrd", "--topo-order", NULL);
+ strvec_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
+ "--full-index", "--cherry-pick", "--right-only",
+ "--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
+ "--no-cover-letter", "--pretty=mboxrd", "--topo-order",
+ "--no-base", NULL);
if (opts->git_format_patch_opt.len)
- argv_array_split(&format_patch.args,
- opts->git_format_patch_opt.buf);
- argv_array_push(&format_patch.args, revisions.buf);
+ strvec_split(&format_patch.args,
+ opts->git_format_patch_opt.buf);
+ strvec_push(&format_patch.args, revisions.buf);
if (opts->restrict_revision)
- argv_array_pushf(&format_patch.args, "^%s",
- oid_to_hex(&opts->restrict_revision->object.oid));
+ strvec_pushf(&format_patch.args, "^%s",
+ oid_to_hex(&opts->restrict_revision->object.oid));
status = run_command(&format_patch);
if (status) {
unlink(rebased_patches);
free(rebased_patches);
- argv_array_clear(&am.args);
+ strvec_clear(&am.args);
- reset_head(&opts->orig_head, "checkout", opts->head_name, 0,
- "HEAD", NULL);
+ reset_head(the_repository, &opts->orig_head, "checkout",
+ opts->head_name, 0,
+ "HEAD", NULL, DEFAULT_REFLOG_ACTION);
error(_("\ngit encountered an error while preparing the "
"patches to replay\n"
"these revisions:\n"
@@ -1045,20 +908,20 @@ static int run_am(struct rebase_options *opts)
status = error_errno(_("could not open '%s' for reading"),
rebased_patches);
free(rebased_patches);
- argv_array_clear(&am.args);
+ strvec_clear(&am.args);
return status;
}
- argv_array_pushv(&am.args, opts->git_am_opts.argv);
- argv_array_push(&am.args, "--rebasing");
- argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
- argv_array_push(&am.args, "--patch-format=mboxrd");
+ strvec_pushv(&am.args, opts->git_am_opts.v);
+ strvec_push(&am.args, "--rebasing");
+ strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
+ strvec_push(&am.args, "--patch-format=mboxrd");
if (opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE)
- argv_array_push(&am.args, "--rerere-autoupdate");
+ strvec_push(&am.args, "--rerere-autoupdate");
else if (opts->allow_rerere_autoupdate == RERERE_NOAUTOUPDATE)
- argv_array_push(&am.args, "--no-rerere-autoupdate");
+ strvec_push(&am.args, "--no-rerere-autoupdate");
if (opts->gpg_sign_opt)
- argv_array_push(&am.args, opts->gpg_sign_opt);
+ strvec_push(&am.args, opts->gpg_sign_opt);
status = run_command(&am);
unlink(rebased_patches);
free(rebased_patches);
@@ -1080,8 +943,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
int status;
const char *backend, *backend_func;
- if (opts->type == REBASE_INTERACTIVE) {
- /* Run builtin interactive rebase */
+ if (opts->type == REBASE_MERGE) {
+ /* Run sequencer-based rebase */
setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1);
if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
setenv("GIT_SEQUENCE_EDITOR", ":", 1);
@@ -1094,11 +957,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
opts->gpg_sign_opt = tmp;
}
- status = run_rebase_interactive(opts, action);
+ status = run_sequencer_rebase(opts, action);
goto finished_rebase;
}
- if (opts->type == REBASE_AM) {
+ if (opts->type == REBASE_APPLY) {
status = run_am(opts);
goto finished_rebase;
}
@@ -1118,9 +981,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
add_var(&script_snippet, "revisions", opts->revisions);
add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
- add_var(&script_snippet, "GIT_QUIET",
- opts->flags & REBASE_NO_QUIET ? "" : "t");
- sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
+ sq_quote_argv_pretty(&buf, opts->git_am_opts.v);
add_var(&script_snippet, "git_am_opt", buf.buf);
strbuf_release(&buf);
add_var(&script_snippet, "verbose",
@@ -1155,7 +1016,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
add_var(&script_snippet, "git_format_patch_opt",
opts->git_format_patch_opt.buf);
- if (is_interactive(opts) &&
+ if (is_merge(opts) &&
!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
strbuf_addstr(&script_snippet,
"GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
@@ -1163,10 +1024,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
}
switch (opts->type) {
- case REBASE_AM:
- backend = "git-rebase--am";
- backend_func = "git_rebase__am";
- break;
case REBASE_PRESERVE_MERGES:
backend = "git-rebase--preserve-merges";
backend_func = "git_rebase__preserve_merges";
@@ -1177,23 +1034,22 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)
}
strbuf_addf(&script_snippet,
- ". git-sh-setup && . git-rebase--common &&"
- " . %s && %s", backend, backend_func);
+ ". git-sh-setup && . %s && %s", backend, backend_func);
argv[0] = script_snippet.buf;
status = run_command_v_opt(argv, RUN_USING_SHELL);
finished_rebase:
if (opts->dont_finish_rebase)
; /* do nothing */
- else if (opts->type == REBASE_INTERACTIVE)
- ; /* interactive rebase cleans up after itself */
+ else if (opts->type == REBASE_MERGE)
+ ; /* merge backend cleans up after itself */
else if (status == 0) {
if (!file_exists(state_dir_path("stopped-sha", opts)))
finish_rebase(opts);
} else if (status == 2) {
struct strbuf dir = STRBUF_INIT;
- apply_autostash(opts);
+ apply_autostash(state_dir_path("autostash", opts));
strbuf_addstr(&dir, opts->state_dir);
remove_dir_recursively(&dir, 0);
strbuf_release(&dir);
@@ -1244,6 +1100,10 @@ static int rebase_config(const char *var, const char *value, void *data)
return 0;
}
+ if (!strcmp(var, "rebase.backend")) {
+ return git_config_string(&opts->default_backend, var, value);
+ }
+
return git_default_config(var, value, data);
}
@@ -1265,28 +1125,60 @@ static int is_linear_history(struct commit *from, struct commit *to)
return 1;
}
-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,
- struct object_id *merge_base)
+static int can_fast_forward(struct commit *onto, struct commit *upstream,
+ struct commit *restrict_revision,
+ struct object_id *head_oid, struct object_id *merge_base)
{
struct commit *head = lookup_commit(the_repository, head_oid);
- struct commit_list *merge_bases;
- int res;
+ struct commit_list *merge_bases = NULL;
+ int res = 0;
if (!head)
- return 0;
+ goto done;
merge_bases = get_merge_bases(onto, head);
- if (merge_bases && !merge_bases->next) {
- oidcpy(merge_base, &merge_bases->item->object.oid);
- res = oideq(merge_base, &onto->object.oid);
- } else {
+ if (!merge_bases || merge_bases->next) {
oidcpy(merge_base, &null_oid);
- res = 0;
+ goto done;
}
+
+ oidcpy(merge_base, &merge_bases->item->object.oid);
+ if (!oideq(merge_base, &onto->object.oid))
+ goto done;
+
+ if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))
+ goto done;
+
+ if (!upstream)
+ goto done;
+
+ free_commit_list(merge_bases);
+ merge_bases = get_merge_bases(upstream, head);
+ if (!merge_bases || merge_bases->next)
+ goto done;
+
+ if (!oideq(&onto->object.oid, &merge_bases->item->object.oid))
+ goto done;
+
+ res = 1;
+
+done:
free_commit_list(merge_bases);
return res && is_linear_history(onto, head);
}
+static int parse_opt_am(const struct option *opt, const char *arg, int unset)
+{
+ struct rebase_options *opts = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+ BUG_ON_OPT_ARG(arg);
+
+ opts->type = REBASE_APPLY;
+
+ return 0;
+}
+
/* -i followed by -m is still -i */
static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
{
@@ -1295,7 +1187,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
- if (!is_interactive(opts))
+ if (!is_merge(opts))
opts->type = REBASE_MERGE;
return 0;
@@ -1310,12 +1202,35 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
- opts->type = REBASE_INTERACTIVE;
+ opts->type = REBASE_MERGE;
opts->flags |= REBASE_INTERACTIVE_EXPLICIT;
return 0;
}
+static enum empty_type parse_empty_value(const char *value)
+{
+ if (!strcasecmp(value, "drop"))
+ return EMPTY_DROP;
+ else if (!strcasecmp(value, "keep"))
+ return EMPTY_KEEP;
+ else if (!strcasecmp(value, "ask"))
+ return EMPTY_ASK;
+
+ die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
+}
+
+static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
+{
+ struct rebase_options *options = opt->value;
+ enum empty_type value = parse_empty_value(arg);
+
+ BUG_ON_OPT_NEG(unset);
+
+ options->empty = value;
+ return 0;
+}
+
static void NORETURN error_on_missing_default_upstream(void)
{
struct branch *current_branch = branch_get(NULL);
@@ -1351,14 +1266,14 @@ static void set_reflog_action(struct rebase_options *options)
const char *env;
struct strbuf buf = STRBUF_INIT;
- if (!is_interactive(options))
+ if (!is_merge(options))
return;
env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
if (env && strcmp("rebase", env))
return; /* only override it if it is "rebase" */
- strbuf_addf(&buf, "rebase -i (%s)", options->action);
+ strbuf_addf(&buf, "rebase (%s)", options->action);
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);
strbuf_release(&buf);
}
@@ -1375,17 +1290,18 @@ static int check_exec_cmd(const char *cmd)
return 0;
}
-
int cmd_rebase(int argc, const char **argv, const char *prefix)
{
struct rebase_options options = REBASE_OPTIONS_INIT;
const char *branch_name;
int ret, flags, total_argc, in_progress = 0;
+ int keep_base = 0;
int ok_to_skip_pre_rebase = 0;
struct strbuf msg = STRBUF_INIT;
struct strbuf revisions = STRBUF_INIT;
struct strbuf buf = STRBUF_INIT;
struct object_id merge_base;
+ int ignore_whitespace = 0;
enum action action = ACTION_NONE;
const char *gpg_sign = NULL;
struct string_list exec = STRING_LIST_INIT_NODUP;
@@ -1395,15 +1311,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
struct object_id squash_onto;
char *squash_onto_name = NULL;
int reschedule_failed_exec = -1;
+ int allow_preemptive_ff = 1;
struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name,
N_("revision"),
N_("rebase onto given branch instead of upstream")),
+ OPT_BOOL(0, "keep-base", &keep_base,
+ N_("use the merge-base of upstream and branch as the current base")),
OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
N_("allow pre-rebase hook to run")),
OPT_NEGBIT('q', "quiet", &options.flags,
N_("be quiet. implies --no-stat"),
- REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
+ REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
OPT_BIT('v', "verbose", &options.flags,
N_("display a diffstat of what changed upstream"),
REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
@@ -1411,17 +1330,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
N_("do not show diffstat of what changed upstream"),
PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT },
OPT_BOOL(0, "signoff", &options.signoff,
- N_("add a Signed-off-by: line to each commit")),
- OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &options.git_am_opts,
- NULL, N_("passed to 'git am'"),
- PARSE_OPT_NOARG),
- OPT_PASSTHRU_ARGV(0, "committer-date-is-author-date",
- &options.git_am_opts, NULL,
- N_("passed to 'git am'"), PARSE_OPT_NOARG),
- OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL,
- N_("passed to 'git am'"), PARSE_OPT_NOARG),
+ N_("add a Signed-off-by trailer to each commit")),
+ OPT_BOOL(0, "committer-date-is-author-date",
+ &options.committer_date_is_author_date,
+ N_("make committer date match author date")),
+ OPT_BOOL(0, "reset-author-date", &options.ignore_date,
+ N_("ignore author date and use current date")),
+ OPT_HIDDEN_BOOL(0, "ignore-date", &options.ignore_date,
+ N_("synonym of --reset-author-date")),
OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"),
N_("passed to 'git apply'"), 0),
+ OPT_BOOL(0, "ignore-whitespace", &ignore_whitespace,
+ N_("ignore changes in whitespace")),
OPT_PASSTHRU_ARGV(0, "whitespace", &options.git_am_opts,
N_("action"), N_("passed to 'git apply'"), 0),
OPT_BIT('f', "force-rebase", &options.flags,
@@ -1444,34 +1364,44 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
OPT_CMDMODE(0, "show-current-patch", &action,
N_("show the patch file being applied or merged"),
ACTION_SHOW_CURRENT_PATCH),
- { OPTION_CALLBACK, 'm', "merge", &options, NULL,
+ OPT_CALLBACK_F(0, "apply", &options, NULL,
+ N_("use apply strategies to rebase"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG,
+ parse_opt_am),
+ OPT_CALLBACK_F('m', "merge", &options, NULL,
N_("use merging strategies to rebase"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
- parse_opt_merge },
- { OPTION_CALLBACK, 'i', "interactive", &options, NULL,
+ parse_opt_merge),
+ OPT_CALLBACK_F('i', "interactive", &options, NULL,
N_("let the user edit the list of commits to rebase"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
- parse_opt_interactive },
- OPT_SET_INT('p', "preserve-merges", &options.type,
- N_("(DEPRECATED) try to recreate merges instead of "
- "ignoring them"), REBASE_PRESERVE_MERGES),
+ parse_opt_interactive),
+ OPT_SET_INT_F('p', "preserve-merges", &options.type,
+ N_("(DEPRECATED) try to recreate merges instead of "
+ "ignoring them"),
+ REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
- OPT_BOOL('k', "keep-empty", &options.keep_empty,
- N_("preserve empty commits during rebase")),
+ OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}",
+ N_("how to handle commits that become empty"),
+ PARSE_OPT_NONEG, parse_opt_empty),
+ OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
+ N_("keep commits which start empty"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
+ parse_opt_keep_empty),
OPT_BOOL(0, "autosquash", &options.autosquash,
N_("move commits that begin with "
"squash!/fixup! under -i")),
{ OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"),
N_("GPG-sign commits"),
PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
- OPT_BOOL(0, "autostash", &options.autostash,
- N_("automatically stash/stash pop before and after")),
+ OPT_AUTOSTASH(&options.autostash),
OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
N_("add exec lines after each commit of the "
"editable list")),
- OPT_BOOL(0, "allow-empty-message",
- &options.allow_empty_message,
- N_("allow rebasing commits with empty messages")),
+ OPT_BOOL_F(0, "allow-empty-message",
+ &options.allow_empty_message,
+ N_("allow rebasing commits with empty messages"),
+ PARSE_OPT_HIDDEN),
{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
N_("mode"),
N_("try to rebase merges instead of skipping them"),
@@ -1489,6 +1419,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "reschedule-failed-exec",
&reschedule_failed_exec,
N_("automatically re-schedule any `exec` that fails")),
+ OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks,
+ N_("apply all changes, even those already present upstream")),
OPT_END(),
};
int i;
@@ -1497,12 +1429,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_rebase_usage,
builtin_rebase_options);
- prefix = setup_git_directory();
- trace_repo_setup(prefix);
- setup_work_tree();
-
options.allow_empty_message = 1;
git_config(rebase_config, &options);
+ /* options.gpg_sign_opt will be either "-S" or NULL */
+ gpg_sign = options.gpg_sign_opt ? "" : NULL;
+ FREE_AND_NULL(options.gpg_sign_opt);
if (options.use_legacy_rebase ||
!git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
@@ -1515,7 +1446,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die(_("It looks like 'git am' is in progress. Cannot rebase."));
if (is_directory(apply_dir())) {
- options.type = REBASE_AM;
+ options.type = REBASE_APPLY;
options.state_dir = apply_dir();
} else if (is_directory(merge_dir())) {
strbuf_reset(&buf);
@@ -1527,7 +1458,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/interactive", merge_dir());
if(file_exists(buf.buf)) {
- options.type = REBASE_INTERACTIVE;
+ options.type = REBASE_MERGE;
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
} else
options.type = REBASE_MERGE;
@@ -1556,16 +1487,26 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
warning(_("git rebase --preserve-merges is deprecated. "
"Use --rebase-merges instead."));
+ if (keep_base) {
+ if (options.onto_name)
+ die(_("cannot combine '--keep-base' with '--onto'"));
+ if (options.root)
+ die(_("cannot combine '--keep-base' with '--root'"));
+ }
+
+ if (options.root && fork_point > 0)
+ die(_("cannot combine '--root' with '--fork-point'"));
+
if (action != ACTION_NONE && !in_progress)
die(_("No rebase in progress?"));
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
- if (action == ACTION_EDIT_TODO && !is_interactive(&options))
+ if (action == ACTION_EDIT_TODO && !is_merge(&options))
die(_("The --edit-todo action can only be used during "
"interactive rebase."));
if (trace2_is_enabled()) {
- if (is_interactive(&options))
+ if (is_merge(&options))
trace2_cmd_mode("interactive");
else if (exec.nr)
trace2_cmd_mode("interactive-exec");
@@ -1613,10 +1554,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);
- if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD,
- NULL, NULL) < 0)
+ if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
+ NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
die(_("could not discard worktree changes"));
- remove_branch_state(the_repository);
+ remove_branch_state(the_repository, 0);
if (read_basic_state(&options))
exit(1);
goto run_rebase;
@@ -1631,17 +1572,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (read_basic_state(&options))
exit(1);
- if (reset_head(&options.orig_head, "reset",
+ if (reset_head(the_repository, &options.orig_head, "reset",
options.head_name, RESET_HEAD_HARD,
- NULL, NULL) < 0)
+ NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
die(_("could not move back to %s"),
oid_to_hex(&options.orig_head));
- remove_branch_state(the_repository);
+ remove_branch_state(the_repository, 0);
ret = !!finish_rebase(&options);
goto cleanup;
}
case ACTION_QUIT: {
- if (options.type == REBASE_INTERACTIVE) {
+ save_autostash(state_dir_path("autostash", &options));
+ if (options.type == REBASE_MERGE) {
struct replay_opts replay = REPLAY_OPTS_INIT;
replay.action = REPLAY_INTERACTIVE_REBASE;
@@ -1690,13 +1632,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
state_dir_base, cmd_live_rebase, buf.buf);
}
- for (i = 0; i < options.git_am_opts.argc; i++) {
- const char *option = options.git_am_opts.argv[i], *p;
- if (!strcmp(option, "--committer-date-is-author-date") ||
- !strcmp(option, "--ignore-date") ||
- !strcmp(option, "--whitespace=fix") ||
+ if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
+ (action != ACTION_NONE) ||
+ (exec.nr > 0) ||
+ options.autosquash) {
+ allow_preemptive_ff = 0;
+ }
+ if (options.committer_date_is_author_date || options.ignore_date)
+ options.flags |= REBASE_FORCE;
+
+ for (i = 0; i < options.git_am_opts.nr; i++) {
+ const char *option = options.git_am_opts.v[i], *p;
+ if (!strcmp(option, "--whitespace=fix") ||
!strcmp(option, "--whitespace=strip"))
- options.flags |= REBASE_FORCE;
+ allow_preemptive_ff = 0;
else if (skip_prefix(option, "-C", &p)) {
while (*p)
if (!isdigit(*(p++)))
@@ -1714,20 +1663,21 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
exit(1);
if (!(options.flags & REBASE_NO_QUIET))
- argv_array_push(&options.git_am_opts, "-q");
+ strvec_push(&options.git_am_opts, "-q");
+
+ if (options.empty != EMPTY_UNSPECIFIED)
+ imply_merge(&options, "--empty");
- if (options.keep_empty)
- imply_interactive(&options, "--keep-empty");
+ if (options.reapply_cherry_picks)
+ imply_merge(&options, "--reapply-cherry-picks");
- if (gpg_sign) {
- free(options.gpg_sign_opt);
+ if (gpg_sign)
options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
- }
if (exec.nr) {
int i;
- imply_interactive(&options, "--exec");
+ imply_merge(&options, "--exec");
strbuf_reset(&buf);
for (i = 0; i < exec.nr; i++)
@@ -1743,7 +1693,24 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
else if (strcmp("no-rebase-cousins", rebase_merges))
die(_("Unknown mode: %s"), rebase_merges);
options.rebase_merges = 1;
- imply_interactive(&options, "--rebase-merges");
+ imply_merge(&options, "--rebase-merges");
+ }
+
+ if (options.type == REBASE_APPLY) {
+ if (ignore_whitespace)
+ strvec_push(&options.git_am_opts,
+ "--ignore-whitespace");
+ if (options.committer_date_is_author_date)
+ strvec_push(&options.git_am_opts,
+ "--committer-date-is-author-date");
+ if (options.ignore_date)
+ strvec_push(&options.git_am_opts, "--ignore-date");
+ } else {
+ /* REBASE_MERGE and PRESERVE_MERGES */
+ if (ignore_whitespace) {
+ string_list_append(&strategy_options,
+ "ignore-space-change");
+ }
}
if (strategy_options.nr) {
@@ -1762,10 +1729,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.strategy) {
options.strategy = xstrdup(options.strategy);
switch (options.type) {
- case REBASE_AM:
+ case REBASE_APPLY:
die(_("--strategy requires --merge or --interactive"));
case REBASE_MERGE:
- case REBASE_INTERACTIVE:
case REBASE_PRESERVE_MERGES:
/* compatible */
break;
@@ -1778,52 +1744,75 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
}
if (options.type == REBASE_MERGE)
- imply_interactive(&options, "--merge");
+ imply_merge(&options, "--merge");
if (options.root && !options.onto_name)
- imply_interactive(&options, "--root without --onto");
+ imply_merge(&options, "--root without --onto");
if (isatty(2) && options.flags & REBASE_NO_QUIET)
strbuf_addstr(&options.git_format_patch_opt, " --progress");
+ if (options.git_am_opts.nr || options.type == REBASE_APPLY) {
+ /* all am options except -q are compatible only with --apply */
+ for (i = options.git_am_opts.nr - 1; i >= 0; i--)
+ if (strcmp(options.git_am_opts.v[i], "-q"))
+ break;
+
+ if (i >= 0) {
+ if (is_merge(&options))
+ die(_("cannot combine apply options with "
+ "merge options"));
+ else
+ options.type = REBASE_APPLY;
+ }
+ }
+
+ if (options.type == REBASE_UNSPECIFIED) {
+ if (!strcmp(options.default_backend, "merge"))
+ imply_merge(&options, "--merge");
+ else if (!strcmp(options.default_backend, "apply"))
+ options.type = REBASE_APPLY;
+ else
+ die(_("Unknown rebase backend: %s"),
+ options.default_backend);
+ }
+
+ if (options.type == REBASE_MERGE &&
+ !options.strategy &&
+ getenv("GIT_TEST_MERGE_ALGORITHM"))
+ options.strategy = xstrdup(getenv("GIT_TEST_MERGE_ALGORITHM"));
+
switch (options.type) {
case REBASE_MERGE:
- case REBASE_INTERACTIVE:
case REBASE_PRESERVE_MERGES:
options.state_dir = merge_dir();
break;
- case REBASE_AM:
+ case REBASE_APPLY:
options.state_dir = apply_dir();
break;
default:
- /* the default rebase backend is `--am` */
- options.type = REBASE_AM;
- options.state_dir = apply_dir();
- break;
+ BUG("options.type was just set above; should be unreachable.");
}
- if (reschedule_failed_exec > 0 && !is_interactive(&options))
+ if (options.empty == EMPTY_UNSPECIFIED) {
+ if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
+ options.empty = EMPTY_ASK;
+ else if (exec.nr > 0)
+ options.empty = EMPTY_KEEP;
+ else
+ options.empty = EMPTY_DROP;
+ }
+ if (reschedule_failed_exec > 0 && !is_merge(&options))
die(_("--reschedule-failed-exec requires "
"--exec or --interactive"));
if (reschedule_failed_exec >= 0)
options.reschedule_failed_exec = reschedule_failed_exec;
- if (options.git_am_opts.argc) {
- /* all am options except -q are compatible only with --am */
- for (i = options.git_am_opts.argc - 1; i >= 0; i--)
- if (strcmp(options.git_am_opts.argv[i], "-q"))
- break;
-
- if (is_interactive(&options) && i >= 0)
- die(_("cannot combine am options with either "
- "interactive or merge options"));
- }
-
if (options.signoff) {
if (options.type == REBASE_PRESERVE_MERGES)
die("cannot combine '--signoff' with "
"'--preserve-merges'");
- argv_array_push(&options.git_am_opts, "--signoff");
+ strvec_push(&options.git_am_opts, "--signoff");
options.flags |= REBASE_FORCE;
}
@@ -1842,15 +1831,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
"'--reschedule-failed-exec'"));
}
- if (options.rebase_merges) {
- if (strategy_options.nr)
- die(_("cannot combine '--rebase-merges' with "
- "'--strategy-option'"));
- if (options.strategy)
- die(_("cannot combine '--rebase-merges' with "
- "'--strategy'"));
- }
-
if (!options.root) {
if (argc < 1) {
struct branch *branch;
@@ -1881,7 +1861,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.squash_onto = &squash_onto;
options.onto_name = squash_onto_name =
xstrdup(oid_to_hex(&squash_onto));
- }
+ } else
+ options.root_with_onto = 1;
+
options.upstream_name = NULL;
options.upstream = NULL;
if (argc > 1)
@@ -1891,12 +1873,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
}
/* Make sure the branch to rebase onto is valid. */
- if (!options.onto_name)
+ if (keep_base) {
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, options.upstream_name);
+ strbuf_addstr(&buf, "...");
+ options.onto_name = xstrdup(buf.buf);
+ } else if (!options.onto_name)
options.onto_name = options.upstream_name;
if (strstr(options.onto_name, "...")) {
- if (get_oid_mb(options.onto_name, &merge_base) < 0)
- die(_("'%s': need exactly one merge base"),
- options.onto_name);
+ if (get_oid_mb(options.onto_name, &merge_base) < 0) {
+ if (keep_base)
+ die(_("'%s': need exactly one merge base with branch"),
+ options.upstream_name);
+ else
+ die(_("'%s': need exactly one merge base"),
+ options.onto_name);
+ }
options.onto = lookup_commit_or_die(&merge_base,
options.onto_name);
} else {
@@ -1921,10 +1913,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
/* Is it a local branch? */
strbuf_reset(&buf);
strbuf_addf(&buf, "refs/heads/%s", branch_name);
- if (!read_ref(buf.buf, &options.orig_head))
+ if (!read_ref(buf.buf, &options.orig_head)) {
+ die_if_checked_out(buf.buf, 1);
options.head_name = xstrdup(buf.buf);
/* If not is it a valid ref (branch or commit)? */
- else if (!get_oid(branch_name, &options.orig_head))
+ } else if (!get_oid(branch_name, &options.orig_head))
options.head_name = NULL;
else
die(_("fatal: no such branch/commit '%s'"),
@@ -1962,60 +1955,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die(_("could not read index"));
if (options.autostash) {
- struct lock_file lock_file = LOCK_INIT;
- int fd;
-
- fd = hold_locked_index(&lock_file, 0);
- refresh_cache(REFRESH_QUIET);
- if (0 <= fd)
- repo_update_index_if_able(the_repository, &lock_file);
- rollback_lock_file(&lock_file);
-
- if (has_unstaged_changes(the_repository, 1) ||
- has_uncommitted_changes(the_repository, 1)) {
- const char *autostash =
- state_dir_path("autostash", &options);
- struct child_process stash = CHILD_PROCESS_INIT;
- struct object_id oid;
- struct commit *head =
- lookup_commit_reference(the_repository,
- &options.orig_head);
-
- argv_array_pushl(&stash.args,
- "stash", "create", "autostash", NULL);
- stash.git_cmd = 1;
- stash.no_stdin = 1;
- strbuf_reset(&buf);
- if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
- die(_("Cannot autostash"));
- strbuf_trim_trailing_newline(&buf);
- if (get_oid(buf.buf, &oid))
- die(_("Unexpected stash response: '%s'"),
- buf.buf);
- strbuf_reset(&buf);
- strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
-
- if (safe_create_leading_directories_const(autostash))
- die(_("Could not create directory for '%s'"),
- options.state_dir);
- write_file(autostash, "%s", oid_to_hex(&oid));
- printf(_("Created autostash: %s\n"), buf.buf);
- if (reset_head(&head->object.oid, "reset --hard",
- NULL, RESET_HEAD_HARD, NULL, NULL) < 0)
- die(_("could not reset --hard"));
- printf(_("HEAD is now at %s"),
- find_unique_abbrev(&head->object.oid,
- DEFAULT_ABBREV));
- strbuf_reset(&buf);
- pp_commit_easy(CMIT_FMT_ONELINE, head, &buf);
- if (buf.len > 0)
- printf(" %s", buf.buf);
- putchar('\n');
-
- if (discard_index(the_repository->index) < 0 ||
- repo_read_index(the_repository) < 0)
- die(_("could not read index"));
- }
+ create_autostash(the_repository, state_dir_path("autostash", &options),
+ DEFAULT_REFLOG_ACTION);
}
if (require_clean_work_tree(the_repository, "rebase",
@@ -2031,34 +1972,30 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
/*
* Check if we are already based on onto with linear history,
- * but this should be done only when upstream and onto are the same
- * and if this is not an interactive rebase.
+ * in which case we could fast-forward without replacing the commits
+ * with new commits recreated by replaying their changes.
+ *
+ * Note that can_fast_forward() initializes merge_base, so we have to
+ * call it before checking allow_preemptive_ff.
*/
- if (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&
- !is_interactive(&options) && !options.restrict_revision &&
- options.upstream &&
- !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {
+ if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
+ &options.orig_head, &merge_base) &&
+ allow_preemptive_ff) {
int flag;
if (!(options.flags & REBASE_FORCE)) {
/* Lazily switch to the target branch if needed... */
if (options.switch_to) {
- struct object_id oid;
-
- if (get_oid(options.switch_to, &oid) < 0) {
- ret = !!error(_("could not parse '%s'"),
- options.switch_to);
- goto cleanup;
- }
-
strbuf_reset(&buf);
strbuf_addf(&buf, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
options.switch_to);
- if (reset_head(&oid, "checkout",
+ if (reset_head(the_repository,
+ &options.orig_head, "checkout",
options.head_name,
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, buf.buf) < 0) {
+ NULL, buf.buf,
+ DEFAULT_REFLOG_ACTION) < 0) {
ret = !!error(_("could not switch to "
"%s"),
options.switch_to);
@@ -2120,7 +2057,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
diff_flush(&opts);
}
- if (is_interactive(&options))
+ if (is_merge(&options))
goto run_rebase;
/* Detach HEAD and reset the tree */
@@ -2130,10 +2067,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&msg, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
- if (reset_head(&options.onto->object.oid, "checkout", NULL,
- RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL,
+ RESET_HEAD_DETACH | RESET_ORIG_HEAD |
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, msg.buf))
+ NULL, msg.buf, DEFAULT_REFLOG_ACTION))
die(_("Could not detach HEAD"));
strbuf_release(&msg);
@@ -2148,8 +2085,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&msg, "rebase finished: %s onto %s",
options.head_name ? options.head_name : "detached HEAD",
oid_to_hex(&options.onto->object.oid));
- reset_head(NULL, "Fast-forwarded", options.head_name,
- RESET_HEAD_REFS_ONLY, "HEAD", msg.buf);
+ reset_head(the_repository, NULL, "Fast-forwarded", options.head_name,
+ RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
+ DEFAULT_REFLOG_ACTION);
strbuf_release(&msg);
ret = !!finish_rebase(&options);
goto cleanup;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c5f5da940b..f1f0f7bef6 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -12,11 +12,10 @@
#include "object.h"
#include "remote.h"
#include "connect.h"
-#include "transport.h"
#include "string-list.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "connected.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "version.h"
#include "tag.h"
#include "gpg-interface.h"
@@ -28,6 +27,8 @@
#include "object-store.h"
#include "protocol.h"
#include "commit-reach.h"
+#include "worktree.h"
+#include "shallow.h"
static const char * const receive_pack_usage[] = {
N_("git receive-pack <git-dir>"),
@@ -56,6 +57,7 @@ static int advertise_push_options;
static int unpack_limit = 100;
static off_t max_input_size;
static int report_status;
+static int report_status_v2;
static int use_sideband;
static int use_atomic;
static int use_push_options;
@@ -96,6 +98,17 @@ static int keepalive_in_sec = 5;
static struct tmp_objdir *tmp_objdir;
+static struct proc_receive_ref {
+ unsigned int want_add:1,
+ want_delete:1,
+ want_modify:1,
+ negative_ref:1;
+ char *ref_prefix;
+ struct proc_receive_ref *next;
+} *proc_receive_ref;
+
+static void proc_receive_ref_append(const char *prefix);
+
static enum deny_action parse_deny_action(const char *var, const char *value)
{
if (value) {
@@ -228,6 +241,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "receive.procreceiverefs") == 0) {
+ if (!value)
+ return config_error_nonbool(var);
+ proc_receive_ref_append(value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -239,7 +259,7 @@ static void show_ref(const char *path, const struct object_id *oid)
struct strbuf cap = STRBUF_INIT;
strbuf_addstr(&cap,
- "report-status delete-refs side-band-64k quiet");
+ "report-status report-status-v2 delete-refs side-band-64k quiet");
if (advertise_atomic_push)
strbuf_addstr(&cap, " atomic");
if (prefer_ofs_delta)
@@ -248,6 +268,7 @@ static void show_ref(const char *path, const struct object_id *oid)
strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
if (advertise_push_options)
strbuf_addstr(&cap, " push-options");
+ strbuf_addf(&cap, " object-format=%s", the_hash_algo->name);
strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
packet_write_fmt(1, "%s %s%c%s\n",
oid_to_hex(oid), path, 0, cap.buf);
@@ -308,17 +329,94 @@ static void write_head_info(void)
packet_flush(1);
}
+#define RUN_PROC_RECEIVE_SCHEDULED 1
+#define RUN_PROC_RECEIVE_RETURNED 2
struct command {
struct command *next;
const char *error_string;
+ struct ref_push_report *report;
unsigned int skip_update:1,
- did_not_exist:1;
+ did_not_exist:1,
+ run_proc_receive:2;
int index;
struct object_id old_oid;
struct object_id new_oid;
char ref_name[FLEX_ARRAY]; /* more */
};
+static void proc_receive_ref_append(const char *prefix)
+{
+ struct proc_receive_ref *ref_pattern;
+ char *p;
+ int len;
+
+ ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));
+ p = strchr(prefix, ':');
+ if (p) {
+ while (prefix < p) {
+ if (*prefix == 'a')
+ ref_pattern->want_add = 1;
+ else if (*prefix == 'd')
+ ref_pattern->want_delete = 1;
+ else if (*prefix == 'm')
+ ref_pattern->want_modify = 1;
+ else if (*prefix == '!')
+ ref_pattern->negative_ref = 1;
+ prefix++;
+ }
+ prefix++;
+ } else {
+ ref_pattern->want_add = 1;
+ ref_pattern->want_delete = 1;
+ ref_pattern->want_modify = 1;
+ }
+ len = strlen(prefix);
+ while (len && prefix[len - 1] == '/')
+ len--;
+ ref_pattern->ref_prefix = xmemdupz(prefix, len);
+ if (!proc_receive_ref) {
+ proc_receive_ref = ref_pattern;
+ } else {
+ struct proc_receive_ref *end;
+
+ end = proc_receive_ref;
+ while (end->next)
+ end = end->next;
+ end->next = ref_pattern;
+ }
+}
+
+static int proc_receive_ref_matches(struct command *cmd)
+{
+ struct proc_receive_ref *p;
+
+ if (!proc_receive_ref)
+ return 0;
+
+ for (p = proc_receive_ref; p; p = p->next) {
+ const char *match = p->ref_prefix;
+ const char *remains;
+
+ if (!p->want_add && is_null_oid(&cmd->old_oid))
+ continue;
+ else if (!p->want_delete && is_null_oid(&cmd->new_oid))
+ continue;
+ else if (!p->want_modify &&
+ !is_null_oid(&cmd->old_oid) &&
+ !is_null_oid(&cmd->new_oid))
+ continue;
+
+ if (skip_prefix(cmd->ref_name, match, &remains) &&
+ (!*remains || *remains == '/')) {
+ if (!p->negative_ref)
+ return 1;
+ } else if (p->negative_ref) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
@@ -418,24 +516,22 @@ static int copy_to_sideband(int in, int out, void *arg)
return 0;
}
-#define HMAC_BLOCK_SIZE 64
-
-static void hmac_sha1(unsigned char *out,
+static void hmac_hash(unsigned char *out,
const char *key_in, size_t key_len,
const char *text, size_t text_len)
{
- unsigned char key[HMAC_BLOCK_SIZE];
- unsigned char k_ipad[HMAC_BLOCK_SIZE];
- unsigned char k_opad[HMAC_BLOCK_SIZE];
+ unsigned char key[GIT_MAX_BLKSZ];
+ unsigned char k_ipad[GIT_MAX_BLKSZ];
+ unsigned char k_opad[GIT_MAX_BLKSZ];
int i;
- git_SHA_CTX ctx;
+ git_hash_ctx ctx;
/* RFC 2104 2. (1) */
- memset(key, '\0', HMAC_BLOCK_SIZE);
- if (HMAC_BLOCK_SIZE < key_len) {
- git_SHA1_Init(&ctx);
- git_SHA1_Update(&ctx, key_in, key_len);
- git_SHA1_Final(key, &ctx);
+ memset(key, '\0', GIT_MAX_BLKSZ);
+ if (the_hash_algo->blksz < key_len) {
+ the_hash_algo->init_fn(&ctx);
+ the_hash_algo->update_fn(&ctx, key_in, key_len);
+ the_hash_algo->final_fn(key, &ctx);
} else {
memcpy(key, key_in, key_len);
}
@@ -447,29 +543,29 @@ static void hmac_sha1(unsigned char *out,
}
/* RFC 2104 2. (3) & (4) */
- git_SHA1_Init(&ctx);
- git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad));
- git_SHA1_Update(&ctx, text, text_len);
- git_SHA1_Final(out, &ctx);
+ the_hash_algo->init_fn(&ctx);
+ the_hash_algo->update_fn(&ctx, k_ipad, sizeof(k_ipad));
+ the_hash_algo->update_fn(&ctx, text, text_len);
+ the_hash_algo->final_fn(out, &ctx);
/* RFC 2104 2. (6) & (7) */
- git_SHA1_Init(&ctx);
- git_SHA1_Update(&ctx, k_opad, sizeof(k_opad));
- git_SHA1_Update(&ctx, out, GIT_SHA1_RAWSZ);
- git_SHA1_Final(out, &ctx);
+ the_hash_algo->init_fn(&ctx);
+ the_hash_algo->update_fn(&ctx, k_opad, sizeof(k_opad));
+ the_hash_algo->update_fn(&ctx, out, the_hash_algo->rawsz);
+ the_hash_algo->final_fn(out, &ctx);
}
static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp)
{
struct strbuf buf = STRBUF_INIT;
- unsigned char sha1[GIT_SHA1_RAWSZ];
+ unsigned char hash[GIT_MAX_RAWSZ];
strbuf_addf(&buf, "%s:%"PRItime, path, stamp);
- hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));
+ hmac_hash(hash, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));
strbuf_release(&buf);
- /* RFC 2104 5. HMAC-SHA1-80 */
- strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, GIT_SHA1_HEXSZ, sha1_to_hex(sha1));
+ /* RFC 2104 5. HMAC-SHA1 or HMAC-SHA256 */
+ strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, (int)the_hash_algo->hexsz, hash_to_hex(hash));
return strbuf_detach(&buf, NULL);
}
@@ -501,12 +597,27 @@ static char *find_header(const char *msg, size_t len, const char *key,
return NULL;
}
+/*
+ * Return zero if a and b are equal up to n bytes and nonzero if they are not.
+ * This operation is guaranteed to run in constant time to avoid leaking data.
+ */
+static int constant_memequal(const char *a, const char *b, size_t n)
+{
+ int res = 0;
+ size_t i;
+
+ for (i = 0; i < n; i++)
+ res |= a[i] ^ b[i];
+ return res;
+}
+
static const char *check_nonce(const char *buf, size_t len)
{
char *nonce = find_header(buf, len, "nonce", NULL);
timestamp_t stamp, ostamp;
char *bohmac, *expect = NULL;
const char *retval = NONCE_BAD;
+ size_t noncelen;
if (!nonce) {
retval = NONCE_MISSING;
@@ -548,8 +659,14 @@ static const char *check_nonce(const char *buf, size_t len)
goto leave;
}
+ noncelen = strlen(nonce);
expect = prepare_push_cert_nonce(service_dir, stamp);
- if (strcmp(expect, nonce)) {
+ if (noncelen != strlen(expect)) {
+ /* This is not even the right size. */
+ retval = NONCE_BAD;
+ goto leave;
+ }
+ if (constant_memequal(expect, nonce, noncelen)) {
/* Not what we would have signed earlier */
retval = NONCE_BAD;
goto leave;
@@ -646,31 +763,32 @@ static void prepare_push_cert_sha1(struct child_process *proc)
nonce_status = check_nonce(push_cert.buf, bogs);
}
if (!is_null_oid(&push_cert_oid)) {
- argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s",
- oid_to_hex(&push_cert_oid));
- argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s",
- sigcheck.signer ? sigcheck.signer : "");
- argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s",
- sigcheck.key ? sigcheck.key : "");
- argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c",
- sigcheck.result);
+ strvec_pushf(&proc->env_array, "GIT_PUSH_CERT=%s",
+ oid_to_hex(&push_cert_oid));
+ strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s",
+ sigcheck.signer ? sigcheck.signer : "");
+ strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s",
+ sigcheck.key ? sigcheck.key : "");
+ strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c",
+ sigcheck.result);
if (push_cert_nonce) {
- argv_array_pushf(&proc->env_array,
- "GIT_PUSH_CERT_NONCE=%s",
- push_cert_nonce);
- argv_array_pushf(&proc->env_array,
- "GIT_PUSH_CERT_NONCE_STATUS=%s",
- nonce_status);
+ strvec_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE=%s",
+ push_cert_nonce);
+ strvec_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE_STATUS=%s",
+ nonce_status);
if (nonce_status == NONCE_SLOP)
- argv_array_pushf(&proc->env_array,
- "GIT_PUSH_CERT_NONCE_SLOP=%ld",
- nonce_stamp_slop);
+ strvec_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE_SLOP=%ld",
+ nonce_stamp_slop);
}
}
}
struct receive_hook_feed_state {
struct command *cmd;
+ struct ref_push_report *report;
int skip_broken;
struct strbuf buf;
const struct string_list *push_options;
@@ -699,16 +817,16 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
if (feed_state->push_options) {
int i;
for (i = 0; i < feed_state->push_options->nr; i++)
- argv_array_pushf(&proc.env_array,
- "GIT_PUSH_OPTION_%d=%s", i,
- feed_state->push_options->items[i].string);
- argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
- feed_state->push_options->nr);
+ strvec_pushf(&proc.env_array,
+ "GIT_PUSH_OPTION_%d=%s", i,
+ feed_state->push_options->items[i].string);
+ strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
+ feed_state->push_options->nr);
} else
- argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
+ strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
if (tmp_objdir)
- argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));
+ strvec_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
@@ -758,11 +876,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
cmd = cmd->next;
if (!cmd)
return -1; /* EOF */
+ if (!bufp)
+ return 0; /* OK, can feed something. */
strbuf_reset(&state->buf);
- strbuf_addf(&state->buf, "%s %s %s\n",
- oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
- cmd->ref_name);
- state->cmd = cmd->next;
+ if (!state->report)
+ state->report = cmd->report;
+ if (state->report) {
+ struct object_id *old_oid;
+ struct object_id *new_oid;
+ const char *ref_name;
+
+ old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;
+ new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;
+ ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name;
+ strbuf_addf(&state->buf, "%s %s %s\n",
+ oid_to_hex(old_oid), oid_to_hex(new_oid),
+ ref_name);
+ state->report = state->report->next;
+ if (!state->report)
+ state->cmd = cmd->next;
+ } else {
+ strbuf_addf(&state->buf, "%s %s %s\n",
+ oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
+ cmd->ref_name);
+ state->cmd = cmd->next;
+ }
if (bufp) {
*bufp = state->buf.buf;
*sizep = state->buf.len;
@@ -781,6 +919,7 @@ static int run_receive_hook(struct command *commands,
strbuf_init(&state.buf, 0);
state.cmd = commands;
state.skip_broken = skip_broken;
+ state.report = NULL;
if (feed_receive_hook(&state, NULL, NULL))
return 0;
state.cmd = commands;
@@ -819,14 +958,310 @@ static int run_update_hook(struct command *cmd)
return finish_command(&proc);
}
-static int is_ref_checked_out(const char *ref)
+static struct command *find_command_by_refname(struct command *list,
+ const char *refname)
{
- if (is_bare_repository())
- return 0;
+ for (; list; list = list->next)
+ if (!strcmp(list->ref_name, refname))
+ return list;
+ return NULL;
+}
- if (!head_name)
- return 0;
- return !strcmp(head_name, ref);
+static int read_proc_receive_report(struct packet_reader *reader,
+ struct command *commands,
+ struct strbuf *errmsg)
+{
+ struct command *cmd;
+ struct command *hint = NULL;
+ struct ref_push_report *report = NULL;
+ int new_report = 0;
+ int code = 0;
+ int once = 0;
+ int response = 0;
+
+ for (;;) {
+ struct object_id old_oid, new_oid;
+ const char *head;
+ const char *refname;
+ char *p;
+ enum packet_read_status status;
+
+ status = packet_reader_read(reader);
+ if (status != PACKET_READ_NORMAL) {
+ /* Check whether proc-receive exited abnormally */
+ if (status == PACKET_READ_EOF && !response) {
+ strbuf_addstr(errmsg, "proc-receive exited abnormally");
+ return -1;
+ }
+ break;
+ }
+ response++;
+
+ head = reader->line;
+ p = strchr(head, ' ');
+ if (!p) {
+ strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head);
+ code = -1;
+ continue;
+ }
+ *p++ = '\0';
+ if (!strcmp(head, "option")) {
+ const char *key, *val;
+
+ if (!hint || !(report || new_report)) {
+ if (!once++)
+ strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n");
+ code = -1;
+ continue;
+ }
+ if (new_report) {
+ if (!hint->report) {
+ hint->report = xcalloc(1, sizeof(struct ref_push_report));
+ report = hint->report;
+ } else {
+ report = hint->report;
+ while (report->next)
+ report = report->next;
+ report->next = xcalloc(1, sizeof(struct ref_push_report));
+ report = report->next;
+ }
+ new_report = 0;
+ }
+ key = p;
+ p = strchr(key, ' ');
+ if (p)
+ *p++ = '\0';
+ val = p;
+ if (!strcmp(key, "refname"))
+ report->ref_name = xstrdup_or_null(val);
+ else if (!strcmp(key, "old-oid") && val &&
+ !parse_oid_hex(val, &old_oid, &val))
+ report->old_oid = oiddup(&old_oid);
+ else if (!strcmp(key, "new-oid") && val &&
+ !parse_oid_hex(val, &new_oid, &val))
+ report->new_oid = oiddup(&new_oid);
+ else if (!strcmp(key, "forced-update"))
+ report->forced_update = 1;
+ else if (!strcmp(key, "fall-through"))
+ /* Fall through, let 'receive-pack' to execute it. */
+ hint->run_proc_receive = 0;
+ continue;
+ }
+
+ report = NULL;
+ new_report = 0;
+ refname = p;
+ p = strchr(refname, ' ');
+ if (p)
+ *p++ = '\0';
+ if (strcmp(head, "ok") && strcmp(head, "ng")) {
+ strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n",
+ head, refname);
+ code = -1;
+ continue;
+ }
+
+ /* first try searching at our hint, falling back to all refs */
+ if (hint)
+ hint = find_command_by_refname(hint, refname);
+ if (!hint)
+ hint = find_command_by_refname(commands, refname);
+ if (!hint) {
+ strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n",
+ refname);
+ code = -1;
+ continue;
+ }
+ if (!hint->run_proc_receive) {
+ strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n",
+ refname);
+ code = -1;
+ continue;
+ }
+ hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;
+ if (!strcmp(head, "ng")) {
+ if (p)
+ hint->error_string = xstrdup(p);
+ else
+ hint->error_string = "failed";
+ code = -1;
+ continue;
+ }
+ new_report = 1;
+ }
+
+ for (cmd = commands; cmd; cmd = cmd->next)
+ if (cmd->run_proc_receive && !cmd->error_string &&
+ !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {
+ cmd->error_string = "proc-receive failed to report status";
+ code = -1;
+ }
+ return code;
+}
+
+static int run_proc_receive_hook(struct command *commands,
+ const struct string_list *push_options)
+{
+ struct child_process proc = CHILD_PROCESS_INIT;
+ struct async muxer;
+ struct command *cmd;
+ const char *argv[2];
+ struct packet_reader reader;
+ struct strbuf cap = STRBUF_INIT;
+ struct strbuf errmsg = STRBUF_INIT;
+ int hook_use_push_options = 0;
+ int version = 0;
+ int code;
+
+ argv[0] = find_hook("proc-receive");
+ if (!argv[0]) {
+ rp_error("cannot find hook 'proc-receive'");
+ return -1;
+ }
+ argv[1] = NULL;
+
+ proc.argv = argv;
+ proc.in = -1;
+ proc.out = -1;
+ proc.trace2_hook_name = "proc-receive";
+
+ if (use_sideband) {
+ memset(&muxer, 0, sizeof(muxer));
+ muxer.proc = copy_to_sideband;
+ muxer.in = -1;
+ code = start_async(&muxer);
+ if (code)
+ return code;
+ proc.err = muxer.in;
+ } else {
+ proc.err = 0;
+ }
+
+ code = start_command(&proc);
+ if (code) {
+ if (use_sideband)
+ finish_async(&muxer);
+ return code;
+ }
+
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ /* Version negotiaton */
+ packet_reader_init(&reader, proc.out, NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_GENTLE_ON_EOF);
+ if (use_atomic)
+ strbuf_addstr(&cap, " atomic");
+ if (use_push_options)
+ strbuf_addstr(&cap, " push-options");
+ if (cap.len) {
+ code = packet_write_fmt_gently(proc.in, "version=1%c%s\n", '\0', cap.buf + 1);
+ strbuf_release(&cap);
+ } else {
+ code = packet_write_fmt_gently(proc.in, "version=1\n");
+ }
+ if (!code)
+ code = packet_flush_gently(proc.in);
+
+ if (!code)
+ for (;;) {
+ int linelen;
+ enum packet_read_status status;
+
+ status = packet_reader_read(&reader);
+ if (status != PACKET_READ_NORMAL) {
+ /* Check whether proc-receive exited abnormally */
+ if (status == PACKET_READ_EOF)
+ code = -1;
+ break;
+ }
+
+ if (reader.pktlen > 8 && starts_with(reader.line, "version=")) {
+ version = atoi(reader.line + 8);
+ linelen = strlen(reader.line);
+ if (linelen < reader.pktlen) {
+ const char *feature_list = reader.line + linelen + 1;
+ if (parse_feature_request(feature_list, "push-options"))
+ hook_use_push_options = 1;
+ }
+ }
+ }
+
+ if (code) {
+ strbuf_addstr(&errmsg, "fail to negotiate version with proc-receive hook");
+ goto cleanup;
+ }
+
+ switch (version) {
+ case 0:
+ /* fallthrough */
+ case 1:
+ break;
+ default:
+ strbuf_addf(&errmsg, "proc-receive version '%d' is not supported",
+ version);
+ code = -1;
+ goto cleanup;
+ }
+
+ /* Send commands */
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)
+ continue;
+ code = packet_write_fmt_gently(proc.in, "%s %s %s",
+ oid_to_hex(&cmd->old_oid),
+ oid_to_hex(&cmd->new_oid),
+ cmd->ref_name);
+ if (code)
+ break;
+ }
+ if (!code)
+ code = packet_flush_gently(proc.in);
+ if (code) {
+ strbuf_addstr(&errmsg, "fail to write commands to proc-receive hook");
+ goto cleanup;
+ }
+
+ /* Send push options */
+ if (hook_use_push_options) {
+ struct string_list_item *item;
+
+ for_each_string_list_item(item, push_options) {
+ code = packet_write_fmt_gently(proc.in, "%s", item->string);
+ if (code)
+ break;
+ }
+ if (!code)
+ code = packet_flush_gently(proc.in);
+ if (code) {
+ strbuf_addstr(&errmsg,
+ "fail to write push-options to proc-receive hook");
+ goto cleanup;
+ }
+ }
+
+ /* Read result from proc-receive */
+ code = read_proc_receive_report(&reader, commands, &errmsg);
+
+cleanup:
+ close(proc.in);
+ close(proc.out);
+ if (use_sideband)
+ finish_async(&muxer);
+ if (finish_command(&proc))
+ code = -1;
+ if (errmsg.len >0) {
+ char *p = errmsg.buf;
+
+ p += errmsg.len - 1;
+ if (*p == '\n')
+ *p = '\0';
+ rp_error("%s", errmsg.buf);
+ strbuf_release(&errmsg);
+ }
+ sigchain_pop(SIGPIPE);
+
+ return code;
}
static char *refuse_unconfigured_deny_msg =
@@ -867,7 +1302,7 @@ static void refuse_unconfigured_deny_delete_current(void)
static int command_singleton_iterator(void *cb_data, struct object_id *oid);
static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
{
- struct lock_file shallow_lock = LOCK_INIT;
+ struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT;
struct oid_array extra = OID_ARRAY_INIT;
struct check_connected_options opt = CHECK_CONNECTED_INIT;
uint32_t mask = 1 << (cmd->index % 32);
@@ -884,12 +1319,12 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
opt.env = tmp_objdir_env(tmp_objdir);
setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
if (check_connected(command_singleton_iterator, cmd, &opt)) {
- rollback_lock_file(&shallow_lock);
+ rollback_shallow_file(the_repository, &shallow_lock);
oid_array_clear(&extra);
return -1;
}
- commit_lock_file(&shallow_lock);
+ commit_shallow_file(the_repository, &shallow_lock);
/*
* Make sure setup_alternate_shallow() for the next ref does
@@ -920,7 +1355,7 @@ static int head_has_history(void)
}
static const char *push_to_deploy(unsigned char *sha1,
- struct argv_array *env,
+ struct strvec *env,
const char *work_tree)
{
const char *update_refresh[] = {
@@ -939,7 +1374,7 @@ static const char *push_to_deploy(unsigned char *sha1,
struct child_process child = CHILD_PROCESS_INIT;
child.argv = update_refresh;
- child.env = env->argv;
+ child.env = env->v;
child.dir = work_tree;
child.no_stdin = 1;
child.stdout_to_stderr = 1;
@@ -950,7 +1385,7 @@ static const char *push_to_deploy(unsigned char *sha1,
/* run_command() does not clean up completely; reinitialize */
child_process_init(&child);
child.argv = diff_files;
- child.env = env->argv;
+ child.env = env->v;
child.dir = work_tree;
child.no_stdin = 1;
child.stdout_to_stderr = 1;
@@ -963,7 +1398,7 @@ static const char *push_to_deploy(unsigned char *sha1,
child_process_init(&child);
child.argv = diff_index;
- child.env = env->argv;
+ child.env = env->v;
child.no_stdin = 1;
child.no_stdout = 1;
child.stdout_to_stderr = 0;
@@ -971,10 +1406,10 @@ static const char *push_to_deploy(unsigned char *sha1,
if (run_command(&child))
return "Working directory has staged changes";
- read_tree[3] = sha1_to_hex(sha1);
+ read_tree[3] = hash_to_hex(sha1);
child_process_init(&child);
child.argv = read_tree;
- child.env = env->argv;
+ child.env = env->v;
child.dir = work_tree;
child.no_stdin = 1;
child.no_stdout = 1;
@@ -988,35 +1423,45 @@ static const char *push_to_deploy(unsigned char *sha1,
static const char *push_to_checkout_hook = "push-to-checkout";
-static const char *push_to_checkout(unsigned char *sha1,
- struct argv_array *env,
+static const char *push_to_checkout(unsigned char *hash,
+ struct strvec *env,
const char *work_tree)
{
- argv_array_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
- if (run_hook_le(env->argv, push_to_checkout_hook,
- sha1_to_hex(sha1), NULL))
+ strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
+ if (run_hook_le(env->v, push_to_checkout_hook,
+ hash_to_hex(hash), NULL))
return "push-to-checkout hook declined";
else
return NULL;
}
-static const char *update_worktree(unsigned char *sha1)
+static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree)
{
- const char *retval;
- const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
- struct argv_array env = ARGV_ARRAY_INIT;
+ const char *retval, *work_tree, *git_dir = NULL;
+ struct strvec env = STRVEC_INIT;
+
+ if (worktree && worktree->path)
+ work_tree = worktree->path;
+ else if (git_work_tree_cfg)
+ work_tree = git_work_tree_cfg;
+ else
+ work_tree = "..";
if (is_bare_repository())
return "denyCurrentBranch = updateInstead needs a worktree";
+ if (worktree)
+ git_dir = get_worktree_git_dir(worktree);
+ if (!git_dir)
+ git_dir = get_git_dir();
- argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+ strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir));
if (!find_hook(push_to_checkout_hook))
retval = push_to_deploy(sha1, &env, work_tree);
else
retval = push_to_checkout(sha1, &env, work_tree);
- argv_array_clear(&env);
+ strvec_clear(&env);
return retval;
}
@@ -1029,6 +1474,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
struct object_id *old_oid = &cmd->old_oid;
struct object_id *new_oid = &cmd->new_oid;
int do_update_worktree = 0;
+ const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name);
/* only refs/... are allowed */
if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
@@ -1040,7 +1486,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
free(namespaced_name);
namespaced_name = strbuf_detach(&namespaced_name_buf, NULL);
- if (is_ref_checked_out(namespaced_name)) {
+ if (worktree) {
switch (deny_current_branch) {
case DENY_IGNORE:
break;
@@ -1072,7 +1518,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
return "deletion prohibited";
}
- if (head_name && !strcmp(namespaced_name, head_name)) {
+ if (worktree || (head_name && !strcmp(namespaced_name, head_name))) {
switch (deny_delete_current) {
case DENY_IGNORE:
break;
@@ -1121,7 +1567,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
}
if (do_update_worktree) {
- ret = update_worktree(new_oid->hash);
+ ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name));
if (ret)
return ret;
}
@@ -1183,11 +1629,11 @@ static void run_update_post_hook(struct command *commands)
for (cmd = commands; cmd; cmd = cmd->next) {
if (cmd->error_string || cmd->did_not_exist)
continue;
- if (!proc.args.argc)
- argv_array_push(&proc.args, hook);
- argv_array_push(&proc.args, cmd->ref_name);
+ if (!proc.args.nr)
+ strvec_push(&proc.args, hook);
+ strvec_push(&proc.args, cmd->ref_name);
}
- if (!proc.args.argc)
+ if (!proc.args.nr)
return;
proc.no_stdin = 1;
@@ -1393,7 +1839,7 @@ static void execute_commands_non_atomic(struct command *commands,
struct strbuf err = STRBUF_INIT;
for (cmd = commands; cmd; cmd = cmd->next) {
- if (!should_process_cmd(cmd))
+ if (!should_process_cmd(cmd) || cmd->run_proc_receive)
continue;
transaction = ref_transaction_begin(&err);
@@ -1433,7 +1879,7 @@ static void execute_commands_atomic(struct command *commands,
}
for (cmd = commands; cmd; cmd = cmd->next) {
- if (!should_process_cmd(cmd))
+ if (!should_process_cmd(cmd) || cmd->run_proc_receive)
continue;
cmd->error_string = update(cmd, si);
@@ -1469,6 +1915,7 @@ static void execute_commands(struct command *commands,
struct iterate_data data;
struct async muxer;
int err_fd = 0;
+ int run_proc_receive = 0;
if (unpacker_error) {
for (cmd = commands; cmd; cmd = cmd->next)
@@ -1498,6 +1945,22 @@ static void execute_commands(struct command *commands,
reject_updates_to_hidden(commands);
+ /*
+ * Try to find commands that have special prefix in their reference names,
+ * and mark them to run an external "proc-receive" hook later.
+ */
+ if (proc_receive_ref) {
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!should_process_cmd(cmd))
+ continue;
+
+ if (proc_receive_ref_matches(cmd)) {
+ cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;
+ run_proc_receive = 1;
+ }
+ }
+ }
+
if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
@@ -1524,6 +1987,14 @@ static void execute_commands(struct command *commands,
free(head_name_to_free);
head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL);
+ if (run_proc_receive &&
+ run_proc_receive_hook(commands, push_options))
+ for (cmd = commands; cmd; cmd = cmd->next)
+ if (!cmd->error_string &&
+ !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&
+ (cmd->run_proc_receive || use_atomic))
+ cmd->error_string = "fail to run proc-receive hook";
+
if (use_atomic)
execute_commands_atomic(commands, si);
else
@@ -1603,8 +2074,12 @@ static struct command *read_head_info(struct packet_reader *reader,
linelen = strlen(reader->line);
if (linelen < reader->pktlen) {
const char *feature_list = reader->line + linelen + 1;
+ const char *hash = NULL;
+ int len = 0;
if (parse_feature_request(feature_list, "report-status"))
report_status = 1;
+ if (parse_feature_request(feature_list, "report-status-v2"))
+ report_status_v2 = 1;
if (parse_feature_request(feature_list, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
if (parse_feature_request(feature_list, "quiet"))
@@ -1615,6 +2090,13 @@ static struct command *read_head_info(struct packet_reader *reader,
if (advertise_push_options
&& parse_feature_request(feature_list, "push-options"))
use_push_options = 1;
+ hash = parse_feature_value(feature_list, "object-format", &len, NULL);
+ if (!hash) {
+ hash = hash_algos[GIT_HASH_SHA1].name;
+ len = strlen(hash);
+ }
+ if (xstrncmpz(the_hash_algo->name, hash, len))
+ die("error: unsupported object format '%s'", hash);
}
if (!strcmp(reader->line, "push-cert")) {
@@ -1684,10 +2166,10 @@ static const char *parse_pack_header(struct pack_header *hdr)
static const char *pack_lockfile;
-static void push_header_arg(struct argv_array *args, struct pack_header *hdr)
+static void push_header_arg(struct strvec *args, struct pack_header *hdr)
{
- argv_array_pushf(args, "--pack_header=%"PRIu32",%"PRIu32,
- ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries));
+ strvec_pushf(args, "--pack_header=%"PRIu32",%"PRIu32,
+ ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries));
}
static const char *unpack(int err_fd, struct shallow_info *si)
@@ -1711,8 +2193,8 @@ static const char *unpack(int err_fd, struct shallow_info *si)
if (si->nr_ours || si->nr_theirs) {
alt_shallow_file = setup_temporary_shallow(si->shallow);
- argv_array_push(&child.args, "--shallow-file");
- argv_array_push(&child.args, alt_shallow_file);
+ strvec_push(&child.args, "--shallow-file");
+ strvec_push(&child.args, alt_shallow_file);
}
tmp_objdir = tmp_objdir_create();
@@ -1731,16 +2213,16 @@ static const char *unpack(int err_fd, struct shallow_info *si)
tmp_objdir_add_as_alternate(tmp_objdir);
if (ntohl(hdr.hdr_entries) < unpack_limit) {
- argv_array_push(&child.args, "unpack-objects");
+ strvec_push(&child.args, "unpack-objects");
push_header_arg(&child.args, &hdr);
if (quiet)
- argv_array_push(&child.args, "-q");
+ strvec_push(&child.args, "-q");
if (fsck_objects)
- argv_array_pushf(&child.args, "--strict%s",
- fsck_msg_types.buf);
+ strvec_pushf(&child.args, "--strict%s",
+ fsck_msg_types.buf);
if (max_input_size)
- argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX,
- (uintmax_t)max_input_size);
+ strvec_pushf(&child.args, "--max-input-size=%"PRIuMAX,
+ (uintmax_t)max_input_size);
child.no_stdout = 1;
child.err = err_fd;
child.git_cmd = 1;
@@ -1750,28 +2232,28 @@ static const char *unpack(int err_fd, struct shallow_info *si)
} else {
char hostname[HOST_NAME_MAX + 1];
- argv_array_pushl(&child.args, "index-pack", "--stdin", NULL);
+ strvec_pushl(&child.args, "index-pack", "--stdin", NULL);
push_header_arg(&child.args, &hdr);
if (xgethostname(hostname, sizeof(hostname)))
xsnprintf(hostname, sizeof(hostname), "localhost");
- argv_array_pushf(&child.args,
- "--keep=receive-pack %"PRIuMAX" on %s",
- (uintmax_t)getpid(),
- hostname);
+ strvec_pushf(&child.args,
+ "--keep=receive-pack %"PRIuMAX" on %s",
+ (uintmax_t)getpid(),
+ hostname);
if (!quiet && err_fd)
- argv_array_push(&child.args, "--show-resolving-progress");
+ strvec_push(&child.args, "--show-resolving-progress");
if (use_sideband)
- argv_array_push(&child.args, "--report-end-of-input");
+ strvec_push(&child.args, "--report-end-of-input");
if (fsck_objects)
- argv_array_pushf(&child.args, "--strict%s",
- fsck_msg_types.buf);
+ strvec_pushf(&child.args, "--strict%s",
+ fsck_msg_types.buf);
if (!reject_thin)
- argv_array_push(&child.args, "--fix-thin");
+ strvec_push(&child.args, "--fix-thin");
if (max_input_size)
- argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX,
- (uintmax_t)max_input_size);
+ strvec_pushf(&child.args, "--max-input-size=%"PRIuMAX,
+ (uintmax_t)max_input_size);
child.out = -1;
child.err = err_fd;
child.git_cmd = 1;
@@ -1809,8 +2291,7 @@ static const char *unpack_with_sideband(struct shallow_info *si)
return ret;
}
-static void prepare_shallow_update(struct command *commands,
- struct shallow_info *si)
+static void prepare_shallow_update(struct shallow_info *si)
{
int i, j, k, bitmap_size = DIV_ROUND_UP(si->ref->nr, 32);
@@ -1876,7 +2357,7 @@ static void update_shallow_info(struct command *commands,
si->ref = ref;
if (shallow_update) {
- prepare_shallow_update(commands, si);
+ prepare_shallow_update(si);
return;
}
@@ -1917,6 +2398,51 @@ static void report(struct command *commands, const char *unpack_status)
strbuf_release(&buf);
}
+static void report_v2(struct command *commands, const char *unpack_status)
+{
+ struct command *cmd;
+ struct strbuf buf = STRBUF_INIT;
+ struct ref_push_report *report;
+
+ packet_buf_write(&buf, "unpack %s\n",
+ unpack_status ? unpack_status : "ok");
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ int count = 0;
+
+ if (cmd->error_string) {
+ packet_buf_write(&buf, "ng %s %s\n",
+ cmd->ref_name,
+ cmd->error_string);
+ continue;
+ }
+ packet_buf_write(&buf, "ok %s\n",
+ cmd->ref_name);
+ for (report = cmd->report; report; report = report->next) {
+ if (count++ > 0)
+ packet_buf_write(&buf, "ok %s\n",
+ cmd->ref_name);
+ if (report->ref_name)
+ packet_buf_write(&buf, "option refname %s\n",
+ report->ref_name);
+ if (report->old_oid)
+ packet_buf_write(&buf, "option old-oid %s\n",
+ oid_to_hex(report->old_oid));
+ if (report->new_oid)
+ packet_buf_write(&buf, "option new-oid %s\n",
+ oid_to_hex(report->new_oid));
+ if (report->forced_update)
+ packet_buf_write(&buf, "option forced-update\n");
+ }
+ }
+ packet_buf_flush(&buf);
+
+ if (use_sideband)
+ send_sideband(1, 1, buf.buf, buf.len, use_sideband);
+ else
+ write_or_die(1, buf.buf, buf.len);
+ strbuf_release(&buf);
+}
+
static int delete_only(struct command *commands)
{
struct command *cmd;
@@ -2025,7 +2551,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
&push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
- if (report_status)
+ if (report_status_v2)
+ report_v2(commands, unpack_status);
+ else if (report_status)
report(commands, unpack_status);
run_receive_hook(commands, "post-receive", 1,
&push_options);
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 4d3430900d..ca1d8079f3 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -459,7 +459,7 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
static int reflog_expire_config(const char *var, const char *value, void *cb)
{
const char *pattern, *key;
- int pattern_len;
+ size_t pattern_len;
timestamp_t expire;
int slot;
struct reflog_expire_cfg *ent;
@@ -560,15 +560,16 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
flags |= EXPIRE_REFLOGS_DRY_RUN;
- else if (starts_with(arg, "--expire=")) {
- if (parse_expiry_date(arg + 9, &cb.cmd.expire_total))
+ else if (skip_prefix(arg, "--expire=", &arg)) {
+ if (parse_expiry_date(arg, &cb.cmd.expire_total))
die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_TOTAL;
}
- else if (starts_with(arg, "--expire-unreachable=")) {
- if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable))
+ else if (skip_prefix(arg, "--expire-unreachable=", &arg)) {
+ if (parse_expiry_date(arg, &cb.cmd.expire_unreachable))
die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_UNREACH;
}
@@ -614,7 +615,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
int i;
memset(&collected, 0, sizeof(collected));
- worktrees = get_worktrees(0);
+ worktrees = get_worktrees();
for (p = worktrees; *p; p++) {
if (!all_worktrees && !(*p)->is_current)
continue;
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
index 6a9127a33c..fd3538d4f0 100644
--- a/builtin/remote-ext.c
+++ b/builtin/remote-ext.c
@@ -117,12 +117,12 @@ static char *strip_escapes(const char *str, const char *service,
}
}
-static void parse_argv(struct argv_array *out, const char *arg, const char *service)
+static void parse_argv(struct strvec *out, const char *arg, const char *service)
{
while (*arg) {
char *expanded = strip_escapes(arg, service, &arg);
if (expanded)
- argv_array_push(out, expanded);
+ strvec_push(out, expanded);
free(expanded);
}
}
diff --git a/builtin/remote.c b/builtin/remote.c
index f7edf7f2cb..c1b211b272 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -6,10 +6,11 @@
#include "string-list.h"
#include "strbuf.h"
#include "run-command.h"
+#include "rebase.h"
#include "refs.h"
#include "refspec.h"
#include "object-store.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "commit-reach.h"
static const char * const builtin_remote_usage[] = {
@@ -169,9 +170,9 @@ static int add(int argc, const char **argv)
OPT_STRING_LIST('t', "track", &track, N_("branch"),
N_("branch(es) to track")),
OPT_STRING('m', "master", &master, N_("branch"), N_("master branch")),
- { OPTION_CALLBACK, 0, "mirror", &mirror, "(push|fetch)",
+ OPT_CALLBACK_F(0, "mirror", &mirror, "(push|fetch)",
N_("set up remote as a mirror to push to or fetch from"),
- PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt },
+ PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt),
OPT_END()
};
@@ -190,11 +191,12 @@ static int add(int argc, const char **argv)
url = argv[1];
remote = remote_get(name);
- if (remote_is_configured(remote, 1))
- die(_("remote %s already exists."), name);
+ if (remote_is_configured(remote, 1)) {
+ error(_("remote %s already exists."), name);
+ exit(3);
+ }
- strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
- if (!valid_fetch_refspec(buf2.buf))
+ if (!valid_remote_name(name))
die(_("'%s' is not a valid remote name"), name);
strbuf_addf(&buf, "remote.%s.url", name);
@@ -248,9 +250,8 @@ static int add(int argc, const char **argv)
struct branch_info {
char *remote_name;
struct string_list merge;
- enum {
- NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES
- } rebase;
+ enum rebase_type rebase;
+ char *push_remote_name;
};
static struct string_list branch_list = STRING_LIST_INIT_NODUP;
@@ -264,59 +265,69 @@ static const char *abbrev_ref(const char *name, const char *prefix)
static int config_read_branches(const char *key, const char *value, void *cb)
{
- if (starts_with(key, "branch.")) {
- const char *orig_key = key;
- char *name;
- struct string_list_item *item;
- struct branch_info *info;
- enum { REMOTE, MERGE, REBASE } type;
- size_t key_len;
-
- key += 7;
- if (strip_suffix(key, ".remote", &key_len)) {
- name = xmemdupz(key, key_len);
- type = REMOTE;
- } else if (strip_suffix(key, ".merge", &key_len)) {
- name = xmemdupz(key, key_len);
- type = MERGE;
- } else if (strip_suffix(key, ".rebase", &key_len)) {
- name = xmemdupz(key, key_len);
- type = REBASE;
- } else
- return 0;
+ const char *orig_key = key;
+ char *name;
+ struct string_list_item *item;
+ struct branch_info *info;
+ enum { REMOTE, MERGE, REBASE, PUSH_REMOTE } type;
+ size_t key_len;
- item = string_list_insert(&branch_list, name);
+ if (!starts_with(key, "branch."))
+ return 0;
- if (!item->util)
- item->util = xcalloc(1, sizeof(struct branch_info));
- info = item->util;
- if (type == REMOTE) {
- if (info->remote_name)
- warning(_("more than one %s"), orig_key);
- info->remote_name = xstrdup(value);
- } else if (type == MERGE) {
- char *space = strchr(value, ' ');
- value = abbrev_branch(value);
- while (space) {
- char *merge;
- merge = xstrndup(value, space - value);
- string_list_append(&info->merge, merge);
- value = abbrev_branch(space + 1);
- space = strchr(value, ' ');
- }
- string_list_append(&info->merge, xstrdup(value));
- } else {
- int v = git_parse_maybe_bool(value);
- if (v >= 0)
- info->rebase = v;
- else if (!strcmp(value, "preserve"))
- info->rebase = NORMAL_REBASE;
- else if (!strcmp(value, "merges"))
- info->rebase = REBASE_MERGES;
- else if (!strcmp(value, "interactive"))
- info->rebase = INTERACTIVE_REBASE;
+ key += strlen("branch.");
+ if (strip_suffix(key, ".remote", &key_len))
+ type = REMOTE;
+ else if (strip_suffix(key, ".merge", &key_len))
+ type = MERGE;
+ else if (strip_suffix(key, ".rebase", &key_len))
+ type = REBASE;
+ else if (strip_suffix(key, ".pushremote", &key_len))
+ type = PUSH_REMOTE;
+ else
+ return 0;
+ name = xmemdupz(key, key_len);
+
+ item = string_list_insert(&branch_list, name);
+
+ if (!item->util)
+ item->util = xcalloc(1, sizeof(struct branch_info));
+ info = item->util;
+ switch (type) {
+ case REMOTE:
+ if (info->remote_name)
+ warning(_("more than one %s"), orig_key);
+ info->remote_name = xstrdup(value);
+ break;
+ case MERGE: {
+ char *space = strchr(value, ' ');
+ value = abbrev_branch(value);
+ while (space) {
+ char *merge;
+ merge = xstrndup(value, space - value);
+ string_list_append(&info->merge, merge);
+ value = abbrev_branch(space + 1);
+ space = strchr(value, ' ');
}
+ string_list_append(&info->merge, xstrdup(value));
+ break;
}
+ case REBASE:
+ /*
+ * Consider invalid values as false and check the
+ * truth value with >= REBASE_TRUE.
+ */
+ info->rebase = rebase_parse_value(value);
+ break;
+ case PUSH_REMOTE:
+ if (info->push_remote_name)
+ warning(_("more than one %s"), orig_key);
+ info->push_remote_name = xstrdup(value);
+ break;
+ default:
+ BUG("unexpected type=%d", type);
+ }
+
return 0;
}
@@ -468,6 +479,7 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat
struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
struct refspec_item refspec;
+ memset(&refspec, 0, sizeof(refspec));
refspec.force = 0;
refspec.pattern = 1;
refspec.src = refspec.dst = "refs/heads/*";
@@ -605,6 +617,56 @@ static int migrate_file(struct remote *remote)
return 0;
}
+struct push_default_info
+{
+ const char *old_name;
+ enum config_scope scope;
+ struct strbuf origin;
+ int linenr;
+};
+
+static int config_read_push_default(const char *key, const char *value,
+ void *cb)
+{
+ struct push_default_info* info = cb;
+ if (strcmp(key, "remote.pushdefault") ||
+ !value || strcmp(value, info->old_name))
+ return 0;
+
+ info->scope = current_config_scope();
+ strbuf_reset(&info->origin);
+ strbuf_addstr(&info->origin, current_config_name());
+ info->linenr = current_config_line();
+
+ return 0;
+}
+
+static void handle_push_default(const char* old_name, const char* new_name)
+{
+ struct push_default_info push_default = {
+ old_name, CONFIG_SCOPE_UNKNOWN, STRBUF_INIT, -1 };
+ git_config(config_read_push_default, &push_default);
+ if (push_default.scope >= CONFIG_SCOPE_COMMAND)
+ ; /* pass */
+ else if (push_default.scope >= CONFIG_SCOPE_LOCAL) {
+ int result = git_config_set_gently("remote.pushDefault",
+ new_name);
+ if (new_name && result && result != CONFIG_NOTHING_SET)
+ die(_("could not set '%s'"), "remote.pushDefault");
+ else if (!new_name && result && result != CONFIG_NOTHING_SET)
+ die(_("could not unset '%s'"), "remote.pushDefault");
+ } else if (push_default.scope >= CONFIG_SCOPE_SYSTEM) {
+ /* warn */
+ warning(_("The %s configuration remote.pushDefault in:\n"
+ "\t%s:%d\n"
+ "now names the non-existent remote '%s'"),
+ config_scope_name(push_default.scope),
+ push_default.origin.buf, push_default.linenr,
+ old_name);
+ }
+}
+
+
static int mv(int argc, const char **argv)
{
struct option options[] = {
@@ -625,21 +687,23 @@ static int mv(int argc, const char **argv)
rename.remote_branches = &remote_branches;
oldremote = remote_get(rename.old_name);
- if (!remote_is_configured(oldremote, 1))
- die(_("No such remote: '%s'"), rename.old_name);
+ if (!remote_is_configured(oldremote, 1)) {
+ error(_("No such remote: '%s'"), rename.old_name);
+ exit(2);
+ }
if (!strcmp(rename.old_name, rename.new_name) && oldremote->origin != REMOTE_CONFIG)
return migrate_file(oldremote);
newremote = remote_get(rename.new_name);
- if (remote_is_configured(newremote, 1))
- die(_("remote %s already exists."), rename.new_name);
+ if (remote_is_configured(newremote, 1)) {
+ error(_("remote %s already exists."), rename.new_name);
+ exit(3);
+ }
- strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new_name);
- if (!valid_fetch_refspec(buf.buf))
+ if (!valid_remote_name(rename.new_name))
die(_("'%s' is not a valid remote name"), rename.new_name);
- strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s", rename.old_name);
strbuf_addf(&buf2, "remote.%s", rename.new_name);
if (git_config_rename_section(buf.buf, buf2.buf) < 1)
@@ -680,6 +744,11 @@ static int mv(int argc, const char **argv)
strbuf_addf(&buf, "branch.%s.remote", item->string);
git_config_set(buf.buf, rename.new_name);
}
+ if (info->push_remote_name && !strcmp(info->push_remote_name, rename.old_name)) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "branch.%s.pushremote", item->string);
+ git_config_set(buf.buf, rename.new_name);
+ }
}
if (!refspec_updated)
@@ -693,9 +762,8 @@ static int mv(int argc, const char **argv)
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
int flag = 0;
- struct object_id oid;
- read_ref_full(item->string, RESOLVE_REF_READING, &oid, &flag);
+ read_ref_full(item->string, RESOLVE_REF_READING, NULL, &flag);
if (!(flag & REF_ISSYMREF))
continue;
if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF))
@@ -736,6 +804,9 @@ static int mv(int argc, const char **argv)
die(_("creating '%s' failed"), buf.buf);
}
string_list_clear(&remote_branches, 1);
+
+ handle_push_default(rename.old_name, rename.new_name);
+
return 0;
}
@@ -761,8 +832,10 @@ static int rm(int argc, const char **argv)
usage_with_options(builtin_remote_rm_usage, options);
remote = remote_get(argv[1]);
- if (!remote_is_configured(remote, 1))
- die(_("No such remote: '%s'"), argv[1]);
+ if (!remote_is_configured(remote, 1)) {
+ error(_("No such remote: '%s'"), argv[1]);
+ exit(2);
+ }
known_remotes.to_delete = remote;
for_each_remote(add_known_remote, &known_remotes);
@@ -782,6 +855,13 @@ static int rm(int argc, const char **argv)
die(_("could not unset '%s'"), buf.buf);
}
}
+ if (info->push_remote_name && !strcmp(info->push_remote_name, remote->name)) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "branch.%s.pushremote", item->string);
+ result = git_config_set_gently(buf.buf, NULL);
+ if (result && result != CONFIG_NOTHING_SET)
+ die(_("could not unset '%s'"), buf.buf);
+ }
}
/*
@@ -814,6 +894,8 @@ static int rm(int argc, const char **argv)
strbuf_addf(&buf, "remote.%s", remote->name);
if (git_config_rename_section(buf.buf, NULL) < 1)
return error(_("Could not remove config section '%s'"), buf.buf);
+
+ handle_push_default(remote->name, NULL);
}
return result;
@@ -944,7 +1026,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb
return 0;
if ((n = strlen(branch_item->string)) > show_info->width)
show_info->width = n;
- if (branch_info->rebase)
+ if (branch_info->rebase >= REBASE_TRUE)
show_info->any_rebase = 1;
item = string_list_insert(show_info->list, branch_item->string);
@@ -961,16 +1043,16 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
int width = show_info->width + 4;
int i;
- if (branch_info->rebase && branch_info->merge.nr > 1) {
+ if (branch_info->rebase >= REBASE_TRUE && branch_info->merge.nr > 1) {
error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"),
item->string);
return 0;
}
printf(" %-*s ", show_info->width, item->string);
- if (branch_info->rebase) {
+ if (branch_info->rebase >= REBASE_TRUE) {
const char *msg;
- if (branch_info->rebase == INTERACTIVE_REBASE)
+ if (branch_info->rebase == REBASE_INTERACTIVE)
msg = _("rebases interactively onto remote %s");
else if (branch_info->rebase == REBASE_MERGES)
msg = _("rebases interactively (with merges) onto "
@@ -1279,7 +1361,7 @@ static int set_head(int argc, const char **argv)
result |= error(_("Not a valid ref: %s"), buf2.buf);
else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
result |= error(_("Could not setup %s"), buf.buf);
- if (opt_a)
+ else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
}
@@ -1375,39 +1457,39 @@ static int update(int argc, const char **argv)
N_("prune remotes after fetching")),
OPT_END()
};
- struct argv_array fetch_argv = ARGV_ARRAY_INIT;
+ struct strvec fetch_argv = STRVEC_INIT;
int default_defined = 0;
int retval;
argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
PARSE_OPT_KEEP_ARGV0);
- argv_array_push(&fetch_argv, "fetch");
+ strvec_push(&fetch_argv, "fetch");
if (prune != -1)
- argv_array_push(&fetch_argv, prune ? "--prune" : "--no-prune");
+ strvec_push(&fetch_argv, prune ? "--prune" : "--no-prune");
if (verbose)
- argv_array_push(&fetch_argv, "-v");
- argv_array_push(&fetch_argv, "--multiple");
+ strvec_push(&fetch_argv, "-v");
+ strvec_push(&fetch_argv, "--multiple");
if (argc < 2)
- argv_array_push(&fetch_argv, "default");
+ strvec_push(&fetch_argv, "default");
for (i = 1; i < argc; i++)
- argv_array_push(&fetch_argv, argv[i]);
+ strvec_push(&fetch_argv, argv[i]);
- if (strcmp(fetch_argv.argv[fetch_argv.argc-1], "default") == 0) {
+ if (strcmp(fetch_argv.v[fetch_argv.nr-1], "default") == 0) {
git_config(get_remote_default, &default_defined);
if (!default_defined) {
- argv_array_pop(&fetch_argv);
- argv_array_push(&fetch_argv, "--all");
+ strvec_pop(&fetch_argv);
+ strvec_push(&fetch_argv, "--all");
}
}
- retval = run_command_v_opt(fetch_argv.argv, RUN_GIT_CMD);
- argv_array_clear(&fetch_argv);
+ retval = run_command_v_opt(fetch_argv.v, RUN_GIT_CMD);
+ strvec_clear(&fetch_argv);
return retval;
}
-static int remove_all_fetch_refspecs(const char *remote, const char *key)
+static int remove_all_fetch_refspecs(const char *key)
{
return git_config_set_multivar_gently(key, NULL, NULL, 1);
}
@@ -1434,10 +1516,12 @@ static int set_remote_branches(const char *remotename, const char **branches,
strbuf_addf(&key, "remote.%s.fetch", remotename);
remote = remote_get(remotename);
- if (!remote_is_configured(remote, 1))
- die(_("No such remote '%s'"), remotename);
+ if (!remote_is_configured(remote, 1)) {
+ error(_("No such remote '%s'"), remotename);
+ exit(2);
+ }
- if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
+ if (!add_mode && remove_all_fetch_refspecs(key.buf)) {
strbuf_release(&key);
return 1;
}
@@ -1488,8 +1572,10 @@ static int get_url(int argc, const char **argv)
remotename = argv[0];
remote = remote_get(remotename);
- if (!remote_is_configured(remote, 1))
- die(_("No such remote '%s'"), remotename);
+ if (!remote_is_configured(remote, 1)) {
+ error(_("No such remote '%s'"), remotename);
+ exit(2);
+ }
url_nr = 0;
if (push_mode) {
@@ -1556,8 +1642,10 @@ static int set_url(int argc, const char **argv)
oldurl = newurl;
remote = remote_get(remotename);
- if (!remote_is_configured(remote, 1))
- die(_("No such remote '%s'"), remotename);
+ if (!remote_is_configured(remote, 1)) {
+ error(_("No such remote '%s'"), remotename);
+ exit(2);
+ }
if (push_mode) {
strbuf_addf(&name_buf, "remote.%s.pushurl", remotename);
diff --git a/builtin/repack.c b/builtin/repack.c
index f834b5551b..01e7767c79 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -7,10 +7,13 @@
#include "sigchain.h"
#include "strbuf.h"
#include "string-list.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "midx.h"
#include "packfile.h"
+#include "prune-packed.h"
#include "object-store.h"
+#include "promisor-remote.h"
+#include "shallow.h"
static int delta_base_offset = 1;
static int pack_kept_objects = -1;
@@ -129,19 +132,13 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list,
static void remove_redundant_pack(const char *dir_name, const char *base_name)
{
- const char *exts[] = {".pack", ".idx", ".keep", ".bitmap", ".promisor"};
- int i;
struct strbuf buf = STRBUF_INIT;
- size_t plen;
-
- strbuf_addf(&buf, "%s/%s", dir_name, base_name);
- plen = buf.len;
-
- for (i = 0; i < ARRAY_SIZE(exts); i++) {
- strbuf_setlen(&buf, plen);
- strbuf_addstr(&buf, exts[i]);
- unlink(buf.buf);
- }
+ struct multi_pack_index *m = get_local_multi_pack_index(the_repository);
+ strbuf_addf(&buf, "%s.pack", base_name);
+ if (m && midx_contains_pack(m, buf.buf))
+ clear_midx_file(the_repository);
+ strbuf_insertf(&buf, 0, "%s/", dir_name);
+ unlink_pack_path(buf.buf, 1);
strbuf_release(&buf);
}
@@ -160,28 +157,28 @@ struct pack_objects_args {
static void prepare_pack_objects(struct child_process *cmd,
const struct pack_objects_args *args)
{
- argv_array_push(&cmd->args, "pack-objects");
+ strvec_push(&cmd->args, "pack-objects");
if (args->window)
- argv_array_pushf(&cmd->args, "--window=%s", args->window);
+ strvec_pushf(&cmd->args, "--window=%s", args->window);
if (args->window_memory)
- argv_array_pushf(&cmd->args, "--window-memory=%s", args->window_memory);
+ strvec_pushf(&cmd->args, "--window-memory=%s", args->window_memory);
if (args->depth)
- argv_array_pushf(&cmd->args, "--depth=%s", args->depth);
+ strvec_pushf(&cmd->args, "--depth=%s", args->depth);
if (args->threads)
- argv_array_pushf(&cmd->args, "--threads=%s", args->threads);
+ strvec_pushf(&cmd->args, "--threads=%s", args->threads);
if (args->max_pack_size)
- argv_array_pushf(&cmd->args, "--max-pack-size=%s", args->max_pack_size);
+ strvec_pushf(&cmd->args, "--max-pack-size=%s", args->max_pack_size);
if (args->no_reuse_delta)
- argv_array_pushf(&cmd->args, "--no-reuse-delta");
+ strvec_pushf(&cmd->args, "--no-reuse-delta");
if (args->no_reuse_object)
- argv_array_pushf(&cmd->args, "--no-reuse-object");
+ strvec_pushf(&cmd->args, "--no-reuse-object");
if (args->local)
- argv_array_push(&cmd->args, "--local");
+ strvec_push(&cmd->args, "--local");
if (args->quiet)
- argv_array_push(&cmd->args, "--quiet");
+ strvec_push(&cmd->args, "--quiet");
if (delta_base_offset)
- argv_array_push(&cmd->args, "--delta-base-offset");
- argv_array_push(&cmd->args, packtmp);
+ strvec_push(&cmd->args, "--delta-base-offset");
+ strvec_push(&cmd->args, packtmp);
cmd->git_cmd = 1;
cmd->out = -1;
}
@@ -200,7 +197,7 @@ static int write_oid(const struct object_id *oid, struct packed_git *pack,
die(_("could not start pack-objects to repack promisor objects"));
}
- xwrite(cmd->in, oid_to_hex(oid), GIT_SHA1_HEXSZ);
+ xwrite(cmd->in, oid_to_hex(oid), the_hash_algo->hexsz);
xwrite(cmd->in, "\n", 1);
return 0;
}
@@ -242,6 +239,13 @@ static void repack_promisor_objects(const struct pack_objects_args *args,
/*
* pack-objects creates the .pack and .idx files, but not the
* .promisor file. Create the .promisor file, which is empty.
+ *
+ * NEEDSWORK: fetch-pack sometimes generates non-empty
+ * .promisor files containing the ref names and associated
+ * hashes at the point of generation of the corresponding
+ * packfile, but this would not preserve their contents. Maybe
+ * concatenate the contents of all .promisor files instead of
+ * just creating a new empty file.
*/
promisor_name = mkpathdup("%s-%s.promisor", packtmp,
line.buf);
@@ -286,7 +290,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
int keep_unreachable = 0;
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
int no_update_server_info = 0;
- int midx_cleared = 0;
struct pack_objects_args po_args = {NULL};
struct option builtin_repack_options[] = {
@@ -343,11 +346,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
(unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE)))
die(_("--keep-unreachable and -A are incompatible"));
- if (write_bitmaps < 0)
- write_bitmaps = (pack_everything & ALL_INTO_ONE) &&
- is_bare_repository();
+ if (write_bitmaps < 0) {
+ if (!(pack_everything & ALL_INTO_ONE) ||
+ !is_bare_repository())
+ write_bitmaps = 0;
+ }
if (pack_kept_objects < 0)
- pack_kept_objects = write_bitmaps;
+ pack_kept_objects = write_bitmaps > 0;
if (write_bitmaps && !(pack_everything & ALL_INTO_ONE))
die(_(incremental_bitmap_conflict_error));
@@ -359,22 +364,24 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
prepare_pack_objects(&cmd, &po_args);
- argv_array_push(&cmd.args, "--keep-true-parents");
+ strvec_push(&cmd.args, "--keep-true-parents");
if (!pack_kept_objects)
- argv_array_push(&cmd.args, "--honor-pack-keep");
+ strvec_push(&cmd.args, "--honor-pack-keep");
for (i = 0; i < keep_pack_list.nr; i++)
- argv_array_pushf(&cmd.args, "--keep-pack=%s",
- keep_pack_list.items[i].string);
- argv_array_push(&cmd.args, "--non-empty");
- argv_array_push(&cmd.args, "--all");
- argv_array_push(&cmd.args, "--reflog");
- argv_array_push(&cmd.args, "--indexed-objects");
- if (repository_format_partial_clone)
- argv_array_push(&cmd.args, "--exclude-promisor-objects");
- if (write_bitmaps)
- argv_array_push(&cmd.args, "--write-bitmap-index");
+ strvec_pushf(&cmd.args, "--keep-pack=%s",
+ keep_pack_list.items[i].string);
+ strvec_push(&cmd.args, "--non-empty");
+ strvec_push(&cmd.args, "--all");
+ strvec_push(&cmd.args, "--reflog");
+ strvec_push(&cmd.args, "--indexed-objects");
+ if (has_promisor_remote())
+ strvec_push(&cmd.args, "--exclude-promisor-objects");
+ if (write_bitmaps > 0)
+ strvec_push(&cmd.args, "--write-bitmap-index");
+ else if (write_bitmaps < 0)
+ strvec_push(&cmd.args, "--write-bitmap-index-quiet");
if (use_delta_islands)
- argv_array_push(&cmd.args, "--delta-islands");
+ strvec_push(&cmd.args, "--delta-islands");
if (pack_everything & ALL_INTO_ONE) {
get_non_kept_pack_filenames(&existing_packs, &keep_pack_list);
@@ -383,23 +390,23 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (existing_packs.nr && delete_redundant) {
if (unpack_unreachable) {
- argv_array_pushf(&cmd.args,
- "--unpack-unreachable=%s",
- unpack_unreachable);
- argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
+ strvec_pushf(&cmd.args,
+ "--unpack-unreachable=%s",
+ unpack_unreachable);
+ strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
} else if (pack_everything & LOOSEN_UNREACHABLE) {
- argv_array_push(&cmd.args,
- "--unpack-unreachable");
+ strvec_push(&cmd.args,
+ "--unpack-unreachable");
} else if (keep_unreachable) {
- argv_array_push(&cmd.args, "--keep-unreachable");
- argv_array_push(&cmd.args, "--pack-loose-unreachable");
+ strvec_push(&cmd.args, "--keep-unreachable");
+ strvec_push(&cmd.args, "--pack-loose-unreachable");
} else {
- argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
+ strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
}
}
} else {
- argv_array_push(&cmd.args, "--unpacked");
- argv_array_push(&cmd.args, "--incremental");
+ strvec_push(&cmd.args, "--unpacked");
+ strvec_push(&cmd.args, "--incremental");
}
cmd.no_stdin = 1;
@@ -435,11 +442,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
char *fname, *fname_old;
- if (!midx_cleared) {
- clear_midx_file(the_repository);
- midx_cleared = 1;
- }
-
fname = mkpathdup("%s/pack-%s%s", packdir,
item->string, exts[ext].name);
if (!file_exists(fname)) {
@@ -567,7 +569,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
remove_temporary_files();
if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0))
- write_midx_file(get_object_directory());
+ write_midx_file(get_object_directory(), 0);
string_list_clear(&names, 0);
string_list_clear(&rollback, 0);
diff --git a/builtin/replace.c b/builtin/replace.c
index 644b21ca8d..cd48765911 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -228,13 +228,13 @@ static int export_object(const struct object_id *oid, enum object_type type,
if (fd < 0)
return error_errno(_("unable to open %s for writing"), filename);
- argv_array_push(&cmd.args, "--no-replace-objects");
- argv_array_push(&cmd.args, "cat-file");
+ strvec_push(&cmd.args, "--no-replace-objects");
+ strvec_push(&cmd.args, "cat-file");
if (raw)
- argv_array_push(&cmd.args, type_name(type));
+ strvec_push(&cmd.args, type_name(type));
else
- argv_array_push(&cmd.args, "-p");
- argv_array_push(&cmd.args, oid_to_hex(oid));
+ strvec_push(&cmd.args, "-p");
+ strvec_push(&cmd.args, oid_to_hex(oid));
cmd.git_cmd = 1;
cmd.out = fd;
@@ -272,7 +272,7 @@ static int import_object(struct object_id *oid, enum object_type type,
return error(_("unable to spawn mktree"));
}
- if (strbuf_read(&result, cmd.out, 41) < 0) {
+ if (strbuf_read(&result, cmd.out, the_hash_algo->hexsz + 1) < 0) {
error_errno(_("unable to read from mktree"));
close(fd);
close(cmd.out);
@@ -358,14 +358,15 @@ static int replace_parents(struct strbuf *buf, int argc, const char **argv)
struct strbuf new_parents = STRBUF_INIT;
const char *parent_start, *parent_end;
int i;
+ const unsigned hexsz = the_hash_algo->hexsz;
/* find existing parents */
parent_start = buf->buf;
- parent_start += GIT_SHA1_HEXSZ + 6; /* "tree " + "hex sha1" + "\n" */
+ parent_start += hexsz + 6; /* "tree " + "hex sha1" + "\n" */
parent_end = parent_start;
while (starts_with(parent_end, "parent "))
- parent_end += 48; /* "parent " + "hex sha1" + "\n" */
+ parent_end += hexsz + 8; /* "parent " + "hex sha1" + "\n" */
/* prepare new parents */
for (i = 0; i < argc; i++) {
@@ -408,7 +409,8 @@ static int check_one_mergetag(struct commit *commit,
struct tag *tag;
int i;
- hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);
+ hash_object_file(the_hash_algo, extra->value, extra->len,
+ type_name(OBJ_TAG), &tag_oid);
tag = lookup_tag(the_repository, &tag_oid);
if (!tag)
return error(_("bad mergetag in commit '%s'"), ref);
@@ -421,7 +423,7 @@ static int check_one_mergetag(struct commit *commit,
if (get_oid(mergetag_data->argv[i], &oid) < 0)
return error(_("not a valid object name: '%s'"),
mergetag_data->argv[i]);
- if (oideq(&tag->tagged->oid, &oid))
+ if (oideq(get_tagged_oid(tag), &oid))
return 0; /* found */
}
@@ -500,7 +502,7 @@ static int convert_graft_file(int force)
const char *graft_file = get_graft_file(the_repository);
FILE *fp = fopen_or_warn(graft_file, "r");
struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
if (!fp)
return -1;
@@ -510,10 +512,10 @@ static int convert_graft_file(int force)
if (*buf.buf == '#')
continue;
- argv_array_split(&args, buf.buf);
- if (args.argc && create_graft(args.argc, args.argv, force, 1))
+ strvec_split(&args, buf.buf);
+ if (args.nr && create_graft(args.nr, args.v, force, 1))
strbuf_addf(&err, "\n\t%s", buf.buf);
- argv_array_clear(&args);
+ strvec_clear(&args);
}
fclose(fp);
diff --git a/builtin/reset.c b/builtin/reset.c
index 26ef9a7bd0..c635b062c3 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -30,8 +30,9 @@
static const char * const git_reset_usage[] = {
N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
- N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
- N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
+ N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."),
+ N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"),
+ N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"),
NULL
};
@@ -45,7 +46,7 @@ static inline int is_merge(void)
return !access(git_path_merge_head(the_repository), F_OK);
}
-static int reset_index(const struct object_id *oid, int reset_type, int quiet)
+static int reset_index(const char *ref, const struct object_id *oid, int reset_type, int quiet)
{
int i, nr = 0;
struct tree_desc desc[2];
@@ -59,6 +60,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
opts.dst_index = &the_index;
opts.fn = oneway_merge;
opts.merge = 1;
+ init_checkout_metadata(&opts.meta, ref, oid, NULL);
if (!quiet)
opts.verbose_update = 1;
switch (reset_type) {
@@ -79,13 +81,13 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet)
struct object_id head_oid;
if (get_oid("HEAD", &head_oid))
return error(_("You do not have a valid HEAD."));
- if (!fill_tree_descriptor(desc + nr, &head_oid))
+ if (!fill_tree_descriptor(the_repository, desc + nr, &head_oid))
return error(_("Failed to find tree of HEAD."));
nr++;
opts.fn = twoway_merge;
}
- if (!fill_tree_descriptor(desc + nr, oid)) {
+ if (!fill_tree_descriptor(the_repository, desc + nr, oid)) {
error(_("Failed to find tree of %s."), oid_to_hex(oid));
goto out;
}
@@ -284,8 +286,8 @@ static int git_reset_config(const char *var, const char *value, void *cb)
int cmd_reset(int argc, const char **argv, const char *prefix)
{
int reset_type = NONE, update_ref_status = 0, quiet = 0;
- int patch_mode = 0, unborn;
- const char *rev;
+ int patch_mode = 0, pathspec_file_nul = 0, unborn;
+ const char *rev, *pathspec_from_file = NULL;
struct object_id oid;
struct pathspec pathspec;
int intent_to_add = 0;
@@ -300,12 +302,14 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
N_("reset HEAD, index and working tree"), MERGE),
OPT_SET_INT(0, "keep", &reset_type,
N_("reset HEAD but keep local changes"), KEEP),
- { OPTION_CALLBACK, 0, "recurse-submodules", NULL,
+ OPT_CALLBACK_F(0, "recurse-submodules", NULL,
"reset", "control recursive updating of submodules",
- PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+ PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater),
OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
OPT_BOOL('N', "intent-to-add", &intent_to_add,
N_("record only the fact that removed paths will be added later")),
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END()
};
@@ -316,11 +320,25 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_DASHDASH);
parse_args(&pathspec, argv, prefix, patch_mode, &rev);
+ if (pathspec_from_file) {
+ if (patch_mode)
+ die(_("--pathspec-from-file is incompatible with --patch"));
+
+ if (pathspec.nr)
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+ parse_pathspec_file(&pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ prefix, pathspec_from_file, pathspec_file_nul);
+ } else if (pathspec_file_nul) {
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
+ }
+
unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
oidcpy(&oid, the_hash_algo->empty_tree);
- } else if (!pathspec.nr) {
+ } else if (!pathspec.nr && !patch_mode) {
struct commit *commit;
if (get_oid_committish(rev, &oid))
die(_("Failed to resolve '%s' as a valid revision."), rev);
@@ -401,11 +419,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
}
}
} else {
- int err = reset_index(&oid, reset_type, quiet);
+ struct object_id dummy;
+ char *ref = NULL;
+ int err;
+
+ dwim_ref(rev, strlen(rev), &dummy, &ref, 0);
+ if (ref && !starts_with(ref, "refs/"))
+ ref = NULL;
+
+ err = reset_index(ref, &oid, reset_type, quiet);
if (reset_type == KEEP && !err)
- err = reset_index(&oid, MIXED, quiet);
+ err = reset_index(ref, &oid, MIXED, quiet);
if (err)
die(_("Could not reset index file to revision '%s'."), rev);
+ free(ref);
}
if (write_locked_index(&the_index, &lock, COMMIT_LOCK))
@@ -421,7 +448,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
print_new_head_line(lookup_commit_reference(the_repository, &oid));
}
if (!pathspec.nr)
- remove_branch_state(the_repository);
+ remove_branch_state(the_repository, 0);
return update_ref_status;
}
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 9f31837d30..25c6c3b38d 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -18,7 +18,6 @@
#include "reflog-walk.h"
#include "oidset.h"
#include "packfile.h"
-#include "object-store.h"
static const char rev_list_usage[] =
"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -49,6 +48,7 @@ static const char rev_list_usage[] =
" --objects | --objects-edge\n"
" --unpacked\n"
" --header | --pretty\n"
+" --[no-]object-names\n"
" --abbrev=<n> | --no-abbrev\n"
" --abbrev-commit\n"
" --left-right\n"
@@ -75,9 +75,12 @@ enum missing_action {
};
static enum missing_action arg_missing_action;
+/* display only the oid of each object encountered */
+static int arg_show_object_names = 1;
+
#define DEFAULT_OIDSET_SIZE (16*1024)
-static void finish_commit(struct commit *commit, void *data);
+static void finish_commit(struct commit *commit);
static void show_commit(struct commit *commit, void *data)
{
struct rev_list_info *info = data;
@@ -86,7 +89,7 @@ static void show_commit(struct commit *commit, void *data)
display_progress(progress, ++progress_counter);
if (info->flags & REV_LIST_QUIET) {
- finish_commit(commit, data);
+ finish_commit(commit);
return;
}
@@ -99,7 +102,7 @@ static void show_commit(struct commit *commit, void *data)
revs->count_left++;
else
revs->count_right++;
- finish_commit(commit, data);
+ finish_commit(commit);
return;
}
@@ -188,10 +191,10 @@ static void show_commit(struct commit *commit, void *data)
putchar('\n');
}
maybe_flush_or_die(stdout, "stdout");
- finish_commit(commit, data);
+ finish_commit(commit);
}
-static void finish_commit(struct commit *commit, void *data)
+static void finish_commit(struct commit *commit)
{
if (commit->parents) {
free_commit_list(commit->parents);
@@ -250,12 +253,30 @@ static int finish_object(struct object *obj, const char *name, void *cb_data)
static void show_object(struct object *obj, const char *name, void *cb_data)
{
struct rev_list_info *info = cb_data;
+ struct rev_info *revs = info->revs;
+
if (finish_object(obj, name, cb_data))
return;
display_progress(progress, ++progress_counter);
if (info->flags & REV_LIST_QUIET)
return;
- show_object_with_name(stdout, obj, name);
+
+ if (revs->count) {
+ /*
+ * The object count is always accumulated in the .count_right
+ * field for traversal that is not a left-right traversal,
+ * and cmd_rev_list() made sure that a .count request that
+ * wants to count non-commit objects, which is handled by
+ * the show_object() callback, does not ask for .left_right.
+ */
+ revs->count_right++;
+ return;
+ }
+
+ if (arg_show_object_names)
+ show_object_with_name(stdout, obj, name);
+ else
+ printf("%s\n", oid_to_hex(&obj->oid));
}
static void show_edge(struct commit *commit)
@@ -358,6 +379,79 @@ static inline int parse_missing_action_value(const char *value)
return 0;
}
+static int try_bitmap_count(struct rev_info *revs,
+ struct list_objects_filter_options *filter)
+{
+ uint32_t commit_count = 0,
+ tag_count = 0,
+ tree_count = 0,
+ blob_count = 0;
+ int max_count;
+ struct bitmap_index *bitmap_git;
+
+ /* This function only handles counting, not general traversal. */
+ if (!revs->count)
+ return -1;
+
+ /*
+ * A bitmap result can't know left/right, etc, because we don't
+ * actually traverse.
+ */
+ if (revs->left_right || revs->cherry_mark)
+ return -1;
+
+ /*
+ * If we're counting reachable objects, we can't handle a max count of
+ * commits to traverse, since we don't know which objects go with which
+ * commit.
+ */
+ if (revs->max_count >= 0 &&
+ (revs->tag_objects || revs->tree_objects || revs->blob_objects))
+ return -1;
+
+ /*
+ * This must be saved before doing any walking, since the revision
+ * machinery will count it down to zero while traversing.
+ */
+ max_count = revs->max_count;
+
+ bitmap_git = prepare_bitmap_walk(revs, filter);
+ if (!bitmap_git)
+ return -1;
+
+ count_bitmap_commit_list(bitmap_git, &commit_count,
+ revs->tree_objects ? &tree_count : NULL,
+ revs->blob_objects ? &blob_count : NULL,
+ revs->tag_objects ? &tag_count : NULL);
+ if (max_count >= 0 && max_count < commit_count)
+ commit_count = max_count;
+
+ printf("%d\n", commit_count + tree_count + blob_count + tag_count);
+ free_bitmap_index(bitmap_git);
+ return 0;
+}
+
+static int try_bitmap_traversal(struct rev_info *revs,
+ struct list_objects_filter_options *filter)
+{
+ struct bitmap_index *bitmap_git;
+
+ /*
+ * We can't use a bitmap result with a traversal limit, since the set
+ * of commits we'd get would be essentially random.
+ */
+ if (revs->max_count >= 0)
+ return -1;
+
+ bitmap_git = prepare_bitmap_walk(revs, filter);
+ if (!bitmap_git)
+ return -1;
+
+ traverse_bitmap_commit_list(bitmap_git, revs, &show_object_fast);
+ free_bitmap_index(bitmap_git);
+ return 0;
+}
+
int cmd_rev_list(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -464,10 +558,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
parse_list_objects_filter(&filter_options, arg);
if (filter_options.choice && !revs.blob_objects)
die(_("object filtering requires --objects"));
- if (filter_options.choice == LOFC_SPARSE_OID &&
- !filter_options.sparse_oid_value)
- die(_("invalid sparse value '%s'"),
- filter_options.filter_spec);
continue;
}
if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) {
@@ -484,6 +574,16 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (skip_prefix(arg, "--missing=", &arg))
continue; /* already handled above */
+ if (!strcmp(arg, ("--no-object-names"))) {
+ arg_show_object_names = 0;
+ continue;
+ }
+
+ if (!strcmp(arg, ("--object-names"))) {
+ arg_show_object_names = 1;
+ continue;
+ }
+
usage(rev_list_usage);
}
@@ -509,8 +609,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (revs.show_notes)
die(_("rev-list does not support display of notes"));
- if (filter_options.choice && use_bitmap_index)
- die(_("cannot combine --use-bitmap-index with object filtering"));
+ if (revs.count &&
+ (revs.tag_objects || revs.tree_objects || revs.blob_objects) &&
+ (revs.left_right || revs.cherry_mark))
+ die(_("marked counting is incompatible with --objects"));
save_commit_buffer = (revs.verbose_header ||
revs.grep_filter.pattern_list ||
@@ -521,28 +623,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (show_progress)
progress = start_delayed_progress(show_progress, 0);
- if (use_bitmap_index && !revs.prune) {
- if (revs.count && !revs.left_right && !revs.cherry_mark) {
- uint32_t commit_count;
- int max_count = revs.max_count;
- struct bitmap_index *bitmap_git;
- if ((bitmap_git = prepare_bitmap_walk(&revs))) {
- count_bitmap_commit_list(bitmap_git, &commit_count, NULL, NULL, NULL);
- if (max_count >= 0 && max_count < commit_count)
- commit_count = max_count;
- printf("%d\n", commit_count);
- free_bitmap_index(bitmap_git);
- return 0;
- }
- } else if (revs.max_count < 0 &&
- revs.tag_objects && revs.tree_objects && revs.blob_objects) {
- struct bitmap_index *bitmap_git;
- if ((bitmap_git = prepare_bitmap_walk(&revs))) {
- traverse_bitmap_commit_list(bitmap_git, &show_object_fast);
- free_bitmap_index(bitmap_git);
- return 0;
- }
- }
+ if (use_bitmap_index) {
+ if (!try_bitmap_count(&revs, &filter_options))
+ return 0;
+ if (!try_bitmap_traversal(&revs, &filter_options))
+ return 0;
}
if (prepare_revision_walk(&revs))
@@ -552,8 +637,15 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (bisect_list) {
int reaches, all;
+ unsigned bisect_flags = 0;
+
+ if (bisect_find_all)
+ bisect_flags |= FIND_BISECTION_ALL;
+
+ if (revs.first_parent_only)
+ bisect_flags |= FIND_BISECTION_FIRST_PARENT_ONLY;
- find_bisection(&revs.commits, &reaches, &all, bisect_find_all);
+ find_bisection(&revs.commits, &reaches, &all, bisect_flags);
if (bisect_show_vars)
return show_bisect_vars(&info, reaches, all);
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index f8bbe6d47e..69ba7326cf 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -16,6 +16,7 @@
#include "split-index.h"
#include "submodule.h"
#include "commit-reach.h"
+#include "shallow.h"
#define DO_REVS 1
#define DO_NOREV 2
@@ -135,7 +136,7 @@ static void show_rev(int type, const struct object_id *oid, const char *name)
struct object_id discard;
char *full;
- switch (dwim_ref(name, strlen(name), &discard, &full)) {
+ switch (dwim_ref(name, strlen(name), &discard, &full, 0)) {
case 0:
/*
* Not found -- not a ref. We could
@@ -593,6 +594,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
const char *name = NULL;
struct object_context unused;
struct strbuf buf = STRBUF_INIT;
+ const int hexsz = the_hash_algo->hexsz;
+ int seen_end_of_options = 0;
if (argc > 1 && !strcmp("--parseopt", argv[1]))
return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -620,21 +623,29 @@ 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, "--local-env-vars")) {
- int i;
- for (i = 0; local_repo_env[i]; i++)
- printf("%s\n", local_repo_env[i]);
+ if (as_is) {
+ if (show_file(arg, output_prefix) && as_is < 2)
+ verify_filename(prefix, arg, 0);
continue;
}
- if (!strcmp(arg, "--resolve-git-dir")) {
- const char *gitdir = argv[++i];
- if (!gitdir)
- die("--resolve-git-dir requires an argument");
- gitdir = resolve_gitdir(gitdir);
- if (!gitdir)
- die("not a gitdir '%s'", argv[i]);
- puts(gitdir);
- continue;
+
+ if (!seen_end_of_options) {
+ if (!strcmp(arg, "--local-env-vars")) {
+ int i;
+ for (i = 0; local_repo_env[i]; i++)
+ printf("%s\n", local_repo_env[i]);
+ continue;
+ }
+ if (!strcmp(arg, "--resolve-git-dir")) {
+ const char *gitdir = argv[++i];
+ if (!gitdir)
+ die("--resolve-git-dir requires an argument");
+ gitdir = resolve_gitdir(gitdir);
+ if (!gitdir)
+ die("not a gitdir '%s'", argv[i]);
+ puts(gitdir);
+ continue;
+ }
}
/* The rest of the options require a git repository. */
@@ -644,41 +655,36 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
did_repo_setup = 1;
}
- if (!strcmp(arg, "--git-path")) {
- if (!argv[i + 1])
- die("--git-path requires an argument");
- strbuf_reset(&buf);
- puts(relative_path(git_path("%s", argv[i + 1]),
- prefix, &buf));
- i++;
- continue;
- }
- if (as_is) {
- if (show_file(arg, output_prefix) && as_is < 2)
- verify_filename(prefix, arg, 0);
- continue;
- }
- if (!strcmp(arg,"-n")) {
- if (++i >= argc)
- die("-n requires an argument");
- if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
- show(arg);
- show(argv[i]);
- }
- continue;
- }
- if (starts_with(arg, "-n")) {
- if ((filter & DO_FLAGS) && (filter & DO_REVS))
- show(arg);
+ if (!strcmp(arg, "--")) {
+ as_is = 2;
+ /* Pass on the "--" if we show anything but files.. */
+ if (filter & (DO_FLAGS | DO_REVS))
+ show_file(arg, 0);
continue;
}
- if (*arg == '-') {
- if (!strcmp(arg, "--")) {
- as_is = 2;
- /* Pass on the "--" if we show anything but files.. */
- if (filter & (DO_FLAGS | DO_REVS))
- show_file(arg, 0);
+ if (!seen_end_of_options && *arg == '-') {
+ if (!strcmp(arg, "--git-path")) {
+ if (!argv[i + 1])
+ die("--git-path requires an argument");
+ strbuf_reset(&buf);
+ puts(relative_path(git_path("%s", argv[i + 1]),
+ prefix, &buf));
+ i++;
+ continue;
+ }
+ if (!strcmp(arg,"-n")) {
+ if (++i >= argc)
+ die("-n requires an argument");
+ if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
+ show(arg);
+ show(argv[i]);
+ }
+ continue;
+ }
+ if (starts_with(arg, "-n")) {
+ if ((filter & DO_FLAGS) && (filter & DO_REVS))
+ show(arg);
continue;
}
if (!strcmp(arg, "--default")) {
@@ -730,8 +736,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
abbrev = strtoul(arg, NULL, 10);
if (abbrev < MINIMUM_ABBREV)
abbrev = MINIMUM_ABBREV;
- else if (40 <= abbrev)
- abbrev = 40;
+ else if (hexsz <= abbrev)
+ abbrev = hexsz;
continue;
}
if (!strcmp(arg, "--sq")) {
@@ -802,12 +808,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
const char *work_tree = get_git_work_tree();
if (work_tree)
puts(work_tree);
+ else
+ die("this operation must be run in a work tree");
continue;
}
if (!strcmp(arg, "--show-superproject-working-tree")) {
- const char *superproject = get_superproject_working_tree();
- if (superproject)
- puts(superproject);
+ struct strbuf superproject = STRBUF_INIT;
+ if (get_superproject_working_tree(&superproject))
+ puts(superproject.buf);
+ strbuf_release(&superproject);
continue;
}
if (!strcmp(arg, "--show-prefix")) {
@@ -854,7 +863,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!gitdir && !prefix)
gitdir = ".git";
if (gitdir) {
- puts(real_path(gitdir));
+ struct strbuf realpath = STRBUF_INIT;
+ strbuf_realpath(&realpath, gitdir, 1);
+ puts(realpath.buf);
+ strbuf_release(&realpath);
continue;
}
}
@@ -918,6 +930,23 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
show_datestring("--min-age=", arg);
continue;
}
+ if (opt_with_value(arg, "--show-object-format", &arg)) {
+ const char *val = arg ? arg : "storage";
+
+ if (strcmp(val, "storage") &&
+ strcmp(val, "input") &&
+ strcmp(val, "output"))
+ die("unknown mode for --show-object-format: %s",
+ arg);
+ puts(the_hash_algo->name);
+ continue;
+ }
+ if (!strcmp(arg, "--end-of-options")) {
+ seen_end_of_options = 1;
+ if (filter & (DO_FLAGS | DO_REVS))
+ show_file(arg, 0);
+ continue;
+ }
if (show_flag(arg) && verify)
die_no_single_rev(quiet);
continue;
diff --git a/builtin/revert.c b/builtin/revert.c
index d4dcedbdc6..314a86c562 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -102,11 +102,12 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'),
OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'),
OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'),
+ OPT_CMDMODE(0, "skip", &cmd, N_("skip current commit and continue"), 's'),
OPT_CLEANUP(&cleanup_arg),
OPT_BOOL('n', "no-commit", &opts->no_commit, N_("don't automatically commit")),
OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")),
OPT_NOOP_NOARG('r', NULL),
- OPT_BOOL('s', "signoff", &opts->signoff, N_("add Signed-off-by:")),
+ OPT_BOOL('s', "signoff", &opts->signoff, N_("add a Signed-off-by trailer")),
OPT_CALLBACK('m', "mainline", opts, N_("parent-number"),
N_("select mainline parent"), option_parse_m),
OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
@@ -151,6 +152,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
this_operation = "--quit";
else if (cmd == 'c')
this_operation = "--continue";
+ else if (cmd == 's')
+ this_operation = "--skip";
else {
assert(cmd == 'a');
this_operation = "--abort";
@@ -169,6 +172,11 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
NULL);
}
+ if (!opts->strategy && opts->default_strategy) {
+ opts->strategy = opts->default_strategy;
+ opts->default_strategy = NULL;
+ }
+
if (opts->allow_ff)
verify_opt_compatible(me, "--ff",
"--signoff", opts->signoff,
@@ -199,17 +207,21 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
/* These option values will be free()d */
opts->gpg_sign = xstrdup_or_null(opts->gpg_sign);
opts->strategy = xstrdup_or_null(opts->strategy);
+ if (!opts->strategy && getenv("GIT_TEST_MERGE_ALGORITHM"))
+ opts->strategy = xstrdup(getenv("GIT_TEST_MERGE_ALGORITHM"));
if (cmd == 'q') {
int ret = sequencer_remove_state(opts);
if (!ret)
- remove_branch_state(the_repository);
+ remove_branch_state(the_repository, 0);
return ret;
}
if (cmd == 'c')
return sequencer_continue(the_repository, opts);
if (cmd == 'a')
return sequencer_rollback(the_repository, opts);
+ if (cmd == 's')
+ return sequencer_skip(the_repository, opts);
return sequencer_pick_revisions(the_repository, opts);
}
diff --git a/builtin/rm.c b/builtin/rm.c
index bf4a443e13..4858631e0f 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -61,7 +61,7 @@ static void print_error_files(struct string_list *files_list,
}
}
-static void submodules_absorb_gitdir_if_needed(const char *prefix)
+static void submodules_absorb_gitdir_if_needed(void)
{
int i;
for (i = 0; i < list.nr; i++) {
@@ -83,7 +83,7 @@ static void submodules_absorb_gitdir_if_needed(const char *prefix)
continue;
if (!submodule_uses_gitfile(name))
- absorb_git_dir_into_superproject(prefix, name,
+ absorb_git_dir_into_superproject(name,
ABSORB_GITDIR_RECURSE_SUBMODULES);
}
}
@@ -179,7 +179,7 @@ static int check_local_mod(struct object_id *head, int index_only)
* way as changed from the HEAD.
*/
if (no_head
- || get_tree_entry(head, name, &oid, &mode)
+ || get_tree_entry(the_repository, head, name, &oid, &mode)
|| ce->ce_mode != create_ce_mode(mode)
|| !oideq(&ce->oid, &oid))
staged_changes = 1;
@@ -235,7 +235,8 @@ static int check_local_mod(struct object_id *head, int index_only)
}
static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
-static int ignore_unmatch = 0;
+static int ignore_unmatch = 0, pathspec_file_nul;
+static char *pathspec_from_file;
static struct option builtin_rm_options[] = {
OPT__DRY_RUN(&show_only, N_("dry run")),
@@ -245,6 +246,8 @@ static struct option builtin_rm_options[] = {
OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")),
OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
N_("exit with a zero status even if nothing matched")),
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END(),
};
@@ -259,8 +262,24 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, builtin_rm_options,
builtin_rm_usage, 0);
- if (!argc)
- usage_with_options(builtin_rm_usage, builtin_rm_options);
+
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD,
+ prefix, argv);
+
+ if (pathspec_from_file) {
+ if (pathspec.nr)
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+ parse_pathspec_file(&pathspec, 0,
+ PATHSPEC_PREFER_CWD,
+ prefix, pathspec_from_file, pathspec_file_nul);
+ } else if (pathspec_file_nul) {
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
+ }
+
+ if (!pathspec.nr)
+ die(_("No pathspec was given. Which files should I remove?"));
if (!index_only)
setup_work_tree();
@@ -270,9 +289,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die(_("index file corrupt"));
- parse_pathspec(&pathspec, 0,
- PATHSPEC_PREFER_CWD,
- prefix, argv);
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL);
seen = xcalloc(pathspec.nr, 1);
@@ -313,7 +329,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
}
if (!index_only)
- submodules_absorb_gitdir_if_needed(prefix);
+ submodules_absorb_gitdir_if_needed();
/*
* If not forced, the file, the index and the HEAD (if exists)
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 098ebf22d0..a7e01667b0 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -11,7 +11,7 @@
#include "quote.h"
#include "transport.h"
#include "version.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "gpg-interface.h"
#include "gettext.h"
#include "protocol.h"
@@ -29,10 +29,12 @@ static struct send_pack_args args;
static void print_helper_status(struct ref *ref)
{
struct strbuf buf = STRBUF_INIT;
+ struct ref_push_report *report;
for (; ref; ref = ref->next) {
const char *msg = NULL;
const char *res;
+ int count = 0;
switch(ref->status) {
case REF_STATUS_NONE:
@@ -69,6 +71,11 @@ static void print_helper_status(struct ref *ref)
msg = "stale info";
break;
+ case REF_STATUS_REJECT_REMOTE_UPDATED:
+ res = "error";
+ msg = "remote ref updated since checkout";
+ break;
+
case REF_STATUS_REJECT_ALREADY_EXISTS:
res = "error";
msg = "already exists";
@@ -94,6 +101,23 @@ static void print_helper_status(struct ref *ref)
}
strbuf_addch(&buf, '\n');
+ if (ref->status == REF_STATUS_OK) {
+ for (report = ref->report; report; report = report->next) {
+ if (count++ > 0)
+ strbuf_addf(&buf, "ok %s\n", ref->name);
+ if (report->ref_name)
+ strbuf_addf(&buf, "option refname %s\n",
+ report->ref_name);
+ if (report->old_oid)
+ strbuf_addf(&buf, "option old-oid %s\n",
+ oid_to_hex(report->old_oid));
+ if (report->new_oid)
+ strbuf_addf(&buf, "option new-oid %s\n",
+ oid_to_hex(report->new_oid));
+ if (report->forced_update)
+ strbuf_addstr(&buf, "option forced-update\n");
+ }
+ }
write_or_die(1, buf.buf, buf.len);
}
strbuf_release(&buf);
@@ -154,6 +178,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
int progress = -1;
int from_stdin = 0;
struct push_cas_option cas = {0};
+ int force_if_includes = 0;
struct packet_reader reader;
struct option options[] = {
@@ -165,9 +190,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
OPT_BOOL('n' , "dry-run", &dry_run, N_("dry run")),
OPT_BOOL(0, "mirror", &send_mirror, N_("mirror all refs")),
OPT_BOOL('f', "force", &force_update, N_("force updates")),
- { OPTION_CALLBACK,
- 0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
- PARSE_OPT_OPTARG, option_parse_push_signed },
+ OPT_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"),
+ PARSE_OPT_OPTARG, option_parse_push_signed),
OPT_STRING_LIST(0, "push-option", &push_options,
N_("server-specific"),
N_("option to transmit")),
@@ -177,10 +201,11 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("use stateless RPC protocol")),
OPT_BOOL(0, "stdin", &from_stdin, N_("read refs from stdin")),
OPT_BOOL(0, "helper-status", &helper_status, N_("print status from remote helper")),
- { OPTION_CALLBACK,
- 0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
+ OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
N_("require old value of ref to be at this value"),
- PARSE_OPT_OPTARG, parseopt_push_cas_option },
+ PARSE_OPT_OPTARG, parseopt_push_cas_option),
+ OPT_BOOL(0, TRANS_OPT_FORCE_IF_INCLUDES, &force_if_includes,
+ N_("require remote updates to be integrated locally")),
OPT_END()
};
@@ -282,6 +307,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
if (!is_empty_cas(&cas))
apply_push_cas(&cas, remote, remote_refs);
+ if (!is_empty_cas(&cas) && force_if_includes)
+ cas.use_force_if_includes = 1;
+
set_ref_status_for_push(remote_refs, args.send_mirror,
args.force_update);
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 65cd41392c..c52e4ccd19 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -9,6 +9,8 @@
#include "mailmap.h"
#include "shortlog.h"
#include "parse-options.h"
+#include "trailer.h"
+#include "strmap.h"
static char const * const shortlog_usage[] = {
N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"),
@@ -49,12 +51,12 @@ static int compare_by_list(const void *a1, const void *a2)
}
static void insert_one_record(struct shortlog *log,
- const char *author,
+ const char *ident,
const char *oneline)
{
struct string_list_item *item;
- item = string_list_insert(&log->list, author);
+ item = string_list_insert(&log->list, ident);
if (log->summary)
item->util = (void *)(UTIL_TO_INT(item) + 1);
@@ -97,8 +99,8 @@ static void insert_one_record(struct shortlog *log,
}
}
-static int parse_stdin_author(struct shortlog *log,
- struct strbuf *out, const char *in)
+static int parse_ident(struct shortlog *log,
+ struct strbuf *out, const char *in)
{
const char *mailbuf, *namebuf;
size_t namelen, maillen;
@@ -122,18 +124,33 @@ static int parse_stdin_author(struct shortlog *log,
static void read_from_stdin(struct shortlog *log)
{
- struct strbuf author = STRBUF_INIT;
- struct strbuf mapped_author = STRBUF_INIT;
+ struct strbuf ident = STRBUF_INIT;
+ struct strbuf mapped_ident = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
static const char *author_match[2] = { "Author: ", "author " };
static const char *committer_match[2] = { "Commit: ", "committer " };
const char **match;
- match = log->committer ? committer_match : author_match;
- while (strbuf_getline_lf(&author, stdin) != EOF) {
+ if (HAS_MULTI_BITS(log->groups))
+ die(_("using multiple --group options with stdin is not supported"));
+
+ switch (log->groups) {
+ case SHORTLOG_GROUP_AUTHOR:
+ match = author_match;
+ break;
+ case SHORTLOG_GROUP_COMMITTER:
+ match = committer_match;
+ break;
+ case SHORTLOG_GROUP_TRAILER:
+ die(_("using --group=trailer with stdin is not supported"));
+ default:
+ BUG("unhandled shortlog group");
+ }
+
+ while (strbuf_getline_lf(&ident, stdin) != EOF) {
const char *v;
- if (!skip_prefix(author.buf, match[0], &v) &&
- !skip_prefix(author.buf, match[1], &v))
+ if (!skip_prefix(ident.buf, match[0], &v) &&
+ !skip_prefix(ident.buf, match[1], &v))
continue;
while (strbuf_getline_lf(&oneline, stdin) != EOF &&
oneline.len)
@@ -142,23 +159,64 @@ static void read_from_stdin(struct shortlog *log)
!oneline.len)
; /* discard blanks */
- strbuf_reset(&mapped_author);
- if (parse_stdin_author(log, &mapped_author, v) < 0)
+ strbuf_reset(&mapped_ident);
+ if (parse_ident(log, &mapped_ident, v) < 0)
continue;
- insert_one_record(log, mapped_author.buf, oneline.buf);
+ insert_one_record(log, mapped_ident.buf, oneline.buf);
}
- strbuf_release(&author);
- strbuf_release(&mapped_author);
+ strbuf_release(&ident);
+ strbuf_release(&mapped_ident);
strbuf_release(&oneline);
}
+static void insert_records_from_trailers(struct shortlog *log,
+ struct strset *dups,
+ struct commit *commit,
+ struct pretty_print_context *ctx,
+ const char *oneline)
+{
+ struct trailer_iterator iter;
+ const char *commit_buffer, *body;
+ struct strbuf ident = STRBUF_INIT;
+
+ /*
+ * Using format_commit_message("%B") would be simpler here, but
+ * this saves us copying the message.
+ */
+ commit_buffer = logmsg_reencode(commit, NULL, ctx->output_encoding);
+ body = strstr(commit_buffer, "\n\n");
+ if (!body)
+ return;
+
+ trailer_iterator_init(&iter, body);
+ while (trailer_iterator_advance(&iter)) {
+ const char *value = iter.val.buf;
+
+ if (!string_list_has_string(&log->trailers, iter.key.buf))
+ continue;
+
+ strbuf_reset(&ident);
+ if (!parse_ident(log, &ident, value))
+ value = ident.buf;
+
+ if (!strset_add(dups, value))
+ continue;
+ insert_one_record(log, value, oneline);
+ }
+ trailer_iterator_release(&iter);
+
+ strbuf_release(&ident);
+ unuse_commit_buffer(commit, commit_buffer);
+}
+
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
- struct strbuf author = STRBUF_INIT;
+ struct strbuf ident = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
+ struct strset dups = STRSET_INIT;
struct pretty_print_context ctx = {0};
- const char *fmt;
+ const char *oneline_str;
ctx.fmt = CMIT_FMT_USERFORMAT;
ctx.abbrev = log->abbrev;
@@ -166,21 +224,38 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
ctx.date_mode.type = DATE_NORMAL;
ctx.output_encoding = get_log_output_encoding();
- fmt = log->committer ?
- (log->email ? "%cN <%cE>" : "%cN") :
- (log->email ? "%aN <%aE>" : "%aN");
-
- format_commit_message(commit, fmt, &author, &ctx);
if (!log->summary) {
if (log->user_format)
pretty_print_commit(&ctx, commit, &oneline);
else
format_commit_message(commit, "%s", &oneline, &ctx);
}
+ oneline_str = oneline.len ? oneline.buf : "<none>";
+
+ if (log->groups & SHORTLOG_GROUP_AUTHOR) {
+ strbuf_reset(&ident);
+ format_commit_message(commit,
+ log->email ? "%aN <%aE>" : "%aN",
+ &ident, &ctx);
+ if (!HAS_MULTI_BITS(log->groups) ||
+ strset_add(&dups, ident.buf))
+ insert_one_record(log, ident.buf, oneline_str);
+ }
+ if (log->groups & SHORTLOG_GROUP_COMMITTER) {
+ strbuf_reset(&ident);
+ format_commit_message(commit,
+ log->email ? "%cN <%cE>" : "%cN",
+ &ident, &ctx);
+ if (!HAS_MULTI_BITS(log->groups) ||
+ strset_add(&dups, ident.buf))
+ insert_one_record(log, ident.buf, oneline_str);
+ }
+ if (log->groups & SHORTLOG_GROUP_TRAILER) {
+ insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str);
+ }
- insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>");
-
- strbuf_release(&author);
+ strset_clear(&dups);
+ strbuf_release(&ident);
strbuf_release(&oneline);
}
@@ -241,6 +316,28 @@ static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int parse_group_option(const struct option *opt, const char *arg, int unset)
+{
+ struct shortlog *log = opt->value;
+ const char *field;
+
+ if (unset) {
+ log->groups = 0;
+ string_list_clear(&log->trailers, 0);
+ } else if (!strcasecmp(arg, "author"))
+ log->groups |= SHORTLOG_GROUP_AUTHOR;
+ else if (!strcasecmp(arg, "committer"))
+ log->groups |= SHORTLOG_GROUP_COMMITTER;
+ else if (skip_prefix(arg, "trailer:", &field)) {
+ log->groups |= SHORTLOG_GROUP_TRAILER;
+ string_list_append(&log->trailers, field);
+ } else
+ return error(_("unknown group type: %s"), arg);
+
+ return 0;
+}
+
+
void shortlog_init(struct shortlog *log)
{
memset(log, 0, sizeof(*log));
@@ -251,6 +348,8 @@ void shortlog_init(struct shortlog *log)
log->wrap = DEFAULT_WRAPLEN;
log->in1 = DEFAULT_INDENT1;
log->in2 = DEFAULT_INDENT2;
+ log->trailers.strdup_strings = 1;
+ log->trailers.cmp = strcasecmp;
}
int cmd_shortlog(int argc, const char **argv, const char *prefix)
@@ -260,17 +359,20 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
int nongit = !startup_info->have_repository;
const struct option options[] = {
- OPT_BOOL('c', "committer", &log.committer,
- N_("Group by committer rather than author")),
+ OPT_BIT('c', "committer", &log.groups,
+ N_("Group by committer rather than author"),
+ SHORTLOG_GROUP_COMMITTER),
OPT_BOOL('n', "numbered", &log.sort_by_number,
N_("sort output according to the number of commits per author")),
OPT_BOOL('s', "summary", &log.summary,
N_("Suppress commit descriptions, only provides commit count")),
OPT_BOOL('e', "email", &log.email,
N_("Show the email address of each author")),
- { OPTION_CALLBACK, 'w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"),
+ OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"),
N_("Linewrap output"), PARSE_OPT_OPTARG,
- &parse_wrap_args },
+ &parse_wrap_args),
+ OPT_CALLBACK(0, "group", &log, N_("field"),
+ N_("Group by field"), parse_group_option),
OPT_END(),
};
@@ -311,6 +413,10 @@ parse_done:
log.abbrev = rev.abbrev;
log.file = rev.diffopt.file;
+ if (!log.groups)
+ log.groups = SHORTLOG_GROUP_AUTHOR;
+ string_list_sort(&log.trailers);
+
/* assume HEAD if from a tty */
if (!nongit && !rev.pending.nr && isatty(0))
add_head_to_pending(&rev);
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 082daeac32..d6d2dabeca 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -4,7 +4,7 @@
#include "refs.h"
#include "builtin.h"
#include "color.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "parse-options.h"
#include "dir.h"
#include "commit-slab.h"
@@ -20,7 +20,7 @@ static const char* show_branch_usage[] = {
static int showbranch_use_color = -1;
-static struct argv_array default_args = ARGV_ARRAY_INIT;
+static struct strvec default_args = STRVEC_INIT;
/*
* TODO: convert this use of commit->object.flags to commit-slab
@@ -514,7 +514,6 @@ static int show_merge_base(struct commit_list *seen, int num_rev)
static int show_independent(struct commit **rev,
int num_rev,
- char **ref_name,
unsigned int *rev_mask)
{
int i;
@@ -537,7 +536,7 @@ static void append_one_rev(const char *av)
append_ref(av, &revkey, 0);
return;
}
- if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
+ if (strpbrk(av, "*?[")) {
/* glob style match */
int saved_matches = ref_name_cnt;
@@ -562,9 +561,9 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
* default_arg is now passed to parse_options(), so we need to
* mimic the real argv a bit better.
*/
- if (!default_args.argc)
- argv_array_push(&default_args, "show-branch");
- argv_array_push(&default_args, value);
+ if (!default_args.nr)
+ strvec_push(&default_args, "show-branch");
+ strvec_push(&default_args, value);
return 0;
}
@@ -672,11 +671,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
N_("topologically sort, maintaining date order "
"where possible"),
REV_SORT_BY_COMMIT_DATE),
- { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
+ OPT_CALLBACK_F('g', "reflog", &reflog_base, N_("<n>[,<base>]"),
N_("show <n> most recent ref-log entries starting at "
"base"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
- parse_reflog_param },
+ parse_reflog_param),
OPT_END()
};
@@ -685,9 +684,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
git_config(git_show_branch_config, NULL);
/* If nothing is specified, try the default first */
- if (ac == 1 && default_args.argc) {
- ac = default_args.argc;
- av = default_args.argv;
+ if (ac == 1 && default_args.nr) {
+ ac = default_args.nr;
+ av = default_args.v;
}
ac = parse_options(ac, av, prefix, builtin_show_branch_options,
@@ -742,7 +741,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
die(Q_("only %d entry can be shown at one time.",
"only %d entries can be shown at one time.",
MAX_REVS), MAX_REVS);
- if (!dwim_ref(*av, strlen(*av), &oid, &ref))
+ if (!dwim_ref(*av, strlen(*av), &oid, &ref, 0))
die(_("no such ref %s"), *av);
/* Has the base been specified? */
@@ -862,7 +861,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
return show_merge_base(seen, num_rev);
if (independent)
- return show_independent(rev, num_rev, ref_name, rev_mask);
+ return show_independent(rev, num_rev, rev_mask);
/* Show list; --more=-1 means list-only */
if (1 < num_rev || extra < 0) {
diff --git a/builtin/show-index.c b/builtin/show-index.c
index a6e678809e..8106b03a6b 100644
--- a/builtin/show-index.c
+++ b/builtin/show-index.c
@@ -1,9 +1,12 @@
#include "builtin.h"
#include "cache.h"
#include "pack.h"
+#include "parse-options.h"
-static const char show_index_usage[] =
-"git show-index";
+static const char *const show_index_usage[] = {
+ "git show-index [--object-format=<hash-algorithm>]",
+ NULL
+};
int cmd_show_index(int argc, const char **argv, const char *prefix)
{
@@ -11,9 +14,26 @@ int cmd_show_index(int argc, const char **argv, const char *prefix)
unsigned nr;
unsigned int version;
static unsigned int top_index[256];
+ unsigned hashsz;
+ const char *hash_name = NULL;
+ int hash_algo;
+ const struct option show_index_options[] = {
+ OPT_STRING(0, "object-format", &hash_name, N_("hash-algorithm"),
+ N_("specify the hash algorithm to use")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, show_index_options, show_index_usage, 0);
+
+ if (hash_name) {
+ hash_algo = hash_algo_by_name(hash_name);
+ if (hash_algo == GIT_HASH_UNKNOWN)
+ die(_("Unknown hash algorithm"));
+ repo_set_hash_algo(the_repository, hash_algo);
+ }
+
+ hashsz = the_hash_algo->rawsz;
- if (argc != 1)
- usage(show_index_usage);
if (fread(top_index, 2 * 4, 1, stdin) != 1)
die("unable to read header");
if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) {
@@ -36,23 +56,23 @@ int cmd_show_index(int argc, const char **argv, const char *prefix)
}
if (version == 1) {
for (i = 0; i < nr; i++) {
- unsigned int offset, entry[6];
+ unsigned int offset, entry[(GIT_MAX_RAWSZ + 4) / sizeof(unsigned int)];
- if (fread(entry, 4 + 20, 1, stdin) != 1)
+ if (fread(entry, 4 + hashsz, 1, stdin) != 1)
die("unable to read entry %u/%u", i, nr);
offset = ntohl(entry[0]);
- printf("%u %s\n", offset, sha1_to_hex((void *)(entry+1)));
+ printf("%u %s\n", offset, hash_to_hex((void *)(entry+1)));
}
} else {
unsigned off64_nr = 0;
struct {
- unsigned char sha1[20];
+ struct object_id oid;
uint32_t crc;
uint32_t off;
} *entries;
ALLOC_ARRAY(entries, nr);
for (i = 0; i < nr; i++)
- if (fread(entries[i].sha1, 20, 1, stdin) != 1)
+ if (fread(entries[i].oid.hash, hashsz, 1, stdin) != 1)
die("unable to read sha1 %u/%u", i, nr);
for (i = 0; i < nr; i++)
if (fread(&entries[i].crc, 4, 1, stdin) != 1)
@@ -77,7 +97,7 @@ int cmd_show_index(int argc, const char **argv, const char *prefix)
}
printf("%" PRIuMAX " %s (%08"PRIx32")\n",
(uintmax_t) offset,
- sha1_to_hex(entries[i].sha1),
+ oid_to_hex(&entries[i].oid),
ntohl(entries[i].crc));
}
free(entries);
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 6456da70cc..ae60b4acf2 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -169,15 +169,15 @@ static const struct option show_ref_options[] = {
N_("show the HEAD reference, even if it would be filtered out")),
OPT_BOOL('d', "dereference", &deref_tags,
N_("dereference tags into object IDs")),
- { OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"),
- N_("only show SHA1 hash using <n> digits"),
- PARSE_OPT_OPTARG, &hash_callback },
+ OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+ N_("only show SHA1 hash using <n> digits"),
+ PARSE_OPT_OPTARG, &hash_callback),
OPT__ABBREV(&abbrev),
OPT__QUIET(&quiet,
N_("do not print results to stdout (useful with --verify)")),
- { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
- N_("pattern"), N_("show refs from stdin that aren't in local repository"),
- PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
+ OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg,
+ N_("pattern"), N_("show refs from stdin that aren't in local repository"),
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
OPT_END()
};
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
new file mode 100644
index 0000000000..e3140db2a0
--- /dev/null
+++ b/builtin/sparse-checkout.c
@@ -0,0 +1,663 @@
+#include "builtin.h"
+#include "config.h"
+#include "dir.h"
+#include "parse-options.h"
+#include "pathspec.h"
+#include "repository.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "string-list.h"
+#include "cache.h"
+#include "cache-tree.h"
+#include "lockfile.h"
+#include "resolve-undo.h"
+#include "unpack-trees.h"
+#include "wt-status.h"
+#include "quote.h"
+
+static const char *empty_base = "";
+
+static char const * const builtin_sparse_checkout_usage[] = {
+ N_("git sparse-checkout (init|list|set|add|reapply|disable) <options>"),
+ NULL
+};
+
+static char *get_sparse_checkout_filename(void)
+{
+ return git_pathdup("info/sparse-checkout");
+}
+
+static void write_patterns_to_file(FILE *fp, struct pattern_list *pl)
+{
+ int i;
+
+ for (i = 0; i < pl->nr; i++) {
+ struct path_pattern *p = pl->patterns[i];
+
+ if (p->flags & PATTERN_FLAG_NEGATIVE)
+ fprintf(fp, "!");
+
+ fprintf(fp, "%s", p->pattern);
+
+ if (p->flags & PATTERN_FLAG_MUSTBEDIR)
+ fprintf(fp, "/");
+
+ fprintf(fp, "\n");
+ }
+}
+
+static char const * const builtin_sparse_checkout_list_usage[] = {
+ N_("git sparse-checkout list"),
+ NULL
+};
+
+static int sparse_checkout_list(int argc, const char **argv)
+{
+ static struct option builtin_sparse_checkout_list_options[] = {
+ OPT_END(),
+ };
+ struct pattern_list pl;
+ char *sparse_filename;
+ int res;
+
+ argc = parse_options(argc, argv, NULL,
+ builtin_sparse_checkout_list_options,
+ builtin_sparse_checkout_list_usage, 0);
+
+ memset(&pl, 0, sizeof(pl));
+
+ pl.use_cone_patterns = core_sparse_checkout_cone;
+
+ sparse_filename = get_sparse_checkout_filename();
+ res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
+ free(sparse_filename);
+
+ if (res < 0) {
+ warning(_("this worktree is not sparse (sparse-checkout file may not exist)"));
+ return 0;
+ }
+
+ if (pl.use_cone_patterns) {
+ int i;
+ struct pattern_entry *pe;
+ struct hashmap_iter iter;
+ struct string_list sl = STRING_LIST_INIT_DUP;
+
+ hashmap_for_each_entry(&pl.recursive_hashmap, &iter, pe, ent) {
+ /* pe->pattern starts with "/", skip it */
+ string_list_insert(&sl, pe->pattern + 1);
+ }
+
+ string_list_sort(&sl);
+
+ for (i = 0; i < sl.nr; i++) {
+ quote_c_style(sl.items[i].string, NULL, stdout, 0);
+ printf("\n");
+ }
+
+ return 0;
+ }
+
+ write_patterns_to_file(stdout, &pl);
+ clear_pattern_list(&pl);
+
+ return 0;
+}
+
+static int update_working_directory(struct pattern_list *pl)
+{
+ enum update_sparsity_result result;
+ struct unpack_trees_options o;
+ struct lock_file lock_file = LOCK_INIT;
+ struct repository *r = the_repository;
+
+ /* If no branch has been checked out, there are no updates to make. */
+ if (is_index_unborn(r->index))
+ return UPDATE_SPARSITY_SUCCESS;
+
+ memset(&o, 0, sizeof(o));
+ o.verbose_update = isatty(2);
+ o.update = 1;
+ o.head_idx = -1;
+ o.src_index = r->index;
+ o.dst_index = r->index;
+ o.skip_sparse_checkout = 0;
+ o.pl = pl;
+
+ setup_work_tree();
+
+ repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR);
+
+ setup_unpack_trees_porcelain(&o, "sparse-checkout");
+ result = update_sparsity(&o);
+ clear_unpack_trees_porcelain(&o);
+
+ if (result == UPDATE_SPARSITY_WARNINGS)
+ /*
+ * We don't do any special handling of warnings from untracked
+ * files in the way or dirty entries that can't be removed.
+ */
+ result = UPDATE_SPARSITY_SUCCESS;
+ if (result == UPDATE_SPARSITY_SUCCESS)
+ write_locked_index(r->index, &lock_file, COMMIT_LOCK);
+ else
+ rollback_lock_file(&lock_file);
+
+ return result;
+}
+
+static char *escaped_pattern(char *pattern)
+{
+ char *p = pattern;
+ struct strbuf final = STRBUF_INIT;
+
+ while (*p) {
+ if (is_glob_special(*p))
+ strbuf_addch(&final, '\\');
+
+ strbuf_addch(&final, *p);
+ p++;
+ }
+
+ return strbuf_detach(&final, NULL);
+}
+
+static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
+{
+ int i;
+ struct pattern_entry *pe;
+ struct hashmap_iter iter;
+ struct string_list sl = STRING_LIST_INIT_DUP;
+ struct strbuf parent_pattern = STRBUF_INIT;
+
+ hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent) {
+ if (hashmap_get_entry(&pl->recursive_hashmap, pe, ent, NULL))
+ continue;
+
+ if (!hashmap_contains_parent(&pl->recursive_hashmap,
+ pe->pattern,
+ &parent_pattern))
+ string_list_insert(&sl, pe->pattern);
+ }
+
+ string_list_sort(&sl);
+ string_list_remove_duplicates(&sl, 0);
+
+ fprintf(fp, "/*\n!/*/\n");
+
+ for (i = 0; i < sl.nr; i++) {
+ char *pattern = escaped_pattern(sl.items[i].string);
+
+ if (strlen(pattern))
+ fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern);
+ free(pattern);
+ }
+
+ string_list_clear(&sl, 0);
+
+ hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent) {
+ if (!hashmap_contains_parent(&pl->recursive_hashmap,
+ pe->pattern,
+ &parent_pattern))
+ string_list_insert(&sl, pe->pattern);
+ }
+
+ strbuf_release(&parent_pattern);
+
+ string_list_sort(&sl);
+ string_list_remove_duplicates(&sl, 0);
+
+ for (i = 0; i < sl.nr; i++) {
+ char *pattern = escaped_pattern(sl.items[i].string);
+ fprintf(fp, "%s/\n", pattern);
+ free(pattern);
+ }
+}
+
+static int write_patterns_and_update(struct pattern_list *pl)
+{
+ char *sparse_filename;
+ FILE *fp;
+ int fd;
+ struct lock_file lk = LOCK_INIT;
+ int result;
+
+ sparse_filename = get_sparse_checkout_filename();
+
+ if (safe_create_leading_directories(sparse_filename))
+ die(_("failed to create directory for sparse-checkout file"));
+
+ fd = hold_lock_file_for_update(&lk, sparse_filename,
+ LOCK_DIE_ON_ERROR);
+
+ result = update_working_directory(pl);
+ if (result) {
+ rollback_lock_file(&lk);
+ free(sparse_filename);
+ clear_pattern_list(pl);
+ update_working_directory(NULL);
+ return result;
+ }
+
+ fp = xfdopen(fd, "w");
+
+ if (core_sparse_checkout_cone)
+ write_cone_to_file(fp, pl);
+ else
+ write_patterns_to_file(fp, pl);
+
+ fflush(fp);
+ commit_lock_file(&lk);
+
+ free(sparse_filename);
+ clear_pattern_list(pl);
+
+ return 0;
+}
+
+enum sparse_checkout_mode {
+ MODE_NO_PATTERNS = 0,
+ MODE_ALL_PATTERNS = 1,
+ MODE_CONE_PATTERNS = 2,
+};
+
+static int set_config(enum sparse_checkout_mode mode)
+{
+ const char *config_path;
+
+ if (upgrade_repository_format(1) < 0)
+ die(_("unable to upgrade repository format to enable worktreeConfig"));
+ if (git_config_set_gently("extensions.worktreeConfig", "true")) {
+ error(_("failed to set extensions.worktreeConfig setting"));
+ return 1;
+ }
+
+ config_path = git_path("config.worktree");
+ git_config_set_in_file_gently(config_path,
+ "core.sparseCheckout",
+ mode ? "true" : NULL);
+
+ git_config_set_in_file_gently(config_path,
+ "core.sparseCheckoutCone",
+ mode == MODE_CONE_PATTERNS ? "true" : NULL);
+
+ return 0;
+}
+
+static char const * const builtin_sparse_checkout_init_usage[] = {
+ N_("git sparse-checkout init [--cone]"),
+ NULL
+};
+
+static struct sparse_checkout_init_opts {
+ int cone_mode;
+} init_opts;
+
+static int sparse_checkout_init(int argc, const char **argv)
+{
+ struct pattern_list pl;
+ char *sparse_filename;
+ int res;
+ struct object_id oid;
+ int mode;
+ struct strbuf pattern = STRBUF_INIT;
+
+ static struct option builtin_sparse_checkout_init_options[] = {
+ OPT_BOOL(0, "cone", &init_opts.cone_mode,
+ N_("initialize the sparse-checkout in cone mode")),
+ OPT_END(),
+ };
+
+ repo_read_index(the_repository);
+
+ argc = parse_options(argc, argv, NULL,
+ builtin_sparse_checkout_init_options,
+ builtin_sparse_checkout_init_usage, 0);
+
+ if (init_opts.cone_mode) {
+ mode = MODE_CONE_PATTERNS;
+ core_sparse_checkout_cone = 1;
+ } else
+ mode = MODE_ALL_PATTERNS;
+
+ if (set_config(mode))
+ return 1;
+
+ memset(&pl, 0, sizeof(pl));
+
+ sparse_filename = get_sparse_checkout_filename();
+ res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
+
+ /* If we already have a sparse-checkout file, use it. */
+ if (res >= 0) {
+ free(sparse_filename);
+ core_apply_sparse_checkout = 1;
+ return update_working_directory(NULL);
+ }
+
+ if (get_oid("HEAD", &oid)) {
+ FILE *fp;
+
+ /* assume we are in a fresh repo, but update the sparse-checkout file */
+ fp = xfopen(sparse_filename, "w");
+ if (!fp)
+ die(_("failed to open '%s'"), sparse_filename);
+
+ free(sparse_filename);
+ fprintf(fp, "/*\n!/*/\n");
+ fclose(fp);
+ return 0;
+ }
+
+ strbuf_addstr(&pattern, "/*");
+ add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
+ strbuf_addstr(&pattern, "!/*/");
+ add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0);
+
+ return write_patterns_and_update(&pl);
+}
+
+static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
+{
+ struct pattern_entry *e = xmalloc(sizeof(*e));
+ e->patternlen = path->len;
+ e->pattern = strbuf_detach(path, NULL);
+ hashmap_entry_init(&e->ent,
+ ignore_case ?
+ strihash(e->pattern) :
+ strhash(e->pattern));
+
+ hashmap_add(&pl->recursive_hashmap, &e->ent);
+
+ while (e->patternlen) {
+ char *slash = strrchr(e->pattern, '/');
+ char *oldpattern = e->pattern;
+ size_t newlen;
+
+ if (slash == e->pattern)
+ break;
+
+ newlen = slash - e->pattern;
+ e = xmalloc(sizeof(struct pattern_entry));
+ e->patternlen = newlen;
+ e->pattern = xstrndup(oldpattern, newlen);
+ hashmap_entry_init(&e->ent,
+ ignore_case ?
+ strihash(e->pattern) :
+ strhash(e->pattern));
+
+ if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL))
+ hashmap_add(&pl->parent_hashmap, &e->ent);
+ }
+}
+
+static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl)
+{
+ strbuf_trim(line);
+
+ strbuf_trim_trailing_dir_sep(line);
+
+ if (strbuf_normalize_path(line))
+ die(_("could not normalize path %s"), line->buf);
+
+ if (!line->len)
+ return;
+
+ if (line->buf[0] != '/')
+ strbuf_insertstr(line, 0, "/");
+
+ insert_recursive_pattern(pl, line);
+}
+
+static char const * const builtin_sparse_checkout_set_usage[] = {
+ N_("git sparse-checkout (set|add) (--stdin | <patterns>)"),
+ NULL
+};
+
+static struct sparse_checkout_set_opts {
+ int use_stdin;
+} set_opts;
+
+static void add_patterns_from_input(struct pattern_list *pl,
+ int argc, const char **argv)
+{
+ int i;
+ if (core_sparse_checkout_cone) {
+ struct strbuf line = STRBUF_INIT;
+
+ hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0);
+ hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0);
+ pl->use_cone_patterns = 1;
+
+ if (set_opts.use_stdin) {
+ struct strbuf unquoted = STRBUF_INIT;
+ while (!strbuf_getline(&line, stdin)) {
+ if (line.buf[0] == '"') {
+ strbuf_reset(&unquoted);
+ if (unquote_c_style(&unquoted, line.buf, NULL))
+ die(_("unable to unquote C-style string '%s'"),
+ line.buf);
+
+ strbuf_swap(&unquoted, &line);
+ }
+
+ strbuf_to_cone_pattern(&line, pl);
+ }
+
+ strbuf_release(&unquoted);
+ } else {
+ for (i = 0; i < argc; i++) {
+ strbuf_setlen(&line, 0);
+ strbuf_addstr(&line, argv[i]);
+ strbuf_to_cone_pattern(&line, pl);
+ }
+ }
+ } else {
+ if (set_opts.use_stdin) {
+ struct strbuf line = STRBUF_INIT;
+
+ while (!strbuf_getline(&line, stdin)) {
+ size_t len;
+ char *buf = strbuf_detach(&line, &len);
+ add_pattern(buf, empty_base, 0, pl, 0);
+ }
+ } else {
+ for (i = 0; i < argc; i++)
+ add_pattern(argv[i], empty_base, 0, pl, 0);
+ }
+ }
+}
+
+enum modify_type {
+ REPLACE,
+ ADD,
+};
+
+static void add_patterns_cone_mode(int argc, const char **argv,
+ struct pattern_list *pl)
+{
+ struct strbuf buffer = STRBUF_INIT;
+ struct pattern_entry *pe;
+ struct hashmap_iter iter;
+ struct pattern_list existing;
+ char *sparse_filename = get_sparse_checkout_filename();
+
+ add_patterns_from_input(pl, argc, argv);
+
+ memset(&existing, 0, sizeof(existing));
+ existing.use_cone_patterns = core_sparse_checkout_cone;
+
+ if (add_patterns_from_file_to_list(sparse_filename, "", 0,
+ &existing, NULL))
+ die(_("unable to load existing sparse-checkout patterns"));
+ free(sparse_filename);
+
+ hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) {
+ if (!hashmap_contains_parent(&pl->recursive_hashmap,
+ pe->pattern, &buffer) ||
+ !hashmap_contains_parent(&pl->parent_hashmap,
+ pe->pattern, &buffer)) {
+ strbuf_reset(&buffer);
+ strbuf_addstr(&buffer, pe->pattern);
+ insert_recursive_pattern(pl, &buffer);
+ }
+ }
+
+ clear_pattern_list(&existing);
+ strbuf_release(&buffer);
+}
+
+static void add_patterns_literal(int argc, const char **argv,
+ struct pattern_list *pl)
+{
+ char *sparse_filename = get_sparse_checkout_filename();
+ if (add_patterns_from_file_to_list(sparse_filename, "", 0,
+ pl, NULL))
+ die(_("unable to load existing sparse-checkout patterns"));
+ free(sparse_filename);
+ add_patterns_from_input(pl, argc, argv);
+}
+
+static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
+{
+ int result;
+ int changed_config = 0;
+ struct pattern_list pl;
+ memset(&pl, 0, sizeof(pl));
+
+ switch (m) {
+ case ADD:
+ if (core_sparse_checkout_cone)
+ add_patterns_cone_mode(argc, argv, &pl);
+ else
+ add_patterns_literal(argc, argv, &pl);
+ break;
+
+ case REPLACE:
+ add_patterns_from_input(&pl, argc, argv);
+ break;
+ }
+
+ if (!core_apply_sparse_checkout) {
+ set_config(MODE_ALL_PATTERNS);
+ core_apply_sparse_checkout = 1;
+ changed_config = 1;
+ }
+
+ result = write_patterns_and_update(&pl);
+
+ if (result && changed_config)
+ set_config(MODE_NO_PATTERNS);
+
+ clear_pattern_list(&pl);
+ return result;
+}
+
+static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
+ enum modify_type m)
+{
+ static struct option builtin_sparse_checkout_set_options[] = {
+ OPT_BOOL(0, "stdin", &set_opts.use_stdin,
+ N_("read patterns from standard in")),
+ OPT_END(),
+ };
+
+ repo_read_index(the_repository);
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_sparse_checkout_set_options,
+ builtin_sparse_checkout_set_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+
+ return modify_pattern_list(argc, argv, m);
+}
+
+static char const * const builtin_sparse_checkout_reapply_usage[] = {
+ N_("git sparse-checkout reapply"),
+ NULL
+};
+
+static int sparse_checkout_reapply(int argc, const char **argv)
+{
+ static struct option builtin_sparse_checkout_reapply_options[] = {
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, NULL,
+ builtin_sparse_checkout_reapply_options,
+ builtin_sparse_checkout_reapply_usage, 0);
+
+ repo_read_index(the_repository);
+ return update_working_directory(NULL);
+}
+
+static char const * const builtin_sparse_checkout_disable_usage[] = {
+ N_("git sparse-checkout disable"),
+ NULL
+};
+
+static int sparse_checkout_disable(int argc, const char **argv)
+{
+ static struct option builtin_sparse_checkout_disable_options[] = {
+ OPT_END(),
+ };
+ struct pattern_list pl;
+ struct strbuf match_all = STRBUF_INIT;
+
+ argc = parse_options(argc, argv, NULL,
+ builtin_sparse_checkout_disable_options,
+ builtin_sparse_checkout_disable_usage, 0);
+
+ repo_read_index(the_repository);
+
+ memset(&pl, 0, sizeof(pl));
+ hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
+ hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0);
+ pl.use_cone_patterns = 0;
+ core_apply_sparse_checkout = 1;
+
+ strbuf_addstr(&match_all, "/*");
+ add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0);
+
+ if (update_working_directory(&pl))
+ die(_("error while refreshing working directory"));
+
+ clear_pattern_list(&pl);
+ return set_config(MODE_NO_PATTERNS);
+}
+
+int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
+{
+ static struct option builtin_sparse_checkout_options[] = {
+ OPT_END(),
+ };
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_sparse_checkout_usage,
+ builtin_sparse_checkout_options);
+
+ argc = parse_options(argc, argv, prefix,
+ builtin_sparse_checkout_options,
+ builtin_sparse_checkout_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ git_config(git_default_config, NULL);
+
+ if (argc > 0) {
+ if (!strcmp(argv[0], "list"))
+ return sparse_checkout_list(argc, argv);
+ if (!strcmp(argv[0], "init"))
+ return sparse_checkout_init(argc, argv);
+ if (!strcmp(argv[0], "set"))
+ return sparse_checkout_set(argc, argv, prefix, REPLACE);
+ if (!strcmp(argv[0], "add"))
+ return sparse_checkout_set(argc, argv, prefix, ADD);
+ if (!strcmp(argv[0], "reapply"))
+ return sparse_checkout_reapply(argc, argv);
+ if (!strcmp(argv[0], "disable"))
+ return sparse_checkout_disable(argc, argv);
+ }
+
+ usage_with_options(builtin_sparse_checkout_usage,
+ builtin_sparse_checkout_options);
+}
diff --git a/builtin/stash.c b/builtin/stash.c
index 5fe2af2394..e1f8235fdd 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -7,7 +7,7 @@
#include "cache-tree.h"
#include "unpack-trees.h"
#include "merge-recursive.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "run-command.h"
#include "dir.h"
#include "rerere.h"
@@ -27,6 +27,7 @@ static const char * const git_stash_usage[] = {
N_("git stash clear"),
N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
" [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
+ " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
" [--] [<pathspec>...]]"),
N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
" [-u|--include-untracked] [-a|--all] [<message>]"),
@@ -184,7 +185,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)
end_of_rev = strchrnul(revision, '@');
strbuf_add(&symbolic, revision, end_of_rev - revision);
- ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref);
+ ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref, 0);
strbuf_release(&symbolic);
switch (ret) {
case 0: /* Not found, but valid ref */
@@ -276,8 +277,8 @@ static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit)
* however it should be done together with apply_cached.
*/
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL);
- argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex);
+ strvec_pushl(&cp.args, "diff-tree", "--binary", NULL);
+ strvec_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex);
return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
}
@@ -292,7 +293,7 @@ static int apply_cached(struct strbuf *out)
* buffer.
*/
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "apply", "--cached", NULL);
+ strvec_pushl(&cp.args, "apply", "--cached", NULL);
return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
}
@@ -305,7 +306,7 @@ static int reset_head(void)
* API for resetting.
*/
cp.git_cmd = 1;
- argv_array_push(&cp.args, "reset");
+ strvec_push(&cp.args, "reset");
return run_command(&cp);
}
@@ -334,9 +335,9 @@ static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
* converted together with update_index.
*/
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only",
- "--diff-filter=A", NULL);
- argv_array_push(&cp.args, c_tree_hex);
+ strvec_pushl(&cp.args, "diff-index", "--cached", "--name-only",
+ "--diff-filter=A", NULL);
+ strvec_push(&cp.args, c_tree_hex);
return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
}
@@ -349,7 +350,7 @@ static int update_index(struct strbuf *out)
* function exposed in order to remove this forking.
*/
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
+ strvec_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
}
@@ -364,10 +365,10 @@ static int restore_untracked(struct object_id *u_tree)
* run_command to fork processes that will not interfere.
*/
cp.git_cmd = 1;
- argv_array_push(&cp.args, "read-tree");
- argv_array_push(&cp.args, oid_to_hex(u_tree));
- argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
- stash_index_path.buf);
+ strvec_push(&cp.args, "read-tree");
+ strvec_push(&cp.args, oid_to_hex(u_tree));
+ strvec_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
if (run_command(&cp)) {
remove_path(stash_index_path.buf);
return -1;
@@ -375,9 +376,9 @@ static int restore_untracked(struct object_id *u_tree)
child_process_init(&cp);
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "checkout-index", "--all", NULL);
- argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
- stash_index_path.buf);
+ strvec_pushl(&cp.args, "checkout-index", "--all", NULL);
+ strvec_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
res = run_command(&cp);
remove_path(stash_index_path.buf);
@@ -396,7 +397,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
const struct object_id *bases[1];
read_cache_preload(NULL);
- if (refresh_cache(REFRESH_QUIET))
+ if (refresh_and_write_cache(REFRESH_QUIET, 0, 0))
return -1;
if (write_cache_as_tree(&c_tree, 0, NULL))
@@ -427,6 +428,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
return error(_("could not save index tree"));
reset_head();
+ discard_cache();
+ read_cache();
}
}
@@ -481,13 +484,12 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
if (ret)
return -1;
+ /* read back the result of update_index() back from the disk */
discard_cache();
+ read_cache();
}
- if (quiet) {
- if (refresh_cache(REFRESH_QUIET))
- warning("could not refresh index");
- } else {
+ if (!quiet) {
struct child_process cp = CHILD_PROCESS_INIT;
/*
@@ -497,7 +499,11 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
*/
cp.git_cmd = 1;
cp.dir = prefix;
- argv_array_push(&cp.args, "status");
+ strvec_pushf(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT"=%s",
+ absolute_path(get_git_work_tree()));
+ strvec_pushf(&cp.env_array, GIT_DIR_ENVIRONMENT"=%s",
+ absolute_path(get_git_dir()));
+ strvec_push(&cp.args, "status");
run_command(&cp);
}
@@ -528,11 +534,22 @@ static int apply_stash(int argc, const char **argv, const char *prefix)
return ret;
}
+static int reject_reflog_ent(struct object_id *ooid, struct object_id *noid,
+ const char *email, timestamp_t timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ return 1;
+}
+
+static int reflog_is_empty(const char *refname)
+{
+ return !for_each_reflog_ent(refname, reject_reflog_ent, NULL);
+}
+
static int do_drop_stash(struct stash_info *info, int quiet)
{
int ret;
struct child_process cp_reflog = CHILD_PROCESS_INIT;
- struct child_process cp = CHILD_PROCESS_INIT;
/*
* reflog does not provide a simple function for deleting refs. One will
@@ -540,9 +557,9 @@ static int do_drop_stash(struct stash_info *info, int quiet)
*/
cp_reflog.git_cmd = 1;
- argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref",
- "--rewrite", NULL);
- argv_array_push(&cp_reflog.args, info->revision.buf);
+ strvec_pushl(&cp_reflog.args, "reflog", "delete", "--updateref",
+ "--rewrite", NULL);
+ strvec_push(&cp_reflog.args, info->revision.buf);
ret = run_command(&cp_reflog);
if (!ret) {
if (!quiet)
@@ -553,19 +570,7 @@ static int do_drop_stash(struct stash_info *info, int quiet)
info->revision.buf);
}
- /*
- * This could easily be replaced by get_oid, but currently it will throw
- * a fatal error when a reflog is empty, which we can not recover from.
- */
- cp.git_cmd = 1;
- /* Even though --quiet is specified, rev-parse still outputs the hash */
- cp.no_stdout = 1;
- argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL);
- argv_array_pushf(&cp.args, "%s@{0}", ref_stash);
- ret = run_command(&cp);
-
- /* do_clear_stash if we just dropped the last stash entry */
- if (ret)
+ if (reflog_is_empty(ref_stash))
do_clear_stash();
return 0;
@@ -657,9 +662,9 @@ static int branch_stash(int argc, const char **argv, const char *prefix)
return -1;
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "checkout", "-b", NULL);
- argv_array_push(&cp.args, branch);
- argv_array_push(&cp.args, oid_to_hex(&info.b_commit));
+ strvec_pushl(&cp.args, "checkout", "-b", NULL);
+ strvec_push(&cp.args, branch);
+ strvec_push(&cp.args, oid_to_hex(&info.b_commit));
ret = run_command(&cp);
if (!ret)
ret = do_apply_stash(prefix, &info, 1, 0);
@@ -686,16 +691,17 @@ static int list_stash(int argc, const char **argv, const char *prefix)
return 0;
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "log", "--format=%gd: %gs", "-g",
- "--first-parent", "-m", NULL);
- argv_array_pushv(&cp.args, argv);
- argv_array_push(&cp.args, ref_stash);
- argv_array_push(&cp.args, "--");
+ strvec_pushl(&cp.args, "log", "--format=%gd: %gs", "-g",
+ "--first-parent", "-m", NULL);
+ strvec_pushv(&cp.args, argv);
+ strvec_push(&cp.args, ref_stash);
+ strvec_push(&cp.args, "--");
return run_command(&cp);
}
static int show_stat = 1;
static int show_patch;
+static int use_legacy_stash;
static int git_stash_config(const char *var, const char *value, void *cb)
{
@@ -707,7 +713,11 @@ static int git_stash_config(const char *var, const char *value, void *cb)
show_patch = git_config_bool(var, value);
return 0;
}
- return git_default_config(var, value, cb);
+ if (!strcmp(var, "stash.usebuiltin")) {
+ use_legacy_stash = !git_config_bool(var, value);
+ return 0;
+ }
+ return git_diff_basic_config(var, value, cb);
}
static int show_stash(int argc, const char **argv, const char *prefix)
@@ -716,8 +726,8 @@ static int show_stash(int argc, const char **argv, const char *prefix)
int ret = 0;
struct stash_info info;
struct rev_info rev;
- struct argv_array stash_args = ARGV_ARRAY_INIT;
- struct argv_array revision_args = ARGV_ARRAY_INIT;
+ struct strvec stash_args = STRVEC_INIT;
+ struct strvec revision_args = STRVEC_INIT;
struct option options[] = {
OPT_END()
};
@@ -726,16 +736,16 @@ static int show_stash(int argc, const char **argv, const char *prefix)
git_config(git_diff_ui_config, NULL);
init_revisions(&rev, prefix);
- argv_array_push(&revision_args, argv[0]);
+ strvec_push(&revision_args, argv[0]);
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-')
- argv_array_push(&stash_args, argv[i]);
+ strvec_push(&stash_args, argv[i]);
else
- argv_array_push(&revision_args, argv[i]);
+ strvec_push(&revision_args, argv[i]);
}
- ret = get_stash_info(&info, stash_args.argc, stash_args.argv);
- argv_array_clear(&stash_args);
+ ret = get_stash_info(&info, stash_args.nr, stash_args.v);
+ strvec_clear(&stash_args);
if (ret)
return -1;
@@ -743,8 +753,7 @@ static int show_stash(int argc, const char **argv, const char *prefix)
* The config settings are applied only if there are not passed
* any options.
*/
- if (revision_args.argc == 1) {
- git_config(git_stash_config, NULL);
+ if (revision_args.nr == 1) {
if (show_stat)
rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT;
@@ -757,7 +766,7 @@ static int show_stash(int argc, const char **argv, const char *prefix)
}
}
- argc = setup_revisions(revision_args.argc, revision_args.argv, &rev, NULL);
+ argc = setup_revisions(revision_args.nr, revision_args.v, &rev, NULL);
if (argc > 1) {
free_stash_info(&info);
usage_with_options(git_stash_show_usage, options);
@@ -832,12 +841,12 @@ static int store_stash(int argc, const char **argv, const char *prefix)
return do_store_stash(&obj, stash_msg, quiet);
}
-static void add_pathspecs(struct argv_array *args,
+static void add_pathspecs(struct strvec *args,
const struct pathspec *ps) {
int i;
for (i = 0; i < ps->nr; i++)
- argv_array_push(args, ps->items[i].original);
+ strvec_push(args, ps->items[i].original);
}
/*
@@ -851,33 +860,23 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked,
struct strbuf *untracked_files)
{
int i;
- int max_len;
int found = 0;
- char *seen;
struct dir_struct dir;
- memset(&dir, 0, sizeof(dir));
+ dir_init(&dir);
if (include_untracked != INCLUDE_ALL_FILES)
setup_standard_excludes(&dir);
- seen = xcalloc(ps->nr, 1);
-
- max_len = fill_directory(&dir, the_repository->index, ps);
+ fill_directory(&dir, the_repository->index, ps);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- if (dir_path_match(&the_index, ent, ps, max_len, seen)) {
- found++;
- strbuf_addstr(untracked_files, ent->name);
- /* NUL-terminate: will be fed to update-index -z */
- strbuf_addch(untracked_files, '\0');
- }
- free(ent);
+ found++;
+ strbuf_addstr(untracked_files, ent->name);
+ /* NUL-terminate: will be fed to update-index -z */
+ strbuf_addch(untracked_files, '\0');
}
- free(seen);
- free(dir.entries);
- free(dir.ignored);
- clear_directory(&dir);
+ dir_clear(&dir);
return found;
}
@@ -957,9 +956,9 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
struct index_state istate = { NULL };
cp_upd_index.git_cmd = 1;
- argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
- "--remove", "--stdin", NULL);
- argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
+ strvec_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
+ "--remove", "--stdin", NULL);
+ strvec_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
stash_index_path.buf);
strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf);
@@ -993,32 +992,35 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
{
int ret = 0;
struct child_process cp_read_tree = CHILD_PROCESS_INIT;
- struct child_process cp_add_i = CHILD_PROCESS_INIT;
struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
struct index_state istate = { NULL };
+ char *old_index_env = NULL, *old_repo_index_file;
remove_path(stash_index_path.buf);
cp_read_tree.git_cmd = 1;
- argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL);
- argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s",
- stash_index_path.buf);
+ strvec_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL);
+ strvec_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
if (run_command(&cp_read_tree)) {
ret = -1;
goto done;
}
/* Find out what the user wants. */
- cp_add_i.git_cmd = 1;
- argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash",
- "--", NULL);
- add_pathspecs(&cp_add_i.args, ps);
- argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s",
- stash_index_path.buf);
- if (run_command(&cp_add_i)) {
- ret = -1;
- goto done;
- }
+ old_repo_index_file = the_repository->index_file;
+ the_repository->index_file = stash_index_path.buf;
+ old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
+ setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
+
+ ret = run_add_interactive(NULL, "--patch=stash", ps);
+
+ the_repository->index_file = old_repo_index_file;
+ if (old_index_env && *old_index_env)
+ setenv(INDEX_ENVIRONMENT, old_index_env, 1);
+ else
+ unsetenv(INDEX_ENVIRONMENT);
+ FREE_AND_NULL(old_index_env);
/* State of the working tree. */
if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
@@ -1028,8 +1030,8 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps,
}
cp_diff_tree.git_cmd = 1;
- argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
- oid_to_hex(&info->w_tree), "--", NULL);
+ strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD",
+ oid_to_hex(&info->w_tree), "--", NULL);
if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
ret = -1;
goto done;
@@ -1082,10 +1084,11 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps
}
cp_upd_index.git_cmd = 1;
- argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
- "--remove", "--stdin", NULL);
- argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
- stash_index_path.buf);
+ strvec_pushl(&cp_upd_index.args, "update-index",
+ "--ignore-skip-worktree-entries",
+ "-z", "--add", "--remove", "--stdin", NULL);
+ strvec_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len,
NULL, 0, NULL, 0)) {
@@ -1129,7 +1132,10 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
prepare_fallback_ident("git stash", "git@stash");
read_cache_preload(NULL);
- refresh_cache(REFRESH_QUIET);
+ if (refresh_and_write_cache(REFRESH_QUIET, 0, 0) < 0) {
+ ret = -1;
+ goto done;
+ }
if (get_oid("HEAD", &info->b_commit)) {
if (!quiet)
@@ -1290,7 +1296,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
free(ps_matched);
}
- if (refresh_cache(REFRESH_QUIET)) {
+ if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) {
ret = -1;
goto done;
}
@@ -1332,10 +1338,10 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "clean", "--force",
- "--quiet", "-d", NULL);
+ strvec_pushl(&cp.args, "clean", "--force",
+ "--quiet", "-d", NULL);
if (include_untracked == INCLUDE_ALL_FILES)
- argv_array_push(&cp.args, "-x");
+ strvec_push(&cp.args, "-x");
if (run_command(&cp)) {
ret = -1;
goto done;
@@ -1349,12 +1355,12 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
struct strbuf out = STRBUF_INIT;
cp_add.git_cmd = 1;
- argv_array_push(&cp_add.args, "add");
+ strvec_push(&cp_add.args, "add");
if (!include_untracked)
- argv_array_push(&cp_add.args, "-u");
+ strvec_push(&cp_add.args, "-u");
if (include_untracked == INCLUDE_ALL_FILES)
- argv_array_push(&cp_add.args, "--force");
- argv_array_push(&cp_add.args, "--");
+ strvec_push(&cp_add.args, "--force");
+ strvec_push(&cp_add.args, "--");
add_pathspecs(&cp_add.args, ps);
if (run_command(&cp_add)) {
ret = -1;
@@ -1362,9 +1368,9 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
}
cp_diff.git_cmd = 1;
- argv_array_pushl(&cp_diff.args, "diff-index", "-p",
- "--cached", "--binary", "HEAD", "--",
- NULL);
+ strvec_pushl(&cp_diff.args, "diff-index", "-p",
+ "--cached", "--binary", "HEAD", "--",
+ NULL);
add_pathspecs(&cp_diff.args, ps);
if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) {
ret = -1;
@@ -1372,8 +1378,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
}
cp_apply.git_cmd = 1;
- argv_array_pushl(&cp_apply.args, "apply", "--index",
- "-R", NULL);
+ strvec_pushl(&cp_apply.args, "apply", "--index",
+ "-R", NULL);
if (pipe_command(&cp_apply, out.buf, out.len, NULL, 0,
NULL, 0)) {
ret = -1;
@@ -1382,8 +1388,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
} else {
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "reset", "--hard", "-q",
- NULL);
+ strvec_pushl(&cp.args, "reset", "--hard", "-q",
+ "--no-recurse-submodules", NULL);
if (run_command(&cp)) {
ret = -1;
goto done;
@@ -1391,30 +1397,16 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
}
if (keep_index == 1 && !is_null_oid(&info.i_tree)) {
- struct child_process cp_ls = CHILD_PROCESS_INIT;
- struct child_process cp_checkout = CHILD_PROCESS_INIT;
- struct strbuf out = STRBUF_INIT;
-
- if (reset_tree(&info.i_tree, 0, 1)) {
- ret = -1;
- goto done;
- }
-
- cp_ls.git_cmd = 1;
- argv_array_pushl(&cp_ls.args, "ls-files", "-z",
- "--modified", "--", NULL);
-
- add_pathspecs(&cp_ls.args, ps);
- if (pipe_command(&cp_ls, NULL, 0, &out, 0, NULL, 0)) {
- ret = -1;
- goto done;
- }
+ struct child_process cp = CHILD_PROCESS_INIT;
- cp_checkout.git_cmd = 1;
- argv_array_pushl(&cp_checkout.args, "checkout-index",
- "-z", "--force", "--stdin", NULL);
- if (pipe_command(&cp_checkout, out.buf, out.len, NULL,
- 0, NULL, 0)) {
+ cp.git_cmd = 1;
+ strvec_pushl(&cp.args, "checkout", "--no-overlay",
+ oid_to_hex(&info.i_tree), "--", NULL);
+ if (!ps->nr)
+ strvec_push(&cp.args, ":/");
+ else
+ add_pathspecs(&cp.args, ps);
+ if (run_command(&cp)) {
ret = -1;
goto done;
}
@@ -1424,7 +1416,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "apply", "-R", NULL);
+ strvec_pushl(&cp.args, "apply", "-R", NULL);
if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) {
if (!quiet)
@@ -1438,7 +1430,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "reset", "-q", "--", NULL);
+ strvec_pushl(&cp.args, "reset", "-q", "--", NULL);
add_pathspecs(&cp.args, ps);
if (run_command(&cp)) {
ret = -1;
@@ -1453,13 +1445,17 @@ done:
return ret;
}
-static int push_stash(int argc, const char **argv, const char *prefix)
+static int push_stash(int argc, const char **argv, const char *prefix,
+ int push_assumed)
{
+ int force_assume = 0;
int keep_index = -1;
int patch_mode = 0;
int include_untracked = 0;
int quiet = 0;
+ int pathspec_file_nul = 0;
const char *stash_msg = NULL;
+ const char *pathspec_from_file = NULL;
struct pathspec ps;
struct option options[] = {
OPT_BOOL('k', "keep-index", &keep_index,
@@ -1473,16 +1469,45 @@ static int push_stash(int argc, const char **argv, const char *prefix)
N_("include ignore files"), 2),
OPT_STRING('m', "message", &stash_msg, N_("message"),
N_("stash message")),
+ OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
+ OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END()
};
- if (argc)
+ if (argc) {
+ force_assume = !strcmp(argv[0], "-p");
argc = parse_options(argc, argv, prefix, options,
git_stash_push_usage,
- 0);
+ PARSE_OPT_KEEP_DASHDASH);
+ }
+
+ if (argc) {
+ if (!strcmp(argv[0], "--")) {
+ argc--;
+ argv++;
+ } else if (push_assumed && !force_assume) {
+ die("subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'",
+ argv[0]);
+ }
+ }
parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
prefix, argv);
+
+ if (pathspec_from_file) {
+ if (patch_mode)
+ die(_("--pathspec-from-file is incompatible with --patch"));
+
+ if (ps.nr)
+ die(_("--pathspec-from-file is incompatible with pathspec arguments"));
+
+ parse_pathspec_file(&ps, 0,
+ PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
+ prefix, pathspec_from_file, pathspec_file_nul);
+ } else if (pathspec_file_nul) {
+ die(_("--pathspec-file-nul requires --pathspec-from-file"));
+ }
+
return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
include_untracked);
}
@@ -1527,55 +1552,22 @@ static int save_stash(int argc, const char **argv, const char *prefix)
return ret;
}
-static int use_builtin_stash(void)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
- struct strbuf out = STRBUF_INIT;
- int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1);
-
- if (env != -1)
- return env;
-
- argv_array_pushl(&cp.args,
- "config", "--bool", "stash.usebuiltin", NULL);
- cp.git_cmd = 1;
- if (capture_command(&cp, &out, 6)) {
- strbuf_release(&out);
- return 1;
- }
-
- strbuf_trim(&out);
- ret = !strcmp("true", out.buf);
- strbuf_release(&out);
- return ret;
-}
-
int cmd_stash(int argc, const char **argv, const char *prefix)
{
- int i = -1;
pid_t pid = getpid();
const char *index_file;
- struct argv_array args = ARGV_ARRAY_INIT;
+ struct strvec args = STRVEC_INIT;
struct option options[] = {
OPT_END()
};
- if (!use_builtin_stash()) {
- const char *path = mkpath("%s/git-legacy-stash",
- git_exec_path());
-
- if (sane_execvp(path, (char **)argv) < 0)
- die_errno(_("could not exec %s"), path);
- else
- BUG("sane_execvp() returned???");
- }
+ git_config(git_stash_config, NULL);
- prefix = setup_git_directory();
- trace_repo_setup(prefix);
- setup_work_tree();
-
- git_config(git_diff_basic_config, NULL);
+ if (use_legacy_stash ||
+ !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1))
+ warning(_("the stash.useBuiltin support has been removed!\n"
+ "See its entry in 'git help config' for details."));
argc = parse_options(argc, argv, prefix, options, git_stash_usage,
PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
@@ -1585,7 +1577,7 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
(uintmax_t)pid);
if (!argc)
- return !!push_stash(0, NULL, prefix);
+ return !!push_stash(0, NULL, prefix, 0);
else if (!strcmp(argv[0], "apply"))
return !!apply_stash(argc, argv, prefix);
else if (!strcmp(argv[0], "clear"))
@@ -1605,45 +1597,15 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
else if (!strcmp(argv[0], "create"))
return !!create_stash(argc, argv, prefix);
else if (!strcmp(argv[0], "push"))
- return !!push_stash(argc, argv, prefix);
+ return !!push_stash(argc, argv, prefix, 0);
else if (!strcmp(argv[0], "save"))
return !!save_stash(argc, argv, prefix);
else if (*argv[0] != '-')
usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
git_stash_usage, options);
- if (strcmp(argv[0], "-p")) {
- while (++i < argc && strcmp(argv[i], "--")) {
- /*
- * `akpqu` is a string which contains all short options,
- * except `-m` which is verified separately.
- */
- if ((strlen(argv[i]) == 2) && *argv[i] == '-' &&
- strchr("akpqu", argv[i][1]))
- continue;
-
- if (!strcmp(argv[i], "--all") ||
- !strcmp(argv[i], "--keep-index") ||
- !strcmp(argv[i], "--no-keep-index") ||
- !strcmp(argv[i], "--patch") ||
- !strcmp(argv[i], "--quiet") ||
- !strcmp(argv[i], "--include-untracked"))
- continue;
-
- /*
- * `-m` and `--message=` are verified separately because
- * they need to be immediately followed by a string
- * (i.e.`-m"foobar"` or `--message="foobar"`).
- */
- if (starts_with(argv[i], "-m") ||
- starts_with(argv[i], "--message="))
- continue;
-
- usage_with_options(git_stash_usage, options);
- }
- }
-
- argv_array_push(&args, "push");
- argv_array_pushv(&args, argv);
- return !!push_stash(args.argc, args.argv, prefix);
+ /* Assume 'stash push' */
+ strvec_push(&args, "push");
+ strvec_pushv(&args, argv);
+ return !!push_stash(args.nr, args.v, prefix, 1);
}
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index f9264c0621..c30896c897 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -20,6 +20,7 @@
#include "diff.h"
#include "object-store.h"
#include "dir.h"
+#include "advice.h"
#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
@@ -293,9 +294,9 @@ static char *compute_rev_name(const char *sub_path, const char* object_id)
cp.git_cmd = 1;
cp.no_stderr = 1;
- argv_array_push(&cp.args, "describe");
- argv_array_pushv(&cp.args, *d);
- argv_array_push(&cp.args, object_id);
+ strvec_push(&cp.args, "describe");
+ strvec_pushv(&cp.args, *d);
+ strvec_push(&cp.args, object_id);
if (!capture_command(&cp, &sb, 0)) {
strbuf_strip_suffix(&sb, "\n");
@@ -425,7 +426,7 @@ static int module_list(int argc, const char **argv, const char *prefix)
const struct cache_entry *ce = list.entries[i];
if (ce_stage(ce))
- printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1));
+ printf("%06o %s U\t", ce->ce_mode, oid_to_hex(&null_oid));
else
printf("%06o %s %d\t", ce->ce_mode,
oid_to_hex(&ce->oid), ce_stage(ce));
@@ -443,19 +444,19 @@ static void for_each_listed_submodule(const struct module_list *list,
fn(list->entries[i], cb_data);
}
-struct cb_foreach {
+struct foreach_cb {
int argc;
const char **argv;
const char *prefix;
int quiet;
int recursive;
};
-#define CB_FOREACH_INIT { 0 }
+#define FOREACH_CB_INIT { 0 }
static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
void *cb_data)
{
- struct cb_foreach *info = cb_data;
+ struct foreach_cb *info = cb_data;
const char *path = list_item->name;
const struct object_id *ce_oid = &list_item->oid;
@@ -494,12 +495,12 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
char *toplevel = xgetcwd();
struct strbuf sb = STRBUF_INIT;
- argv_array_pushf(&cp.env_array, "name=%s", sub->name);
- argv_array_pushf(&cp.env_array, "sm_path=%s", path);
- argv_array_pushf(&cp.env_array, "displaypath=%s", displaypath);
- argv_array_pushf(&cp.env_array, "sha1=%s",
- oid_to_hex(ce_oid));
- argv_array_pushf(&cp.env_array, "toplevel=%s", toplevel);
+ strvec_pushf(&cp.env_array, "name=%s", sub->name);
+ strvec_pushf(&cp.env_array, "sm_path=%s", path);
+ strvec_pushf(&cp.env_array, "displaypath=%s", displaypath);
+ strvec_pushf(&cp.env_array, "sha1=%s",
+ oid_to_hex(ce_oid));
+ strvec_pushf(&cp.env_array, "toplevel=%s", toplevel);
/*
* Since the path variable was accessible from the script
@@ -508,15 +509,15 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
* on windows. And since environment variables are
* case-insensitive in windows, it interferes with the
* existing PATH variable. Hence, to avoid that, we expose
- * path via the args argv_array and not via env_array.
+ * path via the args strvec and not via env_array.
*/
sq_quote_buf(&sb, path);
- argv_array_pushf(&cp.args, "path=%s; %s",
- sb.buf, info->argv[0]);
+ strvec_pushf(&cp.args, "path=%s; %s",
+ sb.buf, info->argv[0]);
strbuf_release(&sb);
free(toplevel);
} else {
- argv_array_pushv(&cp.args, info->argv);
+ strvec_pushv(&cp.args, info->argv);
}
if (!info->quiet)
@@ -533,16 +534,16 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
cpr.dir = path;
prepare_submodule_repo_env(&cpr.env_array);
- argv_array_pushl(&cpr.args, "--super-prefix", NULL);
- argv_array_pushf(&cpr.args, "%s/", displaypath);
- argv_array_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive",
- NULL);
+ strvec_pushl(&cpr.args, "--super-prefix", NULL);
+ strvec_pushf(&cpr.args, "%s/", displaypath);
+ strvec_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive",
+ NULL);
if (info->quiet)
- argv_array_push(&cpr.args, "--quiet");
+ strvec_push(&cpr.args, "--quiet");
- argv_array_push(&cpr.args, "--");
- argv_array_pushv(&cpr.args, info->argv);
+ strvec_push(&cpr.args, "--");
+ strvec_pushv(&cpr.args, info->argv);
if (run_command(&cpr))
die(_("run_command returned non-zero status while "
@@ -556,7 +557,7 @@ cleanup:
static int module_foreach(int argc, const char **argv, const char *prefix)
{
- struct cb_foreach info = CB_FOREACH_INIT;
+ struct foreach_cb info = FOREACH_CB_INIT;
struct pathspec pathspec;
struct module_list list = MODULE_LIST_INIT;
@@ -611,7 +612,6 @@ struct init_cb {
const char *prefix;
unsigned int flags;
};
-
#define INIT_CB_INIT { NULL, 0 }
static void init_submodule(const char *path, const char *prefix,
@@ -741,7 +741,6 @@ struct status_cb {
const char *prefix;
unsigned int flags;
};
-
#define STATUS_CB_INIT { NULL, 0 }
static void print_status(unsigned int flags, char state, const char *path,
@@ -778,9 +777,11 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
unsigned int flags)
{
char *displaypath;
- struct argv_array diff_files_args = ARGV_ARRAY_INIT;
+ struct strvec diff_files_args = STRVEC_INIT;
struct rev_info rev;
int diff_files_result;
+ struct strbuf buf = STRBUF_INIT;
+ const char *git_dir;
if (!submodule_from_path(the_repository, &null_oid, path))
die(_("no submodule mapping found in .gitmodules for path '%s'"),
@@ -793,21 +794,30 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
goto cleanup;
}
- if (!is_submodule_active(the_repository, path)) {
+ strbuf_addf(&buf, "%s/.git", path);
+ git_dir = read_gitfile(buf.buf);
+ if (!git_dir)
+ git_dir = buf.buf;
+
+ if (!is_submodule_active(the_repository, path) ||
+ !is_git_directory(git_dir)) {
print_status(flags, '-', path, ce_oid, displaypath);
+ strbuf_release(&buf);
goto cleanup;
}
+ strbuf_release(&buf);
- argv_array_pushl(&diff_files_args, "diff-files",
- "--ignore-submodules=dirty", "--quiet", "--",
- path, NULL);
+ strvec_pushl(&diff_files_args, "diff-files",
+ "--ignore-submodules=dirty", "--quiet", "--",
+ path, NULL);
git_config(git_diff_basic_config, NULL);
- repo_init_revisions(the_repository, &rev, prefix);
+
+ repo_init_revisions(the_repository, &rev, NULL);
rev.abbrev = 0;
- diff_files_args.argc = setup_revisions(diff_files_args.argc,
- diff_files_args.argv,
- &rev, NULL);
+ diff_files_args.nr = setup_revisions(diff_files_args.nr,
+ diff_files_args.v,
+ &rev, NULL);
diff_files_result = run_diff_files(&rev, 0);
if (!diff_result_code(&rev.diffopt, diff_files_result)) {
@@ -837,23 +847,23 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
cpr.dir = path;
prepare_submodule_repo_env(&cpr.env_array);
- argv_array_push(&cpr.args, "--super-prefix");
- argv_array_pushf(&cpr.args, "%s/", displaypath);
- argv_array_pushl(&cpr.args, "submodule--helper", "status",
- "--recursive", NULL);
+ strvec_push(&cpr.args, "--super-prefix");
+ strvec_pushf(&cpr.args, "%s/", displaypath);
+ strvec_pushl(&cpr.args, "submodule--helper", "status",
+ "--recursive", NULL);
if (flags & OPT_CACHED)
- argv_array_push(&cpr.args, "--cached");
+ strvec_push(&cpr.args, "--cached");
if (flags & OPT_QUIET)
- argv_array_push(&cpr.args, "--quiet");
+ strvec_push(&cpr.args, "--quiet");
if (run_command(&cpr))
die(_("failed to recurse into submodule '%s'"), path);
}
cleanup:
- argv_array_clear(&diff_files_args);
+ strvec_clear(&diff_files_args);
free(displaypath);
}
@@ -917,11 +927,437 @@ static int module_name(int argc, const char **argv, const char *prefix)
return 0;
}
+struct module_cb {
+ unsigned int mod_src;
+ unsigned int mod_dst;
+ struct object_id oid_src;
+ struct object_id oid_dst;
+ char status;
+ const char *sm_path;
+};
+#define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL }
+
+struct module_cb_list {
+ struct module_cb **entries;
+ int alloc, nr;
+};
+#define MODULE_CB_LIST_INIT { NULL, 0, 0 }
+
+struct summary_cb {
+ int argc;
+ const char **argv;
+ const char *prefix;
+ unsigned int cached: 1;
+ unsigned int for_status: 1;
+ unsigned int files: 1;
+ int summary_limit;
+};
+#define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 }
+
+enum diff_cmd {
+ DIFF_INDEX,
+ DIFF_FILES
+};
+
+static char *verify_submodule_committish(const char *sm_path,
+ const char *committish)
+{
+ struct child_process cp_rev_parse = CHILD_PROCESS_INIT;
+ struct strbuf result = STRBUF_INIT;
+
+ cp_rev_parse.git_cmd = 1;
+ cp_rev_parse.dir = sm_path;
+ prepare_submodule_repo_env(&cp_rev_parse.env_array);
+ strvec_pushl(&cp_rev_parse.args, "rev-parse", "-q", "--short", NULL);
+ strvec_pushf(&cp_rev_parse.args, "%s^0", committish);
+ strvec_push(&cp_rev_parse.args, "--");
+
+ if (capture_command(&cp_rev_parse, &result, 0))
+ return NULL;
+
+ strbuf_trim_trailing_newline(&result);
+ return strbuf_detach(&result, NULL);
+}
+
+static void print_submodule_summary(struct summary_cb *info, char *errmsg,
+ int total_commits, const char *displaypath,
+ const char *src_abbrev, const char *dst_abbrev,
+ struct module_cb *p)
+{
+ if (p->status == 'T') {
+ if (S_ISGITLINK(p->mod_dst))
+ printf(_("* %s %s(blob)->%s(submodule)"),
+ displaypath, src_abbrev, dst_abbrev);
+ else
+ printf(_("* %s %s(submodule)->%s(blob)"),
+ displaypath, src_abbrev, dst_abbrev);
+ } else {
+ printf("* %s %s...%s",
+ displaypath, src_abbrev, dst_abbrev);
+ }
+
+ if (total_commits < 0)
+ printf(":\n");
+ else
+ printf(" (%d):\n", total_commits);
+
+ if (errmsg) {
+ printf(_("%s"), errmsg);
+ } else if (total_commits > 0) {
+ struct child_process cp_log = CHILD_PROCESS_INIT;
+
+ cp_log.git_cmd = 1;
+ cp_log.dir = p->sm_path;
+ prepare_submodule_repo_env(&cp_log.env_array);
+ strvec_pushl(&cp_log.args, "log", NULL);
+
+ if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst)) {
+ if (info->summary_limit > 0)
+ strvec_pushf(&cp_log.args, "-%d",
+ info->summary_limit);
+
+ strvec_pushl(&cp_log.args, "--pretty= %m %s",
+ "--first-parent", NULL);
+ strvec_pushf(&cp_log.args, "%s...%s",
+ src_abbrev, dst_abbrev);
+ } else if (S_ISGITLINK(p->mod_dst)) {
+ strvec_pushl(&cp_log.args, "--pretty= > %s",
+ "-1", dst_abbrev, NULL);
+ } else {
+ strvec_pushl(&cp_log.args, "--pretty= < %s",
+ "-1", src_abbrev, NULL);
+ }
+ run_command(&cp_log);
+ }
+ printf("\n");
+}
+
+static void generate_submodule_summary(struct summary_cb *info,
+ struct module_cb *p)
+{
+ char *displaypath, *src_abbrev = NULL, *dst_abbrev;
+ int missing_src = 0, missing_dst = 0;
+ char *errmsg = NULL;
+ int total_commits = -1;
+
+ if (!info->cached && oideq(&p->oid_dst, &null_oid)) {
+ if (S_ISGITLINK(p->mod_dst)) {
+ struct ref_store *refs = get_submodule_ref_store(p->sm_path);
+ if (refs)
+ refs_head_ref(refs, handle_submodule_head_ref, &p->oid_dst);
+ } else if (S_ISLNK(p->mod_dst) || S_ISREG(p->mod_dst)) {
+ struct stat st;
+ int fd = open(p->sm_path, O_RDONLY);
+
+ if (fd < 0 || fstat(fd, &st) < 0 ||
+ index_fd(&the_index, &p->oid_dst, fd, &st, OBJ_BLOB,
+ p->sm_path, 0))
+ error(_("couldn't hash object from '%s'"), p->sm_path);
+ } else {
+ /* for a submodule removal (mode:0000000), don't warn */
+ if (p->mod_dst)
+ warning(_("unexpected mode %o\n"), p->mod_dst);
+ }
+ }
+
+ if (S_ISGITLINK(p->mod_src)) {
+ if (p->status != 'D')
+ src_abbrev = verify_submodule_committish(p->sm_path,
+ oid_to_hex(&p->oid_src));
+ if (!src_abbrev) {
+ missing_src = 1;
+ /*
+ * As `rev-parse` failed, we fallback to getting
+ * the abbreviated hash using oid_src. We do
+ * this as we might still need the abbreviated
+ * hash in cases like a submodule type change, etc.
+ */
+ src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7);
+ }
+ } else {
+ /*
+ * The source does not point to a submodule.
+ * So, we fallback to getting the abbreviation using
+ * oid_src as we might still need the abbreviated
+ * hash in cases like submodule add, etc.
+ */
+ src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7);
+ }
+
+ if (S_ISGITLINK(p->mod_dst)) {
+ dst_abbrev = verify_submodule_committish(p->sm_path,
+ oid_to_hex(&p->oid_dst));
+ if (!dst_abbrev) {
+ missing_dst = 1;
+ /*
+ * As `rev-parse` failed, we fallback to getting
+ * the abbreviated hash using oid_dst. We do
+ * this as we might still need the abbreviated
+ * hash in cases like a submodule type change, etc.
+ */
+ dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7);
+ }
+ } else {
+ /*
+ * The destination does not point to a submodule.
+ * So, we fallback to getting the abbreviation using
+ * oid_dst as we might still need the abbreviated
+ * hash in cases like a submodule removal, etc.
+ */
+ dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7);
+ }
+
+ displaypath = get_submodule_displaypath(p->sm_path, info->prefix);
+
+ if (!missing_src && !missing_dst) {
+ struct child_process cp_rev_list = CHILD_PROCESS_INIT;
+ struct strbuf sb_rev_list = STRBUF_INIT;
+
+ strvec_pushl(&cp_rev_list.args, "rev-list",
+ "--first-parent", "--count", NULL);
+ if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst))
+ strvec_pushf(&cp_rev_list.args, "%s...%s",
+ src_abbrev, dst_abbrev);
+ else
+ strvec_push(&cp_rev_list.args, S_ISGITLINK(p->mod_src) ?
+ src_abbrev : dst_abbrev);
+ strvec_push(&cp_rev_list.args, "--");
+
+ cp_rev_list.git_cmd = 1;
+ cp_rev_list.dir = p->sm_path;
+ prepare_submodule_repo_env(&cp_rev_list.env_array);
+
+ if (!capture_command(&cp_rev_list, &sb_rev_list, 0))
+ total_commits = atoi(sb_rev_list.buf);
+
+ strbuf_release(&sb_rev_list);
+ } else {
+ /*
+ * Don't give error msg for modification whose dst is not
+ * submodule, i.e., deleted or changed to blob
+ */
+ if (S_ISGITLINK(p->mod_dst)) {
+ struct strbuf errmsg_str = STRBUF_INIT;
+ if (missing_src && missing_dst) {
+ strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commits %s and %s\n",
+ displaypath, oid_to_hex(&p->oid_src),
+ oid_to_hex(&p->oid_dst));
+ } else {
+ strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commit %s\n",
+ displaypath, missing_src ?
+ oid_to_hex(&p->oid_src) :
+ oid_to_hex(&p->oid_dst));
+ }
+ errmsg = strbuf_detach(&errmsg_str, NULL);
+ }
+ }
+
+ print_submodule_summary(info, errmsg, total_commits,
+ displaypath, src_abbrev,
+ dst_abbrev, p);
+
+ free(displaypath);
+ free(src_abbrev);
+ free(dst_abbrev);
+}
+
+static void prepare_submodule_summary(struct summary_cb *info,
+ struct module_cb_list *list)
+{
+ int i;
+ for (i = 0; i < list->nr; i++) {
+ const struct submodule *sub;
+ struct module_cb *p = list->entries[i];
+ struct strbuf sm_gitdir = STRBUF_INIT;
+
+ if (p->status == 'D' || p->status == 'T') {
+ generate_submodule_summary(info, p);
+ continue;
+ }
+
+ if (info->for_status && p->status != 'A' &&
+ (sub = submodule_from_path(the_repository,
+ &null_oid, p->sm_path))) {
+ char *config_key = NULL;
+ const char *value;
+ int ignore_all = 0;
+
+ config_key = xstrfmt("submodule.%s.ignore",
+ sub->name);
+ if (!git_config_get_string_tmp(config_key, &value))
+ ignore_all = !strcmp(value, "all");
+ else if (sub->ignore)
+ ignore_all = !strcmp(sub->ignore, "all");
+
+ free(config_key);
+ if (ignore_all)
+ continue;
+ }
+
+ /* Also show added or modified modules which are checked out */
+ strbuf_addstr(&sm_gitdir, p->sm_path);
+ if (is_nonbare_repository_dir(&sm_gitdir))
+ generate_submodule_summary(info, p);
+ strbuf_release(&sm_gitdir);
+ }
+}
+
+static void submodule_summary_callback(struct diff_queue_struct *q,
+ struct diff_options *options,
+ void *data)
+{
+ int i;
+ struct module_cb_list *list = data;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ struct module_cb *temp;
+
+ if (!S_ISGITLINK(p->one->mode) && !S_ISGITLINK(p->two->mode))
+ continue;
+ temp = (struct module_cb*)malloc(sizeof(struct module_cb));
+ temp->mod_src = p->one->mode;
+ temp->mod_dst = p->two->mode;
+ temp->oid_src = p->one->oid;
+ temp->oid_dst = p->two->oid;
+ temp->status = p->status;
+ temp->sm_path = xstrdup(p->one->path);
+
+ ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
+ list->entries[list->nr++] = temp;
+ }
+}
+
+static const char *get_diff_cmd(enum diff_cmd diff_cmd)
+{
+ switch (diff_cmd) {
+ case DIFF_INDEX: return "diff-index";
+ case DIFF_FILES: return "diff-files";
+ default: BUG("bad diff_cmd value %d", diff_cmd);
+ }
+}
+
+static int compute_summary_module_list(struct object_id *head_oid,
+ struct summary_cb *info,
+ enum diff_cmd diff_cmd)
+{
+ struct strvec diff_args = STRVEC_INIT;
+ struct rev_info rev;
+ struct module_cb_list list = MODULE_CB_LIST_INIT;
+
+ strvec_push(&diff_args, get_diff_cmd(diff_cmd));
+ if (info->cached)
+ strvec_push(&diff_args, "--cached");
+ strvec_pushl(&diff_args, "--ignore-submodules=dirty", "--raw", NULL);
+ if (head_oid)
+ strvec_push(&diff_args, oid_to_hex(head_oid));
+ strvec_push(&diff_args, "--");
+ if (info->argc)
+ strvec_pushv(&diff_args, info->argv);
+
+ git_config(git_diff_basic_config, NULL);
+ init_revisions(&rev, info->prefix);
+ rev.abbrev = 0;
+ precompose_argv(diff_args.nr, diff_args.v);
+ setup_revisions(diff_args.nr, diff_args.v, &rev, NULL);
+ rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = submodule_summary_callback;
+ rev.diffopt.format_callback_data = &list;
+
+ if (!info->cached) {
+ if (diff_cmd == DIFF_INDEX)
+ setup_work_tree();
+ if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
+ perror("read_cache_preload");
+ return -1;
+ }
+ } else if (read_cache() < 0) {
+ perror("read_cache");
+ return -1;
+ }
+
+ if (diff_cmd == DIFF_INDEX)
+ run_diff_index(&rev, info->cached);
+ else
+ run_diff_files(&rev, 0);
+ prepare_submodule_summary(info, &list);
+ strvec_clear(&diff_args);
+ return 0;
+}
+
+static int module_summary(int argc, const char **argv, const char *prefix)
+{
+ struct summary_cb info = SUMMARY_CB_INIT;
+ int cached = 0;
+ int for_status = 0;
+ int files = 0;
+ int summary_limit = -1;
+ enum diff_cmd diff_cmd = DIFF_INDEX;
+ struct object_id head_oid;
+ int ret;
+
+ struct option module_summary_options[] = {
+ OPT_BOOL(0, "cached", &cached,
+ N_("use the commit stored in the index instead of the submodule HEAD")),
+ OPT_BOOL(0, "files", &files,
+ N_("to compare the commit in the index with that in the submodule HEAD")),
+ OPT_BOOL(0, "for-status", &for_status,
+ N_("skip submodules with 'ignore_config' value set to 'all'")),
+ OPT_INTEGER('n', "summary-limit", &summary_limit,
+ N_("limit the summary size")),
+ OPT_END()
+ };
+
+ const char *const git_submodule_helper_usage[] = {
+ N_("git submodule--helper summary [<options>] [<commit>] [--] [<path>]"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, module_summary_options,
+ git_submodule_helper_usage, 0);
+
+ if (!summary_limit)
+ return 0;
+
+ if (!get_oid(argc ? argv[0] : "HEAD", &head_oid)) {
+ if (argc) {
+ argv++;
+ argc--;
+ }
+ } else if (!argc || !strcmp(argv[0], "HEAD")) {
+ /* before the first commit: compare with an empty tree */
+ oidcpy(&head_oid, the_hash_algo->empty_tree);
+ if (argc) {
+ argv++;
+ argc--;
+ }
+ } else {
+ if (get_oid("HEAD", &head_oid))
+ die(_("could not fetch a revision for HEAD"));
+ }
+
+ if (files) {
+ if (cached)
+ die(_("--cached and --files are mutually exclusive"));
+ diff_cmd = DIFF_FILES;
+ }
+
+ info.argc = argc;
+ info.argv = argv;
+ info.prefix = prefix;
+ info.cached = !!cached;
+ info.files = !!files;
+ info.for_status = !!for_status;
+ info.summary_limit = summary_limit;
+
+ ret = compute_summary_module_list((diff_cmd == DIFF_INDEX) ? &head_oid : NULL,
+ &info, diff_cmd);
+ return ret;
+}
+
struct sync_cb {
const char *prefix;
unsigned int flags;
};
-
#define SYNC_CB_INIT { NULL, 0 }
static void sync_submodule(const char *path, const char *prefix,
@@ -983,8 +1419,8 @@ static void sync_submodule(const char *path, const char *prefix,
prepare_submodule_repo_env(&cp.env_array);
cp.git_cmd = 1;
cp.dir = path;
- argv_array_pushl(&cp.args, "submodule--helper",
- "print-default-remote", NULL);
+ strvec_pushl(&cp.args, "submodule--helper",
+ "print-default-remote", NULL);
strbuf_reset(&sb);
if (capture_command(&cp, &sb, 0))
@@ -1009,13 +1445,13 @@ static void sync_submodule(const char *path, const char *prefix,
cpr.dir = path;
prepare_submodule_repo_env(&cpr.env_array);
- argv_array_push(&cpr.args, "--super-prefix");
- argv_array_pushf(&cpr.args, "%s/", displaypath);
- argv_array_pushl(&cpr.args, "submodule--helper", "sync",
- "--recursive", NULL);
+ strvec_push(&cpr.args, "--super-prefix");
+ strvec_pushf(&cpr.args, "%s/", displaypath);
+ strvec_pushl(&cpr.args, "submodule--helper", "sync",
+ "--recursive", NULL);
if (flags & OPT_QUIET)
- argv_array_push(&cpr.args, "--quiet");
+ strvec_push(&cpr.args, "--quiet");
if (run_command(&cpr))
die(_("failed to recurse into submodule '%s'"),
@@ -1115,8 +1551,8 @@ static void deinit_submodule(const char *path, const char *prefix,
if (!(flags & OPT_FORCE)) {
struct child_process cp_rm = CHILD_PROCESS_INIT;
cp_rm.git_cmd = 1;
- argv_array_pushl(&cp_rm.args, "rm", "-qn",
- path, NULL);
+ strvec_pushl(&cp_rm.args, "rm", "-qn",
+ path, NULL);
if (run_command(&cp_rm))
die(_("Submodule work tree '%s' contains local "
@@ -1144,8 +1580,8 @@ static void deinit_submodule(const char *path, const char *prefix,
displaypath);
cp_config.git_cmd = 1;
- argv_array_pushl(&cp_config.args, "config", "--get-regexp", NULL);
- argv_array_pushf(&cp_config.args, "submodule.%s\\.", sub->name);
+ strvec_pushl(&cp_config.args, "config", "--get-regexp", NULL);
+ strvec_pushf(&cp_config.args, "submodule.%s\\.", sub->name);
/* remove the .git/config entries (unless the user already did it) */
if (!capture_command(&cp_config, &sb_config, 0) && sb_config.len) {
@@ -1223,32 +1659,36 @@ static int module_deinit(int argc, const char **argv, const char *prefix)
static int clone_submodule(const char *path, const char *gitdir, const char *url,
const char *depth, struct string_list *reference, int dissociate,
- int quiet, int progress)
+ int quiet, int progress, int single_branch)
{
struct child_process cp = CHILD_PROCESS_INIT;
- argv_array_push(&cp.args, "clone");
- argv_array_push(&cp.args, "--no-checkout");
+ strvec_push(&cp.args, "clone");
+ strvec_push(&cp.args, "--no-checkout");
if (quiet)
- argv_array_push(&cp.args, "--quiet");
+ strvec_push(&cp.args, "--quiet");
if (progress)
- argv_array_push(&cp.args, "--progress");
+ strvec_push(&cp.args, "--progress");
if (depth && *depth)
- argv_array_pushl(&cp.args, "--depth", depth, NULL);
+ strvec_pushl(&cp.args, "--depth", depth, NULL);
if (reference->nr) {
struct string_list_item *item;
for_each_string_list_item(item, reference)
- argv_array_pushl(&cp.args, "--reference",
- item->string, NULL);
+ strvec_pushl(&cp.args, "--reference",
+ item->string, NULL);
}
if (dissociate)
- argv_array_push(&cp.args, "--dissociate");
+ strvec_push(&cp.args, "--dissociate");
if (gitdir && *gitdir)
- argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
+ strvec_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
+ if (single_branch >= 0)
+ strvec_push(&cp.args, single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
- argv_array_push(&cp.args, "--");
- argv_array_push(&cp.args, url);
- argv_array_push(&cp.args, path);
+ strvec_push(&cp.args, "--");
+ strvec_push(&cp.args, url);
+ strvec_push(&cp.args, path);
cp.git_cmd = 1;
prepare_submodule_repo_env(&cp.env_array);
@@ -1269,6 +1709,13 @@ struct submodule_alternate_setup {
#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \
SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
+static const char alternate_error_advice[] = N_(
+"An alternate computed from a superproject's alternate is invalid.\n"
+"To allow Git to clone without an alternate in such a case, set\n"
+"submodule.alternateErrorStrategy to 'info' or, equivalently, clone with\n"
+"'--reference-if-able' instead of '--reference'."
+);
+
static int add_possible_reference_from_superproject(
struct object_directory *odb, void *sas_cb)
{
@@ -1300,6 +1747,8 @@ static int add_possible_reference_from_superproject(
} else {
switch (sas->error_mode) {
case SUBMODULE_ALTERNATE_ERROR_DIE:
+ if (advice_submodule_alternate_error_strategy_die)
+ advise(_(alternate_error_advice));
die(_("submodule '%s' cannot add alternate: %s"),
sas->submodule_name, err.buf);
case SUBMODULE_ALTERNATE_ERROR_INFO:
@@ -1362,6 +1811,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
struct string_list reference = STRING_LIST_INIT_NODUP;
int dissociate = 0, require_init = 0;
char *sm_alternate = NULL, *error_strategy = NULL;
+ int single_branch = -1;
struct option module_clone_options[] = {
OPT_STRING(0, "prefix", &prefix,
@@ -1389,12 +1839,15 @@ static int module_clone(int argc, const char **argv, const char *prefix)
N_("force cloning progress")),
OPT_BOOL(0, "require-init", &require_init,
N_("disallow cloning into non-empty directory")),
+ OPT_BOOL(0, "single-branch", &single_branch,
+ N_("clone only one branch, HEAD or --branch")),
OPT_END()
};
const char *const git_submodule_helper_usage[] = {
N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
"[--reference <repository>] [--name <name>] [--depth <depth>] "
+ "[--single-branch] "
"--url <url> --path <path>"),
NULL
};
@@ -1427,7 +1880,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
prepare_possible_alternates(name, &reference);
if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate,
- quiet, progress))
+ quiet, progress, single_branch))
die(_("clone of '%s' into submodule path '%s' failed"),
url, path);
} else {
@@ -1482,7 +1935,7 @@ static void determine_submodule_update_strategy(struct repository *r,
if (parse_submodule_update_strategy(update, out) < 0)
die(_("Invalid update mode '%s' for submodule path '%s'"),
update, path);
- } else if (!repo_config_get_string_const(r, key, &val)) {
+ } else if (!repo_config_get_string_tmp(r, key, &val)) {
if (parse_submodule_update_strategy(val, out) < 0)
die(_("Invalid update mode '%s' configured for submodule path '%s'"),
val, path);
@@ -1551,6 +2004,7 @@ struct submodule_update_clone {
const char *depth;
const char *recursive_prefix;
const char *prefix;
+ int single_branch;
/* to be consumed by git-submodule.sh */
struct update_clone_data *update_clone;
@@ -1565,10 +2019,14 @@ struct submodule_update_clone {
int max_jobs;
};
-#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
- SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, 0, \
- NULL, NULL, NULL, \
- NULL, 0, 0, 0, NULL, 0, 0, 1}
+#define SUBMODULE_UPDATE_CLONE_INIT { \
+ .list = MODULE_LIST_INIT, \
+ .update = SUBMODULE_UPDATE_STRATEGY_INIT, \
+ .recommend_shallow = -1, \
+ .references = STRING_LIST_INIT_DUP, \
+ .single_branch = -1, \
+ .max_jobs = 1, \
+}
static void next_submodule_warn_missing(struct submodule_update_clone *suc,
@@ -1633,7 +2091,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
}
key = xstrfmt("submodule.%s.update", sub->name);
- if (!repo_config_get_string_const(the_repository, key, &update_string)) {
+ if (!repo_config_get_string_tmp(the_repository, key, &update_string)) {
update_type = parse_submodule_update_type(update_string);
} else {
update_type = sub->update_strategy.type;
@@ -1656,7 +2114,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
strbuf_reset(&sb);
strbuf_addf(&sb, "submodule.%s.url", sub->name);
- if (repo_config_get_string_const(the_repository, sb.buf, &url)) {
+ if (repo_config_get_string_tmp(the_repository, sb.buf, &url)) {
if (starts_with_dot_slash(sub->url) ||
starts_with_dot_dot_slash(sub->url)) {
url = compute_submodule_clone_url(sub->url);
@@ -1683,34 +2141,38 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
child->no_stdin = 1;
child->stdout_to_stderr = 1;
child->err = -1;
- argv_array_push(&child->args, "submodule--helper");
- argv_array_push(&child->args, "clone");
+ strvec_push(&child->args, "submodule--helper");
+ strvec_push(&child->args, "clone");
if (suc->progress)
- argv_array_push(&child->args, "--progress");
+ strvec_push(&child->args, "--progress");
if (suc->quiet)
- argv_array_push(&child->args, "--quiet");
+ strvec_push(&child->args, "--quiet");
if (suc->prefix)
- argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
+ strvec_pushl(&child->args, "--prefix", suc->prefix, NULL);
if (suc->recommend_shallow && sub->recommend_shallow == 1)
- argv_array_push(&child->args, "--depth=1");
+ strvec_push(&child->args, "--depth=1");
if (suc->require_init)
- argv_array_push(&child->args, "--require-init");
- argv_array_pushl(&child->args, "--path", sub->path, NULL);
- argv_array_pushl(&child->args, "--name", sub->name, NULL);
- argv_array_pushl(&child->args, "--url", url, NULL);
+ strvec_push(&child->args, "--require-init");
+ strvec_pushl(&child->args, "--path", sub->path, NULL);
+ strvec_pushl(&child->args, "--name", sub->name, NULL);
+ strvec_pushl(&child->args, "--url", url, NULL);
if (suc->references.nr) {
struct string_list_item *item;
for_each_string_list_item(item, &suc->references)
- argv_array_pushl(&child->args, "--reference", item->string, NULL);
+ strvec_pushl(&child->args, "--reference", item->string, NULL);
}
if (suc->dissociate)
- argv_array_push(&child->args, "--dissociate");
+ strvec_push(&child->args, "--dissociate");
if (suc->depth)
- argv_array_push(&child->args, suc->depth);
+ strvec_push(&child->args, suc->depth);
+ if (suc->single_branch >= 0)
+ strvec_push(&child->args, suc->single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
cleanup:
- strbuf_reset(&displaypath_sb);
- strbuf_reset(&sb);
+ strbuf_release(&displaypath_sb);
+ strbuf_release(&sb);
if (need_free_url)
free((void*)url);
@@ -1886,11 +2348,13 @@ static int update_clone(int argc, const char **argv, const char *prefix)
N_("force cloning progress")),
OPT_BOOL(0, "require-init", &suc.require_init,
N_("disallow cloning into non-empty directory")),
+ OPT_BOOL(0, "single-branch", &suc.single_branch,
+ N_("clone only one branch, HEAD or --branch")),
OPT_END()
};
const char *const git_submodule_helper_usage[] = {
- N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"),
+ N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"),
NULL
};
suc.prefix = prefix;
@@ -1936,12 +2400,12 @@ static const char *remote_submodule_branch(const char *path)
return NULL;
key = xstrfmt("submodule.%s.branch", sub->name);
- if (repo_config_get_string_const(the_repository, key, &branch))
+ if (repo_config_get_string_tmp(the_repository, key, &branch))
branch = sub->branch;
free(key);
if (!branch)
- return "master";
+ return "HEAD";
if (!strcmp(branch, ".")) {
const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
@@ -2061,7 +2525,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
{
const struct submodule *sub;
const char *path;
- char *cw;
+ const char *cw;
struct repository subrepo;
if (argc != 2)
@@ -2076,7 +2540,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
if (repo_submodule_init(&subrepo, the_repository, sub))
die(_("could not get a repository handle for submodule '%s'"), path);
- if (!repo_config_get_string(&subrepo, "core.worktree", &cw)) {
+ if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) {
char *cfg_file, *abs_path;
const char *rel_path;
struct strbuf sb = STRBUF_INIT;
@@ -2124,8 +2588,7 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
return 1;
for (i = 0; i < list.nr; i++)
- absorb_git_dir_into_superproject(prefix,
- list.entries[i]->name, flags);
+ absorb_git_dir_into_superproject(list.entries[i]->name, flags);
return 0;
}
@@ -2207,6 +2670,80 @@ static int module_config(int argc, const char **argv, const char *prefix)
usage_with_options(git_submodule_helper_usage, module_config_options);
}
+static int module_set_url(int argc, const char **argv, const char *prefix)
+{
+ int quiet = 0;
+ const char *newurl;
+ const char *path;
+ char *config_name;
+
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("Suppress output for setting url of a submodule")),
+ OPT_END()
+ };
+ const char *const usage[] = {
+ N_("git submodule--helper set-url [--quiet] <path> <newurl>"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (argc != 2 || !(path = argv[0]) || !(newurl = argv[1]))
+ usage_with_options(usage, options);
+
+ config_name = xstrfmt("submodule.%s.url", path);
+
+ config_set_in_gitmodules_file_gently(config_name, newurl);
+ sync_submodule(path, prefix, quiet ? OPT_QUIET : 0);
+
+ free(config_name);
+
+ return 0;
+}
+
+static int module_set_branch(int argc, const char **argv, const char *prefix)
+{
+ int opt_default = 0, ret;
+ const char *opt_branch = NULL;
+ const char *path;
+ char *config_name;
+
+ /*
+ * We accept the `quiet` option for uniformity across subcommands,
+ * though there is nothing to make less verbose in this subcommand.
+ */
+ struct option options[] = {
+ OPT_NOOP_NOARG('q', "quiet"),
+ OPT_BOOL('d', "default", &opt_default,
+ N_("set the default tracking branch to master")),
+ OPT_STRING('b', "branch", &opt_branch, N_("branch"),
+ N_("set the default tracking branch")),
+ OPT_END()
+ };
+ const char *const usage[] = {
+ N_("git submodule--helper set-branch [-q|--quiet] (-d|--default) <path>"),
+ N_("git submodule--helper set-branch [-q|--quiet] (-b|--branch) <branch> <path>"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (!opt_branch && !opt_default)
+ die(_("--branch or --default required"));
+
+ if (opt_branch && opt_default)
+ die(_("--branch and --default are mutually exclusive"));
+
+ if (argc != 1 || !(path = argv[0]))
+ usage_with_options(usage, options);
+
+ config_name = xstrfmt("submodule.%s.branch", path);
+ ret = config_set_in_gitmodules_file_gently(config_name, opt_branch);
+
+ free(config_name);
+ return !!ret;
+}
+
#define SUPPORT_SUPER_PREFIX (1<<0)
struct cmd_struct {
@@ -2231,12 +2768,15 @@ static struct cmd_struct commands[] = {
{"print-default-remote", print_default_remote, 0},
{"sync", module_sync, SUPPORT_SUPER_PREFIX},
{"deinit", module_deinit, 0},
+ {"summary", module_summary, SUPPORT_SUPER_PREFIX},
{"remote-branch", resolve_remote_submodule_branch, 0},
{"push-check", push_check, 0},
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
{"is-active", is_active, 0},
{"check-name", check_name, 0},
{"config", module_config, 0},
+ {"set-url", module_set_url, 0},
+ {"set-branch", module_set_branch, 0},
};
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --git a/builtin/tag.c b/builtin/tag.c
index ef37dccf86..ecf011776d 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -17,7 +17,7 @@
#include "diff.h"
#include "revision.h"
#include "gpg-interface.h"
-#include "sha1-array.h"
+#include "oid-array.h"
#include "column.h"
#include "ref-filter.h"
@@ -26,13 +26,14 @@ static const char * const git_tag_usage[] = {
"\t\t<tagname> [<head>]"),
N_("git tag -d <tagname>..."),
N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n"
- "\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
+ "\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"),
N_("git tag -v [--format=<format>] <tagname>..."),
NULL
};
static unsigned int colopts;
static int force_sign_annotate;
+static int config_sign_tag = -1; /* unspecified */
static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
struct ref_format *format)
@@ -144,6 +145,11 @@ static int git_tag_config(const char *var, const char *value, void *cb)
int status;
struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
+ if (!strcmp(var, "tag.gpgsign")) {
+ config_sign_tag = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "tag.sort")) {
if (!value)
return config_error_nonbool(var);
@@ -225,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref,
if (type <= OBJ_NONE)
die(_("bad object type."));
- if (type == OBJ_TAG && advice_nested_tag)
- advise(_(message_advice_nested_tag), tag, object_ref);
+ if (type == OBJ_TAG)
+ advise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag),
+ tag, object_ref);
strbuf_addf(&header,
"object %s\n"
@@ -403,8 +410,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
OPT_GROUP(N_("Tag creation options")),
OPT_BOOL('a', "annotate", &annotate,
N_("annotated tag, needs a message")),
- { OPTION_CALLBACK, 'm', "message", &msg, N_("message"),
- N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg },
+ OPT_CALLBACK_F('m', "message", &msg, N_("message"),
+ N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg),
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
@@ -442,27 +449,31 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
memset(&opt, 0, sizeof(opt));
memset(&filter, 0, sizeof(filter));
filter.lines = -1;
+ opt.sign = -1;
argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
- if (keyid) {
- opt.sign = 1;
- set_signing_key(keyid);
- }
- create_tag_object = (opt.sign || annotate || msg.given || msgfile);
-
if (!cmdmode) {
if (argc == 0)
cmdmode = 'l';
else if (filter.with_commit || filter.no_commit ||
- filter.points_at.nr || filter.merge_commit ||
- filter.lines != -1)
+ filter.reachable_from || filter.unreachable_from ||
+ filter.points_at.nr || filter.lines != -1)
cmdmode = 'l';
}
if (cmdmode == 'l')
setup_auto_pager("tag", 1);
+ if (opt.sign == -1)
+ opt.sign = cmdmode ? 0 : config_sign_tag > 0;
+
+ if (keyid) {
+ opt.sign = 1;
+ set_signing_key(keyid);
+ }
+ create_tag_object = (opt.sign || annotate || msg.given || msgfile);
+
if ((create_tag_object || force) && (cmdmode != 0))
usage_with_options(git_tag_usage, options);
@@ -474,7 +485,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
}
if (!sorting)
sorting = ref_default_sorting();
- sorting->ignore_case = icase;
+ ref_sorting_icase_all(sorting, icase);
filter.ignore_case = icase;
if (cmdmode == 'l') {
int ret;
@@ -498,7 +509,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
die(_("--no-contains option is only allowed in list mode"));
if (filter.points_at.nr)
die(_("--points-at option is only allowed in list mode"));
- if (filter.merge_commit)
+ if (filter.reachable_from || filter.unreachable_from)
die(_("--merged and --no-merged options are only allowed in list mode"));
if (cmdmode == 'd')
return for_each_tag_name(argv, delete_tag, NULL);
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 80478808b3..dd4a75e030 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -24,6 +24,7 @@ static off_t consumed_bytes;
static off_t max_input_size;
static git_hash_ctx ctx;
static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
+static struct progress *progress;
/*
* When running under --strict mode, objects whose reachability are
@@ -92,6 +93,7 @@ static void use(int bytes)
consumed_bytes += bytes;
if (max_input_size && consumed_bytes > max_input_size)
die(_("pack exceeds maximum allowed size"));
+ display_throughput(progress, consumed_bytes);
}
static void *get_data(unsigned long size)
@@ -263,7 +265,8 @@ static void write_object(unsigned nr, enum object_type type,
} else {
struct object *obj;
int eaten;
- hash_object_file(buf, size, type_name(type), &obj_list[nr].oid);
+ hash_object_file(the_hash_algo, buf, size, type_name(type),
+ &obj_list[nr].oid);
added_object(nr, type, buf, size);
obj = parse_object_buffer(the_repository, &obj_list[nr].oid,
type, size, buf,
@@ -332,7 +335,7 @@ static int resolve_against_held(unsigned nr, const struct object_id *base,
{
struct object *obj;
struct obj_buffer *obj_buffer;
- obj = lookup_object(the_repository, base->hash);
+ obj = lookup_object(the_repository, base);
if (!obj)
return 0;
obj_buffer = lookup_object_buffer(obj);
@@ -484,7 +487,6 @@ static void unpack_one(unsigned nr)
static void unpack_all(void)
{
int i;
- struct progress *progress = NULL;
struct pack_header *hdr = fill(sizeof(struct pack_header));
nr_objects = ntohl(hdr->hdr_entries);
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 3f8cc6ccb4..79087bccea 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -35,6 +35,7 @@ static int verbose;
static int mark_valid_only;
static int mark_skip_worktree_only;
static int mark_fsmonitor_only;
+static int ignore_skip_worktree_entries;
#define MARK_FLAG 1
#define UNMARK_FLAG 2
static struct strbuf mtime_dir = STRBUF_INIT;
@@ -381,7 +382,8 @@ static int process_path(const char *path, struct stat *st, int stat_errno)
* so updating it does not make sense.
* On the other hand, removing it from index should work
*/
- if (allow_remove && remove_file_from_cache(path))
+ if (!ignore_skip_worktree_entries && allow_remove &&
+ remove_file_from_cache(path))
return error("%s: cannot remove from the index", path);
return 0;
}
@@ -601,7 +603,7 @@ static struct cache_entry *read_one_ent(const char *which,
struct object_id oid;
struct cache_entry *ce;
- if (get_tree_entry(ent, path, &oid, &mode)) {
+ if (get_tree_entry(the_repository, ent, path, &oid, &mode)) {
if (which)
error("%s: not in %s branch.", path, which);
return NULL;
@@ -966,6 +968,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
struct parse_opt_ctx_t ctx;
strbuf_getline_fn getline_fn;
int parseopt_state = PARSE_OPT_UNKNOWN;
+ struct repository *r = the_repository;
struct option options[] = {
OPT_BIT('q', NULL, &refresh_args.flags,
N_("continue refresh even when index needs update"),
@@ -982,14 +985,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
OPT_BIT(0, "unmerged", &refresh_args.flags,
N_("refresh even if index contains unmerged entries"),
REFRESH_UNMERGED),
- {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL,
+ OPT_CALLBACK_F(0, "refresh", &refresh_args, NULL,
N_("refresh stat information"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
- refresh_callback},
- {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL,
+ refresh_callback),
+ OPT_CALLBACK_F(0, "really-refresh", &refresh_args, NULL,
N_("like --refresh, but ignore assume-unchanged setting"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
- really_refresh_callback},
+ really_refresh_callback),
{OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL,
N_("<mode>,<object>,<path>"),
N_("add the specified entry to the index"),
@@ -997,10 +1000,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
NULL, 0,
cacheinfo_callback},
- {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+|-)x",
+ OPT_CALLBACK_F(0, "chmod", &set_executable_bit, "(+|-)x",
N_("override the executable bit of the listed files"),
PARSE_OPT_NONEG,
- chmod_callback},
+ chmod_callback),
{OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL,
N_("mark files as \"not changing\""),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
@@ -1013,6 +1016,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
{OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL,
N_("clear skip-worktree bit"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
+ OPT_BOOL(0, "ignore-skip-worktree-entries", &ignore_skip_worktree_entries,
+ N_("do not touch index-only entries")),
OPT_SET_INT(0, "info-only", &info_only,
N_("add to index only; do not add content to object database"), 1),
OPT_SET_INT(0, "force-remove", &force_remove,
@@ -1040,10 +1045,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
REFRESH_IGNORE_MISSING),
OPT_SET_INT(0, "verbose", &verbose,
N_("report actions to standard output"), 1),
- {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL,
+ OPT_CALLBACK_F(0, "clear-resolve-undo", NULL, NULL,
N_("(for porcelains) forget saved unresolved conflicts"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
- resolve_undo_clear_callback},
+ resolve_undo_clear_callback),
OPT_INTEGER(0, "index-version", &preferred_index_format,
N_("write index in this format")),
OPT_BOOL(0, "split-index", &split_index,
@@ -1180,11 +1185,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
remove_split_index(&the_index);
}
+ prepare_repo_settings(r);
switch (untracked_cache) {
case UC_UNSPECIFIED:
break;
case UC_DISABLE:
- if (git_config_get_untracked_cache() == 1)
+ if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)
warning(_("core.untrackedCache is set to true; "
"remove or change it, if you really want to "
"disable the untracked cache"));
@@ -1196,7 +1202,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
return !test_if_untracked_cache_is_supported();
case UC_ENABLE:
case UC_FORCE:
- if (git_config_get_untracked_cache() == 0)
+ if (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE)
warning(_("core.untrackedCache is set to false; "
"remove or change it, if you really want to "
"enable the untracked cache"));
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 2d8f7f0578..8a2df4459c 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -4,7 +4,7 @@
#include "builtin.h"
#include "parse-options.h"
#include "quote.h"
-#include "argv-array.h"
+#include "strvec.h"
static const char * const git_update_ref_usage[] = {
N_("git update-ref [<options>] -d <refname> [<old-val>]"),
@@ -50,7 +50,7 @@ static const char *parse_arg(const char *next, struct strbuf *arg)
* the argument. Die if C-quoting is malformed or the reference name
* is invalid.
*/
-static char *parse_refname(struct strbuf *input, const char **next)
+static char *parse_refname(const char **next)
{
struct strbuf ref = STRBUF_INIT;
@@ -95,7 +95,7 @@ static char *parse_refname(struct strbuf *input, const char **next)
* provided but cannot be converted to a SHA-1, die. flags can
* include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.
*/
-static int parse_next_oid(struct strbuf *input, const char **next,
+static int parse_next_oid(const char **next, const char *end,
struct object_id *oid,
const char *command, const char *refname,
int flags)
@@ -103,7 +103,7 @@ static int parse_next_oid(struct strbuf *input, const char **next,
struct strbuf arg = STRBUF_INIT;
int ret = 0;
- if (*next == input->buf + input->len)
+ if (*next == end)
goto eof;
if (line_termination) {
@@ -128,7 +128,7 @@ static int parse_next_oid(struct strbuf *input, const char **next,
die("%s %s: expected NUL but got: %s",
command, refname, *next);
(*next)++;
- if (*next == input->buf + input->len)
+ if (*next == end)
goto eof;
strbuf_addstr(&arg, *next);
*next += arg.len;
@@ -178,23 +178,23 @@ static int parse_next_oid(struct strbuf *input, const char **next,
* depending on how line_termination is set.
*/
-static const char *parse_cmd_update(struct ref_transaction *transaction,
- struct strbuf *input, const char *next)
+static void parse_cmd_update(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid, old_oid;
int have_old;
- refname = parse_refname(input, &next);
+ refname = parse_refname(&next);
if (!refname)
die("update: missing <ref>");
- if (parse_next_oid(input, &next, &new_oid, "update", refname,
+ if (parse_next_oid(&next, end, &new_oid, "update", refname,
PARSE_SHA1_ALLOW_EMPTY))
die("update %s: missing <newvalue>", refname);
- have_old = !parse_next_oid(input, &next, &old_oid, "update", refname,
+ have_old = !parse_next_oid(&next, end, &old_oid, "update", refname,
PARSE_SHA1_OLD);
if (*next != line_termination)
@@ -209,22 +209,20 @@ static const char *parse_cmd_update(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
-
- return next;
}
-static const char *parse_cmd_create(struct ref_transaction *transaction,
- struct strbuf *input, const char *next)
+static void parse_cmd_create(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id new_oid;
- refname = parse_refname(input, &next);
+ refname = parse_refname(&next);
if (!refname)
die("create: missing <ref>");
- if (parse_next_oid(input, &next, &new_oid, "create", refname, 0))
+ if (parse_next_oid(&next, end, &new_oid, "create", refname, 0))
die("create %s: missing <newvalue>", refname);
if (is_null_oid(&new_oid))
@@ -241,23 +239,21 @@ static const char *parse_cmd_create(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
-
- return next;
}
-static const char *parse_cmd_delete(struct ref_transaction *transaction,
- struct strbuf *input, const char *next)
+static void parse_cmd_delete(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id old_oid;
int have_old;
- refname = parse_refname(input, &next);
+ refname = parse_refname(&next);
if (!refname)
die("delete: missing <ref>");
- if (parse_next_oid(input, &next, &old_oid, "delete", refname,
+ if (parse_next_oid(&next, end, &old_oid, "delete", refname,
PARSE_SHA1_OLD)) {
have_old = 0;
} else {
@@ -277,22 +273,20 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
-
- return next;
}
-static const char *parse_cmd_verify(struct ref_transaction *transaction,
- struct strbuf *input, const char *next)
+static void parse_cmd_verify(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
struct strbuf err = STRBUF_INIT;
char *refname;
struct object_id old_oid;
- refname = parse_refname(input, &next);
+ refname = parse_refname(&next);
if (!refname)
die("verify: missing <ref>");
- if (parse_next_oid(input, &next, &old_oid, "verify", refname,
+ if (parse_next_oid(&next, end, &old_oid, "verify", refname,
PARSE_SHA1_OLD))
oidclr(&old_oid);
@@ -306,50 +300,179 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction,
update_flags = default_flags;
free(refname);
strbuf_release(&err);
-
- return next;
}
-static const char *parse_cmd_option(struct strbuf *input, const char *next)
+static void parse_cmd_option(struct ref_transaction *transaction,
+ const char *next, const char *end)
{
const char *rest;
if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination)
update_flags |= REF_NO_DEREF;
else
die("option unknown: %s", next);
- return rest;
}
-static void update_refs_stdin(struct ref_transaction *transaction)
+static void parse_cmd_start(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ if (*next != line_termination)
+ die("start: extra input: %s", next);
+ puts("start: ok");
+}
+
+static void parse_cmd_prepare(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf error = STRBUF_INIT;
+ if (*next != line_termination)
+ die("prepare: extra input: %s", next);
+ if (ref_transaction_prepare(transaction, &error))
+ die("prepare: %s", error.buf);
+ puts("prepare: ok");
+}
+
+static void parse_cmd_abort(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf error = STRBUF_INIT;
+ if (*next != line_termination)
+ die("abort: extra input: %s", next);
+ if (ref_transaction_abort(transaction, &error))
+ die("abort: %s", error.buf);
+ puts("abort: ok");
+}
+
+static void parse_cmd_commit(struct ref_transaction *transaction,
+ const char *next, const char *end)
+{
+ struct strbuf error = STRBUF_INIT;
+ if (*next != line_termination)
+ die("commit: extra input: %s", next);
+ if (ref_transaction_commit(transaction, &error))
+ die("commit: %s", error.buf);
+ puts("commit: ok");
+ ref_transaction_free(transaction);
+}
+
+enum update_refs_state {
+ /* Non-transactional state open for updates. */
+ UPDATE_REFS_OPEN,
+ /* A transaction has been started. */
+ UPDATE_REFS_STARTED,
+ /* References are locked and ready for commit */
+ UPDATE_REFS_PREPARED,
+ /* Transaction has been committed or closed. */
+ UPDATE_REFS_CLOSED,
+};
+
+static const struct parse_cmd {
+ const char *prefix;
+ void (*fn)(struct ref_transaction *, const char *, const char *);
+ unsigned args;
+ enum update_refs_state state;
+} command[] = {
+ { "update", parse_cmd_update, 3, UPDATE_REFS_OPEN },
+ { "create", parse_cmd_create, 2, UPDATE_REFS_OPEN },
+ { "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN },
+ { "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN },
+ { "option", parse_cmd_option, 1, UPDATE_REFS_OPEN },
+ { "start", parse_cmd_start, 0, UPDATE_REFS_STARTED },
+ { "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },
+ { "abort", parse_cmd_abort, 0, UPDATE_REFS_CLOSED },
+ { "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
+};
+
+static void update_refs_stdin(void)
{
- struct strbuf input = STRBUF_INIT;
- const char *next;
+ struct strbuf input = STRBUF_INIT, err = STRBUF_INIT;
+ enum update_refs_state state = UPDATE_REFS_OPEN;
+ struct ref_transaction *transaction;
+ int i, j;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction)
+ die("%s", err.buf);
- if (strbuf_read(&input, 0, 1000) < 0)
- die_errno("could not read from stdin");
- next = input.buf;
/* Read each line dispatch its command */
- while (next < input.buf + input.len) {
- if (*next == line_termination)
+ while (!strbuf_getwholeline(&input, stdin, line_termination)) {
+ const struct parse_cmd *cmd = NULL;
+
+ if (*input.buf == line_termination)
die("empty command in input");
- else if (isspace(*next))
- die("whitespace before command: %s", next);
- else if (skip_prefix(next, "update ", &next))
- next = parse_cmd_update(transaction, &input, next);
- else if (skip_prefix(next, "create ", &next))
- next = parse_cmd_create(transaction, &input, next);
- else if (skip_prefix(next, "delete ", &next))
- next = parse_cmd_delete(transaction, &input, next);
- else if (skip_prefix(next, "verify ", &next))
- next = parse_cmd_verify(transaction, &input, next);
- else if (skip_prefix(next, "option ", &next))
- next = parse_cmd_option(&input, next);
- else
- die("unknown command: %s", next);
-
- next++;
+ else if (isspace(*input.buf))
+ die("whitespace before command: %s", input.buf);
+
+ for (i = 0; i < ARRAY_SIZE(command); i++) {
+ const char *prefix = command[i].prefix;
+ char c;
+
+ if (!starts_with(input.buf, prefix))
+ continue;
+
+ /*
+ * If the command has arguments, verify that it's
+ * followed by a space. Otherwise, it shall be followed
+ * by a line terminator.
+ */
+ c = command[i].args ? ' ' : line_termination;
+ if (input.buf[strlen(prefix)] != c)
+ continue;
+
+ cmd = &command[i];
+ break;
+ }
+ if (!cmd)
+ die("unknown command: %s", input.buf);
+
+ /*
+ * Read additional arguments if NUL-terminated. Do not raise an
+ * error in case there is an early EOF to let the command
+ * handle missing arguments with a proper error message.
+ */
+ for (j = 1; line_termination == '\0' && j < cmd->args; j++)
+ if (strbuf_appendwholeline(&input, stdin, line_termination))
+ break;
+
+ switch (state) {
+ case UPDATE_REFS_OPEN:
+ case UPDATE_REFS_STARTED:
+ /* Do not downgrade a transaction to a non-transaction. */
+ if (cmd->state >= state)
+ state = cmd->state;
+ break;
+ case UPDATE_REFS_PREPARED:
+ if (cmd->state != UPDATE_REFS_CLOSED)
+ die("prepared transactions can only be closed");
+ state = cmd->state;
+ break;
+ case UPDATE_REFS_CLOSED:
+ die("transaction is closed");
+ break;
+ }
+
+ cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,
+ input.buf + input.len);
+ }
+
+ switch (state) {
+ case UPDATE_REFS_OPEN:
+ /* Commit by default if no transaction was requested. */
+ if (ref_transaction_commit(transaction, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
+ break;
+ case UPDATE_REFS_STARTED:
+ case UPDATE_REFS_PREPARED:
+ /* If using a transaction, we want to abort it. */
+ if (ref_transaction_abort(transaction, &err))
+ die("%s", err.buf);
+ break;
+ case UPDATE_REFS_CLOSED:
+ /* Otherwise no need to do anything, the transaction was closed already. */
+ break;
}
+ strbuf_release(&err);
strbuf_release(&input);
}
@@ -384,21 +507,11 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
}
if (read_stdin) {
- struct strbuf err = STRBUF_INIT;
- struct ref_transaction *transaction;
-
- transaction = ref_transaction_begin(&err);
- if (!transaction)
- die("%s", err.buf);
if (delete || argc > 0)
usage_with_options(git_update_ref_usage, options);
if (end_null)
line_termination = '\0';
- update_refs_stdin(transaction);
- if (ref_transaction_commit(transaction, &err))
- die("%s", err.buf);
- ref_transaction_free(transaction);
- strbuf_release(&err);
+ update_refs_stdin();
return 0;
}
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index 018879737a..24654b4c9b 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -7,7 +7,7 @@
#include "pkt-line.h"
#include "sideband.h"
#include "run-command.h"
-#include "argv-array.h"
+#include "strvec.h"
static const char upload_archive_usage[] =
"git upload-archive <repo>";
@@ -19,7 +19,7 @@ static const char deadchild[] =
int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
{
- struct argv_array sent_argv = ARGV_ARRAY_INIT;
+ struct strvec sent_argv = STRVEC_INIT;
const char *arg_cmd = "argument ";
if (argc != 2 || !strcmp(argv[1], "-h"))
@@ -31,21 +31,21 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
init_archivers();
/* put received options in sent_argv[] */
- argv_array_push(&sent_argv, "git-upload-archive");
+ strvec_push(&sent_argv, "git-upload-archive");
for (;;) {
char *buf = packet_read_line(0, NULL);
if (!buf)
break; /* got a flush */
- if (sent_argv.argc > MAX_ARGS)
+ if (sent_argv.nr > MAX_ARGS)
die("Too many options (>%d)", MAX_ARGS - 1);
if (!starts_with(buf, arg_cmd))
die("'argument' token or flush expected");
- argv_array_push(&sent_argv, buf + strlen(arg_cmd));
+ strvec_push(&sent_argv, buf + strlen(arg_cmd));
}
/* parse all options sent by the client */
- return write_archive(sent_argv.argc, sent_argv.argv, prefix,
+ return write_archive(sent_argv.nr, sent_argv.v, prefix,
the_repository, NULL, 1);
}
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c
index 42dc4da5a1..6da8fa2607 100644
--- a/builtin/upload-pack.c
+++ b/builtin/upload-pack.c
@@ -33,7 +33,7 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
packet_trace_identity("upload-pack");
read_replace_refs = 0;
- argc = parse_options(argc, argv, NULL, options, upload_pack_usage, 0);
+ argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0);
if (argc != 1)
usage_with_options(upload_pack_usage, options);
diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c
index 4e93914e59..40c69a0bed 100644
--- a/builtin/verify-commit.c
+++ b/builtin/verify-commit.c
@@ -20,15 +20,14 @@ static const char * const verify_commit_usage[] = {
NULL
};
-static int run_gpg_verify(const struct object_id *oid, const char *buf, unsigned long size, unsigned flags)
+static int run_gpg_verify(struct commit *commit, unsigned flags)
{
struct signature_check signature_check;
int ret;
memset(&signature_check, 0, sizeof(signature_check));
- ret = check_commit_signature(lookup_commit(the_repository, oid),
- &signature_check);
+ ret = check_commit_signature(commit, &signature_check);
print_signature_buffer(&signature_check, flags);
signature_check_clear(&signature_check);
@@ -37,26 +36,20 @@ static int run_gpg_verify(const struct object_id *oid, const char *buf, unsigned
static int verify_commit(const char *name, unsigned flags)
{
- enum object_type type;
struct object_id oid;
- char *buf;
- unsigned long size;
- int ret;
+ struct object *obj;
if (get_oid(name, &oid))
return error("commit '%s' not found.", name);
- buf = read_object_file(&oid, &type, &size);
- if (!buf)
+ obj = parse_object(the_repository, &oid);
+ if (!obj)
return error("%s: unable to read file.", name);
- if (type != OBJ_COMMIT)
+ if (obj->type != OBJ_COMMIT)
return error("%s: cannot verify a non-commit object of type %s.",
- name, type_name(type));
-
- ret = run_gpg_verify(&oid, buf, size, flags);
+ name, type_name(obj->type));
- free(buf);
- return ret;
+ return run_gpg_verify((struct commit *)obj, flags);
}
static int git_verify_commit_config(const char *var, const char *value, void *cb)
diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c
index c2a1a5c504..05c5213594 100644
--- a/builtin/verify-pack.c
+++ b/builtin/verify-pack.c
@@ -7,21 +7,26 @@
#define VERIFY_PACK_VERBOSE 01
#define VERIFY_PACK_STAT_ONLY 02
-static int verify_one_pack(const char *path, unsigned int flags)
+static int verify_one_pack(const char *path, unsigned int flags, const char *hash_algo)
{
struct child_process index_pack = CHILD_PROCESS_INIT;
- const char *argv[] = {"index-pack", NULL, NULL, NULL };
+ struct strvec *argv = &index_pack.args;
struct strbuf arg = STRBUF_INIT;
int verbose = flags & VERIFY_PACK_VERBOSE;
int stat_only = flags & VERIFY_PACK_STAT_ONLY;
int err;
+ strvec_push(argv, "index-pack");
+
if (stat_only)
- argv[1] = "--verify-stat-only";
+ strvec_push(argv, "--verify-stat-only");
else if (verbose)
- argv[1] = "--verify-stat";
+ strvec_push(argv, "--verify-stat");
else
- argv[1] = "--verify";
+ strvec_push(argv, "--verify");
+
+ if (hash_algo)
+ strvec_pushf(argv, "--object-format=%s", hash_algo);
/*
* In addition to "foo.pack" we accept "foo.idx" and "foo";
@@ -31,9 +36,8 @@ static int verify_one_pack(const char *path, unsigned int flags)
if (strbuf_strip_suffix(&arg, ".idx") ||
!ends_with(arg.buf, ".pack"))
strbuf_addstr(&arg, ".pack");
- argv[2] = arg.buf;
+ strvec_push(argv, arg.buf);
- index_pack.argv = argv;
index_pack.git_cmd = 1;
err = run_command(&index_pack);
@@ -60,12 +64,15 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
{
int err = 0;
unsigned int flags = 0;
+ const char *object_format = NULL;
int i;
const struct option verify_pack_options[] = {
OPT_BIT('v', "verbose", &flags, N_("verbose"),
VERIFY_PACK_VERBOSE),
OPT_BIT('s', "stat-only", &flags, N_("show statistics only"),
VERIFY_PACK_STAT_ONLY),
+ OPT_STRING(0, "object-format", &object_format, N_("hash"),
+ N_("specify the hash algorithm to use")),
OPT_END()
};
@@ -75,7 +82,7 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
if (argc < 1)
usage_with_options(verify_pack_usage, verify_pack_options);
for (i = 0; i < argc; i++) {
- if (verify_one_pack(argv[i], flags))
+ if (verify_one_pack(argv[i], flags, object_format))
err = 1;
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
index d2a7e2f3f1..197fd24a55 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -4,13 +4,12 @@
#include "builtin.h"
#include "dir.h"
#include "parse-options.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "branch.h"
#include "refs.h"
#include "run-command.h"
#include "sigchain.h"
#include "submodule.h"
-#include "refs.h"
#include "utf8.h"
#include "worktree.h"
@@ -68,7 +67,12 @@ static void delete_worktrees_dir_if_empty(void)
rmdir(git_path("worktrees")); /* ignore failed removal */
}
-static int prune_worktree(const char *id, struct strbuf *reason)
+/*
+ * Return true if worktree entry should be pruned, along with the reason for
+ * pruning. Otherwise, return false and the worktree's path, or NULL if it
+ * cannot be determined. Caller is responsible for freeing returned path.
+ */
+static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath)
{
struct stat st;
char *path;
@@ -76,20 +80,21 @@ static int prune_worktree(const char *id, struct strbuf *reason)
size_t len;
ssize_t read_result;
+ *wtpath = NULL;
if (!is_directory(git_path("worktrees/%s", id))) {
- strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
+ strbuf_addstr(reason, _("not a valid directory"));
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);
+ strbuf_addstr(reason, _("gitdir file does not exist"));
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));
+ strbuf_addf(reason, _("unable to read gitdir file (%s)"),
+ strerror(errno));
return 1;
}
len = xsize_t(st.st_size);
@@ -97,8 +102,8 @@ static int prune_worktree(const char *id, struct strbuf *reason)
read_result = read_in_full(fd, path, len);
if (read_result < 0) {
- strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
- id, strerror(errno));
+ strbuf_addf(reason, _("unable to read gitdir file (%s)"),
+ strerror(errno));
close(fd);
free(path);
return 1;
@@ -107,53 +112,103 @@ static int prune_worktree(const char *id, struct strbuf *reason)
if (read_result != len) {
strbuf_addf(reason,
- _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
- id, (uintmax_t)len, (uintmax_t)read_result);
+ _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
+ (uintmax_t)len, (uintmax_t)read_result);
free(path);
return 1;
}
while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
len--;
if (!len) {
- strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
+ strbuf_addstr(reason, _("invalid gitdir file"));
free(path);
return 1;
}
path[len] = '\0';
if (!file_exists(path)) {
- free(path);
if (stat(git_path("worktrees/%s/index", id), &st) ||
st.st_mtime <= expire) {
- strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
+ strbuf_addstr(reason, _("gitdir file points to non-existent location"));
+ free(path);
return 1;
} else {
+ *wtpath = path;
return 0;
}
}
- free(path);
+ *wtpath = path;
return 0;
}
+static void prune_worktree(const char *id, const char *reason)
+{
+ if (show_only || verbose)
+ printf_ln(_("Removing %s/%s: %s"), "worktrees", id, reason);
+ if (!show_only)
+ delete_git_dir(id);
+}
+
+static int prune_cmp(const void *a, const void *b)
+{
+ const struct string_list_item *x = a;
+ const struct string_list_item *y = b;
+ int c;
+
+ if ((c = fspathcmp(x->string, y->string)))
+ return c;
+ /*
+ * paths same; prune_dupes() removes all but the first worktree entry
+ * having the same path, so sort main worktree ('util' is NULL) above
+ * linked worktrees ('util' not NULL) since main worktree can't be
+ * removed
+ */
+ if (!x->util)
+ return -1;
+ if (!y->util)
+ return 1;
+ /* paths same; sort by .git/worktrees/<id> */
+ return strcmp(x->util, y->util);
+}
+
+static void prune_dups(struct string_list *l)
+{
+ int i;
+
+ QSORT(l->items, l->nr, prune_cmp);
+ for (i = 1; i < l->nr; i++) {
+ if (!fspathcmp(l->items[i].string, l->items[i - 1].string))
+ prune_worktree(l->items[i].util, "duplicate entry");
+ }
+}
+
static void prune_worktrees(void)
{
struct strbuf reason = STRBUF_INIT;
+ struct strbuf main_path = STRBUF_INIT;
+ struct string_list kept = STRING_LIST_INIT_NODUP;
DIR *dir = opendir(git_path("worktrees"));
struct dirent *d;
if (!dir)
return;
while ((d = readdir(dir)) != NULL) {
+ char *path;
if (is_dot_or_dotdot(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;
- delete_git_dir(d->d_name);
+ if (should_prune_worktree(d->d_name, &reason, &path))
+ prune_worktree(d->d_name, reason.buf);
+ else if (path)
+ string_list_append(&kept, path)->util = xstrdup(d->d_name);
}
closedir(dir);
+
+ strbuf_add_absolute_path(&main_path, get_git_common_dir());
+ /* massage main worktree absolute path to match 'gitdir' content */
+ strbuf_strip_suffix(&main_path, "/.");
+ string_list_append(&kept, strbuf_detach(&main_path, NULL));
+ prune_dups(&kept);
+ string_list_clear(&kept, 1);
+
if (!show_only)
delete_worktrees_dir_if_empty();
strbuf_release(&reason);
@@ -225,58 +280,55 @@ static const char *worktree_basename(const char *path, int *olen)
return name;
}
-static void validate_worktree_add(const char *path, const struct add_opts *opts)
+/* check that path is viable location for worktree */
+static void check_candidate_path(const char *path,
+ int force,
+ struct worktree **worktrees,
+ const char *cmd)
{
- struct worktree **worktrees;
struct worktree *wt;
int locked;
if (file_exists(path) && !is_empty_dir(path))
die(_("'%s' already exists"), path);
- worktrees = get_worktrees(0);
- /*
- * find_worktree()'s suffix matching may undesirably find the main
- * rather than a linked worktree (for instance, when the basenames
- * of the main worktree and the one being created are the same).
- * We're only interested in linked worktrees, so skip the main
- * worktree with +1.
- */
- wt = find_worktree(worktrees + 1, NULL, path);
+ wt = find_worktree_by_path(worktrees, path);
if (!wt)
- goto done;
+ return;
locked = !!worktree_lock_reason(wt);
- if ((!locked && opts->force) || (locked && opts->force > 1)) {
+ if ((!locked && force) || (locked && force > 1)) {
if (delete_git_dir(wt->id))
- die(_("unable to re-add worktree '%s'"), path);
- goto done;
+ die(_("unusable worktree destination '%s'"), path);
+ return;
}
if (locked)
- die(_("'%s' is a missing but locked worktree;\nuse 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path);
+ die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path, cmd);
else
- die(_("'%s' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear"), path);
-
-done:
- free_worktrees(worktrees);
+ die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), path, cmd);
}
static int add_worktree(const char *path, const char *refname,
const struct add_opts *opts)
{
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
- struct strbuf sb = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
const char *name;
struct child_process cp = CHILD_PROCESS_INIT;
- struct argv_array child_env = ARGV_ARRAY_INIT;
+ struct strvec child_env = STRVEC_INIT;
unsigned int counter = 0;
int len, ret;
struct strbuf symref = STRBUF_INIT;
struct commit *commit = NULL;
int is_branch = 0;
+ struct strbuf sb_name = STRBUF_INIT;
+ struct worktree **worktrees;
- validate_worktree_add(path, opts);
+ worktrees = get_worktrees();
+ check_candidate_path(path, opts->force, worktrees, "add");
+ free_worktrees(worktrees);
+ worktrees = NULL;
/* is 'refname' a branch or commit? */
if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
@@ -290,7 +342,13 @@ static int add_worktree(const char *path, const char *refname,
die(_("invalid reference: %s"), refname);
name = worktree_basename(path, &len);
- git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name);
+ strbuf_add(&sb, name, path + len - name);
+ sanitize_refname_component(sb.buf, &sb_name);
+ if (!sb_name.len)
+ BUG("How come '%s' becomes empty after sanitization?", sb.buf);
+ strbuf_reset(&sb);
+ name = sb_name.buf;
+ git_path_buf(&sb_repo, "worktrees/%s", name);
len = sb_repo.len;
if (safe_create_leading_directories_const(sb_repo.buf))
die_errno(_("could not create leading directories of '%s'"),
@@ -331,9 +389,11 @@ static int add_worktree(const char *path, const char *refname,
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
- write_file(sb.buf, "%s", real_path(sb_git.buf));
+ strbuf_realpath(&realpath, sb_git.buf, 1);
+ write_file(sb.buf, "%s", realpath.buf);
+ strbuf_realpath(&realpath, get_git_common_dir(), 1);
write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
- real_path(get_git_common_dir()), name);
+ realpath.buf, name);
/*
* This is to keep resolve_ref() happy. We need a valid HEAD
* or is_git_directory() will reject the directory. Any value which
@@ -343,37 +403,37 @@ static int add_worktree(const char *path, const char *refname,
*/
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
- write_file(sb.buf, "%s", sha1_to_hex(null_sha1));
+ write_file(sb.buf, "%s", oid_to_hex(&null_oid));
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
- argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
- argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
+ strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
+ strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
cp.git_cmd = 1;
if (!is_branch)
- argv_array_pushl(&cp.args, "update-ref", "HEAD",
- oid_to_hex(&commit->object.oid), NULL);
+ strvec_pushl(&cp.args, "update-ref", "HEAD",
+ oid_to_hex(&commit->object.oid), NULL);
else {
- argv_array_pushl(&cp.args, "symbolic-ref", "HEAD",
- symref.buf, NULL);
+ strvec_pushl(&cp.args, "symbolic-ref", "HEAD",
+ symref.buf, NULL);
if (opts->quiet)
- argv_array_push(&cp.args, "--quiet");
+ strvec_push(&cp.args, "--quiet");
}
- cp.env = child_env.argv;
+ cp.env = child_env.v;
ret = run_command(&cp);
if (ret)
goto done;
if (opts->checkout) {
cp.argv = NULL;
- argv_array_clear(&cp.args);
- argv_array_pushl(&cp.args, "reset", "--hard", NULL);
+ strvec_clear(&cp.args);
+ strvec_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL);
if (opts->quiet)
- argv_array_push(&cp.args, "--quiet");
- cp.env = child_env.argv;
+ strvec_push(&cp.args, "--quiet");
+ cp.env = child_env.v;
ret = run_command(&cp);
if (ret)
goto done;
@@ -405,19 +465,21 @@ done:
cp.env = env;
cp.argv = NULL;
cp.trace2_hook_name = "post-checkout";
- argv_array_pushl(&cp.args, absolute_path(hook),
- oid_to_hex(&null_oid),
- oid_to_hex(&commit->object.oid),
- "1", NULL);
+ strvec_pushl(&cp.args, absolute_path(hook),
+ oid_to_hex(&null_oid),
+ oid_to_hex(&commit->object.oid),
+ "1", NULL);
ret = run_command(&cp);
}
}
- argv_array_clear(&child_env);
+ strvec_clear(&child_env);
strbuf_release(&sb);
strbuf_release(&symref);
strbuf_release(&sb_repo);
strbuf_release(&sb_git);
+ strbuf_release(&sb_name);
+ strbuf_release(&realpath);
return ret;
}
@@ -493,7 +555,7 @@ static int add(int ac, const char **av, const char *prefix)
N_("create a new branch")),
OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
N_("create or reset a branch")),
- OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
+ OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")),
OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")),
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
@@ -557,15 +619,15 @@ static int add(int ac, const char **av, const char *prefix)
if (new_branch) {
struct child_process cp = CHILD_PROCESS_INIT;
cp.git_cmd = 1;
- argv_array_push(&cp.args, "branch");
+ strvec_push(&cp.args, "branch");
if (new_branch_force)
- argv_array_push(&cp.args, "--force");
+ strvec_push(&cp.args, "--force");
if (opts.quiet)
- argv_array_push(&cp.args, "--quiet");
- argv_array_push(&cp.args, new_branch);
- argv_array_push(&cp.args, branch);
+ strvec_push(&cp.args, "--quiet");
+ strvec_push(&cp.args, new_branch);
+ strvec_push(&cp.args, branch);
if (opt_track)
- argv_array_push(&cp.args, opt_track);
+ strvec_push(&cp.args, opt_track);
if (run_command(&cp))
return -1;
branch = new_branch;
@@ -614,8 +676,11 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
} else
strbuf_addstr(&sb, "(error)");
}
- printf("%s\n", sb.buf);
+ if (!is_main_worktree(wt) && worktree_lock_reason(wt))
+ strbuf_addstr(&sb, " locked");
+
+ printf("%s\n", sb.buf);
strbuf_release(&sb);
}
@@ -635,6 +700,23 @@ static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
}
}
+static int pathcmp(const void *a_, const void *b_)
+{
+ const struct worktree *const *a = a_;
+ const struct worktree *const *b = b_;
+ return fspathcmp((*a)->path, (*b)->path);
+}
+
+static void pathsort(struct worktree **wt)
+{
+ int n = 0;
+ struct worktree **p = wt;
+
+ while (*p++)
+ n++;
+ QSORT(wt, n, pathcmp);
+}
+
static int list(int ac, const char **av, const char *prefix)
{
int porcelain = 0;
@@ -648,9 +730,12 @@ static int list(int ac, const char **av, const char *prefix)
if (ac)
usage_with_options(worktree_usage, options);
else {
- struct worktree **worktrees = get_worktrees(GWT_SORT_LINKED);
+ struct worktree **worktrees = get_worktrees();
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
+ /* sort worktrees by path but keep main worktree at top */
+ pathsort(worktrees + 1);
+
if (!porcelain)
measure_widths(worktrees, &abbrev, &path_maxlen);
@@ -679,7 +764,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
if (ac != 1)
usage_with_options(worktree_usage, options);
- worktrees = get_worktrees(0);
+ worktrees = get_worktrees();
wt = find_worktree(worktrees, prefix, av[0]);
if (!wt)
die(_("'%s' is not a working tree"), av[0]);
@@ -712,7 +797,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
if (ac != 1)
usage_with_options(worktree_usage, options);
- worktrees = get_worktrees(0);
+ worktrees = get_worktrees();
wt = find_worktree(worktrees, prefix, av[0]);
if (!wt)
die(_("'%s' is not a working tree"), av[0]);
@@ -786,7 +871,7 @@ static int move_worktree(int ac, const char **av, const char *prefix)
strbuf_addstr(&dst, path);
free(path);
- worktrees = get_worktrees(0);
+ worktrees = get_worktrees();
wt = find_worktree(worktrees, prefix, av[0]);
if (!wt)
die(_("'%s' is not a working tree"), av[0]);
@@ -801,8 +886,7 @@ static int move_worktree(int ac, const char **av, const char *prefix)
strbuf_trim_trailing_dir_sep(&dst);
strbuf_addstr(&dst, sep);
}
- if (file_exists(dst.buf))
- die(_("target '%s' already exists"), dst.buf);
+ check_candidate_path(dst.buf, force, worktrees, "move");
validate_no_submodules(wt);
@@ -843,7 +927,6 @@ static int move_worktree(int ac, const char **av, const char *prefix)
static void check_clean_worktree(struct worktree *wt,
const char *original_path)
{
- struct argv_array child_env = ARGV_ARRAY_INIT;
struct child_process cp;
char buf[1];
int ret;
@@ -854,15 +937,14 @@ static void check_clean_worktree(struct worktree *wt,
*/
validate_no_submodules(wt);
- argv_array_pushf(&child_env, "%s=%s/.git",
- GIT_DIR_ENVIRONMENT, wt->path);
- argv_array_pushf(&child_env, "%s=%s",
- GIT_WORK_TREE_ENVIRONMENT, wt->path);
- memset(&cp, 0, sizeof(cp));
- argv_array_pushl(&cp.args, "status",
- "--porcelain", "--ignore-submodules=none",
- NULL);
- cp.env = child_env.argv;
+ child_process_init(&cp);
+ strvec_pushf(&cp.env_array, "%s=%s/.git",
+ GIT_DIR_ENVIRONMENT, wt->path);
+ strvec_pushf(&cp.env_array, "%s=%s",
+ GIT_WORK_TREE_ENVIRONMENT, wt->path);
+ strvec_pushl(&cp.args, "status",
+ "--porcelain", "--ignore-submodules=none",
+ NULL);
cp.git_cmd = 1;
cp.dir = wt->path;
cp.out = -1;
@@ -872,7 +954,7 @@ static void check_clean_worktree(struct worktree *wt,
original_path);
ret = xread(cp.out, buf, sizeof(buf));
if (ret)
- die(_("'%s' is dirty, use --force to delete it"),
+ die(_("'%s' contains modified or untracked files, use --force to delete it"),
original_path);
close(cp.out);
ret = finish_command(&cp);
@@ -913,7 +995,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
if (ac != 1)
usage_with_options(worktree_usage, options);
- worktrees = get_worktrees(0);
+ worktrees = get_worktrees();
wt = find_worktree(worktrees, prefix, av[0]);
if (!wt)
die(_("'%s' is not a working tree"), av[0]);
@@ -949,6 +1031,34 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
return ret;
}
+static void report_repair(int iserr, const char *path, const char *msg, void *cb_data)
+{
+ if (!iserr) {
+ printf_ln(_("repair: %s: %s"), msg, path);
+ } else {
+ int *exit_status = (int *)cb_data;
+ fprintf_ln(stderr, _("error: %s: %s"), msg, path);
+ *exit_status = 1;
+ }
+}
+
+static int repair(int ac, const char **av, const char *prefix)
+{
+ const char **p;
+ const char *self[] = { ".", NULL };
+ struct option options[] = {
+ OPT_END()
+ };
+ int rc = 0;
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ repair_worktrees(report_repair, &rc);
+ p = ac > 0 ? av : self;
+ for (; *p; p++)
+ repair_worktree_at_path(*p, report_repair, &rc);
+ return rc;
+}
+
int cmd_worktree(int ac, const char **av, const char *prefix)
{
struct option options[] = {
@@ -975,5 +1085,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
return move_worktree(ac - 1, av + 1, prefix);
if (!strcmp(av[1], "remove"))
return remove_worktree(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "repair"))
+ return repair(ac - 1, av + 1, prefix);
usage_with_options(worktree_usage, options);
}
diff --git a/builtin/write-tree.c b/builtin/write-tree.c
index 3d46d22ee5..45d61707e7 100644
--- a/builtin/write-tree.c
+++ b/builtin/write-tree.c
@@ -16,16 +16,16 @@ static const char * const write_tree_usage[] = {
NULL
};
-int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
+int cmd_write_tree(int argc, const char **argv, const char *cmd_prefix)
{
int flags = 0, ret;
- const char *prefix = NULL;
+ const char *tree_prefix = NULL;
struct object_id oid;
const char *me = "git-write-tree";
struct option write_tree_options[] = {
OPT_BIT(0, "missing-ok", &flags, N_("allow missing objects"),
WRITE_TREE_MISSING_OK),
- OPT_STRING(0, "prefix", &prefix, N_("<prefix>/"),
+ OPT_STRING(0, "prefix", &tree_prefix, N_("<prefix>/"),
N_("write tree object for a subdirectory <prefix>")),
{ OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL,
N_("only useful for debugging"),
@@ -35,10 +35,10 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
};
git_config(git_default_config, NULL);
- argc = parse_options(argc, argv, unused_prefix, write_tree_options,
+ argc = parse_options(argc, argv, cmd_prefix, write_tree_options,
write_tree_usage, 0);
- ret = write_cache_as_tree(&oid, flags, prefix);
+ ret = write_cache_as_tree(&oid, flags, tree_prefix);
switch (ret) {
case 0:
printf("%s\n", oid_to_hex(&oid));
@@ -50,7 +50,7 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
die("%s: error building trees", me);
break;
case WRITE_TREE_PREFIX_ERROR:
- die("%s: prefix %s not found", me, prefix);
+ die("%s: prefix %s not found", me, tree_prefix);
break;
}
return ret;