summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/add.c257
-rw-r--r--builtin/am.c281
-rw-r--r--builtin/annotate.c10
-rw-r--r--builtin/bisect--helper.c793
-rw-r--r--builtin/blame.c198
-rw-r--r--builtin/branch.c105
-rw-r--r--builtin/bugreport.c197
-rw-r--r--builtin/bundle.c250
-rw-r--r--builtin/cat-file.c37
-rw-r--r--builtin/check-ignore.c46
-rw-r--r--builtin/check-mailmap.c2
-rw-r--r--builtin/checkout--worker.c145
-rw-r--r--builtin/checkout-index.c78
-rw-r--r--builtin/checkout.c1173
-rw-r--r--builtin/clean.c146
-rw-r--r--builtin/clone.c375
-rw-r--r--builtin/column.c10
-rw-r--r--builtin/commit-graph.c293
-rw-r--r--builtin/commit-tree.c12
-rw-r--r--builtin/commit.c395
-rw-r--r--builtin/config.c164
-rw-r--r--builtin/credential-cache--daemon.c319
-rw-r--r--builtin/credential-cache.c157
-rw-r--r--builtin/credential-store.c199
-rw-r--r--builtin/credential.c3
-rw-r--r--builtin/describe.c109
-rw-r--r--builtin/diff-files.c15
-rw-r--r--builtin/diff-index.c23
-rw-r--r--builtin/diff-tree.c31
-rw-r--r--builtin/diff.c213
-rw-r--r--builtin/difftool.c104
-rw-r--r--builtin/env--helper.c100
-rw-r--r--builtin/fast-export.c334
-rw-r--r--builtin/fast-import.c3636
-rw-r--r--builtin/fetch-pack.c26
-rw-r--r--builtin/fetch.c866
-rw-r--r--builtin/fmt-merge-msg.c656
-rw-r--r--builtin/for-each-ref.c22
-rw-r--r--builtin/for-each-repo.c59
-rw-r--r--builtin/fsck.c178
-rw-r--r--builtin/gc.c1437
-rw-r--r--builtin/grep.c221
-rw-r--r--builtin/hash-object.c2
-rw-r--r--builtin/help.c107
-rw-r--r--builtin/index-pack.c625
-rw-r--r--builtin/init-db.c173
-rw-r--r--builtin/interpret-trailers.c4
-rw-r--r--builtin/log.c596
-rw-r--r--builtin/ls-files.c208
-rw-r--r--builtin/ls-remote.c23
-rw-r--r--builtin/ls-tree.c6
-rw-r--r--builtin/mailinfo.c115
-rw-r--r--builtin/merge-base.c12
-rw-r--r--builtin/merge-index.c5
-rw-r--r--builtin/merge-ours.c4
-rw-r--r--builtin/merge-recursive.c4
-rw-r--r--builtin/merge-tree.c33
-rw-r--r--builtin/merge.c256
-rw-r--r--builtin/mktag.c234
-rw-r--r--builtin/mktree.c6
-rw-r--r--builtin/multi-pack-index.c172
-rw-r--r--builtin/mv.c12
-rw-r--r--builtin/name-rev.c309
-rw-r--r--builtin/notes.c60
-rw-r--r--builtin/pack-objects.c1169
-rw-r--r--builtin/pack-redundant.c55
-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.c391
-rw-r--r--builtin/push.c267
-rw-r--r--builtin/range-diff.c39
-rw-r--r--builtin/read-tree.c18
-rw-r--r--builtin/rebase.c950
-rw-r--r--builtin/receive-pack.c802
-rw-r--r--builtin/reflog.c16
-rw-r--r--builtin/remote-ext.c4
-rw-r--r--builtin/remote.c296
-rw-r--r--builtin/repack.c496
-rw-r--r--builtin/replace.c30
-rw-r--r--builtin/rerere.c4
-rw-r--r--builtin/reset.c53
-rw-r--r--builtin/rev-list.c261
-rw-r--r--builtin/rev-parse.c249
-rw-r--r--builtin/revert.c20
-rw-r--r--builtin/rm.c75
-rw-r--r--builtin/send-pack.c43
-rw-r--r--builtin/shortlog.c186
-rw-r--r--builtin/show-branch.c36
-rw-r--r--builtin/show-index.c44
-rw-r--r--builtin/show-ref.c14
-rw-r--r--builtin/sparse-checkout.c678
-rw-r--r--builtin/stash.c633
-rw-r--r--builtin/submodule--helper.c1165
-rw-r--r--builtin/symbolic-ref.c4
-rw-r--r--builtin/tag.c122
-rw-r--r--builtin/unpack-objects.c22
-rw-r--r--builtin/update-index.c32
-rw-r--r--builtin/update-ref.c260
-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.c383
-rw-r--r--builtin/write-tree.c12
105 files changed, 19292 insertions, 6319 deletions
diff --git a/builtin/add.c b/builtin/add.c
index dd18e5c9b6..17528e8f92 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,25 +29,39 @@ 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;
int add_errors;
};
-static void chmod_pathspec(struct pathspec *pathspec, char flip)
+static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only)
{
- int i;
+ int i, ret = 0;
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
+ int err;
+
+ if (ce_skip_worktree(ce))
+ continue;
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
continue;
- if (chmod_cache_entry(ce, flip) < 0)
- fprintf(stderr, "cannot chmod %cx '%s'\n", flip, ce->name);
+ if (!show_only)
+ err = chmod_cache_entry(ce, flip);
+ else
+ err = S_ISREG(ce->ce_mode) ? 0 : -1;
+
+ if (err < 0)
+ ret = error(_("cannot chmod %cx '%s'"), flip, ce->name);
}
+
+ return ret;
}
static int fix_unmerged_status(struct diff_filepair *p,
@@ -132,6 +147,8 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
+ if (ce_skip_worktree(ce))
+ continue;
if (ce_stage(ce))
continue; /* do not touch unmerged paths */
if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode))
@@ -160,48 +177,110 @@ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
*dst++ = entry;
}
dir->nr = dst - dir->entries;
- add_pathspec_matches_against_index(pathspec, &the_index, seen);
+ add_pathspec_matches_against_index(pathspec, &the_index, seen,
+ PS_IGNORE_SKIP_WORKTREE);
return seen;
}
-static void refresh(int verbose, const struct pathspec *pathspec)
+static int refresh(int verbose, const struct pathspec *pathspec)
{
char *seen;
- int i;
+ int i, ret = 0;
+ char *skip_worktree_seen = NULL;
+ struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
+ int flags = REFRESH_IGNORE_SKIP_WORKTREE |
+ (verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET);
+ struct pattern_list pl = { 0 };
+ int sparse_checkout_enabled = !get_sparse_checkout_patterns(&pl);
seen = xcalloc(pathspec->nr, 1);
- refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
- pathspec, seen, _("Unstaged changes after refreshing the index:"));
+ refresh_index(&the_index, flags, pathspec, seen,
+ _("Unstaged changes after refreshing the index:"));
for (i = 0; i < pathspec->nr; i++) {
- if (!seen[i])
- die(_("pathspec '%s' did not match any files"),
- pathspec->items[i].match);
+ if (!seen[i]) {
+ const char *path = pathspec->items[i].original;
+ int dtype = DT_REG;
+
+ if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) ||
+ (sparse_checkout_enabled &&
+ !path_matches_pattern_list(path, strlen(path), NULL,
+ &dtype, &pl, &the_index))) {
+ string_list_append(&only_match_skip_worktree,
+ pathspec->items[i].original);
+ } else {
+ die(_("pathspec '%s' did not match any files"),
+ pathspec->items[i].original);
+ }
+ }
}
+
+ if (only_match_skip_worktree.nr) {
+ advise_on_updating_sparse_paths(&only_match_skip_worktree);
+ ret = 1;
+ }
+
free(seen);
+ free(skip_worktree_seen);
+ string_list_clear(&only_match_skip_worktree, 0);
+ return ret;
}
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;
+ }
- argv_array_push(&argv, "add--interactive");
+ 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);
+ }
+
+ 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 +377,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 +388,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 +402,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 +453,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;
}
@@ -389,7 +476,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
{
int exit_status = 0;
struct pathspec pathspec;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
int flags;
int add_new_files;
int require_pathspec;
@@ -402,11 +489,30 @@ 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 (show_only)
+ die(_("--dry-run is incompatible with --interactive/--patch"));
+ 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 +524,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"));
@@ -432,20 +534,10 @@ int cmd_add(int argc, const char **argv, const char *prefix)
add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
- 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));
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 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;
- }
+ hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
/*
* Check the "pathspec '%s' did not match any files" block
@@ -456,6 +548,38 @@ 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"));
@@ -466,7 +590,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
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);
@@ -479,15 +602,18 @@ int cmd_add(int argc, const char **argv, const char *prefix)
}
if (refresh_only) {
- refresh(verbose, &pathspec);
+ exit_status |= refresh(verbose, &pathspec);
goto finish;
}
if (pathspec.nr) {
int i;
+ char *skip_worktree_seen = NULL;
+ struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
if (!seen)
- seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
+ seen = find_pathspecs_matching_against_index(&pathspec,
+ &the_index, PS_IGNORE_SKIP_WORKTREE);
/*
* file_exists() assumes exact match
@@ -501,12 +627,24 @@ int cmd_add(int argc, const char **argv, const char *prefix)
for (i = 0; i < pathspec.nr; i++) {
const char *path = pathspec.items[i].match;
+
if (pathspec.items[i].magic & PATHSPEC_EXCLUDE)
continue;
- if (!seen[i] && path[0] &&
- ((pathspec.items[i].magic &
- (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
- !file_exists(path))) {
+ if (seen[i])
+ continue;
+
+ if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
+ string_list_append(&only_match_skip_worktree,
+ pathspec.items[i].original);
+ continue;
+ }
+
+ /* Don't complain at 'git add .' on empty repo */
+ if (!path[0])
+ continue;
+
+ if ((pathspec.items[i].magic & (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
+ !file_exists(path)) {
if (ignore_missing) {
int dtype = DT_UNKNOWN;
if (is_excluded(&dir, &the_index, path, &dtype))
@@ -517,7 +655,16 @@ int cmd_add(int argc, const char **argv, const char *prefix)
pathspec.items[i].original);
}
}
+
+
+ if (only_match_skip_worktree.nr) {
+ advise_on_updating_sparse_paths(&only_match_skip_worktree);
+ exit_status = 1;
+ }
+
free(seen);
+ free(skip_worktree_seen);
+ string_list_clear(&only_match_skip_worktree, 0);
}
plug_bulk_checkin();
@@ -531,7 +678,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
exit_status |= add_files(&dir, flags);
if (chmod_arg && pathspec.nr)
- chmod_pathspec(&pathspec, chmod_arg[0]);
+ exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only);
unplug_bulk_checkin();
finish:
@@ -539,7 +686,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..0c2ad96b70 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,8 @@ struct am_state {
int keep; /* enum keep_type */
int message_id;
int scissors; /* enum scissors_type */
- struct argv_array git_apply_opts;
+ int quoted_cr; /* enum quoted_cr_action */
+ struct strvec git_apply_opts;
const char *resolvemsg;
int committer_date_is_author_date;
int ignore_date;
@@ -141,8 +146,9 @@ static void am_state_init(struct am_state *state)
git_config_get_bool("am.messageid", &state->message_id);
state->scissors = SCISSORS_UNSET;
+ state->quoted_cr = quoted_cr_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 +164,17 @@ 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);
+}
+
+static int am_option_parse_quoted_cr(const struct option *opt,
+ const char *arg, int unset)
+{
+ BUG_ON_OPT_NEG(unset);
+
+ if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)
+ return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr");
+ return 0;
}
/**
@@ -194,6 +210,7 @@ static void write_state_bool(const struct am_state *state,
* If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline
* at the end.
*/
+__attribute__((format (printf, 3, 4)))
static void say(const struct am_state *state, FILE *fp, const char *fmt, ...)
{
va_list ap;
@@ -393,9 +410,15 @@ static void am_load(struct am_state *state)
else
state->scissors = SCISSORS_UNSET;
+ read_state_file(&sb, state, "quoted-cr", 1);
+ if (!*sb.buf)
+ state->quoted_cr = quoted_cr_unset;
+ else if (mailinfo_parse_quoted_cr_action(sb.buf, &state->quoted_cr) != 0)
+ die(_("could not parse %s"), am_path(state, "quoted-cr"));
+
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 +471,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 +670,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 +806,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 +824,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 +1021,25 @@ 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);
+ switch (state->quoted_cr) {
+ case quoted_cr_unset:
+ str = "";
+ break;
+ case quoted_cr_nowarn:
+ str = "nowarn";
+ break;
+ case quoted_cr_warn:
+ str = "warn";
+ break;
+ case quoted_cr_strip:
+ str = "strip";
+ break;
+ default:
+ BUG("invalid value for state->quoted_cr");
+ }
+ write_state_text(state, "quoted-cr", str);
+
+ sq_quote_argv(&sb, state->git_apply_opts.v);
write_state_text(state, "apply-opt", sb.buf);
if (state->rebasing)
@@ -1072,19 +1113,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.
*/
@@ -1171,6 +1199,18 @@ static int parse_mail(struct am_state *state, const char *mail)
BUG("invalid value for state->scissors");
}
+ switch (state->quoted_cr) {
+ case quoted_cr_unset:
+ break;
+ case quoted_cr_nowarn:
+ case quoted_cr_warn:
+ case quoted_cr_strip:
+ mi.quoted_cr = state->quoted_cr;
+ break;
+ default:
+ BUG("invalid value for state->quoted_cr");
+ }
+
mi.input = xfopen(mail, "r");
mi.output = xfopen(am_path(state, "info"), "w");
if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch")))
@@ -1272,7 +1312,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 +1439,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 +1449,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 +1475,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 +1503,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 +1580,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 +1605,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 +1629,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 +1730,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 +1745,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 +1821,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 +1849,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 +2003,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 +2119,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 +2136,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 +2186,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 +2196,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 +2254,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 +2273,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)")),
@@ -2193,6 +2291,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
0, PARSE_OPT_NONEG),
OPT_BOOL('c', "scissors", &state.scissors,
N_("strip everything before a scissors line")),
+ OPT_CALLBACK_F(0, "quoted-cr", &state.quoted_cr, N_("action"),
+ N_("pass it through git-mailinfo"),
+ PARSE_OPT_NONEG, am_option_parse_quoted_cr),
OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
N_("pass it through git-apply"),
0),
@@ -2225,24 +2326,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,
- N_("restore the original branch and abort the patching operation."),
+ OPT_CMDMODE(0, "abort", &resume.mode,
+ N_("restore the original branch and abort the patching operation"),
RESUME_ABORT),
- OPT_CMDMODE(0, "quit", &resume,
- N_("abort the patching operation but keep HEAD where it is."),
+ 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 +2395,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 +2414,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 +2425,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 +2464,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..f184eaeac6 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -4,34 +4,40 @@
#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-state (bad|new) [<rev>]"),
+ N_("git bisect--helper --bisect-state (good|old) [<rev>...]"),
+ N_("git bisect--helper --bisect-replay <filename>"),
+ N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"),
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 +58,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 +82,67 @@ 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);
+}
+
+__attribute__((format (printf, 2, 3)))
+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;
+}
+
+__attribute__((format (printf, 2, 3)))
+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 +173,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 +181,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 = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good);
- res = fprintf(fp, "%s\n%s\n", bad, good);
- res |= fclose(fp);
- return (res < 0) ? -1 : 0;
-}
-
-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 +204,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 +246,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 +284,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 +322,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 +349,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 +357,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 +439,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 +447,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 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_start(struct bisect_terms *terms, int no_checkout,
- const char **argv, int argc)
+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 +631,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 +648,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 +705,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 +742,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,66 +771,305 @@ 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++) {
+ struct commit *commit;
+
+ if (get_oid(*argv, &oid)){
+ error(_("Bad rev input: %s"), *argv);
+ oid_array_clear(&revs);
+ return BISECT_FAILED;
+ }
+
+ commit = lookup_commit_reference(the_repository, &oid);
+ if (!commit)
+ die(_("Bad rev input (not a commit): %s"), *argv);
+
+ oid_array_append(&revs, &commit->object.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);
+}
+
+static enum bisect_error bisect_log(void)
+{
+ int fd, status;
+ const char* filename = git_path_bisect_log();
+
+ if (is_empty_or_missing_file(filename))
+ return error(_("We are not bisecting."));
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return BISECT_FAILED;
+
+ status = copy_fd(fd, STDOUT_FILENO);
+ close(fd);
+ return status ? BISECT_FAILED : BISECT_OK;
+}
+
+static int process_replay_line(struct bisect_terms *terms, struct strbuf *line)
+{
+ const char *p = line->buf + strspn(line->buf, " \t");
+ char *word_end, *rev;
+
+ if ((!skip_prefix(p, "git bisect", &p) &&
+ !skip_prefix(p, "git-bisect", &p)) || !isspace(*p))
+ return 0;
+ p += strspn(p, " \t");
+
+ word_end = (char *)p + strcspn(p, " \t");
+ rev = word_end + strspn(word_end, " \t");
+ *word_end = '\0'; /* NUL-terminate the word */
+
+ get_terms(terms);
+ if (check_and_set_terms(terms, p))
+ return -1;
+
+ if (!strcmp(p, "start")) {
+ struct strvec argv = STRVEC_INIT;
+ int res;
+ sq_dequote_to_strvec(rev, &argv);
+ res = bisect_start(terms, argv.v, argv.nr);
+ strvec_clear(&argv);
+ return res;
+ }
+
+ if (one_of(p, terms->term_good,
+ terms->term_bad, "skip", NULL))
+ return bisect_write(p, rev, terms, 0);
+
+ if (!strcmp(p, "terms")) {
+ struct strvec argv = STRVEC_INIT;
+ int res;
+ sq_dequote_to_strvec(rev, &argv);
+ res = bisect_terms(terms, argv.nr == 1 ? argv.v[0] : NULL);
+ strvec_clear(&argv);
+ return res;
+ }
+ error(_("'%s'?? what are you talking about?"), p);
+
+ return -1;
+}
+
+static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *filename)
+{
+ FILE *fp = NULL;
+ enum bisect_error res = BISECT_OK;
+ struct strbuf line = STRBUF_INIT;
+
+ if (is_empty_or_missing_file(filename))
+ return error(_("cannot read file '%s' for replaying"), filename);
+
+ if (bisect_reset(NULL))
+ return BISECT_FAILED;
+
+ fp = fopen(filename, "r");
+ if (!fp)
+ return BISECT_FAILED;
+
+ while ((strbuf_getline(&line, fp) != EOF) && !res)
+ res = process_replay_line(terms, &line);
+
+ strbuf_release(&line);
+ fclose(fp);
+
+ if (res)
+ return BISECT_FAILED;
+
+ return bisect_auto_next(terms, NULL);
+}
+
+static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **argv, int argc)
+{
+ int i;
+ enum bisect_error res;
+ struct strvec argv_state = STRVEC_INIT;
+
+ strvec_push(&argv_state, "skip");
+
+ for (i = 0; i < argc; i++) {
+ const char *dotdot = strstr(argv[i], "..");
+
+ if (dotdot) {
+ struct rev_info revs;
+ struct commit *commit;
+
+ init_revisions(&revs, NULL);
+ setup_revisions(2, argv + i - 1, &revs, NULL);
+
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed\n"));
+ while ((commit = get_revision(&revs)) != NULL)
+ strvec_push(&argv_state,
+ oid_to_hex(&commit->object.oid));
+
+ reset_revision_walk();
+ } else {
+ strvec_push(&argv_state, argv[i]);
+ }
+ }
+ res = bisect_state(terms, argv_state.v, argv_state.nr);
+
+ strvec_clear(&argv_state);
+ return res;
}
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_WRITE,
- CHECK_AND_SET_TERMS,
+ BISECT_RESET = 1,
BISECT_NEXT_CHECK,
BISECT_TERMS,
- BISECT_START
+ BISECT_START,
+ BISECT_AUTOSTART,
+ BISECT_NEXT,
+ BISECT_STATE,
+ BISECT_LOG,
+ BISECT_REPLAY,
+ BISECT_SKIP
} 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,
- N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE),
- OPT_CMDMODE(0, "check-and-set-terms", &cmdmode,
- N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS),
OPT_CMDMODE(0, "bisect-next-check", &cmdmode,
N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK),
OPT_CMDMODE(0, "bisect-terms", &cmdmode,
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-state", &cmdmode,
+ N_("mark the state of ref (or refs)"), BISECT_STATE),
+ OPT_CMDMODE(0, "bisect-log", &cmdmode,
+ N_("list the bisection steps so far"), BISECT_LOG),
+ OPT_CMDMODE(0, "bisect-replay", &cmdmode,
+ N_("replay the bisection process from the given file"), BISECT_REPLAY),
+ OPT_CMDMODE(0, "bisect-skip", &cmdmode,
+ N_("skip some commits for checkout"), BISECT_SKIP),
OPT_BOOL(0, "no-log", &nolog,
N_("no log for BISECT_WRITE")),
OPT_END()
@@ -662,34 +1084,10 @@ 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"));
- return !!bisect_reset(argc ? argv[0] : NULL);
- case BISECT_WRITE:
- if (argc != 4 && argc != 5)
- return error(_("--bisect-write requires either 4 or 5 arguments"));
- set_terms(&terms, argv[3], argv[2]);
- res = bisect_write(argv[0], argv[1], &terms, nolog);
- break;
- case CHECK_AND_SET_TERMS:
- if (argc != 3)
- return error(_("--check-and-set-terms requires 3 arguments"));
- set_terms(&terms, argv[2], argv[1]);
- res = check_and_set_terms(&terms, argv[0]);
+ res = bisect_reset(argc ? argv[0] : NULL);
break;
case BISECT_NEXT_CHECK:
if (argc != 2 && argc != 3)
@@ -704,11 +1102,46 @@ 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_STATE:
+ set_terms(&terms, "bad", "good");
+ get_terms(&terms);
+ res = bisect_state(&terms, argv, argc);
+ break;
+ case BISECT_LOG:
+ if (argc)
+ return error(_("--bisect-log requires 0 arguments"));
+ res = bisect_log();
+ break;
+ case BISECT_REPLAY:
+ if (argc != 1)
+ return error(_("no logfile given"));
+ set_terms(&terms, "bad", "good");
+ res = bisect_replay(&terms, argv[0]);
+ break;
+ case BISECT_SKIP:
+ set_terms(&terms, "bad", "good");
+ get_terms(&terms);
+ res = bisect_skip(&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..641523ff9a 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)
{
@@ -422,13 +425,11 @@ static void setup_default_color_by_age(void)
parse_color_fields("blue,12 month ago,white,1 month ago,red");
}
-static void determine_line_heat(struct blame_entry *ent, const char **dest_color)
+static void determine_line_heat(struct commit_info *ci, const char **dest_color)
{
int i = 0;
- struct commit_info ci;
- get_commit_info(ent->suspect->commit, &ci, 1);
- while (i < colorfield_nr && ci.author_time > colorfield[i].hop)
+ while (i < colorfield_nr && ci->author_time > colorfield[i].hop)
i++;
*dest_color = colorfield[i].col;
@@ -450,14 +451,14 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
cp = blame_nth_line(sb, ent->lno);
if (opt & OUTPUT_SHOW_AGE_WITH_COLOR) {
- determine_line_heat(ent, &default_color);
+ determine_line_heat(&ci, &default_color);
color = default_color;
reset = GIT_COLOR_RESET;
}
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 +481,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 +705,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 +802,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,43 +858,39 @@ 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(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")),
- OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE),
- OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
- OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
- OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN),
- OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
- OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT),
- OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP),
- OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME),
- 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_BOOL(0, "incremental", &incremental, N_("show blame entries as we find them, incrementally")),
+ 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")),
+ OPT_BIT(0, "score-debug", &output_option, N_("show output score for blame entries"), OUTPUT_SHOW_SCORE),
+ OPT_BIT('f', "show-name", &output_option, N_("show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
+ OPT_BIT('n', "show-number", &output_option, N_("show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
+ OPT_BIT('p', "porcelain", &output_option, N_("show in a format designed for machine consumption"), OUTPUT_PORCELAIN),
+ OPT_BIT(0, "line-porcelain", &output_option, N_("show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
+ OPT_BIT('c', NULL, &output_option, N_("use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT),
+ OPT_BIT('t', NULL, &output_option, N_("show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP),
+ OPT_BIT('l', NULL, &output_option, N_("show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME),
+ 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_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")),
+ 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 +900,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 +947,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 +1081,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 +1111,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,21 +1136,20 @@ 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;
sb.xdl_opts = xdl_opts;
sb.no_whole_file_rename = no_whole_file_rename;
- read_mailmap(&mailmap, NULL);
+ read_mailmap(&mailmap);
sb.found_guilty_entry = &found_guilty_entry;
sb.found_guilty_entry_data = &pi;
@@ -1116,5 +1196,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..b23b1d1752 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;
@@ -199,6 +202,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
int remote_branch = 0;
struct strbuf bname = STRBUF_INIT;
unsigned allowed_interpret;
+ struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ int branch_name_pos;
switch (kinds) {
case FILTER_REFS_REMOTES:
@@ -216,6 +222,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
default:
die(_("cannot use -a with -d"));
}
+ branch_name_pos = strcspn(fmt, "%");
if (!force) {
head_rev = lookup_commit_reference(the_repository, &head_oid);
@@ -262,30 +269,35 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
goto next;
}
- if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid,
- REF_NO_DEREF)) {
- error(remote_branch
- ? _("Error deleting remote-tracking branch '%s'")
- : _("Error deleting branch '%s'"),
- bname.buf);
- ret = 1;
- goto next;
- }
- if (!quiet) {
- printf(remote_branch
- ? _("Deleted remote-tracking branch %s (was %s).\n")
- : _("Deleted branch %s (was %s).\n"),
- bname.buf,
- (flags & REF_ISBROKEN) ? "broken"
- : (flags & REF_ISSYMREF) ? target
- : find_unique_abbrev(&oid, DEFAULT_ABBREV));
- }
- delete_branch_config(bname.buf);
+ item = string_list_append(&refs_to_delete, name);
+ item->util = xstrdup((flags & REF_ISBROKEN) ? "broken"
+ : (flags & REF_ISSYMREF) ? target
+ : find_unique_abbrev(&oid, DEFAULT_ABBREV));
next:
free(target);
}
+ if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
+ ret = 1;
+
+ for_each_string_list_item(item, &refs_to_delete) {
+ char *describe_ref = item->util;
+ char *name = item->string;
+ if (!ref_exists(name)) {
+ char *refname = name + branch_name_pos;
+ if (!quiet)
+ printf(remote_branch
+ ? _("Deleted remote-tracking branch %s (was %s).\n")
+ : _("Deleted branch %s (was %s).\n"),
+ name + branch_name_pos, describe_ref);
+
+ delete_branch_config(refname);
+ }
+ free(describe_ref);
+ }
+ string_list_clear(&refs_to_delete, 0);
+
free(name);
strbuf_release(&bname);
@@ -342,9 +354,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 +376,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)");
@@ -394,6 +411,8 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
{
int i;
struct ref_array array;
+ struct strbuf out = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
int maxwidth = 0;
const char *remote_prefix = "";
char *to_free = NULL;
@@ -423,8 +442,8 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
ref_array_sort(sorting, &array);
for (i = 0; i < array.nr; i++) {
- struct strbuf out = STRBUF_INIT;
- struct strbuf err = STRBUF_INIT;
+ strbuf_reset(&err);
+ strbuf_reset(&out);
if (format_ref_array_item(array.items[i], format, &out, &err))
die("%s", err.buf);
if (column_active(colopts)) {
@@ -435,10 +454,10 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
fwrite(out.buf, 1, out.len, stdout);
putchar('\n');
}
- strbuf_release(&err);
- strbuf_release(&out);
}
+ strbuf_release(&err);
+ strbuf_release(&out);
ref_array_clear(&array);
free(to_free);
}
@@ -460,7 +479,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++) {
@@ -530,7 +549,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
oldref.buf, newref.buf);
- if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf))
+ if (!copy &&
+ (!head || strcmp(oldname, head) || !is_null_oid(&head_oid)) &&
+ rename_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch rename failed"));
if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch copy failed"));
@@ -616,7 +637,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 +666,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 +701,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)
@@ -718,7 +737,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
print_current_branch_name();
return 0;
} else if (list) {
- /* git branch --local also shows HEAD when it is detached */
+ /* git branch --list also shows HEAD when it is detached */
if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
filter.kind |= FILTER_REFS_DETACHED_HEAD;
filter.name_patterns = argv;
@@ -731,7 +750,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
*/
if (!sorting)
sorting = ref_default_sorting();
- sorting->ignore_case = icase;
+ ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
+ ref_sorting_set_sort_flags_all(
+ sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1);
print_ref_list(&filter, sorting, &format);
print_columns(&output, colopts, NULL);
string_list_clear(&output, 0);
@@ -823,10 +844,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
die(_("Branch '%s' has no upstream information"), branch->name);
strbuf_addf(&buf, "branch.%s.remote", branch->name);
- git_config_set_multivar(buf.buf, NULL, NULL, 1);
+ git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.merge", branch->name);
- git_config_set_multivar(buf.buf, NULL, NULL, 1);
+ git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_release(&buf);
} else if (argc > 0 && argc <= 2) {
if (filter.kind != FILTER_REFS_BRANCHES)
diff --git a/builtin/bugreport.c b/builtin/bugreport.c
new file mode 100644
index 0000000000..9915a5841d
--- /dev/null
+++ b/builtin/bugreport.c
@@ -0,0 +1,197 @@
+#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);
+ struct tm tm;
+ char *option_output = NULL;
+ char *option_suffix = "%Y-%m-%d-%H%M";
+ const char *user_relative_path = NULL;
+ char *prefixed_filename;
+
+ 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 */
+ prefixed_filename = prefix_filename(prefix,
+ option_output ? option_output : "");
+ strbuf_addstr(&report_path, prefixed_filename);
+ strbuf_complete(&report_path, '/');
+
+ strbuf_addstr(&report_path, "git-bugreport-");
+ strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 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);
+
+ free(prefixed_filename);
+ 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..053a51bea1 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,207 @@
* 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)
-{
- struct bundle_header header;
- const char *cmd, *bundle_file;
+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[],
+ 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;
+ int ret;
+ 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()
+ };
+ 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."));
+ ret = !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version);
+ free(bundle_file);
+ return ret;
+}
+
+static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) {
+ struct bundle_header header = BUNDLE_HEADER_INIT;
int bundle_fd = -1;
+ int quiet = 0;
+ int ret;
+ struct option options[] = {
+ OPT_BOOL('q', "quiet", &quiet,
+ N_("do not show bundle details")),
+ OPT_END()
+ };
+ char *bundle_file;
+
+ argc = parse_options_cmd_bundle(argc, argv, prefix,
+ builtin_bundle_verify_usage, options, &bundle_file);
+ /* bundle internals use argv[1] as further parameters */
- if (argc < 3)
- usage(builtin_bundle_usage);
-
- cmd = argv[1];
- bundle_file = prefix_filename(prefix, argv[2]);
- argc -= 2;
- argv += 2;
-
- memset(&header, 0, sizeof(header));
- if (strcmp(cmd, "create") && (bundle_fd =
- read_bundle_header(bundle_file, &header)) < 0)
- return 1;
-
- 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 ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) {
+ ret = 1;
+ goto cleanup;
}
- if (!strcmp(cmd, "list-heads")) {
- close(bundle_fd);
- return !!list_bundle_refs(&header, argc, argv);
+ close(bundle_fd);
+ if (verify_bundle(the_repository, &header, !quiet)) {
+ ret = 1;
+ goto cleanup;
+ }
+
+ fprintf(stderr, _("%s is okay\n"), bundle_file);
+ ret = 0;
+cleanup:
+ free(bundle_file);
+ bundle_header_release(&header);
+ return ret;
+}
+
+static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix) {
+ struct bundle_header header = BUNDLE_HEADER_INIT;
+ int bundle_fd = -1;
+ int ret;
+ struct option options[] = {
+ OPT_END()
+ };
+ 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 */
+
+ if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) {
+ ret = 1;
+ goto cleanup;
+ }
+ close(bundle_fd);
+ ret = !!list_bundle_refs(&header, argc, argv);
+cleanup:
+ free(bundle_file);
+ bundle_header_release(&header);
+ return ret;
+}
+
+static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) {
+ struct bundle_header header = BUNDLE_HEADER_INIT;
+ int bundle_fd = -1;
+ int ret;
+ struct option options[] = {
+ OPT_END()
+ };
+ 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 */
+
+ if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) {
+ ret = 1;
+ goto cleanup;
+ }
+ if (!startup_info->have_repository)
+ die(_("Need a repository to unbundle."));
+ ret = !!unbundle(the_repository, &header, bundle_fd, 0) ||
+ list_bundle_refs(&header, argc, argv);
+ bundle_header_release(&header);
+cleanup:
+ free(bundle_file);
+ return ret;
+}
+
+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..243fe6844b 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));
@@ -507,12 +512,6 @@ static int batch_objects(struct batch_options *opt)
if (opt->cmdmode)
data.split_on_whitespace = 1;
- if (opt->all_objects) {
- struct object_info empty = OBJECT_INFO_INIT;
- if (!memcmp(&data.info, &empty, sizeof(empty)))
- data.skip_object_info = 1;
- }
-
/*
* If we are printing out the object, then always fill in the type,
* since we will want to decide whether or not to stream.
@@ -522,9 +521,13 @@ static int batch_objects(struct batch_options *opt)
if (opt->all_objects) {
struct object_cb_data cb;
+ struct object_info empty = OBJECT_INFO_INIT;
+
+ if (!memcmp(&data.info, &empty, sizeof(empty)))
+ data.skip_object_info = 1;
- 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 +594,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 +648,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..2191256965 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) {
@@ -100,21 +100,26 @@ static int check_ignore(struct dir_struct *dir,
* should not be ignored, in order to be consistent with
* 'git status', 'git add' etc.
*/
- seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
+ seen = find_pathspecs_matching_against_index(&pathspec, &the_index,
+ PS_HEED_SKIP_WORKTREE);
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);
+ clear_pathspec(&pathspec);
return num_ignored;
}
@@ -148,7 +153,7 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
int cmd_check_ignore(int argc, const char **argv, const char *prefix)
{
int num_ignored;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
git_config(git_default_config, NULL);
@@ -177,7 +182,6 @@ 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));
setup_standard_excludes(&dir);
if (stdin_paths) {
@@ -187,7 +191,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/check-mailmap.c b/builtin/check-mailmap.c
index cdce144f3b..7dc47e4793 100644
--- a/builtin/check-mailmap.c
+++ b/builtin/check-mailmap.c
@@ -47,7 +47,7 @@ int cmd_check_mailmap(int argc, const char **argv, const char *prefix)
if (argc == 0 && !use_stdin)
die(_("no contacts specified"));
- read_mailmap(&mailmap, NULL);
+ read_mailmap(&mailmap);
for (i = 0; i < argc; ++i)
check_mailmap(&mailmap, argv[i]);
diff --git a/builtin/checkout--worker.c b/builtin/checkout--worker.c
new file mode 100644
index 0000000000..fb9fd13b73
--- /dev/null
+++ b/builtin/checkout--worker.c
@@ -0,0 +1,145 @@
+#include "builtin.h"
+#include "config.h"
+#include "entry.h"
+#include "parallel-checkout.h"
+#include "parse-options.h"
+#include "pkt-line.h"
+
+static void packet_to_pc_item(const char *buffer, int len,
+ struct parallel_checkout_item *pc_item)
+{
+ const struct pc_item_fixed_portion *fixed_portion;
+ const char *variant;
+ char *encoding;
+
+ if (len < sizeof(struct pc_item_fixed_portion))
+ BUG("checkout worker received too short item (got %dB, exp %dB)",
+ len, (int)sizeof(struct pc_item_fixed_portion));
+
+ fixed_portion = (struct pc_item_fixed_portion *)buffer;
+
+ if (len - sizeof(struct pc_item_fixed_portion) !=
+ fixed_portion->name_len + fixed_portion->working_tree_encoding_len)
+ BUG("checkout worker received corrupted item");
+
+ variant = buffer + sizeof(struct pc_item_fixed_portion);
+
+ /*
+ * Note: the main process uses zero length to communicate that the
+ * encoding is NULL. There is no use case that requires sending an
+ * actual empty string, since convert_attrs() never sets
+ * ca.working_tree_enconding to "".
+ */
+ if (fixed_portion->working_tree_encoding_len) {
+ encoding = xmemdupz(variant,
+ fixed_portion->working_tree_encoding_len);
+ variant += fixed_portion->working_tree_encoding_len;
+ } else {
+ encoding = NULL;
+ }
+
+ memset(pc_item, 0, sizeof(*pc_item));
+ pc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len, NULL);
+ pc_item->ce->ce_namelen = fixed_portion->name_len;
+ pc_item->ce->ce_mode = fixed_portion->ce_mode;
+ memcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);
+ oidcpy(&pc_item->ce->oid, &fixed_portion->oid);
+
+ pc_item->id = fixed_portion->id;
+ pc_item->ca.crlf_action = fixed_portion->crlf_action;
+ pc_item->ca.ident = fixed_portion->ident;
+ pc_item->ca.working_tree_encoding = encoding;
+}
+
+static void report_result(struct parallel_checkout_item *pc_item)
+{
+ struct pc_item_result res = { 0 };
+ size_t size;
+
+ res.id = pc_item->id;
+ res.status = pc_item->status;
+
+ if (pc_item->status == PC_ITEM_WRITTEN) {
+ res.st = pc_item->st;
+ size = sizeof(res);
+ } else {
+ size = PC_ITEM_RESULT_BASE_SIZE;
+ }
+
+ packet_write(1, (const char *)&res, size);
+}
+
+/* Free the worker-side malloced data, but not pc_item itself. */
+static void release_pc_item_data(struct parallel_checkout_item *pc_item)
+{
+ free((char *)pc_item->ca.working_tree_encoding);
+ discard_cache_entry(pc_item->ce);
+}
+
+static void worker_loop(struct checkout *state)
+{
+ struct parallel_checkout_item *items = NULL;
+ size_t i, nr = 0, alloc = 0;
+
+ while (1) {
+ int len = packet_read(0, NULL, NULL, packet_buffer,
+ sizeof(packet_buffer), 0);
+
+ if (len < 0)
+ BUG("packet_read() returned negative value");
+ else if (!len)
+ break;
+
+ ALLOC_GROW(items, nr + 1, alloc);
+ packet_to_pc_item(packet_buffer, len, &items[nr++]);
+ }
+
+ for (i = 0; i < nr; i++) {
+ struct parallel_checkout_item *pc_item = &items[i];
+ write_pc_item(pc_item, state);
+ report_result(pc_item);
+ release_pc_item_data(pc_item);
+ }
+
+ packet_flush(1);
+
+ free(items);
+}
+
+static const char * const checkout_worker_usage[] = {
+ N_("git checkout--worker [<options>]"),
+ NULL
+};
+
+int cmd_checkout__worker(int argc, const char **argv, const char *prefix)
+{
+ struct checkout state = CHECKOUT_INIT;
+ struct option checkout_worker_options[] = {
+ OPT_STRING(0, "prefix", &state.base_dir, N_("string"),
+ N_("when creating files, prepend <string>")),
+ OPT_END()
+ };
+
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(checkout_worker_usage,
+ checkout_worker_options);
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, checkout_worker_options,
+ checkout_worker_usage, 0);
+ if (argc > 0)
+ usage_with_options(checkout_worker_usage, checkout_worker_options);
+
+ if (state.base_dir)
+ state.base_dir_len = strlen(state.base_dir);
+
+ /*
+ * Setting this on a worker won't actually update the index. We just
+ * need to tell the checkout machinery to lstat() the written entries,
+ * so that we can send this data back to the main process.
+ */
+ state.refresh_cache = 1;
+
+ worker_loop(&state);
+ return 0;
+}
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index 1ac1cc290e..e21620d964 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -11,6 +11,8 @@
#include "quote.h"
#include "cache-tree.h"
#include "parse-options.h"
+#include "entry.h"
+#include "parallel-checkout.h"
#define CHECKOUT_ALL 4
static int nul_term_line;
@@ -23,22 +25,35 @@ static struct checkout state = CHECKOUT_INIT;
static void write_tempfile_record(const char *name, const char *prefix)
{
int i;
+ int have_tempname = 0;
if (CHECKOUT_ALL == checkout_stage) {
- for (i = 1; i < 4; i++) {
- if (i > 1)
- putchar(' ');
- if (topath[i][0])
- fputs(topath[i], stdout);
- else
- putchar('.');
+ for (i = 1; i < 4; i++)
+ if (topath[i][0]) {
+ have_tempname = 1;
+ break;
+ }
+
+ if (have_tempname) {
+ for (i = 1; i < 4; i++) {
+ if (i > 1)
+ putchar(' ');
+ if (topath[i][0])
+ fputs(topath[i], stdout);
+ else
+ putchar('.');
+ }
}
- } else
+ } else if (topath[checkout_stage][0]) {
+ have_tempname = 1;
fputs(topath[checkout_stage], stdout);
+ }
- putchar('\t');
- write_name_quoted_relative(name, prefix, stdout,
- nul_term_line ? '\0' : '\n');
+ if (have_tempname) {
+ putchar('\t');
+ write_name_quoted_relative(name, prefix, stdout,
+ nul_term_line ? '\0' : '\n');
+ }
for (i = 0; i < 4; i++) {
topath[i][0] = 0;
@@ -79,6 +94,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)
@@ -93,11 +116,13 @@ static int checkout_file(const char *name, const char *prefix)
return -1;
}
-static void checkout_all(const char *prefix, int prefix_length)
+static int checkout_all(const char *prefix, int prefix_length)
{
int i, errs = 0;
struct cache_entry *last_ce = NULL;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr ; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce) != checkout_stage
@@ -120,11 +145,7 @@ static void checkout_all(const char *prefix, int prefix_length)
}
if (last_ce && to_tempfile)
write_tempfile_record(last_ce->name, prefix);
- if (errs)
- /* we have already done our error reporting.
- * exit with the same code as die().
- */
- exit(128);
+ return !!errs;
}
static const char * const builtin_checkout_index_usage[] = {
@@ -159,6 +180,8 @@ 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;
+ int pc_workers, pc_threshold;
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
@@ -177,9 +200,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()
};
@@ -213,6 +236,10 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
}
+ get_parallel_checkout_configs(&pc_workers, &pc_threshold);
+ if (pc_workers > 1)
+ init_parallel_checkout();
+
/* Check out named files first */
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
@@ -223,7 +250,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,7 +272,7 @@ 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);
@@ -253,7 +280,14 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
}
if (all)
- checkout_all(prefix, prefix_length);
+ err |= checkout_all(prefix, prefix_length);
+
+ if (pc_workers > 1)
+ err |= run_parallel_checkout(&state, pc_workers, pc_threshold,
+ NULL, NULL);
+
+ if (err)
+ return 1;
if (is_lock_file_locked(&lock_file) &&
write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
diff --git a/builtin/checkout.c b/builtin/checkout.c
index ffa776c6e1..b5d477919a 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1,32 +1,33 @@
#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;
+#include "entry.h"
+#include "parallel-checkout.h"
static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
@@ -34,12 +35,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 +59,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,19 +81,34 @@ 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)
{
return run_hook_le(NULL, "post-checkout",
- oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid),
- oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid),
+ oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
+ oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
changed ? "1" : "0", NULL);
/* "new_commit" can be NULL when checking out from the index before
a commit exists. */
@@ -78,7 +116,7 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm
}
static int update_some(const struct object_id *oid, struct strbuf *base,
- const char *pathname, unsigned mode, int stage, void *context)
+ const char *pathname, unsigned mode, void *context)
{
int len;
struct cache_entry *ce;
@@ -105,6 +143,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);
@@ -118,8 +157,8 @@ static int update_some(const struct object_id *oid, struct strbuf *base,
static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
{
- read_tree_recursive(the_repository, tree, "", 0, 0,
- pathspec, update_some, NULL);
+ read_tree(the_repository, tree,
+ pathspec, update_some, NULL);
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
@@ -192,7 +231,8 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
return error(_("path '%s' does not have their version"), ce->name);
}
-static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts)
+static int checkout_merged(int pos, const struct checkout *state,
+ int *nr_checkouts, struct mem_pool *ce_mem_pool)
{
struct cache_entry *ce = active_cache[pos];
const char *path = ce->name;
@@ -202,6 +242,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 +264,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);
@@ -252,11 +293,10 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko
if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))
die(_("Unable to add merge result for '%s'"), path);
free(result_buf.ptr);
- ce = make_transient_cache_entry(mode, &oid, path, 2);
+ ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);
if (!ce)
die(_("make_cache_entry failed for path '%s'"), path);
status = checkout_entry(ce, state, NULL, nr_checkouts);
- discard_cache_entry(ce);
return status;
}
@@ -284,7 +324,7 @@ static void mark_ce_for_checkout_overlay(struct cache_entry *ce,
* If it comes from the tree-ish, we already know it
* matches the pathspec and could just stamp
* CE_MATCHED to it from update_some(). But we still
- * need ps_matched and read_tree_recursive (and
+ * need ps_matched and read_tree (and
* eventually tree_entry_interesting) cannot fill
* ps_matched yet. Once it can, we can avoid calling
* match_pathspec() for _all_ entries when
@@ -313,17 +353,92 @@ 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;
+ int pc_workers, pc_threshold;
+ struct mem_pool ce_mem_pool;
+
+ state.force = 1;
+ state.refresh_cache = 1;
+ state.istate = &the_index;
+
+ mem_pool_init(&ce_mem_pool, 0);
+ get_parallel_checkout_configs(&pc_workers, &pc_threshold);
+ init_checkout_metadata(&state.meta, info->refname,
+ info->commit ? &info->commit->object.oid : &info->oid,
+ NULL);
+
+ enable_delayed_checkout(&state);
+
+ if (pc_workers > 1)
+ init_parallel_checkout();
+
+ 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,
+ &ce_mem_pool);
+ pos = skip_same_name(ce, pos) - 1;
+ }
+ }
+ if (pc_workers > 1)
+ errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
+ NULL, NULL);
+ mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
+ 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 +448,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 +458,61 @@ 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. Also, when we were given a
+ * tree-object, new_branch_info->commit would be NULL, but we
+ * do not have to do any replacement, either.
+ */
+ if (rev && new_branch_info->commit && 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 +553,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 +571,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);
@@ -478,6 +616,7 @@ static void show_local_changes(struct object *head,
diff_setup_done(&rev.diffopt);
add_pending_object(&rev, head, NULL);
run_diff_index(&rev, 0);
+ object_array_clear(&rev.pending);
}
static void describe_detached_head(const char *msg, struct commit *commit)
@@ -497,7 +636,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 +652,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 +674,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 +692,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 +699,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 +742,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 +755,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 +767,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 +789,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,51 +803,47 @@ 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;
}
}
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
-
if (!cache_tree_fully_valid(active_cache_tree))
cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
- if (!opts->force && !opts->quiet)
+ 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 +909,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 +948,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 +1044,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 +1057,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 +1068,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 +1082,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 +1121,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 +1138,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 +1257,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 +1300,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 +1332,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 +1346,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 +1373,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 +1437,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 +1448,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 +1458,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 +1470,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 +1508,156 @@ 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);
+
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
- git_config(git_checkout_config, &opts);
+ opts->track = BRANCH_TRACK_UNSPECIFIED;
- opts.track = BRANCH_TRACK_UNSPECIFIED;
+ 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;
- argc = parse_options(argc, argv, prefix, options, checkout_usage,
- PARSE_OPT_KEEP_DASHDASH);
+ argc = parse_options(argc, argv, prefix, options,
+ usagestr, parseopt_flags);
- dwim_new_local_branch = !no_dwim_new_local_branch;
- if (opts.show_progress < 0) {
- if (opts.quiet)
- opts.show_progress = 0;
+ 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 +1665,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 +1682,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 (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
+ if (opts->patch_mode)
+ die(_("--pathspec-from-file is incompatible with --patch"));
+
+ 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..98a2860409 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;
@@ -187,10 +189,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
strbuf_complete(path, '/');
len = path->len;
- while ((e = readdir(dir)) != NULL) {
+ while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
struct stat st;
- if (is_dot_or_dotdot(e->d_name))
- continue;
strbuf_setlen(path, len);
strbuf_addstr(path, e->d_name);
@@ -200,7 +200,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 +208,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 +236,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 +264,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 +419,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 +578,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;
}
@@ -624,7 +621,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
nr += chosen[i];
}
- result = xcalloc(st_add(nr, 1), sizeof(int));
+ CALLOC_ARRAY(result, st_add(nr, 1));
for (i = 0; i < stuff->nr && j < nr; i++) {
if (chosen[i])
result[j++] = i;
@@ -644,11 +641,11 @@ static int clean_cmd(void)
static int filter_by_patterns_cmd(void)
{
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
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 +658,14 @@ 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");
+ 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 +673,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 +695,7 @@ static int filter_by_patterns_cmd(void)
}
strbuf_list_free(ignore_list);
- clear_directory(&dir);
+ dir_clear(&dir);
}
strbuf_release(&confirm);
@@ -756,12 +750,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;
}
@@ -897,11 +889,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
struct strbuf abs_path = STRBUF_INIT;
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
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 +903,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 +920,6 @@ 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"));
-
if (!interactive && !dry_run && !force) {
if (config_set)
die(_("clean.requireForce set to true and neither -i, -n, nor -f given; "
@@ -946,21 +931,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,
@@ -971,35 +999,23 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- int matches = 0;
struct stat st;
const char *rel;
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);
- if (S_ISDIR(st.st_mode) && !remove_directories &&
- matches != MATCHED_EXACTLY)
+ if (S_ISDIR(st.st_mode) && !remove_directories)
continue;
rel = relative_path(ent->name, prefix, &buf);
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 +1023,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 +1041,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..66fe66679c 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:
@@ -49,15 +50,19 @@ static int option_no_checkout, option_bare, option_mirror, option_single_branch
static int option_local = -1, option_no_hardlinks, option_shared;
static int option_no_tags;
static int option_shallow_submodules;
+static int option_reject_shallow = -1; /* unspecified */
+static int config_reject_shallow = -1; /* unspecified */
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 +72,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)
@@ -86,6 +92,8 @@ static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
OPT_BOOL(0, "progress", &option_progress,
N_("force progress reporting")),
+ OPT_BOOL(0, "reject-shallow", &option_reject_shallow,
+ N_("don't clone shallow repository")),
OPT_BOOL('n', "no-checkout", &option_no_checkout,
N_("don't create a checkout")),
OPT_BOOL(0, "bare", &option_bare, N_("create a bare repository")),
@@ -99,10 +107,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 +150,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 +366,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 +403,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 +470,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 +494,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 +516,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 +648,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 +683,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 +692,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 +726,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 +745,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 +788,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 +807,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 +815,70 @@ 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");
+ }
- err = run_command_v_opt(args.argv, RUN_GIT_CMD);
- argv_array_clear(&args);
+ if (option_single_branch >= 0)
+ strvec_push(&args, option_single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
+
+ 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);
+ }
+ if (!strcmp(k, "clone.rejectshallow"))
+ config_reject_shallow = git_config_bool(k, 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 +931,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 +961,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);
@@ -891,16 +970,16 @@ static int dir_exists(const char *path)
int cmd_clone(int argc, const char **argv, const char *prefix)
{
int is_bundle = 0, is_local;
+ int reject_shallow = 0;
const char *repo_name, *repo, *work_tree, *git_dir;
- char *path, *dir;
- int dest_exists;
+ char *path = NULL, *dir, *display_repo = NULL;
+ int dest_exists, real_dest_exists = 0;
const struct ref *refs, *remote_head;
- const struct ref *remote_head_points_at;
+ struct ref *remote_head_points_at = NULL;
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 +987,13 @@ 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 transport_ls_refs_options transport_ls_refs_options =
+ TRANSPORT_LS_REFS_OPTIONS_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 +1022,17 @@ 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)
+ if (path) {
+ FREE_AND_NULL(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 +1044,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 +1093,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 +1147,45 @@ 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);
+
+ /*
+ * If option_reject_shallow is specified from CLI option,
+ * ignore config_reject_shallow from git_clone_config.
+ */
+ if (config_reject_shallow != -1)
+ reject_shallow = config_reject_shallow;
+ if (option_reject_shallow != -1)
+ reject_shallow = option_reject_shallow;
+
+ /*
+ * 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 +1194,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 +1210,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);
@@ -1111,6 +1234,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (filter_options.choice)
warning(_("--filter is ignored in local clones; use file:// instead."));
if (!access(mkpath("%s/shallow", path), F_OK)) {
+ if (reject_shallow)
+ die(_("source repository is shallow, reject to clone."));
if (option_local > 0)
warning(_("source repository is shallow, ignoring --local"));
is_local = 0;
@@ -1122,6 +1247,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport_set_option(transport, TRANS_OPT_KEEP, "yes");
+ if (reject_shallow)
+ transport_set_option(transport, TRANS_OPT_REJECT_SHALLOW, "1");
if (option_depth)
transport_set_option(transport, TRANS_OPT_DEPTH,
option_depth);
@@ -1142,29 +1269,39 @@ 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");
- refspec_ref_prefixes(&remote->fetch, &ref_prefixes);
+ strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
+ refspec_ref_prefixes(&remote->fetch,
+ &transport_ls_refs_options.ref_prefixes);
if (option_branch)
- expand_ref_prefix(&ref_prefixes, option_branch);
+ expand_ref_prefix(&transport_ls_refs_options.ref_prefixes,
+ option_branch);
if (!option_no_tags)
- argv_array_push(&ref_prefixes, "refs/tags/");
+ strvec_push(&transport_ls_refs_options.ref_prefixes,
+ "refs/tags/");
- refs = transport_get_remote_refs(transport, &ref_prefixes);
+ refs = transport_get_remote_refs(transport, &transport_ls_refs_options);
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
@@ -1182,8 +1319,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
break;
}
- if (!is_local && !complete_refs_before_fetch)
- transport_fetch_refs(transport, mapped_refs);
+ if (!is_local && !complete_refs_before_fetch) {
+ if (transport_fetch_refs(transport, mapped_refs))
+ die(_("remote transport reported error"));
+ }
remote_head = find_ref_by_name(refs, "HEAD");
remote_head_points_at =
@@ -1195,7 +1334,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 +1342,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,25 +1350,42 @@ 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;
+ char *ref;
+
+ if (transport_ls_refs_options.unborn_head_target &&
+ skip_prefix(transport_ls_refs_options.unborn_head_target,
+ "refs/heads/", &branch)) {
+ ref = transport_ls_refs_options.unborn_head_target;
+ transport_ls_refs_options.unborn_head_target = NULL;
+ create_symref("HEAD", ref, reflog_msg.buf);
+ } else {
+ branch = git_default_branch_name(0);
+ 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);
- else if (refs && complete_refs_before_fetch)
- transport_fetch_refs(transport, mapped_refs);
+ else if (refs && complete_refs_before_fetch) {
+ if (transport_fetch_refs(transport, mapped_refs))
+ die(_("remote transport reported error"));
+ }
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 +1406,20 @@ 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);
+ free_refs(mapped_refs);
+ free_refs(remote_head_points_at);
+ free(dir);
+ free(path);
+ UNLEAK(repo);
junk_mode = JUNK_LEAVE_ALL;
- argv_array_clear(&ref_prefixes);
+ strvec_clear(&transport_ls_refs_options.ref_prefixes);
+ free(transport_ls_refs_options.unborn_head_target);
return err;
}
diff --git a/builtin/column.c b/builtin/column.c
index 5228ccf37a..40d4b3bee2 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -27,10 +27,10 @@ int cmd_column(int argc, const char **argv, const char *prefix)
OPT_STRING(0, "command", &real_command, N_("name"), N_("lookup config vars")),
OPT_COLUMN(0, "mode", &colopts, N_("layout to use")),
OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")),
- OPT_INTEGER(0, "width", &copts.width, N_("Maximum width")),
- OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("Padding space on left border")),
- OPT_INTEGER(0, "nl", &copts.nl, N_("Padding space on right border")),
- OPT_INTEGER(0, "padding", &copts.padding, N_("Padding space between columns")),
+ OPT_INTEGER(0, "width", &copts.width, N_("maximum width")),
+ OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")),
+ OPT_INTEGER(0, "nl", &copts.nl, N_("padding space on right border")),
+ OPT_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")),
OPT_END()
};
@@ -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..cd86315221 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,119 +37,178 @@ 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")),
+ 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,
N_("dir"),
- N_("The object directory to store the graph")),
+ N_("the object directory to store the graph")),
OPT_BOOL(0, "reachable", &opts.reachable,
N_("start walk at all refs")),
OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
@@ -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;
}
@@ -205,7 +314,7 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix)
static struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
- N_("The object directory to store the graph")),
+ N_("the object directory to store the graph")),
OPT_END(),
};
@@ -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..243c626307 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",
@@ -99,14 +105,16 @@ static const char *template_file;
*/
static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message;
-static char *fixup_message, *squash_message;
+static char *fixup_message, *fixup_commit, *squash_message;
+static const char *fixup_prefix;
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;
+static struct strvec trailer_args = STRVEC_INIT;
/*
* The default commit message cleanup mode will remove the lines
@@ -119,13 +127,20 @@ 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;
static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+ BUG_ON_OPT_NEG(unset);
+
+ strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+ return 0;
+}
+
static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
{
enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -176,12 +191,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;
@@ -251,6 +261,8 @@ static int list_paths(struct string_list *list, const char *with_tree,
free(max_prefix);
}
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
struct string_list_item *item;
@@ -326,7 +338,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 +352,32 @@ 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 && !allow_empty &&
+ (!amend || (fixup_message && strcmp(fixup_prefix, "amend"))))))
+ 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 +385,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 +487,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 +546,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 +573,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);
}
@@ -655,6 +694,22 @@ static void adjust_comment_line_char(const struct strbuf *sb)
comment_line_char = *p;
}
+static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,
+ struct pretty_print_context *ctx)
+{
+ const char *buffer, *subject, *fmt;
+
+ buffer = get_commit_buffer(commit, NULL);
+ find_commit_subject(buffer, &subject);
+ /*
+ * If we amend the 'amend!' commit then we don't want to
+ * duplicate the subject line.
+ */
+ fmt = starts_with(subject, "amend!") ? "%b" : "%B";
+ format_commit_message(commit, fmt, sb, ctx);
+ unuse_commit_buffer(commit, buffer);
+}
+
static int prepare_to_commit(const char *index_file, const char *prefix,
struct commit *current_head,
struct wt_status *s,
@@ -719,15 +774,33 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
} else if (fixup_message) {
struct pretty_print_context ctx = {0};
struct commit *commit;
- commit = lookup_commit_reference_by_name(fixup_message);
+ char *fmt;
+ commit = lookup_commit_reference_by_name(fixup_commit);
if (!commit)
- die(_("could not lookup commit %s"), fixup_message);
+ die(_("could not lookup commit %s"), fixup_commit);
ctx.output_encoding = get_commit_output_encoding();
- format_commit_message(commit, "fixup! %s\n\n",
- &sb, &ctx);
- if (have_option_m)
- strbuf_addbuf(&sb, &message);
+ fmt = xstrfmt("%s! %%s\n\n", fixup_prefix);
+ format_commit_message(commit, fmt, &sb, &ctx);
+ free(fmt);
hook_arg1 = "message";
+
+ /*
+ * Only `-m` commit message option is checked here, as
+ * it supports `--fixup` to append the commit message.
+ *
+ * The other commit message options `-c`/`-C`/`-F` are
+ * incompatible with all the forms of `--fixup` and
+ * have already errored out while parsing the `git commit`
+ * options.
+ */
+ if (have_option_m && !strcmp(fixup_prefix, "fixup"))
+ strbuf_addbuf(&sb, &message);
+
+ if (!strcmp(fixup_prefix, "amend")) {
+ if (have_option_m)
+ die(_("cannot combine -m with --fixup:%s"), fixup_message);
+ prepare_amend_commit(commit, &sb, &ctx);
+ }
} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {
size_t merge_msg_start;
@@ -768,7 +841,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";
}
@@ -816,44 +889,49 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
int ident_shown = 0;
int saved_color_setting;
struct ident_split ci, ai;
-
+ const char *hint_cleanup_all = allow_empty_message ?
+ _("Please enter the commit message for your changes."
+ " Lines starting\nwith '%c' will be ignored.\n") :
+ _("Please enter the commit message for your changes."
+ " Lines starting\nwith '%c' will be ignored, and an empty"
+ " message aborts the commit.\n");
+ const char *hint_cleanup_space = allow_empty_message ?
+ _("Please enter the commit message for your changes."
+ " Lines starting\n"
+ "with '%c' will be kept; you may remove them"
+ " yourself if you want to.\n") :
+ _("Please enter the commit message for your changes."
+ " Lines starting\n"
+ "with '%c' will be kept; you may remove them"
+ " yourself if you want to.\n"
+ "An empty message aborts the commit.\n");
if (whence != FROM_COMMIT) {
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");
if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL)
- status_printf(s, GIT_COLOR_NORMAL,
- _("Please enter the commit message for your changes."
- " Lines starting\nwith '%c' will be ignored, and an empty"
- " message aborts the commit.\n"), comment_line_char);
+ status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_all, comment_line_char);
else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
if (whence == FROM_COMMIT && !merge_contains_scissors)
wt_status_add_cut_line(s->fp);
} else /* COMMIT_MSG_CLEANUP_SPACE, that is. */
- status_printf(s, GIT_COLOR_NORMAL,
- _("Please enter the commit message for your changes."
- " Lines starting\n"
- "with '%c' will be kept; you may remove them"
- " yourself if you want to.\n"
- "An empty message aborts the commit.\n"), comment_line_char);
+ status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_space, comment_line_char);
/*
* These should never fail because they come from our own
@@ -907,6 +985,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (get_oid(parent, &oid)) {
int i, ita_nr = 0;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++)
if (ce_intent_to_add(active_cache[i]))
ita_nr++;
@@ -934,6 +1014,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
fclose(s->fp);
+ if (trailer_args.nr) {
+ struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+ strvec_pushl(&run_trailer.args, "interpret-trailers",
+ "--in-place", git_path_commit_editmsg(), NULL);
+ strvec_pushv(&run_trailer.args, trailer_args.v);
+ run_trailer.git_cmd = 1;
+ if (run_command(&run_trailer))
+ die(_("unable to pass trailers to --trailers"));
+ strvec_clear(&trailer_args);
+ }
+
/*
* Reject an attempt to record a non-merge empty commit without
* explicit --allow-empty. In the cherry-pick case, it may be
@@ -941,16 +1033,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 +1071,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 &&
@@ -1011,7 +1107,7 @@ static const char *find_author_by_nickname(const char *name)
av[++ac] = NULL;
setup_revisions(ac, av, &revs, NULL);
revs.mailmap = &mailmap;
- read_mailmap(revs.mailmap, NULL);
+ read_mailmap(revs.mailmap);
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
@@ -1078,9 +1174,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,10 +1205,34 @@ 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;
}
+static void check_fixup_reword_options(int argc, const char *argv[]) {
+ if (whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("You are in the middle of a merge -- cannot reword."));
+ else if (is_from_cherry_pick(whence))
+ die(_("You are in the middle of a cherry-pick -- cannot reword."));
+ }
+ if (argc)
+ die(_("cannot combine reword option of --fixup with path '%s'"), *argv);
+ if (patch_interactive || interactive || all || also || only)
+ die(_("reword option of --fixup is mutually exclusive with --patch/--interactive/--all/--include/--only"));
+}
+
static int parse_and_validate_options(int argc, const char *argv[],
const struct option *options,
const char * const usage[],
@@ -1129,7 +1251,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (force_author && renew_authorship)
die(_("Using both --reset-author and --author does not make sense"));
- if (logfile || have_option_m || use_message || fixup_message)
+ if (logfile || have_option_m || use_message)
use_editor = 0;
if (0 <= edit_flag)
use_editor = edit_flag;
@@ -1140,8 +1262,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 +1287,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 +1297,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 +1308,42 @@ 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."));
+
+ if (fixup_message) {
+ /*
+ * We limit --fixup's suboptions to only alpha characters.
+ * If the first character after a run of alpha is colon,
+ * then the part before the colon may be a known suboption
+ * name like `amend` or `reword`, or a misspelt suboption
+ * name. In either case, we treat it as
+ * --fixup=<suboption>:<arg>.
+ *
+ * Otherwise, we are dealing with --fixup=<commit>.
+ */
+ char *p = fixup_message;
+ while (isalpha(*p))
+ p++;
+ if (p > fixup_message && *p == ':') {
+ *p = '\0';
+ fixup_commit = p + 1;
+ if (!strcmp("amend", fixup_message) ||
+ !strcmp("reword", fixup_message)) {
+ fixup_prefix = "amend";
+ allow_empty = 1;
+ if (*fixup_message == 'r') {
+ check_fixup_reword_options(argc, argv);
+ only = 1;
+ }
+ } else {
+ die(_("unknown option: --fixup=%s:%s"), fixup_message, fixup_commit);
+ }
+ } else {
+ fixup_commit = fixup_message;
+ fixup_prefix = "fixup";
+ use_editor = 0;
+ }
+ }
+
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
handle_untracked_files_arg(s);
@@ -1198,13 +1358,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 +1406,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 +1487,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,15 +1508,18 @@ 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(),
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_status_usage, builtin_status_options);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
status_init_config(&s, git_status_config);
argc = parse_options(argc, argv, prefix,
builtin_status_options,
@@ -1386,7 +1553,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 +1610,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")),
@@ -1480,10 +1624,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
- OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
+ /*
+ * TRANSLATORS: Leave "[(amend|reword):]" as-is,
+ * and only translate <commit>.
+ */
+ OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword 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_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
+ 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 +1664,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,
@@ -1538,6 +1689,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_usage, builtin_commit_options);
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
status_init_config(&s, git_commit_config);
s.commit_template = 1;
status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
@@ -1558,8 +1712,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 +1762,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);
}
@@ -1635,8 +1791,21 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
exit(1);
}
+ if (fixup_message && starts_with(sb.buf, "amend! ") &&
+ !allow_empty_message) {
+ struct strbuf body = STRBUF_INIT;
+ size_t len = commit_subject_length(sb.buf);
+ strbuf_addstr(&body, sb.buf + len);
+ if (message_is_empty(&body, cleanup_mode)) {
+ rollback_index_files();
+ fprintf(stderr, _("Aborting commit due to empty commit message body.\n"));
+ exit(1);
+ }
+ strbuf_release(&body);
+ }
+
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 +1813,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 +1827,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 +1836,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 +1857,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..865fddd6ce 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -14,6 +14,7 @@ static const char *const builtin_config_usage[] = {
static char *key;
static regex_t *key_regexp;
+static const char *value_pattern;
static regex_t *regexp;
static int show_keys;
static int omit_values;
@@ -29,10 +30,12 @@ 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;
+static int fixed_value;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
@@ -64,6 +67,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 +97,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"))
@@ -129,17 +135,18 @@ static struct option builtin_config_options[] = {
OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")),
OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
OPT_GROUP(N_("Action")),
- OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
- OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL),
- OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP),
+ OPT_BIT(0, "get", &actions, N_("get value: name [value-pattern]"), ACTION_GET),
+ OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-pattern]"), ACTION_GET_ALL),
+ OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-pattern]"), ACTION_GET_REGEXP),
OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
- OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL),
+ OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value-pattern]"), ACTION_REPLACE_ALL),
OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
- OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET),
- OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL),
+ OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-pattern]"), ACTION_UNSET),
+ OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-pattern]"), ACTION_UNSET_ALL),
OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST),
+ OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")),
OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR),
OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL),
@@ -148,13 +155,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 +187,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 +234,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 +257,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)
@@ -270,6 +299,8 @@ static int collect_config(const char *key_, const char *value_, void *cb)
return 0;
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
return 0;
+ if (fixed_value && strcmp(value_pattern, (value_?value_:"")))
+ return 0;
if (regexp != NULL &&
(do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
return 0;
@@ -280,7 +311,7 @@ static int collect_config(const char *key_, const char *value_, void *cb)
return format_config(&values->items[values->nr++], key_, value_);
}
-static int get_value(const char *key_, const char *regex_)
+static int get_value(const char *key_, const char *regex_, unsigned flags)
{
int ret = CONFIG_GENERIC_ERROR;
struct strbuf_list values = {NULL};
@@ -317,7 +348,9 @@ static int get_value(const char *key_, const char *regex_)
}
}
- if (regex_) {
+ if (regex_ && (flags & CONFIG_FLAGS_FIXED_VALUE))
+ value_pattern = regex_;
+ else if (regex_) {
if (regex_[0] == '!') {
do_not_match = 1;
regex_++;
@@ -395,6 +428,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))
@@ -598,6 +638,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = !startup_info->have_repository;
char *value;
+ int flags = 0;
given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));
@@ -612,22 +653,27 @@ 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) {
- char *user_config = expand_user_path("~/.gitconfig", 0);
- char *xdg_config = xdg_config_home("config");
+ char *user_config, *xdg_config;
+ git_global_config(&user_config, &xdg_config);
if (!user_config)
/*
* It is unknown if HOME/.gitconfig exists, so
@@ -637,6 +683,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 +694,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
free(xdg_config);
}
}
- else if (use_system_config)
- given_config_source.file = git_etc_gitconfig();
- else if (use_local_config)
+ else if (use_system_config) {
+ given_config_source.file = git_system_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 +712,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 +733,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';
@@ -719,6 +774,42 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config();
}
+ /* check usage of --fixed-value */
+ if (fixed_value) {
+ int allowed_usage = 0;
+
+ switch (actions) {
+ /* git config --get <name> <value-pattern> */
+ case ACTION_GET:
+ /* git config --get-all <name> <value-pattern> */
+ case ACTION_GET_ALL:
+ /* git config --get-regexp <name-pattern> <value-pattern> */
+ case ACTION_GET_REGEXP:
+ /* git config --unset <name> <value-pattern> */
+ case ACTION_UNSET:
+ /* git config --unset-all <name> <value-pattern> */
+ case ACTION_UNSET_ALL:
+ allowed_usage = argc > 1 && !!argv[1];
+ break;
+
+ /* git config <name> <value> <value-pattern> */
+ case ACTION_SET_ALL:
+ /* git config --replace-all <name> <value> <value-pattern> */
+ case ACTION_REPLACE_ALL:
+ allowed_usage = argc > 2 && !!argv[2];
+ break;
+
+ /* other options don't allow --fixed-value */
+ }
+
+ if (!allowed_usage) {
+ error(_("--fixed-value only applies with 'value-pattern'"));
+ usage_builtin_config();
+ }
+
+ flags |= CONFIG_FLAGS_FIXED_VALUE;
+ }
+
if (actions & PAGING_ACTIONS)
setup_auto_pager("config", 1);
@@ -780,7 +871,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
value = normalize_value(argv[0], argv[1]);
UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file,
- argv[0], value, argv[2], 0);
+ argv[0], value, argv[2],
+ flags);
}
else if (actions == ACTION_ADD) {
check_write();
@@ -789,7 +881,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value,
- CONFIG_REGEX_NONE, 0);
+ CONFIG_REGEX_NONE,
+ flags);
}
else if (actions == ACTION_REPLACE_ALL) {
check_write();
@@ -797,23 +890,24 @@ int cmd_config(int argc, const char **argv, const char *prefix)
value = normalize_value(argv[0], argv[1]);
UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file,
- argv[0], value, argv[2], 1);
+ argv[0], value, argv[2],
+ flags | CONFIG_FLAGS_MULTI_REPLACE);
}
else if (actions == ACTION_GET) {
check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1]);
+ return get_value(argv[0], argv[1], flags);
}
else if (actions == ACTION_GET_ALL) {
do_all = 1;
check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1]);
+ return get_value(argv[0], argv[1], flags);
}
else if (actions == ACTION_GET_REGEXP) {
show_keys = 1;
use_key_regexp = 1;
do_all = 1;
check_argc(argc, 1, 2);
- return get_value(argv[0], argv[1]);
+ return get_value(argv[0], argv[1], flags);
}
else if (actions == ACTION_GET_URLMATCH) {
check_argc(argc, 2, 2);
@@ -824,7 +918,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 1, 2);
if (argc == 2)
return git_config_set_multivar_in_file_gently(given_config_source.file,
- argv[0], NULL, argv[1], 0);
+ argv[0], NULL, argv[1],
+ flags);
else
return git_config_set_in_file_gently(given_config_source.file,
argv[0], NULL);
@@ -833,7 +928,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_write();
check_argc(argc, 1, 2);
return git_config_set_multivar_in_file_gently(given_config_source.file,
- argv[0], NULL, argv[1], 1);
+ argv[0], NULL, argv[1],
+ flags | CONFIG_FLAGS_MULTI_REPLACE);
}
else if (actions == ACTION_RENAME_SECTION) {
int ret;
diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c
new file mode 100644
index 0000000000..4c6c89ab0d
--- /dev/null
+++ b/builtin/credential-cache--daemon.c
@@ -0,0 +1,319 @@
+#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)
+{
+ struct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;
+ int fd;
+
+ fd = unix_stream_listen(socket_path, &opts);
+ 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..e8a7415747
--- /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, 0);
+
+ 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 = interpolate_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..62a4f3c265
--- /dev/null
+++ b/builtin/credential-store.c
@@ -0,0 +1,199 @@
+#include "builtin.h"
+#include "config.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)
+{
+ int timeout_ms = 1000;
+
+ git_config_get_int("credentialstore.locktimeoutms", &timeout_ms);
+ if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms) < 0)
+ die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms);
+ 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 = interpolate_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..e912ba50d7 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);
@@ -190,7 +194,7 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
}
/* Is it annotated? */
- if (!peel_ref(path, &peeled)) {
+ if (!peel_iterated_oid(oid, &peeled)) {
is_annotated = !oideq(oid, &peeled);
} else {
oidcpy(&peeled, oid);
@@ -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 process_commit_data pcd = { null_oid, oid, dst, &revs};
+ 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..70103c4095 100644
--- a/builtin/diff-files.c
+++ b/builtin/diff-files.c
@@ -7,6 +7,7 @@
#include "cache.h"
#include "config.h"
#include "diff.h"
+#include "diff-merges.h"
#include "commit.h"
#include "revision.h"
#include "builtin.h"
@@ -28,7 +29,14 @@ 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;
- precompose_argv(argc, argv);
+
+ /*
+ * 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;
+
+ prefix = precompose_argv_prefix(argc, argv, prefix);
argc = setup_revisions(argc, argv, &rev, NULL);
while (1 < argc && argv[1][0] == '-') {
@@ -46,6 +54,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
}
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_RAW;
+ rev.diffopt.rotate_to_strict = 1;
/*
* Make sure there are NO revision (i.e. pending object) parameter,
@@ -62,9 +71,9 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
* was not asked to. "diff-files -c -p" should not densify
* (the user should ask with "diff-files --cc" explicitly).
*/
- if (rev.max_count == -1 && !rev.combine_merges &&
+ if (rev.max_count == -1 &&
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
- rev.combine_merges = rev.dense_combined_merges = 1;
+ diff_merges_set_dense_combined_if_unset(&rev);
if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index 93ec642423..cf09559e42 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -2,6 +2,7 @@
#include "cache.h"
#include "config.h"
#include "diff.h"
+#include "diff-merges.h"
#include "commit.h"
#include "revision.h"
#include "builtin.h"
@@ -15,7 +16,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;
@@ -25,20 +26,32 @@ int cmd_diff_index(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;
- precompose_argv(argc, argv);
+ prefix = precompose_argv_prefix(argc, argv, prefix);
+
+ /*
+ * We need no diff for merges options, and we need to avoid conflict
+ * with our own meaning of "-m".
+ */
+ diff_merges_suppress_options_parsing();
argc = setup_revisions(argc, argv, &rev, NULL);
for (i = 1; i < argc; i++) {
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 if (!strcmp(arg, "-m"))
+ rev.match_missing = 1;
else
usage(diff_cache_usage);
}
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_RAW;
+ rev.diffopt.rotate_to_strict = 1;
+
/*
* Make sure there is one revision (i.e. pending object),
* and there is no revision filtering parameters.
@@ -46,7 +59,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 +69,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..f33d30d57b 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);
@@ -124,9 +126,17 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
memset(&s_r_opt, 0, sizeof(s_r_opt));
s_r_opt.tweak = diff_tree_tweak_rev;
- precompose_argv(argc, argv);
+ prefix = precompose_argv_prefix(argc, argv, prefix);
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,20 @@ 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"));
+
+ opt->diffopt.rotate_to_strict = 1;
+
/*
* 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 +177,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);
@@ -168,6 +194,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
int saved_nrl = 0;
int saved_dcctc = 0;
+ opt->diffopt.rotate_to_strict = 0;
if (opt->diffopt.detect_rename) {
if (!the_index.cache)
repo_read_index(the_repository);
diff --git a/builtin/diff.c b/builtin/diff.c
index 42ac803091..dd8ce688ba 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -6,24 +6,32 @@
#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"
#include "blob.h"
#include "tag.h"
#include "diff.h"
+#include "diff-merges.h"
#include "diffcore.h"
#include "revision.h"
#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 [--merge-base] [<commit>] [--] [<path>...]\n"
+" or: git diff [<options>] [--merge-base] <commit> [<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)
{
@@ -90,7 +98,7 @@ static int builtin_diff_b_f(struct rev_info *revs,
stuff_change(&revs->diffopt,
blob[0]->mode, canon_mode(st.st_mode),
- &blob[0]->item->oid, &null_oid,
+ &blob[0]->item->oid, null_oid(),
1, 0,
blob[0]->path ? blob[0]->path : path,
path);
@@ -127,11 +135,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 +154,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 +164,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 +173,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;
@@ -192,12 +217,11 @@ static int builtin_diff_combined(struct rev_info *revs,
if (argc > 1)
usage(builtin_diff_usage);
- if (!revs->dense_combined_merges && !revs->combine_merges)
- revs->dense_combined_merges = revs->combine_merges = 1;
+ diff_merges_set_dense_combined_if_unset(revs);
+
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;
}
@@ -242,9 +266,9 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
* dense one, --cc can be explicitly asked for, or just rely
* on the default).
*/
- if (revs->max_count == -1 && !revs->combine_merges &&
+ if (revs->max_count == -1 &&
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
- revs->combine_merges = revs->dense_combined_merges = 1;
+ diff_merges_set_dense_combined_if_unset(revs);
setup_work_tree();
if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
@@ -254,6 +278,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 +389,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:
@@ -317,7 +453,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
init_diff_ui_defaults();
git_config(git_diff_ui_config, NULL);
- precompose_argv(argc, argv);
+ prefix = precompose_argv_prefix(argc, argv, prefix);
repo_init_revisions(the_repository, &rev, prefix);
@@ -355,6 +491,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
rev.diffopt.flags.recursive = 1;
+ rev.diffopt.rotate_to_strict = 1;
setup_diff_pager(&rev.diffopt);
@@ -382,6 +519,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 +534,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 +577,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..6a9242a803 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -18,11 +18,12 @@
#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"
#include "dir.h"
+#include "entry.h"
static int trust_exit_code;
@@ -125,12 +126,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 +149,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 +167,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 +185,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 +211,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 +226,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 +242,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))
@@ -315,7 +323,7 @@ static int checkout_path(unsigned mode, struct object_id *oid,
struct cache_entry *ce;
int ret;
- ce = make_transient_cache_entry(mode, oid, path, 0);
+ ce = make_transient_cache_entry(mode, oid, path, 0, NULL);
ret = checkout_entry(ce, state, NULL, NULL);
discard_cache_entry(ce);
@@ -335,7 +343,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 +387,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 +468,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 +538,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 +557,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);
@@ -577,6 +585,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
rc = run_command_v_opt(helper_argv, flags);
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&wtindex);
+
/*
* If the diff includes working copy files and those
* files were modified during the diff, then the changes
@@ -659,12 +670,12 @@ 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
};
- int ret = 0, i;
+ int i;
if (prompt > 0)
env[2] = "GIT_DIFFTOOL_PROMPT=true";
@@ -672,11 +683,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);
- exit(ret);
+ strvec_push(&args, argv[i]);
+ return run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env);
}
int cmd_difftool(int argc, const char **argv, const char *prefix)
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..3c20f164f0 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",
@@ -847,7 +923,6 @@ static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)
if (!tag)
die("Tag %s points nowhere?", e->name);
return (struct commit *)tag;
- break;
}
default:
return NULL;
@@ -867,7 +942,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) {
@@ -941,7 +1016,7 @@ static void handle_tags_and_duplicates(struct string_list *extras)
* it.
*/
printf("reset %s\nfrom %s\n\n",
- name, oid_to_hex(&null_oid));
+ name, oid_to_hex(null_oid()));
continue;
}
@@ -950,7 +1025,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
@@ -960,7 +1035,7 @@ static void handle_tags_and_duplicates(struct string_list *extras)
if (!reference_excluded_commits) {
/* delete the ref */
printf("reset %s\nfrom %s\n\n",
- name, oid_to_hex(&null_oid));
+ name, oid_to_hex(null_oid()));
continue;
}
/* set ref to commit using oid, not mark */
@@ -1007,11 +1082,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;
@@ -1066,8 +1146,39 @@ static void handle_deletes(void)
continue;
printf("reset %s\nfrom %s\n\n",
- refspec->dst, oid_to_hex(&null_oid));
+ refspec->dst, oid_to_hex(null_oid()));
+ }
+}
+
+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)
@@ -1075,7 +1186,9 @@ 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,24 +1201,36 @@ 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")),
+ N_("dump marks to this file")),
OPT_STRING(0, "import-marks", &import_filename, N_("file"),
- N_("Import marks from this 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")),
+ N_("fake a tagger when tags lack one")),
OPT_BOOL(0, "full-tree", &full_tree,
- N_("Output full tree for each commit")),
+ N_("output full tree for each commit")),
OPT_BOOL(0, "use-done-feature", &use_done_feature,
- N_("Use the done feature to terminate the stream")),
- OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")),
+ N_("use the done feature to terminate the stream")),
+ OPT_BOOL(0, "no-data", &no_data, N_("skip output of blob data")),
OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"),
- N_("Apply refspec to exported refs")),
+ 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")),
+ &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")),
+ 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 +1252,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 +1267,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..20406f6775
--- /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_oid_fn(&oid, &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_oid_fn(&oid, &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;
+ oidread(&e->versions[0].oid, (unsigned char *)c);
+ oidread(&e->versions[1].oid, (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++;
+ CALLOC_ARRAY(ms, 1);
+
+ 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);
+ CALLOC_ARRAY(atom_table, atom_table_sz);
+ CALLOC_ARRAY(branch_table, branch_table_sz);
+ CALLOC_ARRAY(avail_tree_table, avail_tree_table_sz);
+ 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..c2d96f4c89 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,8 @@ 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 +232,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..e064687dbd 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,9 @@ enum {
};
static int fetch_prune_config = -1; /* unspecified */
+static int fetch_show_forced_updates = 1;
+static uint64_t forced_updates_ms = 0;
+static int prefetch = 0;
static int prune = -1; /* unspecified */
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
@@ -46,10 +56,15 @@ 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 int atomic_fetch;
static enum transport_family family;
static const char *depth;
static const char *deepen_since;
@@ -66,6 +81,9 @@ 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 negotiate_only;
static int git_fetch_config(const char *k, const char *v, void *cb)
{
@@ -79,6 +97,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 +109,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,8 +143,12 @@ 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_BOOL(0, "atomic", &atomic_fetch,
+ N_("use atomic transaction to update references")),
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
N_("path to upload pack on remote end")),
OPT__FORCE(&force, N_("force overwrite of local reference"), 0),
@@ -124,17 +158,21 @@ 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(0, "prefetch", &prefetch,
+ N_("modify the refspec to place all refs within refs/prefetch/")),
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 +190,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),
@@ -168,7 +206,19 @@ static struct option builtin_fetch_options[] = {
TRANSPORT_FAMILY_IPV6),
OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"),
N_("report that we have only objects reachable from this object")),
+ OPT_BOOL(0, "negotiate-only", &negotiate_only,
+ N_("do not fetch a packfile; instead, print ancestors of negotiation tips")),
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 +275,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 +311,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 +348,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 +372,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 +388,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 +402,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 +420,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 +437,59 @@ 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 void filter_prefetch_refspec(struct refspec *rs)
+{
+ int i;
+
+ if (!prefetch)
+ return;
+
+ for (i = 0; i < rs->nr; i++) {
+ struct strbuf new_dst = STRBUF_INIT;
+ char *old_dst;
+ const char *sub = NULL;
+
+ if (rs->items[i].negative)
+ continue;
+ if (!rs->items[i].dst ||
+ (rs->items[i].src &&
+ !strncmp(rs->items[i].src, "refs/tags/", 10))) {
+ int j;
+
+ free(rs->items[i].src);
+ free(rs->items[i].dst);
+
+ for (j = i + 1; j < rs->nr; j++) {
+ rs->items[j - 1] = rs->items[j];
+ rs->raw[j - 1] = rs->raw[j];
+ }
+ rs->nr--;
+ i--;
+ continue;
+ }
+
+ old_dst = rs->items[i].dst;
+ strbuf_addstr(&new_dst, "refs/prefetch/");
+
+ /*
+ * If old_dst starts with "refs/", then place
+ * sub after that prefix. Otherwise, start at
+ * the beginning of the string.
+ */
+ if (!skip_prefix(old_dst, "refs/", &sub))
+ sub = old_dst;
+ strbuf_addstr(&new_dst, sub);
+
+ rs->items[i].dst = strbuf_detach(&new_dst, NULL);
+ rs->items[i].force = 1;
+
+ free(old_dst);
+ }
}
static struct ref *get_ref_map(struct remote *remote,
@@ -402,6 +506,11 @@ 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;
+
+ filter_prefetch_refspec(rs);
+ if (remote)
+ filter_prefetch_refspec(&remote->fetch);
if (rs->nr) {
struct refspec *fetch_refspec;
@@ -471,7 +580,7 @@ static struct ref *get_ref_map(struct remote *remote,
if (has_merge &&
!strcmp(branch->remote_name, remote->name))
add_merge_config(&ref_map, remote_refs, branch, &tail);
- } else {
+ } else if (!prefetch) {
ref_map = get_remote_ref(remote_refs, "HEAD");
if (!ref_map)
die(_("Couldn't find remote ref HEAD"));
@@ -493,26 +602,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);
+
+ 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_from_hash(&existing_refs,
- strhash(refname),
- refname);
+ 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;
}
@@ -522,13 +646,14 @@ static struct ref *get_ref_map(struct remote *remote,
static int s_update_ref(const char *action,
struct ref *ref,
+ struct ref_transaction *transaction,
int check_old)
{
char *msg;
char *rla = getenv("GIT_REFLOG_ACTION");
- struct ref_transaction *transaction;
+ struct ref_transaction *our_transaction = NULL;
struct strbuf err = STRBUF_INIT;
- int ret, df_conflict = 0;
+ int ret;
if (dry_run)
return 0;
@@ -536,31 +661,47 @@ static int s_update_ref(const char *action,
rla = default_rla.buf;
msg = xstrfmt("%s: %s", rla, action);
- transaction = ref_transaction_begin(&err);
- if (!transaction ||
- ref_transaction_update(transaction, ref->name,
- &ref->new_oid,
- check_old ? &ref->old_oid : NULL,
- 0, msg, &err))
- goto fail;
+ /*
+ * If no transaction was passed to us, we manage the transaction
+ * ourselves. Otherwise, we trust the caller to handle the transaction
+ * lifecycle.
+ */
+ if (!transaction) {
+ transaction = our_transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ ret = STORE_REF_ERROR_OTHER;
+ goto out;
+ }
+ }
- ret = ref_transaction_commit(transaction, &err);
+ ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
+ check_old ? &ref->old_oid : NULL,
+ 0, msg, &err);
if (ret) {
- df_conflict = (ret == TRANSACTION_NAME_CONFLICT);
- goto fail;
+ ret = STORE_REF_ERROR_OTHER;
+ goto out;
}
- ref_transaction_free(transaction);
- strbuf_release(&err);
- free(msg);
- return 0;
-fail:
- ref_transaction_free(transaction);
- error("%s", err.buf);
+ if (our_transaction) {
+ switch (ref_transaction_commit(our_transaction, &err)) {
+ case 0:
+ break;
+ case TRANSACTION_NAME_CONFLICT:
+ ret = STORE_REF_ERROR_DF_CONFLICT;
+ goto out;
+ default:
+ ret = STORE_REF_ERROR_OTHER;
+ goto out;
+ }
+ }
+
+out:
+ ref_transaction_free(our_transaction);
+ if (ret)
+ error("%s", err.buf);
strbuf_release(&err);
free(msg);
- return df_conflict ? STORE_REF_ERROR_DF_CONFLICT
- : STORE_REF_ERROR_OTHER;
+ return ret;
}
static int refcol_width = 10;
@@ -607,7 +748,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"))
@@ -698,6 +839,7 @@ static void format_display(struct strbuf *display, char code,
}
static int update_local_ref(struct ref *ref,
+ struct ref_transaction *transaction,
const char *remote,
const struct ref *remote_ref,
struct strbuf *display,
@@ -707,6 +849,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)
@@ -737,7 +880,7 @@ static int update_local_ref(struct ref *ref,
starts_with(ref->name, "refs/tags/")) {
if (force || ref->force) {
int r;
- r = s_update_ref("updating tag", ref, 0);
+ r = s_update_ref("updating tag", ref, transaction, 0);
format_display(display, r ? '!' : 't', _("[tag update]"),
r ? _("unable to update local ref") : NULL,
remote, pretty_ref, summary_width);
@@ -774,20 +917,29 @@ static int update_local_ref(struct ref *ref,
what = _("[new ref]");
}
- r = s_update_ref(msg, ref, 0);
+ r = s_update_ref(msg, ref, transaction, 0);
format_display(display, r ? '!' : '*', what,
r ? _("unable to update local ref") : NULL,
remote, pretty_ref, summary_width);
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);
- r = s_update_ref("fast-forward", ref, 1);
+ r = s_update_ref("fast-forward", ref, transaction, 1);
format_display(display, r ? '!' : ' ', quickref.buf,
r ? _("unable to update local ref") : NULL,
remote, pretty_ref, summary_width);
@@ -799,7 +951,7 @@ static int update_local_ref(struct ref *ref,
strbuf_add_unique_abbrev(&quickref, &current->object.oid, DEFAULT_ABBREV);
strbuf_addstr(&quickref, "...");
strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV);
- r = s_update_ref("forced-update", ref, 1);
+ r = s_update_ref("forced-update", ref, transaction, 1);
format_display(display, r ? '!' : '+', quickref.buf,
r ? _("unable to update local ref") : _("forced update"),
remote, pretty_ref, summary_width);
@@ -826,23 +978,115 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
return 0;
}
+struct fetch_head {
+ FILE *fp;
+ struct strbuf buf;
+};
+
+static int open_fetch_head(struct fetch_head *fetch_head)
+{
+ const char *filename = git_path_fetch_head(the_repository);
+
+ if (write_fetch_head) {
+ fetch_head->fp = fopen(filename, "a");
+ if (!fetch_head->fp)
+ return error_errno(_("cannot open %s"), filename);
+ strbuf_init(&fetch_head->buf, 0);
+ } else {
+ fetch_head->fp = NULL;
+ }
+
+ return 0;
+}
+
+static void append_fetch_head(struct fetch_head *fetch_head,
+ const struct object_id *old_oid,
+ enum fetch_head_status fetch_head_status,
+ const char *note,
+ const char *url, size_t url_len)
+{
+ char old_oid_hex[GIT_MAX_HEXSZ + 1];
+ const char *merge_status_marker;
+ size_t i;
+
+ if (!fetch_head->fp)
+ return;
+
+ switch (fetch_head_status) {
+ case FETCH_HEAD_NOT_FOR_MERGE:
+ merge_status_marker = "not-for-merge";
+ break;
+ case FETCH_HEAD_MERGE:
+ merge_status_marker = "";
+ break;
+ default:
+ /* do not write anything to FETCH_HEAD */
+ return;
+ }
+
+ strbuf_addf(&fetch_head->buf, "%s\t%s\t%s",
+ oid_to_hex_r(old_oid_hex, old_oid), merge_status_marker, note);
+ for (i = 0; i < url_len; ++i)
+ if ('\n' == url[i])
+ strbuf_addstr(&fetch_head->buf, "\\n");
+ else
+ strbuf_addch(&fetch_head->buf, url[i]);
+ strbuf_addch(&fetch_head->buf, '\n');
+
+ /*
+ * When using an atomic fetch, we do not want to update FETCH_HEAD if
+ * any of the reference updates fails. We thus have to write all
+ * updates to a buffer first and only commit it as soon as all
+ * references have been successfully updated.
+ */
+ if (!atomic_fetch) {
+ strbuf_write(&fetch_head->buf, fetch_head->fp);
+ strbuf_reset(&fetch_head->buf);
+ }
+}
+
+static void commit_fetch_head(struct fetch_head *fetch_head)
+{
+ if (!fetch_head->fp || !atomic_fetch)
+ return;
+ strbuf_write(&fetch_head->buf, fetch_head->fp);
+}
+
+static void close_fetch_head(struct fetch_head *fetch_head)
+{
+ if (!fetch_head->fp)
+ return;
+
+ fclose(fetch_head->fp);
+ strbuf_release(&fetch_head->buf);
+}
+
+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)
{
- FILE *fp;
+ struct fetch_head fetch_head;
struct commit *commit;
int url_len, i, rc = 0;
- struct strbuf note = STRBUF_INIT;
+ struct strbuf note = STRBUF_INIT, err = STRBUF_INIT;
+ struct ref_transaction *transaction = NULL;
const char *what, *kind;
struct ref *rm;
char *url;
- const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(the_repository);
int want_status;
int summary_width = transport_summary_width(ref_map);
- fp = fopen(filename, "a");
- if (!fp)
- return error_errno(_("cannot open %s"), filename);
+ rc = open_fetch_head(&fetch_head);
+ if (rc)
+ return -1;
if (raw_url)
url = transport_anonymize_url(raw_url);
@@ -850,13 +1094,23 @@ 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;
}
}
+ if (atomic_fetch) {
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ error("%s", err.buf);
+ goto abort;
+ }
+ }
+
prepare_format_display(ref_map);
/*
@@ -869,11 +1123,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
want_status++) {
for (rm = ref_map; rm; rm = rm->next) {
struct ref *ref = NULL;
- const char *merge_status_marker = "";
if (rm->status == REF_STATUS_REJECT_SHALLOW) {
if (want_status == FETCH_HEAD_MERGE)
- warning(_("reject %s because shallow roots are not allowed to be updated"),
+ warning(_("rejected %s because shallow roots are not allowed to be updated"),
rm->peer_ref ? rm->peer_ref->name : rm->name);
continue;
}
@@ -894,25 +1147,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;
@@ -931,37 +1180,27 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
strbuf_addf(&note, "%s ", kind);
strbuf_addf(&note, "'%s' of ", what);
}
- switch (rm->fetch_head_status) {
- case FETCH_HEAD_NOT_FOR_MERGE:
- merge_status_marker = "not-for-merge";
- /* fall-through */
- case FETCH_HEAD_MERGE:
- fprintf(fp, "%s\t%s\t%s",
- oid_to_hex(&rm->old_oid),
- merge_status_marker,
- note.buf);
- for (i = 0; i < url_len; ++i)
- if ('\n' == url[i])
- fputs("\\n", fp);
- else
- fputc(url[i], fp);
- fputc('\n', fp);
- break;
- default:
- /* do not write anything to FETCH_HEAD */
- break;
- }
+
+ append_fetch_head(&fetch_head, &rm->old_oid,
+ rm->fetch_head_status,
+ note.buf, url, url_len);
strbuf_reset(&note);
if (ref) {
- rc |= update_local_ref(ref, what, rm, &note,
- summary_width);
+ rc |= update_local_ref(ref, transaction, 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"),
@@ -974,15 +1213,37 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
}
}
+ if (!rc && transaction) {
+ rc = ref_transaction_commit(transaction, &err);
+ if (rc) {
+ error("%s", err.buf);
+ goto abort;
+ }
+ }
+
+ if (!rc)
+ commit_fetch_head(&fetch_head);
+
if (rc & STORE_REF_ERROR_DF_CONFLICT)
error(_("some local refs could not be updated; try running\n"
" '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);
+ strbuf_release(&err);
+ ref_transaction_free(transaction);
free(url);
- fclose(fp);
+ close_fetch_head(&fetch_head);
return rc;
}
@@ -1012,7 +1273,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 +1285,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 +1305,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;
}
@@ -1160,7 +1428,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
if (!has_glob_specials(s)) {
struct object_id oid;
if (get_oid(s, &oid))
- die("%s is not a valid object", s);
+ die(_("%s is not a valid object"), s);
+ if (!has_object(the_repository, &oid, 0))
+ die(_("the object %s does not exist"), s);
oid_array_append(oids, &oid);
continue;
}
@@ -1196,13 +1466,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 +1517,8 @@ 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 transport_ls_refs_options transport_ls_refs_options =
+ TRANSPORT_LS_REFS_OPTIONS_INIT;
int must_list_refs = 1;
if (tags == TAGS_DEFAULT) {
@@ -1261,7 +1529,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;
@@ -1270,7 +1538,7 @@ static int do_fetch(struct transport *transport,
if (rs->nr) {
int i;
- refspec_ref_prefixes(rs, &ref_prefixes);
+ refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);
/*
* We can avoid listing refs if all of them are exact
@@ -1284,20 +1552,25 @@ static int do_fetch(struct transport *transport,
}
}
} else if (transport->remote && transport->remote->fetch.nr)
- refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);
+ refspec_ref_prefixes(&transport->remote->fetch,
+ &transport_ls_refs_options.ref_prefixes);
if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
must_list_refs = 1;
- if (ref_prefixes.argc)
- argv_array_push(&ref_prefixes, "refs/tags/");
+ if (transport_ls_refs_options.ref_prefixes.nr)
+ strvec_push(&transport_ls_refs_options.ref_prefixes,
+ "refs/tags/");
}
- if (must_list_refs)
- remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
- else
+ if (must_list_refs) {
+ trace2_region_enter("fetch", "remote_refs", the_repository);
+ remote_refs = transport_get_remote_refs(transport,
+ &transport_ls_refs_options);
+ trace2_region_leave("fetch", "remote_refs", the_repository);
+ } else
remote_refs = NULL;
- argv_array_clear(&ref_prefixes);
+ strvec_clear(&transport_ls_refs_options.ref_prefixes);
ref_map = get_ref_map(transport->remote, remote_refs, rs,
tags, &autotags);
@@ -1325,6 +1598,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 +1708,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 +1859,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 +1927,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 +1966,34 @@ 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 (negotiate_only && !negotiation_tip.nr)
+ die(_("--negotiate-only needs one or more --negotiate-tip=*"));
if (deepen_relative) {
if (deepen_relative < 0)
@@ -1619,8 +2017,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)
@@ -1652,20 +2051,62 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
}
- if (remote) {
- if (filter_options.choice || repository_format_partial_clone)
+ if (negotiate_only) {
+ struct oidset acked_commits = OIDSET_INIT;
+ struct oidset_iter iter;
+ const struct object_id *oid;
+
+ if (!remote)
+ die(_("must supply remote when using --negotiate-only"));
+ gtransport = prepare_transport(remote, 1);
+ if (gtransport->smart_options) {
+ gtransport->smart_options->acked_commits = &acked_commits;
+ } else {
+ warning(_("Protocol does not support --negotiate-only, exiting."));
+ return 1;
+ }
+ if (server_options.nr)
+ gtransport->server_options = &server_options;
+ result = transport_fetch_refs(gtransport, NULL);
+
+ oidset_iter_init(&acked_commits, &iter);
+ while ((oid = oidset_iter_next(&iter)))
+ printf("%s\n", oid_to_hex(oid));
+ oidset_clear(&acked_commits);
+ } else if (remote) {
+ 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 (atomic_fetch)
+ die(_("--atomic can only be used when fetching "
+ "from one remote"));
+
+ 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 +2116,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..89cb6307d4 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
};
@@ -22,6 +22,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
struct ref_array array;
struct ref_filter filter;
struct ref_format format = REF_FORMAT_INIT;
+ struct strbuf output = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
struct option opts[] = {
OPT_BIT('s', "shell", &format.quote_style,
@@ -70,7 +72,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_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
filter.ignore_case = icase;
filter.name_patterns = argv;
@@ -80,8 +82,20 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
if (!maxcount || array.nr < maxcount)
maxcount = array.nr;
- for (i = 0; i < maxcount; i++)
- show_ref_array_item(array.items[i], &format);
+ for (i = 0; i < maxcount; i++) {
+ strbuf_reset(&err);
+ strbuf_reset(&output);
+ if (format_ref_array_item(array.items[i], &format, &output, &err))
+ die("%s", err.buf);
+ fwrite(output.buf, 1, output.len, stdout);
+ putchar('\n');
+ }
+
+ strbuf_release(&err);
+ strbuf_release(&output);
ref_array_clear(&array);
+ free_commit_list(filter.with_commit);
+ free_commit_list(filter.no_commit);
+ UNLEAK(sorting);
return 0;
}
diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c
new file mode 100644
index 0000000000..fd86e5a861
--- /dev/null
+++ b/builtin/for-each-repo.c
@@ -0,0 +1,59 @@
+#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, int argc, const char ** argv)
+{
+ int i;
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "-C", path, NULL);
+
+ for (i = 0; i < argc; i++)
+ strvec_push(&child.args, argv[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;
+
+ 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>"));
+
+ values = repo_config_get_value_multi(the_repository,
+ config_key);
+
+ /*
+ * Do nothing on an empty list, which is equivalent to the case
+ * where the config variable does not exist at all.
+ */
+ if (!values)
+ return 0;
+
+ for (i = 0; !result && i < values->nr; i++)
+ result = run_command_on_repo(values->items[i].string, argc, argv);
+
+ return result;
+}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index d26fb0a044..b42b6fe21f 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -49,101 +49,68 @@ 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");
return ret;
}
-static int fsck_config(const char *var, const char *value, void *cb)
-{
- if (strcmp(var, "fsck.skiplist") == 0) {
- const char *path;
- struct strbuf sb = STRBUF_INIT;
-
- if (git_config_pathname(&path, var, value))
- return 1;
- strbuf_addf(&sb, "skiplist=%s", path);
- free((char *)path);
- fsck_set_msg_types(&fsck_obj_options, sb.buf);
- strbuf_release(&sb);
- return 0;
- }
-
- if (skip_prefix(var, "fsck.", &var)) {
- fsck_set_msg_type(&fsck_obj_options, var, value);
- return 0;
- }
-
- return git_default_config(var, value, cb);
-}
-
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,
+ enum fsck_msg_type msg_type,
+ enum fsck_msg_id msg_id,
+ 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);
}
}
static struct object_array pending;
-static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options)
+static int mark_object(struct object *obj, enum object_type type,
+ void *data, struct fsck_options *options)
{
struct object *parent = data;
@@ -155,7 +122,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 +148,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;
@@ -227,7 +195,8 @@ static int traverse_reachable(void)
return !!result;
}
-static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options)
+static int mark_used(struct object *obj, enum object_type object_type,
+ void *data, struct fsck_options *options)
{
if (!obj)
return 1;
@@ -238,7 +207,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 +222,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 +261,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 +288,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 +308,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 +327,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 +346,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 +404,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 +418,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 +426,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 +470,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 +539,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 +558,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 +714,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 +726,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(the_repository, oid);
obj->flags |= HAS_OBJ;
}
@@ -830,10 +800,9 @@ 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);
+ git_config(git_fsck_config, &fsck_obj_options);
if (connectivity_only) {
for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
@@ -879,7 +848,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 +859,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;
}
@@ -914,6 +882,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
verify_index_checksum = 1;
verify_ce_order = 1;
read_cache();
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
unsigned int mode;
struct blob *blob;
@@ -928,10 +898,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 +936,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..6ce5ca4512 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,11 @@ 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 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;
@@ -88,7 +91,7 @@ static void process_log_file(void)
*/
int saved_errno = errno;
fprintf(stderr, _("Failed to fstat %s: %s"),
- get_tempfile_path(log_lock.tempfile),
+ get_lock_file_path(&log_lock),
strerror(saved_errno));
fflush(stderr);
commit_lock_file(&log_lock);
@@ -148,7 +151,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);
@@ -160,6 +162,15 @@ static void gc_config(void)
git_config(git_default_config, NULL);
}
+struct maintenance_run_opts;
+static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts)
+{
+ struct strvec pack_refs_cmd = STRVEC_INIT;
+ strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
+
+ return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD);
+}
+
static int too_many_loose_objects(void)
{
/*
@@ -298,7 +309,7 @@ static uint64_t estimate_repack_memory(struct packed_git *pack)
/* and then obj_hash[], underestimated in fact */
heap += sizeof(struct object *) * nr_objects;
/* revindex is used also */
- heap += sizeof(struct revindex_entry) * nr_objects;
+ heap += (sizeof(off_t) + sizeof(uint32_t)) * nr_objects;
/*
* read_sha1_file() (either at delta calculation phase, or
* writing phase) also fills up the delta base cache
@@ -312,18 +323,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 +343,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 +470,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 +526,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 && maintenance_task_pack_refs(NULL))
+ die(FAILED_RUN, "pack-refs");
- 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 +542,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 +556,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 +564,11 @@ 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(&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 +587,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 +612,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 +632,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 +664,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 +695,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 +710,1321 @@ 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_iterated_oid(oid, &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(struct remote *remote, void *cbdata)
+{
+ struct maintenance_run_opts *opts = cbdata;
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ if (remote->skip_default_update)
+ return 0;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "fetch", remote->name,
+ "--prefetch", "--prune", "--no-tags",
+ "--no-write-fetch-head", "--recurse-submodules=no",
+ NULL);
+
+ if (opts->quiet)
+ strvec_push(&child.args, "--quiet");
+
+ return !!run_command(&child);
+}
+
+static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
+{
+ git_config_set_multivar_gently("log.excludedecoration",
+ "refs/prefetch/",
+ "refs/prefetch/",
+ CONFIG_FLAGS_FIXED_VALUE |
+ CONFIG_FLAGS_MULTI_REPLACE);
+
+ if (for_each_remote(fetch_remote, opts)) {
+ error(_("failed to prefetch remotes"));
+ return 1;
+ }
+
+ return 0;
+}
+
+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,
+ TASK_PACK_REFS,
+
+ /* 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,
+ },
+ [TASK_PACK_REFS] = {
+ "pack-refs",
+ maintenance_task_pack_refs,
+ NULL,
+ },
+};
+
+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;
+ tasks[TASK_PACK_REFS].enabled = 1;
+ tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY;
+ }
+}
+
+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 char *get_maintpath(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char *p = the_repository->worktree ?
+ the_repository->worktree : the_repository->gitdir;
+
+ strbuf_realpath(&sb, p, 1);
+ return strbuf_detach(&sb, NULL);
+}
+
+static int maintenance_register(void)
+{
+ int rc;
+ char *config_value;
+ struct child_process config_set = CHILD_PROCESS_INIT;
+ struct child_process config_get = CHILD_PROCESS_INIT;
+ char *maintpath = get_maintpath();
+
+ /* 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",
+ "--fixed-value", "maintenance.repo", maintpath, NULL);
+ config_get.out = -1;
+
+ if (start_command(&config_get)) {
+ rc = error(_("failed to run 'git config'"));
+ goto done;
+ }
+
+ /* We already have this value in our config! */
+ if (!finish_command(&config_get)) {
+ rc = 0;
+ goto done;
+ }
+
+ config_set.git_cmd = 1;
+ strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo",
+ maintpath, NULL);
+
+ rc = run_command(&config_set);
+
+done:
+ free(maintpath);
+ return rc;
+}
+
+static int maintenance_unregister(void)
+{
+ int rc;
+ struct child_process config_unset = CHILD_PROCESS_INIT;
+ char *maintpath = get_maintpath();
+
+ config_unset.git_cmd = 1;
+ strvec_pushl(&config_unset.args, "config", "--global", "--unset",
+ "--fixed-value", "maintenance.repo", maintpath, NULL);
+
+ rc = run_command(&config_unset);
+ free(maintpath);
+ return rc;
+}
+
+static const char *get_frequency(enum schedule_priority schedule)
+{
+ switch (schedule) {
+ case SCHEDULE_HOURLY:
+ return "hourly";
+ case SCHEDULE_DAILY:
+ return "daily";
+ case SCHEDULE_WEEKLY:
+ return "weekly";
+ default:
+ BUG("invalid schedule %d", schedule);
+ }
+}
+
+static char *launchctl_service_name(const char *frequency)
+{
+ struct strbuf label = STRBUF_INIT;
+ strbuf_addf(&label, "org.git-scm.git.%s", frequency);
+ return strbuf_detach(&label, NULL);
+}
+
+static char *launchctl_service_filename(const char *name)
+{
+ char *expanded;
+ struct strbuf filename = STRBUF_INIT;
+ strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name);
+
+ expanded = interpolate_path(filename.buf, 1);
+ if (!expanded)
+ die(_("failed to expand path '%s'"), filename.buf);
+
+ strbuf_release(&filename);
+ return expanded;
+}
+
+static char *launchctl_get_uid(void)
+{
+ return xstrfmt("gui/%d", getuid());
+}
+
+static int launchctl_boot_plist(int enable, const char *filename, const char *cmd)
+{
+ int result;
+ struct child_process child = CHILD_PROCESS_INIT;
+ char *uid = launchctl_get_uid();
+
+ strvec_split(&child.args, cmd);
+ if (enable)
+ strvec_push(&child.args, "bootstrap");
+ else
+ strvec_push(&child.args, "bootout");
+ strvec_push(&child.args, uid);
+ strvec_push(&child.args, filename);
+
+ child.no_stderr = 1;
+ child.no_stdout = 1;
+
+ if (start_command(&child))
+ die(_("failed to start launchctl"));
+
+ result = finish_command(&child);
+
+ free(uid);
+ return result;
+}
+
+static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd)
+{
+ const char *frequency = get_frequency(schedule);
+ char *name = launchctl_service_name(frequency);
+ char *filename = launchctl_service_filename(name);
+ int result = launchctl_boot_plist(0, filename, cmd);
+ unlink(filename);
+ free(filename);
+ free(name);
+ return result;
+}
+
+static int launchctl_remove_plists(const char *cmd)
+{
+ return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) ||
+ launchctl_remove_plist(SCHEDULE_DAILY, cmd) ||
+ launchctl_remove_plist(SCHEDULE_WEEKLY, cmd);
+}
+
+static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+{
+ FILE *plist;
+ int i;
+ const char *preamble, *repeat;
+ const char *frequency = get_frequency(schedule);
+ char *name = launchctl_service_name(frequency);
+ char *filename = launchctl_service_filename(name);
+
+ if (safe_create_leading_directories(filename))
+ die(_("failed to create directories for '%s'"), filename);
+ plist = xfopen(filename, "w");
+
+ preamble = "<?xml version=\"1.0\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">"
+ "<dict>\n"
+ "<key>Label</key><string>%s</string>\n"
+ "<key>ProgramArguments</key>\n"
+ "<array>\n"
+ "<string>%s/git</string>\n"
+ "<string>--exec-path=%s</string>\n"
+ "<string>for-each-repo</string>\n"
+ "<string>--config=maintenance.repo</string>\n"
+ "<string>maintenance</string>\n"
+ "<string>run</string>\n"
+ "<string>--schedule=%s</string>\n"
+ "</array>\n"
+ "<key>StartCalendarInterval</key>\n"
+ "<array>\n";
+ fprintf(plist, preamble, name, exec_path, exec_path, frequency);
+
+ switch (schedule) {
+ case SCHEDULE_HOURLY:
+ repeat = "<dict>\n"
+ "<key>Hour</key><integer>%d</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n";
+ for (i = 1; i <= 23; i++)
+ fprintf(plist, repeat, i);
+ break;
+
+ case SCHEDULE_DAILY:
+ repeat = "<dict>\n"
+ "<key>Day</key><integer>%d</integer>\n"
+ "<key>Hour</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n";
+ for (i = 1; i <= 6; i++)
+ fprintf(plist, repeat, i);
+ break;
+
+ case SCHEDULE_WEEKLY:
+ fprintf(plist,
+ "<dict>\n"
+ "<key>Day</key><integer>0</integer>\n"
+ "<key>Hour</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n");
+ break;
+
+ default:
+ /* unreachable */
+ break;
+ }
+ fprintf(plist, "</array>\n</dict>\n</plist>\n");
+ fclose(plist);
+
+ /* bootout might fail if not already running, so ignore */
+ launchctl_boot_plist(0, filename, cmd);
+ if (launchctl_boot_plist(1, filename, cmd))
+ die(_("failed to bootstrap service %s"), filename);
+
+ free(filename);
+ free(name);
+ return 0;
+}
+
+static int launchctl_add_plists(const char *cmd)
+{
+ const char *exec_path = git_exec_path();
+
+ return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) ||
+ launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) ||
+ launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd);
+}
+
+static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd)
+{
+ if (run_maintenance)
+ return launchctl_add_plists(cmd);
+ else
+ return launchctl_remove_plists(cmd);
+}
+
+static char *schtasks_task_name(const char *frequency)
+{
+ struct strbuf label = STRBUF_INIT;
+ strbuf_addf(&label, "Git Maintenance (%s)", frequency);
+ return strbuf_detach(&label, NULL);
+}
+
+static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd)
+{
+ int result;
+ struct strvec args = STRVEC_INIT;
+ const char *frequency = get_frequency(schedule);
+ char *name = schtasks_task_name(frequency);
+
+ strvec_split(&args, cmd);
+ strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL);
+
+ result = run_command_v_opt(args.v, 0);
+
+ strvec_clear(&args);
+ free(name);
+ return result;
+}
+
+static int schtasks_remove_tasks(const char *cmd)
+{
+ return schtasks_remove_task(SCHEDULE_HOURLY, cmd) ||
+ schtasks_remove_task(SCHEDULE_DAILY, cmd) ||
+ schtasks_remove_task(SCHEDULE_WEEKLY, cmd);
+}
+
+static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+{
+ int result;
+ struct child_process child = CHILD_PROCESS_INIT;
+ const char *xml;
+ struct tempfile *tfile;
+ const char *frequency = get_frequency(schedule);
+ char *name = schtasks_task_name(frequency);
+ struct strbuf tfilename = STRBUF_INIT;
+
+ strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
+ get_git_common_dir(), frequency);
+ tfile = xmks_tempfile(tfilename.buf);
+ strbuf_release(&tfilename);
+
+ if (!fdopen_tempfile(tfile, "w"))
+ die(_("failed to create temp xml file"));
+
+ xml = "<?xml version=\"1.0\" ?>\n"
+ "<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n"
+ "<Triggers>\n"
+ "<CalendarTrigger>\n";
+ fputs(xml, tfile->fp);
+
+ switch (schedule) {
+ case SCHEDULE_HOURLY:
+ fprintf(tfile->fp,
+ "<StartBoundary>2020-01-01T01:00:00</StartBoundary>\n"
+ "<Enabled>true</Enabled>\n"
+ "<ScheduleByDay>\n"
+ "<DaysInterval>1</DaysInterval>\n"
+ "</ScheduleByDay>\n"
+ "<Repetition>\n"
+ "<Interval>PT1H</Interval>\n"
+ "<Duration>PT23H</Duration>\n"
+ "<StopAtDurationEnd>false</StopAtDurationEnd>\n"
+ "</Repetition>\n");
+ break;
+
+ case SCHEDULE_DAILY:
+ fprintf(tfile->fp,
+ "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n"
+ "<Enabled>true</Enabled>\n"
+ "<ScheduleByWeek>\n"
+ "<DaysOfWeek>\n"
+ "<Monday />\n"
+ "<Tuesday />\n"
+ "<Wednesday />\n"
+ "<Thursday />\n"
+ "<Friday />\n"
+ "<Saturday />\n"
+ "</DaysOfWeek>\n"
+ "<WeeksInterval>1</WeeksInterval>\n"
+ "</ScheduleByWeek>\n");
+ break;
+
+ case SCHEDULE_WEEKLY:
+ fprintf(tfile->fp,
+ "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n"
+ "<Enabled>true</Enabled>\n"
+ "<ScheduleByWeek>\n"
+ "<DaysOfWeek>\n"
+ "<Sunday />\n"
+ "</DaysOfWeek>\n"
+ "<WeeksInterval>1</WeeksInterval>\n"
+ "</ScheduleByWeek>\n");
+ break;
+
+ default:
+ break;
+ }
+
+ xml = "</CalendarTrigger>\n"
+ "</Triggers>\n"
+ "<Principals>\n"
+ "<Principal id=\"Author\">\n"
+ "<LogonType>InteractiveToken</LogonType>\n"
+ "<RunLevel>LeastPrivilege</RunLevel>\n"
+ "</Principal>\n"
+ "</Principals>\n"
+ "<Settings>\n"
+ "<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
+ "<Enabled>true</Enabled>\n"
+ "<Hidden>true</Hidden>\n"
+ "<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>\n"
+ "<WakeToRun>false</WakeToRun>\n"
+ "<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n"
+ "<Priority>7</Priority>\n"
+ "</Settings>\n"
+ "<Actions Context=\"Author\">\n"
+ "<Exec>\n"
+ "<Command>\"%s\\git.exe\"</Command>\n"
+ "<Arguments>--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
+ "</Exec>\n"
+ "</Actions>\n"
+ "</Task>\n";
+ fprintf(tfile->fp, xml, exec_path, exec_path, frequency);
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
+ get_tempfile_path(tfile), NULL);
+ close_tempfile_gently(tfile);
+
+ child.no_stdout = 1;
+ child.no_stderr = 1;
+
+ if (start_command(&child))
+ die(_("failed to start schtasks"));
+ result = finish_command(&child);
+
+ delete_tempfile(&tfile);
+ free(name);
+ return result;
+}
+
+static int schtasks_schedule_tasks(const char *cmd)
+{
+ const char *exec_path = git_exec_path();
+
+ return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) ||
+ schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) ||
+ schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd);
+}
+
+static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd)
+{
+ if (run_maintenance)
+ return schtasks_schedule_tasks(cmd);
+ else
+ return schtasks_remove_tasks(cmd);
+}
+
+#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
+#define END_LINE "# END GIT MAINTENANCE SCHEDULE"
+
+static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
+{
+ 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;
+ struct strbuf line = STRBUF_INIT;
+
+ strvec_split(&crontab_list.args, cmd);
+ strvec_push(&crontab_list.args, "-l");
+ crontab_list.in = -1;
+ crontab_list.out = dup(fd);
+ crontab_list.git_cmd = 0;
+
+ if (start_command(&crontab_list))
+ return error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
+
+ /* 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(fd, "r");
+ rewind(cron_list);
+
+ strvec_split(&crontab_edit.args, cmd);
+ crontab_edit.in = -1;
+ crontab_edit.git_cmd = 0;
+
+ if (start_command(&crontab_edit))
+ return error(_("failed to run 'crontab'; your system might not support 'cron'"));
+
+ 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;
+ else if (in_old_region && !strcmp(line.buf, END_LINE))
+ in_old_region = 0;
+ else if (!in_old_region)
+ fprintf(cron_in, "%s\n", line.buf);
+ }
+ strbuf_release(&line);
+
+ 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"));
+ else
+ fclose(cron_list);
+ return result;
+}
+
+#if defined(__APPLE__)
+static const char platform_scheduler[] = "launchctl";
+#elif defined(GIT_WINDOWS_NATIVE)
+static const char platform_scheduler[] = "schtasks";
+#else
+static const char platform_scheduler[] = "crontab";
+#endif
+
+static int update_background_schedule(int enable)
+{
+ int result;
+ const char *scheduler = platform_scheduler;
+ const char *cmd = scheduler;
+ char *testing;
+ struct lock_file lk;
+ char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
+
+ testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
+ if (testing) {
+ char *sep = strchr(testing, ':');
+ if (!sep)
+ die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing);
+ *sep = '\0';
+ scheduler = testing;
+ cmd = sep + 1;
+ }
+
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ result = error(_("another process is scheduling background maintenance"));
+ goto cleanup;
+ }
+
+ if (!strcmp(scheduler, "launchctl"))
+ result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd);
+ else if (!strcmp(scheduler, "schtasks"))
+ result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd);
+ else if (!strcmp(scheduler, "crontab"))
+ result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd);
+ else
+ die("unknown background scheduler: %s", scheduler);
+
+ rollback_lock_file(&lk);
+
+cleanup:
+ free(lock_path);
+ free(testing);
+ 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..7d2f8e5adb 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,24 +200,22 @@ 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);
}
- threads = xcalloc(num_threads, sizeof(*threads));
+ CALLOC_ARRAY(threads, num_threads);
for (i = 0; i < num_threads; i++) {
int err;
struct grep_opt *o = grep_opt_dup(opt);
o->output = strbuf_out;
- if (i)
- o->debug = 0;
compile_grep_patterns(o);
err = pthread_create(&threads[i], NULL, run, o);
@@ -257,12 +255,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 +293,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 +332,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 +358,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 +395,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 +413,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 +451,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 +463,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 +481,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);
@@ -497,8 +504,14 @@ static int grep_cache(struct grep_opt *opt,
if (repo_read_index(repo) < 0)
die(_("index file corrupt"));
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(repo->index);
for (nr = 0; nr < repo->index->cache_nr; nr++) {
const struct cache_entry *ce = repo->index->cache[nr];
+
+ if (!cached && ce_skip_worktree(ce))
+ continue;
+
strbuf_setlen(&name, name_base_len);
strbuf_addstr(&name, ce->name);
@@ -511,8 +524,7 @@ static int grep_cache(struct grep_opt *opt,
* cache entry are identical, even if worktree file has
* been modified, so use cache version instead
*/
- if (cached || (ce->ce_flags & CE_VALID) ||
- ce_skip_worktree(ce)) {
+ if (cached || (ce->ce_flags & CE_VALID)) {
if (ce_stage(ce) || ce_intent_to_add(ce))
continue;
hit |= grep_oid(opt, &ce->oid, name.buf,
@@ -522,7 +534,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 +598,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 +610,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 +636,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 +667,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)) {
@@ -676,10 +704,9 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
int exc_std, int use_index)
{
- struct dir_struct dir;
+ struct dir_struct dir = DIR_INIT;
int i, hit = 0;
- memset(&dir, 0, sizeof(dir));
if (!use_index)
dir.flags |= DIR_NO_GITLINKS;
if (exc_std)
@@ -687,12 +714,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,27 +920,24 @@ 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,
N_("show only matches from files that match all patterns")),
- OPT_SET_INT_F(0, "debug", &opt.debug,
- N_("show parse tree for grep expression"),
- 1, PARSE_OPT_HIDDEN),
OPT_GROUP(""),
{ OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
N_("pager"), N_("show matching files in the pager"),
@@ -926,7 +949,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_END()
};
- init_grep_defaults(the_repository);
git_config(grep_cmd_config, NULL);
grep_init(&opt, the_repository, prefix);
@@ -954,6 +976,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 +1083,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 +1096,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 +1105,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,20 +1145,20 @@ 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();
if (!use_index && (untracked || cached))
die(_("--cached or --untracked cannot be used with --no-index"));
+ if (untracked && cached)
+ die(_("--untracked cannot be used with --cached"));
+
if (!use_index || untracked) {
int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
hit = grep_directory(&opt, &pathspec, use_exclude, use_index);
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..b7eec06c3d 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;
@@ -350,10 +436,9 @@ static void exec_viewer(const char *name, const char *page)
warning(_("'%s': unknown man viewer."), name);
}
-static void show_man_page(const char *git_cmd)
+static void show_man_page(const char *page)
{
struct man_viewer_list *viewer;
- const char *page = cmd_to_page(git_cmd);
const char *fallback = getenv("GIT_MAN_VIEWER");
setup_man_path();
@@ -367,9 +452,8 @@ static void show_man_page(const char *git_cmd)
die(_("no man viewer handled the request"));
}
-static void show_info_page(const char *git_cmd)
+static void show_info_page(const char *page)
{
- const char *page = cmd_to_page(git_cmd);
setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
execlp("info", "info", "gitman", page, (char *)NULL);
die(_("no info viewer handled the request"));
@@ -400,9 +484,8 @@ static void open_html(const char *path)
execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
}
-static void show_html_page(const char *git_cmd)
+static void show_html_page(const char *page)
{
- const char *page = cmd_to_page(git_cmd);
struct strbuf page_path; /* it leaks but we exec bellow */
get_html_page_path(&page_path, page);
@@ -462,6 +545,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
{
int nongit;
enum help_format parsed_help_format;
+ const char *page;
argc = parse_options(argc, argv, prefix, builtin_help_options,
builtin_help_usage, 0);
@@ -493,7 +577,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));
@@ -520,16 +604,17 @@ int cmd_help(int argc, const char **argv, const char *prefix)
argv[0] = check_git_cmd(argv[0]);
+ page = cmd_to_page(argv[0]);
switch (help_format) {
case HELP_FORMAT_NONE:
case HELP_FORMAT_MAN:
- show_man_page(argv[0]);
+ show_man_page(page);
break;
case HELP_FORMAT_INFO:
- show_info_page(argv[0]);
+ show_info_page(page);
break;
case HELP_FORMAT_WEB:
- show_html_page(argv[0]);
+ show_html_page(page);
break;
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 0d55f73b0b..8336466865 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -14,10 +14,10 @@
#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>])";
+"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--[no-]rev-index] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
struct object_entry {
struct pack_idx_entry idx;
@@ -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;
};
@@ -78,7 +120,7 @@ static int nr_threads;
static int from_stdin;
static int strict;
static int do_fsck_object;
-static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
+static struct fsck_options fsck_options = FSCK_OPTIONS_MISSING_GITMODULES;
static int verbose;
static int show_resolving_progress;
static int show_stat;
@@ -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,11 +182,10 @@ 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);
- thread_data = xcalloc(nr_threads, sizeof(*thread_data));
+ CALLOC_ARRAY(thread_data, nr_threads);
for (i = 0; i < nr_threads; i++) {
thread_data[i].pack_fd = open(curr_pack, O_RDONLY);
if (thread_data[i].pack_fd == -1)
@@ -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++)
@@ -176,7 +212,8 @@ static void cleanup_thread(void)
free(thread_data);
}
-static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options)
+static int mark_link(struct object *obj, enum object_type type,
+ void *data, struct fsck_options *options)
{
if (!obj)
return -1;
@@ -332,9 +369,7 @@ static void parse_pack_header(void)
use(sizeof(struct pack_header));
}
-static NORETURN void bad_object(off_t offset, const char *format,
- ...) __attribute__((format (printf, 2, 3)));
-
+__attribute__((format (printf, 2, 3)))
static NORETURN void bad_object(off_t offset, const char *format, ...)
{
va_list params;
@@ -364,56 +399,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)
@@ -466,7 +487,7 @@ static void *unpack_entry_data(off_t offset, unsigned long size,
bad_object(offset, _("inflate returned %d"), status);
git_inflate_end(&stream);
if (oid)
- the_hash_algo->final_fn(oid->hash, &c);
+ the_hash_algo->final_oid_fn(oid, &c);
return buf == fixed_buf ? NULL : buf;
}
@@ -501,7 +522,7 @@ static void *unpack_raw_entry(struct object_entry *obj,
switch (obj->type) {
case OBJ_REF_DELTA:
- hashcpy(ref_oid->hash, fill(the_hash_algo->rawsz));
+ oidread(ref_oid, fill(the_hash_algo->rawsz));
use(the_hash_algo->rawsz);
break;
case OBJ_OFS_DELTA:
@@ -614,7 +635,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 +645,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 +659,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 +689,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 +699,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 +713,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 +778,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 +887,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.
+ * Ensure that this node has been reconstructed and return its contents.
*
- * 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.
- *
- * 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 +912,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 +928,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 +936,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 +969,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 +1009,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 +1238,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 +1253,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);
}
/*
@@ -1321,7 +1356,7 @@ static struct object_entry *append_obj_to_pack(struct hashfile *f,
obj[1].idx.offset += write_compressed(f, buf, size);
obj[0].idx.crc32 = crc32_end(f);
hashflush(f);
- hashcpy(obj->idx.oid.hash, sha1);
+ oidread(&obj->idx.oid, sha1);
return obj;
}
@@ -1352,7 +1387,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,44 +1400,50 @@ 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);
}
-static const char *derive_filename(const char *pack_name, const char *suffix,
- struct strbuf *buf)
+static const char *derive_filename(const char *pack_name, const char *strip,
+ const char *suffix, struct strbuf *buf)
{
size_t len;
- if (!strip_suffix(pack_name, ".pack", &len))
- die(_("packfile name '%s' does not end with '.pack'"),
- pack_name);
+ if (!strip_suffix(pack_name, strip, &len) || !len ||
+ pack_name[len - 1] != '.')
+ die(_("packfile name '%s' does not end with '.%s'"),
+ pack_name, strip);
strbuf_add(buf, pack_name, len);
- strbuf_addch(buf, '.');
strbuf_addstr(buf, suffix);
return buf->buf;
}
@@ -1417,7 +1458,7 @@ static void write_special_file(const char *suffix, const char *msg,
int msg_len = strlen(msg);
if (pack_name)
- filename = derive_filename(pack_name, suffix, &name_buf);
+ filename = derive_filename(pack_name, "pack", suffix, &name_buf);
else
filename = odb_pack_name(&name_buf, hash, suffix);
@@ -1442,12 +1483,14 @@ static void write_special_file(const char *suffix, const char *msg,
static void final(const char *final_pack_name, const char *curr_pack_name,
const char *final_index_name, const char *curr_index_name,
+ const char *final_rev_index_name, const char *curr_rev_index_name,
const char *keep_msg, const char *promisor_msg,
unsigned char *hash)
{
const char *report = "pack";
struct strbuf pack_name = STRBUF_INIT;
struct strbuf index_name = STRBUF_INIT;
+ struct strbuf rev_index_name = STRBUF_INIT;
int err;
if (!from_stdin) {
@@ -1482,6 +1525,16 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
} else
chmod(final_index_name, 0444);
+ if (curr_rev_index_name) {
+ if (final_rev_index_name != curr_rev_index_name) {
+ if (!final_rev_index_name)
+ final_rev_index_name = odb_pack_name(&rev_index_name, hash, "rev");
+ if (finalize_object_file(curr_rev_index_name, final_rev_index_name))
+ die(_("cannot store reverse index file"));
+ } else
+ chmod(final_rev_index_name, 0444);
+ }
+
if (do_fsck_object) {
struct packed_git *p;
p = add_packed_git(final_index_name, strlen(final_index_name), 0);
@@ -1490,11 +1543,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);
@@ -1511,6 +1564,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
}
}
+ strbuf_release(&rev_index_name);
strbuf_release(&index_name);
strbuf_release(&pack_name);
}
@@ -1536,6 +1590,12 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)
}
return 0;
}
+ if (!strcmp(k, "pack.writereverseindex")) {
+ if (git_config_bool(k, v))
+ opts->flags |= WRITE_REV;
+ else
+ opts->flags &= ~WRITE_REV;
+ }
return git_default_config(k, v, cb);
}
@@ -1552,14 +1612,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 */
@@ -1603,7 +1659,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
/*
* Get rid of the idx file as we do not need it anymore.
* NEEDSWORK: extract this bit from free_pack_by_name() in
- * sha1-file.c, perhaps? It shouldn't matter very much as we
+ * object-file.c, perhaps? It shouldn't matter very much as we
* know we haven't installed this pack (hence we never have
* read anything from it).
*/
@@ -1617,7 +1673,7 @@ static void show_pack_info(int stat_only)
unsigned long *chain_histogram = NULL;
if (deepest_delta)
- chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long));
+ CALLOC_ARRAY(chain_histogram, deepest_delta);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
@@ -1657,17 +1713,20 @@ static void show_pack_info(int stat_only)
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
- int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
+ int i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index;
const char *curr_index;
- const char *index_name = NULL, *pack_name = NULL;
+ const char *curr_rev_index = NULL;
+ const char *index_name = NULL, *pack_name = NULL, *rev_index_name = NULL;
const char *keep_msg = NULL;
const char *promisor_msg = NULL;
struct strbuf index_name_buf = STRBUF_INIT;
+ struct strbuf rev_index_name_buf = STRBUF_INIT;
struct pack_idx_entry **idx_objects;
struct pack_idx_option opts;
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
@@ -1688,6 +1747,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (prefix && chdir(prefix))
die(_("Cannot come back to cwd"));
+ if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0))
+ rev_index = 1;
+ else
+ rev_index = !!(opts.flags & (WRITE_REV_VERIFY | WRITE_REV));
+
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -1761,6 +1825,15 @@ 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 if (!strcmp(arg, "--rev-index")) {
+ rev_index = 1;
+ } else if (!strcmp(arg, "--no-rev-index")) {
+ rev_index = 0;
} else
usage(index_pack_usage);
continue;
@@ -1777,8 +1850,19 @@ 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);
+ index_name = derive_filename(pack_name, "pack", "idx", &index_name_buf);
+
+ opts.flags &= ~(WRITE_REV | WRITE_REV_VERIFY);
+ if (rev_index) {
+ opts.flags |= verify ? WRITE_REV_VERIFY : WRITE_REV;
+ if (index_name)
+ rev_index_name = derive_filename(index_name,
+ "idx", "rev",
+ &rev_index_name_buf);
+ }
if (verify) {
if (!index_name)
@@ -1791,17 +1875,30 @@ 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);
parse_pack_header();
- objects = xcalloc(st_add(nr_objects, 1), sizeof(struct object_entry));
+ CALLOC_ARRAY(objects, st_add(nr_objects, 1));
if (show_stat)
- obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct object_stat));
- ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry));
+ CALLOC_ARRAY(obj_stat, st_add(nr_objects, 1));
+ CALLOC_ARRAY(ofs_deltas, nr_objects);
parse_pack_objects(pack_hash);
if (report_end_of_input)
write_in_full(2, "\0", 1);
@@ -1819,11 +1916,16 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
for (i = 0; i < nr_objects; i++)
idx_objects[i] = &objects[i].idx;
curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_hash);
+ if (rev_index)
+ curr_rev_index = write_rev_file(rev_index_name, idx_objects,
+ nr_objects, pack_hash,
+ opts.flags);
free(idx_objects);
if (!verify)
final(pack_name, curr_pack,
index_name, curr_index,
+ rev_index_name, curr_rev_index,
keep_msg, promisor_msg,
pack_hash);
else
@@ -1834,10 +1936,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
free(objects);
strbuf_release(&index_name_buf);
+ strbuf_release(&rev_index_name_buf);
if (pack_name == NULL)
free((void *) curr_pack);
if (index_name == NULL)
free((void *) curr_index);
+ if (rev_index_name == NULL)
+ free((void *) curr_rev_index);
/*
* Let the caller know this pack is not self contained
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 944ec77fe1..2167796ff2 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,9 +21,10 @@
#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;
static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
DIR *dir)
@@ -91,7 +93,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
}
}
-static void copy_templates(const char *template_dir)
+static void copy_templates(const char *template_dir, const char *init_template_dir)
{
struct strbuf path = STRBUF_INIT;
struct strbuf template_path = STRBUF_INIT;
@@ -104,7 +106,7 @@ static void copy_templates(const char *template_dir)
if (!template_dir)
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
if (!template_dir)
- template_dir = init_db_template_dir;
+ template_dir = init_template_dir;
if (!template_dir)
template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR);
if (!template_dir[0]) {
@@ -151,17 +153,6 @@ free_return:
clear_repository_format(&template_format);
}
-static int git_init_db_config(const char *k, const char *v, void *cb)
-{
- if (!strcmp(k, "init.templatedir"))
- return git_config_pathname(&init_db_template_dir, k, v);
-
- if (starts_with(k, "core."))
- return platform_core_config(k, v, cb);
-
- return 0;
-}
-
/*
* If the git_dir is not directly inside the working tree, then git will not
* find it by default, and we need to set the worktree explicitly.
@@ -176,21 +167,41 @@ 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,
+ int quiet)
{
struct stat st1;
struct strbuf buf = STRBUF_INIT;
char *path;
- char repo_version_string[10];
char junk[2];
int reinit;
int filemode;
struct strbuf err = STRBUF_INIT;
-
- /* Just look for `init.templatedir` */
- init_db_template_dir = NULL; /* re-set in case it was set before */
- git_config(git_init_db_config, NULL);
+ const char *init_template_dir = NULL;
+ const char *work_tree = get_git_work_tree();
/*
* First copy the templates -- we might have the default
@@ -201,7 +212,9 @@ static int create_default_files(const char *template_path,
* values (since we've just potentially changed what's available on
* disk).
*/
- copy_templates(template_path);
+ git_config_get_pathname("init.templatedir", &init_template_dir);
+ copy_templates(template_path, init_template_dir);
+ free((char *)init_template_dir);
git_config_clear();
reset_shared_repository();
git_config(git_default_config, NULL);
@@ -210,7 +223,7 @@ static int create_default_files(const char *template_path,
* We must make sure command-line options continue to override any
* values we might have just re-read from the config.
*/
- is_bare_repository_cfg = init_is_bare_repository;
+ is_bare_repository_cfg = init_is_bare_repository || !work_tree;
if (init_shared_repository != -1)
set_shared_repository(init_shared_repository);
@@ -233,21 +246,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(quiet);
+
+ 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");
@@ -266,7 +287,6 @@ static int create_default_files(const char *template_path,
if (is_bare_repository())
git_config_set("core.bare", "true");
else {
- const char *work_tree = get_git_work_tree();
git_config_set("core.bare", "false");
/* allow template config file to override the default */
if (log_all_ref_updates == LOG_REFS_UNSET)
@@ -335,17 +355,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,18 +399,18 @@ 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;
- /* Just look for `core.hidedotfiles` */
- git_config(git_init_db_config, NULL);
+ /* Ensure `core.hidedotfiles` is processed */
+ git_config(platform_core_config, NULL);
safe_create_dir(git_dir, 0);
@@ -378,9 +421,16 @@ 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);
+
+ validate_hash_algorithm(&repo_fmt, hash);
- reinit = create_default_files(template_dir, original_git_dir);
+ reinit = create_default_files(template_dir, original_git_dir,
+ initial_branch, &repo_fmt,
+ flags & INIT_DB_QUIET);
+ if (reinit && initial_branch)
+ warning(_("re-init: ignored --initial-branch=%s"),
+ initial_branch);
create_object_directory();
@@ -482,6 +532,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,16 +547,25 @@ 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);
- if (template_dir && *template_dir && !is_absolute_path(template_dir))
+ if (template_dir && *template_dir && !is_absolute_path(template_dir)) {
template_dir = absolute_pathdup(template_dir);
+ UNLEAK(template_dir);
+ }
if (argc == 1) {
int mkdir_tried = 0;
@@ -546,6 +608,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 +635,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 +680,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 +691,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..3d7717ba5c 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -12,6 +12,7 @@
#include "color.h"
#include "commit.h"
#include "diff.h"
+#include "diff-merges.h"
#include "revision.h"
#include "log-tree.h"
#include "builtin.h"
@@ -33,10 +34,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 +47,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 +67,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)
@@ -125,7 +134,6 @@ static int log_line_range_callback(const struct option *option, const char *arg,
static void init_log_defaults(void)
{
- init_grep_defaults(the_repository);
init_diff_ui_defaults();
decoration_style = auto_decoration_style();
@@ -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_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)
@@ -215,7 +231,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
if (mailmap) {
rev->mailmap = xcalloc(1, sizeof(struct string_list));
- read_mailmap(rev->mailmap, NULL);
+ read_mailmap(rev->mailmap);
}
if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) {
@@ -229,8 +245,23 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
rev->abbrev_commit = 0;
}
+ if (rev->commit_format == CMIT_FMT_USERFORMAT && !w.decorate)
+ decoration_style = 0;
+
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);
}
@@ -279,10 +310,11 @@ static struct itimerval early_output_timer;
static void log_show_early(struct rev_info *revs, struct commit_list *list)
{
- int i = revs->early_output, close_file = revs->diffopt.close_file;
+ int i = revs->early_output;
int show_header = 1;
+ int no_free = revs->diffopt.no_free;
- revs->diffopt.close_file = 0;
+ revs->diffopt.no_free = 0;
sort_in_topological_order(&list, revs->sort_order);
while (list && i) {
struct commit *commit = list->item;
@@ -299,8 +331,8 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
case commit_ignore:
break;
case commit_error:
- if (close_file)
- fclose(revs->diffopt.file);
+ revs->diffopt.no_free = no_free;
+ diff_free(&revs->diffopt);
return;
}
list = list->next;
@@ -308,8 +340,8 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
/* Did we already get enough commits for the early output? */
if (!i) {
- if (close_file)
- fclose(revs->diffopt.file);
+ revs->diffopt.no_free = 0;
+ diff_free(&revs->diffopt);
return;
}
@@ -373,7 +405,7 @@ static int cmd_log_walk(struct rev_info *rev)
{
struct commit *commit;
int saved_nrl = 0;
- int saved_dcctc = 0, close_file = rev->diffopt.close_file;
+ int saved_dcctc = 0;
if (rev->early_output)
setup_early_output();
@@ -389,7 +421,7 @@ static int cmd_log_walk(struct rev_info *rev)
* and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
* retain that state information if replacing rev->diffopt in this loop
*/
- rev->diffopt.close_file = 0;
+ rev->diffopt.no_free = 1;
while ((commit = get_revision(rev)) != NULL) {
if (!log_tree_commit(rev, commit) && rev->max_count >= 0)
/*
@@ -414,8 +446,8 @@ static int cmd_log_walk(struct rev_info *rev)
}
rev->diffopt.degraded_cc_to_c = saved_dcctc;
rev->diffopt.needed_rename_limit = saved_nrl;
- if (close_file)
- fclose(rev->diffopt.file);
+ rev->diffopt.no_free = 0;
+ diff_free(&rev->diffopt);
if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
rev->diffopt.flags.check_failed) {
@@ -432,6 +464,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;
@@ -444,6 +484,8 @@ static int git_log_config(const char *var, const char *value, void *cb)
decoration_style = 0; /* maybe warn? */
return 0;
}
+ if (!strcmp(var, "log.diffmerges"))
+ return diff_merges_config(value);
if (!strcmp(var, "log.showroot")) {
default_show_root = git_config_bool(var, value);
return 0;
@@ -562,7 +604,7 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev)
static int show_tree_object(const struct object_id *oid,
struct strbuf *base,
- const char *pathname, unsigned mode, int stage, void *context)
+ const char *pathname, unsigned mode, void *context)
{
FILE *file = context;
fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
@@ -572,15 +614,10 @@ 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 */
- rev->ignore_merges = 0;
- if (!rev->first_parent_only && !rev->combine_merges) {
- /* No "--first-parent", "-c", or "--cc" */
- rev->combine_merges = 1;
- rev->dense_combined_merges = 1;
- }
- }
+ if (rev->first_parent_only)
+ diff_merges_default_to_first_parent(rev);
+ else
+ diff_merges_default_to_dense_combined(rev);
if (!rev->diffopt.output_format)
rev->diffopt.output_format = DIFF_FORMAT_PATCH;
}
@@ -622,6 +659,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 +671,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;
@@ -648,9 +686,9 @@ int cmd_show(int argc, const char **argv, const char *prefix)
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
name,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- read_tree_recursive(the_repository, (struct tree *)o, "",
- 0, 0, &match_all, show_tree_object,
- rev.diffopt.file);
+ read_tree(the_repository, (struct tree *)o,
+ &match_all, show_tree_object,
+ rev.diffopt.file);
rev.shown_one = 1;
break;
case OBJ_COMMIT:
@@ -700,13 +738,8 @@ static void log_setup_revisions_tweak(struct rev_info *rev,
rev->prune_data.nr == 1)
rev->diffopt.flags.follow_renames = 1;
- /* Turn --cc/-c into -p --cc/-c when -p was not given */
- 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)
- rev->ignore_merges = 0;
+ if (rev->first_parent_only)
+ diff_merges_default_to_first_parent(rev);
}
int cmd_log(int argc, const char **argv, const char *prefix)
@@ -759,23 +792,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 +897,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 +919,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 +937,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 +962,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 +1051,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 +1071,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 +1097,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 +1174,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 +1190,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 +1206,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 +1218,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 +1228,22 @@ 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;
+ struct range_diff_options range_diff_opts = {
+ .creation_factor = rev->creation_factor,
+ .dual_color = 1,
+ .diffopt = &opts,
+ .other_arg = &other_arg
+ };
+
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);
- show_range_diff(rev->rdiff1, rev->rdiff2,
- rev->creation_factor, 1, &opts);
+ get_notes_args(&other_arg, rev);
+ show_range_diff(rev->rdiff1, rev->rdiff2, &range_diff_opts);
+ strvec_clear(&other_arg);
}
}
@@ -1225,9 +1355,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 +1404,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 +1442,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 +1471,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;
+
+ 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 (base_commit && strcmp(base_commit, "auto")) {
+ 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 +1508,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 +1550,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 +1567,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 +1636,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;
@@ -1466,13 +1667,19 @@ static void print_bases(struct base_tree_info *bases, FILE *file)
oidclr(&bases->base_commit);
}
-static const char *diff_title(struct strbuf *sb, int reroll_count,
- const char *generic, const char *rerolled)
+static const char *diff_title(struct strbuf *sb,
+ const char *reroll_count,
+ const char *generic,
+ const char *rerolled)
{
- if (reroll_count <= 0)
+ int v;
+
+ /* RFC may be v0, so allow -v1 to diff against v0 */
+ if (reroll_count && !strtol_i(reroll_count, 10, &v) &&
+ v >= 1)
+ strbuf_addf(sb, rerolled, v - 1);
+ else
strbuf_addstr(sb, generic);
- else /* RFC may be v0, so allow -v1 to diff against v0 */
- strbuf_addf(sb, rerolled, reroll_count - 1);
return sb->buf;
}
@@ -1483,16 +1690,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 = is_range_diff_range(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);
}
}
@@ -1517,10 +1728,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
struct strbuf buf = STRBUF_INIT;
int use_patch_format = 0;
int quiet = 0;
- int reroll_count = -1;
+ const char *reroll_count = NULL;
+ 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 +1745,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,
@@ -1549,20 +1762,25 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
N_("use <sfx> instead of '.patch'")),
OPT_INTEGER(0, "start-number", &start_number,
N_("start numbering patches at <n> instead of 1")),
- OPT_INTEGER('v', "reroll-count", &reroll_count,
+ OPT_STRING('v', "reroll-count", &reroll_count, N_("reroll-count"),
N_("mark the series as Nth re-roll")),
- { OPTION_CALLBACK, 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"),
- N_("Use [<prefix>] instead of [PATCH]"),
- PARSE_OPT_NONEG, subject_prefix_callback },
- { OPTION_CALLBACK, 'o', "output-directory", &output_directory,
+ 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),
+ 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),
+ 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 +1791,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 +1834,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,9 +1866,17 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
PARSE_OPT_KEEP_DASHDASH);
- if (0 < reroll_count) {
+ /* 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 (reroll_count) {
struct strbuf sprefix = STRBUF_INIT;
- strbuf_addf(&sprefix, "%s v%d",
+
+ strbuf_addf(&sprefix, "%s v%s",
rev.subject_prefix, reroll_count);
rev.reroll_count = reroll_count;
rev.subject_prefix = strbuf_detach(&sprefix, NULL);
@@ -1726,26 +1955,49 @@ 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 we must instruct it not to close after each diff.
+ */
+ rev.diffopt.no_free = 1;
+ } 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 +2126,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 +2144,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 +2199,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 +2212,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 +2223,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..29a26ad8ae 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -12,6 +12,7 @@
#include "dir.h"
#include "builtin.h"
#include "tree.h"
+#include "cache-tree.h"
#include "parse-options.h"
#include "resolve-undo.h"
#include "string-list.h"
@@ -35,6 +36,7 @@ static int line_terminator = '\n';
static int debug_mode;
static int show_eol;
static int recurse_submodules;
+static int skipping_duplicates;
static const char *prefix;
static int max_prefix_len;
@@ -55,7 +57,7 @@ static const char *tag_modified = "";
static const char *tag_skip_worktree = "";
static const char *tag_resolve_undo = "";
-static void write_eolinfo(const struct index_state *istate,
+static void write_eolinfo(struct index_state *istate,
const struct cache_entry *ce, const char *path)
{
if (show_eol) {
@@ -120,7 +122,7 @@ static void print_debug(const struct cache_entry *ce)
}
}
-static void show_dir_entry(const struct index_state *istate,
+static void show_dir_entry(struct index_state *istate,
const char *tag, struct dir_entry *ent)
{
int len = max_prefix_len;
@@ -128,15 +130,16 @@ 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);
write_name(ent->name);
}
-static void show_other_files(const struct index_state *istate,
+static void show_other_files(struct index_state *istate,
const struct dir_struct *dir)
{
int i;
@@ -149,7 +152,7 @@ static void show_other_files(const struct index_state *istate,
}
}
-static void show_killed_files(const struct index_state *istate,
+static void show_killed_files(struct index_state *istate,
const struct dir_struct *dir)
{
int i;
@@ -207,7 +210,7 @@ static void show_submodule(struct repository *superproject,
{
struct repository subrepo;
const struct submodule *sub = submodule_from_path(superproject,
- &null_oid, path);
+ null_oid(), path);
if (repo_submodule_init(&subrepo, superproject, sub))
return;
@@ -251,7 +254,7 @@ static void show_ce(struct repository *repo, struct dir_struct *dir,
}
}
-static void show_ru_info(const struct index_state *istate)
+static void show_ru_info(struct index_state *istate)
{
struct string_list_item *item;
@@ -311,45 +314,61 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
if (show_killed)
show_killed_files(repo->index, dir);
}
- if (show_cached || show_stage) {
- for (i = 0; i < repo->index->cache_nr; i++) {
- const struct cache_entry *ce = repo->index->cache[i];
- construct_fullname(&fullname, repo, ce);
+ if (!(show_cached || show_stage || show_deleted || show_modified))
+ return;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(repo->index);
+ for (i = 0; i < repo->index->cache_nr; i++) {
+ const struct cache_entry *ce = repo->index->cache[i];
+ struct stat st;
+ int stat_err;
- if ((dir->flags & DIR_SHOW_IGNORED) &&
- !ce_excluded(dir, repo->index, fullname.buf, ce))
- continue;
- if (show_unmerged && !ce_stage(ce))
- continue;
- if (ce->ce_flags & CE_UPDATE)
- continue;
+ construct_fullname(&fullname, repo, ce);
+
+ if ((dir->flags & DIR_SHOW_IGNORED) &&
+ !ce_excluded(dir, repo->index, fullname.buf, ce))
+ continue;
+ if (ce->ce_flags & CE_UPDATE)
+ continue;
+ if ((show_cached || show_stage) &&
+ (!show_unmerged || ce_stage(ce))) {
show_ce(repo, dir, ce, fullname.buf,
ce_stage(ce) ? tag_unmerged :
(ce_skip_worktree(ce) ? tag_skip_worktree :
tag_cached));
+ if (skipping_duplicates)
+ goto skip_to_next_name;
}
- }
- if (show_deleted || show_modified) {
- for (i = 0; i < repo->index->cache_nr; i++) {
- const struct cache_entry *ce = repo->index->cache[i];
- struct stat st;
- int err;
- construct_fullname(&fullname, repo, ce);
-
- if ((dir->flags & DIR_SHOW_IGNORED) &&
- !ce_excluded(dir, repo->index, fullname.buf, ce))
- continue;
- if (ce->ce_flags & CE_UPDATE)
- continue;
- if (ce_skip_worktree(ce))
- continue;
- err = lstat(fullname.buf, &st);
- if (show_deleted && err)
- show_ce(repo, dir, ce, fullname.buf, tag_removed);
- if (show_modified && ie_modified(repo->index, ce, &st, 0))
- show_ce(repo, dir, ce, fullname.buf, tag_modified);
+ if (!(show_deleted || show_modified))
+ continue;
+ if (ce_skip_worktree(ce))
+ continue;
+ stat_err = lstat(fullname.buf, &st);
+ if (stat_err && (errno != ENOENT && errno != ENOTDIR))
+ error_errno("cannot lstat '%s'", fullname.buf);
+ if (stat_err && show_deleted) {
+ show_ce(repo, dir, ce, fullname.buf, tag_removed);
+ if (skipping_duplicates)
+ goto skip_to_next_name;
+ }
+ if (show_modified &&
+ (stat_err || ie_modified(repo->index, ce, &st, 0))) {
+ show_ce(repo, dir, ce, fullname.buf, tag_modified);
+ if (skipping_duplicates)
+ goto skip_to_next_name;
+ }
+ continue;
+
+skip_to_next_name:
+ {
+ int j;
+ struct cache_entry **cache = repo->index->cache;
+ for (j = i + 1; j < repo->index->cache_nr; j++)
+ if (strcmp(ce->name, cache[j]->name))
+ break;
+ i = j - 1; /* compensate for the for loop */
}
}
@@ -404,6 +423,53 @@ static int get_common_prefix_len(const char *common_prefix)
return common_prefix_len;
}
+static int read_one_entry_opt(struct index_state *istate,
+ const struct object_id *oid,
+ struct strbuf *base,
+ const char *pathname,
+ unsigned mode, int opt)
+{
+ int len;
+ struct cache_entry *ce;
+
+ if (S_ISDIR(mode))
+ return READ_TREE_RECURSIVE;
+
+ len = strlen(pathname);
+ ce = make_empty_cache_entry(istate, base->len + len);
+
+ ce->ce_mode = create_ce_mode(mode);
+ ce->ce_flags = create_ce_flags(1);
+ ce->ce_namelen = base->len + len;
+ memcpy(ce->name, base->buf, base->len);
+ memcpy(ce->name + base->len, pathname, len+1);
+ oidcpy(&ce->oid, oid);
+ return add_index_entry(istate, ce, opt);
+}
+
+static int read_one_entry(const struct object_id *oid, struct strbuf *base,
+ const char *pathname, unsigned mode,
+ void *context)
+{
+ struct index_state *istate = context;
+ return read_one_entry_opt(istate, oid, base, pathname,
+ mode,
+ ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+}
+
+/*
+ * This is used when the caller knows there is no existing entries at
+ * the stage that will conflict with the entry being added.
+ */
+static int read_one_entry_quick(const struct object_id *oid, struct strbuf *base,
+ const char *pathname, unsigned mode,
+ void *context)
+{
+ struct index_state *istate = context;
+ return read_one_entry_opt(istate, oid, base, pathname,
+ mode, ADD_CACHE_JUST_APPEND);
+}
+
/*
* Read the tree specified with --with-tree option
* (typically, HEAD) into stage #1 and then
@@ -420,6 +486,8 @@ void overlay_tree_on_index(struct index_state *istate,
struct pathspec pathspec;
struct cache_entry *last_stage0 = NULL;
int i;
+ read_tree_fn_t fn = NULL;
+ int err;
if (get_oid(tree_name, &oid))
die("tree-ish %s not found.", tree_name);
@@ -428,6 +496,8 @@ void overlay_tree_on_index(struct index_state *istate,
die("bad tree-ish %s", tree_name);
/* Hoist the unmerged entries up to stage #3 to make room */
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(istate);
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
if (!ce_stage(ce))
@@ -442,9 +512,32 @@ void overlay_tree_on_index(struct index_state *istate,
PATHSPEC_PREFER_CWD, prefix, matchbuf);
} else
memset(&pathspec, 0, sizeof(pathspec));
- if (read_tree(the_repository, tree, 1, &pathspec, istate))
+
+ /*
+ * See if we have cache entry at the stage. If so,
+ * do it the original slow way, otherwise, append and then
+ * sort at the end.
+ */
+ for (i = 0; !fn && i < istate->cache_nr; i++) {
+ const struct cache_entry *ce = istate->cache[i];
+ if (ce_stage(ce) == 1)
+ fn = read_one_entry;
+ }
+
+ if (!fn)
+ fn = read_one_entry_quick;
+ err = read_tree(the_repository, tree, &pathspec, fn, istate);
+ if (err)
die("unable to read tree entries %s", tree_name);
+ /*
+ * Sort the cache entry -- we need to nuke the cache tree, though.
+ */
+ if (fn == read_one_entry_quick) {
+ cache_tree_free(&istate->cache_tree);
+ QSORT(istate->cache, istate->cache_nr, cmp_cache_name_compare);
+ }
+
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
switch (ce_stage(ce)) {
@@ -492,7 +585,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;
}
@@ -514,9 +607,9 @@ static int option_parse_exclude_standard(const struct option *opt,
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;
+ char *max_prefix;
+ struct dir_struct dir = DIR_INIT;
+ 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 +647,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),
@@ -577,13 +670,14 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
N_("pretend that paths removed since <tree-ish> are still present")),
OPT__ABBREV(&abbrev),
OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")),
+ OPT_BOOL(0, "deduplicate", &skipping_duplicates,
+ N_("suppress duplicate entries")),
OPT_END()
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(ls_files_usage, builtin_ls_files_options);
- memset(&dir, 0, sizeof(dir));
prefix = cmd_prefix;
if (prefix)
prefix_len = strlen(prefix);
@@ -594,9 +688,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 ";
@@ -616,6 +710,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
* you also show the stage information.
*/
show_stage = 1;
+ if (show_tag || show_stage)
+ skipping_duplicates = 0;
if (dir.exclude_per_dir)
exc_given = 1;
@@ -655,6 +751,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (pathspec.nr && error_unmatch)
ps_matched = xcalloc(pathspec.nr, 1);
+ if ((dir.flags & DIR_SHOW_IGNORED) && !show_others && !show_cached)
+ die("ls-files -i must be used with either -o or -c");
+
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
die("ls-files --ignored needs some exclude pattern");
@@ -687,6 +786,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
return bad ? 1 : 0;
}
- UNLEAK(dir);
+ dir_clear(&dir);
+ free(max_prefix);
return 0;
}
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 6ef519514b..1794548c71 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -45,7 +45,8 @@ 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 transport_ls_refs_options transport_options =
+ TRANSPORT_LS_REFS_OPTIONS_INIT;
int i;
struct string_list server_options = STRING_LIST_INIT_DUP;
@@ -83,18 +84,20 @@ 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 *));
+ CALLOC_ARRAY(pattern, argc);
for (i = 1; i < argc; i++) {
pattern[i - 1] = xstrfmt("*/%s", argv[i]);
}
}
if (flags & REF_TAGS)
- argv_array_push(&ref_prefixes, "refs/tags/");
+ strvec_push(&transport_options.ref_prefixes, "refs/tags/");
if (flags & REF_HEADS)
- argv_array_push(&ref_prefixes, "refs/heads/");
+ strvec_push(&transport_options.ref_prefixes, "refs/heads/");
remote = remote_get(dest);
if (!remote) {
@@ -107,7 +110,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;
}
@@ -117,10 +119,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
if (server_options.nr)
transport->server_options = &server_options;
- ref = transport_get_remote_refs(transport, &ref_prefixes);
- if (transport_disconnect(transport)) {
- UNLEAK(sorting);
- return 1;
+ ref = transport_get_remote_refs(transport, &transport_options);
+ if (ref) {
+ int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
+ repo_set_hash_algo(the_repository, hash_algo);
}
if (!dest && !quiet)
@@ -146,7 +148,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
status = 0; /* we found something */
}
- UNLEAK(sorting);
ref_array_clear(&ref_array);
+ if (transport_disconnect(transport))
+ return 1;
return status;
}
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 7cad3f24eb..3a442631c7 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -62,7 +62,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
}
static int show_tree(const struct object_id *oid, struct strbuf *base,
- const char *pathname, unsigned mode, int stage, void *context)
+ const char *pathname, unsigned mode, void *context)
{
int retval = 0;
int baselen;
@@ -185,6 +185,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
tree = parse_tree_indirect(&oid);
if (!tree)
die("not a tree object");
- return !!read_tree_recursive(the_repository, tree, "", 0, 0,
- &pathspec, show_tree, NULL);
+ return !!read_tree(the_repository, tree,
+ &pathspec, show_tree, NULL);
}
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index cfb667a594..01d16ef9e5 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -7,54 +7,103 @@
#include "utf8.h"
#include "strbuf.h"
#include "mailinfo.h"
+#include "parse-options.h"
-static const char mailinfo_usage[] =
- "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info";
+static const char * const mailinfo_usage[] = {
+ /* TRANSLATORS: keep <> in "<" mail ">" info. */
+ N_("git mailinfo [<options>] <msg> <patch> < mail >info"),
+ NULL,
+};
+
+struct metainfo_charset
+{
+ enum {
+ CHARSET_DEFAULT,
+ CHARSET_NO_REENCODE,
+ CHARSET_EXPLICIT,
+ } policy;
+ const char *charset;
+};
+
+static int parse_opt_explicit_encoding(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct metainfo_charset *meta_charset = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+
+ meta_charset->policy = CHARSET_EXPLICIT;
+ meta_charset->charset = arg;
+
+ return 0;
+}
+
+static int parse_opt_quoted_cr(const struct option *opt, const char *arg, int unset)
+{
+ BUG_ON_OPT_NEG(unset);
+
+ if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)
+ return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr");
+ return 0;
+}
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
{
- const char *def_charset;
+ struct metainfo_charset meta_charset;
struct mailinfo mi;
int status;
char *msgfile, *patchfile;
+ struct option options[] = {
+ OPT_BOOL('k', NULL, &mi.keep_subject, N_("keep subject")),
+ OPT_BOOL('b', NULL, &mi.keep_non_patch_brackets_in_subject,
+ N_("keep non patch brackets in subject")),
+ OPT_BOOL('m', "message-id", &mi.add_message_id,
+ N_("copy Message-ID to the end of commit message")),
+ OPT_SET_INT_F('u', NULL, &meta_charset.policy,
+ N_("re-code metadata to i18n.commitEncoding"),
+ CHARSET_DEFAULT, PARSE_OPT_NONEG),
+ OPT_SET_INT_F('n', NULL, &meta_charset.policy,
+ N_("disable charset re-coding of metadata"),
+ CHARSET_NO_REENCODE, PARSE_OPT_NONEG),
+ OPT_CALLBACK_F(0, "encoding", &meta_charset, N_("encoding"),
+ N_("re-code metadata to this encoding"),
+ PARSE_OPT_NONEG, parse_opt_explicit_encoding),
+ OPT_BOOL(0, "scissors", &mi.use_scissors, N_("use scissors")),
+ OPT_CALLBACK_F(0, "quoted-cr", &mi.quoted_cr, N_("<action>"),
+ N_("action when quoted CR is found"),
+ PARSE_OPT_NONEG, parse_opt_quoted_cr),
+ OPT_HIDDEN_BOOL(0, "inbody-headers", &mi.use_inbody_headers,
+ N_("use headers in message's body")),
+ OPT_END()
+ };
+
setup_mailinfo(&mi);
+ meta_charset.policy = CHARSET_DEFAULT;
- def_charset = get_commit_output_encoding();
- mi.metainfo_charset = def_charset;
-
- while (1 < argc && argv[1][0] == '-') {
- if (!strcmp(argv[1], "-k"))
- mi.keep_subject = 1;
- else if (!strcmp(argv[1], "-b"))
- mi.keep_non_patch_brackets_in_subject = 1;
- else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id"))
- mi.add_message_id = 1;
- else if (!strcmp(argv[1], "-u"))
- mi.metainfo_charset = def_charset;
- else if (!strcmp(argv[1], "-n"))
- mi.metainfo_charset = NULL;
- else if (starts_with(argv[1], "--encoding="))
- mi.metainfo_charset = argv[1] + 11;
- else if (!strcmp(argv[1], "--scissors"))
- mi.use_scissors = 1;
- else if (!strcmp(argv[1], "--no-scissors"))
- mi.use_scissors = 0;
- else if (!strcmp(argv[1], "--no-inbody-headers"))
- mi.use_inbody_headers = 0;
- else
- usage(mailinfo_usage);
- argc--; argv++;
- }
+ argc = parse_options(argc, argv, prefix, options, mailinfo_usage, 0);
- if (argc != 3)
- usage(mailinfo_usage);
+ if (argc != 2)
+ usage_with_options(mailinfo_usage, options);
+
+ switch (meta_charset.policy) {
+ case CHARSET_DEFAULT:
+ mi.metainfo_charset = get_commit_output_encoding();
+ break;
+ case CHARSET_NO_REENCODE:
+ mi.metainfo_charset = NULL;
+ break;
+ case CHARSET_EXPLICIT:
+ break;
+ default:
+ BUG("invalid meta_charset.policy");
+ }
mi.input = stdin;
mi.output = stdout;
- msgfile = prefix_filename(prefix, argv[1]);
- patchfile = prefix_filename(prefix, argv[2]);
+ msgfile = prefix_filename(prefix, argv[0]);
+ patchfile = prefix_filename(prefix, argv[1]);
status = !!mailinfo(&mi, msgfile, patchfile);
clear_mailinfo(&mi);
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-index.c b/builtin/merge-index.c
index 38ea6ad6ca..c0383fe9df 100644
--- a/builtin/merge-index.c
+++ b/builtin/merge-index.c
@@ -58,6 +58,8 @@ static void merge_one_path(const char *path)
static void merge_all(void)
{
int i;
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
@@ -80,6 +82,9 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)
read_cache();
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
+
i = 1;
if (!strcmp(argv[i], "-o")) {
one_shot = 1;
diff --git a/builtin/merge-ours.c b/builtin/merge-ours.c
index 4594507420..3583cff71c 100644
--- a/builtin/merge-ours.c
+++ b/builtin/merge-ours.c
@@ -28,6 +28,6 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die_errno("read_cache failed");
if (index_differs_from(the_repository, "HEAD", NULL, 0))
- exit(2);
- exit(0);
+ return 2;
+ return 0;
}
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..5dc94d6f88 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -107,14 +107,12 @@ static void show_diff(struct merge_list *entry)
mmfile_t src, dst;
xpparam_t xpp;
xdemitconf_t xecfg;
- xdemitcb_t ecb;
+ xdemitcb_t ecb = { .out_line = show_outf };
+ memset(&xpp, 0, sizeof(xpp));
xpp.flags = 0;
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
- ecb.out_hunk = NULL;
- ecb.out_line = show_outf;
- ecb.priv = NULL;
src.ptr = origin(entry, &size);
if (!src.ptr)
@@ -180,8 +178,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 +204,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 +220,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 +351,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 +368,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..febb0c99c9 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -14,6 +14,7 @@
#include "lockfile.h"
#include "run-command.h"
#include "diff.h"
+#include "diff-merges.h"
#include "refs.h"
#include "refspec.h"
#include "commit.h"
@@ -28,6 +29,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 +39,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)
@@ -52,8 +56,8 @@ struct strategy {
static const char * const builtin_merge_usage[] = {
N_("git merge [<options>] [<commit>...]"),
- N_("git merge --abort"),
- N_("git merge --continue"),
+ "git merge --abort",
+ "git merge --continue",
NULL
};
@@ -61,6 +65,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 +74,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 },
+ { "recursive", NO_TRIVIAL },
{ "octopus", DEFAULT_OCTOPUS },
+ { "ort", DEFAULT_TWOHEAD | NO_TRIVIAL },
{ "resolve", 0 },
{ "ours", NO_FAST_FORWARD | NO_TRIVIAL },
{ "subtree", NO_FAST_FORWARD | NO_TRIVIAL },
@@ -155,10 +162,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];
@@ -196,7 +210,7 @@ static struct strategy *get_strategy(const char *name)
exit(1);
}
- ret = xcalloc(1, sizeof(struct strategy));
+ CALLOC_ARRAY(ret, 1);
ret->name = xstrdup(name);
ret->attr = NO_TRIVIAL;
return ret;
@@ -238,9 +252,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 +288,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 +297,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;
@@ -384,11 +393,15 @@ static void restore_state(const struct object_id *head,
}
/* This is called when no merge was necessary. */
-static void finish_up_to_date(const char *msg)
+static void finish_up_to_date(void)
{
- if (verbosity >= 0)
- printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg);
- drop_save();
+ if (verbosity >= 0) {
+ if (squash)
+ puts(_("Already up to date. (nothing to squash)"));
+ else
+ puts(_("Already up to date."));
+ }
+ remove_merge_branch_state(the_repository);
}
static void squash_message(struct commit *commit, struct commit_list *remoteheads)
@@ -401,7 +414,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead
printf(_("Squash commit -- not updating HEAD\n"));
repo_init_revisions(the_repository, &rev, NULL);
- rev.ignore_merges = 1;
+ diff_merges_suppress(&rev);
rev.commit_format = CMIT_FMT_MEDIUM;
commit->object.flags |= UNINTERESTING;
@@ -450,7 +463,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 +470,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 +490,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);
}
@@ -490,7 +503,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
struct strbuf bname = STRBUF_INIT;
struct merge_remote_desc *desc;
const char *ptr;
- char *found_ref;
+ char *found_ref = NULL;
int len, early;
strbuf_branchname(&bname, remote, 0);
@@ -501,7 +514,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);
@@ -573,6 +586,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
strbuf_addf(msg, "%s\t\tcommit '%s'\n",
oid_to_hex(&remote_head->object.oid), remote);
cleanup:
+ free(found_ref);
strbuf_release(&buf);
strbuf_release(&bname);
}
@@ -600,10 +614,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 +635,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 +649,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 +711,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,13 +734,12 @@ 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;
for (x = 0; x < xopts_nr; x++)
if (parse_merge_opt(&o, xopts[x]))
- die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
+ die(_("unknown strategy option: -X%s"), xopts[x]);
o.branch1 = head_arg;
o.branch2 = merge_remote_util(remoteheads->item)->name;
@@ -732,8 +748,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 +840,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");
@@ -830,9 +862,11 @@ static void prepare_to_commit(struct commit_list *remoteheads)
strbuf_commented_addf(&msg, "\n");
}
strbuf_commented_addf(&msg, _(merge_editor_comment));
- strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ?
- scissors_editor_comment :
- no_scissors_editor_comment), comment_line_char);
+ if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
+ strbuf_commented_addf(&msg, _(scissors_editor_comment));
+ else
+ strbuf_commented_addf(&msg,
+ _(no_scissors_editor_comment), comment_line_char);
}
if (signoff)
append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);
@@ -846,7 +880,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 +898,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 +911,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 +926,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 +938,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 +1286,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 +1313,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 +1322,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 +1376,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 +1442,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 +1466,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);
}
}
@@ -1428,6 +1487,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
fast_forward = FF_NO;
}
+ if (!use_strategies && !pull_twohead &&
+ remoteheads && !remoteheads->next) {
+ char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM");
+ if (default_strategy)
+ append_strategy(get_strategy(default_strategy));
+ }
if (!use_strategies) {
if (!remoteheads)
; /* already up-to-date */
@@ -1470,7 +1535,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* If head can reach all the merge then we are up to date.
* but first the most common case of merging one remote.
*/
- finish_up_to_date(_("Already up to date."));
+ finish_up_to_date();
goto done;
} else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
@@ -1496,16 +1561,21 @@ 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,
overwrite_ignore)) {
+ apply_autostash(git_path_merge_autostash(the_repository));
ret = 1;
goto done;
}
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)
;
@@ -1554,13 +1624,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
}
if (up_to_date) {
- finish_up_to_date(_("Already up to date. Yeeah!"));
+ finish_up_to_date();
goto done;
}
}
if (fast_forward == FF_ONLY)
- die(_("Not possible to fast-forward, aborting."));
+ die_ff_impossible();
+
+ 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 +1655,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 +1673,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;
}
/*
@@ -1657,6 +1718,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else
fprintf(stderr, _("Merge with strategy %s failed.\n"),
use_strategies[0]->name);
+ apply_autostash(git_path_merge_autostash(the_repository));
ret = 2;
goto done;
} else if (best_strategy == wt_strategy)
@@ -1664,15 +1726,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else {
printf(_("Rewinding the tree to pristine...\n"));
restore_state(&head_commit->object.oid, &stash);
- printf(_("Using the %s to prepare resolving by hand.\n"),
+ printf(_("Using the %s strategy to prepare resolving by hand.\n"),
best_strategy);
try_merge_strategy(best_strategy, common, remoteheads,
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..dddcccdd36 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -1,176 +1,108 @@
#include "builtin.h"
+#include "parse-options.h"
#include "tag.h"
#include "replace-object.h"
#include "object-store.h"
-
-/*
- * A signature file has a very simple fixed format: four lines
- * of "object <sha1>" + "type <typename>" + "tag <tagname>" +
- * "tagger <committer>", followed by a blank line, a free-form tag
- * message and a signature block that git itself doesn't care about,
- * but that can be verified with gpg or similar.
- *
- * The first four lines are guaranteed to be at least 83 bytes:
- * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
- * shortest possible type-line, "tag .\n" at 6 bytes is the shortest
- * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is
- * the shortest possible tagger-line.
- */
-
-/*
- * We refuse to tag something we can't verify. Just because.
- */
-static int verify_object(const struct object_id *oid, const char *expected_type)
+#include "fsck.h"
+#include "config.h"
+
+static char const * const builtin_mktag_usage[] = {
+ N_("git mktag"),
+ NULL
+};
+static int option_strict = 1;
+
+static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
+
+static int mktag_fsck_error_func(struct fsck_options *o,
+ const struct object_id *oid,
+ enum object_type object_type,
+ enum fsck_msg_type msg_type,
+ enum fsck_msg_id msg_id,
+ const char *message)
{
- int ret = -1;
- enum object_type type;
- unsigned long size;
- void *buffer = read_object_file(oid, &type, &size);
- 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);
- free(buffer);
+ switch (msg_type) {
+ case FSCK_WARN:
+ if (!option_strict) {
+ fprintf_ln(stderr, _("warning: tag input does not pass fsck: %s"), message);
+ return 0;
+
+ }
+ /* fallthrough */
+ case FSCK_ERROR:
+ /*
+ * We treat both warnings and errors as errors, things
+ * like missing "tagger" lines are "only" warnings
+ * under fsck, we've always considered them an error.
+ */
+ fprintf_ln(stderr, _("error: tag input does not pass fsck: %s"), message);
+ return 1;
+ default:
+ BUG(_("%d (FSCK_IGNORE?) should never trigger this callback"),
+ msg_type);
}
- return ret;
}
-static int verify_tag(char *buffer, unsigned long size)
+static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type)
{
- int typelen;
- char type[20];
- struct object_id oid;
- const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb, *p;
- size_t len;
-
- if (size < 84)
- return error("wanna fool me ? you obviously got the size wrong !");
-
- buffer[size] = 0;
-
- /* Verify object line */
- object = buffer;
- if (memcmp(object, "object ", 7))
- return error("char%d: does not start with \"object \"", 0);
-
- if (parse_oid_hex(object + 7, &oid, &p))
- return error("char%d: could not get SHA1 hash", 7);
-
- /* Verify type line */
- type_line = p + 1;
- if (memcmp(type_line - 1, "\ntype ", 6))
- return error("char%d: could not find \"\\ntype \"", 47);
-
- /* Verify tag-line */
- tag_line = strchr(type_line, '\n');
- if (!tag_line)
- return error("char%"PRIuMAX": could not find next \"\\n\"",
- (uintmax_t) (type_line - buffer));
- tag_line++;
- if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
- return error("char%"PRIuMAX": no \"tag \" found",
- (uintmax_t) (tag_line - buffer));
-
- /* Get the actual type */
- typelen = tag_line - type_line - strlen("type \n");
- if (typelen >= sizeof(type))
- return error("char%"PRIuMAX": type too long",
- (uintmax_t) (type_line+5 - buffer));
-
- memcpy(type, type_line+5, typelen);
- type[typelen] = 0;
-
- /* Verify that the object matches */
- if (verify_object(&oid, type))
- return error("char%d: could not verify object %s", 7, oid_to_hex(&oid));
-
- /* Verify the tag-name: we don't allow control characters or spaces in it */
- tag_line += 4;
- for (;;) {
- unsigned char c = *tag_line++;
- if (c == '\n')
- break;
- if (c > ' ')
- continue;
- return error("char%"PRIuMAX": could not verify tag name",
- (uintmax_t) (tag_line - buffer));
- }
-
- /* Verify the tagger line */
- tagger_line = tag_line;
-
- if (memcmp(tagger_line, "tagger ", 7))
- return error("char%"PRIuMAX": could not find \"tagger \"",
- (uintmax_t) (tagger_line - buffer));
-
- /*
- * Check for correct form for name and email
- * i.e. " <" followed by "> " on _this_ line
- * No angle brackets within the name or email address fields.
- * No spaces within the email address field.
- */
- tagger_line += 7;
- if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) ||
- strpbrk(tagger_line, "<>\n") != lb+1 ||
- strpbrk(lb+2, "><\n ") != rb)
- return error("char%"PRIuMAX": malformed tagger field",
- (uintmax_t) (tagger_line - buffer));
-
- /* Check for author name, at least one character, space is acceptable */
- if (lb == tagger_line)
- return error("char%"PRIuMAX": missing tagger name",
- (uintmax_t) (tagger_line - buffer));
-
- /* timestamp, 1 or more digits followed by space */
- tagger_line = rb + 2;
- if (!(len = strspn(tagger_line, "0123456789")))
- return error("char%"PRIuMAX": missing tag timestamp",
- (uintmax_t) (tagger_line - buffer));
- tagger_line += len;
- if (*tagger_line != ' ')
- return error("char%"PRIuMAX": malformed tag timestamp",
- (uintmax_t) (tagger_line - buffer));
- tagger_line++;
-
- /* timezone, 5 digits [+-]hhmm, max. 1400 */
- if (!((tagger_line[0] == '+' || tagger_line[0] == '-') &&
- strspn(tagger_line+1, "0123456789") == 4 &&
- tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400))
- return error("char%"PRIuMAX": malformed tag timezone",
- (uintmax_t) (tagger_line - buffer));
- tagger_line += 6;
-
- /* Verify the blank line separating the header from the body */
- if (*tagger_line != '\n')
- return error("char%"PRIuMAX": trailing garbage in tag header",
- (uintmax_t) (tagger_line - buffer));
+ int ret;
+ enum object_type type;
+ unsigned long size;
+ void *buffer;
+ const struct object_id *repl;
+
+ buffer = read_object_file(tagged_oid, &type, &size);
+ if (!buffer)
+ die(_("could not read tagged object '%s'"),
+ oid_to_hex(tagged_oid));
+ if (type != *tagged_type)
+ die(_("object '%s' tagged as '%s', but is a '%s' type"),
+ oid_to_hex(tagged_oid),
+ type_name(*tagged_type), type_name(type));
+
+ repl = lookup_replace_object(the_repository, tagged_oid);
+ ret = check_object_signature(the_repository, repl,
+ buffer, size, type_name(*tagged_type));
+ free(buffer);
- /* The actual stuff afterwards we don't care about.. */
- return 0;
+ return ret;
}
int cmd_mktag(int argc, const char **argv, const char *prefix)
{
+ static struct option builtin_mktag_options[] = {
+ OPT_BOOL(0, "strict", &option_strict,
+ N_("enable more strict checking")),
+ OPT_END(),
+ };
struct strbuf buf = STRBUF_INIT;
+ struct object_id tagged_oid;
+ int tagged_type;
struct object_id result;
- if (argc != 1)
- usage("git mktag");
+ argc = parse_options(argc, argv, NULL,
+ builtin_mktag_options,
+ builtin_mktag_usage, 0);
- if (strbuf_read(&buf, 0, 4096) < 0) {
- die_errno("could not read from stdin");
- }
+ if (strbuf_read(&buf, 0, 0) < 0)
+ die_errno(_("could not read from stdin"));
+
+ fsck_options.error_func = mktag_fsck_error_func;
+ fsck_set_msg_type_from_ids(&fsck_options, FSCK_MSG_EXTRA_HEADER_ENTRY,
+ FSCK_WARN);
+ /* config might set fsck.extraHeaderEntry=* again */
+ git_config(git_fsck_config, &fsck_options);
+ if (fsck_tag_standalone(NULL, buf.buf, buf.len, &fsck_options,
+ &tagged_oid, &tagged_type))
+ die(_("tag on stdin did not pass our strict fsck check"));
- /* Verify it for some basic sanity: it needs to start with
- "object <sha1>\ntype\ntagger " */
- if (verify_tag(buf.buf, buf.len) < 0)
- die("invalid tag signature file");
+ if (verify_object_in_tag(&tagged_oid, &tagged_type))
+ die(_("tag on stdin did not refer to a valid object"));
if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0)
- die("unable to write tag file");
+ die(_("unable to write tag file"));
strbuf_release(&buf);
- printf("%s\n", oid_to_hex(&result));
+ puts(oid_to_hex(&result));
return 0;
}
diff --git a/builtin/mktree.c b/builtin/mktree.c
index 94e82b8504..ae78ca1c02 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) {
/*
@@ -189,5 +189,5 @@ int cmd_mktree(int ac, const char **av, const char *prefix)
used=0; /* reset tree entry buffer for re-use in batch mode */
}
strbuf_release(&sb);
- exit(0);
+ return 0;
}
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index 72dfd3dadc..8ff0dee2ec 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -4,49 +4,181 @@
#include "parse-options.h"
#include "midx.h"
#include "trace2.h"
+#include "object-store.h"
+#define BUILTIN_MIDX_WRITE_USAGE \
+ N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]")
+
+#define BUILTIN_MIDX_VERIFY_USAGE \
+ N_("git multi-pack-index [<options>] verify")
+
+#define BUILTIN_MIDX_EXPIRE_USAGE \
+ N_("git multi-pack-index [<options>] expire")
+
+#define BUILTIN_MIDX_REPACK_USAGE \
+ N_("git multi-pack-index [<options>] repack [--batch-size=<size>]")
+
+static char const * const builtin_multi_pack_index_write_usage[] = {
+ BUILTIN_MIDX_WRITE_USAGE,
+ NULL
+};
+static char const * const builtin_multi_pack_index_verify_usage[] = {
+ BUILTIN_MIDX_VERIFY_USAGE,
+ NULL
+};
+static char const * const builtin_multi_pack_index_expire_usage[] = {
+ BUILTIN_MIDX_EXPIRE_USAGE,
+ NULL
+};
+static char const * const builtin_multi_pack_index_repack_usage[] = {
+ BUILTIN_MIDX_REPACK_USAGE,
+ NULL
+};
static char const * const builtin_multi_pack_index_usage[] = {
- N_("git multi-pack-index [--object-dir=<dir>] (write|verify)"),
+ BUILTIN_MIDX_WRITE_USAGE,
+ BUILTIN_MIDX_VERIFY_USAGE,
+ BUILTIN_MIDX_EXPIRE_USAGE,
+ BUILTIN_MIDX_REPACK_USAGE,
NULL
};
static struct opts_multi_pack_index {
const char *object_dir;
+ const char *preferred_pack;
+ unsigned long batch_size;
+ unsigned flags;
} opts;
-int cmd_multi_pack_index(int argc, const char **argv,
- const char *prefix)
+static struct option common_opts[] = {
+ OPT_FILENAME(0, "object-dir", &opts.object_dir,
+ N_("object directory containing set of packfile and pack-index pairs")),
+ OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"), MIDX_PROGRESS),
+ OPT_END(),
+};
+
+static struct option *add_common_options(struct option *prev)
{
- 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")),
+ return parse_options_concat(common_opts, prev);
+}
+
+static int cmd_multi_pack_index_write(int argc, const char **argv)
+{
+ struct option *options;
+ static struct option builtin_multi_pack_index_write_options[] = {
+ OPT_STRING(0, "preferred-pack", &opts.preferred_pack,
+ N_("preferred-pack"),
+ N_("pack for reuse when computing a multi-pack bitmap")),
OPT_END(),
};
+ options = add_common_options(builtin_multi_pack_index_write_options);
+
+ trace2_cmd_mode(argv[0]);
+
+ argc = parse_options(argc, argv, NULL,
+ options, builtin_multi_pack_index_write_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (argc)
+ usage_with_options(builtin_multi_pack_index_write_usage,
+ options);
+
+ FREE_AND_NULL(options);
+
+ return write_midx_file(opts.object_dir, opts.preferred_pack,
+ opts.flags);
+}
+
+static int cmd_multi_pack_index_verify(int argc, const char **argv)
+{
+ struct option *options = common_opts;
+
+ trace2_cmd_mode(argv[0]);
+
+ argc = parse_options(argc, argv, NULL,
+ options, builtin_multi_pack_index_verify_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (argc)
+ usage_with_options(builtin_multi_pack_index_verify_usage,
+ options);
+
+ return verify_midx_file(the_repository, opts.object_dir, opts.flags);
+}
+
+static int cmd_multi_pack_index_expire(int argc, const char **argv)
+{
+ struct option *options = common_opts;
+
+ trace2_cmd_mode(argv[0]);
+
+ argc = parse_options(argc, argv, NULL,
+ options, builtin_multi_pack_index_expire_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (argc)
+ usage_with_options(builtin_multi_pack_index_expire_usage,
+ options);
+
+ return expire_midx_packs(the_repository, opts.object_dir, opts.flags);
+}
+
+static int cmd_multi_pack_index_repack(int argc, const char **argv)
+{
+ struct option *options;
+ static struct option builtin_multi_pack_index_repack_options[] = {
+ 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(),
+ };
+
+ options = add_common_options(builtin_multi_pack_index_repack_options);
+
+ trace2_cmd_mode(argv[0]);
+
+ argc = parse_options(argc, argv, NULL,
+ options,
+ builtin_multi_pack_index_repack_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+ if (argc)
+ usage_with_options(builtin_multi_pack_index_repack_usage,
+ options);
+
+ FREE_AND_NULL(options);
+
+ return midx_repack(the_repository, opts.object_dir,
+ (size_t)opts.batch_size, opts.flags);
+}
+
+int cmd_multi_pack_index(int argc, const char **argv,
+ const char *prefix)
+{
+ struct option *builtin_multi_pack_index_options = common_opts;
+
git_config(git_default_config, NULL);
+ if (isatty(2))
+ opts.flags |= MIDX_PROGRESS;
argc = parse_options(argc, argv, prefix,
builtin_multi_pack_index_options,
- builtin_multi_pack_index_usage, 0);
+ builtin_multi_pack_index_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
if (!opts.object_dir)
opts.object_dir = get_object_directory();
if (argc == 0)
+ goto usage;
+
+ if (!strcmp(argv[0], "repack"))
+ return cmd_multi_pack_index_repack(argc, argv);
+ else if (!strcmp(argv[0], "write"))
+ return cmd_multi_pack_index_write(argc, argv);
+ else if (!strcmp(argv[0], "verify"))
+ return cmd_multi_pack_index_verify(argc, argv);
+ else if (!strcmp(argv[0], "expire"))
+ return cmd_multi_pack_index_expire(argc, argv);
+ else {
+ error(_("unrecognized subcommand: %s"), argv[0]);
+usage:
usage_with_options(builtin_multi_pack_index_usage,
builtin_multi_pack_index_options);
-
- if (argc > 1) {
- die(_("too many arguments"));
- return 1;
}
-
- trace2_cmd_mode(argv[0]);
-
- if (!strcmp(argv[0], "write"))
- return write_midx_file(opts.object_dir);
- if (!strcmp(argv[0], "verify"))
- return verify_midx_file(the_repository, opts.object_dir);
-
- die(_("unrecognized verb: %s"), argv[0]);
}
diff --git a/builtin/mv.c b/builtin/mv.c
index be15ba7044..c2f96c8e89 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, 0))) {
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) {
@@ -300,5 +303,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("Unable to write new index file"));
+ string_list_clear(&src_for_dst, 0);
+ UNLEAK(source);
+ UNLEAK(dest_path);
+ free(submodule_gitfile);
+ free(modes);
return 0;
}
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 05ccf53e00..b221d30014 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 "sha1-lookup.h"
+#include "prio-queue.h"
+#include "hash-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,22 +361,39 @@ 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 const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
+static void name_tips(void)
{
- struct tip_table_entry *table = table_;
- return table[ix].oid.hash;
+ 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 struct object_id *nth_tip_table_ent(size_t ix, const void *table_)
+{
+ const struct tip_table_entry *table = table_;
+ return &table[ix].oid;
}
static const char *get_exact_ref_match(const struct object *o)
@@ -297,8 +408,8 @@ static const char *get_exact_ref_match(const struct object *o)
tip_table.sorted = 1;
}
- found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr,
- nth_tip_table_ent);
+ found = oid_pos(&o->oid, tip_table.table, tip_table.nr,
+ nth_tip_table_ent);
if (0 <= found)
return tip_table.table[found].refname;
return NULL;
@@ -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..74bba39ca8 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -373,7 +373,7 @@ static int list(int argc, const char **argv, const char *prefix)
git_notes_list_usage, 0);
if (1 < argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
usage_with_options(git_notes_list_usage, options);
}
@@ -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),
@@ -428,7 +428,7 @@ static int add(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_ARGV0);
if (2 < argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
usage_with_options(git_notes_add_usage, options);
}
@@ -506,19 +506,19 @@ static int copy(int argc, const char **argv, const char *prefix)
if (from_stdin || rewrite_cmd) {
if (argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
usage_with_options(git_notes_copy_usage, options);
} else {
return notes_copy_from_stdin(force, rewrite_cmd);
}
}
- if (argc < 2) {
- error(_("too few parameters"));
+ if (argc < 1) {
+ error(_("too few arguments"));
usage_with_options(git_notes_copy_usage, options);
}
if (2 < argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
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()
@@ -595,7 +595,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_ARGV0);
if (2 < argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
usage_with_options(usage, options);
}
@@ -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);
@@ -662,7 +662,7 @@ static int show(int argc, const char **argv, const char *prefix)
0);
if (1 < argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
usage_with_options(git_notes_show_usage, options);
}
@@ -730,7 +730,7 @@ static int merge_commit(struct notes_merge_options *o)
else
oidclr(&parent_oid);
- t = xcalloc(1, sizeof(struct notes_tree));
+ CALLOC_ARRAY(t, 1);
init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
o->local_ref = local_ref_to_free =
@@ -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);
@@ -812,7 +812,7 @@ static int merge(int argc, const char **argv, const char *prefix)
error(_("must specify a notes ref to merge"));
usage_with_options(git_notes_merge_usage, options);
} else if (!do_merge && argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
usage_with_options(git_notes_merge_usage, options);
}
@@ -960,7 +960,7 @@ static int prune(int argc, const char **argv, const char *prefix)
0);
if (argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
usage_with_options(git_notes_prune_usage, options);
}
@@ -982,7 +982,7 @@ static int get_ref(int argc, const char **argv, const char *prefix)
git_notes_get_ref_usage, 0);
if (argc) {
- error(_("too many parameters"));
+ error(_("too many arguments"));
usage_with_options(git_notes_get_ref_usage, options);
}
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 787ae10288..df49f656b9 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -26,14 +26,144 @@
#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"
+
+/*
+ * Objects we are going to pack are collected in the `to_pack` structure.
+ * It contains an array (dynamically expanded) of the object data, and a map
+ * that can resolve SHA1s to their position in the array.
+ */
+static struct packing_data to_pack;
+
+static inline struct object_entry *oe_delta(
+ const struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (!e->delta_idx)
+ return NULL;
+ if (e->ext_base)
+ return &pack->ext_bases[e->delta_idx - 1];
+ else
+ return &pack->objects[e->delta_idx - 1];
+}
+
+static inline unsigned long oe_delta_size(struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (e->delta_size_valid)
+ return e->delta_size_;
+
+ /*
+ * pack->delta_size[] can't be NULL because oe_set_delta_size()
+ * must have been called when a new delta is saved with
+ * oe_set_delta().
+ * If oe_delta() returns NULL (i.e. default state, which means
+ * delta_size_valid is also false), then the caller must never
+ * call oe_delta_size().
+ */
+ return pack->delta_size[e - pack->objects];
+}
+
+unsigned long oe_get_size_slow(struct packing_data *pack,
+ const struct object_entry *e);
+
+static inline unsigned long oe_size(struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (e->size_valid)
+ return e->size_;
+
+ return oe_get_size_slow(pack, e);
+}
+
+static inline void oe_set_delta(struct packing_data *pack,
+ struct object_entry *e,
+ struct object_entry *delta)
+{
+ if (delta)
+ e->delta_idx = (delta - pack->objects) + 1;
+ else
+ e->delta_idx = 0;
+}
+
+static inline struct object_entry *oe_delta_sibling(
+ const struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (e->delta_sibling_idx)
+ return &pack->objects[e->delta_sibling_idx - 1];
+ return NULL;
+}
+
+static inline struct object_entry *oe_delta_child(
+ const struct packing_data *pack,
+ const struct object_entry *e)
+{
+ if (e->delta_child_idx)
+ return &pack->objects[e->delta_child_idx - 1];
+ return NULL;
+}
+
+static inline void oe_set_delta_child(struct packing_data *pack,
+ struct object_entry *e,
+ struct object_entry *delta)
+{
+ if (delta)
+ e->delta_child_idx = (delta - pack->objects) + 1;
+ else
+ e->delta_child_idx = 0;
+}
+
+static inline void oe_set_delta_sibling(struct packing_data *pack,
+ struct object_entry *e,
+ struct object_entry *delta)
+{
+ if (delta)
+ e->delta_sibling_idx = (delta - pack->objects) + 1;
+ else
+ e->delta_sibling_idx = 0;
+}
+
+static inline void oe_set_size(struct packing_data *pack,
+ struct object_entry *e,
+ unsigned long size)
+{
+ if (size < pack->oe_size_limit) {
+ e->size_ = size;
+ e->size_valid = 1;
+ } else {
+ e->size_valid = 0;
+ if (oe_get_size_slow(pack, e) != size)
+ BUG("'size' is supposed to be the object size!");
+ }
+}
+
+static inline void oe_set_delta_size(struct packing_data *pack,
+ struct object_entry *e,
+ unsigned long size)
+{
+ if (size < pack->oe_delta_size_limit) {
+ e->delta_size_ = size;
+ e->delta_size_valid = 1;
+ } else {
+ packing_data_lock(pack);
+ if (!pack->delta_size)
+ ALLOC_ARRAY(pack->delta_size, pack->nr_alloc);
+ packing_data_unlock(pack);
+
+ pack->delta_size[e - pack->objects] = size;
+ e->delta_size_valid = 0;
+ }
+}
#define IN_PACK(obj) oe_in_pack(&to_pack, obj)
#define SIZE(obj) oe_size(&to_pack, obj)
@@ -54,13 +184,6 @@ static const char *pack_usage[] = {
NULL
};
-/*
- * Objects we are going to pack are collected in the `to_pack` structure.
- * It contains an array (dynamically expanded) of the object data, and a map
- * that can resolve SHA1s to their position in the array.
- */
-static struct packing_data to_pack;
-
static struct pack_idx_entry **written_list;
static uint32_t nr_result, nr_written, nr_seen;
static struct bitmap_index *bitmap_git;
@@ -92,11 +215,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 +239,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 +249,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 +298,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.
*/
@@ -283,6 +422,17 @@ static void copy_pack_data(struct hashfile *f,
}
}
+static inline int oe_size_greater_than(struct packing_data *pack,
+ const struct object_entry *lhs,
+ unsigned long rhs)
+{
+ if (lhs->size_valid)
+ return lhs->size_ > rhs;
+ if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
+ return 1;
+ return oe_get_size_slow(pack, lhs) > rhs;
+}
+
/* Return 0 if we will bust the pack-size limit */
static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entry *entry,
unsigned long limit, int usable_delta)
@@ -299,7 +449,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);
@@ -400,7 +551,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
{
struct packed_git *p = IN_PACK(entry);
struct pack_window *w_curs = NULL;
- struct revindex_entry *revidx;
+ uint32_t pos;
off_t offset;
enum object_type type = oe_type(entry);
off_t datalen;
@@ -417,10 +568,15 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry,
type, entry_size);
offset = entry->in_pack_offset;
- revidx = find_pack_revindex(p, offset);
- datalen = revidx[1].offset - offset;
+ if (offset_to_pack_pos(p, offset, &pos) < 0)
+ die(_("write_reuse_object: could not locate %s, expected at "
+ "offset %"PRIuMAX" in pack %s"),
+ oid_to_hex(&entry->idx.oid), (uintmax_t)offset,
+ p->pack_name);
+ datalen = pack_pos_to_offset(p, pos + 1) - offset;
if (!pack_to_stdout && p->index_version > 1 &&
- check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
+ check_pack_crc(p, &w_curs, offset, datalen,
+ pack_pos_to_index(p, pos))) {
error(_("bad packed object CRC for %s"),
oid_to_hex(&entry->idx.oid));
unuse_pack(&w_curs);
@@ -606,18 +762,26 @@ 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);
+ if (!peel_iterated_oid(oid, &peeled)) {
+ entry = packlist_find(&to_pack, &peeled);
if (entry)
entry->tagged = 1;
}
return 0;
}
+static inline unsigned char oe_layer(struct packing_data *pack,
+ struct object_entry *e)
+{
+ if (!pack->layer)
+ return 0;
+ return pack->layer[e - pack->objects];
+}
+
static inline void add_to_write_order(struct object_entry **wo,
unsigned int *endp,
struct object_entry *e)
@@ -780,57 +944,211 @@ 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 difference for "original" minus the offset of the first object of
+ * this chunk in the generated packfile. */
+ 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;
+ }
- if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1)
- die_errno(_("unable to seek in reused packfile"));
+ /*
+ * The first chunk starts at zero, so we can't have gone below
+ * there.
+ */
+ assert(lo);
+ return reused_chunks[lo-1].difference;
+}
- if (reuse_packfile_offset < 0)
- reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz;
+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;
- total = to_write = reuse_packfile_offset - sizeof(struct pack_header);
+ offset = pack_pos_to_offset(reuse_packfile, pos);
+ next = pack_pos_to_offset(reuse_packfile, pos + 1);
- while (to_write) {
- int read_pack = xread(fd, buffer, sizeof(buffer));
+ record_reused_object(offset, offset - hashfile_total(out));
- if (read_pack <= 0)
- die_errno(_("unable to read from reused packfile"));
+ cur = offset;
+ type = unpack_object_header(reuse_packfile, w_curs, &cur, &size);
+ assert(type >= 0);
- if (read_pack > to_write)
- read_pack = to_write;
+ if (type == OBJ_OFS_DELTA) {
+ off_t base_offset;
+ off_t fixup;
+
+ unsigned char header[MAX_PACK_OBJECT_HEADER];
+ unsigned len;
+
+ 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) {
+ uint32_t base_pos;
+ struct object_id base_oid;
+
+ if (offset_to_pack_pos(reuse_packfile, base_offset, &base_pos) < 0)
+ die(_("expected object at offset %"PRIuMAX" "
+ "in pack %s"),
+ (uintmax_t)base_offset,
+ reuse_packfile->pack_name);
+
+ nth_packed_object_id(&base_oid, reuse_packfile,
+ pack_pos_to_index(reuse_packfile, base_pos));
+
+ 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;
+ }
- hashwrite(f, buffer, read_pack);
- to_write -= read_pack;
+ /* 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 = pack_pos_to_offset(reuse_packfile, written)
+ - 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;
- close(fd);
- written = reuse_packfile_objects;
- display_progress(progress_state, written);
- return reuse_packfile_offset - sizeof(struct pack_header);
+ offset += ewah_bit_ctz64(word >> offset);
+ write_reused_pack_one(pos + offset, f, &w_curs);
+ display_progress(progress_state, ++written);
+ }
+ }
+
+ 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_(
@@ -852,7 +1170,7 @@ static void write_pack_file(void)
write_order = compute_write_order();
do {
- struct object_id oid;
+ unsigned char hash[GIT_MAX_RAWSZ];
char *pack_tmp_name = NULL;
if (pack_to_stdout)
@@ -863,11 +1181,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;
@@ -883,16 +1199,17 @@ static void write_pack_file(void)
* If so, rewrite it like in fast-import
*/
if (pack_to_stdout) {
- finalize_hashfile(f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_CLOSE);
+ finalize_hashfile(f, hash, CSUM_HASH_IN_STREAM | CSUM_CLOSE);
} else if (nr_written == nr_remaining) {
- finalize_hashfile(f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
+ finalize_hashfile(f, hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE);
} else {
- int fd = finalize_hashfile(f, oid.hash, 0);
- fixup_pack_header_footer(fd, oid.hash, pack_tmp_name,
- nr_written, oid.hash, offset);
+ int fd = finalize_hashfile(f, hash, 0);
+ fixup_pack_header_footer(fd, hash, pack_tmp_name,
+ nr_written, 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;
}
}
@@ -923,22 +1240,21 @@ static void write_pack_file(void)
strbuf_addf(&tmpname, "%s-", base_name);
if (write_bitmap_index) {
- bitmap_writer_set_checksum(oid.hash);
+ bitmap_writer_set_checksum(hash);
bitmap_writer_build_type_index(
&to_pack, written_list, nr_written);
}
finish_tmp_packfile(&tmpname, pack_tmp_name,
written_list, nr_written,
- &pack_idx_opts, oid.hash);
+ &pack_idx_opts, hash);
if (write_bitmap_index) {
- strbuf_addf(&tmpname, "%s.bitmap", oid_to_hex(&oid));
+ strbuf_addf(&tmpname, "%s.bitmap", hash_to_hex(hash));
stop_progress(&progress_state);
bitmap_writer_show_progress(progress);
- bitmap_writer_reuse_bitmaps(&to_pack);
bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
bitmap_writer_build(&to_pack);
bitmap_writer_finish(written_list, nr_written,
@@ -948,7 +1264,7 @@ static void write_pack_file(void)
strbuf_release(&tmpname);
free(pack_tmp_name);
- puts(oid_to_hex(&oid));
+ puts(hash_to_hex(hash));
}
/* mark written objects as written to previous pack */
@@ -991,12 +1307,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;
@@ -1009,7 +1328,8 @@ static int have_duplicate_entry(const struct object_id *oid,
return 1;
}
-static int want_found_object(int exclude, struct packed_git *p)
+static int want_found_object(const struct object_id *oid, int exclude,
+ struct packed_git *p)
{
if (exclude)
return 1;
@@ -1025,27 +1345,82 @@ static int want_found_object(int exclude, struct packed_git *p)
* make sure no copy of this object appears in _any_ pack that makes us
* to omit the object, so we need to check all the packs.
*
- * We can however first check whether these options can possible matter;
+ * We can however first check whether these options can possibly matter;
* if they do not matter we know we want the object in generated pack.
* Otherwise, we signal "-1" at the end to tell the caller that we do
* not know either way, and it needs to check more packs.
*/
- if (!ignore_packed_keep_on_disk &&
- !ignore_packed_keep_in_core &&
- (!local || !have_non_local_packs))
- return 1;
+ /*
+ * Objects in packs borrowed from elsewhere are discarded regardless of
+ * if they appear in other packs that weren't borrowed.
+ */
if (local && !p->pack_local)
return 0;
- if (p->pack_local &&
- ((ignore_packed_keep_on_disk && p->pack_keep) ||
- (ignore_packed_keep_in_core && p->pack_keep_in_core)))
- return 0;
+
+ /*
+ * Then handle .keep first, as we have a fast(er) path there.
+ */
+ if (ignore_packed_keep_on_disk || ignore_packed_keep_in_core) {
+ /*
+ * Set the flags for the kept-pack cache to be the ones we want
+ * to ignore.
+ *
+ * That is, if we are ignoring objects in on-disk keep packs,
+ * then we want to search through the on-disk keep and ignore
+ * the in-core ones.
+ */
+ unsigned flags = 0;
+ if (ignore_packed_keep_on_disk)
+ flags |= ON_DISK_KEEP_PACKS;
+ if (ignore_packed_keep_in_core)
+ flags |= IN_CORE_KEEP_PACKS;
+
+ if (ignore_packed_keep_on_disk && p->pack_keep)
+ return 0;
+ if (ignore_packed_keep_in_core && p->pack_keep_in_core)
+ return 0;
+ if (has_object_kept_pack(oid, flags))
+ return 0;
+ }
+
+ /*
+ * At this point we know definitively that either we don't care about
+ * keep-packs, or the object is not in one. Keep checking other
+ * conditions...
+ */
+ if (!local || !have_non_local_packs)
+ return 1;
/* we don't know yet; keep looking for more packs */
return -1;
}
+static int want_object_in_pack_one(struct packed_git *p,
+ const struct object_id *oid,
+ int exclude,
+ struct packed_git **found_pack,
+ off_t *found_offset)
+{
+ off_t offset;
+
+ if (p == *found_pack)
+ offset = *found_offset;
+ else
+ offset = find_pack_entry_one(oid->hash, p);
+
+ if (offset) {
+ if (!*found_pack) {
+ if (!is_pack_valid(p))
+ return -1;
+ *found_offset = offset;
+ *found_pack = p;
+ }
+ return want_found_object(oid, exclude, p);
+ }
+ return -1;
+}
+
/*
* Check whether we want the object in the pack (e.g., we do not want
* objects found in non-local stores if the "--local" option was used).
@@ -1073,7 +1448,7 @@ static int want_object_in_pack(const struct object_id *oid,
* are present we will determine the answer right now.
*/
if (*found_pack) {
- want = want_found_object(exclude, *found_pack);
+ want = want_found_object(oid, exclude, *found_pack);
if (want != -1)
return want;
}
@@ -1081,50 +1456,38 @@ static int want_object_in_pack(const struct object_id *oid,
for (m = get_multi_pack_index(the_repository); m; m = m->next) {
struct pack_entry e;
if (fill_midx_entry(the_repository, oid, &e, m)) {
- struct packed_git *p = e.p;
- off_t offset;
-
- if (p == *found_pack)
- offset = *found_offset;
- else
- offset = find_pack_entry_one(oid->hash, p);
-
- if (offset) {
- if (!*found_pack) {
- if (!is_pack_valid(p))
- continue;
- *found_offset = offset;
- *found_pack = p;
- }
- want = want_found_object(exclude, p);
- if (want != -1)
- return want;
- }
+ want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset);
+ if (want != -1)
+ return want;
}
}
list_for_each(pos, get_packed_git_mru(the_repository)) {
struct packed_git *p = list_entry(pos, struct packed_git, mru);
- off_t offset;
+ want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset);
+ if (!exclude && want > 0)
+ list_move(&p->mru,
+ get_packed_git_mru(the_repository));
+ if (want != -1)
+ return want;
+ }
- if (p == *found_pack)
- offset = *found_offset;
- else
- offset = find_pack_entry_one(oid->hash, p);
-
- if (offset) {
- if (!*found_pack) {
- if (!is_pack_valid(p))
- continue;
- *found_offset = offset;
- *found_pack = p;
+ 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;
+ }
}
- want = want_found_object(exclude, p);
- if (!exclude && want > 0)
- list_move(&p->mru,
- get_packed_git_mru(the_repository));
- if (want != -1)
- return want;
}
}
@@ -1136,13 +1499,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 +1528,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 +1546,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 +1555,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 +1788,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;
@@ -1439,7 +1800,7 @@ static void add_preferred_base(struct object_id *oid)
}
}
- it = xcalloc(1, sizeof(*it));
+ CALLOC_ARRAY(it, 1);
it->next = pbase_tree;
pbase_tree = it;
@@ -1484,21 +1845,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 +1868,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 +1880,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 +1950,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:
@@ -1600,17 +1982,19 @@ static void check_object(struct object_entry *entry)
goto give_up;
}
if (reuse_delta && !entry->preferred_base) {
- struct revindex_entry *revidx;
- revidx = find_pack_revindex(p, ofs);
- if (!revidx)
+ uint32_t pos;
+ if (offset_to_pack_pos(p, ofs, &pos) < 0)
goto give_up;
- base_ref = nth_packed_object_sha1(p, revidx->nr);
+ if (!nth_packed_object_id(&base_ref, p,
+ pack_pos_to_index(p, pos)))
+ 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 +2004,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 +2037,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 {
@@ -1867,14 +2261,14 @@ static void get_object_details(void)
progress_state = start_progress(_("Counting objects"),
to_pack.nr_objects);
- sorted_by_offset = xcalloc(to_pack.nr_objects, sizeof(struct object_entry *));
+ CALLOC_ARRAY(sorted_by_offset, to_pack.nr_objects);
for (i = 0; i < to_pack.nr_objects; i++)
sorted_by_offset[i] = to_pack.objects + i;
QSORT(sorted_by_offset, to_pack.nr_objects, pack_offset_sort);
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;
@@ -1977,6 +2371,26 @@ static pthread_mutex_t progress_mutex;
* progress_mutex for protection.
*/
+static inline int oe_size_less_than(struct packing_data *pack,
+ const struct object_entry *lhs,
+ unsigned long rhs)
+{
+ if (lhs->size_valid)
+ return lhs->size_ < rhs;
+ if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */
+ return 0;
+ return oe_get_size_slow(pack, lhs) < rhs;
+}
+
+static inline void oe_set_tree_depth(struct packing_data *pack,
+ struct object_entry *e,
+ unsigned int tree_depth)
+{
+ if (!pack->tree_depth)
+ CALLOC_ARRAY(pack->tree_depth, pack->nr_alloc);
+ pack->tree_depth[e - pack->objects] = tree_depth;
+}
+
/*
* Return the size of the object without doing any delta
* reconstruction (so non-deltas are true object sizes, but deltas
@@ -2199,7 +2613,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
struct unpacked *array;
unsigned long mem_usage = 0;
- array = xcalloc(window, sizeof(struct unpacked));
+ CALLOC_ARRAY(array, window);
for (;;) {
struct object_entry *entry;
@@ -2334,15 +2748,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 +2788,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);
@@ -2447,7 +2850,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
if (progress > pack_to_stdout)
fprintf_ln(stderr, _("Delta compression using up to %d threads"),
delta_search_threads);
- p = xcalloc(delta_search_threads, sizeof(*p));
+ CALLOC_ARRAY(p, delta_search_threads);
/* Partition the work amongst work threads. */
for (i = 0; i < delta_search_threads; i++) {
@@ -2560,6 +2963,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 +2981,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);
@@ -2589,13 +2999,11 @@ static void add_tag_chain(const struct object_id *oid)
}
}
-static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data)
+static int add_ref_tag(const char *tag, const struct object_id *oid, int flag, void *cb_data)
{
struct object_id peeled;
- if (starts_with(path, "refs/tags/") && /* is a tag? */
- !peel_ref(path, &peeled) && /* peelable? */
- packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */
+ if (!peel_iterated_oid(oid, &peeled) && obj_is_packed(&peeled))
add_tag_chain(oid);
return 0;
}
@@ -2663,6 +3071,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 +3116,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,9 +3138,241 @@ static int git_pack_config(const char *k, const char *v, void *cb)
pack_idx_opts.version);
return 0;
}
+ if (!strcmp(k, "pack.writereverseindex")) {
+ if (git_config_bool(k, v))
+ pack_idx_opts.flags |= WRITE_REV;
+ else
+ pack_idx_opts.flags &= ~WRITE_REV;
+ 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);
}
+/* Counters for trace2 output when in --stdin-packs mode. */
+static int stdin_packs_found_nr;
+static int stdin_packs_hints_nr;
+
+static int add_object_entry_from_pack(const struct object_id *oid,
+ struct packed_git *p,
+ uint32_t pos,
+ void *_data)
+{
+ struct rev_info *revs = _data;
+ struct object_info oi = OBJECT_INFO_INIT;
+ off_t ofs;
+ enum object_type type;
+
+ display_progress(progress_state, ++nr_seen);
+
+ if (have_duplicate_entry(oid, 0))
+ return 0;
+
+ ofs = nth_packed_object_offset(p, pos);
+ if (!want_object_in_pack(oid, 0, &p, &ofs))
+ return 0;
+
+ oi.typep = &type;
+ if (packed_object_info(the_repository, p, ofs, &oi) < 0)
+ die(_("could not get type of object %s in pack %s"),
+ oid_to_hex(oid), p->pack_name);
+ else if (type == OBJ_COMMIT) {
+ /*
+ * commits in included packs are used as starting points for the
+ * subsequent revision walk
+ */
+ add_pending_oid(revs, NULL, oid, 0);
+ }
+
+ stdin_packs_found_nr++;
+
+ create_object_entry(oid, type, 0, 0, 0, p, ofs);
+
+ return 0;
+}
+
+static void show_commit_pack_hint(struct commit *commit, void *_data)
+{
+ /* nothing to do; commits don't have a namehash */
+}
+
+static void show_object_pack_hint(struct object *object, const char *name,
+ void *_data)
+{
+ struct object_entry *oe = packlist_find(&to_pack, &object->oid);
+ if (!oe)
+ return;
+
+ /*
+ * Our 'to_pack' list was constructed by iterating all objects packed in
+ * included packs, and so doesn't have a non-zero hash field that you
+ * would typically pick up during a reachability traversal.
+ *
+ * Make a best-effort attempt to fill in the ->hash and ->no_try_delta
+ * here using a now in order to perhaps improve the delta selection
+ * process.
+ */
+ oe->hash = pack_name_hash(name);
+ oe->no_try_delta = name && no_try_delta(name);
+
+ stdin_packs_hints_nr++;
+}
+
+static int pack_mtime_cmp(const void *_a, const void *_b)
+{
+ struct packed_git *a = ((const struct string_list_item*)_a)->util;
+ struct packed_git *b = ((const struct string_list_item*)_b)->util;
+
+ /*
+ * order packs by descending mtime so that objects are laid out
+ * roughly as newest-to-oldest
+ */
+ if (a->mtime < b->mtime)
+ return 1;
+ else if (b->mtime < a->mtime)
+ return -1;
+ else
+ return 0;
+}
+
+static void read_packs_list_from_stdin(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list include_packs = STRING_LIST_INIT_DUP;
+ struct string_list exclude_packs = STRING_LIST_INIT_DUP;
+ struct string_list_item *item = NULL;
+
+ struct packed_git *p;
+ struct rev_info revs;
+
+ repo_init_revisions(the_repository, &revs, NULL);
+ /*
+ * Use a revision walk to fill in the namehash of objects in the include
+ * packs. To save time, we'll avoid traversing through objects that are
+ * in excluded packs.
+ *
+ * That may cause us to avoid populating all of the namehash fields of
+ * all included objects, but our goal is best-effort, since this is only
+ * an optimization during delta selection.
+ */
+ revs.no_kept_objects = 1;
+ revs.keep_pack_cache_flags |= IN_CORE_KEEP_PACKS;
+ revs.blob_objects = 1;
+ revs.tree_objects = 1;
+ revs.tag_objects = 1;
+ revs.ignore_missing_links = 1;
+
+ while (strbuf_getline(&buf, stdin) != EOF) {
+ if (!buf.len)
+ continue;
+
+ if (*buf.buf == '^')
+ string_list_append(&exclude_packs, buf.buf + 1);
+ else
+ string_list_append(&include_packs, buf.buf);
+
+ strbuf_reset(&buf);
+ }
+
+ string_list_sort(&include_packs);
+ string_list_sort(&exclude_packs);
+
+ for (p = get_all_packs(the_repository); p; p = p->next) {
+ const char *pack_name = pack_basename(p);
+
+ item = string_list_lookup(&include_packs, pack_name);
+ if (!item)
+ item = string_list_lookup(&exclude_packs, pack_name);
+
+ if (item)
+ item->util = p;
+ }
+
+ /*
+ * Arguments we got on stdin may not even be packs. First
+ * check that to avoid segfaulting later on in
+ * e.g. pack_mtime_cmp(), excluded packs are handled below.
+ *
+ * Since we first parsed our STDIN and then sorted the input
+ * lines the pack we error on will be whatever line happens to
+ * sort first. This is lazy, it's enough that we report one
+ * bad case here, we don't need to report the first/last one,
+ * or all of them.
+ */
+ for_each_string_list_item(item, &include_packs) {
+ struct packed_git *p = item->util;
+ if (!p)
+ die(_("could not find pack '%s'"), item->string);
+ }
+
+ /*
+ * Then, handle all of the excluded packs, marking them as
+ * kept in-core so that later calls to add_object_entry()
+ * discards any objects that are also found in excluded packs.
+ */
+ for_each_string_list_item(item, &exclude_packs) {
+ struct packed_git *p = item->util;
+ if (!p)
+ die(_("could not find pack '%s'"), item->string);
+ p->pack_keep_in_core = 1;
+ }
+
+ /*
+ * Order packs by ascending mtime; use QSORT directly to access the
+ * string_list_item's ->util pointer, which string_list_sort() does not
+ * provide.
+ */
+ QSORT(include_packs.items, include_packs.nr, pack_mtime_cmp);
+
+ for_each_string_list_item(item, &include_packs) {
+ struct packed_git *p = item->util;
+ if (!p)
+ die(_("could not find pack '%s'"), item->string);
+ for_each_object_in_pack(p,
+ add_object_entry_from_pack,
+ &revs,
+ FOR_EACH_OBJECT_PACK_ORDER);
+ }
+
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
+ traverse_commit_list(&revs,
+ show_commit_pack_hint,
+ show_object_pack_hint,
+ NULL);
+
+ trace2_data_intmax("pack-objects", the_repository, "stdin_packs_found",
+ stdin_packs_found_nr);
+ trace2_data_intmax("pack-objects", the_repository, "stdin_packs_hints",
+ stdin_packs_hints_nr);
+
+ strbuf_release(&buf);
+ string_list_clear(&include_packs, 0);
+ string_list_clear(&exclude_packs, 0);
+}
+
static void read_object_list_from_stdin(void)
{
char line[GIT_MAX_HEXSZ + 1 + PATH_MAX + 2];
@@ -2795,7 +3436,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 +3450,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 +3464,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 +3540,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 +3562,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(the_repository, &oid);
if (!(o->flags & OBJECT_ADDED))
mark_in_pack_object(o, p, &in_pack);
o->flags |= OBJECT_ADDED;
@@ -3011,10 +3652,11 @@ 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;
+ uint32_t loosened_objects_nr = 0;
struct object_id oid;
for (p = get_all_packs(the_repository); p; p = p->next) {
@@ -3025,14 +3667,19 @@ 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))
+ !loosened_object_can_be_discarded(&oid, p->mtime)) {
if (force_object_loose(&oid, p->mtime))
die(_("unable to force loose object"));
+ loosened_objects_nr++;
+ }
}
}
+
+ trace2_data_intmax("pack-objects", the_repository,
+ "loosen_unused_packed_objects/loosened", loosened_objects_nr);
}
/*
@@ -3042,8 +3689,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 +3699,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, 0)))
return -1;
if (pack_options_allow_reuse() &&
@@ -3060,13 +3707,15 @@ 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);
+ nr_seen += reuse_packfile_objects;
+ display_progress(progress_state, nr_seen);
}
- 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;
}
@@ -3082,6 +3731,37 @@ static void record_recent_commit(struct commit *commit, void *data)
oid_array_append(&recent_objects, &commit->object.oid);
}
+static int mark_bitmap_preferred_tip(const char *refname,
+ const struct object_id *oid, int flags,
+ void *_data)
+{
+ struct object_id peeled;
+ struct object *object;
+
+ if (!peel_iterated_oid(oid, &peeled))
+ oid = &peeled;
+
+ object = parse_object_or_die(oid, refname);
+ if (object->type == OBJ_COMMIT)
+ object->flags |= NEEDS_BITMAP;
+
+ return 0;
+}
+
+static void mark_bitmap_preferred_tips(void)
+{
+ struct string_list_item *item;
+ const struct string_list *preferred_tips;
+
+ preferred_tips = bitmap_preferred_tips(the_repository);
+ if (!preferred_tips)
+ return;
+
+ for_each_string_list_item(item, preferred_tips) {
+ for_each_ref_in(item->string, mark_bitmap_preferred_tip, NULL);
+ }
+}
+
static void get_object_list(int ac, const char **av)
{
struct rev_info revs;
@@ -3117,7 +3797,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;
@@ -3136,6 +3816,9 @@ static void get_object_list(int ac, const char **av)
if (use_delta_islands)
load_delta_islands(the_repository, progress);
+ if (write_bitmap_index)
+ mark_bitmap_preferred_tips();
+
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
mark_edges_uninteresting(&revs, show_edge, sparse);
@@ -3158,11 +3841,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,9 +3914,10 @@ 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;
+ int stdin_packs = 0;
struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
struct option pack_objects_options[] = {
OPT_SET_INT('q', "quiet", &progress,
@@ -3245,9 +3929,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,
@@ -3284,6 +3968,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
OPT_SET_INT_F(0, "indexed-objects", &rev_list_index,
N_("include objects referred to by the index"),
1, PARSE_OPT_NONEG),
+ OPT_BOOL(0, "stdin-packs", &stdin_packs,
+ N_("read packs from stdin")),
OPT_BOOL(0, "stdout", &pack_to_stdout,
N_("output pack to stdout")),
OPT_BOOL(0, "include-tag", &include_tag,
@@ -3292,9 +3978,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 +3997,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,9 +4023,15 @@ 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);
+ if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0))
+ pack_idx_opts.flags |= WRITE_REV;
progress = isatty(2);
argc = parse_options(argc, argv, prefix, pack_objects_options,
@@ -3344,6 +4044,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (pack_to_stdout != !base_name || argc)
usage_with_options(pack_usage, pack_objects_options);
+ if (depth < 0)
+ depth = 0;
if (depth >= (1 << OE_DEPTH_BITS)) {
warning(_("delta chain depth %d is too deep, forcing %d"),
depth, (1 << OE_DEPTH_BITS) - 1);
@@ -3354,37 +4056,39 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
(1U << OE_Z_DELTA_BITS) - 1);
cache_max_small_delta_size = (1U << OE_Z_DELTA_BITS) - 1;
}
+ if (window < 0)
+ window = 0;
- 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) {
+ if (rev_list_unpacked && !stdin_packs) {
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,9 +4125,13 @@ 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;
+ if (stdin_packs)
+ die(_("cannot use --filter with --stdin-packs"));
}
+ if (stdin_packs && use_internal_rev_list)
+ die(_("cannot use internal rev list with --stdin-packs"));
+
/*
* "soft" reasons not to use bitmaps - for on-disk repack by default we want
*
@@ -3447,7 +4155,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;
@@ -3482,15 +4190,21 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (progress)
progress_state = start_progress(_("Enumerating objects"), 0);
- if (!use_internal_rev_list)
+ if (stdin_packs) {
+ /* avoids adding objects in excluded packs */
+ ignore_packed_keep_in_core = 1;
+ read_packs_list_from_stdin();
+ if (rev_list_unpacked)
+ add_unreachable_loose_objects();
+ } else 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)
- for_each_ref(add_ref_tag, NULL);
+ for_each_tag_ref(add_ref_tag, NULL);
stop_progress(&progress_state);
trace2_region_leave("pack-objects", "enumerate-objects",
the_repository);
@@ -3506,13 +4220,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..8bf5c0acad 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -20,7 +20,7 @@ static int load_all_packs, verbose, alt_odb;
struct llist_item {
struct llist_item *next;
- const struct object_id *oid;
+ struct object_id oid;
};
static struct llist {
struct llist_item *front;
@@ -95,10 +95,10 @@ static struct llist * llist_copy(struct llist *list)
static inline struct llist_item *llist_insert(struct llist *list,
struct llist_item *after,
- const struct object_id *oid)
+ const unsigned char *oid)
{
struct llist_item *new_item = llist_item_get();
- new_item->oid = oid;
+ oidread(&new_item->oid, oid);
new_item->next = NULL;
if (after != NULL) {
@@ -118,7 +118,7 @@ static inline struct llist_item *llist_insert(struct llist *list,
}
static inline struct llist_item *llist_insert_back(struct llist *list,
- const struct object_id *oid)
+ const unsigned char *oid)
{
return llist_insert(list, list->back, oid);
}
@@ -130,9 +130,9 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
l = (hint == NULL) ? list->front : hint;
while (l) {
- int cmp = oidcmp(l->oid, oid);
+ int cmp = oidcmp(&l->oid, oid);
if (cmp > 0) { /* we insert before this entry */
- return llist_insert(list, prev, oid);
+ return llist_insert(list, prev, oid->hash);
}
if (!cmp) { /* already exists */
return l;
@@ -141,11 +141,11 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list,
l = l->next;
}
/* insert at the end */
- return llist_insert_back(list, oid);
+ return llist_insert_back(list, oid->hash);
}
/* returns a pointer to an item in front of sha1 */
-static inline struct llist_item * llist_sorted_remove(struct llist *list, const struct object_id *oid, struct llist_item *hint)
+static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *oid, struct llist_item *hint)
{
struct llist_item *prev, *l;
@@ -153,7 +153,7 @@ redo_from_start:
l = (hint == NULL) ? list->front : hint;
prev = NULL;
while (l) {
- const int cmp = oidcmp(l->oid, oid);
+ const int cmp = hashcmp(l->oid.hash, oid);
if (cmp > 0) /* not in list, since sorted */
return prev;
if (!cmp) { /* found */
@@ -188,7 +188,7 @@ static void llist_sorted_difference_inplace(struct llist *A,
b = B->front;
while (b) {
- hint = llist_sorted_remove(A, b->oid, hint);
+ hint = llist_sorted_remove(A, b->oid.hash, hint);
b = b->next;
}
}
@@ -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;
@@ -260,10 +260,10 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
/* cmp ~ p1 - p2 */
if (cmp == 0) {
p1_hint = llist_sorted_remove(p1->unique_objects,
- (const struct object_id *)(p1_base + p1_off),
+ p1_base + p1_off,
p1_hint);
p2_hint = llist_sorted_remove(p2->unique_objects,
- (const struct object_id *)(p1_base + p1_off),
+ p1_base + p1_off,
p2_hint);
p1_off += p1_step;
p2_off += p2_step;
@@ -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;
@@ -373,7 +373,7 @@ static void sort_pack_list(struct pack_list **pl)
return;
/* prepare an array of packed_list for easier sorting */
- ary = xcalloc(n, sizeof(struct pack_list *));
+ CALLOC_ARRAY(ary, n);
for (n = 0, p = *pl; p; p = p->next)
ary[n++] = p;
@@ -455,7 +455,7 @@ static void load_all_objects(void)
l = pl->remaining_objects->front;
while (l) {
hint = llist_insert_sorted_unique(all_objects,
- l->oid, hint);
+ &l->oid, hint);
l = l->next;
}
pl = pl->next;
@@ -473,6 +473,12 @@ static void cmp_local_packs(void)
{
struct pack_list *subset, *pl = local_packs;
+ /* only one packfile */
+ if (!pl->next) {
+ llist_init(&pl->unique_objects);
+ return;
+ }
+
while ((subset = pl)) {
while ((subset = subset->next))
cmp_two_packs(pl, subset);
@@ -499,7 +505,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))
@@ -515,7 +521,7 @@ static struct pack_list * add_pack(struct packed_git *p)
base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
step = the_hash_algo->rawsz + ((p->index_version < 2) ? 4 : 0);
while (off < p->num_objects * step) {
- llist_insert_back(l.remaining_objects, (const struct object_id *)(base + off));
+ llist_insert_back(l.remaining_objects, base + off);
off += step;
}
l.all_objects_size = l.remaining_objects->size;
@@ -554,6 +560,7 @@ static void load_all(void)
int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
{
int i;
+ int i_still_use_this = 0;
struct pack_list *min = NULL, *red, *pl;
struct llist *ignore;
struct object_id *oid;
@@ -580,12 +587,24 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
alt_odb = 1;
continue;
}
+ if (!strcmp(arg, "--i-still-use-this")) {
+ i_still_use_this = 1;
+ continue;
+ }
if (*arg == '-')
usage(pack_redundant_usage);
else
break;
}
+ if (!i_still_use_this) {
+ fputs(_("'git pack-redundant' is nominated for removal.\n"
+ "If you still use this command, please add an extra\n"
+ "option, '--i-still-use-this', on the command line\n"
+ "and let us know you still use it by sending an e-mail\n"
+ "to <git@vger.kernel.org>. Thanks.\n"), stderr);
+ }
+
if (load_all_packs)
load_all();
else
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..b311ea6b9d 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,
- "(false|true|merges|preserve|interactive)",
- N_("incorporate changes by rebasing rather than merging"),
- PARSE_OPT_OPTARG, parse_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),
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");
}
/**
@@ -320,7 +324,7 @@ static const char *config_get_ff(void)
* looks for the value of "pull.rebase". If both configuration keys do not
* exist, returns REBASE_FALSE.
*/
-static enum rebase_type config_get_rebase(void)
+static enum rebase_type config_get_rebase(int *rebase_unspecified)
{
struct branch *curr_branch = branch_get("HEAD");
const char *value;
@@ -340,6 +344,8 @@ static enum rebase_type config_get_rebase(void)
if (!git_config_get_value("pull.rebase", &value))
return parse_config_rebase("pull.rebase", value, 1);
+ *rebase_unspecified = 1;
+
return REBASE_FALSE;
}
@@ -348,6 +354,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 +363,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 +510,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 +596,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 +623,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 +636,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 +649,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 +787,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;
@@ -812,67 +839,124 @@ static int get_octopus_merge_base(struct object_id *merge_base,
/**
* Given the current HEAD oid, the merge head returned from git-fetch and the
- * fork point calculated by get_rebase_fork_point(), runs git-rebase with the
- * appropriate arguments and returns its exit status.
+ * fork point calculated by get_rebase_fork_point(), compute the <newbase> and
+ * <upstream> arguments to use for the upcoming git-rebase invocation.
*/
-static int run_rebase(const struct object_id *curr_head,
+static int get_rebase_newbase_and_upstream(struct object_id *newbase,
+ struct object_id *upstream,
+ const struct object_id *curr_head,
const struct object_id *merge_head,
const struct object_id *fork_point)
{
- int ret;
struct object_id oct_merge_base;
- struct argv_array args = ARGV_ARRAY_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");
+ if (fork_point && !is_null_oid(fork_point))
+ oidcpy(upstream, fork_point);
+ else
+ oidcpy(upstream, merge_head);
+
+ oidcpy(newbase, merge_head);
+
+ return 0;
+}
+
+/**
+ * Given the <newbase> and <upstream> calculated by
+ * get_rebase_newbase_and_upstream(), runs git-rebase with the
+ * appropriate arguments and returns its exit status.
+ */
+static int run_rebase(const struct object_id *newbase,
+ const struct object_id *upstream)
+{
+ int ret;
+ struct strvec args = STRVEC_INIT;
+
+ 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_signoff)
+ strvec_push(&args, opt_signoff);
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(newbase));
- if (fork_point && !is_null_oid(fork_point))
- argv_array_push(&args, oid_to_hex(fork_point));
- else
- argv_array_push(&args, oid_to_hex(merge_head));
+ strvec_push(&args, oid_to_hex(upstream));
- 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;
}
+static int get_can_ff(struct object_id *orig_head,
+ struct oid_array *merge_heads)
+{
+ int ret;
+ struct commit_list *list = NULL;
+ struct commit *merge_head, *head;
+ struct object_id *orig_merge_head;
+
+ if (merge_heads->nr > 1)
+ return 0;
+
+ orig_merge_head = &merge_heads->oid[0];
+ head = lookup_commit_reference(the_repository, orig_head);
+ commit_list_insert(head, &list);
+ merge_head = lookup_commit_reference(the_repository, orig_merge_head);
+ ret = repo_is_descendant_of(the_repository, merge_head, list);
+ free_commit_list(list);
+ return ret;
+}
+
+static void show_advice_pull_non_ff(void)
+{
+ advise(_("You have divergent branches and need to specify how to reconcile them.\n"
+ "You can do so by running one of the following commands sometime before\n"
+ "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"));
+}
+
int cmd_pull(int argc, const char **argv, const char *prefix)
{
const char *repo, **refspecs;
struct oid_array merge_heads = OID_ARRAY_INIT;
struct object_id orig_head, curr_head;
struct object_id rebase_fork_point;
- int autostash;
+ int rebase_unspecified = 0;
+ int can_ff;
if (!getenv("GIT_REFLOG_ACTION"))
set_reflog_message(argc, argv);
@@ -890,11 +974,25 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
parse_repo_refspecs(argc, argv, &repo, &refspecs);
- if (!opt_ff)
+ if (!opt_ff) {
opt_ff = xstrdup_or_null(config_get_ff());
+ /*
+ * A subtle point: opt_ff was set on the line above via
+ * reading from config. opt_rebase, in contrast, is set
+ * before this point via command line options. The setting
+ * of opt_rebase via reading from config (using
+ * config_get_rebase()) does not happen until later. We
+ * are relying on the next if-condition happening before
+ * the config_get_rebase() call so that an explicit
+ * "--rebase" can override a config setting of
+ * pull.ff=only.
+ */
+ if (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, "--ff-only"))
+ opt_ff = "--ff";
+ }
if (opt_rebase < 0)
- opt_rebase = config_get_rebase();
+ opt_rebase = config_get_rebase(&rebase_unspecified);
if (read_cache_unmerged())
die_resolve_conflict("pull");
@@ -905,11 +1003,8 @@ 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) {
+ int autostash = config_autostash;
if (opt_autostash != -1)
autostash = opt_autostash;
@@ -968,31 +1063,47 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
die(_("Cannot merge multiple branches into empty head."));
return pull_into_void(merge_heads.oid, &curr_head);
}
- if (opt_rebase && merge_heads.nr > 1)
- die(_("Cannot rebase onto multiple branches."));
+ if (merge_heads.nr > 1) {
+ if (opt_rebase)
+ die(_("Cannot rebase onto multiple branches."));
+ if (opt_ff && !strcmp(opt_ff, "--ff-only"))
+ die(_("Cannot fast-forward to multiple branches."));
+ }
+
+ can_ff = get_can_ff(&orig_head, &merge_heads);
+
+ /* ff-only takes precedence over rebase */
+ if (opt_ff && !strcmp(opt_ff, "--ff-only")) {
+ if (!can_ff)
+ die_ff_impossible();
+ opt_rebase = REBASE_FALSE;
+ }
+ /* If no action specified and we can't fast forward, then warn. */
+ if (!opt_ff && rebase_unspecified && !can_ff) {
+ show_advice_pull_non_ff();
+ die(_("Need to specify how to reconcile divergent branches."));
+ }
if (opt_rebase) {
int ret = 0;
+
+ struct object_id newbase;
+ struct object_id upstream;
+ get_rebase_newbase_and_upstream(&newbase, &upstream, &curr_head,
+ merge_heads.oid, &rebase_fork_point);
+
if ((recurse_submodules == RECURSE_SUBMODULES_ON ||
recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&
- submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head))
+ submodule_touches_in_range(the_repository, &upstream, &curr_head))
die(_("cannot rebase with locally recorded submodule modifications"));
- if (!autostash) {
- struct commit_list *list = NULL;
- struct commit *merge_head, *head;
-
- head = lookup_commit_reference(the_repository,
- &orig_head);
- commit_list_insert(head, &list);
- merge_head = lookup_commit_reference(the_repository,
- &merge_heads.oid[0]);
- if (is_descendant_of(merge_head, list)) {
- /* we can fast-forward this without invoking rebase */
- opt_ff = "--ff-only";
- ret = run_merge();
- }
+
+ if (can_ff) {
+ /* we can fast-forward this without invoking rebase */
+ opt_ff = "--ff-only";
+ ret = run_merge();
+ } else {
+ ret = run_rebase(&newbase, &upstream);
}
- 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..e8b10a9b7e 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, ':'))
+ if (strchr(ref, ':') || !*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,
@@ -188,88 +185,73 @@ static const char message_detached_head_die[] =
"\n"
" git push %s HEAD:<name-of-remote-branch>\n");
-static void setup_push_upstream(struct remote *remote, struct branch *branch,
- int triangular, int simple)
+static const char *get_upstream_ref(struct branch *branch, const char *remote_name)
{
- struct strbuf refspec = STRBUF_INIT;
-
- if (!branch)
- die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n"
"\n"
" git push --set-upstream %s %s\n"),
branch->name,
- remote->name,
+ remote_name,
branch->name);
if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name);
- if (triangular)
- die(_("You are pushing to remote '%s', which is not the upstream of\n"
- "your current branch '%s', without telling me what to push\n"
- "to update which remote branch."),
- remote->name, branch->name);
-
- if (simple) {
- /* Additional safety */
- if (strcmp(branch->refname, branch->merge[0]->src))
- die_push_simple(branch, remote);
- }
-
- strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src);
- refspec_append(&rs, refspec.buf);
-}
-
-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);
-}
-static int is_workflow_triangular(struct remote *remote)
-{
- struct remote *fetch_remote = remote_get(NULL);
- return (fetch_remote && fetch_remote != remote);
+ return branch->merge[0]->src;
}
static void setup_default_push_refspecs(struct remote *remote)
{
- struct branch *branch = branch_get(NULL);
- int triangular = is_workflow_triangular(remote);
+ struct branch *branch;
+ const char *dst;
+ int same_remote;
switch (push_default) {
- default:
case PUSH_DEFAULT_MATCHING:
refspec_append(&rs, ":");
+ return;
+
+ case PUSH_DEFAULT_NOTHING:
+ die(_("You didn't specify any refspecs to push, and "
+ "push.default is \"nothing\"."));
+ return;
+ default:
break;
+ }
+
+ branch = branch_get(NULL);
+ if (!branch)
+ die(_(message_detached_head_die), remote->name);
+
+ dst = branch->refname;
+ same_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));
+ switch (push_default) {
+ default:
case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:
- if (triangular)
- setup_push_current(remote, branch);
- else
- setup_push_upstream(remote, branch, triangular, 1);
+ if (!same_remote)
+ break;
+ if (strcmp(branch->refname, get_upstream_ref(branch, remote->name)))
+ die_push_simple(branch, remote);
break;
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote, branch, triangular, 0);
+ if (!same_remote)
+ die(_("You are pushing to remote '%s', which is not the upstream of\n"
+ "your current branch '%s', without telling me what to push\n"
+ "to update which remote branch."),
+ remote->name, branch->name);
+ dst = get_upstream_ref(branch, remote->name);
break;
case PUSH_DEFAULT_CURRENT:
- setup_push_current(remote, branch);
- break;
-
- case PUSH_DEFAULT_NOTHING:
- die(_("You didn't specify any refspecs to push, and "
- "push.default is \"nothing\"."));
break;
}
+
+ refspec_appendf(&rs, "%s:%s", branch->refname, dst);
}
static const char message_advice_pull_before_push[] =
@@ -299,6 +281,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 +322,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 +352,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 +378,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 +432,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 +516,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 +537,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 +550,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 +569,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 +592,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 +608,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..50318849d6 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -3,6 +3,7 @@
#include "parse-options.h"
#include "range-diff.h"
#include "config.h"
+#include "revision.h"
static const char * const builtin_range_diff_usage[] = {
N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
@@ -13,14 +14,27 @@ NULL
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 };
- int simple_color = -1;
+ struct strvec other_arg = STRVEC_INIT;
+ struct range_diff_options range_diff_opts = {
+ .creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT,
+ .diffopt = &diffopt,
+ .other_arg = &other_arg
+ };
+ int simple_color = -1, left_only = 0, right_only = 0;
struct option range_diff_options[] = {
- OPT_INTEGER(0, "creation-factor", &creation_factor,
- N_("Percentage by which creation is weighted")),
+ OPT_INTEGER(0, "creation-factor",
+ &range_diff_opts.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_BOOL(0, "left-only", &left_only,
+ N_("only emit output related to the first range")),
+ OPT_BOOL(0, "right-only", &right_only,
+ N_("only emit output related to the second range")),
OPT_END()
};
struct option *options;
@@ -32,7 +46,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);
@@ -42,12 +56,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
diffopt.use_color = 1;
if (argc == 2) {
- if (!strstr(argv[0], ".."))
- die(_("no .. in range: '%s'"), argv[0]);
+ if (!is_range_diff_range(argv[0]))
+ die(_("not a commit range: '%s'"), argv[0]);
strbuf_addstr(&range1, argv[0]);
- if (!strstr(argv[1], ".."))
- die(_("no .. in range: '%s'"), argv[1]);
+ if (!is_range_diff_range(argv[1]))
+ die(_("not a commit range: '%s'"), argv[1]);
strbuf_addstr(&range2, argv[1]);
} else if (argc == 3) {
strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
@@ -77,9 +91,12 @@ 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);
+ range_diff_opts.dual_color = simple_color < 1;
+ range_diff_opts.left_only = left_only;
+ range_diff_opts.right_only = right_only;
+ res = show_range_diff(range1.buf, range2.buf, &range_diff_opts);
+ 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..c284a7ace1 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,20 +92,27 @@ 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;
char *strategy, *strategy_opts;
struct strbuf git_format_patch_opt;
int reschedule_failed_exec;
- int use_legacy_rebase;
+ int reapply_cherry_picks;
+ int fork_point;
};
#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_format_patch_opt = STRBUF_INIT \
+ .git_am_opts = STRVEC_INIT, \
+ .git_format_patch_opt = STRBUF_INIT, \
+ .fork_point = -1, \
}
static struct replay_opts get_replay_opts(const struct rebase_options *opts)
@@ -102,6 +120,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 +129,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 = xstrdup_or_null(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 +277,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 +302,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 +334,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 +357,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 +376,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 +402,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 +433,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 +463,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
@@ -441,14 +485,18 @@ static const char * const builtin_rebase_interactive_usage[] = {
int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
{
struct rebase_options opts = REBASE_OPTIONS_INIT;
- struct object_id squash_onto = null_oid;
+ struct object_id squash_onto = *null_oid();
enum action command = ACTION_NONE;
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 +556,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 +566,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 +606,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 +613,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 +632,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 +656,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 +670,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 +679,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 +688,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 +708,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 +727,26 @@ 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;
-}
-
-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;
+ write_file(state_dir_path("signoff", opts), "--signoff");
- 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);
+ unlink(git_path_auto_merge(the_repository));
+ 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 +784,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 +799,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 +825,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 +839,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 +848,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 +866,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 +910,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 +945,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 +959,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 +983,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 +1018,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 +1026,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 +1036,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);
@@ -1239,11 +1097,15 @@ static int rebase_config(const char *var, const char *value, void *data)
return 0;
}
- if (!strcmp(var, "rebase.usebuiltin")) {
- opts->use_legacy_rebase = !git_config_bool(var, value);
+ if (!strcmp(var, "rebase.forkpoint")) {
+ opts->fork_point = git_config_bool(var, value) ? -1 : 0;
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 +1127,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 {
- oidcpy(merge_base, &null_oid);
- res = 0;
+ if (!merge_bases || merge_bases->next) {
+ oidcpy(merge_base, null_oid());
+ 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 +1189,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 +1204,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 +1268,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,35 +1292,38 @@ 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;
const char *rebase_merges = NULL;
- int fork_point = -1;
struct string_list strategy_options = STRING_LIST_INIT_NODUP;
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 +1331,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,39 +1365,49 @@ 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"),
PARSE_OPT_OPTARG, NULL, (intptr_t)""},
- OPT_BOOL(0, "fork-point", &fork_point,
+ OPT_BOOL(0, "fork-point", &options.fork_point,
N_("use 'merge-base --fork-point' to refine upstream")),
OPT_STRING('s', "strategy", &options.strategy,
N_("strategy"), N_("use the given merge strategy")),
@@ -1489,6 +1420,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,17 +1430,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);
-
- if (options.use_legacy_rebase ||
- !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
- warning(_("the rebase.useBuiltin support has been removed!\n"
- "See its entry in 'git help config' for details."));
+ /* options.gpg_sign_opt will be either "-S" or NULL */
+ gpg_sign = options.gpg_sign_opt ? "" : NULL;
+ FREE_AND_NULL(options.gpg_sign_opt);
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/applying", apply_dir());
@@ -1515,7 +1442,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 +1454,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 +1483,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 && options.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 +1550,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 +1568,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 +1628,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 +1659,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,14 +1689,31 @@ 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) {
int i;
if (!options.strategy)
- options.strategy = "recursive";
+ options.strategy = "ort";
strbuf_reset(&buf);
for (i = 0; i < strategy_options.nr; i++)
@@ -1762,10 +1725,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 +1740,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 +1827,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;
@@ -1860,8 +1836,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
NULL);
if (!options.upstream_name)
error_on_missing_default_upstream();
- if (fork_point < 0)
- fork_point = 1;
+ if (options.fork_point < 0)
+ options.fork_point = 1;
} else {
options.upstream_name = argv[0];
argc--;
@@ -1881,7 +1857,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 +1869,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 +1909,13 @@ 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) &&
+ lookup_commit_reference(the_repository,
+ &options.orig_head))
options.head_name = NULL;
else
die(_("fatal: no such branch/commit '%s'"),
@@ -1950,7 +1941,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
} else
BUG("unexpected number of arguments left to parse");
- if (fork_point > 0) {
+ if (options.fork_point > 0) {
struct commit *head =
lookup_commit_reference(the_repository,
&options.orig_head);
@@ -1962,60 +1953,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 +1970,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 +2055,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 +2065,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 +2083,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;
@@ -2173,6 +2109,8 @@ cleanup:
free(options.head_name);
free(options.gpg_sign_opt);
free(options.cmd);
+ free(options.strategy);
+ strbuf_release(&options.git_format_patch_opt);
free(squash_onto_name);
return ret;
}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c5f5da940b..2d1f97e1ca 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>"),
@@ -53,9 +54,11 @@ static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int advertise_atomic_push = 1;
static int advertise_push_options;
+static int advertise_sid;
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 +99,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 +242,18 @@ 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;
+ }
+
+ if (strcmp(var, "transfer.advertisesid") == 0) {
+ advertise_sid = git_config_bool(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -239,7 +265,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 +274,9 @@ 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");
+ if (advertise_sid)
+ strbuf_addf(&cap, " session-id=%s", trace2_session_id());
+ 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);
@@ -300,7 +329,7 @@ static void write_head_info(void)
for_each_alternate_ref(show_one_alternate_ref, &seen);
oidset_clear(&seen);
if (!sent_capabilities)
- show_ref("capabilities^{}", &null_oid);
+ show_ref("capabilities^{}", null_oid());
advertise_shallow_grafts(1);
@@ -308,19 +337,93 @@ 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 rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
-static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
+static void proc_receive_ref_append(const char *prefix)
+{
+ struct proc_receive_ref *ref_pattern;
+ char *p;
+ int len;
+
+ CALLOC_ARRAY(ref_pattern, 1);
+ 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 report_message(const char *prefix, const char *err, va_list params)
{
@@ -339,6 +442,7 @@ static void report_message(const char *prefix, const char *err, va_list params)
xwrite(2, msg, sz);
}
+__attribute__((format (printf, 1, 2)))
static void rp_warning(const char *err, ...)
{
va_list params;
@@ -347,6 +451,7 @@ static void rp_warning(const char *err, ...)
va_end(params);
}
+__attribute__((format (printf, 1, 2)))
static void rp_error(const char *err, ...)
{
va_list params;
@@ -418,24 +523,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 +550,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 +604,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 +666,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;
@@ -639,38 +763,39 @@ static void prepare_push_cert_sha1(struct child_process *proc)
memset(&sigcheck, '\0', sizeof(sigcheck));
- bogs = parse_signature(push_cert.buf, push_cert.len);
+ bogs = parse_signed_buffer(push_cert.buf, push_cert.len);
check_signature(push_cert.buf, bogs, push_cert.buf + bogs,
push_cert.len - bogs, &sigcheck);
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 +824,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 +883,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 +926,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 +965,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) {
+ CALLOC_ARRAY(hint->report, 1);
+ 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 +1309,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 +1326,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 +1362,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 +1381,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 +1392,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 +1405,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 +1413,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 +1430,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 +1481,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 +1493,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 +1525,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 +1574,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 +1636,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 +1846,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 +1886,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 +1922,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 +1952,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 +1994,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
@@ -1571,7 +2049,7 @@ static void queue_commands_from_cert(struct command **tail,
die("malformed push certificate %.*s", 100, push_cert->buf);
else
boc += 2;
- eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len);
+ eoc = push_cert->buf + parse_signed_buffer(push_cert->buf, push_cert->len);
while (boc < eoc) {
const char *eol = memchr(boc, '\n', eoc - boc);
@@ -1603,8 +2081,13 @@ 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;
+ const char *client_sid;
+ 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 +2098,19 @@ 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);
+ client_sid = parse_feature_value(feature_list, "session-id", &len, NULL);
+ if (client_sid) {
+ char *sid = xstrndup(client_sid, len);
+ trace2_data_string("transfer", NULL, "client-sid", client_sid);
+ free(sid);
+ }
}
if (!strcmp(reader->line, "push-cert")) {
@@ -1684,10 +2180,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 +2207,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 +2227,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,35 +2246,35 @@ 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;
status = start_command(&child);
if (status)
return "index-pack fork failed";
- pack_lockfile = index_pack_lockfile(child.out);
+ pack_lockfile = index_pack_lockfile(child.out, NULL);
close(child.out);
status = finish_command(&child);
if (status)
@@ -1809,19 +2305,16 @@ 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);
ALLOC_ARRAY(si->used_shallow, si->shallow->nr);
assign_shallow_commits_to_refs(si, si->used_shallow, NULL);
- si->need_reachability_test =
- xcalloc(si->shallow->nr, sizeof(*si->need_reachability_test));
- si->reachable =
- xcalloc(si->shallow->nr, sizeof(*si->reachable));
- si->shallow_ref = xcalloc(si->ref->nr, sizeof(*si->shallow_ref));
+ CALLOC_ARRAY(si->need_reachability_test, si->shallow->nr);
+ CALLOC_ARRAY(si->reachable, si->shallow->nr);
+ CALLOC_ARRAY(si->shallow_ref, si->ref->nr);
for (i = 0; i < si->nr_ours; i++)
si->need_reachability_test[si->ours[i]] = 1;
@@ -1876,7 +2369,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 +2410,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 +2563,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..09541d1c80 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;
}
@@ -601,6 +602,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
*/
if (cb.cmd.stalefix) {
repo_init_revisions(the_repository, &cb.cmd.revs, prefix);
+ cb.cmd.revs.do_not_die_on_missing_tree = 1;
+ cb.cmd.revs.ignore_missing = 1;
+ cb.cmd.revs.ignore_missing_links = 1;
if (flags & EXPIRE_REFLOGS_VERBOSE)
printf(_("Marking reachable objects..."));
mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL);
@@ -614,7 +618,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..7f88e6ce9d 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);
@@ -219,7 +221,7 @@ static int add(int argc, const char **argv)
if (fetch_tags != TAGS_DEFAULT) {
strbuf_reset(&buf);
- strbuf_addf(&buf, "remote.%s.tagopt", name);
+ strbuf_addf(&buf, "remote.%s.tagOpt", name);
git_config_set(buf.buf,
fetch_tags == TAGS_SET ? "--tags" : "--no-tags");
}
@@ -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)
@@ -648,7 +712,7 @@ static int mv(int argc, const char **argv)
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.fetch", rename.new_name);
- git_config_set_multivar(buf.buf, NULL, NULL, 1);
+ git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name);
for (i = 0; i < oldremote->fetch.raw_nr; i++) {
char *ptr;
@@ -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;
@@ -856,9 +938,6 @@ static int get_remote_ref_states(const char *name,
struct ref_states *states,
int query)
{
- struct transport *transport;
- const struct ref *remote_refs;
-
states->remote = remote_get(name);
if (!states->remote)
return error(_("No such remote: '%s'"), name);
@@ -866,10 +945,12 @@ static int get_remote_ref_states(const char *name,
read_branches();
if (query) {
+ struct transport *transport;
+ const struct ref *remote_refs;
+
transport = transport_get(states->remote, states->remote->url_nr > 0 ?
states->remote->url[0] : NULL);
remote_refs = transport_get_remote_refs(transport, NULL);
- transport_disconnect(transport);
states->queried = 1;
if (query & GET_REF_STATES)
@@ -878,6 +959,7 @@ static int get_remote_ref_states(const char *name,
get_head_names(remote_refs, states);
if (query & GET_PUSH_REF_STATES)
get_push_ref_states(remote_refs, states);
+ transport_disconnect(transport);
} else {
for_each_ref(append_ref_to_tracked_list, states);
string_list_sort(&states->tracked);
@@ -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,41 +1457,42 @@ 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);
+ return git_config_set_multivar_gently(key, NULL, NULL,
+ CONFIG_FLAGS_MULTI_REPLACE);
}
static void add_branches(struct remote *remote, const char **branches,
@@ -1434,10 +1517,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 +1573,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 +1643,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);
@@ -1598,7 +1687,8 @@ static int set_url(int argc, const char **argv)
if (!delete_mode)
git_config_set_multivar(name_buf.buf, newurl, oldurl, 0);
else
- git_config_set_multivar(name_buf.buf, NULL, oldurl, 1);
+ git_config_set_multivar(name_buf.buf, NULL, oldurl,
+ CONFIG_FLAGS_MULTI_REPLACE);
out:
strbuf_release(&name_buf);
return 0;
diff --git a/builtin/repack.c b/builtin/repack.c
index f834b5551b..5f9bc74adc 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -7,16 +7,20 @@
#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"
+#include "pack.h"
static int delta_base_offset = 1;
static int pack_kept_objects = -1;
static int write_bitmaps = -1;
static int use_delta_islands;
-static char *packdir, *packtmp;
+static char *packdir, *packtmp_name, *packtmp;
static const char *const git_repack_usage[] = {
N_("git repack [<options>]"),
@@ -129,19 +133,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 +158,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,11 +198,43 @@ 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;
}
+static struct {
+ const char *name;
+ unsigned optional:1;
+} exts[] = {
+ {".pack"},
+ {".idx"},
+ {".rev", 1},
+ {".bitmap", 1},
+ {".promisor", 1},
+};
+
+static unsigned populate_pack_exts(char *name)
+{
+ struct stat statbuf;
+ struct strbuf path = STRBUF_INIT;
+ unsigned ret = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(exts); i++) {
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s-%s%s", packtmp, name, exts[i].name);
+
+ if (stat(path.buf, &statbuf))
+ continue;
+
+ ret |= (1 << i);
+ }
+
+ strbuf_release(&path);
+ return ret;
+}
+
static void repack_promisor_objects(const struct pack_objects_args *args,
struct string_list *names)
{
@@ -233,22 +263,30 @@ static void repack_promisor_objects(const struct pack_objects_args *args,
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
+ struct string_list_item *item;
char *promisor_name;
- int fd;
+
if (line.len != the_hash_algo->hexsz)
die(_("repack: Expecting full hex object ID lines only from pack-objects."));
- string_list_append(names, line.buf);
+ item = string_list_append(names, line.buf);
/*
* 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);
- fd = open(promisor_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
- if (fd < 0)
- die_errno(_("unable to create '%s'"), promisor_name);
- close(fd);
+ write_promisor_file(promisor_name, NULL, 0);
+
+ item->util = (void *)(uintptr_t)populate_pack_exts(item->string);
+
free(promisor_name);
}
fclose(out);
@@ -259,24 +297,152 @@ static void repack_promisor_objects(const struct pack_objects_args *args,
#define ALL_INTO_ONE 1
#define LOOSEN_UNREACHABLE 2
+struct pack_geometry {
+ struct packed_git **pack;
+ uint32_t pack_nr, pack_alloc;
+ uint32_t split;
+};
+
+static uint32_t geometry_pack_weight(struct packed_git *p)
+{
+ if (open_pack_index(p))
+ die(_("cannot open index for %s"), p->pack_name);
+ return p->num_objects;
+}
+
+static int geometry_cmp(const void *va, const void *vb)
+{
+ uint32_t aw = geometry_pack_weight(*(struct packed_git **)va),
+ bw = geometry_pack_weight(*(struct packed_git **)vb);
+
+ if (aw < bw)
+ return -1;
+ if (aw > bw)
+ return 1;
+ return 0;
+}
+
+static void init_pack_geometry(struct pack_geometry **geometry_p)
+{
+ struct packed_git *p;
+ struct pack_geometry *geometry;
+
+ *geometry_p = xcalloc(1, sizeof(struct pack_geometry));
+ geometry = *geometry_p;
+
+ for (p = get_all_packs(the_repository); p; p = p->next) {
+ if (!pack_kept_objects && p->pack_keep)
+ continue;
+
+ ALLOC_GROW(geometry->pack,
+ geometry->pack_nr + 1,
+ geometry->pack_alloc);
+
+ geometry->pack[geometry->pack_nr] = p;
+ geometry->pack_nr++;
+ }
+
+ QSORT(geometry->pack, geometry->pack_nr, geometry_cmp);
+}
+
+static void split_pack_geometry(struct pack_geometry *geometry, int factor)
+{
+ uint32_t i;
+ uint32_t split;
+ off_t total_size = 0;
+
+ if (!geometry->pack_nr) {
+ geometry->split = geometry->pack_nr;
+ return;
+ }
+
+ /*
+ * First, count the number of packs (in descending order of size) which
+ * already form a geometric progression.
+ */
+ for (i = geometry->pack_nr - 1; i > 0; i--) {
+ struct packed_git *ours = geometry->pack[i];
+ struct packed_git *prev = geometry->pack[i - 1];
+
+ if (unsigned_mult_overflows(factor, geometry_pack_weight(prev)))
+ die(_("pack %s too large to consider in geometric "
+ "progression"),
+ prev->pack_name);
+
+ if (geometry_pack_weight(ours) < factor * geometry_pack_weight(prev))
+ break;
+ }
+
+ split = i;
+
+ if (split) {
+ /*
+ * Move the split one to the right, since the top element in the
+ * last-compared pair can't be in the progression. Only do this
+ * when we split in the middle of the array (otherwise if we got
+ * to the end, then the split is in the right place).
+ */
+ split++;
+ }
+
+ /*
+ * Then, anything to the left of 'split' must be in a new pack. But,
+ * creating that new pack may cause packs in the heavy half to no longer
+ * form a geometric progression.
+ *
+ * Compute an expected size of the new pack, and then determine how many
+ * packs in the heavy half need to be joined into it (if any) to restore
+ * the geometric progression.
+ */
+ for (i = 0; i < split; i++) {
+ struct packed_git *p = geometry->pack[i];
+
+ if (unsigned_add_overflows(total_size, geometry_pack_weight(p)))
+ die(_("pack %s too large to roll up"), p->pack_name);
+ total_size += geometry_pack_weight(p);
+ }
+ for (i = split; i < geometry->pack_nr; i++) {
+ struct packed_git *ours = geometry->pack[i];
+
+ if (unsigned_mult_overflows(factor, total_size))
+ die(_("pack %s too large to roll up"), ours->pack_name);
+
+ if (geometry_pack_weight(ours) < factor * total_size) {
+ if (unsigned_add_overflows(total_size,
+ geometry_pack_weight(ours)))
+ die(_("pack %s too large to roll up"),
+ ours->pack_name);
+
+ split++;
+ total_size += geometry_pack_weight(ours);
+ } else
+ break;
+ }
+
+ geometry->split = split;
+}
+
+static void clear_pack_geometry(struct pack_geometry *geometry)
+{
+ if (!geometry)
+ return;
+
+ free(geometry->pack);
+ geometry->pack_nr = 0;
+ geometry->pack_alloc = 0;
+ geometry->split = 0;
+}
+
int cmd_repack(int argc, const char **argv, const char *prefix)
{
- struct {
- const char *name;
- unsigned optional:1;
- } exts[] = {
- {".pack"},
- {".idx"},
- {".bitmap", 1},
- {".promisor", 1},
- };
struct child_process cmd = CHILD_PROCESS_INIT;
struct string_list_item *item;
struct string_list names = STRING_LIST_INIT_DUP;
struct string_list rollback = STRING_LIST_INIT_NODUP;
struct string_list existing_packs = STRING_LIST_INIT_DUP;
+ struct pack_geometry *geometry = NULL;
struct strbuf line = STRBUF_INIT;
- int i, ext, ret, failed;
+ int i, ext, ret;
FILE *out;
/* variables to be filled by option parsing */
@@ -286,8 +452,8 @@ 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};
+ int geometric_factor = 0;
struct option builtin_repack_options[] = {
OPT_BIT('a', NULL, &pack_everything,
@@ -328,6 +494,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
N_("repack objects in packs marked with .keep")),
OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"),
N_("do not repack this pack")),
+ OPT_INTEGER('g', "geometric", &geometric_factor,
+ N_("find a geometric progression with factor <N>")),
OPT_END()
};
@@ -343,38 +511,62 @@ 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));
+ if (geometric_factor) {
+ if (pack_everything)
+ die(_("--geometric is incompatible with -A, -a"));
+ init_pack_geometry(&geometry);
+ split_pack_geometry(geometry, geometric_factor);
+ }
+
packdir = mkpathdup("%s/pack", get_object_directory());
- packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid());
+ packtmp_name = xstrfmt(".tmp-%d-pack", (int)getpid());
+ packtmp = mkpathdup("%s/%s", packdir, packtmp_name);
sigchain_push_common(remove_pack_on_signal);
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");
+ if (!geometry) {
+ /*
+ * We need to grab all reachable objects, including those that
+ * are reachable from reflogs and the index.
+ *
+ * When repacking into a geometric progression of packs,
+ * however, we ask 'git pack-objects --stdin-packs', and it is
+ * not about packing objects based on reachability but about
+ * repacking all the objects in specified packs and loose ones
+ * (indeed, --stdin-packs is incompatible with these options).
+ */
+ 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);
@@ -382,32 +574,56 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
repack_promisor_objects(&po_args, &names);
if (existing_packs.nr && delete_redundant) {
+ for_each_string_list_item(item, &names) {
+ strvec_pushf(&cmd.args, "--keep-pack=%s-%s.pack",
+ packtmp_name, item->string);
+ }
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 if (geometry) {
+ strvec_push(&cmd.args, "--stdin-packs");
+ strvec_push(&cmd.args, "--unpacked");
} 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;
+ if (geometry)
+ cmd.in = -1;
+ else
+ cmd.no_stdin = 1;
ret = start_command(&cmd);
if (ret)
return ret;
+ if (geometry) {
+ FILE *in = xfdopen(cmd.in, "w");
+ /*
+ * The resulting pack should contain all objects in packs that
+ * are going to be rolled up, but exclude objects in packs which
+ * are being left alone.
+ */
+ for (i = 0; i < geometry->split; i++)
+ fprintf(in, "%s\n", pack_basename(geometry->pack[i]));
+ for (i = geometry->split; i < geometry->pack_nr; i++)
+ fprintf(in, "^%s\n", pack_basename(geometry->pack[i]));
+ fclose(in);
+ }
+
out = xfdopen(cmd.out, "r");
while (strbuf_getline_lf(&line, out) != EOF) {
if (line.len != the_hash_algo->hexsz)
@@ -422,118 +638,42 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (!names.nr && !po_args.quiet)
printf_ln(_("Nothing new to pack."));
+ for_each_string_list_item(item, &names) {
+ item->util = (void *)(uintptr_t)populate_pack_exts(item->string);
+ }
+
close_object_store(the_repository->objects);
/*
* Ok we have prepared all new packfiles.
- * First see if there are packs of the same name and if so
- * if we can move them out of the way (this can happen if we
- * repacked immediately after packing fully.
*/
- failed = 0;
for_each_string_list_item(item, &names) {
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)) {
- free(fname);
- continue;
- }
-
- fname_old = mkpathdup("%s/old-%s%s", packdir,
- item->string, exts[ext].name);
- if (file_exists(fname_old))
- if (unlink(fname_old))
- failed = 1;
-
- if (!failed && rename(fname, fname_old)) {
- free(fname);
- free(fname_old);
- failed = 1;
- break;
- } else {
- string_list_append(&rollback, fname);
- free(fname_old);
- }
- }
- if (failed)
- break;
- }
- if (failed) {
- struct string_list rollback_failure = STRING_LIST_INIT_DUP;
- for_each_string_list_item(item, &rollback) {
- char *fname, *fname_old;
- fname = mkpathdup("%s/%s", packdir, item->string);
- fname_old = mkpathdup("%s/old-%s", packdir, item->string);
- if (rename(fname_old, fname))
- string_list_append(&rollback_failure, fname);
- free(fname);
- free(fname_old);
- }
-
- if (rollback_failure.nr) {
- int i;
- fprintf(stderr,
- _("WARNING: Some packs in use have been renamed by\n"
- "WARNING: prefixing old- to their name, in order to\n"
- "WARNING: replace them with the new version of the\n"
- "WARNING: file. But the operation failed, and the\n"
- "WARNING: attempt to rename them back to their\n"
- "WARNING: original names also failed.\n"
- "WARNING: Please rename them in %s manually:\n"), packdir);
- for (i = 0; i < rollback_failure.nr; i++)
- fprintf(stderr, "WARNING: old-%s -> %s\n",
- rollback_failure.items[i].string,
- rollback_failure.items[i].string);
- }
- exit(1);
- }
-
- /* Now the ones with the same name are out of the way... */
- for_each_string_list_item(item, &names) {
- for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
- char *fname, *fname_old;
- struct stat statbuffer;
- int exists = 0;
fname = mkpathdup("%s/pack-%s%s",
packdir, item->string, exts[ext].name);
fname_old = mkpathdup("%s-%s%s",
packtmp, item->string, exts[ext].name);
- if (!stat(fname_old, &statbuffer)) {
- statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
- chmod(fname_old, statbuffer.st_mode);
- exists = 1;
- }
- if (exists || !exts[ext].optional) {
+
+ if (((uintptr_t)item->util) & (1 << ext)) {
+ struct stat statbuffer;
+ if (!stat(fname_old, &statbuffer)) {
+ statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ chmod(fname_old, statbuffer.st_mode);
+ }
+
if (rename(fname_old, fname))
die_errno(_("renaming '%s' failed"), fname_old);
- }
- free(fname);
- free(fname_old);
- }
- }
+ } else if (!exts[ext].optional)
+ die(_("missing required file: %s"), fname_old);
+ else if (unlink(fname) < 0 && errno != ENOENT)
+ die_errno(_("could not unlink: %s"), fname);
- /* Remove the "old-" files */
- for_each_string_list_item(item, &names) {
- for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
- char *fname;
- fname = mkpathdup("%s/old-%s%s",
- packdir,
- item->string,
- exts[ext].name);
- if (remove_path(fname))
- warning(_("failed to remove '%s'"), fname);
free(fname);
+ free(fname_old);
}
}
-
/* End of pack replacement. */
reprepare_packed_git(the_repository);
@@ -551,6 +691,25 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (!string_list_has_string(&names, sha1))
remove_redundant_pack(packdir, item->string);
}
+
+ if (geometry) {
+ struct strbuf buf = STRBUF_INIT;
+
+ uint32_t i;
+ for (i = 0; i < geometry->split; i++) {
+ struct packed_git *p = geometry->pack[i];
+ if (string_list_has_string(&names,
+ hash_to_hex(p->hash)))
+ continue;
+
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, pack_basename(p));
+ strbuf_strip_suffix(&buf, ".pack");
+
+ remove_redundant_pack(packdir, buf.buf);
+ }
+ strbuf_release(&buf);
+ }
if (!po_args.quiet && isatty(2))
opts |= PRUNE_PACKED_VERBOSE;
prune_packed_objects(opts);
@@ -567,11 +726,12 @@ 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(), NULL, 0);
string_list_clear(&names, 0);
string_list_clear(&rollback, 0);
string_list_clear(&existing_packs, 0);
+ clear_pack_geometry(geometry);
strbuf_release(&line);
return 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/rerere.c b/builtin/rerere.c
index fd3be17b97..83d7a778e3 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -28,7 +28,7 @@ static int diff_two(const char *file1, const char *label1,
{
xpparam_t xpp;
xdemitconf_t xecfg;
- xdemitcb_t ecb;
+ xdemitcb_t ecb = { .out_line = outf };
mmfile_t minus, plus;
int ret;
@@ -41,8 +41,6 @@ static int diff_two(const char *file1, const char *label1,
xpp.flags = 0;
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
- ecb.out_hunk = NULL;
- ecb.out_line = outf;
ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
free(minus.ptr);
diff --git a/builtin/reset.c b/builtin/reset.c
index 26ef9a7bd0..43e855cb88 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/"))
+ FREE_AND_NULL(ref);
+
+ 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..36cb909eba 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,25 @@ 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 int show_disk_usage;
+static off_t total_disk_usage;
+
+static off_t get_object_disk_usage(struct object *obj)
+{
+ off_t size;
+ struct object_info oi = OBJECT_INFO_INIT;
+ oi.disk_sizep = &size;
+ if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0)
+ die(_("unable to get disk usage of %s"), oid_to_hex(&obj->oid));
+ return size;
+}
+
+static void finish_commit(struct commit *commit);
static void show_commit(struct commit *commit, void *data)
{
struct rev_list_info *info = data;
@@ -85,8 +101,11 @@ static void show_commit(struct commit *commit, void *data)
display_progress(progress, ++progress_counter);
+ if (show_disk_usage)
+ total_disk_usage += get_object_disk_usage(&commit->object);
+
if (info->flags & REV_LIST_QUIET) {
- finish_commit(commit, data);
+ finish_commit(commit);
return;
}
@@ -99,7 +118,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;
}
@@ -108,13 +127,15 @@ static void show_commit(struct commit *commit, void *data)
if (info->header_prefix)
fputs(info->header_prefix, stdout);
- if (!revs->graph)
- fputs(get_revision_mark(revs, commit), stdout);
- if (revs->abbrev_commit && revs->abbrev)
- fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev),
- stdout);
- else
- fputs(oid_to_hex(&commit->object.oid), stdout);
+ if (revs->include_header) {
+ if (!revs->graph)
+ fputs(get_revision_mark(revs, commit), stdout);
+ if (revs->abbrev_commit && revs->abbrev)
+ fputs(find_unique_abbrev(&commit->object.oid, revs->abbrev),
+ stdout);
+ else
+ fputs(oid_to_hex(&commit->object.oid), stdout);
+ }
if (revs->print_parents) {
struct commit_list *parents = commit->parents;
while (parents) {
@@ -134,7 +155,7 @@ static void show_commit(struct commit *commit, void *data)
show_decorations(revs, commit);
if (revs->commit_format == CMIT_FMT_ONELINE)
putchar(' ');
- else
+ else if (revs->include_header)
putchar('\n');
if (revs->verbose_header) {
@@ -188,10 +209,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 +271,32 @@ 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 (show_disk_usage)
+ total_disk_usage += get_object_disk_usage(obj);
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 +399,99 @@ 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,
+ int filter_provided_objects)
+{
+ 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, filter_provided_objects);
+ 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,
+ int filter_provided_objects)
+{
+ 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, filter_provided_objects);
+ if (!bitmap_git)
+ return -1;
+
+ traverse_bitmap_commit_list(bitmap_git, revs, &show_object_fast);
+ free_bitmap_index(bitmap_git);
+ return 0;
+}
+
+static int try_bitmap_disk_usage(struct rev_info *revs,
+ struct list_objects_filter_options *filter,
+ int filter_provided_objects)
+{
+ struct bitmap_index *bitmap_git;
+
+ if (!show_disk_usage)
+ return -1;
+
+ bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects);
+ if (!bitmap_git)
+ return -1;
+
+ printf("%"PRIuMAX"\n",
+ (uintmax_t)get_disk_usage_from_bitmap(bitmap_git, revs));
+ return 0;
+}
+
int cmd_rev_list(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -370,6 +504,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
int bisect_show_vars = 0;
int bisect_find_all = 0;
int use_bitmap_index = 0;
+ int filter_provided_objects = 0;
const char *show_progress = NULL;
if (argc == 2 && !strcmp(argv[1], "-h"))
@@ -379,6 +514,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &revs, prefix);
revs.abbrev = DEFAULT_ABBREV;
revs.commit_format = CMIT_FMT_UNSPECIFIED;
+ revs.include_header = 1;
/*
* Scan the argument list before invoking setup_revisions(), so that we
@@ -464,16 +600,16 @@ 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))) {
list_objects_filter_set_no_filter(&filter_options);
continue;
}
+ if (!strcmp(arg, "--filter-provided-objects")) {
+ filter_provided_objects = 1;
+ continue;
+ }
if (!strcmp(arg, "--filter-print-omitted")) {
arg_print_omitted = 1;
continue;
@@ -484,13 +620,41 @@ 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;
+ }
+
+ if (!strcmp(arg, ("--commit-header"))) {
+ revs.include_header = 1;
+ continue;
+ }
+
+ if (!strcmp(arg, ("--no-commit-header"))) {
+ revs.include_header = 0;
+ continue;
+ }
+
+ if (!strcmp(arg, "--disk-usage")) {
+ show_disk_usage = 1;
+ info.flags |= REV_LIST_QUIET;
+ continue;
+ }
+
usage(rev_list_usage);
}
+ if (revs.commit_format != CMIT_FMT_USERFORMAT)
+ revs.include_header = 1;
if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
/* The command line has a --pretty */
info.hdr_termination = '\n';
- if (revs.commit_format == CMIT_FMT_ONELINE)
+ if (revs.commit_format == CMIT_FMT_ONELINE || !revs.include_header)
info.header_prefix = "";
else
info.header_prefix = "commit ";
@@ -509,8 +673,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 +687,13 @@ 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, filter_provided_objects))
+ return 0;
+ if (!try_bitmap_disk_usage(&revs, &filter_options, filter_provided_objects))
+ return 0;
+ if (!try_bitmap_traversal(&revs, &filter_options, filter_provided_objects))
+ return 0;
}
if (prepare_revision_walk(&revs))
@@ -552,13 +703,30 @@ 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);
}
+ if (filter_provided_objects) {
+ struct commit_list *c;
+ for (i = 0; i < revs.pending.nr; i++) {
+ struct object_array_entry *pending = revs.pending.objects + i;
+ pending->item->flags |= NOT_USER_GIVEN;
+ }
+ for (c = revs.commits; c; c = c->next)
+ c->item->object.flags |= NOT_USER_GIVEN;
+ }
+
if (arg_print_omitted)
oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE);
if (arg_missing_action == MA_PRINT)
@@ -598,5 +766,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
printf("%d\n", revs.count_left + revs.count_right);
}
+ if (show_disk_usage)
+ printf("%"PRIuMAX"\n", (uintmax_t)total_disk_usage);
+
return 0;
}
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index f8bbe6d47e..22c4e1a4ff 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
@@ -434,11 +435,11 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
/* get the usage up to the first line with a -- on it */
for (;;) {
if (strbuf_getline(&sb, stdin) == EOF)
- die("premature end of input");
+ die(_("premature end of input"));
ALLOC_GROW(usage, unb + 1, usz);
if (!strcmp("--", sb.buf)) {
if (unb < 1)
- die("no usage string given before the `--' separator");
+ die(_("no usage string given before the `--' separator"));
usage[unb] = NULL;
break;
}
@@ -544,7 +545,7 @@ static void die_no_single_rev(int quiet)
if (quiet)
exit(1);
else
- die("Needed a single revision");
+ die(_("Needed a single revision"));
}
static const char builtin_rev_parse_usage[] =
@@ -582,6 +583,75 @@ static void handle_ref_opt(const char *pattern, const char *prefix)
clear_ref_exclusion(&ref_excludes);
}
+enum format_type {
+ /* We would like a relative path. */
+ FORMAT_RELATIVE,
+ /* We would like a canonical absolute path. */
+ FORMAT_CANONICAL,
+ /* We would like the default behavior. */
+ FORMAT_DEFAULT,
+};
+
+enum default_type {
+ /* Our default is a relative path. */
+ DEFAULT_RELATIVE,
+ /* Our default is a relative path if there's a shared root. */
+ DEFAULT_RELATIVE_IF_SHARED,
+ /* Our default is a canonical absolute path. */
+ DEFAULT_CANONICAL,
+ /* Our default is not to modify the item. */
+ DEFAULT_UNMODIFIED,
+};
+
+static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
+{
+ char *cwd = NULL;
+ /*
+ * We don't ever produce a relative path if prefix is NULL, so set the
+ * prefix to the current directory so that we can produce a relative
+ * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then
+ * we want an absolute path unless the two share a common prefix, so don't
+ * set it in that case, since doing so causes a relative path to always
+ * be produced if possible.
+ */
+ if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
+ prefix = cwd = xgetcwd();
+ if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
+ puts(path);
+ } else if (format == FORMAT_RELATIVE ||
+ (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
+ /*
+ * In order for relative_path to work as expected, we need to
+ * make sure that both paths are absolute paths. If we don't,
+ * we can end up with an unexpected absolute path that the user
+ * didn't want.
+ */
+ struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
+ if (!is_absolute_path(path)) {
+ strbuf_realpath_forgiving(&realbuf, path, 1);
+ path = realbuf.buf;
+ }
+ if (!is_absolute_path(prefix)) {
+ strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
+ prefix = prefixbuf.buf;
+ }
+ puts(relative_path(path, prefix, &buf));
+ strbuf_release(&buf);
+ strbuf_release(&realbuf);
+ strbuf_release(&prefixbuf);
+ } else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
+ struct strbuf buf = STRBUF_INIT;
+ puts(relative_path(path, prefix, &buf));
+ strbuf_release(&buf);
+ } else {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_realpath_forgiving(&buf, path, 1);
+ puts(buf.buf);
+ strbuf_release(&buf);
+ }
+ free(cwd);
+}
+
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
{
int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
@@ -593,6 +663,9 @@ 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;
+ enum format_type format = FORMAT_DEFAULT;
if (argc > 1 && !strcmp("--parseopt", argv[1]))
return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -620,21 +693,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,53 +725,61 @@ 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);
+ print_path(git_path("%s", argv[i + 1]), prefix,
+ format,
+ DEFAULT_RELATIVE_IF_SHARED);
+ 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 (opt_with_value(arg, "--path-format", &arg)) {
+ if (!arg)
+ die(_("--path-format requires an argument"));
+ if (!strcmp(arg, "absolute")) {
+ format = FORMAT_CANONICAL;
+ } else if (!strcmp(arg, "relative")) {
+ format = FORMAT_RELATIVE;
+ } else {
+ die(_("unknown argument to --path-format: %s"), arg);
+ }
continue;
}
if (!strcmp(arg, "--default")) {
def = argv[++i];
if (!def)
- die("--default requires an argument");
+ die(_("--default requires an argument"));
continue;
}
if (!strcmp(arg, "--prefix")) {
prefix = argv[++i];
if (!prefix)
- die("--prefix requires an argument");
+ die(_("--prefix requires an argument"));
startup_info->prefix = prefix;
output_prefix = 1;
continue;
@@ -730,8 +819,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")) {
@@ -759,7 +848,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
else if (!strcmp(arg, "loose"))
abbrev_ref_strict = 0;
else
- die("unknown mode for --abbrev-ref: %s",
+ die(_("unknown mode for --abbrev-ref: %s"),
arg);
}
continue;
@@ -801,13 +890,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--show-toplevel")) {
const char *work_tree = get_git_work_tree();
if (work_tree)
- puts(work_tree);
+ print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);
+ 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))
+ print_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);
+ strbuf_release(&superproject);
continue;
}
if (!strcmp(arg, "--show-prefix")) {
@@ -841,33 +933,38 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
char *cwd;
int len;
+ enum format_type wanted = format;
if (arg[2] == 'g') { /* --git-dir */
if (gitdir) {
- puts(gitdir);
+ print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);
continue;
}
if (!prefix) {
- puts(".git");
+ print_path(".git", prefix, format, DEFAULT_UNMODIFIED);
continue;
}
} else { /* --absolute-git-dir */
+ wanted = FORMAT_CANONICAL;
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;
}
}
cwd = xgetcwd();
len = strlen(cwd);
- printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s%s.git", cwd, len && cwd[len-1] != '/' ? "/" : "");
free(cwd);
+ print_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);
continue;
}
if (!strcmp(arg, "--git-common-dir")) {
- strbuf_reset(&buf);
- puts(relative_path(get_git_common_dir(),
- prefix, &buf));
+ print_path(get_git_common_dir(), prefix, format, DEFAULT_RELATIVE_IF_SHARED);
continue;
}
if (!strcmp(arg, "--is-inside-git-dir")) {
@@ -897,8 +994,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (the_index.split_index) {
const struct object_id *oid = &the_index.split_index->base_oid;
const char *path = git_path("sharedindex.%s", oid_to_hex(oid));
- strbuf_reset(&buf);
- puts(relative_path(path, prefix, &buf));
+ print_path(path, prefix, format, DEFAULT_RELATIVE);
}
continue;
}
@@ -918,6 +1014,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;
@@ -945,7 +1058,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (verify)
die_no_single_rev(quiet);
if (has_dashdash)
- die("bad revision '%s'", arg);
+ die(_("bad revision '%s'"), arg);
as_is = 1;
if (!show_file(arg, output_prefix))
continue;
diff --git a/builtin/revert.c b/builtin/revert.c
index d4dcedbdc6..237f2f18d4 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,12 +172,17 @@ 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,
"--no-commit", opts->no_commit,
"-x", opts->record_origin,
- "--edit", opts->edit,
+ "--edit", opts->edit > 0,
NULL);
if (cmd) {
@@ -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);
}
@@ -218,8 +230,6 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
- if (isatty(0))
- opts.edit = 1;
opts.action = REPLAY_REVERT;
sequencer_init_config(&opts);
res = run_sequencer(argc, argv, &opts);
diff --git a/builtin/rm.c b/builtin/rm.c
index bf4a443e13..8a24c715e0 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -5,6 +5,7 @@
*/
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
+#include "advice.h"
#include "config.h"
#include "lockfile.h"
#include "dir.h"
@@ -61,7 +62,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 +84,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 +180,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 +236,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,13 +247,15 @@ 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(),
};
int cmd_rm(int argc, const char **argv, const char *prefix)
{
struct lock_file lock_file = LOCK_INIT;
- int i;
+ int i, ret = 0;
struct pathspec pathspec;
char *seen;
@@ -259,8 +263,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,15 +290,16 @@ 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);
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
+ if (ce_skip_worktree(ce))
+ continue;
if (!ce_path_match(&the_index, ce, &pathspec, seen))
continue;
ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
@@ -292,28 +313,40 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (pathspec.nr) {
const char *original;
int seen_any = 0;
+ char *skip_worktree_seen = NULL;
+ struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
+
for (i = 0; i < pathspec.nr; i++) {
original = pathspec.items[i].original;
- if (!seen[i]) {
- if (!ignore_unmatch) {
- die(_("pathspec '%s' did not match any files"),
- original);
- }
- }
- else {
+ if (seen[i])
seen_any = 1;
- }
+ else if (ignore_unmatch)
+ continue;
+ else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
+ string_list_append(&only_match_skip_worktree, original);
+ else
+ die(_("pathspec '%s' did not match any files"), original);
+
if (!recursive && seen[i] == MATCHED_RECURSIVELY)
die(_("not removing '%s' recursively without -r"),
*original ? original : ".");
}
+ if (only_match_skip_worktree.nr) {
+ advise_on_updating_sparse_paths(&only_match_skip_worktree);
+ ret = 1;
+ }
+ free(skip_worktree_seen);
+ string_list_clear(&only_match_skip_worktree, 0);
+
if (!seen_any)
- exit(0);
+ exit(ret);
}
+ clear_pathspec(&pathspec);
+ free(seen);
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)
@@ -389,5 +422,5 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
COMMIT_LOCK | SKIP_IF_UNCHANGED))
die(_("Unable to write new index file"));
- return 0;
+ return ret;
}
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 098ebf22d0..729dea1d25 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()
};
@@ -205,6 +230,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.atomic = atomic;
args.stateless_rpc = stateless_rpc;
args.push_options = push_options.nr ? &push_options : NULL;
+ args.url = dest;
if (from_stdin) {
if (args.stateless_rpc) {
@@ -282,6 +308,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..3e7ab1ca82 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,18 +51,17 @@ 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);
else {
- const char *dot3 = log->common_repo_prefix;
- char *buffer, *p;
+ char *buffer;
struct strbuf subject = STRBUF_INIT;
const char *eol;
@@ -80,25 +81,14 @@ static void insert_one_record(struct shortlog *log,
format_subject(&subject, oneline, " ");
buffer = strbuf_detach(&subject, NULL);
- if (dot3) {
- int dot3len = strlen(dot3);
- if (dot3len > 5) {
- while ((p = strstr(buffer, dot3)) != NULL) {
- int taillen = strlen(p) - dot3len;
- memcpy(p, "/.../", 5);
- memmove(p + 5, p + dot3len, taillen + 1);
- }
- }
- }
-
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct string_list));
string_list_append(item->util, buffer);
}
}
-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 +112,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 +147,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 +212,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,16 +304,40 @@ 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));
- read_mailmap(&log->mailmap, &log->common_repo_prefix);
+ read_mailmap(&log->mailmap);
log->list.strdup_strings = 1;
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 +347,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")),
+ 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>]]"),
- N_("Linewrap output"), PARSE_OPT_OPTARG,
- &parse_wrap_args },
+ N_("show the email address of each author")),
+ OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"),
+ N_("linewrap output"), PARSE_OPT_OPTARG,
+ &parse_wrap_args),
+ OPT_CALLBACK(0, "group", &log, N_("field"),
+ N_("group by field"), parse_group_option),
OPT_END(),
};
@@ -311,6 +401,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..d77ce7aeb3 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) {
@@ -940,9 +939,12 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
mark = '*';
else
mark = '+';
- printf("%s%c%s",
- get_color_code(i),
- mark, get_color_reset_code());
+ if (mark == ' ')
+ putchar(mark);
+ else
+ printf("%s%c%s",
+ get_color_code(i),
+ mark, get_color_reset_code());
}
putchar(' ');
}
diff --git a/builtin/show-index.c b/builtin/show-index.c
index a6e678809e..0e0b9fb95b 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,24 +56,26 @@ 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)
+ for (i = 0; i < nr; i++) {
+ if (fread(entries[i].oid.hash, hashsz, 1, stdin) != 1)
die("unable to read sha1 %u/%u", i, nr);
+ entries[i].oid.algo = hash_algo_by_ptr(the_hash_algo);
+ }
for (i = 0; i < nr; i++)
if (fread(&entries[i].crc, 4, 1, stdin) != 1)
die("unable to read crc %u/%u", i, nr);
@@ -77,7 +99,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..7f8a5332f8 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -40,7 +40,7 @@ static void show_one(const char *refname, const struct object_id *oid)
if (!deref_tags)
return;
- if (!peel_ref(refname, &peeled)) {
+ if (!peel_iterated_oid(oid, &peeled)) {
hex = find_unique_abbrev(&peeled, abbrev);
printf("%s %s^{}\n", hex, refname);
}
@@ -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..8ba9f13787
--- /dev/null
+++ b/builtin/sparse-checkout.c
@@ -0,0 +1,678 @@
+#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"
+#include "sparse-index.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 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, 0);
+ 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;
+
+ r->index->sparse_checkout_patterns = pl;
+
+ 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);
+
+ r->index->sparse_checkout_patterns = NULL;
+ 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);
+
+ if (mode == MODE_NO_PATTERNS)
+ set_sparse_index_config(the_repository, 0);
+
+ return 0;
+}
+
+static char const * const builtin_sparse_checkout_init_usage[] = {
+ N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"),
+ NULL
+};
+
+static struct sparse_checkout_init_opts {
+ int cone_mode;
+ int sparse_index;
+} 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_BOOL(0, "sparse-index", &init_opts.sparse_index,
+ N_("toggle the use of a sparse index")),
+ OPT_END(),
+ };
+
+ repo_read_index(the_repository);
+
+ init_opts.sparse_index = -1;
+
+ 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, 0);
+
+ if (init_opts.sparse_index >= 0) {
+ if (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)
+ die(_("failed to modify sparse-index config"));
+
+ /* force an index rewrite */
+ repo_read_index(the_repository);
+ the_repository->index->updated_workdir = 1;
+ }
+
+ core_apply_sparse_checkout = 1;
+
+ /* If we already have a sparse-checkout file, use it. */
+ if (res >= 0) {
+ free(sparse_filename);
+ 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);
+ pl.use_cone_patterns = init_opts.cone_mode;
+
+ 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, fspathhash(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, fspathhash(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, 0))
+ 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, 0))
+ 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 = xcalloc(1, 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);
+ free(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);
+
+ prepare_repo_settings(the_repository);
+ the_repository->settings.sparse_index = 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 fde6397caa..8f42360ca9 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -7,14 +7,16 @@
#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 "entry.h"
#include "rerere.h"
#include "revision.h"
#include "log-tree.h"
#include "diffcore.h"
#include "exec-cmd.h"
+#include "entry.h"
#define INCLUDE_ALL_FILES 2
@@ -24,9 +26,10 @@ static const char * const git_stash_usage[] = {
N_("git stash drop [-q|--quiet] [<stash>]"),
N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
N_("git stash branch <branchname> [<stash>]"),
- N_("git stash clear"),
+ "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>]"),
@@ -64,7 +67,7 @@ static const char * const git_stash_branch_usage[] = {
};
static const char * const git_stash_clear_usage[] = {
- N_("git stash clear"),
+ "git stash clear",
NULL
};
@@ -86,7 +89,7 @@ static const char * const git_stash_save_usage[] = {
NULL
};
-static const char *ref_stash = "refs/stash";
+static const char ref_stash[] = "refs/stash";
static struct strbuf stash_index_path = STRBUF_INIT;
/*
@@ -184,7 +187,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 */
@@ -221,7 +224,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix)
PARSE_OPT_STOP_AT_NON_OPTION);
if (argc)
- return error(_("git stash clear with parameters is "
+ return error(_("git stash clear with arguments is "
"unimplemented"));
return do_clear_stash();
@@ -276,8 +279,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 +295,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 +308,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);
}
@@ -324,35 +327,6 @@ static void add_diff_to_buf(struct diff_queue_struct *q,
}
}
-static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
- const char *c_tree_hex = oid_to_hex(c_tree);
-
- /*
- * diff-index is very similar to diff-tree above, and should be
- * 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);
- return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
-}
-
-static int update_index(struct strbuf *out)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
-
- /*
- * Update-index is very complicated and may need to have a public
- * function exposed in order to remove this forking.
- */
- cp.git_cmd = 1;
- argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
- return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
-}
-
static int restore_untracked(struct object_id *u_tree)
{
int res;
@@ -364,10 +338,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,15 +349,130 @@ 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);
return res;
}
+static void unstage_changes_unless_new(struct object_id *orig_tree)
+{
+ /*
+ * When we enter this function, there has been a clean merge of
+ * relevant trees, and the merge logic always stages whatever merges
+ * cleanly. We want to unstage those changes, unless it corresponds
+ * to a file that didn't exist as of orig_tree.
+ *
+ * However, if any SKIP_WORKTREE path is modified relative to
+ * orig_tree, then we want to clear the SKIP_WORKTREE bit and write
+ * it to the worktree before unstaging.
+ */
+
+ struct checkout state = CHECKOUT_INIT;
+ struct diff_options diff_opts;
+ struct lock_file lock = LOCK_INIT;
+ int i;
+
+ /* If any entries have skip_worktree set, we'll have to check 'em out */
+ state.force = 1;
+ state.quiet = 1;
+ state.refresh_cache = 1;
+ state.istate = &the_index;
+
+ /*
+ * Step 1: get a difference between orig_tree (which corresponding
+ * to the index before a merge was run) and the current index
+ * (reflecting the changes brought in by the merge).
+ */
+ diff_setup(&diff_opts);
+ diff_opts.flags.recursive = 1;
+ diff_opts.detect_rename = 0;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_setup_done(&diff_opts);
+
+ do_diff_cache(orig_tree, &diff_opts);
+ diffcore_std(&diff_opts);
+
+ /* Iterate over the paths that changed due to the merge... */
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p;
+ struct cache_entry *ce;
+ int pos;
+
+ /* Look up the path's position in the current index. */
+ p = diff_queued_diff.queue[i];
+ pos = index_name_pos(&the_index, p->two->path,
+ strlen(p->two->path));
+
+ /*
+ * Step 2: Place changes in the working tree
+ *
+ * Stash is about restoring changes *to the working tree*.
+ * So if the merge successfully got a new version of some
+ * path, but left it out of the working tree, then clear the
+ * SKIP_WORKTREE bit and write it to the working tree.
+ */
+ if (pos >= 0 && ce_skip_worktree(active_cache[pos])) {
+ struct stat st;
+
+ ce = active_cache[pos];
+ if (!lstat(ce->name, &st)) {
+ /* Conflicting path present; relocate it */
+ struct strbuf new_path = STRBUF_INIT;
+ int fd;
+
+ strbuf_addf(&new_path,
+ "%s.stash.XXXXXX", ce->name);
+ fd = xmkstemp(new_path.buf);
+ close(fd);
+ printf(_("WARNING: Untracked file in way of "
+ "tracked file! Renaming\n "
+ " %s -> %s\n"
+ " to make room.\n"),
+ ce->name, new_path.buf);
+ if (rename(ce->name, new_path.buf))
+ die("Failed to move %s to %s\n",
+ ce->name, new_path.buf);
+ strbuf_release(&new_path);
+ }
+ checkout_entry(ce, &state, NULL, NULL);
+ ce->ce_flags &= ~CE_SKIP_WORKTREE;
+ }
+
+ /*
+ * Step 3: "unstage" changes, as long as they are still tracked
+ */
+ if (p->one->oid_valid) {
+ /*
+ * Path existed in orig_tree; restore index entry
+ * from that tree in order to "unstage" the changes.
+ */
+ int option = ADD_CACHE_OK_TO_REPLACE;
+ if (pos < 0)
+ option = ADD_CACHE_OK_TO_ADD;
+
+ ce = make_cache_entry(&the_index,
+ p->one->mode,
+ &p->one->oid,
+ p->one->path,
+ 0, 0);
+ add_index_entry(&the_index, ce, option);
+ }
+ }
+ diff_flush(&diff_opts);
+
+ /*
+ * Step 4: write the new index to disk
+ */
+ repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);
+ if (write_locked_index(&the_index, &lock,
+ COMMIT_LOCK | SKIP_IF_UNCHANGED))
+ die(_("Unable to write index."));
+}
+
static int do_apply_stash(const char *prefix, struct stash_info *info,
int index, int quiet)
{
@@ -396,7 +485,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))
@@ -418,7 +507,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
ret = apply_cached(&out);
strbuf_release(&out);
if (ret)
- return error(_("conflicts in index."
+ return error(_("conflicts in index. "
"Try without --index."));
discard_cache();
@@ -427,6 +516,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();
}
}
@@ -464,30 +555,10 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
if (reset_tree(&index_tree, 0, 0))
return -1;
} else {
- struct strbuf out = STRBUF_INIT;
-
- if (get_newly_staged(&out, &c_tree)) {
- strbuf_release(&out);
- return -1;
- }
-
- if (reset_tree(&c_tree, 0, 1)) {
- strbuf_release(&out);
- return -1;
- }
-
- ret = update_index(&out);
- strbuf_release(&out);
- if (ret)
- return -1;
-
- discard_cache();
+ unstage_changes_unless_new(&c_tree);
}
- if (quiet) {
- if (refresh_cache(REFRESH_QUIET))
- warning("could not refresh index");
- } else {
+ if (!quiet) {
struct child_process cp = CHILD_PROCESS_INIT;
/*
@@ -497,7 +568,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 +603,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 +626,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 +639,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 +731,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 +760,18 @@ 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", 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 show_include_untracked;
+static int use_legacy_stash;
static int git_stash_config(const char *var, const char *value, void *cb)
{
@@ -707,7 +783,42 @@ 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.showincludeuntracked")) {
+ show_include_untracked = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "stash.usebuiltin")) {
+ use_legacy_stash = !git_config_bool(var, value);
+ return 0;
+ }
+ return git_diff_basic_config(var, value, cb);
+}
+
+static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)
+{
+ const struct object_id *oid[] = { &info->w_commit, &info->u_tree };
+ struct tree *tree[ARRAY_SIZE(oid)];
+ struct tree_desc tree_desc[ARRAY_SIZE(oid)];
+ struct unpack_trees_options unpack_tree_opt = { 0 };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(oid); i++) {
+ tree[i] = parse_tree_indirect(oid[i]);
+ if (parse_tree(tree[i]) < 0)
+ die(_("failed to parse tree"));
+ init_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size);
+ }
+
+ unpack_tree_opt.head_idx = -1;
+ unpack_tree_opt.src_index = &the_index;
+ unpack_tree_opt.dst_index = &the_index;
+ unpack_tree_opt.merge = 1;
+ unpack_tree_opt.fn = stash_worktree_untracked_merge;
+
+ if (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt))
+ die(_("failed to unpack trees"));
+
+ do_diff_cache(&info->b_commit, diff_opt);
}
static int show_stash(int argc, const char **argv, const char *prefix)
@@ -716,9 +827,20 @@ 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;
+ enum {
+ UNTRACKED_NONE,
+ UNTRACKED_INCLUDE,
+ UNTRACKED_ONLY
+ } show_untracked = show_include_untracked ? UNTRACKED_INCLUDE : UNTRACKED_NONE;
struct option options[] = {
+ OPT_SET_INT('u', "include-untracked", &show_untracked,
+ N_("include untracked files in the stash"),
+ UNTRACKED_INCLUDE),
+ OPT_SET_INT_F(0, "only-untracked", &show_untracked,
+ N_("only show untracked files in the stash"),
+ UNTRACKED_ONLY, PARSE_OPT_NONEG),
OPT_END()
};
@@ -726,16 +848,20 @@ 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]);
+ argc = parse_options(argc, argv, prefix, options, git_stash_show_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_DASHDASH);
+
+ 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 +869,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 +882,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);
@@ -769,7 +894,21 @@ static int show_stash(int argc, const char **argv, const char *prefix)
rev.diffopt.flags.recursive = 1;
setup_diff_pager(&rev.diffopt);
- diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ switch (show_untracked) {
+ case UNTRACKED_NONE:
+ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ break;
+ case UNTRACKED_ONLY:
+ if (info.has_u)
+ diff_root_tree_oid(&info.u_tree, "", &rev.diffopt);
+ break;
+ case UNTRACKED_INCLUDE:
+ if (info.has_u)
+ diff_include_untracked(&info, &rev.diffopt);
+ else
+ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ break;
+ }
log_tree_diff_flush(&rev);
free_stash_info(&info);
@@ -832,12 +971,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 +990,22 @@ 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;
+ struct dir_struct dir = DIR_INIT;
- memset(&dir, 0, sizeof(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 +1085,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 +1121,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 +1159,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 +1213,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 +1261,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)
@@ -1277,6 +1412,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
int i;
char *ps_matched = xcalloc(ps->nr, 1);
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (i = 0; i < active_nr; i++)
ce_path_match(&the_index, active_cache[i], ps,
ps_matched);
@@ -1290,7 +1427,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 +1469,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 +1486,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 +1499,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 +1509,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 +1519,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 +1528,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 +1547,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 +1561,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 +1576,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 +1600,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 +1683,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???");
- }
-
- prefix = setup_git_directory();
- trace_repo_setup(prefix);
- setup_work_tree();
+ git_config(git_stash_config, NULL);
- 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 +1708,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 +1728,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..ef2776a9e4 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -19,7 +19,7 @@
#include "diffcore.h"
#include "diff.h"
#include "object-store.h"
-#include "dir.h"
+#include "advice.h"
#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
@@ -187,11 +187,13 @@ static char *relative_url(const char *remote_url,
out = xstrdup(sb.buf + 2);
else
out = xstrdup(sb.buf);
- strbuf_reset(&sb);
- if (!up_path || !is_relative)
+ if (!up_path || !is_relative) {
+ strbuf_release(&sb);
return out;
+ }
+ strbuf_reset(&sb);
strbuf_addf(&sb, "%s%s", up_path, out);
free(out);
return strbuf_detach(&sb, NULL);
@@ -293,9 +295,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 +427,8 @@ 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 +446,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;
@@ -465,7 +468,7 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
displaypath = get_submodule_displaypath(path, info->prefix);
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
die(_("No url found for submodule path '%s' in .gitmodules"),
@@ -494,12 +497,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 +511,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 +536,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,14 +559,14 @@ 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;
struct option module_foreach_options[] = {
- OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")),
+ OPT__QUIET(&info.quiet, N_("suppress output of entering each submodule command")),
OPT_BOOL(0, "recursive", &info.recursive,
- N_("Recurse into nested submodules")),
+ N_("recurse into nested submodules")),
OPT_END()
};
@@ -611,7 +614,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,
@@ -623,7 +625,7 @@ static void init_submodule(const char *path, const char *prefix,
displaypath = get_submodule_displaypath(path, prefix);
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
die(_("No url found for submodule path '%s' in .gitmodules"),
@@ -706,7 +708,7 @@ static int module_init(int argc, const char **argv, const char *prefix)
int quiet = 0;
struct option module_init_options[] = {
- OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
+ OPT__QUIET(&quiet, N_("suppress output for initializing a submodule")),
OPT_END()
};
@@ -741,7 +743,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,36 +779,47 @@ 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))
+ if (!submodule_from_path(the_repository, null_oid(), path))
die(_("no submodule mapping found in .gitmodules for path '%s'"),
path);
displaypath = get_submodule_displaypath(path, prefix);
if ((CE_STAGEMASK & ce_flags) >> CE_STAGESHIFT) {
- print_status(flags, 'U', path, &null_oid, displaypath);
+ print_status(flags, 'U', path, null_oid(), displaypath);
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 +849,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);
}
@@ -873,8 +885,8 @@ static int module_status(int argc, const char **argv, const char *prefix)
int quiet = 0;
struct option module_status_options[] = {
- OPT__QUIET(&quiet, N_("Suppress submodule status output")),
- OPT_BIT(0, "cached", &info.flags, N_("Use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED),
+ OPT__QUIET(&quiet, N_("suppress submodule status output")),
+ OPT_BIT(0, "cached", &info.flags, N_("use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED),
OPT_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE),
OPT_END()
};
@@ -906,7 +918,7 @@ static int module_name(int argc, const char **argv, const char *prefix)
if (argc != 2)
usage(_("git submodule--helper name <path>"));
- sub = submodule_from_path(the_repository, &null_oid, argv[1]);
+ sub = submodule_from_path(the_repository, null_oid(), argv[1]);
if (!sub)
die(_("no submodule mapping found in .gitmodules for path '%s'"),
@@ -917,11 +929,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_prefix(diff_args.nr, diff_args.v, NULL);
+ 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_("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,
@@ -937,7 +1375,7 @@ static void sync_submodule(const char *path, const char *prefix,
if (!is_submodule_active(the_repository, path))
return;
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (sub && sub->url) {
if (starts_with_dot_dot_slash(sub->url) ||
@@ -983,8 +1421,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 +1447,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'"),
@@ -1046,9 +1484,9 @@ static int module_sync(int argc, const char **argv, const char *prefix)
int recursive = 0;
struct option module_sync_options[] = {
- OPT__QUIET(&quiet, N_("Suppress output of synchronizing submodule url")),
+ OPT__QUIET(&quiet, N_("suppress output of synchronizing submodule url")),
OPT_BOOL(0, "recursive", &recursive,
- N_("Recurse into nested submodules")),
+ N_("recurse into nested submodules")),
OPT_END()
};
@@ -1089,7 +1527,7 @@ static void deinit_submodule(const char *path, const char *prefix,
struct strbuf sb_config = STRBUF_INIT;
char *sub_git_dir = xstrfmt("%s/.git", path);
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub || !sub->name)
goto cleanup;
@@ -1115,8 +1553,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 +1582,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) {
@@ -1184,9 +1622,9 @@ static int module_deinit(int argc, const char **argv, const char *prefix)
int all = 0;
struct option module_deinit_options[] = {
- OPT__QUIET(&quiet, N_("Suppress submodule status output")),
- OPT__FORCE(&force, N_("Remove submodule working trees even if they contain local changes"), 0),
- OPT_BOOL(0, "all", &all, N_("Unregister all submodules")),
+ OPT__QUIET(&quiet, N_("suppress submodule status output")),
+ OPT__FORCE(&force, N_("remove submodule working trees even if they contain local changes"), 0),
+ OPT_BOOL(0, "all", &all, N_("unregister all submodules")),
OPT_END()
};
@@ -1221,41 +1659,20 @@ static int module_deinit(int argc, const char **argv, const char *prefix)
return 0;
}
-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)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
-
- argv_array_push(&cp.args, "clone");
- argv_array_push(&cp.args, "--no-checkout");
- if (quiet)
- argv_array_push(&cp.args, "--quiet");
- if (progress)
- argv_array_push(&cp.args, "--progress");
- if (depth && *depth)
- argv_array_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);
- }
- if (dissociate)
- argv_array_push(&cp.args, "--dissociate");
- if (gitdir && *gitdir)
- argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
-
- argv_array_push(&cp.args, "--");
- argv_array_push(&cp.args, url);
- argv_array_push(&cp.args, path);
-
- cp.git_cmd = 1;
- prepare_submodule_repo_env(&cp.env_array);
- cp.no_stdin = 1;
-
- return run_command(&cp);
-}
+struct module_clone_data {
+ const char *prefix;
+ const char *path;
+ const char *name;
+ const char *url;
+ const char *depth;
+ struct string_list reference;
+ unsigned int quiet: 1;
+ unsigned int progress: 1;
+ unsigned int dissociate: 1;
+ unsigned int require_init: 1;
+ int single_branch;
+};
+#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 }
struct submodule_alternate_setup {
const char *submodule_name;
@@ -1269,6 +1686,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 +1724,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:
@@ -1352,36 +1778,128 @@ static void prepare_possible_alternates(const char *sm_name,
free(error_strategy);
}
-static int module_clone(int argc, const char **argv, const char *prefix)
+static int clone_submodule(struct module_clone_data *clone_data)
{
- const char *name = NULL, *url = NULL, *depth = NULL;
- int quiet = 0;
- int progress = 0;
- char *p, *path = NULL, *sm_gitdir;
- struct strbuf sb = STRBUF_INIT;
- struct string_list reference = STRING_LIST_INIT_NODUP;
- int dissociate = 0, require_init = 0;
+ char *p, *sm_gitdir;
char *sm_alternate = NULL, *error_strategy = NULL;
+ struct strbuf sb = STRBUF_INIT;
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), clone_data->name);
+ sm_gitdir = absolute_pathdup(sb.buf);
+ strbuf_reset(&sb);
+
+ if (!is_absolute_path(clone_data->path)) {
+ strbuf_addf(&sb, "%s/%s", get_git_work_tree(), clone_data->path);
+ clone_data->path = strbuf_detach(&sb, NULL);
+ } else {
+ clone_data->path = xstrdup(clone_data->path);
+ }
+
+ if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0)
+ die(_("refusing to create/use '%s' in another submodule's "
+ "git dir"), sm_gitdir);
+
+ if (!file_exists(sm_gitdir)) {
+ if (safe_create_leading_directories_const(sm_gitdir) < 0)
+ die(_("could not create directory '%s'"), sm_gitdir);
+
+ prepare_possible_alternates(clone_data->name, &clone_data->reference);
+
+ strvec_push(&cp.args, "clone");
+ strvec_push(&cp.args, "--no-checkout");
+ if (clone_data->quiet)
+ strvec_push(&cp.args, "--quiet");
+ if (clone_data->progress)
+ strvec_push(&cp.args, "--progress");
+ if (clone_data->depth && *(clone_data->depth))
+ strvec_pushl(&cp.args, "--depth", clone_data->depth, NULL);
+ if (clone_data->reference.nr) {
+ struct string_list_item *item;
+ for_each_string_list_item(item, &clone_data->reference)
+ strvec_pushl(&cp.args, "--reference",
+ item->string, NULL);
+ }
+ if (clone_data->dissociate)
+ strvec_push(&cp.args, "--dissociate");
+ if (sm_gitdir && *sm_gitdir)
+ strvec_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL);
+ if (clone_data->single_branch >= 0)
+ strvec_push(&cp.args, clone_data->single_branch ?
+ "--single-branch" :
+ "--no-single-branch");
+
+ strvec_push(&cp.args, "--");
+ strvec_push(&cp.args, clone_data->url);
+ strvec_push(&cp.args, clone_data->path);
+
+ cp.git_cmd = 1;
+ prepare_submodule_repo_env(&cp.env_array);
+ cp.no_stdin = 1;
+
+ if(run_command(&cp))
+ die(_("clone of '%s' into submodule path '%s' failed"),
+ clone_data->url, clone_data->path);
+ } else {
+ if (clone_data->require_init && !access(clone_data->path, X_OK) &&
+ !is_empty_dir(clone_data->path))
+ die(_("directory not empty: '%s'"), clone_data->path);
+ if (safe_create_leading_directories_const(clone_data->path) < 0)
+ die(_("could not create directory '%s'"), clone_data->path);
+ strbuf_addf(&sb, "%s/index", sm_gitdir);
+ unlink_or_warn(sb.buf);
+ strbuf_reset(&sb);
+ }
+
+ connect_work_tree_and_git_dir(clone_data->path, sm_gitdir, 0);
+
+ p = git_pathdup_submodule(clone_data->path, "config");
+ if (!p)
+ die(_("could not get submodule directory for '%s'"), clone_data->path);
+
+ /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
+ git_config_get_string("submodule.alternateLocation", &sm_alternate);
+ if (sm_alternate)
+ git_config_set_in_file(p, "submodule.alternateLocation",
+ sm_alternate);
+ git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
+ if (error_strategy)
+ git_config_set_in_file(p, "submodule.alternateErrorStrategy",
+ error_strategy);
+
+ free(sm_alternate);
+ free(error_strategy);
+
+ strbuf_release(&sb);
+ free(sm_gitdir);
+ free(p);
+ return 0;
+}
+
+static int module_clone(int argc, const char **argv, const char *prefix)
+{
+ int dissociate = 0, quiet = 0, progress = 0, require_init = 0;
+ struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
struct option module_clone_options[] = {
- OPT_STRING(0, "prefix", &prefix,
+ OPT_STRING(0, "prefix", &clone_data.prefix,
N_("path"),
N_("alternative anchor for relative paths")),
- OPT_STRING(0, "path", &path,
+ OPT_STRING(0, "path", &clone_data.path,
N_("path"),
N_("where the new submodule will be cloned to")),
- OPT_STRING(0, "name", &name,
+ OPT_STRING(0, "name", &clone_data.name,
N_("string"),
N_("name of the new submodule")),
- OPT_STRING(0, "url", &url,
+ OPT_STRING(0, "url", &clone_data.url,
N_("string"),
N_("url where to clone the submodule from")),
- OPT_STRING_LIST(0, "reference", &reference,
+ OPT_STRING_LIST(0, "reference", &clone_data.reference,
N_("repo"),
N_("reference repository")),
OPT_BOOL(0, "dissociate", &dissociate,
N_("use --reference only while cloning")),
- OPT_STRING(0, "depth", &depth,
+ OPT_STRING(0, "depth", &clone_data.depth,
N_("string"),
N_("depth for shallow clones")),
OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
@@ -1389,12 +1907,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", &clone_data.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
};
@@ -1402,67 +1923,16 @@ static int module_clone(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, module_clone_options,
git_submodule_helper_usage, 0);
- if (argc || !url || !path || !*path)
+ clone_data.dissociate = !!dissociate;
+ clone_data.quiet = !!quiet;
+ clone_data.progress = !!progress;
+ clone_data.require_init = !!require_init;
+
+ if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path))
usage_with_options(git_submodule_helper_usage,
module_clone_options);
- strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
- sm_gitdir = absolute_pathdup(sb.buf);
- strbuf_reset(&sb);
-
- if (!is_absolute_path(path)) {
- strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path);
- path = strbuf_detach(&sb, NULL);
- } else
- path = xstrdup(path);
-
- if (validate_submodule_git_dir(sm_gitdir, name) < 0)
- die(_("refusing to create/use '%s' in another submodule's "
- "git dir"), sm_gitdir);
-
- if (!file_exists(sm_gitdir)) {
- if (safe_create_leading_directories_const(sm_gitdir) < 0)
- die(_("could not create directory '%s'"), sm_gitdir);
-
- prepare_possible_alternates(name, &reference);
-
- if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate,
- quiet, progress))
- die(_("clone of '%s' into submodule path '%s' failed"),
- url, path);
- } else {
- if (require_init && !access(path, X_OK) && !is_empty_dir(path))
- die(_("directory not empty: '%s'"), path);
- if (safe_create_leading_directories_const(path) < 0)
- die(_("could not create directory '%s'"), path);
- strbuf_addf(&sb, "%s/index", sm_gitdir);
- unlink_or_warn(sb.buf);
- strbuf_reset(&sb);
- }
-
- connect_work_tree_and_git_dir(path, sm_gitdir, 0);
-
- p = git_pathdup_submodule(path, "config");
- if (!p)
- die(_("could not get submodule directory for '%s'"), path);
-
- /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
- git_config_get_string("submodule.alternateLocation", &sm_alternate);
- if (sm_alternate)
- git_config_set_in_file(p, "submodule.alternateLocation",
- sm_alternate);
- git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
- if (error_strategy)
- git_config_set_in_file(p, "submodule.alternateErrorStrategy",
- error_strategy);
-
- free(sm_alternate);
- free(error_strategy);
-
- strbuf_release(&sb);
- free(sm_gitdir);
- free(path);
- free(p);
+ clone_submodule(&clone_data);
return 0;
}
@@ -1472,7 +1942,7 @@ static void determine_submodule_update_strategy(struct repository *r,
const char *update,
struct submodule_update_strategy *out)
{
- const struct submodule *sub = submodule_from_path(r, &null_oid, path);
+ const struct submodule *sub = submodule_from_path(r, null_oid(), path);
char *key;
const char *val;
@@ -1482,7 +1952,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 +2021,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 +2036,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,
@@ -1619,7 +2094,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
goto cleanup;
}
- sub = submodule_from_path(the_repository, &null_oid, ce->name);
+ sub = submodule_from_path(the_repository, null_oid(), ce->name);
if (suc->recursive_prefix)
displaypath = relative_path(suc->recursive_prefix,
@@ -1633,7 +2108,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 +2131,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 +2158,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);
@@ -1875,7 +2354,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "dissociate", &suc.dissociate,
N_("use --reference only while cloning")),
OPT_STRING(0, "depth", &suc.depth, "<depth>",
- N_("Create a shallow clone truncated to the "
+ N_("create a shallow clone truncated to the "
"specified number of revisions")),
OPT_INTEGER('j', "jobs", &suc.max_jobs,
N_("parallel jobs")),
@@ -1886,11 +2365,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;
@@ -1931,17 +2412,17 @@ static const char *remote_submodule_branch(const char *path)
const char *branch = NULL;
char *key;
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
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 +2542,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)
@@ -2069,14 +2550,14 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix)
path = argv[1];
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
BUG("We could get the submodule handle before?");
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 +2605,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 +2687,255 @@ 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;
+}
+
+struct add_data {
+ const char *prefix;
+ const char *branch;
+ const char *reference_path;
+ const char *sm_path;
+ const char *sm_name;
+ const char *repo;
+ const char *realrepo;
+ int depth;
+ unsigned int force: 1;
+ unsigned int quiet: 1;
+ unsigned int progress: 1;
+ unsigned int dissociate: 1;
+};
+#define ADD_DATA_INIT { .depth = -1 }
+
+static void show_fetch_remotes(FILE *output, const char *git_dir_path)
+{
+ struct child_process cp_remote = CHILD_PROCESS_INIT;
+ struct strbuf sb_remote_out = STRBUF_INIT;
+
+ cp_remote.git_cmd = 1;
+ strvec_pushf(&cp_remote.env_array,
+ "GIT_DIR=%s", git_dir_path);
+ strvec_push(&cp_remote.env_array, "GIT_WORK_TREE=.");
+ strvec_pushl(&cp_remote.args, "remote", "-v", NULL);
+ if (!capture_command(&cp_remote, &sb_remote_out, 0)) {
+ char *next_line;
+ char *line = sb_remote_out.buf;
+ while ((next_line = strchr(line, '\n')) != NULL) {
+ size_t len = next_line - line;
+ if (strip_suffix_mem(line, &len, " (fetch)"))
+ fprintf(output, " %.*s\n", (int)len, line);
+ line = next_line + 1;
+ }
+ }
+
+ strbuf_release(&sb_remote_out);
+}
+
+static int add_submodule(const struct add_data *add_data)
+{
+ char *submod_gitdir_path;
+ struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
+
+ /* perhaps the path already exists and is already a git repo, else clone it */
+ if (is_directory(add_data->sm_path)) {
+ struct strbuf sm_path = STRBUF_INIT;
+ strbuf_addstr(&sm_path, add_data->sm_path);
+ submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
+ if (is_nonbare_repository_dir(&sm_path))
+ printf(_("Adding existing repo at '%s' to the index\n"),
+ add_data->sm_path);
+ else
+ die(_("'%s' already exists and is not a valid git repo"),
+ add_data->sm_path);
+ strbuf_release(&sm_path);
+ free(submod_gitdir_path);
+ } else {
+ struct child_process cp = CHILD_PROCESS_INIT;
+ submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
+
+ if (is_directory(submod_gitdir_path)) {
+ if (!add_data->force) {
+ fprintf(stderr, _("A git directory for '%s' is found "
+ "locally with remote(s):"),
+ add_data->sm_name);
+ show_fetch_remotes(stderr, submod_gitdir_path);
+ free(submod_gitdir_path);
+ die(_("If you want to reuse this local git "
+ "directory instead of cloning again from\n"
+ " %s\n"
+ "use the '--force' option. If the local git "
+ "directory is not the correct repo\n"
+ "or if you are unsure what this means, choose "
+ "another name with the '--name' option.\n"),
+ add_data->realrepo);
+ } else {
+ printf(_("Reactivating local git directory for "
+ "submodule '%s'\n"), add_data->sm_name);
+ }
+ }
+ free(submod_gitdir_path);
+
+ clone_data.prefix = add_data->prefix;
+ clone_data.path = add_data->sm_path;
+ clone_data.name = add_data->sm_name;
+ clone_data.url = add_data->realrepo;
+ clone_data.quiet = add_data->quiet;
+ clone_data.progress = add_data->progress;
+ if (add_data->reference_path)
+ string_list_append(&clone_data.reference,
+ xstrdup(add_data->reference_path));
+ clone_data.dissociate = add_data->dissociate;
+ if (add_data->depth >= 0)
+ clone_data.depth = xstrfmt("%d", add_data->depth);
+
+ if (clone_submodule(&clone_data))
+ return -1;
+
+ prepare_submodule_repo_env(&cp.env_array);
+ cp.git_cmd = 1;
+ cp.dir = add_data->sm_path;
+ strvec_pushl(&cp.args, "checkout", "-f", "-q", NULL);
+
+ if (add_data->branch) {
+ strvec_pushl(&cp.args, "-B", add_data->branch, NULL);
+ strvec_pushf(&cp.args, "origin/%s", add_data->branch);
+ }
+
+ if (run_command(&cp))
+ die(_("unable to checkout submodule '%s'"), add_data->sm_path);
+ }
+ return 0;
+}
+
+static int add_clone(int argc, const char **argv, const char *prefix)
+{
+ int force = 0, quiet = 0, dissociate = 0, progress = 0;
+ struct add_data add_data = ADD_DATA_INIT;
+
+ struct option options[] = {
+ OPT_STRING('b', "branch", &add_data.branch,
+ N_("branch"),
+ N_("branch of repository to checkout on cloning")),
+ OPT_STRING(0, "prefix", &prefix,
+ N_("path"),
+ N_("alternative anchor for relative paths")),
+ OPT_STRING(0, "path", &add_data.sm_path,
+ N_("path"),
+ N_("where the new submodule will be cloned to")),
+ OPT_STRING(0, "name", &add_data.sm_name,
+ N_("string"),
+ N_("name of the new submodule")),
+ OPT_STRING(0, "url", &add_data.realrepo,
+ N_("string"),
+ N_("url where to clone the submodule from")),
+ OPT_STRING(0, "reference", &add_data.reference_path,
+ N_("repo"),
+ N_("reference repository")),
+ OPT_BOOL(0, "dissociate", &dissociate,
+ N_("use --reference only while cloning")),
+ OPT_INTEGER(0, "depth", &add_data.depth,
+ N_("depth for shallow clones")),
+ OPT_BOOL(0, "progress", &progress,
+ N_("force cloning progress")),
+ OPT__FORCE(&force, N_("allow adding an otherwise ignored submodule path"),
+ PARSE_OPT_NOCOMPLETE),
+ OPT__QUIET(&quiet, "suppress output for cloning a submodule"),
+ OPT_END()
+ };
+
+ const char *const usage[] = {
+ N_("git submodule--helper add-clone [<options>...] "
+ "--url <url> --path <path> --name <name>"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (argc != 0)
+ usage_with_options(usage, options);
+
+ add_data.prefix = prefix;
+ add_data.progress = !!progress;
+ add_data.dissociate = !!dissociate;
+ add_data.force = !!force;
+ add_data.quiet = !!quiet;
+
+ if (add_submodule(&add_data))
+ return 1;
+
+ return 0;
+}
+
#define SUPPORT_SUPER_PREFIX (1<<0)
struct cmd_struct {
@@ -2219,6 +2948,7 @@ static struct cmd_struct commands[] = {
{"list", module_list, 0},
{"name", module_name, 0},
{"clone", module_clone, 0},
+ {"add-clone", add_clone, 0},
{"update-module-mode", module_update_module_mode, 0},
{"update-clone", update_clone, 0},
{"ensure-core-worktree", ensure_core_worktree, 0},
@@ -2231,12 +2961,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/symbolic-ref.c b/builtin/symbolic-ref.c
index 80237f0df1..e547a08d6c 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -24,9 +24,11 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)
return 1;
}
if (print) {
+ char *to_free = NULL;
if (shorten)
- refname = shorten_unambiguous_ref(refname, 0);
+ refname = to_free = shorten_unambiguous_ref(refname, 0);
puts(refname);
+ free(to_free);
}
return 0;
}
diff --git a/builtin/tag.c b/builtin/tag.c
index ef37dccf86..452558ec95 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,18 +26,21 @@ 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)
{
struct ref_array array;
+ struct strbuf output = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
char *to_free = NULL;
int i;
@@ -62,8 +65,17 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
filter_refs(&array, filter, FILTER_REFS_TAGS);
ref_array_sort(sorting, &array);
- for (i = 0; i < array.nr; i++)
- show_ref_array_item(array.items[i], format);
+ for (i = 0; i < array.nr; i++) {
+ strbuf_reset(&output);
+ strbuf_reset(&err);
+ if (format_ref_array_item(array.items[i], format, &output, &err))
+ die("%s", err.buf);
+ fwrite(output.buf, 1, output.len, stdout);
+ putchar('\n');
+ }
+
+ strbuf_release(&err);
+ strbuf_release(&output);
ref_array_clear(&array);
free(to_free);
@@ -71,10 +83,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
}
typedef int (*each_tag_name_fn)(const char *name, const char *ref,
- const struct object_id *oid, const void *cb_data);
+ const struct object_id *oid, void *cb_data);
static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
- const void *cb_data)
+ void *cb_data)
{
const char **p;
struct strbuf ref = STRBUF_INIT;
@@ -96,21 +108,45 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
return had_error;
}
-static int delete_tag(const char *name, const char *ref,
- const struct object_id *oid, const void *cb_data)
+static int collect_tags(const char *name, const char *ref,
+ const struct object_id *oid, void *cb_data)
{
- if (delete_ref(NULL, ref, oid, 0))
- return 1;
- printf(_("Deleted tag '%s' (was %s)\n"), name,
- find_unique_abbrev(oid, DEFAULT_ABBREV));
+ struct string_list *ref_list = cb_data;
+
+ string_list_append(ref_list, ref);
+ ref_list->items[ref_list->nr - 1].util = oiddup(oid);
return 0;
}
+static int delete_tags(const char **argv)
+{
+ int result;
+ struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+
+ result = for_each_tag_name(argv, collect_tags, (void *)&refs_to_delete);
+ if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
+ result = 1;
+
+ for_each_string_list_item(item, &refs_to_delete) {
+ const char *name = item->string;
+ struct object_id *oid = item->util;
+ if (!ref_exists(name))
+ printf(_("Deleted tag '%s' (was %s)\n"),
+ item->string + 10,
+ find_unique_abbrev(oid, DEFAULT_ABBREV));
+
+ free(oid);
+ }
+ string_list_clear(&refs_to_delete, 0);
+ return result;
+}
+
static int verify_tag(const char *name, const char *ref,
- const struct object_id *oid, const void *cb_data)
+ const struct object_id *oid, void *cb_data)
{
int flags;
- const struct ref_format *format = cb_data;
+ struct ref_format *format = cb_data;
flags = GPG_VERIFY_VERBOSE;
if (format->format)
@@ -144,6 +180,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);
@@ -168,11 +209,17 @@ static void write_tag_body(int fd, const struct object_id *oid)
{
unsigned long size;
enum object_type type;
- char *buf, *sp;
+ char *buf, *sp, *orig;
+ struct strbuf payload = STRBUF_INIT;
+ struct strbuf signature = STRBUF_INIT;
- buf = read_object_file(oid, &type, &size);
+ orig = buf = read_object_file(oid, &type, &size);
if (!buf)
return;
+ if (parse_signature(buf, size, &payload, &signature)) {
+ buf = payload.buf;
+ size = payload.len;
+ }
/* skip header */
sp = strstr(buf, "\n\n");
@@ -181,9 +228,11 @@ static void write_tag_body(int fd, const struct object_id *oid)
return;
}
sp += 2; /* skip the 2 LFs */
- write_or_die(fd, sp, parse_signature(sp, buf + size - sp));
+ write_or_die(fd, sp, buf + size - sp);
- free(buf);
+ free(orig);
+ strbuf_release(&payload);
+ strbuf_release(&signature);
}
static int build_tag_object(struct strbuf *buf, int sign, struct object_id *result)
@@ -225,8 +274,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 +453,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 +492,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 +528,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
}
if (!sorting)
sorting = ref_default_sorting();
- sorting->ignore_case = icase;
+ ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
filter.ignore_case = icase;
if (cmdmode == 'l') {
int ret;
@@ -498,10 +552,10 @@ 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);
+ return delete_tags(argv);
if (cmdmode == 'v') {
if (format.format && verify_ref_format(&format))
usage_with_options(git_tag_usage, options);
@@ -529,7 +583,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
object_ref = argc == 2 ? argv[1] : "HEAD";
if (argc > 2)
- die(_("too many params"));
+ die(_("too many arguments"));
if (get_oid(object_ref, &object))
die(_("Failed to resolve '%s' as a valid ref."), object_ref);
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 80478808b3..4a9466295b 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
@@ -45,7 +46,7 @@ static struct obj_buffer *lookup_object_buffer(struct object *base)
static void add_object_buffer(struct object *object, char *buffer, unsigned long size)
{
struct obj_buffer *obj;
- obj = xcalloc(1, sizeof(struct obj_buffer));
+ CALLOC_ARRAY(obj, 1);
obj->buffer = buffer;
obj->size = size;
if (add_decoration(&obj_decorate, object, obj))
@@ -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)
@@ -185,7 +187,8 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf)
* that have reachability requirements and calls this function.
* Verify its reachability and validity recursively and write it out.
*/
-static int check_object(struct object *obj, int type, void *data, struct fsck_options *options)
+static int check_object(struct object *obj, enum object_type type,
+ void *data, struct fsck_options *options)
{
struct obj_buffer *obj_buf;
@@ -263,7 +266,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 +336,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);
@@ -351,7 +355,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
struct object_id base_oid;
if (type == OBJ_REF_DELTA) {
- hashcpy(base_oid.hash, fill(the_hash_algo->rawsz));
+ oidread(&base_oid, fill(the_hash_algo->rawsz));
use(the_hash_algo->rawsz);
delta_data = get_data(delta_size);
if (dry_run || !delta_data) {
@@ -417,7 +421,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
* has not been resolved yet.
*/
oidclr(&obj_list[nr].oid);
- add_delta_to_list(nr, &null_oid, base_offset, delta_data, delta_size);
+ add_delta_to_list(nr, null_oid(), base_offset,
+ delta_data, delta_size);
return;
}
}
@@ -484,7 +489,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);
@@ -498,7 +502,7 @@ static void unpack_all(void)
if (!quiet)
progress = start_progress(_("Unpacking objects"), nr_objects);
- obj_list = xcalloc(nr_objects, sizeof(*obj_list));
+ CALLOC_ARRAY(obj_list, nr_objects);
for (i = 0; i < nr_objects; i++) {
unpack_one(i);
display_progress(progress, i + 1);
@@ -573,7 +577,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
the_hash_algo->init_fn(&ctx);
unpack_all();
the_hash_algo->update_fn(&ctx, buffer, offset);
- the_hash_algo->final_fn(oid.hash, &ctx);
+ the_hash_algo->final_oid_fn(&oid, &ctx);
if (strict) {
write_rest();
if (fsck_finish(&fsck_options))
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 3f8cc6ccb4..f1f16f2de5 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;
@@ -743,6 +745,8 @@ static int do_reupdate(int ac, const char **av,
*/
has_head = 0;
redo:
+ /* TODO: audit for interaction with sparse-index. */
+ ensure_full_index(&the_index);
for (pos = 0; pos < active_nr; pos++) {
const struct cache_entry *ce = active_cache[pos];
struct cache_entry *old = NULL;
@@ -966,6 +970,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 +987,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 +1002,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 +1018,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 +1047,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 +1187,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 +1204,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..6029a80544 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,192 @@ 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 input = STRBUF_INIT;
- const char *next;
+ 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, 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:
+ if (state == UPDATE_REFS_STARTED && cmd->state == UPDATE_REFS_STARTED)
+ die("cannot restart ongoing transaction");
+ /* 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:
+ if (cmd->state != UPDATE_REFS_STARTED)
+ die("transaction is closed");
+
+ /*
+ * Open a new transaction if we're currently closed and
+ * get a "start".
+ */
+ state = cmd->state;
+ transaction = ref_transaction_begin(&err);
+ if (!transaction)
+ die("%s", err.buf);
+
+ 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 +520,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..0d0a80da61 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -4,15 +4,15 @@
#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"
+#include "quote.h"
static const char * const worktree_usage[] = {
N_("git worktree add [<options>] <path> [<commit-ish>]"),
@@ -30,7 +30,7 @@ struct add_opts {
int detach;
int quiet;
int checkout;
- int keep_locked;
+ const char *keep_locked;
};
static int show_only;
@@ -68,92 +68,73 @@ 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)
+static void prune_worktree(const char *id, const char *reason)
{
- struct stat st;
- char *path;
- int fd;
- size_t len;
- ssize_t read_result;
+ if (show_only || verbose)
+ printf_ln(_("Removing %s/%s: %s"), "worktrees", id, reason);
+ if (!show_only)
+ delete_git_dir(id);
+}
- if (!is_directory(git_path("worktrees/%s", id))) {
- strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
- return 1;
- }
- if (file_exists(git_path("worktrees/%s/locked", id)))
- return 0;
- if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
- strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
- return 1;
- }
- fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
- if (fd < 0) {
- strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
- id, strerror(errno));
- return 1;
- }
- len = xsize_t(st.st_size);
- path = xmallocz(len);
-
- 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));
- close(fd);
- free(path);
- return 1;
- }
- close(fd);
+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 (read_result != len) {
- strbuf_addf(reason,
- _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
- id, (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);
- free(path);
+ 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");
}
- 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);
- return 1;
- } else {
- return 0;
- }
- }
- free(path);
- return 0;
}
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) {
- if (is_dot_or_dotdot(d->d_name))
- continue;
+ while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
+ char *path;
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, expire))
+ 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 +206,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 +268,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'"),
@@ -318,10 +302,10 @@ static int add_worktree(const char *path, const char *refname,
* after the preparation is over.
*/
strbuf_addf(&sb, "%s/locked", sb_repo.buf);
- if (!opts->keep_locked)
- write_file(sb.buf, "initializing");
+ if (opts->keep_locked)
+ write_file(sb.buf, "%s", opts->keep_locked);
else
- write_file(sb.buf, "added with --lock");
+ write_file(sb.buf, _("initializing"));
strbuf_addf(&sb_git, "%s/.git", path);
if (safe_create_leading_directories_const(sb_git.buf))
@@ -331,9 +315,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 +329,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 +391,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;
}
@@ -456,16 +444,18 @@ static void print_preparing_worktree_line(int detach,
static const char *dwim_branch(const char *path, const char **new_branch)
{
int n;
+ int branch_exists;
const char *s = worktree_basename(path, &n);
const char *branchname = xstrndup(s, n);
struct strbuf ref = STRBUF_INIT;
UNLEAK(branchname);
- if (!strbuf_check_branch_ref(&ref, branchname) &&
- ref_exists(ref.buf)) {
- strbuf_release(&ref);
+
+ branch_exists = !strbuf_check_branch_ref(&ref, branchname) &&
+ ref_exists(ref.buf);
+ strbuf_release(&ref);
+ if (branch_exists)
return branchname;
- }
*new_branch = branchname;
if (guess_remote) {
@@ -485,6 +475,8 @@ static int add(int ac, const char **av, const char *prefix)
const char *branch;
const char *new_branch = NULL;
const char *opt_track = NULL;
+ const char *lock_reason = NULL;
+ int keep_locked = 0;
struct option options[] = {
OPT__FORCE(&opts.force,
N_("checkout <branch> even if already checked out in other worktree"),
@@ -493,9 +485,11 @@ 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_BOOL(0, "lock", &keep_locked, N_("keep the new working tree locked")),
+ OPT_STRING(0, "reason", &lock_reason, N_("string"),
+ N_("reason for locking")),
OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
OPT_PASSTHRU(0, "track", &opt_track, NULL,
N_("set up tracking mode (see git-branch(1))"),
@@ -510,6 +504,13 @@ static int add(int ac, const char **av, const char *prefix)
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
die(_("-b, -B, and --detach are mutually exclusive"));
+ if (lock_reason && !keep_locked)
+ die(_("--reason requires --lock"));
+ if (lock_reason)
+ opts.keep_locked = lock_reason;
+ else if (keep_locked)
+ opts.keep_locked = _("added with --lock");
+
if (ac < 1 || ac > 2)
usage_with_options(worktree_usage, options);
@@ -557,15 +558,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;
@@ -580,6 +581,8 @@ static int add(int ac, const char **av, const char *prefix)
static void show_worktree_porcelain(struct worktree *wt)
{
+ const char *reason;
+
printf("worktree %s\n", wt->path);
if (wt->is_bare)
printf("bare\n");
@@ -590,6 +593,20 @@ static void show_worktree_porcelain(struct worktree *wt)
else if (wt->head_ref)
printf("branch %s\n", wt->head_ref);
}
+
+ reason = worktree_lock_reason(wt);
+ if (reason && *reason) {
+ struct strbuf sb = STRBUF_INIT;
+ quote_c_style(reason, &sb, NULL, 0);
+ printf("locked %s\n", sb.buf);
+ strbuf_release(&sb);
+ } else if (reason)
+ printf("locked\n");
+
+ reason = worktree_prune_reason(wt, expire);
+ if (reason)
+ printf("prunable %s\n", reason);
+
printf("\n");
}
@@ -598,6 +615,7 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
struct strbuf sb = STRBUF_INIT;
int cur_path_len = strlen(wt->path);
int path_adj = cur_path_len - utf8_strwidth(wt->path);
+ const char *reason;
strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
if (wt->is_bare)
@@ -614,8 +632,20 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
} else
strbuf_addstr(&sb, "(error)");
}
- printf("%s\n", sb.buf);
+ reason = worktree_lock_reason(wt);
+ if (verbose && reason && *reason)
+ strbuf_addf(&sb, "\n\tlocked: %s", reason);
+ else if (reason)
+ strbuf_addstr(&sb, " locked");
+
+ reason = worktree_prune_reason(wt, expire);
+ if (verbose && reason)
+ strbuf_addf(&sb, "\n\tprunable: %s", reason);
+ else if (reason)
+ strbuf_addstr(&sb, " prunable");
+
+ printf("%s\n", sb.buf);
strbuf_release(&sb);
}
@@ -635,22 +665,48 @@ 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;
struct option options[] = {
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
+ OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),
+ OPT_EXPIRY_DATE(0, "expire", &expire,
+ N_("add 'prunable' annotation to worktrees older than <time>")),
OPT_END()
};
+ expire = TIME_MAX;
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
if (ac)
usage_with_options(worktree_usage, options);
+ else if (verbose && porcelain)
+ die(_("--verbose and --porcelain are mutually exclusive"));
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 +735,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 +768,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 +842,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 +857,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 +898,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 +908,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 +925,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 +966,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 +1002,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);
+ p = ac > 0 ? av : self;
+ for (; *p; p++)
+ repair_worktree_at_path(*p, report_repair, &rc);
+ repair_worktrees(report_repair, &rc);
+ return rc;
+}
+
int cmd_worktree(int ac, const char **av, const char *prefix)
{
struct option options[] = {
@@ -975,5 +1056,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;