Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.kernel.org/pub/scm/git/git.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/add.c170
-rw-r--r--builtin/apply.c71
-rw-r--r--builtin/archive.c4
-rw-r--r--builtin/bisect--helper.c8
-rw-r--r--builtin/blame.c320
-rw-r--r--builtin/branch.c68
-rw-r--r--builtin/cat-file.c265
-rw-r--r--builtin/check-attr.c26
-rw-r--r--builtin/check-ignore.c137
-rw-r--r--builtin/check-mailmap.c66
-rw-r--r--builtin/checkout-index.c10
-rw-r--r--builtin/checkout.c242
-rw-r--r--builtin/clean.c849
-rw-r--r--builtin/clone.c121
-rw-r--r--builtin/column.c2
-rw-r--r--builtin/commit-tree.c12
-rw-r--r--builtin/commit.c292
-rw-r--r--builtin/config.c199
-rw-r--r--builtin/describe.c126
-rw-r--r--builtin/diff-files.c2
-rw-r--r--builtin/diff-index.c2
-rw-r--r--builtin/diff.c146
-rw-r--r--builtin/fast-export.c127
-rw-r--r--builtin/fetch-pack.c54
-rw-r--r--builtin/fetch.c576
-rw-r--r--builtin/fmt-merge-msg.c10
-rw-r--r--builtin/for-each-ref.c151
-rw-r--r--builtin/fsck.c41
-rw-r--r--builtin/gc.c120
-rw-r--r--builtin/get-tar-commit-id.c40
-rw-r--r--builtin/grep.c74
-rw-r--r--builtin/hash-object.c8
-rw-r--r--builtin/help.c16
-rw-r--r--builtin/index-pack.c47
-rw-r--r--builtin/init-db.c11
-rw-r--r--builtin/log.c110
-rw-r--r--builtin/ls-files.c150
-rw-r--r--builtin/ls-remote.c4
-rw-r--r--builtin/ls-tree.c19
-rw-r--r--builtin/mailinfo.c16
-rw-r--r--builtin/merge-base.c177
-rw-r--r--builtin/merge-file.c2
-rw-r--r--builtin/merge-index.c4
-rw-r--r--builtin/merge-recursive.c4
-rw-r--r--builtin/merge-tree.c8
-rw-r--r--builtin/merge.c88
-rw-r--r--builtin/mv.c163
-rw-r--r--builtin/name-rev.c168
-rw-r--r--builtin/notes.c152
-rw-r--r--builtin/pack-objects.c458
-rw-r--r--builtin/pack-refs.c2
-rw-r--r--builtin/prune-packed.c53
-rw-r--r--builtin/prune.c52
-rw-r--r--builtin/push.c214
-rw-r--r--builtin/read-tree.c7
-rw-r--r--builtin/receive-pack.c329
-rw-r--r--builtin/reflog.c23
-rw-r--r--builtin/remote.c261
-rw-r--r--builtin/repack.c403
-rw-r--r--builtin/replace.c87
-rw-r--r--builtin/rerere.c8
-rw-r--r--builtin/reset.c99
-rw-r--r--builtin/rev-list.c44
-rw-r--r--builtin/rev-parse.c132
-rw-r--r--builtin/revert.c66
-rw-r--r--builtin/rm.c178
-rw-r--r--builtin/send-pack.c41
-rw-r--r--builtin/shortlog.c25
-rw-r--r--builtin/show-branch.c67
-rw-r--r--builtin/show-ref.c27
-rw-r--r--builtin/stripspace.c8
-rw-r--r--builtin/symbolic-ref.c4
-rw-r--r--builtin/tag.c35
-rw-r--r--builtin/tar-tree.c103
-rw-r--r--builtin/unpack-objects.c2
-rw-r--r--builtin/update-index.c62
-rw-r--r--builtin/update-ref.c256
-rw-r--r--builtin/upload-archive.c2
78 files changed, 5814 insertions, 2712 deletions
diff --git a/builtin/add.c b/builtin/add.c
index 61f3cb49ff..de79f35efc 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -105,14 +105,16 @@ static void update_callback(struct diff_queue_struct *q,
}
}
-static void update_files_in_cache(const char *prefix, const char **pathspec,
+static void update_files_in_cache(const char *prefix,
+ const struct pathspec *pathspec,
struct update_callback_data *data)
{
struct rev_info rev;
init_revisions(&rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
- init_pathspec(&rev.prune_data, pathspec);
+ if (pathspec)
+ copy_pathspec(&rev.prune_data, pathspec);
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
rev.diffopt.format_callback_data = data;
@@ -120,7 +122,8 @@ static void update_files_in_cache(const char *prefix, const char **pathspec,
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
}
-int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+int add_files_to_cache(const char *prefix,
+ const struct pathspec *pathspec, int flags)
{
struct update_callback_data data;
@@ -130,91 +133,49 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
return !!data.add_errors;
}
-static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
+static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
{
char *seen;
- int i, specs;
+ int i;
struct dir_entry **src, **dst;
- for (specs = 0; pathspec[specs]; specs++)
- /* nothing */;
- seen = xcalloc(specs, 1);
+ seen = xcalloc(pathspec->nr, 1);
src = dst = dir->entries;
i = dir->nr;
while (--i >= 0) {
struct dir_entry *entry = *src++;
- if (match_pathspec(pathspec, entry->name, entry->len,
- prefix, seen))
+ if (dir_path_match(entry, pathspec, prefix, seen))
*dst++ = entry;
}
dir->nr = dst - dir->entries;
- add_pathspec_matches_against_index(pathspec, seen, specs);
+ add_pathspec_matches_against_index(pathspec, seen);
return seen;
}
-/*
- * Checks the index to see whether any path in pathspec refers to
- * something inside a submodule. If so, dies with an error message.
- */
-static void treat_gitlinks(const char **pathspec)
-{
- int i;
-
- if (!pathspec || !*pathspec)
- return;
-
- for (i = 0; pathspec[i]; i++)
- pathspec[i] = check_path_for_gitlink(pathspec[i]);
-}
-
-static void refresh(int verbose, const char **pathspec)
+static void refresh(int verbose, const struct pathspec *pathspec)
{
char *seen;
- int i, specs;
+ int i;
- for (specs = 0; pathspec[specs]; specs++)
- /* nothing */;
- seen = xcalloc(specs, 1);
+ seen = xcalloc(pathspec->nr, 1);
refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
pathspec, seen, _("Unstaged changes after refreshing the index:"));
- for (i = 0; i < specs; i++) {
+ for (i = 0; i < pathspec->nr; i++) {
if (!seen[i])
- die(_("pathspec '%s' did not match any files"), pathspec[i]);
+ die(_("pathspec '%s' did not match any files"),
+ pathspec->items[i].match);
}
free(seen);
}
-/*
- * Normalizes argv relative to prefix, via get_pathspec(), and then
- * runs die_if_path_beyond_symlink() on each path in the normalized
- * list.
- */
-static const char **validate_pathspec(const char **argv, const char *prefix)
-{
- const char **pathspec = get_pathspec(prefix, argv);
-
- if (pathspec) {
- const char **p;
- for (p = pathspec; *p; p++) {
- die_if_path_beyond_symlink(*p, prefix);
- }
- }
-
- return pathspec;
-}
-
int run_add_interactive(const char *revision, const char *patch_mode,
- const char **pathspec)
+ const struct pathspec *pathspec)
{
- int status, ac, pc = 0;
+ int status, ac, i;
const char **args;
- if (pathspec)
- while (pathspec[pc])
- pc++;
-
- args = xcalloc(sizeof(const char *), (pc + 5));
+ args = xcalloc(sizeof(const char *), (pathspec->nr + 6));
ac = 0;
args[ac++] = "add--interactive";
if (patch_mode)
@@ -222,11 +183,9 @@ int run_add_interactive(const char *revision, const char *patch_mode,
if (revision)
args[ac++] = revision;
args[ac++] = "--";
- if (pc) {
- memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
- ac += pc;
- }
- args[ac] = NULL;
+ for (i = 0; i < pathspec->nr; i++)
+ /* pass original pathspec, to be re-parsed */
+ args[ac++] = pathspec->items[i].original;
status = run_command_v_opt(args, RUN_GIT_CMD);
free(args);
@@ -235,17 +194,17 @@ int run_add_interactive(const char *revision, const char *patch_mode,
int interactive_add(int argc, const char **argv, const char *prefix, int patch)
{
- const char **pathspec = NULL;
+ struct pathspec pathspec;
- if (argc) {
- pathspec = validate_pathspec(argv, prefix);
- if (!pathspec)
- return -1;
- }
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_SYMLINK_LEADING_PATH |
+ PATHSPEC_PREFIX_ORIGIN,
+ prefix, argv);
return run_add_interactive(NULL,
patch ? "--patch" : NULL,
- pathspec);
+ &pathspec);
}
static int edit_patch(int argc, const char **argv, const char *prefix)
@@ -263,21 +222,22 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
if (read_cache() < 0)
- die (_("Could not read the index"));
+ die(_("Could not read the index"));
init_revisions(&rev, prefix);
rev.diffopt.context = 7;
argc = setup_revisions(argc, argv, &rev, NULL);
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev.diffopt.use_color = 0;
DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
out = open(file, O_CREAT | O_WRONLY, 0666);
if (out < 0)
- die (_("Could not open '%s' for writing."), file);
+ die(_("Could not open '%s' for writing."), file);
rev.diffopt.file = xfdopen(out, "w");
rev.diffopt.close_file = 1;
if (run_diff_files(&rev, 0))
- die (_("Could not write patch"));
+ die(_("Could not write patch"));
launch_editor(file, NULL, NULL);
@@ -290,7 +250,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
child.git_cmd = 1;
child.argv = apply_argv;
if (run_command(&child))
- die (_("Could not apply '%s'"), file);
+ die(_("Could not apply '%s'"), file);
unlink(file);
free(file);
@@ -372,7 +332,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
{
int exit_status = 0;
int newfd;
- const char **pathspec;
+ struct pathspec pathspec;
struct dir_struct dir;
int flags;
int add_new_files;
@@ -443,14 +403,23 @@ int cmd_add(int argc, const char **argv, const char *prefix)
fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
return 0;
}
- pathspec = validate_pathspec(argv, prefix);
if (read_cache() < 0)
die(_("index file corrupt"));
- treat_gitlinks(pathspec);
+
+ /*
+ * Check the "pathspec '%s' did not match any files" block
+ * below before enabling new magic.
+ */
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_SYMLINK_LEADING_PATH |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE,
+ prefix, argv);
if (add_new_files) {
int baselen;
+ struct pathspec empty_pathspec;
/* Set up the default git porcelain excludes */
memset(&dir, 0, sizeof(dir));
@@ -459,32 +428,49 @@ int cmd_add(int argc, const char **argv, const char *prefix)
setup_standard_excludes(&dir);
}
+ memset(&empty_pathspec, 0, sizeof(empty_pathspec));
/* This picks up the paths that are not tracked */
- baselen = fill_directory(&dir, pathspec);
- if (pathspec)
- seen = prune_directory(&dir, pathspec, baselen);
+ baselen = fill_directory(&dir, &pathspec);
+ if (pathspec.nr)
+ seen = prune_directory(&dir, &pathspec, baselen);
}
if (refresh_only) {
- refresh(verbose, pathspec);
+ refresh(verbose, &pathspec);
goto finish;
}
- if (pathspec) {
+ if (pathspec.nr) {
int i;
if (!seen)
- seen = find_pathspecs_matching_against_index(pathspec);
- for (i = 0; pathspec[i]; i++) {
- if (!seen[i] && pathspec[i][0]
- && !file_exists(pathspec[i])) {
+ seen = find_pathspecs_matching_against_index(&pathspec);
+
+ /*
+ * file_exists() assumes exact match
+ */
+ GUARD_PATHSPEC(&pathspec,
+ PATHSPEC_FROMTOP |
+ PATHSPEC_LITERAL |
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE |
+ PATHSPEC_EXCLUDE);
+
+ 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 (ignore_missing) {
int dtype = DT_UNKNOWN;
- if (is_excluded(&dir, pathspec[i], &dtype))
- dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
+ if (is_excluded(&dir, path, &dtype))
+ dir_add_ignored(&dir, path, pathspec.items[i].len);
} else
die(_("pathspec '%s' did not match any files"),
- pathspec[i]);
+ pathspec.items[i].original);
}
}
free(seen);
@@ -493,7 +479,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
plug_bulk_checkin();
update_data.flags = flags;
- update_files_in_cache(prefix, pathspec, &update_data);
+ update_files_in_cache(prefix, &pathspec, &update_data);
exit_status |= !!update_data.add_errors;
if (add_new_files)
@@ -501,7 +487,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
unplug_bulk_checkin();
- finish:
+finish:
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
diff --git a/builtin/apply.c b/builtin/apply.c
index 30eefc3c7b..a7e72d57ab 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -473,7 +473,7 @@ static char *find_name_gnu(const char *line, const char *def, int p_value)
/*
* Proposed "new-style" GNU patch/diff format; see
- * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+ * http://marc.info/?l=git&m=112927316408690&w=2
*/
if (unquote_c_style(&name, line, NULL)) {
strbuf_release(&name);
@@ -722,7 +722,7 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
static char *find_name_traditional(const char *line, char *def, int p_value)
{
- size_t len = strlen(line);
+ size_t len;
size_t date_len;
if (*line == '"') {
@@ -906,7 +906,7 @@ static void parse_traditional_patch(const char *first, const char *second, struc
patch->old_name = name;
} else {
patch->old_name = name;
- patch->new_name = xstrdup(name);
+ patch->new_name = null_strdup(name);
}
}
if (!name)
@@ -1409,10 +1409,10 @@ static void recount_diff(const char *line, int size, struct fragment *fragment)
case '\\':
continue;
case '@':
- ret = size < 3 || prefixcmp(line, "@@ ");
+ ret = size < 3 || !starts_with(line, "@@ ");
break;
case 'd':
- ret = size < 5 || prefixcmp(line, "diff ");
+ ret = size < 5 || !starts_with(line, "diff ");
break;
default:
ret = -1;
@@ -1798,11 +1798,11 @@ static struct fragment *parse_binary_hunk(char **buf_p,
*status_p = 0;
- if (!prefixcmp(buffer, "delta ")) {
+ if (starts_with(buffer, "delta ")) {
patch_method = BINARY_DELTA_DEFLATED;
origlen = strtoul(buffer + 6, NULL, 10);
}
- else if (!prefixcmp(buffer, "literal ")) {
+ else if (starts_with(buffer, "literal ")) {
patch_method = BINARY_LITERAL_DEFLATED;
origlen = strtoul(buffer + 8, NULL, 10);
}
@@ -1943,13 +1943,7 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
size - offset - hdrsize, patch);
if (!patchsize) {
- static const char *binhdr[] = {
- "Binary files ",
- "Files ",
- NULL,
- };
static const char git_binary[] = "GIT binary patch\n";
- int i;
int hd = hdrsize + offset;
unsigned long llen = linelen(buffer + hd, size - hd);
@@ -1965,6 +1959,12 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
patchsize = 0;
}
else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) {
+ static const char *binhdr[] = {
+ "Binary files ",
+ "Files ",
+ NULL,
+ };
+ int i;
for (i = 0; binhdr[i]; i++) {
int len = strlen(binhdr[i]);
if (len < size - hd &&
@@ -2999,7 +2999,7 @@ static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsig
return 0;
}
-static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf)
{
if (!ce)
return 0;
@@ -3117,7 +3117,7 @@ static struct patch *previous_patch(struct patch *patch, int *gone)
return previous;
}
-static int verify_index_match(struct cache_entry *ce, struct stat *st)
+static int verify_index_match(const struct cache_entry *ce, struct stat *st)
{
if (S_ISGITLINK(ce->ce_mode)) {
if (!S_ISDIR(st->st_mode))
@@ -3130,7 +3130,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st)
#define SUBMODULE_PATCH_WITHOUT_INDEX 1
static int load_patch_target(struct strbuf *buf,
- struct cache_entry *ce,
+ const struct cache_entry *ce,
struct stat *st,
const char *name,
unsigned expected_mode)
@@ -3160,7 +3160,8 @@ static int load_patch_target(struct strbuf *buf,
* we read from the result of a previous diff.
*/
static int load_preimage(struct image *image,
- struct patch *patch, struct stat *st, struct cache_entry *ce)
+ struct patch *patch, struct stat *st,
+ const struct cache_entry *ce)
{
struct strbuf buf = STRBUF_INIT;
size_t len;
@@ -3273,7 +3274,7 @@ static int load_current(struct image *image, struct patch *patch)
}
static int try_threeway(struct image *image, struct patch *patch,
- struct stat *st, struct cache_entry *ce)
+ struct stat *st, const struct cache_entry *ce)
{
unsigned char pre_sha1[20], post_sha1[20], our_sha1[20];
struct strbuf buf = STRBUF_INIT;
@@ -3343,7 +3344,7 @@ static int try_threeway(struct image *image, struct patch *patch,
return 0;
}
-static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
+static int apply_data(struct patch *patch, struct stat *st, const struct cache_entry *ce)
{
struct image image;
@@ -3525,7 +3526,7 @@ static int check_patch(struct patch *patch)
ok_if_exists = 0;
if (new_name &&
- ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
+ ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) {
int err = check_to_create(new_name, ok_if_exists);
if (err && threeway) {
@@ -3626,12 +3627,12 @@ static int preimage_sha1_in_gitlink_patch(struct patch *p, unsigned char sha1[20
hunk->oldpos == 1 && hunk->oldlines == 1 &&
/* does preimage begin with the heading? */
(preimage = memchr(hunk->patch, '\n', hunk->size)) != NULL &&
- !prefixcmp(++preimage, heading) &&
+ starts_with(++preimage, heading) &&
/* does it record full SHA-1? */
!get_sha1_hex(preimage + sizeof(heading) - 1, sha1) &&
preimage[sizeof(heading) + 40 - 1] == '\n' &&
/* does the abbreviated name on the index line agree with it? */
- !prefixcmp(preimage + sizeof(heading) - 1, p->old_sha1_prefix))
+ starts_with(preimage + sizeof(heading) - 1, p->old_sha1_prefix))
return 0; /* it all looks fine */
/* we may have full object name on the index line */
@@ -3847,7 +3848,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
const char *s = buf;
if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
- die(_("corrupt patch for subproject %s"), path);
+ die(_("corrupt patch for submodule %s"), path);
} else {
if (!cached) {
if (lstat(path, &st) < 0)
@@ -4362,23 +4363,23 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
{ OPTION_CALLBACK, 'p', NULL, NULL, N_("num"),
N_("remove <num> leading slashes from traditional diff paths"),
0, option_parse_p },
- OPT_BOOLEAN(0, "no-add", &no_add,
+ OPT_BOOL(0, "no-add", &no_add,
N_("ignore additions made by the patch")),
- OPT_BOOLEAN(0, "stat", &diffstat,
+ OPT_BOOL(0, "stat", &diffstat,
N_("instead of applying the patch, output diffstat for the input")),
OPT_NOOP_NOARG(0, "allow-binary-replacement"),
OPT_NOOP_NOARG(0, "binary"),
- OPT_BOOLEAN(0, "numstat", &numstat,
+ OPT_BOOL(0, "numstat", &numstat,
N_("show number of added and deleted lines in decimal notation")),
- OPT_BOOLEAN(0, "summary", &summary,
+ OPT_BOOL(0, "summary", &summary,
N_("instead of applying the patch, output a summary for the input")),
- OPT_BOOLEAN(0, "check", &check,
+ OPT_BOOL(0, "check", &check,
N_("instead of applying the patch, see if the patch is applicable")),
- OPT_BOOLEAN(0, "index", &check_index,
+ OPT_BOOL(0, "index", &check_index,
N_("make sure the patch is applicable to the current index")),
- OPT_BOOLEAN(0, "cached", &cached,
+ OPT_BOOL(0, "cached", &cached,
N_("apply a patch without touching the working tree")),
- OPT_BOOLEAN(0, "apply", &force_apply,
+ OPT_BOOL(0, "apply", &force_apply,
N_("also apply the patch (use with --stat/--summary/--check)")),
OPT_BOOL('3', "3way", &threeway,
N_( "attempt three-way merge if a patch does not apply")),
@@ -4398,13 +4399,13 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
{ OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
N_("ignore changes in whitespace when finding context"),
PARSE_OPT_NOARG, option_parse_space_change },
- OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
+ OPT_BOOL('R', "reverse", &apply_in_reverse,
N_("apply the patch in reverse")),
- OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
+ OPT_BOOL(0, "unidiff-zero", &unidiff_zero,
N_("don't expect at least one line of context")),
- OPT_BOOLEAN(0, "reject", &apply_with_reject,
+ OPT_BOOL(0, "reject", &apply_with_reject,
N_("leave the rejected hunks in corresponding *.rej files")),
- OPT_BOOLEAN(0, "allow-overlap", &allow_overlap,
+ OPT_BOOL(0, "allow-overlap", &allow_overlap,
N_("allow overlapping hunks")),
OPT__VERBOSE(&apply_verbosely, N_("be verbose")),
OPT_BIT(0, "inaccurate-eof", &options,
diff --git a/builtin/archive.c b/builtin/archive.c
index 49178f159e..a1e3b940c2 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -57,9 +57,9 @@ static int run_remote_archiver(int argc, const char **argv,
if (!buf)
die(_("git archive: expected ACK/NAK, got EOF"));
if (strcmp(buf, "ACK")) {
- if (!prefixcmp(buf, "NACK "))
+ if (starts_with(buf, "NACK "))
die(_("git archive: NACK %s"), buf + 5);
- if (!prefixcmp(buf, "ERR "))
+ if (starts_with(buf, "ERR "))
die(_("remote error: %s"), buf + 4);
die(_("git archive: protocol error"));
}
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index e3884e3bb6..3324229025 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -13,10 +13,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
int next_all = 0;
int no_checkout = 0;
struct option options[] = {
- OPT_BOOLEAN(0, "next-all", &next_all,
- N_("perform 'git bisect next'")),
- OPT_BOOLEAN(0, "no-checkout", &no_checkout,
- N_("update BISECT_HEAD instead of checking out the current commit")),
+ OPT_BOOL(0, "next-all", &next_all,
+ N_("perform 'git bisect next'")),
+ OPT_BOOL(0, "no-checkout", &no_checkout,
+ N_("update BISECT_HEAD instead of checking out the current commit")),
OPT_END()
};
diff --git a/builtin/blame.c b/builtin/blame.c
index 57a487e052..e5b5d71bad 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -21,6 +21,8 @@
#include "parse-options.h"
#include "utf8.h"
#include "userdiff.h"
+#include "line-range.h"
+#include "line-log.h"
static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
@@ -195,7 +197,6 @@ static void drop_origin_blob(struct origin *o)
* scoreboard structure, sorted by the target line number.
*/
struct blame_entry {
- struct blame_entry *prev;
struct blame_entry *next;
/* the first line of this group in the final image;
@@ -254,15 +255,6 @@ struct scoreboard {
int *lineno;
};
-static inline int same_suspect(struct origin *a, struct origin *b)
-{
- if (a == b)
- return 1;
- if (a->commit != b->commit)
- return 0;
- return !strcmp(a->path, b->path);
-}
-
static void sanity_check_refcnt(struct scoreboard *);
/*
@@ -275,13 +267,11 @@ static void coalesce(struct scoreboard *sb)
struct blame_entry *ent, *next;
for (ent = sb->ent; ent && (next = ent->next); ent = next) {
- if (same_suspect(ent->suspect, next->suspect) &&
+ if (ent->suspect == next->suspect &&
ent->guilty == next->guilty &&
ent->s_lno + ent->num_lines == next->s_lno) {
ent->num_lines += next->num_lines;
ent->next = next->next;
- if (ent->next)
- ent->next->prev = ent;
origin_decref(next->suspect);
free(next);
ent->score = 0;
@@ -407,7 +397,9 @@ static struct origin *find_origin(struct scoreboard *sb,
paths[0] = origin->path;
paths[1] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
+ parse_pathspec(&diff_opts.pathspec,
+ PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
+ PATHSPEC_LITERAL_PATH, "", paths);
diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
@@ -457,7 +449,7 @@ static struct origin *find_origin(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
+ free_pathspec(&diff_opts.pathspec);
if (porigin) {
/*
* Create a freestanding copy that is not part of
@@ -485,15 +477,12 @@ static struct origin *find_rename(struct scoreboard *sb,
struct origin *porigin = NULL;
struct diff_options diff_opts;
int i;
- const char *paths[2];
diff_setup(&diff_opts);
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = origin->path;
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
@@ -515,7 +504,7 @@ static struct origin *find_rename(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
+ free_pathspec(&diff_opts.pathspec);
return porigin;
}
@@ -533,7 +522,7 @@ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
prev = ent;
/* prev, if not NULL, is the last one that is below e */
- e->prev = prev;
+
if (prev) {
e->next = prev->next;
prev->next = e;
@@ -542,8 +531,6 @@ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
e->next = sb->ent;
sb->ent = e;
}
- if (e->next)
- e->next->prev = e;
}
/*
@@ -554,23 +541,26 @@ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
*/
static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
{
- struct blame_entry *p, *n;
+ struct blame_entry *n;
- p = dst->prev;
n = dst->next;
origin_incref(src->suspect);
origin_decref(dst->suspect);
memcpy(dst, src, sizeof(*src));
- dst->prev = p;
dst->next = n;
dst->score = 0;
}
-static const char *nth_line(struct scoreboard *sb, int lno)
+static const char *nth_line(struct scoreboard *sb, long lno)
{
return sb->final_buf + sb->lineno[lno];
}
+static const char *nth_line_cb(void *data, long lno)
+{
+ return nth_line((struct scoreboard *)data, lno);
+}
+
/*
* It is known that lines between tlno to same came from parent, and e
* has an overlap with that range. it also is known that parent's
@@ -736,7 +726,7 @@ static int find_last_in_target(struct scoreboard *sb, struct origin *target)
int last_in_target = -1;
for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target))
+ if (e->guilty || e->suspect != target)
continue;
if (last_in_target < e->s_lno + e->num_lines)
last_in_target = e->s_lno + e->num_lines;
@@ -756,7 +746,7 @@ static void blame_chunk(struct scoreboard *sb,
struct blame_entry *e;
for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target))
+ if (e->guilty || e->suspect != target)
continue;
if (same <= e->s_lno)
continue;
@@ -933,7 +923,6 @@ static void find_copy_in_blob(struct scoreboard *sb,
mmfile_t *file_p)
{
const char *cp;
- int cnt;
mmfile_t file_o;
struct handle_split_cb_data d;
@@ -944,13 +933,7 @@ static void find_copy_in_blob(struct scoreboard *sb,
*/
cp = nth_line(sb, ent->lno);
file_o.ptr = (char *) cp;
- cnt = ent->num_lines;
-
- while (cnt && cp < sb->final_buf + sb->final_buf_size) {
- if (*cp++ == '\n')
- cnt--;
- }
- file_o.size = cp - file_o.ptr;
+ file_o.size = nth_line(sb, ent->lno + ent->num_lines) - cp;
/*
* file_o is a part of final image we are annotating.
@@ -986,7 +969,7 @@ static int find_move_in_parent(struct scoreboard *sb,
while (made_progress) {
made_progress = 0;
for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target) ||
+ if (e->guilty || e->suspect != target ||
ent_score(sb, e) < blame_move_score)
continue;
find_copy_in_blob(sb, e, parent, split, &file_p);
@@ -1021,14 +1004,14 @@ static struct blame_list *setup_blame_list(struct scoreboard *sb,
for (e = sb->ent, num_ents = 0; e; e = e->next)
if (!e->scanned && !e->guilty &&
- same_suspect(e->suspect, target) &&
+ e->suspect == target &&
min_score < ent_score(sb, e))
num_ents++;
if (num_ents) {
blame_list = xcalloc(num_ents, sizeof(struct blame_list));
for (e = sb->ent, i = 0; e; e = e->next)
if (!e->scanned && !e->guilty &&
- same_suspect(e->suspect, target) &&
+ e->suspect == target &&
min_score < ent_score(sb, e))
blame_list[i++].ent = e;
}
@@ -1058,7 +1041,6 @@ static int find_copy_in_parent(struct scoreboard *sb,
int opt)
{
struct diff_options diff_opts;
- const char *paths[1];
int i, j;
int retval;
struct blame_list *blame_list;
@@ -1072,8 +1054,6 @@ static int find_copy_in_parent(struct scoreboard *sb,
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
diff_setup_done(&diff_opts);
/* Try "find copies harder" on new path if requested;
@@ -1156,7 +1136,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
}
reset_scanned_flag(sb);
diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
+ free_pathspec(&diff_opts.pathspec);
return retval;
}
@@ -1175,7 +1155,7 @@ static void pass_whole_blame(struct scoreboard *sb,
origin->file.ptr = NULL;
}
for (e = sb->ent; e; e = e->next) {
- if (!same_suspect(e->suspect, origin))
+ if (e->suspect != origin)
continue;
origin_incref(porigin);
origin_decref(e->suspect);
@@ -1548,8 +1528,7 @@ static void assign_blame(struct scoreboard *sb, int opt)
*/
origin_incref(suspect);
commit = suspect->commit;
- if (!commit->object.parsed)
- parse_commit(commit);
+ parse_commit(commit);
if (reverse ||
(!(commit->object.flags & UNINTERESTING) &&
!(revs->max_age != -1 && commit->date < revs->max_age)))
@@ -1565,7 +1544,7 @@ static void assign_blame(struct scoreboard *sb, int opt)
/* Take responsibility for the remaining entries */
for (ent = sb->ent; ent; ent = ent->next)
- if (same_suspect(ent->suspect, suspect))
+ if (ent->suspect == suspect)
found_guilty_entry(ent);
origin_decref(suspect);
@@ -1578,14 +1557,14 @@ static const char *format_time(unsigned long time, const char *tz_str,
int show_raw_time)
{
static char time_buf[128];
- const char *time_str;
- int time_len;
- int tz;
if (show_raw_time) {
snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
}
else {
+ const char *time_str;
+ int time_len;
+ int tz;
tz = atoi(tz_str);
time_str = show_date(time, tz, blame_date_mode);
time_len = strlen(time_str);
@@ -1770,25 +1749,41 @@ static int prepare_lines(struct scoreboard *sb)
{
const char *buf = sb->final_buf;
unsigned long len = sb->final_buf_size;
- int num = 0, incomplete = 0, bol = 1;
+ const char *end = buf + len;
+ const char *p;
+ int *lineno;
+ int num = 0, incomplete = 0;
- if (len && buf[len-1] != '\n')
- incomplete++; /* incomplete line at the end */
- while (len--) {
- if (bol) {
- sb->lineno = xrealloc(sb->lineno,
- sizeof(int *) * (num + 1));
- sb->lineno[num] = buf - sb->final_buf;
- bol = 0;
- }
- if (*buf++ == '\n') {
+ for (p = buf;;) {
+ p = memchr(p, '\n', end - p);
+ if (p) {
+ p++;
num++;
- bol = 1;
+ continue;
}
+ break;
+ }
+
+ if (len && end[-1] != '\n')
+ incomplete++; /* incomplete line at the end */
+
+ sb->lineno = xmalloc(sizeof(*sb->lineno) * (num + incomplete + 1));
+ lineno = sb->lineno;
+
+ *lineno++ = 0;
+ for (p = buf;;) {
+ p = memchr(p, '\n', end - p);
+ if (p) {
+ p++;
+ *lineno++ = p - buf;
+ continue;
+ }
+ break;
}
- sb->lineno = xrealloc(sb->lineno,
- sizeof(int *) * (num + incomplete + 1));
- sb->lineno[num + incomplete] = buf - sb->final_buf;
+
+ if (incomplete)
+ *lineno++ = len;
+
sb->num_lines = num + incomplete;
return sb->num_lines;
}
@@ -1801,17 +1796,17 @@ static int prepare_lines(struct scoreboard *sb)
static int read_ancestry(const char *graft_file)
{
FILE *fp = fopen(graft_file, "r");
- char buf[1024];
+ struct strbuf buf = STRBUF_INIT;
if (!fp)
return -1;
- while (fgets(buf, sizeof(buf), fp)) {
+ while (!strbuf_getwholeline(&buf, fp, '\n')) {
/* The format is just "Commit Parent1 Parent2 ...\n" */
- int len = strlen(buf);
- struct commit_graft *graft = read_graft_line(buf, len);
+ struct commit_graft *graft = read_graft_line(buf.buf, buf.len);
if (graft)
register_commit_graft(graft, 0);
}
fclose(fp);
+ strbuf_release(&buf);
return 0;
}
@@ -1931,103 +1926,6 @@ static const char *add_prefix(const char *prefix, const char *path)
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
-/*
- * Parsing of (comma separated) one item in the -L option
- */
-static const char *parse_loc(const char *spec,
- struct scoreboard *sb, long lno,
- long begin, long *ret)
-{
- char *term;
- const char *line;
- long num;
- int reg_error;
- regex_t regexp;
- regmatch_t match[1];
-
- /* Allow "-L <something>,+20" to mean starting at <something>
- * for 20 lines, or "-L <something>,-5" for 5 lines ending at
- * <something>.
- */
- if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
- num = strtol(spec + 1, &term, 10);
- if (term != spec + 1) {
- if (spec[0] == '-')
- num = 0 - num;
- if (0 < num)
- *ret = begin + num - 2;
- else if (!num)
- *ret = begin;
- else
- *ret = begin + num;
- return term;
- }
- return spec;
- }
- num = strtol(spec, &term, 10);
- if (term != spec) {
- *ret = num;
- return term;
- }
- if (spec[0] != '/')
- return spec;
-
- /* it could be a regexp of form /.../ */
- for (term = (char *) spec + 1; *term && *term != '/'; term++) {
- if (*term == '\\')
- term++;
- }
- if (*term != '/')
- return spec;
-
- /* try [spec+1 .. term-1] as regexp */
- *term = 0;
- begin--; /* input is in human terms */
- line = nth_line(sb, begin);
-
- if (!(reg_error = regcomp(&regexp, spec + 1, REG_NEWLINE)) &&
- !(reg_error = regexec(&regexp, line, 1, match, 0))) {
- const char *cp = line + match[0].rm_so;
- const char *nline;
-
- while (begin++ < lno) {
- nline = nth_line(sb, begin);
- if (line <= cp && cp < nline)
- break;
- line = nline;
- }
- *ret = begin;
- regfree(&regexp);
- *term++ = '/';
- return term;
- }
- else {
- char errbuf[1024];
- regerror(reg_error, &regexp, errbuf, 1024);
- die("-L parameter '%s': %s", spec + 1, errbuf);
- }
-}
-
-/*
- * Parsing of -L option
- */
-static void prepare_blame_range(struct scoreboard *sb,
- const char *bottomtop,
- long lno,
- long *bottom, long *top)
-{
- const char *term;
-
- term = parse_loc(bottomtop, sb, lno, 1, bottom);
- if (*term == ',') {
- term = parse_loc(term + 1, sb, lno, *bottom + 1, top);
- if (*term)
- usage(blame_usage);
- }
- if (*term)
- usage(blame_usage);
-}
-
static int git_blame_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "blame.showroot")) {
@@ -2324,38 +2222,27 @@ static int blame_move_callback(const struct option *option, const char *arg, int
return 0;
}
-static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
-{
- const char **bottomtop = option->value;
- if (!arg)
- return -1;
- if (*bottomtop)
- die("More than one '-L n,m' option given");
- *bottomtop = arg;
- return 0;
-}
-
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
const char *path;
struct scoreboard sb;
struct origin *o;
- struct blame_entry *ent;
- long dashdash_pos, bottom, top, lno;
+ struct blame_entry *ent = NULL;
+ long dashdash_pos, lno;
const char *final_commit_name = NULL;
enum object_type type;
- static const char *bottomtop = NULL;
+ static struct string_list range_list;
static int output_option = 0, opt = 0;
static int show_stats = 0;
static const char *revs_file = NULL;
static const char *contents_from = NULL;
static const struct option options[] = {
- OPT_BOOLEAN(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
- OPT_BOOLEAN('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
- OPT_BOOLEAN(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
- OPT_BOOLEAN(0, "show-stats", &show_stats, N_("Show work cost statistics")),
+ 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_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),
@@ -2372,13 +2259,16 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
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_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback),
+ OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
OPT__ABBREV(&abbrev),
OPT_END()
};
struct parse_opt_ctx_t ctx;
int cmd_is_annotate = !strcmp(argv[0], "annotate");
+ struct range_set ranges;
+ unsigned int range_i;
+ long anchor;
git_config(git_blame_config, NULL);
init_revisions(&revs, NULL);
@@ -2571,26 +2461,46 @@ parse_done:
num_read_blob++;
lno = prepare_lines(&sb);
- bottom = top = 0;
- if (bottomtop)
- prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
- if (bottom && top && top < bottom) {
- long tmp;
- tmp = top; top = bottom; bottom = tmp;
- }
- if (bottom < 1)
- bottom = 1;
- if (top < 1)
- top = lno;
- bottom--;
- if (lno < top || lno < bottom)
- die("file %s has only %lu lines", path, lno);
-
- ent = xcalloc(1, sizeof(*ent));
- ent->lno = bottom;
- ent->num_lines = top - bottom;
- ent->suspect = o;
- ent->s_lno = bottom;
+ if (lno && !range_list.nr)
+ string_list_append(&range_list, xstrdup("1"));
+
+ anchor = 1;
+ range_set_init(&ranges, range_list.nr);
+ for (range_i = 0; range_i < range_list.nr; ++range_i) {
+ long bottom, top;
+ if (parse_range_arg(range_list.items[range_i].string,
+ nth_line_cb, &sb, lno, anchor,
+ &bottom, &top, sb.path))
+ usage(blame_usage);
+ if (lno < top || ((lno || bottom) && lno < bottom))
+ die("file %s has only %lu lines", path, lno);
+ if (bottom < 1)
+ bottom = 1;
+ if (top < 1)
+ top = lno;
+ bottom--;
+ range_set_append_unsafe(&ranges, bottom, top);
+ anchor = top + 1;
+ }
+ sort_and_merge_range_set(&ranges);
+
+ for (range_i = ranges.nr; range_i > 0; --range_i) {
+ const struct range *r = &ranges.ranges[range_i - 1];
+ long bottom = r->start;
+ long top = r->end;
+ struct blame_entry *next = ent;
+ ent = xcalloc(1, sizeof(*ent));
+ ent->lno = bottom;
+ ent->num_lines = top - bottom;
+ ent->suspect = o;
+ ent->s_lno = bottom;
+ ent->next = next;
+ origin_incref(o);
+ }
+ origin_decref(o);
+
+ range_set_release(&ranges);
+ string_list_clear(&range_list, 0);
sb.ent = ent;
sb.path = path;
diff --git a/builtin/branch.c b/builtin/branch.c
index 083689063f..b4d771673e 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -81,13 +81,13 @@ static int parse_branch_color_slot(const char *var, int ofs)
static int git_branch_config(const char *var, const char *value, void *cb)
{
- if (!prefixcmp(var, "column."))
+ if (starts_with(var, "column."))
return git_column_config(var, value, "branch", &colopts);
if (!strcmp(var, "color.branch")) {
branch_use_color = git_config_colorbool(var, value);
return 0;
}
- if (!prefixcmp(var, "color.branch.")) {
+ if (starts_with(var, "color.branch.")) {
int slot = parse_branch_color_slot(var, 13);
if (slot < 0)
return 0;
@@ -423,19 +423,20 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
char *ref = NULL;
struct branch *branch = branch_get(branch_name);
struct strbuf fancy = STRBUF_INIT;
+ int upstream_is_gone = 0;
+ int added_decoration = 1;
- if (!stat_tracking_info(branch, &ours, &theirs)) {
- if (branch && branch->merge && branch->merge[0]->dst &&
- show_upstream_ref) {
- ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
- if (want_color(branch_use_color))
- strbuf_addf(stat, "[%s%s%s] ",
- branch_get_color(BRANCH_COLOR_UPSTREAM),
- ref, branch_get_color(BRANCH_COLOR_RESET));
- else
- strbuf_addf(stat, "[%s] ", ref);
- }
+ switch (stat_tracking_info(branch, &ours, &theirs)) {
+ case 0:
+ /* no base */
return;
+ case -1:
+ /* with "gone" base */
+ upstream_is_gone = 1;
+ break;
+ default:
+ /* with base */
+ break;
}
if (show_upstream_ref) {
@@ -448,19 +449,29 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
strbuf_addstr(&fancy, ref);
}
- if (!ours) {
- if (ref)
+ if (upstream_is_gone) {
+ if (show_upstream_ref)
+ strbuf_addf(stat, _("[%s: gone]"), fancy.buf);
+ else
+ added_decoration = 0;
+ } else if (!ours && !theirs) {
+ if (show_upstream_ref)
+ strbuf_addf(stat, _("[%s]"), fancy.buf);
+ else
+ added_decoration = 0;
+ } else if (!ours) {
+ if (show_upstream_ref)
strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs);
else
strbuf_addf(stat, _("[behind %d]"), theirs);
} else if (!theirs) {
- if (ref)
+ if (show_upstream_ref)
strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
else
strbuf_addf(stat, _("[ahead %d]"), ours);
} else {
- if (ref)
+ if (show_upstream_ref)
strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
fancy.buf, ours, theirs);
else
@@ -468,7 +479,8 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
ours, theirs);
}
strbuf_release(&fancy);
- strbuf_addch(stat, ' ');
+ if (added_decoration)
+ strbuf_addch(stat, ' ');
free(ref);
}
@@ -490,7 +502,7 @@ static void add_verbose_info(struct strbuf *out, struct ref_item *item,
const char *sub = _(" **** invalid ref ****");
struct commit *commit = item->commit;
- if (commit && !parse_commit(commit)) {
+ if (!parse_commit(commit)) {
pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject);
sub = subject.buf;
}
@@ -797,7 +809,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_SET_INT( 0, "set-upstream", &track, N_("change upstream info"),
BRANCH_TRACK_OVERRIDE),
OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"),
- OPT_BOOLEAN(0, "unset-upstream", &unset_upstream, "Unset the upstream info"),
+ OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"),
OPT__COLOR(&branch_use_color, N_("use colored output")),
OPT_SET_INT('r', "remotes", &kinds, N_("act on remote-tracking branches"),
REF_REMOTE_BRANCH),
@@ -822,10 +834,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
- OPT_BOOLEAN(0, "list", &list, N_("list branch names")),
- OPT_BOOLEAN('l', "create-reflog", &reflog, N_("create the branch's reflog")),
- OPT_BOOLEAN(0, "edit-description", &edit_description,
- N_("edit the description for the branch")),
+ OPT_BOOL(0, "list", &list, N_("list branch names")),
+ OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")),
+ OPT_BOOL(0, "edit-description", &edit_description,
+ N_("edit the description for the branch")),
OPT__FORCE(&force_create, N_("force creation (when already exists)")),
{
OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
@@ -856,7 +868,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (!strcmp(head, "HEAD")) {
detached = 1;
} else {
- if (prefixcmp(head, "refs/heads/"))
+ if (!starts_with(head, "refs/heads/"))
die(_("HEAD not found below refs/heads!"));
head += 11;
}
@@ -872,7 +884,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (with_commit || merge_filter != NO_FILTER)
list = 1;
- if (!!delete + !!rename + !!force_create + !!list + !!new_upstream + !!unset_upstream > 1)
+ if (!!delete + !!rename + !!force_create + !!new_upstream +
+ list + unset_upstream > 1)
usage_with_options(builtin_branch_usage, options);
if (abbrev == -1)
@@ -968,9 +981,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
die(_("no such branch '%s'"), argv[0]);
}
- if (!branch_has_merge_config(branch)) {
+ if (!branch_has_merge_config(branch))
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);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 045cee7bce..d5a93e0e91 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -13,9 +13,6 @@
#include "userdiff.h"
#include "streaming.h"
-#define BATCH 1
-#define BATCH_CHECK 2
-
static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
{
unsigned char sha1[20];
@@ -48,6 +45,14 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
case 'e':
return !has_sha1_file(sha1);
+ case 'c':
+ if (!obj_context.path[0])
+ die("git cat-file --textconv %s: <object> must be <sha1:path>",
+ obj_name);
+
+ if (textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size))
+ break;
+
case 'p':
type = sha1_object_info(sha1, NULL);
if (type < 0)
@@ -70,16 +75,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
/* otherwise just spit out the data */
break;
- case 'c':
- if (!obj_context.path[0])
- die("git cat-file --textconv %s: <object> must be <sha1:path>",
- obj_name);
-
- if (!textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size))
- die("git cat-file --textconv: unable to run textconv on %s",
- obj_name);
- break;
-
case 0:
if (type_from_string(exp_type) == OBJ_BLOB) {
unsigned char blob_sha1[20];
@@ -117,54 +112,211 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
return 0;
}
-static int batch_one_object(const char *obj_name, int print_contents)
-{
+struct expand_data {
unsigned char sha1[20];
- enum object_type type = 0;
+ enum object_type type;
unsigned long size;
- void *contents = NULL;
+ unsigned long disk_size;
+ const char *rest;
+ unsigned char delta_base_sha1[20];
+
+ /*
+ * If mark_query is true, we do not expand anything, but rather
+ * just mark the object_info with items we wish to query.
+ */
+ int mark_query;
+
+ /*
+ * Whether to split the input on whitespace before feeding it to
+ * get_sha1; this is decided during the mark_query phase based on
+ * whether we have a %(rest) token in our format.
+ */
+ int split_on_whitespace;
+
+ /*
+ * After a mark_query run, this object_info is set up to be
+ * passed to sha1_object_info_extended. It will point to the data
+ * elements above, so you can retrieve the response from there.
+ */
+ struct object_info info;
+};
+
+static int is_atom(const char *atom, const char *s, int slen)
+{
+ int alen = strlen(atom);
+ return alen == slen && !memcmp(atom, s, alen);
+}
+
+static void expand_atom(struct strbuf *sb, const char *atom, int len,
+ void *vdata)
+{
+ struct expand_data *data = vdata;
+
+ if (is_atom("objectname", atom, len)) {
+ if (!data->mark_query)
+ strbuf_addstr(sb, sha1_to_hex(data->sha1));
+ } else if (is_atom("objecttype", atom, len)) {
+ if (data->mark_query)
+ data->info.typep = &data->type;
+ else
+ strbuf_addstr(sb, typename(data->type));
+ } else if (is_atom("objectsize", atom, len)) {
+ if (data->mark_query)
+ data->info.sizep = &data->size;
+ else
+ strbuf_addf(sb, "%lu", data->size);
+ } else if (is_atom("objectsize:disk", atom, len)) {
+ if (data->mark_query)
+ data->info.disk_sizep = &data->disk_size;
+ else
+ strbuf_addf(sb, "%lu", data->disk_size);
+ } else if (is_atom("rest", atom, len)) {
+ if (data->mark_query)
+ data->split_on_whitespace = 1;
+ else if (data->rest)
+ strbuf_addstr(sb, data->rest);
+ } else if (is_atom("deltabase", atom, len)) {
+ if (data->mark_query)
+ data->info.delta_base_sha1 = data->delta_base_sha1;
+ else
+ strbuf_addstr(sb, sha1_to_hex(data->delta_base_sha1));
+ } else
+ die("unknown format element: %.*s", len, atom);
+}
+
+static size_t expand_format(struct strbuf *sb, const char *start, void *data)
+{
+ const char *end;
+
+ if (*start != '(')
+ return 0;
+ end = strchr(start + 1, ')');
+ if (!end)
+ die("format element '%s' does not end in ')'", start);
+
+ expand_atom(sb, start + 1, end - start - 1, data);
+
+ return end - start + 1;
+}
+
+static void print_object_or_die(int fd, struct expand_data *data)
+{
+ const unsigned char *sha1 = data->sha1;
+
+ assert(data->info.typep);
+
+ if (data->type == OBJ_BLOB) {
+ if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0)
+ die("unable to stream %s to stdout", sha1_to_hex(sha1));
+ }
+ else {
+ enum object_type type;
+ unsigned long size;
+ void *contents;
+
+ contents = read_sha1_file(sha1, &type, &size);
+ if (!contents)
+ die("object %s disappeared", sha1_to_hex(sha1));
+ if (type != data->type)
+ die("object %s changed type!?", sha1_to_hex(sha1));
+ if (data->info.sizep && size != data->size)
+ die("object %s changed size!?", sha1_to_hex(sha1));
+
+ write_or_die(fd, contents, size);
+ free(contents);
+ }
+}
+
+struct batch_options {
+ int enabled;
+ int print_contents;
+ const char *format;
+};
+
+static int batch_one_object(const char *obj_name, struct batch_options *opt,
+ struct expand_data *data)
+{
+ struct strbuf buf = STRBUF_INIT;
if (!obj_name)
return 1;
- if (get_sha1(obj_name, sha1)) {
+ if (get_sha1(obj_name, data->sha1)) {
printf("%s missing\n", obj_name);
fflush(stdout);
return 0;
}
- if (print_contents == BATCH)
- contents = read_sha1_file(sha1, &type, &size);
- else
- type = sha1_object_info(sha1, &size);
-
- if (type <= 0) {
+ if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) {
printf("%s missing\n", obj_name);
fflush(stdout);
- if (print_contents == BATCH)
- free(contents);
return 0;
}
- printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
- fflush(stdout);
+ strbuf_expand(&buf, opt->format, expand_format, data);
+ strbuf_addch(&buf, '\n');
+ write_or_die(1, buf.buf, buf.len);
+ strbuf_release(&buf);
- if (print_contents == BATCH) {
- write_or_die(1, contents, size);
- printf("\n");
- fflush(stdout);
- free(contents);
+ if (opt->print_contents) {
+ print_object_or_die(1, data);
+ write_or_die(1, "\n", 1);
}
-
return 0;
}
-static int batch_objects(int print_contents)
+static int batch_objects(struct batch_options *opt)
{
struct strbuf buf = STRBUF_INIT;
+ struct expand_data data;
+
+ if (!opt->format)
+ opt->format = "%(objectname) %(objecttype) %(objectsize)";
+
+ /*
+ * Expand once with our special mark_query flag, which will prime the
+ * object_info to be handed to sha1_object_info_extended for each
+ * object.
+ */
+ memset(&data, 0, sizeof(data));
+ data.mark_query = 1;
+ strbuf_expand(&buf, opt->format, expand_format, &data);
+ data.mark_query = 0;
+
+ /*
+ * If we are printing out the object, then always fill in the type,
+ * since we will want to decide whether or not to stream.
+ */
+ if (opt->print_contents)
+ data.info.typep = &data.type;
+
+ /*
+ * We are going to call get_sha1 on a potentially very large number of
+ * objects. In most large cases, these will be actual object sha1s. The
+ * cost to double-check that each one is not also a ref (just so we can
+ * warn) ends up dwarfing the actual cost of the object lookups
+ * themselves. We can work around it by just turning off the warning.
+ */
+ warn_on_object_refname_ambiguity = 0;
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
- int error = batch_one_object(buf.buf, print_contents);
+ int error;
+
+ if (data.split_on_whitespace) {
+ /*
+ * Split at first whitespace, tying off the beginning
+ * of the string and saving the remainder (or NULL) in
+ * data.rest.
+ */
+ char *p = strpbrk(buf.buf, " \t");
+ if (p) {
+ while (*p && strchr(" \t", *p))
+ *p++ = '\0';
+ }
+ data.rest = p;
+ }
+
+ error = batch_one_object(buf.buf, opt, &data);
if (error)
return error;
}
@@ -186,10 +338,29 @@ static int git_cat_file_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
+static int batch_option_callback(const struct option *opt,
+ const char *arg,
+ int unset)
+{
+ struct batch_options *bo = opt->value;
+
+ if (unset) {
+ memset(bo, 0, sizeof(*bo));
+ return 0;
+ }
+
+ bo->enabled = 1;
+ bo->print_contents = !strcmp(opt->long_name, "batch");
+ bo->format = arg;
+
+ return 0;
+}
+
int cmd_cat_file(int argc, const char **argv, const char *prefix)
{
- int opt = 0, batch = 0;
+ int opt = 0;
const char *exp_type = NULL, *obj_name = NULL;
+ struct batch_options batch = {0};
const struct option options[] = {
OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
@@ -200,12 +371,12 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
OPT_SET_INT(0, "textconv", &opt,
N_("for blob objects, run textconv on object's content"), 'c'),
- OPT_SET_INT(0, "batch", &batch,
- N_("show info and content of objects fed from the standard input"),
- BATCH),
- OPT_SET_INT(0, "batch-check", &batch,
- N_("show info about objects fed from the standard input"),
- BATCH_CHECK),
+ { OPTION_CALLBACK, 0, "batch", &batch, "format",
+ N_("show info and content of objects fed from the standard input"),
+ PARSE_OPT_OPTARG, batch_option_callback },
+ { OPTION_CALLBACK, 0, "batch-check", &batch, "format",
+ N_("show info about objects fed from the standard input"),
+ PARSE_OPT_OPTARG, batch_option_callback },
OPT_END()
};
@@ -222,19 +393,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
else
usage_with_options(cat_file_usage, options);
}
- if (!opt && !batch) {
+ if (!opt && !batch.enabled) {
if (argc == 2) {
exp_type = argv[0];
obj_name = argv[1];
} else
usage_with_options(cat_file_usage, options);
}
- if (batch && (opt || argc)) {
+ if (batch.enabled && (opt || argc)) {
usage_with_options(cat_file_usage, options);
}
- if (batch)
- return batch_objects(batch);
+ if (batch.enabled)
+ return batch_objects(&batch);
return cat_one_file(opt, exp_type, obj_name);
}
diff --git a/builtin/check-attr.c b/builtin/check-attr.c
index 075d01d30c..e9af7b2bfb 100644
--- a/builtin/check-attr.c
+++ b/builtin/check-attr.c
@@ -13,14 +13,14 @@ N_("git check-attr --stdin [-z] [-a | --all | attr...] < <list-of-paths>"),
NULL
};
-static int null_term_line;
+static int nul_term_line;
static const struct option check_attr_options[] = {
- OPT_BOOLEAN('a', "all", &all_attrs, N_("report all attributes set on file")),
- OPT_BOOLEAN(0, "cached", &cached_attrs, N_("use .gitattributes only from the index")),
- OPT_BOOLEAN(0 , "stdin", &stdin_paths, N_("read file names from stdin")),
- OPT_BOOLEAN('z', NULL, &null_term_line,
- N_("input paths are terminated by a null character")),
+ OPT_BOOL('a', "all", &all_attrs, N_("report all attributes set on file")),
+ OPT_BOOL(0, "cached", &cached_attrs, N_("use .gitattributes only from the index")),
+ OPT_BOOL(0 , "stdin", &stdin_paths, N_("read file names from stdin")),
+ OPT_BOOL('z', NULL, &nul_term_line,
+ N_("terminate input and output records by a NUL character")),
OPT_END()
};
@@ -38,8 +38,16 @@ static void output_attr(int cnt, struct git_attr_check *check,
else if (ATTR_UNSET(value))
value = "unspecified";
- quote_c_style(file, NULL, stdout, 0);
- printf(": %s: %s\n", git_attr_name(check[j].attr), value);
+ if (nul_term_line) {
+ printf("%s%c" /* path */
+ "%s%c" /* attrname */
+ "%s%c" /* attrvalue */,
+ file, 0, git_attr_name(check[j].attr), 0, value, 0);
+ } else {
+ quote_c_style(file, NULL, stdout, 0);
+ printf(": %s: %s\n", git_attr_name(check[j].attr), value);
+ }
+
}
}
@@ -65,7 +73,7 @@ static void check_attr_stdin_paths(const char *prefix, int cnt,
struct git_attr_check *check)
{
struct strbuf buf, nbuf;
- int line_termination = null_term_line ? 0 : '\n';
+ int line_termination = nul_term_line ? 0 : '\n';
strbuf_init(&buf, 0);
strbuf_init(&nbuf, 0);
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 854a88a056..594463a11b 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -5,38 +5,47 @@
#include "pathspec.h"
#include "parse-options.h"
-static int quiet, verbose, stdin_paths;
+static int quiet, verbose, stdin_paths, show_non_matching, no_index;
static const char * const check_ignore_usage[] = {
"git check-ignore [options] pathname...",
"git check-ignore [options] --stdin < <list-of-paths>",
NULL
};
-static int null_term_line;
+static int nul_term_line;
static const struct option check_ignore_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_GROUP(""),
- OPT_BOOLEAN(0, "stdin", &stdin_paths,
- N_("read file names from stdin")),
- OPT_BOOLEAN('z', NULL, &null_term_line,
- N_("input paths are terminated by a null character")),
+ OPT_BOOL(0, "stdin", &stdin_paths,
+ N_("read file names from stdin")),
+ OPT_BOOL('z', NULL, &nul_term_line,
+ N_("terminate input and output records by a NUL character")),
+ OPT_BOOL('n', "non-matching", &show_non_matching,
+ N_("show non-matching input paths")),
+ OPT_BOOL(0, "no-index", &no_index,
+ N_("ignore index when checking")),
OPT_END()
};
static void output_exclude(const char *path, struct exclude *exclude)
{
- char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : "";
- char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : "";
- if (!null_term_line) {
+ char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : "";
+ char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
+ if (!nul_term_line) {
if (!verbose) {
write_name_quoted(path, stdout, '\n');
} else {
- quote_c_style(exclude->el->src, NULL, stdout, 0);
- printf(":%d:%s%s%s\t",
- exclude->srcpos,
- bang, exclude->pattern, slash);
+ if (exclude) {
+ quote_c_style(exclude->el->src, NULL, stdout, 0);
+ printf(":%d:%s%s%s\t",
+ exclude->srcpos,
+ bang, exclude->pattern, slash);
+ }
+ else {
+ printf("::\t");
+ }
quote_c_style(path, NULL, stdout, 0);
fputc('\n', stdout);
}
@@ -44,70 +53,72 @@ static void output_exclude(const char *path, struct exclude *exclude)
if (!verbose) {
printf("%s%c", path, '\0');
} else {
- printf("%s%c%d%c%s%s%s%c%s%c",
- exclude->el->src, '\0',
- exclude->srcpos, '\0',
- bang, exclude->pattern, slash, '\0',
- path, '\0');
+ if (exclude)
+ printf("%s%c%d%c%s%s%s%c%s%c",
+ exclude->el->src, '\0',
+ exclude->srcpos, '\0',
+ bang, exclude->pattern, slash, '\0',
+ path, '\0');
+ else
+ printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0');
}
}
}
-static int check_ignore(const char *prefix, const char **pathspec)
+static int check_ignore(struct dir_struct *dir,
+ const char *prefix, int argc, const char **argv)
{
- struct dir_struct dir;
- const char *path, *full_path;
+ const char *full_path;
char *seen;
int num_ignored = 0, dtype = DT_UNKNOWN, i;
struct exclude *exclude;
+ struct pathspec pathspec;
- /* read_cache() is only necessary so we can watch out for submodules. */
- if (read_cache() < 0)
- die(_("index file corrupt"));
-
- memset(&dir, 0, sizeof(dir));
- setup_standard_excludes(&dir);
-
- if (!pathspec || !*pathspec) {
+ if (!argc) {
if (!quiet)
fprintf(stderr, "no pathspec given.\n");
return 0;
}
/*
+ * check-ignore just needs paths. Magic beyond :/ is really
+ * irrelevant.
+ */
+ parse_pathspec(&pathspec,
+ PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP,
+ PATHSPEC_SYMLINK_LEADING_PATH |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE |
+ PATHSPEC_KEEP_ORDER,
+ prefix, argv);
+
+ /*
* look for pathspecs matching entries in the index, since these
* should not be ignored, in order to be consistent with
* 'git status', 'git add' etc.
*/
- seen = find_pathspecs_matching_against_index(pathspec);
- for (i = 0; pathspec[i]; i++) {
- path = pathspec[i];
- full_path = prefix_path(prefix, prefix
- ? strlen(prefix) : 0, path);
- full_path = check_path_for_gitlink(full_path);
- die_if_path_beyond_symlink(full_path, prefix);
+ seen = find_pathspecs_matching_against_index(&pathspec);
+ for (i = 0; i < pathspec.nr; i++) {
+ full_path = pathspec.items[i].match;
+ exclude = NULL;
if (!seen[i]) {
- exclude = last_exclude_matching(&dir, full_path, &dtype);
- if (exclude) {
- if (!quiet)
- output_exclude(path, exclude);
- num_ignored++;
- }
+ exclude = last_exclude_matching(dir, full_path, &dtype);
}
+ if (!quiet && (exclude || show_non_matching))
+ output_exclude(pathspec.items[i].original, exclude);
+ if (exclude)
+ num_ignored++;
}
free(seen);
- clear_directory(&dir);
return num_ignored;
}
-static int check_ignore_stdin_paths(const char *prefix)
+static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
{
struct strbuf buf, nbuf;
- char **pathspec = NULL;
- size_t nr = 0, alloc = 0;
- int line_termination = null_term_line ? 0 : '\n';
- int num_ignored;
+ char *pathspec[2] = { NULL, NULL };
+ int line_termination = nul_term_line ? 0 : '\n';
+ int num_ignored = 0;
strbuf_init(&buf, 0);
strbuf_init(&nbuf, 0);
@@ -118,23 +129,20 @@ static int check_ignore_stdin_paths(const char *prefix)
die("line is badly quoted");
strbuf_swap(&buf, &nbuf);
}
- ALLOC_GROW(pathspec, nr + 1, alloc);
- pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf));
- strcpy(pathspec[nr++], buf.buf);
+ pathspec[0] = buf.buf;
+ num_ignored += check_ignore(dir, prefix,
+ 1, (const char **)pathspec);
+ maybe_flush_or_die(stdout, "check-ignore to stdout");
}
- ALLOC_GROW(pathspec, nr + 1, alloc);
- pathspec[nr] = NULL;
- num_ignored = check_ignore(prefix, (const char **)pathspec);
- maybe_flush_or_die(stdout, "attribute to stdout");
strbuf_release(&buf);
strbuf_release(&nbuf);
- free(pathspec);
return num_ignored;
}
int cmd_check_ignore(int argc, const char **argv, const char *prefix)
{
int num_ignored;
+ struct dir_struct dir;
git_config(git_default_config, NULL);
@@ -145,7 +153,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
if (argc > 0)
die(_("cannot specify pathnames with --stdin"));
} else {
- if (null_term_line)
+ if (nul_term_line)
die(_("-z only makes sense with --stdin"));
if (argc == 0)
die(_("no path specified"));
@@ -156,13 +164,24 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix)
if (verbose)
die(_("cannot have both --quiet and --verbose"));
}
+ if (show_non_matching && !verbose)
+ die(_("--non-matching is only valid with --verbose"));
+
+ /* read_cache() is only necessary so we can watch out for submodules. */
+ if (!no_index && read_cache() < 0)
+ die(_("index file corrupt"));
+
+ memset(&dir, 0, sizeof(dir));
+ setup_standard_excludes(&dir);
if (stdin_paths) {
- num_ignored = check_ignore_stdin_paths(prefix);
+ num_ignored = check_ignore_stdin_paths(&dir, prefix);
} else {
- num_ignored = check_ignore(prefix, argv);
+ num_ignored = check_ignore(&dir, prefix, argc, argv);
maybe_flush_or_die(stdout, "ignore to stdout");
}
+ clear_directory(&dir);
+
return !num_ignored;
}
diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c
new file mode 100644
index 0000000000..8f4d809bd8
--- /dev/null
+++ b/builtin/check-mailmap.c
@@ -0,0 +1,66 @@
+#include "builtin.h"
+#include "mailmap.h"
+#include "parse-options.h"
+#include "string-list.h"
+
+static int use_stdin;
+static const char * const check_mailmap_usage[] = {
+N_("git check-mailmap [options] <contact>..."),
+NULL
+};
+
+static const struct option check_mailmap_options[] = {
+ OPT_BOOL(0, "stdin", &use_stdin, N_("also read contacts from stdin")),
+ OPT_END()
+};
+
+static void check_mailmap(struct string_list *mailmap, const char *contact)
+{
+ const char *name, *mail;
+ size_t namelen, maillen;
+ struct ident_split ident;
+
+ if (split_ident_line(&ident, contact, strlen(contact)))
+ die(_("unable to parse contact: %s"), contact);
+
+ name = ident.name_begin;
+ namelen = ident.name_end - ident.name_begin;
+ mail = ident.mail_begin;
+ maillen = ident.mail_end - ident.mail_begin;
+
+ map_user(mailmap, &mail, &maillen, &name, &namelen);
+
+ if (namelen)
+ printf("%.*s ", (int)namelen, name);
+ printf("<%.*s>\n", (int)maillen, mail);
+}
+
+int cmd_check_mailmap(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct string_list mailmap = STRING_LIST_INIT_NODUP;
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, check_mailmap_options,
+ check_mailmap_usage, 0);
+ if (argc == 0 && !use_stdin)
+ die(_("no contacts specified"));
+
+ read_mailmap(&mailmap, NULL);
+
+ for (i = 0; i < argc; ++i)
+ check_mailmap(&mailmap, argv[i]);
+ maybe_flush_or_die(stdout, "stdout");
+
+ if (use_stdin) {
+ struct strbuf buf = STRBUF_INIT;
+ while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ check_mailmap(&mailmap, buf.buf);
+ maybe_flush_or_die(stdout, "stdout");
+ }
+ strbuf_release(&buf);
+ }
+
+ clear_mailmap(&mailmap);
+ return 0;
+}
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index b1feda7d5e..61e75eb60c 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -14,7 +14,7 @@
static int line_termination = '\n';
static int checkout_stage; /* default to checkout stage0 */
static int to_tempfile;
-static char topath[4][PATH_MAX + 1];
+static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
static struct checkout state;
@@ -183,12 +183,12 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
int prefix_length;
int force = 0, quiet = 0, not_new = 0;
struct option builtin_checkout_index_options[] = {
- OPT_BOOLEAN('a', "all", &all,
+ OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
OPT__FORCE(&force, N_("force overwrite of existing files")),
OPT__QUIET(&quiet,
N_("no warning for existing files and files not in index")),
- OPT_BOOLEAN('n', "no-create", &not_new,
+ OPT_BOOL('n', "no-create", &not_new,
N_("don't checkout new files")),
{ OPTION_CALLBACK, 'u', "index", &newfd, NULL,
N_("update stat information in the index file"),
@@ -196,9 +196,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
N_("paths are separated with NUL character"),
PARSE_OPT_NOARG, option_parse_z },
- OPT_BOOLEAN(0, "stdin", &read_from_stdin,
+ OPT_BOOL(0, "stdin", &read_from_stdin,
N_("read list of paths from the standard input")),
- OPT_BOOLEAN(0, "temp", &to_tempfile,
+ OPT_BOOL(0, "temp", &to_tempfile,
N_("write the content to temporary files")),
OPT_CALLBACK(0, "prefix", NULL, N_("string"),
N_("when creating files, prepend <string>"),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 81b4419da5..ada51fa70f 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -46,7 +46,7 @@ struct checkout_opts {
int branch_exists;
const char *prefix;
- const char **pathspec;
+ struct pathspec pathspec;
struct tree *source_tree;
};
@@ -83,12 +83,9 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
return 0;
}
-static int read_tree_some(struct tree *tree, const char **pathspec)
+static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
{
- struct pathspec ps;
- init_pathspec(&ps, pathspec);
- read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL);
- free_pathspec(&ps);
+ read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
@@ -97,7 +94,7 @@ static int read_tree_some(struct tree *tree, const char **pathspec)
return 0;
}
-static int skip_same_name(struct cache_entry *ce, int pos)
+static int skip_same_name(const struct cache_entry *ce, int pos)
{
while (++pos < active_nr &&
!strcmp(active_cache[pos]->name, ce->name))
@@ -105,7 +102,7 @@ static int skip_same_name(struct cache_entry *ce, int pos)
return pos;
}
-static int check_stage(int stage, struct cache_entry *ce, int pos)
+static int check_stage(int stage, const struct cache_entry *ce, int pos)
{
while (pos < active_nr &&
!strcmp(active_cache[pos]->name, ce->name)) {
@@ -119,7 +116,7 @@ static int check_stage(int stage, struct cache_entry *ce, int pos)
return error(_("path '%s' does not have their version"), ce->name);
}
-static int check_stages(unsigned stages, struct cache_entry *ce, int pos)
+static int check_stages(unsigned stages, const struct cache_entry *ce, int pos)
{
unsigned seen = 0;
const char *name = ce->name;
@@ -228,8 +225,6 @@ static int checkout_paths(const struct checkout_opts *opts,
int flag;
struct commit *head;
int errs = 0;
- int stage = opts->writeout_stage;
- int merge = opts->merge;
int newfd;
struct lock_file *lock_file;
@@ -257,20 +252,18 @@ static int checkout_paths(const struct checkout_opts *opts,
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=checkout",
- opts->pathspec);
+ &opts->pathspec);
lock_file = xcalloc(1, sizeof(struct lock_file));
newfd = hold_locked_index(lock_file, 1);
- if (read_cache_preload(opts->pathspec) < 0)
+ if (read_cache_preload(&opts->pathspec) < 0)
return error(_("corrupt index file"));
if (opts->source_tree)
- read_tree_some(opts->source_tree, opts->pathspec);
+ read_tree_some(opts->source_tree, &opts->pathspec);
- for (pos = 0; opts->pathspec[pos]; pos++)
- ;
- ps_matched = xcalloc(1, pos);
+ ps_matched = xcalloc(1, opts->pathspec.nr);
/*
* Make sure all pathspecs participated in locating the paths
@@ -304,12 +297,11 @@ static int checkout_paths(const struct checkout_opts *opts,
* match_pathspec() for _all_ entries when
* opts->source_tree != NULL.
*/
- if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce),
- 0, ps_matched))
+ if (ce_path_match(ce, &opts->pathspec, ps_matched))
ce->ce_flags |= CE_MATCHED;
}
- if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) {
+ if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
free(ps_matched);
return 1;
}
@@ -321,14 +313,14 @@ static int checkout_paths(const struct checkout_opts *opts,
/* Any unmerged paths? */
for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (ce->ce_flags & CE_MATCHED) {
if (!ce_stage(ce))
continue;
if (opts->force) {
warning(_("path '%s' is unmerged"), ce->name);
- } else if (stage) {
- errs |= check_stage(stage, ce, pos);
+ } else if (opts->writeout_stage) {
+ errs |= check_stage(opts->writeout_stage, ce, pos);
} else if (opts->merge) {
errs |= check_stages((1<<2) | (1<<3), ce, pos);
} else {
@@ -352,9 +344,9 @@ static int checkout_paths(const struct checkout_opts *opts,
errs |= checkout_entry(ce, &state, NULL);
continue;
}
- if (stage)
- errs |= checkout_stage(stage, ce, pos, &state);
- else if (merge)
+ if (opts->writeout_stage)
+ errs |= checkout_stage(opts->writeout_stage, ce, pos, &state);
+ else if (opts->merge)
errs |= checkout_merged(pos, &state);
pos = skip_same_name(ce, pos) - 1;
}
@@ -387,8 +379,8 @@ static void show_local_changes(struct object *head,
static void describe_detached_head(const char *msg, struct commit *commit)
{
struct strbuf sb = STRBUF_INIT;
- parse_commit(commit);
- pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
+ if (!parse_commit(commit))
+ pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
fprintf(stderr, "%s %s... %s\n", msg,
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
strbuf_release(&sb);
@@ -587,7 +579,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
struct branch_info *new)
{
struct strbuf msg = STRBUF_INIT;
- const char *old_desc;
+ const char *old_desc, *reflog_msg;
if (opts->new_branch) {
if (opts->new_orphan_branch) {
if (opts->new_branch_log && !log_all_ref_updates) {
@@ -620,8 +612,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
old_desc = old->name;
if (!old_desc && old->commit)
old_desc = sha1_to_hex(old->commit->object.sha1);
- strbuf_addf(&msg, "checkout: moving from %s to %s",
- old_desc ? old_desc : "(invalid)", new->name);
+
+ reflog_msg = getenv("GIT_REFLOG_ACTION");
+ if (!reflog_msg)
+ strbuf_addf(&msg, "checkout: moving from %s to %s",
+ old_desc ? old_desc : "(invalid)", new->name);
+ else
+ strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg));
if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
/* Nothing to do. */
@@ -679,12 +676,12 @@ static int add_pending_uninteresting_ref(const char *refname,
static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
{
- parse_commit(commit);
strbuf_addstr(sb, " ");
strbuf_addstr(sb,
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
strbuf_addch(sb, ' ');
- pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
+ if (!parse_commit(commit))
+ pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
strbuf_addch(sb, '\n');
}
@@ -783,7 +780,7 @@ static int switch_branches(const struct checkout_opts *opts,
if (!(flag & REF_ISSYMREF))
old.path = NULL;
- if (old.path && !prefixcmp(old.path, "refs/heads/"))
+ if (old.path && starts_with(old.path, "refs/heads/"))
old.name = old.path + strlen("refs/heads/");
if (!new->name) {
@@ -791,7 +788,7 @@ static int switch_branches(const struct checkout_opts *opts,
new->commit = old.commit;
if (!new->commit)
die(_("You are on a branch yet to be born"));
- parse_commit(new->commit);
+ parse_commit_or_die(new->commit);
}
ret = merge_working_tree(opts, &old, new, &writeout_error);
@@ -818,45 +815,50 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
return 0;
}
- if (!prefixcmp(var, "submodule."))
+ if (starts_with(var, "submodule."))
return parse_submodule_config_option(var, value);
return git_xmerge_config(var, value, NULL);
}
struct tracking_name_data {
- const char *name;
- char *remote;
+ /* const */ char *src_ref;
+ char *dst_ref;
+ unsigned char *dst_sha1;
int unique;
};
-static int check_tracking_name(const char *refname, const unsigned char *sha1,
- int flags, void *cb_data)
+static int check_tracking_name(struct remote *remote, void *cb_data)
{
struct tracking_name_data *cb = cb_data;
- const char *slash;
-
- if (prefixcmp(refname, "refs/remotes/"))
- return 0;
- slash = strchr(refname + 13, '/');
- if (!slash || strcmp(slash + 1, cb->name))
+ struct refspec query;
+ memset(&query, 0, sizeof(struct refspec));
+ query.src = cb->src_ref;
+ if (remote_find_tracking(remote, &query) ||
+ get_sha1(query.dst, cb->dst_sha1)) {
+ free(query.dst);
return 0;
- if (cb->remote) {
+ }
+ if (cb->dst_ref) {
+ free(query.dst);
cb->unique = 0;
return 0;
}
- cb->remote = xstrdup(refname);
+ cb->dst_ref = query.dst;
return 0;
}
-static const char *unique_tracking_name(const char *name)
+static const char *unique_tracking_name(const char *name, unsigned char *sha1)
{
- struct tracking_name_data cb_data = { NULL, NULL, 1 };
- cb_data.name = name;
- for_each_ref(check_tracking_name, &cb_data);
+ struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
+ char src_ref[PATH_MAX];
+ snprintf(src_ref, PATH_MAX, "refs/heads/%s", name);
+ cb_data.src_ref = src_ref;
+ cb_data.dst_sha1 = sha1;
+ for_each_remote(check_tracking_name, &cb_data);
if (cb_data.unique)
- return cb_data.remote;
- free(cb_data.remote);
+ return cb_data.dst_ref;
+ free(cb_data.dst_ref);
return NULL;
}
@@ -870,7 +872,9 @@ static int parse_branchname_arg(int argc, const char **argv,
int argcount = 0;
unsigned char branch_rev[20];
const char *arg;
- int has_dash_dash;
+ int dash_dash_pos;
+ int has_dash_dash = 0;
+ int i;
/*
* case 1: git checkout <ref> -- [<paths>]
@@ -882,20 +886,30 @@ static int parse_branchname_arg(int argc, const char **argv,
*
* everything after the '--' must be paths.
*
- * case 3: git checkout <something> [<paths>]
+ * case 3: git checkout <something> [--]
*
- * With no paths, if <something> is a commit, that is to
- * switch to the branch or detach HEAD at it. As a special case,
- * if <something> is A...B (missing A or B means HEAD but you can
- * omit at most one side), and if there is a unique merge base
- * between A and B, A...B names that merge base.
+ * (a) If <something> is a commit, that is to
+ * switch to the branch or detach HEAD at it. As a special case,
+ * if <something> is A...B (missing A or B means HEAD but you can
+ * omit at most one side), and if there is a unique merge base
+ * between A and B, A...B names that merge base.
*
- * With no paths, if <something> is _not_ a commit, no -t nor -b
- * was given, and there is a tracking branch whose name is
- * <something> in one and only one remote, then this is a short-hand
- * to fork local <something> from that remote-tracking branch.
+ * (b) If <something> is _not_ a commit, either "--" is present
+ * or <something> is not a path, no -t nor -b was given, and
+ * and there is a tracking branch whose name is <something>
+ * in one and only one remote, then this is a short-hand to
+ * fork local <something> from that remote-tracking branch.
*
- * Otherwise <something> shall not be ambiguous.
+ * (c) Otherwise, if "--" is present, treat it like case (1).
+ *
+ * (d) Otherwise :
+ * - if it's a reference, treat it like case (1)
+ * - else if it's a path, treat it like case (2)
+ * - else: fail.
+ *
+ * case 4: git checkout <something> <paths>
+ *
+ * The first argument must not be ambiguous.
* - If it's *only* a reference, treat it like case (1).
* - If it's only a path, treat it like case (2).
* - else: fail.
@@ -904,28 +918,59 @@ static int parse_branchname_arg(int argc, const char **argv,
if (!argc)
return 0;
- if (!strcmp(argv[0], "--")) /* case (2) */
- return 1;
-
arg = argv[0];
- has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
+ dash_dash_pos = -1;
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ dash_dash_pos = i;
+ break;
+ }
+ }
+ if (dash_dash_pos == 0)
+ return 1; /* case (2) */
+ else if (dash_dash_pos == 1)
+ has_dash_dash = 1; /* case (3) or (1) */
+ else if (dash_dash_pos >= 2)
+ die(_("only one reference expected, %d given."), dash_dash_pos);
if (!strcmp(arg, "-"))
arg = "@{-1}";
if (get_sha1_mb(arg, rev)) {
- if (has_dash_dash) /* case (1) */
- die(_("invalid reference: %s"), arg);
- if (dwim_new_local_branch_ok &&
- !check_filename(NULL, arg) &&
- argc == 1) {
- const char *remote = unique_tracking_name(arg);
- if (!remote || get_sha1(remote, rev))
- return argcount;
- *new_branch = arg;
- arg = remote;
- /* DWIMmed to create local branch */
- } else {
+ /*
+ * Either case (3) or (4), with <something> not being
+ * a commit, or an attempt to use case (1) with an
+ * invalid ref.
+ *
+ * It's likely an error, but we need to find out if
+ * we should auto-create the branch, case (3).(b).
+ */
+ int recover_with_dwim = dwim_new_local_branch_ok;
+
+ if (check_filename(NULL, arg) && !has_dash_dash)
+ recover_with_dwim = 0;
+ /*
+ * Accept "git checkout foo" and "git checkout foo --"
+ * as candidates for dwim.
+ */
+ if (!(argc == 1 && !has_dash_dash) &&
+ !(argc == 2 && has_dash_dash))
+ recover_with_dwim = 0;
+
+ if (recover_with_dwim) {
+ const char *remote = unique_tracking_name(arg, rev);
+ if (remote) {
+ *new_branch = arg;
+ arg = remote;
+ /* DWIMmed to create local branch, case (3).(b) */
+ } else {
+ recover_with_dwim = 0;
+ }
+ }
+
+ if (!recover_with_dwim) {
+ if (has_dash_dash)
+ die(_("invalid reference: %s"), arg);
return argcount;
}
}
@@ -949,13 +994,13 @@ static int parse_branchname_arg(int argc, const char **argv,
/* not a commit */
*source_tree = parse_tree_indirect(rev);
} else {
- parse_commit(new->commit);
+ parse_commit_or_die(new->commit);
*source_tree = new->commit->tree;
}
if (!*source_tree) /* case (1): want a tree */
die(_("reference is not a tree: %s"), arg);
- if (!has_dash_dash) {/* case (3 -> 1) */
+ if (!has_dash_dash) {/* case (3).(d) -> (1) */
/*
* Do not complain the most common case
* git checkout branch
@@ -992,7 +1037,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
static int checkout_branch(struct checkout_opts *opts,
struct branch_info *new)
{
- if (opts->pathspec)
+ if (opts->pathspec.nr)
die(_("paths cannot be used with switching branches"));
if (opts->patch_mode)
@@ -1046,8 +1091,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
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_BOOLEAN('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
- OPT_BOOLEAN(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")),
+ OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
+ OPT_BOOL(0, "detach", &opts.force_detach, N_("detach the 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")),
@@ -1056,16 +1101,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"),
3),
OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)")),
- OPT_BOOLEAN('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
- OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")),
+ OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
+ OPT_BOOL(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")),
OPT_STRING(0, "conflict", &conflict_style, N_("style"),
N_("conflict style (merge or diff3)")),
- OPT_BOOLEAN('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
+ 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")),
- { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
- N_("second guess 'git checkout no-such-branch'"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
+ N_("second guess 'git checkout no-such-branch'")),
OPT_END(),
};
@@ -1106,9 +1150,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
const char *argv0 = argv[0];
if (!argc || !strcmp(argv0, "--"))
die (_("--track needs a branch name"));
- if (!prefixcmp(argv0, "refs/"))
+ if (starts_with(argv0, "refs/"))
argv0 += 5;
- if (!prefixcmp(argv0, "remotes/"))
+ if (starts_with(argv0, "remotes/"))
argv0 += 8;
argv0 = strchr(argv0, '/');
if (!argv0 || !argv0[1])
@@ -1144,9 +1188,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
}
if (argc) {
- opts.pathspec = get_pathspec(prefix, argv);
+ parse_pathspec(&opts.pathspec, 0,
+ opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
+ prefix, argv);
- if (!opts.pathspec)
+ if (!opts.pathspec.nr)
die(_("invalid path specification"));
/*
@@ -1178,7 +1224,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
strbuf_release(&buf);
}
- if (opts.patch_mode || opts.pathspec)
+ if (opts.patch_mode || opts.pathspec.nr)
return checkout_paths(&opts, new.name);
else
return checkout_branch(&opts, &new);
diff --git a/builtin/clean.c b/builtin/clean.c
index 04e396b17a..114d7bf879 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -13,11 +13,17 @@
#include "refs.h"
#include "string-list.h"
#include "quote.h"
+#include "column.h"
+#include "color.h"
+#include "pathspec.h"
static int force = -1; /* unset */
+static int interactive;
+static struct string_list del_list = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
static const char *const builtin_clean_usage[] = {
- N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
+ N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
NULL
};
@@ -27,11 +33,112 @@ static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
static const char *msg_warn_remove_failed = N_("failed to remove %s");
+static int clean_use_color = -1;
+static char clean_colors[][COLOR_MAXLEN] = {
+ GIT_COLOR_RESET,
+ GIT_COLOR_NORMAL, /* PLAIN */
+ GIT_COLOR_BOLD_BLUE, /* PROMPT */
+ GIT_COLOR_BOLD, /* HEADER */
+ GIT_COLOR_BOLD_RED, /* HELP */
+ GIT_COLOR_BOLD_RED, /* ERROR */
+};
+enum color_clean {
+ CLEAN_COLOR_RESET = 0,
+ CLEAN_COLOR_PLAIN = 1,
+ CLEAN_COLOR_PROMPT = 2,
+ CLEAN_COLOR_HEADER = 3,
+ CLEAN_COLOR_HELP = 4,
+ CLEAN_COLOR_ERROR = 5,
+};
+
+#define MENU_OPTS_SINGLETON 01
+#define MENU_OPTS_IMMEDIATE 02
+#define MENU_OPTS_LIST_ONLY 04
+
+struct menu_opts {
+ const char *header;
+ const char *prompt;
+ int flags;
+};
+
+#define MENU_RETURN_NO_LOOP 10
+
+struct menu_item {
+ char hotkey;
+ const char *title;
+ int selected;
+ int (*fn)();
+};
+
+enum menu_stuff_type {
+ MENU_STUFF_TYPE_STRING_LIST = 1,
+ MENU_STUFF_TYPE_MENU_ITEM
+};
+
+struct menu_stuff {
+ enum menu_stuff_type type;
+ int nr;
+ void *stuff;
+};
+
+static int parse_clean_color_slot(const char *var)
+{
+ if (!strcasecmp(var, "reset"))
+ return CLEAN_COLOR_RESET;
+ if (!strcasecmp(var, "plain"))
+ return CLEAN_COLOR_PLAIN;
+ if (!strcasecmp(var, "prompt"))
+ return CLEAN_COLOR_PROMPT;
+ if (!strcasecmp(var, "header"))
+ return CLEAN_COLOR_HEADER;
+ if (!strcasecmp(var, "help"))
+ return CLEAN_COLOR_HELP;
+ if (!strcasecmp(var, "error"))
+ return CLEAN_COLOR_ERROR;
+ return -1;
+}
+
static int git_clean_config(const char *var, const char *value, void *cb)
{
- if (!strcmp(var, "clean.requireforce"))
+ if (starts_with(var, "column."))
+ return git_column_config(var, value, "clean", &colopts);
+
+ /* honors the color.interactive* config variables which also
+ applied in git-add--interactive and git-stash */
+ if (!strcmp(var, "color.interactive")) {
+ clean_use_color = git_config_colorbool(var, value);
+ return 0;
+ }
+ if (starts_with(var, "color.interactive.")) {
+ int slot = parse_clean_color_slot(var +
+ strlen("color.interactive."));
+ if (slot < 0)
+ return 0;
+ if (!value)
+ return config_error_nonbool(var);
+ color_parse(value, var, clean_colors[slot]);
+ return 0;
+ }
+
+ if (!strcmp(var, "clean.requireforce")) {
force = !git_config_bool(var, value);
- return git_default_config(var, value, cb);
+ return 0;
+ }
+
+ /* inspect the color.ui config variable and others */
+ return git_color_default_config(var, value, cb);
+}
+
+static const char *clean_get_color(enum color_clean ix)
+{
+ if (want_color(clean_use_color))
+ return clean_colors[ix];
+ return "";
+}
+
+static void clean_print_color(enum color_clean ix)
+{
+ printf("%s", clean_get_color(ix));
}
static int exclude_cb(const struct option *opt, const char *arg, int unset)
@@ -47,7 +154,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
DIR *dir;
struct strbuf quoted = STRBUF_INIT;
struct dirent *e;
- int res = 0, ret = 0, gone = 1, original_len = path->len, len, i;
+ int res = 0, ret = 0, gone = 1, original_len = path->len, len;
unsigned char submodule_head[20];
struct string_list dels = STRING_LIST_INIT_DUP;
@@ -56,7 +163,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
if (!quiet) {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
quoted.buf);
}
@@ -70,7 +177,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
/* an empty dir could be removed even if it is unreadble */
res = dry_run ? 0 : rmdir(path->buf);
if (res) {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
warning(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
}
@@ -94,7 +201,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, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
string_list_append(&dels, quoted.buf);
} else
*dir_gone = 0;
@@ -102,10 +209,10 @@ 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, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
string_list_append(&dels, quoted.buf);
} else {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
warning(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = 1;
@@ -127,7 +234,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if (!res)
*dir_gone = 1;
else {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
warning(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = 1;
@@ -135,6 +242,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
}
if (!*dir_gone && !quiet) {
+ int i;
for (i = 0; i < dels.nr; i++)
printf(dry_run ? _(msg_would_remove) : _(msg_remove), dels.items[i].string);
}
@@ -142,30 +250,638 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
return ret;
}
+static void pretty_print_dels(void)
+{
+ struct string_list list = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ struct strbuf buf = STRBUF_INIT;
+ const char *qname;
+ struct column_options copts;
+
+ for_each_string_list_item(item, &del_list) {
+ qname = quote_path_relative(item->string, NULL, &buf);
+ string_list_append(&list, qname);
+ }
+
+ /*
+ * always enable column display, we only consult column.*
+ * about layout strategy and stuff
+ */
+ colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+ memset(&copts, 0, sizeof(copts));
+ copts.indent = " ";
+ copts.padding = 2;
+ print_columns(&list, colopts, &copts);
+ strbuf_release(&buf);
+ string_list_clear(&list, 0);
+}
+
+static void pretty_print_menus(struct string_list *menu_list)
+{
+ unsigned int local_colopts = 0;
+ struct column_options copts;
+
+ local_colopts = COL_ENABLED | COL_ROW;
+ memset(&copts, 0, sizeof(copts));
+ copts.indent = " ";
+ copts.padding = 2;
+ print_columns(menu_list, local_colopts, &copts);
+}
+
+static void prompt_help_cmd(int singleton)
+{
+ clean_print_color(CLEAN_COLOR_HELP);
+ printf_ln(singleton ?
+ _("Prompt help:\n"
+ "1 - select a numbered item\n"
+ "foo - select item based on unique prefix\n"
+ " - (empty) select nothing") :
+ _("Prompt help:\n"
+ "1 - select a single item\n"
+ "3-5 - select a range of items\n"
+ "2-3,6-9 - select multiple ranges\n"
+ "foo - select item based on unique prefix\n"
+ "-... - unselect specified items\n"
+ "* - choose all items\n"
+ " - (empty) finish selecting"));
+ clean_print_color(CLEAN_COLOR_RESET);
+}
+
+/*
+ * display menu stuff with number prefix and hotkey highlight
+ */
+static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
+{
+ struct string_list menu_list = STRING_LIST_INIT_DUP;
+ struct strbuf menu = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct menu_item *menu_item;
+ struct string_list_item *string_list_item;
+ int i;
+
+ switch (stuff->type) {
+ default:
+ die("Bad type of menu_staff when print menu");
+ case MENU_STUFF_TYPE_MENU_ITEM:
+ menu_item = (struct menu_item *)stuff->stuff;
+ for (i = 0; i < stuff->nr; i++, menu_item++) {
+ const char *p;
+ int highlighted = 0;
+
+ p = menu_item->title;
+ if ((*chosen)[i] < 0)
+ (*chosen)[i] = menu_item->selected ? 1 : 0;
+ strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1);
+ for (; *p; p++) {
+ if (!highlighted && *p == menu_item->hotkey) {
+ strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));
+ strbuf_addch(&menu, *p);
+ strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));
+ highlighted = 1;
+ } else {
+ strbuf_addch(&menu, *p);
+ }
+ }
+ string_list_append(&menu_list, menu.buf);
+ strbuf_reset(&menu);
+ }
+ break;
+ case MENU_STUFF_TYPE_STRING_LIST:
+ i = 0;
+ for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) {
+ if ((*chosen)[i] < 0)
+ (*chosen)[i] = 0;
+ strbuf_addf(&menu, "%s%2d: %s",
+ (*chosen)[i] ? "*" : " ", i+1, string_list_item->string);
+ string_list_append(&menu_list, menu.buf);
+ strbuf_reset(&menu);
+ i++;
+ }
+ break;
+ }
+
+ pretty_print_menus(&menu_list);
+
+ strbuf_release(&menu);
+ strbuf_release(&buf);
+ string_list_clear(&menu_list, 0);
+}
+
+static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
+{
+ struct menu_item *menu_item;
+ struct string_list_item *string_list_item;
+ int i, len, found = 0;
+
+ len = strlen(choice);
+ switch (menu_stuff->type) {
+ default:
+ die("Bad type of menu_stuff when parse choice");
+ case MENU_STUFF_TYPE_MENU_ITEM:
+
+ menu_item = (struct menu_item *)menu_stuff->stuff;
+ for (i = 0; i < menu_stuff->nr; i++, menu_item++) {
+ if (len == 1 && *choice == menu_item->hotkey) {
+ found = i + 1;
+ break;
+ }
+ if (!strncasecmp(choice, menu_item->title, len)) {
+ if (found) {
+ if (len == 1) {
+ /* continue for hotkey matching */
+ found = -1;
+ } else {
+ found = 0;
+ break;
+ }
+ } else {
+ found = i + 1;
+ }
+ }
+ }
+ break;
+ case MENU_STUFF_TYPE_STRING_LIST:
+ string_list_item = ((struct string_list *)menu_stuff->stuff)->items;
+ for (i = 0; i < menu_stuff->nr; i++, string_list_item++) {
+ if (!strncasecmp(choice, string_list_item->string, len)) {
+ if (found) {
+ found = 0;
+ break;
+ }
+ found = i + 1;
+ }
+ }
+ break;
+ }
+ return found;
+}
+
+
+/*
+ * Parse user input, and return choice(s) for menu (menu_stuff).
+ *
+ * Input
+ * (for single choice)
+ * 1 - select a numbered item
+ * foo - select item based on menu title
+ * - (empty) select nothing
+ *
+ * (for multiple choice)
+ * 1 - select a single item
+ * 3-5 - select a range of items
+ * 2-3,6-9 - select multiple ranges
+ * foo - select item based on menu title
+ * -... - unselect specified items
+ * * - choose all items
+ * - (empty) finish selecting
+ *
+ * The parse result will be saved in array **chosen, and
+ * return number of total selections.
+ */
+static int parse_choice(struct menu_stuff *menu_stuff,
+ int is_single,
+ struct strbuf input,
+ int **chosen)
+{
+ struct strbuf **choice_list, **ptr;
+ int nr = 0;
+ int i;
+
+ if (is_single) {
+ choice_list = strbuf_split_max(&input, '\n', 0);
+ } else {
+ char *p = input.buf;
+ do {
+ if (*p == ',')
+ *p = ' ';
+ } while (*p++);
+ choice_list = strbuf_split_max(&input, ' ', 0);
+ }
+
+ for (ptr = choice_list; *ptr; ptr++) {
+ char *p;
+ int choose = 1;
+ int bottom = 0, top = 0;
+ int is_range, is_number;
+
+ strbuf_trim(*ptr);
+ if (!(*ptr)->len)
+ continue;
+
+ /* Input that begins with '-'; unchoose */
+ if (*(*ptr)->buf == '-') {
+ choose = 0;
+ strbuf_remove((*ptr), 0, 1);
+ }
+
+ is_range = 0;
+ is_number = 1;
+ for (p = (*ptr)->buf; *p; p++) {
+ if ('-' == *p) {
+ if (!is_range) {
+ is_range = 1;
+ is_number = 0;
+ } else {
+ is_number = 0;
+ is_range = 0;
+ break;
+ }
+ } else if (!isdigit(*p)) {
+ is_number = 0;
+ is_range = 0;
+ break;
+ }
+ }
+
+ if (is_number) {
+ bottom = atoi((*ptr)->buf);
+ top = bottom;
+ } else if (is_range) {
+ bottom = atoi((*ptr)->buf);
+ /* a range can be specified like 5-7 or 5- */
+ if (!*(strchr((*ptr)->buf, '-') + 1))
+ top = menu_stuff->nr;
+ else
+ top = atoi(strchr((*ptr)->buf, '-') + 1);
+ } else if (!strcmp((*ptr)->buf, "*")) {
+ bottom = 1;
+ top = menu_stuff->nr;
+ } else {
+ bottom = find_unique((*ptr)->buf, menu_stuff);
+ top = bottom;
+ }
+
+ if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||
+ (is_single && bottom != top)) {
+ clean_print_color(CLEAN_COLOR_ERROR);
+ printf_ln(_("Huh (%s)?"), (*ptr)->buf);
+ clean_print_color(CLEAN_COLOR_RESET);
+ continue;
+ }
+
+ for (i = bottom; i <= top; i++)
+ (*chosen)[i-1] = choose;
+ }
+
+ strbuf_list_free(choice_list);
+
+ for (i = 0; i < menu_stuff->nr; i++)
+ nr += (*chosen)[i];
+ return nr;
+}
+
+/*
+ * Implement a git-add-interactive compatible UI, which is borrowed
+ * from git-add--interactive.perl.
+ *
+ * Return value:
+ *
+ * - Return an array of integers
+ * - , and it is up to you to free the allocated memory.
+ * - The array ends with EOF.
+ * - If user pressed CTRL-D (i.e. EOF), no selection returned.
+ */
+static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
+{
+ struct strbuf choice = STRBUF_INIT;
+ int *chosen, *result;
+ int nr = 0;
+ int eof = 0;
+ int i;
+
+ chosen = xmalloc(sizeof(int) * stuff->nr);
+ /* set chosen as uninitialized */
+ for (i = 0; i < stuff->nr; i++)
+ chosen[i] = -1;
+
+ for (;;) {
+ if (opts->header) {
+ printf_ln("%s%s%s",
+ clean_get_color(CLEAN_COLOR_HEADER),
+ _(opts->header),
+ clean_get_color(CLEAN_COLOR_RESET));
+ }
+
+ /* chosen will be initialized by print_highlight_menu_stuff */
+ print_highlight_menu_stuff(stuff, &chosen);
+
+ if (opts->flags & MENU_OPTS_LIST_ONLY)
+ break;
+
+ if (opts->prompt) {
+ printf("%s%s%s%s",
+ clean_get_color(CLEAN_COLOR_PROMPT),
+ _(opts->prompt),
+ opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ",
+ clean_get_color(CLEAN_COLOR_RESET));
+ }
+
+ if (strbuf_getline(&choice, stdin, '\n') != EOF) {
+ strbuf_trim(&choice);
+ } else {
+ eof = 1;
+ break;
+ }
+
+ /* help for prompt */
+ if (!strcmp(choice.buf, "?")) {
+ prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON);
+ continue;
+ }
+
+ /* for a multiple-choice menu, press ENTER (empty) will return back */
+ if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len)
+ break;
+
+ nr = parse_choice(stuff,
+ opts->flags & MENU_OPTS_SINGLETON,
+ choice,
+ &chosen);
+
+ if (opts->flags & MENU_OPTS_SINGLETON) {
+ if (nr)
+ break;
+ } else if (opts->flags & MENU_OPTS_IMMEDIATE) {
+ break;
+ }
+ }
+
+ if (eof) {
+ result = xmalloc(sizeof(int));
+ *result = EOF;
+ } else {
+ int j = 0;
+
+ /*
+ * recalculate nr, if return back from menu directly with
+ * default selections.
+ */
+ if (!nr) {
+ for (i = 0; i < stuff->nr; i++)
+ nr += chosen[i];
+ }
+
+ result = xmalloc(sizeof(int) * (nr + 1));
+ memset(result, 0, sizeof(int) * (nr + 1));
+ for (i = 0; i < stuff->nr && j < nr; i++) {
+ if (chosen[i])
+ result[j++] = i;
+ }
+ result[j] = EOF;
+ }
+
+ free(chosen);
+ strbuf_release(&choice);
+ return result;
+}
+
+static int clean_cmd(void)
+{
+ return MENU_RETURN_NO_LOOP;
+}
+
+static int filter_by_patterns_cmd(void)
+{
+ struct dir_struct dir;
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf **ignore_list;
+ struct string_list_item *item;
+ struct exclude_list *el;
+ int changed = -1, i;
+
+ for (;;) {
+ if (!del_list.nr)
+ break;
+
+ if (changed)
+ pretty_print_dels();
+
+ clean_print_color(CLEAN_COLOR_PROMPT);
+ printf(_("Input ignore patterns>> "));
+ clean_print_color(CLEAN_COLOR_RESET);
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF)
+ strbuf_trim(&confirm);
+ else
+ 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");
+ ignore_list = strbuf_split_max(&confirm, ' ', 0);
+
+ for (i = 0; ignore_list[i]; i++) {
+ strbuf_trim(ignore_list[i]);
+ if (!ignore_list[i]->len)
+ continue;
+
+ add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1));
+ }
+
+ changed = 0;
+ for_each_string_list_item(item, &del_list) {
+ int dtype = DT_UNKNOWN;
+
+ if (is_excluded(&dir, item->string, &dtype)) {
+ *item->string = '\0';
+ changed++;
+ }
+ }
+
+ if (changed) {
+ string_list_remove_empty_items(&del_list, 0);
+ } else {
+ clean_print_color(CLEAN_COLOR_ERROR);
+ printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
+ clean_print_color(CLEAN_COLOR_RESET);
+ }
+
+ strbuf_list_free(ignore_list);
+ clear_directory(&dir);
+ }
+
+ strbuf_release(&confirm);
+ return 0;
+}
+
+static int select_by_numbers_cmd(void)
+{
+ struct menu_opts menu_opts;
+ struct menu_stuff menu_stuff;
+ struct string_list_item *items;
+ int *chosen;
+ int i, j;
+
+ menu_opts.header = NULL;
+ menu_opts.prompt = N_("Select items to delete");
+ menu_opts.flags = 0;
+
+ menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST;
+ menu_stuff.stuff = &del_list;
+ menu_stuff.nr = del_list.nr;
+
+ chosen = list_and_choose(&menu_opts, &menu_stuff);
+ items = del_list.items;
+ for (i = 0, j = 0; i < del_list.nr; i++) {
+ if (i < chosen[j]) {
+ *(items[i].string) = '\0';
+ } else if (i == chosen[j]) {
+ /* delete selected item */
+ j++;
+ continue;
+ } else {
+ /* end of chosen (chosen[j] == EOF), won't delete */
+ *(items[i].string) = '\0';
+ }
+ }
+
+ string_list_remove_empty_items(&del_list, 0);
+
+ free(chosen);
+ return 0;
+}
+
+static int ask_each_cmd(void)
+{
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list_item *item;
+ const char *qname;
+ int changed = 0, eof = 0;
+
+ for_each_string_list_item(item, &del_list) {
+ /* Ctrl-D should stop removing files */
+ if (!eof) {
+ qname = quote_path_relative(item->string, NULL, &buf);
+ printf(_("remove %s? "), qname);
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+ strbuf_trim(&confirm);
+ } else {
+ putchar('\n');
+ eof = 1;
+ }
+ }
+ if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) {
+ *item->string = '\0';
+ changed++;
+ }
+ }
+
+ if (changed)
+ string_list_remove_empty_items(&del_list, 0);
+
+ strbuf_release(&buf);
+ strbuf_release(&confirm);
+ return MENU_RETURN_NO_LOOP;
+}
+
+static int quit_cmd(void)
+{
+ string_list_clear(&del_list, 0);
+ printf_ln(_("Bye."));
+ return MENU_RETURN_NO_LOOP;
+}
+
+static int help_cmd(void)
+{
+ clean_print_color(CLEAN_COLOR_HELP);
+ printf_ln(_(
+ "clean - start cleaning\n"
+ "filter by pattern - exclude items from deletion\n"
+ "select by numbers - select items to be deleted by numbers\n"
+ "ask each - confirm each deletion (like \"rm -i\")\n"
+ "quit - stop cleaning\n"
+ "help - this screen\n"
+ "? - help for prompt selection"
+ ));
+ clean_print_color(CLEAN_COLOR_RESET);
+ return 0;
+}
+
+static void interactive_main_loop(void)
+{
+ while (del_list.nr) {
+ struct menu_opts menu_opts;
+ struct menu_stuff menu_stuff;
+ struct menu_item menus[] = {
+ {'c', "clean", 0, clean_cmd},
+ {'f', "filter by pattern", 0, filter_by_patterns_cmd},
+ {'s', "select by numbers", 0, select_by_numbers_cmd},
+ {'a', "ask each", 0, ask_each_cmd},
+ {'q', "quit", 0, quit_cmd},
+ {'h', "help", 0, help_cmd},
+ };
+ int *chosen;
+
+ menu_opts.header = N_("*** Commands ***");
+ menu_opts.prompt = N_("What now");
+ menu_opts.flags = MENU_OPTS_SINGLETON;
+
+ menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;
+ menu_stuff.stuff = menus;
+ menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);
+
+ clean_print_color(CLEAN_COLOR_HEADER);
+ printf_ln(Q_("Would remove the following item:",
+ "Would remove the following items:",
+ del_list.nr));
+ clean_print_color(CLEAN_COLOR_RESET);
+
+ pretty_print_dels();
+
+ chosen = list_and_choose(&menu_opts, &menu_stuff);
+
+ if (*chosen != EOF) {
+ int ret;
+ ret = menus[*chosen].fn();
+ if (ret != MENU_RETURN_NO_LOOP) {
+ free(chosen);
+ chosen = NULL;
+ if (!del_list.nr) {
+ clean_print_color(CLEAN_COLOR_ERROR);
+ printf_ln(_("No more files to clean, exiting."));
+ clean_print_color(CLEAN_COLOR_RESET);
+ break;
+ }
+ continue;
+ }
+ } else {
+ quit_cmd();
+ }
+
+ free(chosen);
+ chosen = NULL;
+ break;
+ }
+}
+
int cmd_clean(int argc, const char **argv, const char *prefix)
{
int i, res;
int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
- struct strbuf directory = STRBUF_INIT;
+ struct strbuf abs_path = STRBUF_INIT;
struct dir_struct dir;
- static const char **pathspec;
+ struct pathspec pathspec;
struct strbuf buf = STRBUF_INIT;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
struct exclude_list *el;
+ struct string_list_item *item;
const char *qname;
- char *seen = NULL;
struct option options[] = {
OPT__QUIET(&quiet, N_("do not print names of files removed")),
OPT__DRY_RUN(&dry_run, N_("dry run")),
OPT__FORCE(&force, N_("force")),
- OPT_BOOLEAN('d', NULL, &remove_directories,
+ 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_BOOLEAN('x', NULL, &ignored, N_("remove ignored files, too")),
- OPT_BOOLEAN('X', NULL, &ignored_only,
+ OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
+ OPT_BOOL('X', NULL, &ignored_only,
N_("remove only ignored files")),
OPT_END()
};
@@ -186,12 +902,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (ignored && ignored_only)
die(_("-x and -X cannot be used together"));
- if (!dry_run && !force) {
+ if (!interactive && !dry_run && !force) {
if (config_set)
- die(_("clean.requireForce set to true and neither -n nor -f given; "
+ die(_("clean.requireForce set to true and neither -i, -n nor -f given; "
"refusing to clean"));
else
- die(_("clean.requireForce defaults to true and neither -n nor -f given; "
+ die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; "
"refusing to clean"));
}
@@ -210,80 +926,83 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
for (i = 0; i < exclude_list.nr; i++)
add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
- pathspec = get_pathspec(prefix, argv);
-
- fill_directory(&dir, pathspec);
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD,
+ prefix, argv);
- if (pathspec)
- seen = xmalloc(argc > 0 ? argc : 1);
+ fill_directory(&dir, &pathspec);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
- int len, pos;
int matches = 0;
- struct cache_entry *ce;
struct stat st;
+ const char *rel;
- /*
- * Remove the '/' at the end that directory
- * walking adds for directory entries.
- */
- len = ent->len;
- if (len && ent->name[len-1] == '/')
- len--;
- pos = cache_name_pos(ent->name, len);
- if (0 <= pos)
- continue; /* exact match */
- pos = -pos - 1;
- if (pos < active_nr) {
- ce = active_cache[pos];
- if (ce_namelen(ce) == len &&
- !memcmp(ce->name, ent->name, len))
- continue; /* Yup, this one exists unmerged */
+ if (!cache_name_is_other(ent->name, ent->len))
+ continue;
+
+ if (lstat(ent->name, &st))
+ die_errno("Cannot lstat '%s'", ent->name);
+
+ if (pathspec.nr)
+ matches = dir_path_match(ent, &pathspec, 0, NULL);
+
+ if (S_ISDIR(st.st_mode)) {
+ if (remove_directories || (matches == MATCHED_EXACTLY)) {
+ rel = relative_path(ent->name, prefix, &buf);
+ string_list_append(&del_list, rel);
+ }
+ } else {
+ if (pathspec.nr && !matches)
+ continue;
+ rel = relative_path(ent->name, prefix, &buf);
+ string_list_append(&del_list, rel);
}
+ }
+
+ if (interactive && del_list.nr > 0)
+ interactive_main_loop();
+
+ for_each_string_list_item(item, &del_list) {
+ struct stat st;
+
+ if (prefix)
+ strbuf_addstr(&abs_path, prefix);
+
+ strbuf_addstr(&abs_path, item->string);
/*
* we might have removed this as part of earlier
* recursive directory removal, so lstat() here could
* fail with ENOENT.
*/
- if (lstat(ent->name, &st))
+ if (lstat(abs_path.buf, &st))
continue;
- if (pathspec) {
- memset(seen, 0, argc > 0 ? argc : 1);
- matches = match_pathspec(pathspec, ent->name, len,
- 0, seen);
- }
-
if (S_ISDIR(st.st_mode)) {
- strbuf_addstr(&directory, ent->name);
- if (remove_directories || (matches == MATCHED_EXACTLY)) {
- if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))
- errors++;
- if (gone && !quiet) {
- qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
- printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
- }
+ if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
+ errors++;
+ if (gone && !quiet) {
+ qname = quote_path_relative(item->string, NULL, &buf);
+ printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
- strbuf_reset(&directory);
} else {
- if (pathspec && !matches)
- continue;
- res = dry_run ? 0 : unlink(ent->name);
+ res = dry_run ? 0 : unlink(abs_path.buf);
if (res) {
- qname = quote_path_relative(ent->name, -1, &buf, prefix);
+ qname = quote_path_relative(item->string, NULL, &buf);
warning(_(msg_warn_remove_failed), qname);
errors++;
} else if (!quiet) {
- qname = quote_path_relative(ent->name, -1, &buf, prefix);
+ qname = quote_path_relative(item->string, NULL, &buf);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
}
+ strbuf_reset(&abs_path);
}
- free(seen);
- strbuf_release(&directory);
+ strbuf_release(&abs_path);
+ strbuf_release(&buf);
+ string_list_clear(&del_list, 0);
string_list_clear(&exclude_list, 0);
return (errors != 0);
}
diff --git a/builtin/clone.c b/builtin/clone.c
index 58fee9874f..43e772ccdb 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -18,7 +18,6 @@
#include "transport.h"
#include "strbuf.h"
#include "dir.h"
-#include "pack-refs.h"
#include "sigchain.h"
#include "branch.h"
#include "remote.h"
@@ -63,23 +62,22 @@ static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
OPT_BOOL(0, "progress", &option_progress,
N_("force progress reporting")),
- OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
- N_("don't create a checkout")),
- OPT_BOOLEAN(0, "bare", &option_bare, N_("create a bare repository")),
- { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL,
- N_("create a bare repository"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- OPT_BOOLEAN(0, "mirror", &option_mirror,
- N_("create a mirror repository (implies bare)")),
+ 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")),
+ OPT_HIDDEN_BOOL(0, "naked", &option_bare,
+ N_("create a bare repository")),
+ OPT_BOOL(0, "mirror", &option_mirror,
+ N_("create a mirror repository (implies bare)")),
OPT_BOOL('l', "local", &option_local,
N_("to clone from a local repository")),
- OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
+ OPT_BOOL(0, "no-hardlinks", &option_no_hardlinks,
N_("don't use local hardlinks, always copy")),
- OPT_BOOLEAN('s', "shared", &option_shared,
+ OPT_BOOL('s', "shared", &option_shared,
N_("setup as shared repository")),
- OPT_BOOLEAN(0, "recursive", &option_recursive,
+ OPT_BOOL(0, "recursive", &option_recursive,
N_("initialize submodules in the clone")),
- OPT_BOOLEAN(0, "recurse-submodules", &option_recursive,
+ OPT_BOOL(0, "recurse-submodules", &option_recursive,
N_("initialize submodules in the clone")),
OPT_STRING(0, "template", &option_template, N_("template-directory"),
N_("directory from which templates will be used")),
@@ -254,6 +252,12 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
die(_("reference repository '%s' is not a local repository."),
item->string);
+ if (!access(mkpath("%s/shallow", ref_git), F_OK))
+ die(_("reference repository '%s' is shallow"), item->string);
+
+ if (!access(mkpath("%s/info/grafts", ref_git), F_OK))
+ die(_("reference repository '%s' is grafted"), item->string);
+
strbuf_addf(&alternate, "%s/objects", ref_git);
add_to_alternates_file(alternate.buf);
strbuf_release(&alternate);
@@ -381,13 +385,13 @@ static void clone_local(const char *src_repo, const char *dest_repo)
}
if (0 <= option_verbosity)
- printf(_("done.\n"));
+ fprintf(stderr, _("done.\n"));
}
static const char *junk_work_tree;
static const char *junk_git_dir;
static pid_t junk_pid;
-enum {
+static enum {
JUNK_LEAVE_NONE,
JUNK_LEAVE_REPO,
JUNK_LEAVE_ALL
@@ -494,22 +498,25 @@ static void write_remote_refs(const struct ref *local_refs)
{
const struct ref *r;
+ lock_packed_refs(LOCK_DIE_ON_ERROR);
+
for (r = local_refs; r; r = r->next) {
if (!r->peer_ref)
continue;
add_packed_ref(r->peer_ref->name, r->old_sha1);
}
- pack_refs(PACK_REFS_ALL);
+ if (commit_packed_refs())
+ die_errno("unable to overwrite old ref-pack file");
}
static void write_followtags(const struct ref *refs, const char *msg)
{
const struct ref *ref;
for (ref = refs; ref; ref = ref->next) {
- if (prefixcmp(ref->name, "refs/tags/"))
+ if (!starts_with(ref->name, "refs/tags/"))
continue;
- if (!suffixcmp(ref->name, "^{}"))
+ if (ends_with(ref->name, "^{}"))
continue;
if (!has_sha1_file(ref->old_sha1))
continue;
@@ -542,12 +549,21 @@ static void update_remote_refs(const struct ref *refs,
const struct ref *mapped_refs,
const struct ref *remote_head_points_at,
const char *branch_top,
- const char *msg)
+ const char *msg,
+ struct transport *transport,
+ int check_connectivity)
{
const struct ref *rm = mapped_refs;
- if (check_everything_connected(iterate_ref_map, 0, &rm))
- die(_("remote did not send all necessary objects"));
+ if (check_connectivity) {
+ if (transport->progress)
+ fprintf(stderr, _("Checking connectivity... "));
+ if (check_everything_connected_with_transport(iterate_ref_map,
+ 0, &rm, transport))
+ die(_("remote did not send all necessary objects"));
+ if (transport->progress)
+ fprintf(stderr, _("done.\n"));
+ }
if (refs) {
write_remote_refs(mapped_refs);
@@ -568,7 +584,7 @@ static void update_remote_refs(const struct ref *refs,
static void update_head(const struct ref *our, const struct ref *remote,
const char *msg)
{
- if (our && !prefixcmp(our->name, "refs/heads/")) {
+ if (our && starts_with(our->name, "refs/heads/")) {
/* Local default branch link */
create_symref("HEAD", our->name, NULL);
if (!option_bare) {
@@ -615,7 +631,7 @@ static int checkout(void)
if (advice_detached_head)
detach_advice(sha1_to_hex(sha1));
} else {
- if (prefixcmp(head, "refs/heads/"))
+ if (!starts_with(head, "refs/heads/"))
die(_("HEAD not found below refs/heads!"));
}
free(head);
@@ -693,7 +709,7 @@ static void write_refspec_config(const char* src_ref_prefix,
/*
* otherwise, the next "git fetch" will
* simply fetch from HEAD without updating
- * any remote tracking branch, which is what
+ * any remote-tracking branch, which is what
* we want.
*/
} else {
@@ -781,8 +797,21 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
else
repo = repo_name;
is_local = option_local != 0 && path && !is_bundle;
- if (is_local && option_depth)
- warning(_("--depth is ignored in local clones; use file:// instead."));
+ if (is_local) {
+ if (option_depth)
+ warning(_("--depth is ignored in local clones; use file:// instead."));
+ if (!access(mkpath("%s/shallow", path), F_OK)) {
+ if (option_local > 0)
+ warning(_("source repository is shallow, ignoring --local"));
+ is_local = 0;
+ }
+ }
+ if (option_local > 0 && !is_local)
+ warning(_("--local is ignored"));
+
+ /* no need to be strict, transport_set_option() will validate it again */
+ if (option_depth && atoi(option_depth) < 1)
+ die(_("depth %s is not a positive number"), option_depth);
if (argc == 2)
dir = xstrdup(argv[1]);
@@ -837,9 +866,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (0 <= option_verbosity) {
if (option_bare)
- printf(_("Cloning into bare repository '%s'...\n"), dir);
+ fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir);
else
- printf(_("Cloning into '%s'...\n"), dir);
+ fprintf(stderr, _("Cloning into '%s'...\n"), dir);
}
init_db(option_template, INIT_DB_QUIET);
write_config(&option_config);
@@ -871,25 +900,27 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
remote = remote_get(option_origin);
transport = transport_get(remote, remote->url[0]);
+ transport->cloning = 1;
- if (!is_local) {
- if (!transport->get_refs_list || !transport->fetch)
- die(_("Don't know how to clone %s"), transport->url);
+ if (!transport->get_refs_list || (!is_local && !transport->fetch))
+ die(_("Don't know how to clone %s"), transport->url);
- transport_set_option(transport, TRANS_OPT_KEEP, "yes");
+ transport_set_option(transport, TRANS_OPT_KEEP, "yes");
- if (option_depth)
- transport_set_option(transport, TRANS_OPT_DEPTH,
- option_depth);
- if (option_single_branch)
- transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+ if (option_depth)
+ transport_set_option(transport, TRANS_OPT_DEPTH,
+ option_depth);
+ if (option_single_branch)
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
- transport_set_verbosity(transport, option_verbosity, option_progress);
+ transport_set_verbosity(transport, option_verbosity, option_progress);
- if (option_upload_pack)
- transport_set_option(transport, TRANS_OPT_UPLOADPACK,
- option_upload_pack);
- }
+ if (option_upload_pack)
+ transport_set_option(transport, TRANS_OPT_UPLOADPACK,
+ option_upload_pack);
+
+ if (transport->smart_options && !option_depth)
+ transport->smart_options->check_self_contained_and_connected = 1;
refs = transport_get_remote_refs(transport);
@@ -930,6 +961,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
our_head_points_at = remote_head_points_at;
}
else {
+ if (option_branch)
+ die(_("Remote branch %s not found in upstream %s"),
+ option_branch, option_origin);
+
warning(_("You appear to have cloned an empty repository."));
mapped_refs = NULL;
our_head_points_at = NULL;
@@ -950,7 +985,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport_fetch_refs(transport, mapped_refs);
update_remote_refs(refs, mapped_refs, remote_head_points_at,
- branch_top.buf, reflog_msg.buf);
+ branch_top.buf, reflog_msg.buf, transport, !is_local);
update_head(our_head_points_at, remote_head, reflog_msg.buf);
diff --git a/builtin/column.c b/builtin/column.c
index e125a55fc9..75818520e1 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -34,7 +34,7 @@ int cmd_column(int argc, const char **argv, const char *prefix)
};
/* This one is special and must be the first one */
- if (argc > 1 && !prefixcmp(argv[1], "--command=")) {
+ if (argc > 1 && starts_with(argv[1], "--command=")) {
command = argv[1] + 10;
git_config(column_config, (void *)command);
} else
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index f641ff2a89..987a4c3d73 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -12,6 +12,8 @@
static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1> <changelog";
+static const char *sign_commit;
+
static void new_parent(struct commit *parent, struct commit_list **parents_p)
{
unsigned char *sha1 = parent->object.sha1;
@@ -31,6 +33,10 @@ static int commit_tree_config(const char *var, const char *value, void *cb)
int status = git_gpg_config(var, value, NULL);
if (status)
return status;
+ if (!strcmp(var, "commit.gpgsign")) {
+ sign_commit = git_config_bool(var, value) ? "" : NULL;
+ return 0;
+ }
return git_default_config(var, value, cb);
}
@@ -41,7 +47,6 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
unsigned char tree_sha1[20];
unsigned char commit_sha1[20];
struct strbuf buffer = STRBUF_INIT;
- const char *sign_commit = NULL;
git_config(commit_tree_config, NULL);
@@ -66,6 +71,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
continue;
}
+ if (!strcmp(arg, "--no-gpg-sign")) {
+ sign_commit = NULL;
+ continue;
+ }
+
if (!strcmp(arg, "-m")) {
if (argc <= ++i)
usage(commit_tree_usage);
diff --git a/builtin/commit.c b/builtin/commit.c
index d2f30d960a..3783bcadcd 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -29,6 +29,8 @@
#include "gpg-interface.h"
#include "column.h"
#include "sequencer.h"
+#include "notes-utils.h"
+#include "mailmap.h"
static const char * const builtin_commit_usage[] = {
N_("git commit [options] [--] <pathspec>..."),
@@ -62,8 +64,18 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\
"If you wish to commit it anyway, use:\n"
"\n"
" git commit --allow-empty\n"
+"\n");
+
+static const char empty_cherry_pick_advice_single[] =
+N_("Otherwise, please use 'git reset'\n");
+
+static const char empty_cherry_pick_advice_multi[] =
+N_("If you wish to skip this commit, use:\n"
"\n"
-"Otherwise, please use 'git reset'\n");
+" git reset\n"
+"\n"
+"Then \"git cherry-pick --continue\" will resume cherry-picking\n"
+"the remaining commits.\n");
static const char *use_message_buffer;
static const char commit_editmsg[] = "COMMIT_EDITMSG";
@@ -106,24 +118,29 @@ static enum {
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 show_ignored_in_status;
+static int show_ignored_in_status, have_option_m;
static const char *only_include_assumed;
static struct strbuf message = STRBUF_INIT;
-static enum {
+static enum status_format {
STATUS_FORMAT_NONE = 0,
STATUS_FORMAT_LONG,
STATUS_FORMAT_SHORT,
- STATUS_FORMAT_PORCELAIN
-} status_format;
+ STATUS_FORMAT_PORCELAIN,
+
+ STATUS_FORMAT_UNSPECIFIED
+} status_format = STATUS_FORMAT_UNSPECIFIED;
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
{
struct strbuf *buf = opt->value;
- if (unset)
+ if (unset) {
+ have_option_m = 0;
strbuf_setlen(buf, 0);
- else {
+ } else {
+ have_option_m = 1;
if (buf->len)
strbuf_addch(buf, '\n');
strbuf_addstr(buf, arg);
@@ -136,14 +153,26 @@ static void determine_whence(struct wt_status *s)
{
if (file_exists(git_path("MERGE_HEAD")))
whence = FROM_MERGE;
- else if (file_exists(git_path("CHERRY_PICK_HEAD")))
+ else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
whence = FROM_CHERRY_PICK;
+ if (file_exists(git_path("sequencer")))
+ sequencer_in_use = 1;
+ }
else
whence = FROM_COMMIT;
if (s)
s->whence = whence;
}
+static void status_init_config(struct wt_status *s, config_fn_t fn)
+{
+ wt_status_prepare(s);
+ gitmodules_config();
+ git_config(fn, s);
+ determine_whence(s);
+ s->hints = advice_status_hints; /* must come after git_config() */
+}
+
static void rollback_index_files(void)
{
switch (commit_style) {
@@ -183,17 +212,15 @@ static int commit_index_files(void)
* and return the paths that match the given pattern in list.
*/
static int list_paths(struct string_list *list, const char *with_tree,
- const char *prefix, const char **pattern)
+ const char *prefix, const struct pathspec *pattern)
{
int i;
char *m;
- if (!pattern)
+ if (!pattern->nr)
return 0;
- for (i = 0; pattern[i]; i++)
- ;
- m = xcalloc(1, i);
+ m = xcalloc(1, pattern->nr);
if (with_tree) {
char *max_prefix = common_prefix(pattern);
@@ -202,12 +229,12 @@ static int list_paths(struct string_list *list, const char *with_tree,
}
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
struct string_list_item *item;
if (ce->ce_flags & CE_UPDATE)
continue;
- if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
+ if (!ce_path_match(ce, pattern, m))
continue;
item = string_list_insert(list, ce->name);
if (ce_skip_worktree(ce))
@@ -279,20 +306,20 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
{
int fd;
struct string_list partial;
- const char **pathspec = NULL;
- char *old_index_env = NULL;
+ struct pathspec pathspec;
int refresh_flags = REFRESH_QUIET;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ prefix, argv);
- if (*argv)
- pathspec = get_pathspec(prefix, argv);
-
- if (read_cache_preload(pathspec) < 0)
+ if (read_cache_preload(&pathspec) < 0)
die(_("index file corrupt"));
if (interactive) {
+ char *old_index_env = NULL;
fd = hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
@@ -331,9 +358,9 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
* (A) if all goes well, commit the real index;
* (B) on failure, rollback the real index.
*/
- if (all || (also && pathspec && *pathspec)) {
+ if (all || (also && pathspec.nr)) {
fd = hold_locked_index(&index_lock, 1);
- add_files_to_cache(also ? prefix : NULL, pathspec, 0);
+ add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
refresh_cache_or_die(refresh_flags);
update_main_cache_tree(WRITE_TREE_SILENT);
if (write_cache(fd, active_cache, active_nr) ||
@@ -352,7 +379,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
* and create commit from the_index.
* We still need to refresh the index here.
*/
- if (!only && (!pathspec || !*pathspec)) {
+ if (!only && !pathspec.nr) {
fd = hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
if (active_cache_changed) {
@@ -397,7 +424,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1;
- if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
+ if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
exit(1);
discard_cache();
@@ -457,6 +484,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
case STATUS_FORMAT_PORCELAIN:
wt_porcelain_print(s);
break;
+ case STATUS_FORMAT_UNSPECIFIED:
+ die("BUG: finalize_deferred_config() should have been called");
+ break;
case STATUS_FORMAT_NONE:
case STATUS_FORMAT_LONG:
wt_status_print(s);
@@ -526,7 +556,6 @@ static void determine_author_info(struct strbuf *author_ident)
(lb - strlen(" ") -
(a + strlen("\nauthor "))));
email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<")));
- date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> ")));
len = eol - (rb + strlen("> "));
date = xmalloc(len + 2);
*date = '@';
@@ -571,13 +600,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
{
struct stat statbuf;
struct strbuf committer_ident = STRBUF_INIT;
- int commitable, saved_color_setting;
+ int commitable;
struct strbuf sb = STRBUF_INIT;
- char *buffer;
const char *hook_arg1 = NULL;
const char *hook_arg2 = NULL;
- int ident_shown = 0;
int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
+ int old_display_comment_prefix;
/* This checks and barfs if author is badly specified */
determine_author_info(author_ident);
@@ -619,6 +647,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
logfile);
hook_arg1 = "message";
} else if (use_message) {
+ char *buffer;
buffer = strstr(use_message_buffer, "\n\n");
if (!use_editor && (!buffer || buffer[2] == '\0'))
die(_("commit has empty message"));
@@ -675,6 +704,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (s->fp == NULL)
die_errno(_("could not open '%s'"), git_path(commit_editmsg));
+ /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */
+ old_display_comment_prefix = s->display_comment_prefix;
+ s->display_comment_prefix = 1;
+
+ /*
+ * Most hints are counter-productive when the commit has
+ * already started.
+ */
+ s->hints = 0;
+
if (clean_message_contents)
stripspace(&sb, 0);
@@ -693,7 +732,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
eol = nl - sb.buf;
else
eol = sb.len;
- if (!prefixcmp(sb.buf + previous, "\nConflicts:\n")) {
+ if (starts_with(sb.buf + previous, "\nConflicts:\n")) {
ignore_footer = sb.len - previous;
break;
}
@@ -713,6 +752,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
/* This checks if committer ident is explicitly given */
strbuf_addstr(&committer_ident, git_committer_info(IDENT_STRICT));
if (use_editor && include_status) {
+ int ident_shown = 0;
+ int saved_color_setting;
char *ai_tmp, *ci_tmp;
if (whence != FROM_COMMIT)
status_printf_ln(s, GIT_COLOR_NORMAL,
@@ -800,11 +841,17 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
if (!commitable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
+ 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 (whence == FROM_CHERRY_PICK) {
fputs(_(empty_cherry_pick_advice), stderr);
+ if (!sequencer_in_use)
+ fputs(_(empty_cherry_pick_advice_single), stderr);
+ else
+ fputs(_(empty_cherry_pick_advice_multi), stderr);
+ }
return 0;
}
@@ -858,7 +905,7 @@ static int rest_is_empty(struct strbuf *sb, int start)
eol = sb->len;
if (strlen(sign_off_header) <= eol - i &&
- !prefixcmp(sb->buf + i, sign_off_header)) {
+ starts_with(sb->buf + i, sign_off_header)) {
i = eol;
continue;
}
@@ -909,6 +956,7 @@ static const char *find_author_by_nickname(const char *name)
struct rev_info revs;
struct commit *commit;
struct strbuf buf = STRBUF_INIT;
+ struct string_list mailmap = STRING_LIST_INIT_NODUP;
const char *av[20];
int ac = 0;
@@ -919,13 +967,17 @@ static const char *find_author_by_nickname(const char *name)
av[++ac] = buf.buf;
av[++ac] = NULL;
setup_revisions(ac, av, &revs, NULL);
+ revs.mailmap = &mailmap;
+ read_mailmap(revs.mailmap, NULL);
+
prepare_revision_walk(&revs);
commit = get_revision(&revs);
if (commit) {
struct pretty_print_context ctx = {0};
ctx.date_mode = DATE_NORMAL;
strbuf_release(&buf);
- format_commit_message(commit, "%an <%ae>", &buf, &ctx);
+ format_commit_message(commit, "%aN <%aE>", &buf, &ctx);
+ clear_mailmap(&mailmap);
return strbuf_detach(&buf, NULL);
}
die(_("No existing author found with '%s'"), name);
@@ -958,6 +1010,42 @@ static const char *read_commit_message(const char *name)
return logmsg_reencode(commit, NULL, out_enc);
}
+/*
+ * Enumerate what needs to be propagated when --porcelain
+ * is not in effect here.
+ */
+static struct status_deferred_config {
+ enum status_format status_format;
+ int show_branch;
+} status_deferred_config = {
+ STATUS_FORMAT_UNSPECIFIED,
+ -1 /* unspecified */
+};
+
+static void finalize_deferred_config(struct wt_status *s)
+{
+ int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN &&
+ !s->null_termination);
+
+ if (s->null_termination) {
+ if (status_format == STATUS_FORMAT_NONE ||
+ status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = STATUS_FORMAT_PORCELAIN;
+ else if (status_format == STATUS_FORMAT_LONG)
+ die(_("--long and -z are incompatible"));
+ }
+
+ if (use_deferred_config && status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = status_deferred_config.status_format;
+ if (status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = STATUS_FORMAT_NONE;
+
+ if (use_deferred_config && s->show_branch < 0)
+ s->show_branch = status_deferred_config.show_branch;
+ if (s->show_branch < 0)
+ s->show_branch = 0;
+}
+
static int parse_and_validate_options(int argc, const char *argv[],
const struct option *options,
const char * const usage[],
@@ -968,6 +1056,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
int f = 0;
argc = parse_options(argc, argv, prefix, options, usage, 0);
+ finalize_deferred_config(s);
if (force_author && !strchr(force_author, '>'))
force_author = find_author_by_nickname(force_author);
@@ -975,7 +1064,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 || message.len || use_message || fixup_message)
+ if (logfile || have_option_m || use_message || fixup_message)
use_editor = 0;
if (0 <= edit_flag)
use_editor = edit_flag;
@@ -1028,7 +1117,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (patch_interactive)
interactive = 1;
- if (!!also + !!only + !!all + !!interactive > 1)
+ if (also + only + all + interactive > 1)
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
if (argc == 0 && (also || (only && !amend)))
die(_("No paths with --include/--only does not make sense."));
@@ -1052,12 +1141,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (all && argc > 0)
die(_("Paths with -a does not make sense."));
- if (s->null_termination) {
- if (status_format == STATUS_FORMAT_NONE)
- status_format = STATUS_FORMAT_PORCELAIN;
- else if (status_format == STATUS_FORMAT_LONG)
- die(_("--long and -z are incompatible"));
- }
if (status_format != STATUS_FORMAT_NONE)
dry_run = 1;
@@ -1101,7 +1184,7 @@ static int git_status_config(const char *k, const char *v, void *cb)
{
struct wt_status *s = cb;
- if (!prefixcmp(k, "column."))
+ if (starts_with(k, "column."))
return git_column_config(k, v, "status", &s->colopts);
if (!strcmp(k, "status.submodulesummary")) {
int is_bool;
@@ -1110,11 +1193,26 @@ static int git_status_config(const char *k, const char *v, void *cb)
s->submodule_summary = -1;
return 0;
}
+ if (!strcmp(k, "status.short")) {
+ if (git_config_bool(k, v))
+ status_deferred_config.status_format = STATUS_FORMAT_SHORT;
+ else
+ status_deferred_config.status_format = STATUS_FORMAT_NONE;
+ return 0;
+ }
+ if (!strcmp(k, "status.branch")) {
+ status_deferred_config.show_branch = git_config_bool(k, v);
+ return 0;
+ }
if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
s->use_color = git_config_colorbool(k, v);
return 0;
}
- if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
+ if (!strcmp(k, "status.displaycommentprefix")) {
+ s->display_comment_prefix = git_config_bool(k, v);
+ return 0;
+ }
+ if (starts_with(k, "status.color.") || starts_with(k, "color.status.")) {
int slot = parse_status_slot(k, 13);
if (slot < 0)
return 0;
@@ -1152,22 +1250,22 @@ int cmd_status(int argc, const char **argv, const char *prefix)
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_SET_INT('s', "short", &status_format,
N_("show status concisely"), STATUS_FORMAT_SHORT),
- OPT_BOOLEAN('b', "branch", &s.show_branch,
- N_("show branch information")),
+ OPT_BOOL('b', "branch", &s.show_branch,
+ N_("show branch information")),
OPT_SET_INT(0, "porcelain", &status_format,
N_("machine-readable output"),
STATUS_FORMAT_PORCELAIN),
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
- OPT_BOOLEAN('z', "null", &s.null_termination,
- N_("terminate entries with NUL")),
+ OPT_BOOL('z', "null", &s.null_termination,
+ N_("terminate entries with NUL")),
{ 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_BOOLEAN(0, "ignored", &show_ignored_in_status,
- N_("show ignored files")),
+ OPT_BOOL(0, "ignored", &show_ignored_in_status,
+ N_("show ignored files")),
{ OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
@@ -1178,30 +1276,22 @@ int cmd_status(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_status_usage, builtin_status_options);
- wt_status_prepare(&s);
- gitmodules_config();
- git_config(git_status_config, &s);
- determine_whence(&s);
+ status_init_config(&s, git_status_config);
argc = parse_options(argc, argv, prefix,
builtin_status_options,
builtin_status_usage, 0);
finalize_colopts(&s.colopts, -1);
-
- if (s.null_termination) {
- if (status_format == STATUS_FORMAT_NONE)
- status_format = STATUS_FORMAT_PORCELAIN;
- else if (status_format == STATUS_FORMAT_LONG)
- die(_("--long and -z are incompatible"));
- }
+ finalize_deferred_config(&s);
handle_untracked_files_arg(&s);
if (show_ignored_in_status)
s.show_ignored_files = 1;
- if (*argv)
- s.pathspec = get_pathspec(prefix, argv);
+ parse_pathspec(&s.pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ prefix, argv);
- read_cache_preload(s.pathspec);
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
+ read_cache_preload(&s.pathspec);
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
if (0 <= fd)
@@ -1221,6 +1311,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
case STATUS_FORMAT_PORCELAIN:
wt_porcelain_print(&s);
break;
+ case STATUS_FORMAT_UNSPECIFIED:
+ die("BUG: finalize_deferred_config() should have been called");
+ break;
case STATUS_FORMAT_NONE:
case STATUS_FORMAT_LONG:
s.verbose = verbose;
@@ -1246,7 +1339,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
commit = lookup_commit(sha1);
if (!commit)
die(_("couldn't look up newly created commit"));
- if (!commit || parse_commit(commit))
+ if (parse_commit(commit))
die(_("could not parse newly created commit"));
strbuf_addstr(&format, "format:%h] %s");
@@ -1285,7 +1378,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
printf("[%s%s ",
- !prefixcmp(head, "refs/heads/") ?
+ starts_with(head, "refs/heads/") ?
head + 11 :
!strcmp(head, "HEAD") ?
_("detached HEAD") :
@@ -1314,6 +1407,10 @@ static int git_commit_config(const char *k, const char *v, void *cb)
}
if (!strcmp(k, "commit.cleanup"))
return git_config_string(&cleanup_arg, k, v);
+ if (!strcmp(k, "commit.gpgsign")) {
+ sign_commit = git_config_bool(k, v) ? "" : NULL;
+ return 0;
+ }
status = git_gpg_config(k, v, NULL);
if (status)
@@ -1369,45 +1466,43 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
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")),
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
- OPT_BOOLEAN(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
- OPT_BOOLEAN('s', "signoff", &signoff, N_("add Signed-off-by:")),
+ 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_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")),
- OPT_BOOLEAN(0, "status", &include_status, N_("include status in commit message template")),
+ OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")),
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
/* end commit message options */
OPT_GROUP(N_("Commit contents options")),
- OPT_BOOLEAN('a', "all", &all, N_("commit all changed files")),
- OPT_BOOLEAN('i', "include", &also, N_("add specified files to index for commit")),
- OPT_BOOLEAN(0, "interactive", &interactive, N_("interactively add files")),
- OPT_BOOLEAN('p', "patch", &patch_interactive, N_("interactively add changes")),
- OPT_BOOLEAN('o', "only", &only, N_("commit only specified files")),
- OPT_BOOLEAN('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
- OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")),
+ OPT_BOOL('a', "all", &all, N_("commit all changed files")),
+ OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
+ OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
+ OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
+ OPT_BOOL('o', "only", &only, N_("commit only specified files")),
+ OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
+ OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
STATUS_FORMAT_SHORT),
- OPT_BOOLEAN(0, "branch", &s.show_branch, N_("show branch information")),
+ OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
OPT_SET_INT(0, "porcelain", &status_format,
N_("machine-readable output"), STATUS_FORMAT_PORCELAIN),
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
- OPT_BOOLEAN('z', "null", &s.null_termination,
- N_("terminate entries with NUL")),
- OPT_BOOLEAN(0, "amend", &amend, N_("amend previous commit")),
- OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
+ OPT_BOOL('z', "null", &s.null_termination,
+ N_("terminate entries with NUL")),
+ 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" },
/* end commit contents options */
- { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
- N_("ok to record an empty change"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL,
- N_("ok to record a change with an empty message"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
+ N_("ok to record an empty change")),
+ OPT_HIDDEN_BOOL(0, "allow-empty-message", &allow_empty_message,
+ N_("ok to record a change with an empty message")),
OPT_END()
};
@@ -1415,29 +1510,26 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
struct strbuf sb = STRBUF_INIT;
struct strbuf author_ident = STRBUF_INIT;
const char *index_file, *reflog_msg;
- char *nl, *p;
+ char *nl;
unsigned char sha1[20];
struct ref_lock *ref_lock;
struct commit_list *parents = NULL, **pptr = &parents;
struct stat statbuf;
- int allow_fast_forward = 1;
struct commit *current_head = NULL;
struct commit_extra_header *extra = NULL;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_usage, builtin_commit_options);
- wt_status_prepare(&s);
- gitmodules_config();
- git_config(git_commit_config, &s);
- determine_whence(&s);
+ status_init_config(&s, git_commit_config);
+ status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
s.colopts = 0;
if (get_sha1("HEAD", sha1))
current_head = NULL;
else {
current_head = lookup_commit_or_die(sha1, "HEAD");
- if (!current_head || parse_commit(current_head))
+ if (parse_commit(current_head))
die(_("could not parse HEAD commit"));
}
argc = parse_and_validate_options(argc, argv, builtin_commit_options,
@@ -1470,6 +1562,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
} else if (whence == FROM_MERGE) {
struct strbuf m = STRBUF_INIT;
FILE *fp;
+ int allow_fast_forward = 1;
if (!reflog_msg)
reflog_msg = "commit (merge)";
@@ -1513,11 +1606,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
}
/* Truncate the message just before the diff, if any. */
- if (verbose) {
- p = strstr(sb.buf, "\ndiff --git ");
- if (p != NULL)
- strbuf_setlen(&sb, p - sb.buf + 1);
- }
+ if (verbose)
+ wt_status_truncate_message_at_cut_line(&sb);
if (cleanup_mode != CLEANUP_NONE)
stripspace(&sb, cleanup_mode == CLEANUP_ALL);
@@ -1552,7 +1642,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
!current_head
? NULL
: current_head->object.sha1,
- 0);
+ 0, NULL);
nl = strchr(sb.buf, '\n');
if (nl)
@@ -1591,7 +1681,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (cfg) {
/* we are amending, so current_head is not NULL */
copy_note_for_rewrite(cfg, current_head->object.sha1, sha1);
- finish_copy_notes_for_rewrite(cfg);
+ finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
}
run_rewrite_hook(current_head->object.sha1, sha1);
}
diff --git a/builtin/config.c b/builtin/config.c
index 33c9bf9d84..92ebf23f0a 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -2,6 +2,7 @@
#include "cache.h"
#include "color.h"
#include "parse-options.h"
+#include "urlmatch.h"
static const char *const builtin_config_usage[] = {
N_("git config [options]"),
@@ -21,6 +22,7 @@ static char term = '\n';
static int use_global_config, use_system_config, use_local_config;
static const char *given_config_file;
+static const char *given_config_blob;
static int actions, types;
static const char *get_color_slot, *get_colorbool_slot;
static int end_null;
@@ -41,6 +43,7 @@ static int respect_includes = -1;
#define ACTION_SET_ALL (1<<12)
#define ACTION_GET_COLOR (1<<13)
#define ACTION_GET_COLORBOOL (1<<14)
+#define ACTION_GET_URLMATCH (1<<15)
#define TYPE_BOOL (1<<0)
#define TYPE_INT (1<<1)
@@ -49,14 +52,16 @@ static int respect_includes = -1;
static struct option builtin_config_options[] = {
OPT_GROUP(N_("Config file location")),
- OPT_BOOLEAN(0, "global", &use_global_config, N_("use global config file")),
- OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")),
- OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")),
+ OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
+ OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
+ OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")),
OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")),
+ OPT_STRING(0, "blob", &given_config_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-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, "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),
@@ -73,7 +78,7 @@ static struct option builtin_config_options[] = {
OPT_BIT(0, "bool-or-int", &types, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH),
OPT_GROUP(N_("Other")),
- OPT_BOOLEAN('z', "null", &end_null, N_("terminate values with NUL byte")),
+ OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")),
OPT_BOOL(0, "includes", &respect_includes, N_("respect include directives on lookup")),
OPT_END(),
};
@@ -100,25 +105,13 @@ struct strbuf_list {
int alloc;
};
-static int collect_config(const char *key_, const char *value_, void *cb)
+static int format_config(struct strbuf *buf, const char *key_, const char *value_)
{
- struct strbuf_list *values = cb;
- struct strbuf *buf;
- char value[256];
- const char *vptr = value;
int must_free_vptr = 0;
int must_print_delim = 0;
+ char value[256];
+ const char *vptr = value;
- if (!use_key_regexp && strcmp(key_, key))
- return 0;
- if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
- return 0;
- if (regexp != NULL &&
- (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
- return 0;
-
- ALLOC_GROW(values->items, values->nr + 1, values->alloc);
- buf = &values->items[values->nr++];
strbuf_init(buf, 0);
if (show_keys) {
@@ -126,7 +119,8 @@ static int collect_config(const char *key_, const char *value_, void *cb)
must_print_delim = 1;
}
if (types == TYPE_INT)
- sprintf(value, "%d", git_config_int(key_, value_?value_:""));
+ sprintf(value, "%"PRId64,
+ git_config_int64(key_, value_ ? value_ : ""));
else if (types == TYPE_BOOL)
vptr = git_config_bool(key_, value_) ? "true" : "false";
else if (types == TYPE_BOOL_OR_INT) {
@@ -154,15 +148,27 @@ static int collect_config(const char *key_, const char *value_, void *cb)
strbuf_addch(buf, term);
if (must_free_vptr)
- /* If vptr must be freed, it's a pointer to a
- * dynamically allocated buffer, it's safe to cast to
- * const.
- */
free((char *)vptr);
-
return 0;
}
+static int collect_config(const char *key_, const char *value_, void *cb)
+{
+ struct strbuf_list *values = cb;
+
+ if (!use_key_regexp && strcmp(key_, key))
+ return 0;
+ if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
+ return 0;
+ if (regexp != NULL &&
+ (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
+ return 0;
+
+ ALLOC_GROW(values->items, values->nr + 1, values->alloc);
+
+ return format_config(&values->items[values->nr++], key_, value_);
+}
+
static int get_value(const char *key_, const char *regex_)
{
int ret = CONFIG_GENERIC_ERROR;
@@ -218,7 +224,8 @@ static int get_value(const char *key_, const char *regex_)
}
git_config_with_options(collect_config, &values,
- given_config_file, respect_includes);
+ given_config_file, given_config_blob,
+ respect_includes);
ret = !values.nr;
@@ -262,8 +269,8 @@ static char *normalize_value(const char *key, const char *value)
else {
normalized = xmalloc(64);
if (types == TYPE_INT) {
- int v = git_config_int(key, value);
- sprintf(normalized, "%d", v);
+ int64_t v = git_config_int64(key, value);
+ sprintf(normalized, "%"PRId64, v);
}
else if (types == TYPE_BOOL)
sprintf(normalized, "%s",
@@ -302,7 +309,8 @@ static void get_color(const char *def_color)
get_color_found = 0;
parsed_color[0] = '\0';
git_config_with_options(git_get_color_config, NULL,
- given_config_file, respect_includes);
+ given_config_file, given_config_blob,
+ respect_includes);
if (!get_color_found && def_color)
color_parse(def_color, "command line", parsed_color);
@@ -329,8 +337,10 @@ static int get_colorbool(int print)
{
get_colorbool_found = -1;
get_diff_color_found = -1;
+ get_color_ui_found = -1;
git_config_with_options(git_get_colorbool_config, NULL,
- given_config_file, respect_includes);
+ given_config_file, given_config_blob,
+ respect_includes);
if (get_colorbool_found < 0) {
if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -339,6 +349,10 @@ static int get_colorbool(int print)
get_colorbool_found = get_color_ui_found;
}
+ if (get_colorbool_found < 0)
+ /* default value if none found in config */
+ get_colorbool_found = GIT_COLOR_AUTO;
+
get_colorbool_found = want_color(get_colorbool_found);
if (print) {
@@ -348,6 +362,103 @@ static int get_colorbool(int print)
return get_colorbool_found ? 0 : 1;
}
+static void check_blob_write(void)
+{
+ if (given_config_blob)
+ die("writing config blobs is not supported");
+}
+
+struct urlmatch_current_candidate_value {
+ char value_is_null;
+ struct strbuf value;
+};
+
+static int urlmatch_collect_fn(const char *var, const char *value, void *cb)
+{
+ struct string_list *values = cb;
+ struct string_list_item *item = string_list_insert(values, var);
+ struct urlmatch_current_candidate_value *matched = item->util;
+
+ if (!matched) {
+ matched = xmalloc(sizeof(*matched));
+ strbuf_init(&matched->value, 0);
+ item->util = matched;
+ } else {
+ strbuf_reset(&matched->value);
+ }
+
+ if (value) {
+ strbuf_addstr(&matched->value, value);
+ matched->value_is_null = 0;
+ } else {
+ matched->value_is_null = 1;
+ }
+ return 0;
+}
+
+static char *dup_downcase(const char *string)
+{
+ char *result;
+ size_t len, i;
+
+ len = strlen(string);
+ result = xmalloc(len + 1);
+ for (i = 0; i < len; i++)
+ result[i] = tolower(string[i]);
+ result[i] = '\0';
+ return result;
+}
+
+static int get_urlmatch(const char *var, const char *url)
+{
+ char *section_tail;
+ struct string_list_item *item;
+ struct urlmatch_config config = { STRING_LIST_INIT_DUP };
+ struct string_list values = STRING_LIST_INIT_DUP;
+
+ config.collect_fn = urlmatch_collect_fn;
+ config.cascade_fn = NULL;
+ config.cb = &values;
+
+ if (!url_normalize(url, &config.url))
+ die("%s", config.url.err);
+
+ config.section = dup_downcase(var);
+ section_tail = strchr(config.section, '.');
+ if (section_tail) {
+ *section_tail = '\0';
+ config.key = section_tail + 1;
+ show_keys = 0;
+ } else {
+ config.key = NULL;
+ show_keys = 1;
+ }
+
+ git_config_with_options(urlmatch_config_entry, &config,
+ given_config_file, NULL, respect_includes);
+
+ for_each_string_list_item(item, &values) {
+ struct urlmatch_current_candidate_value *matched = item->util;
+ struct strbuf key = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addstr(&key, item->string);
+ format_config(&buf, key.buf,
+ matched->value_is_null ? NULL : matched->value.buf);
+ fwrite(buf.buf, 1, buf.len, stdout);
+ strbuf_release(&key);
+ strbuf_release(&buf);
+
+ strbuf_release(&matched->value);
+ }
+ string_list_clear(&config.vars, 1);
+ string_list_clear(&values, 1);
+ free(config.url.url);
+
+ free((void *)config.section);
+ return 0;
+}
+
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = !startup_info->have_repository;
@@ -359,7 +470,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
builtin_config_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) {
+ if (use_global_config + use_system_config + use_local_config +
+ !!given_config_file + !!given_config_blob > 1) {
error("only one config file at a time.");
usage_with_options(builtin_config_usage, builtin_config_options);
}
@@ -379,8 +491,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
*/
die("$HOME not set");
- if (access_or_warn(user_config, R_OK) &&
- xdg_config && !access_or_warn(xdg_config, R_OK))
+ if (access_or_warn(user_config, R_OK, 0) &&
+ xdg_config && !access_or_warn(xdg_config, R_OK, 0))
given_config_file = xdg_config;
else
given_config_file = user_config;
@@ -438,6 +550,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 0, 0);
if (git_config_with_options(show_all_config, NULL,
given_config_file,
+ given_config_blob,
respect_includes) < 0) {
if (given_config_file)
die_errno("unable to read config file '%s'",
@@ -450,6 +563,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 0, 0);
if (!given_config_file && nongit)
die("not in a git directory");
+ if (given_config_blob)
+ die("editing blobs is not supported");
git_config(git_default_config, NULL);
launch_editor(given_config_file ?
given_config_file : git_path("config"),
@@ -457,6 +572,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_SET) {
int ret;
+ check_blob_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
ret = git_config_set_in_file(given_config_file, argv[0], value);
@@ -466,18 +582,21 @@ int cmd_config(int argc, const char **argv, const char *prefix)
return ret;
}
else if (actions == ACTION_SET_ALL) {
+ check_blob_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
return git_config_set_multivar_in_file(given_config_file,
argv[0], value, argv[2], 0);
}
else if (actions == ACTION_ADD) {
+ check_blob_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
return git_config_set_multivar_in_file(given_config_file,
argv[0], value, "^$", 0);
}
else if (actions == ACTION_REPLACE_ALL) {
+ check_blob_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
return git_config_set_multivar_in_file(given_config_file,
@@ -499,7 +618,12 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]);
}
+ else if (actions == ACTION_GET_URLMATCH) {
+ check_argc(argc, 2, 2);
+ return get_urlmatch(argv[0], argv[1]);
+ }
else if (actions == ACTION_UNSET) {
+ check_blob_write();
check_argc(argc, 1, 2);
if (argc == 2)
return git_config_set_multivar_in_file(given_config_file,
@@ -509,12 +633,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
argv[0], NULL);
}
else if (actions == ACTION_UNSET_ALL) {
+ check_blob_write();
check_argc(argc, 1, 2);
return git_config_set_multivar_in_file(given_config_file,
argv[0], NULL, argv[1], 1);
}
else if (actions == ACTION_RENAME_SECTION) {
int ret;
+ check_blob_write();
check_argc(argc, 2, 2);
ret = git_config_rename_section_in_file(given_config_file,
argv[0], argv[1]);
@@ -525,6 +651,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_REMOVE_SECTION) {
int ret;
+ check_blob_write();
check_argc(argc, 1, 1);
ret = git_config_rename_section_in_file(given_config_file,
argv[0], NULL);
@@ -544,9 +671,3 @@ int cmd_config(int argc, const char **argv, const char *prefix)
return 0;
}
-
-int cmd_repo_config(int argc, const char **argv, const char *prefix)
-{
- fprintf(stderr, "WARNING: git repo-config is deprecated in favor of git config.\n");
- return cmd_config(argc, argv, prefix);
-}
diff --git a/builtin/describe.c b/builtin/describe.c
index 6636a68cd9..dadd999c41 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -6,13 +6,14 @@
#include "exec_cmd.h"
#include "parse-options.h"
#include "diff.h"
-#include "hash.h"
+#include "hashmap.h"
+#include "argv-array.h"
-#define SEEN (1u<<0)
+#define SEEN (1u << 0)
#define MAX_TAGS (FLAG_BITS - 1)
static const char * const describe_usage[] = {
- N_("git describe [options] <committish>*"),
+ N_("git describe [options] <commit-ish>*"),
N_("git describe [options] --dirty"),
NULL
};
@@ -21,9 +22,10 @@ static int debug; /* Display lots of verbose info */
static int all; /* Any valid ref can be used */
static int tags; /* Allow lightweight tags */
static int longformat;
+static int first_parent;
static int abbrev = -1; /* unspecified */
static int max_candidates = 10;
-static struct hash_table names;
+static struct hashmap names;
static int have_util;
static const char *pattern;
static int always;
@@ -34,20 +36,26 @@ static const char *diff_index_args[] = {
"diff-index", "--quiet", "HEAD", "--", NULL
};
-
struct commit_name {
- struct commit_name *next;
+ struct hashmap_entry entry;
unsigned char peeled[20];
struct tag *tag;
unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
unsigned name_checked:1;
unsigned char sha1[20];
- const char *path;
+ char *path;
};
+
static const char *prio_names[] = {
"head", "lightweight", "annotated",
};
+static int commit_name_cmp(const struct commit_name *cn1,
+ const struct commit_name *cn2, const void *peeled)
+{
+ return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled);
+}
+
static inline unsigned int hash_sha1(const unsigned char *sha1)
{
unsigned int hash;
@@ -57,21 +65,9 @@ static inline unsigned int hash_sha1(const unsigned char *sha1)
static inline struct commit_name *find_commit_name(const unsigned char *peeled)
{
- struct commit_name *n = lookup_hash(hash_sha1(peeled), &names);
- while (n && !!hashcmp(peeled, n->peeled))
- n = n->next;
- return n;
-}
-
-static int set_util(void *chain, void *data)
-{
- struct commit_name *n;
- for (n = chain; n; n = n->next) {
- struct commit *c = lookup_commit_reference_gently(n->peeled, 1);
- if (c)
- c->util = n;
- }
- return 0;
+ struct commit_name key;
+ hashmap_entry_init(&key, hash_sha1(peeled));
+ return hashmap_get(&names, &key, peeled);
}
static int replace_name(struct commit_name *e,
@@ -116,28 +112,24 @@ static void add_to_known_names(const char *path,
struct tag *tag = NULL;
if (replace_name(e, prio, sha1, &tag)) {
if (!e) {
- void **pos;
e = xmalloc(sizeof(struct commit_name));
hashcpy(e->peeled, peeled);
- pos = insert_hash(hash_sha1(peeled), e, &names);
- if (pos) {
- e->next = *pos;
- *pos = e;
- } else {
- e->next = NULL;
- }
+ hashmap_entry_init(e, hash_sha1(peeled));
+ hashmap_add(&names, e);
+ e->path = NULL;
}
e->tag = tag;
e->prio = prio;
e->name_checked = 0;
hashcpy(e->sha1, sha1);
- e->path = path;
+ free(e->path);
+ e->path = xstrdup(path);
}
}
static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
- int is_tag = !prefixcmp(path, "refs/tags/");
+ int is_tag = starts_with(path, "refs/tags/");
unsigned char peeled[20];
int is_annotated, prio;
@@ -288,7 +280,14 @@ static void describe(const char *arg, int last_one)
fprintf(stderr, _("searching to describe %s\n"), arg);
if (!have_util) {
- for_each_hash(&names, set_util, NULL);
+ struct hashmap_iter iter;
+ struct commit *c;
+ struct commit_name *n = hashmap_iter_first(&names, &iter);
+ for (; n; n = hashmap_iter_next(&iter)) {
+ c = lookup_commit_reference_gently(n->peeled, 1);
+ if (c)
+ c->util = n;
+ }
have_util = 1;
}
@@ -336,6 +335,9 @@ static void describe(const char *arg, int last_one)
commit_list_insert_by_date(p, &list);
p->object.flags |= c->object.flags;
parents = parents->next;
+
+ if (first_parent)
+ break;
}
}
@@ -399,11 +401,12 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
{
int contains = 0;
struct option options[] = {
- OPT_BOOLEAN(0, "contains", &contains, N_("find the tag that comes after the commit")),
- OPT_BOOLEAN(0, "debug", &debug, N_("debug search strategy on stderr")),
- OPT_BOOLEAN(0, "all", &all, N_("use any ref")),
- OPT_BOOLEAN(0, "tags", &tags, N_("use any tag, even unannotated")),
- OPT_BOOLEAN(0, "long", &longformat, N_("always use long format")),
+ OPT_BOOL(0, "contains", &contains, N_("find the tag that comes after the commit")),
+ OPT_BOOL(0, "debug", &debug, N_("debug search strategy on stderr")),
+ OPT_BOOL(0, "all", &all, N_("use any ref")),
+ OPT_BOOL(0, "tags", &tags, N_("use any tag, even unannotated")),
+ OPT_BOOL(0, "long", &longformat, N_("always use long format")),
+ OPT_BOOL(0, "first-parent", &first_parent, N_("only follow first parent")),
OPT__ABBREV(&abbrev),
OPT_SET_INT(0, "exact-match", &max_candidates,
N_("only output exact matches"), 0),
@@ -411,11 +414,11 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
N_("consider <n> most recent tags (default: 10)")),
OPT_STRING(0, "match", &pattern, N_("pattern"),
N_("only consider tags matching <pattern>")),
- OPT_BOOLEAN(0, "always", &always,
- N_("show abbreviated commit object as fallback")),
+ OPT_BOOL(0, "always", &always,
+ N_("show abbreviated commit object as fallback")),
{OPTION_STRING, 0, "dirty", &dirty, N_("mark"),
- N_("append <mark> on dirty working tree (default: \"-dirty\")"),
- PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"},
+ N_("append <mark> on dirty working tree (default: \"-dirty\")"),
+ PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"},
OPT_END(),
};
@@ -435,29 +438,29 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
die(_("--long is incompatible with --abbrev=0"));
if (contains) {
- const char **args = xmalloc((7 + argc) * sizeof(char *));
- int i = 0;
- args[i++] = "name-rev";
- args[i++] = "--name-only";
- args[i++] = "--no-undefined";
+ struct argv_array args;
+
+ argv_array_init(&args);
+ argv_array_pushl(&args, "name-rev",
+ "--peel-tag", "--name-only", "--no-undefined",
+ NULL);
if (always)
- args[i++] = "--always";
+ argv_array_push(&args, "--always");
if (!all) {
- args[i++] = "--tags";
- if (pattern) {
- char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
- sprintf(s, "--refs=refs/tags/%s", pattern);
- args[i++] = s;
- }
+ argv_array_push(&args, "--tags");
+ if (pattern)
+ argv_array_pushf(&args, "--refs=refs/tags/%s", pattern);
}
- memcpy(args + i, argv, argc * sizeof(char *));
- args[i + argc] = NULL;
- return cmd_name_rev(i + argc, args, prefix);
+ while (*argv) {
+ argv_array_push(&args, *argv);
+ argv++;
+ }
+ return cmd_name_rev(args.argc, args.argv, prefix);
}
- init_hash(&names);
+ hashmap_init(&names, (hashmap_cmp_fn) commit_name_cmp, 0);
for_each_rawref(get_name, NULL);
- if (!names.nr && !always)
+ if (!names.size && !always)
die(_("No names found, cannot describe anything."));
if (argc == 0) {
@@ -478,11 +481,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
}
describe("HEAD", 1);
} else if (dirty) {
- die(_("--dirty is incompatible with committishes"));
+ die(_("--dirty is incompatible with commit-ishes"));
} else {
- while (argc-- > 0) {
+ while (argc-- > 0)
describe(*argv++, argc == 0);
- }
}
return 0;
}
diff --git a/builtin/diff-files.c b/builtin/diff-files.c
index 46085f862f..9200069363 100644
--- a/builtin/diff-files.c
+++ b/builtin/diff-files.c
@@ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
rev.combine_merges = rev.dense_combined_merges = 1;
- if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
+ if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index 1c737f7921..ce15b23042 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -43,7 +43,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
usage(diff_cache_usage);
if (!cached) {
setup_work_tree();
- if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
+ if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
diff --git a/builtin/diff.c b/builtin/diff.c
index 8c2af6cb43..0f247d2400 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -16,6 +16,9 @@
#include "submodule.h"
#include "sha1-array.h"
+#define DIFF_NO_INDEX_EXPLICIT 1
+#define DIFF_NO_INDEX_IMPLICIT 2
+
struct blobinfo {
unsigned char sha1[20];
const char *name;
@@ -64,15 +67,18 @@ static void stuff_change(struct diff_options *opt,
static int builtin_diff_b_f(struct rev_info *revs,
int argc, const char **argv,
- struct blobinfo *blob,
- const char *path)
+ struct blobinfo *blob)
{
/* Blob vs file in the working tree*/
struct stat st;
+ const char *path;
if (argc > 1)
usage(builtin_diff_usage);
+ GUARD_PATHSPEC(&revs->prune_data, PATHSPEC_FROMTOP | PATHSPEC_LITERAL);
+ path = revs->prune_data.items[0].match;
+
if (lstat(path, &st))
die_errno(_("failed to stat '%s'"), path);
if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
@@ -140,7 +146,7 @@ static int builtin_diff_index(struct rev_info *revs,
usage(builtin_diff_usage);
if (!cached) {
setup_work_tree();
- if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
+ if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
@@ -153,7 +159,8 @@ static int builtin_diff_index(struct rev_info *revs,
static int builtin_diff_tree(struct rev_info *revs,
int argc, const char **argv,
- struct object_array_entry *ent)
+ struct object_array_entry *ent0,
+ struct object_array_entry *ent1)
{
const unsigned char *(sha1[2]);
int swap = 0;
@@ -161,13 +168,14 @@ static int builtin_diff_tree(struct rev_info *revs,
if (argc > 1)
usage(builtin_diff_usage);
- /* We saw two trees, ent[0] and ent[1].
- * if ent[1] is uninteresting, they are swapped
+ /*
+ * We saw two trees, ent0 and ent1. If ent1 is uninteresting,
+ * swap them.
*/
- if (ent[1].item->flags & UNINTERESTING)
+ if (ent1->item->flags & UNINTERESTING)
swap = 1;
- sha1[swap] = ent[0].item->sha1;
- sha1[1-swap] = ent[1].item->sha1;
+ sha1[swap] = ent0->item->sha1;
+ sha1[1 - swap] = ent1->item->sha1;
diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
log_tree_diff_flush(revs);
return 0;
@@ -240,7 +248,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
revs->combine_merges = revs->dense_combined_merges = 1;
setup_work_tree();
- if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
+ if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
@@ -251,11 +259,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
{
int i;
struct rev_info rev;
- struct object_array_entry ent[100];
- int ents = 0, blobs = 0, paths = 0;
- const char *path = NULL;
+ struct object_array ent = OBJECT_ARRAY_INIT;
+ int blobs = 0, paths = 0;
struct blobinfo blob[2];
- int nongit;
+ int nongit = 0, no_index = 0;
int result = 0;
/*
@@ -281,14 +288,59 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
* Other cases are errors.
*/
- prefix = setup_git_directory_gently(&nongit);
- gitmodules_config();
+ /* Were we asked to do --no-index explicitly? */
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(argv[i], "--no-index"))
+ no_index = DIFF_NO_INDEX_EXPLICIT;
+ if (argv[i][0] != '-')
+ break;
+ }
+
+ if (!no_index)
+ prefix = setup_git_directory_gently(&nongit);
+
+ /*
+ * Treat git diff with at least one path outside of the
+ * repo the same as if the command would have been executed
+ * outside of a git repository. In this case it behaves
+ * the same way as "git diff --no-index <a> <b>", which acts
+ * as a colourful "diff" replacement.
+ */
+ if (nongit || ((argc == i + 2) &&
+ (!path_inside_repo(prefix, argv[i]) ||
+ !path_inside_repo(prefix, argv[i + 1]))))
+ no_index = DIFF_NO_INDEX_IMPLICIT;
+
+ if (!no_index)
+ gitmodules_config();
git_config(git_diff_ui_config, NULL);
init_revisions(&rev, prefix);
- /* If this is a no-index diff, just run it and exit there. */
- diff_no_index(&rev, argc, argv, nongit, prefix);
+ if (no_index && argc != i + 2) {
+ if (no_index == DIFF_NO_INDEX_IMPLICIT) {
+ /*
+ * There was no --no-index and there were not two
+ * paths. It is possible that the user intended
+ * to do an inside-repository operation.
+ */
+ fprintf(stderr, "Not a git repository\n");
+ fprintf(stderr,
+ "To compare two paths outside a working tree:\n");
+ }
+ /* Give the usage message for non-repository usage and exit. */
+ usagef("git diff %s <path> <path>",
+ no_index == DIFF_NO_INDEX_EXPLICIT ?
+ "--no-index" : "[--no-index]");
+
+ }
+ if (no_index)
+ /* If this is a no-index diff, just run it and exit there. */
+ diff_no_index(&rev, argc, argv, prefix);
/* Otherwise, we are doing the usual "git" diff */
rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
@@ -337,9 +389,9 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
for (i = 0; i < rev.pending.nr; i++) {
- struct object_array_entry *list = rev.pending.objects+i;
- struct object *obj = list->item;
- const char *name = list->name;
+ struct object_array_entry *entry = &rev.pending.objects[i];
+ struct object *obj = entry->item;
+ const char *name = entry->name;
int flags = (obj->flags & UNINTERESTING);
if (!obj->parsed)
obj = parse_object(obj->sha1);
@@ -348,38 +400,29 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
die(_("invalid object '%s' given."), name);
if (obj->type == OBJ_COMMIT)
obj = &((struct commit *)obj)->tree->object;
+
if (obj->type == OBJ_TREE) {
- if (ARRAY_SIZE(ent) <= ents)
- die(_("more than %d trees given: '%s'"),
- (int) ARRAY_SIZE(ent), name);
obj->flags |= flags;
- ent[ents].item = obj;
- ent[ents].name = name;
- ents++;
- continue;
- }
- if (obj->type == OBJ_BLOB) {
+ add_object_array(obj, name, &ent);
+ } else if (obj->type == OBJ_BLOB) {
if (2 <= blobs)
die(_("more than two blobs given: '%s'"), name);
hashcpy(blob[blobs].sha1, obj->sha1);
blob[blobs].name = name;
- blob[blobs].mode = list->mode;
+ blob[blobs].mode = entry->mode;
blobs++;
- continue;
+ } else {
+ die(_("unhandled object '%s' given."), name);
}
- die(_("unhandled object '%s' given."), name);
}
- if (rev.prune_data.nr) {
- if (!path)
- path = rev.prune_data.items[0].match;
+ if (rev.prune_data.nr)
paths += rev.prune_data.nr;
- }
/*
* Now, do the arguments look reasonable?
*/
- if (!ents) {
+ if (!ent.nr) {
switch (blobs) {
case 0:
result = builtin_diff_files(&rev, argc, argv);
@@ -387,7 +430,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
case 1:
if (paths != 1)
usage(builtin_diff_usage);
- result = builtin_diff_b_f(&rev, argc, argv, blob, path);
+ result = builtin_diff_b_f(&rev, argc, argv, blob);
break;
case 2:
if (paths)
@@ -400,23 +443,26 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
else if (blobs)
usage(builtin_diff_usage);
- else if (ents == 1)
+ else if (ent.nr == 1)
result = builtin_diff_index(&rev, argc, argv);
- else if (ents == 2)
- result = builtin_diff_tree(&rev, argc, argv, ent);
- else if (ent[0].item->flags & UNINTERESTING) {
+ else if (ent.nr == 2)
+ 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[0] == merge-base,
- * ent[ents-2] == A, and ent[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.
+ * 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.
*/
- ent[1] = ent[ents-1];
- result = builtin_diff_tree(&rev, argc, argv, ent);
+ result = builtin_diff_tree(&rev, argc, argv,
+ &ent.objects[0],
+ &ent.objects[ent.nr-1]);
} else
result = builtin_diff_combined(&rev, argc, argv,
- ent, ents);
+ ent.objects, ent.nr);
result = diff_result_code(&rev.diffopt, result);
if (1 < rev.diffopt.skip_stat_unmatch)
refresh_index_quietly();
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index d60d675f6f..b8d8a3aaf9 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -30,6 +30,7 @@ static int fake_missing_tagger;
static int use_done_feature;
static int no_data;
static int full_tree;
+static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
static int parse_opt_signed_tag_mode(const struct option *opt,
const char *arg, int unset)
@@ -286,7 +287,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
- parse_commit(commit);
+ parse_commit_or_die(commit);
author = strstr(commit->buffer, "\nauthor ");
if (!author)
die ("Could not find author in commit %s",
@@ -307,7 +308,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
if (commit->parents &&
get_object_mark(&commit->parents->item->object) != 0 &&
!full_tree) {
- parse_commit(commit->parents->item);
+ parse_commit_or_die(commit->parents->item);
diff_tree_sha1(commit->parents->item->tree->object.sha1,
commit->tree->object.sha1, "", &rev->diffopt);
}
@@ -379,7 +380,7 @@ static void handle_tag(const char *name, struct tag *tag)
int tagged_mark;
struct commit *p;
- /* Trees have no identifer in fast-export output, thus we have no way
+ /* Trees have no identifier in fast-export output, thus we have no way
* to output tags of trees, tags of tags of trees, etc. Simply omit
* such tags.
*/
@@ -475,7 +476,7 @@ static void handle_tag(const char *name, struct tag *tag)
}
}
- if (!prefixcmp(name, "refs/tags/"))
+ if (starts_with(name, "refs/tags/"))
name += 10;
printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n",
name, tagged_mark,
@@ -484,10 +485,32 @@ static void handle_tag(const char *name, struct tag *tag)
(int)message_size, (int)message_size, message ? message : "");
}
-static void get_tags_and_duplicates(struct rev_cmdline_info *info,
- struct string_list *extra_refs)
+static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)
+{
+ switch (e->item->type) {
+ case OBJ_COMMIT:
+ return (struct commit *)e->item;
+ case OBJ_TAG: {
+ struct tag *tag = (struct tag *)e->item;
+
+ /* handle nested tags */
+ while (tag && tag->object.type == OBJ_TAG) {
+ parse_object(tag->object.sha1);
+ string_list_append(&extra_refs, full_name)->util = tag;
+ tag = (struct tag *)tag->tagged;
+ }
+ if (!tag)
+ die("Tag %s points nowhere?", e->name);
+ return (struct commit *)tag;
+ break;
+ }
+ default:
+ return NULL;
+ }
+}
+
+static void get_tags_and_duplicates(struct rev_cmdline_info *info)
{
- struct tag *tag;
int i;
for (i = 0; i < info->nr; i++) {
@@ -502,60 +525,45 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info,
if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
continue;
- switch (e->item->type) {
- case OBJ_COMMIT:
- commit = (struct commit *)e->item;
- break;
- case OBJ_TAG:
- tag = (struct tag *)e->item;
-
- /* handle nested tags */
- while (tag && tag->object.type == OBJ_TAG) {
- parse_object(tag->object.sha1);
- string_list_append(extra_refs, full_name)->util = tag;
- tag = (struct tag *)tag->tagged;
- }
- if (!tag)
- die ("Tag %s points nowhere?", e->name);
- switch(tag->object.type) {
- case OBJ_COMMIT:
- commit = (struct commit *)tag;
- break;
- case OBJ_BLOB:
- export_blob(tag->object.sha1);
- continue;
- default: /* OBJ_TAG (nested tags) is already handled */
- warning("Tag points to object of unexpected type %s, skipping.",
- typename(tag->object.type));
- continue;
- }
- break;
- default:
+ commit = get_commit(e, full_name);
+ if (!commit) {
warning("%s: Unexpected object of type %s, skipping.",
e->name,
typename(e->item->type));
continue;
}
+ switch(commit->object.type) {
+ case OBJ_COMMIT:
+ break;
+ case OBJ_BLOB:
+ export_blob(commit->object.sha1);
+ continue;
+ default: /* OBJ_TAG (nested tags) is already handled */
+ warning("Tag points to object of unexpected type %s, skipping.",
+ typename(commit->object.type));
+ continue;
+ }
+
/*
* This ref will not be updated through a commit, lets make
* sure it gets properly updated eventually.
*/
if (commit->util || commit->object.flags & SHOWN)
- string_list_append(extra_refs, full_name)->util = commit;
+ string_list_append(&extra_refs, full_name)->util = commit;
if (!commit->util)
commit->util = full_name;
}
}
-static void handle_tags_and_duplicates(struct string_list *extra_refs)
+static void handle_tags_and_duplicates(void)
{
struct commit *commit;
int i;
- for (i = extra_refs->nr - 1; i >= 0; i--) {
- const char *name = extra_refs->items[i].string;
- struct object *object = extra_refs->items[i].util;
+ for (i = extra_refs.nr - 1; i >= 0; i--) {
+ const char *name = extra_refs.items[i].string;
+ struct object *object = extra_refs.items[i].util;
switch (object->type) {
case OBJ_TAG:
handle_tag(name, (struct tag *)object);
@@ -613,6 +621,8 @@ static void import_marks(char *input_file)
char *line_end, *mark_end;
unsigned char sha1[20];
struct object *object;
+ struct commit *commit;
+ enum object_type type;
line_end = strchr(line, '\n');
if (line[0] != ':' || !line_end)
@@ -621,23 +631,29 @@ static void import_marks(char *input_file)
mark = strtoumax(line + 1, &mark_end, 10);
if (!mark || mark_end == line + 1
- || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
+ || *mark_end != ' ' || get_sha1_hex(mark_end + 1, sha1))
die("corrupt mark line: %s", line);
if (last_idnum < mark)
last_idnum = mark;
- object = parse_object(sha1);
- if (!object)
+ type = sha1_object_info(sha1, NULL);
+ if (type < 0)
+ die("object not found: %s", sha1_to_hex(sha1));
+
+ if (type != OBJ_COMMIT)
+ /* only commits */
continue;
+ commit = lookup_commit(sha1);
+ if (!commit)
+ die("not a commit? can't happen: %s", sha1_to_hex(sha1));
+
+ object = &commit->object;
+
if (object->flags & SHOWN)
error("Object %s already has a mark", sha1_to_hex(sha1));
- if (object->type != OBJ_COMMIT)
- /* only commits */
- continue;
-
mark_object(object, mark);
object->flags |= SHOWN;
@@ -649,7 +665,6 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
struct object_array commits = OBJECT_ARRAY_INIT;
- struct string_list extra_refs = STRING_LIST_INIT_NODUP;
struct commit *commit;
char *export_filename = NULL, *import_filename = NULL;
uint32_t lastimportid;
@@ -666,11 +681,11 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
N_("Dump marks to this file")),
OPT_STRING(0, "import-marks", &import_filename, N_("file"),
N_("Import marks from this file")),
- OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
- N_("Fake a tagger when tags lack one")),
- OPT_BOOLEAN(0, "full-tree", &full_tree,
- N_("Output full tree for each commit")),
- OPT_BOOLEAN(0, "use-done-feature", &use_done_feature,
+ OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger,
+ N_("Fake a tagger when tags lack one")),
+ OPT_BOOL(0, "full-tree", &full_tree,
+ 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")),
OPT_END()
@@ -701,7 +716,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
if (import_filename && revs.prune_data.nr)
full_tree = 1;
- get_tags_and_duplicates(&revs.cmdline, &extra_refs);
+ get_tags_and_duplicates(&revs.cmdline);
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
@@ -717,7 +732,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
}
}
- handle_tags_and_duplicates(&extra_refs);
+ handle_tags_and_duplicates();
if (export_filename && lastimportid != last_idnum)
export_marks(export_filename);
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index aba4465552..1262b405f8 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -1,16 +1,26 @@
#include "builtin.h"
#include "pkt-line.h"
#include "fetch-pack.h"
+#include "remote.h"
+#include "connect.h"
+#include "sha1-array.h"
static const char fetch_pack_usage[] =
"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
"[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
-"[--no-progress] [-v] [<host>:]<directory> [<refs>...]";
+"[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]";
static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc,
const char *name, int namelen)
{
struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1);
+ unsigned char sha1[20];
+
+ if (namelen > 41 && name[40] == ' ' && !get_sha1_hex(name, sha1)) {
+ hashcpy(ref->old_sha1, sha1);
+ name += 41;
+ namelen -= 41;
+ }
memcpy(ref->name, name, namelen);
ref->name[namelen] = '\0';
@@ -37,6 +47,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
char **pack_lockfile_ptr = NULL;
struct child_process *conn;
struct fetch_pack_args args;
+ struct sha1_array shallow = SHA1_ARRAY_INIT;
packet_trace_identity("fetch-pack");
@@ -46,11 +57,11 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc && *argv[i] == '-'; i++) {
const char *arg = argv[i];
- if (!prefixcmp(arg, "--upload-pack=")) {
+ if (starts_with(arg, "--upload-pack=")) {
args.uploadpack = arg + 14;
continue;
}
- if (!prefixcmp(arg, "--exec=")) {
+ if (starts_with(arg, "--exec=")) {
args.uploadpack = arg + 7;
continue;
}
@@ -79,11 +90,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
args.stdin_refs = 1;
continue;
}
+ if (!strcmp("--diag-url", arg)) {
+ args.diag_url = 1;
+ continue;
+ }
if (!strcmp("-v", arg)) {
args.verbose = 1;
continue;
}
- if (!prefixcmp(arg, "--depth=")) {
+ if (starts_with(arg, "--depth=")) {
args.depth = strtol(arg + 8, NULL, 0);
continue;
}
@@ -100,6 +115,18 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
pack_lockfile_ptr = &pack_lockfile;
continue;
}
+ if (!strcmp("--check-self-contained-and-connected", arg)) {
+ args.check_self_contained_and_connected = 1;
+ continue;
+ }
+ if (!strcmp("--cloning", arg)) {
+ args.cloning = 1;
+ continue;
+ }
+ if (!strcmp("--update-shallow", arg)) {
+ args.update_shallow = 1;
+ continue;
+ }
usage(fetch_pack_usage);
}
@@ -140,18 +167,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
fd[0] = 0;
fd[1] = 1;
} else {
+ int flags = args.verbose ? CONNECT_VERBOSE : 0;
+ if (args.diag_url)
+ flags |= CONNECT_DIAG_URL;
conn = git_connect(fd, dest, args.uploadpack,
- args.verbose ? CONNECT_VERBOSE : 0);
+ flags);
+ if (!conn)
+ return args.diag_url ? 0 : 1;
}
+ get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL, &shallow);
- get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL);
-
- ref = fetch_pack(&args, fd, conn, ref, dest,
- sought, nr_sought, pack_lockfile_ptr);
+ ref = fetch_pack(&args, fd, conn, ref, dest, sought, nr_sought,
+ &shallow, pack_lockfile_ptr);
if (pack_lockfile) {
printf("lock %s\n", pack_lockfile);
fflush(stdout);
}
+ if (args.check_self_contained_and_connected &&
+ args.self_contained_and_connected) {
+ printf("connectivity-ok\n");
+ fflush(stdout);
+ }
close(fd[0]);
close(fd[1]);
if (finish_connect(conn))
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 4b6b1dfe66..55f457c04f 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -30,15 +30,21 @@ enum {
TAGS_SET = 2
};
-static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
+static int fetch_prune_config = -1; /* unspecified */
+static int prune = -1; /* unspecified */
+#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
+
+static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
-static int tags = TAGS_DEFAULT, unshallow;
+static int tags = TAGS_DEFAULT, unshallow, update_shallow;
static const char *depth;
static const char *upload_pack;
static struct strbuf default_rla = STRBUF_INIT;
-static struct transport *transport;
+static struct transport *gtransport;
+static struct transport *gsecondary;
static const char *submodule_prefix = "";
static const char *recurse_submodules_default;
+static int shown_url = 0;
static int option_parse_recurse_submodules(const struct option *opt,
const char *arg, int unset)
@@ -54,30 +60,39 @@ static int option_parse_recurse_submodules(const struct option *opt,
return 0;
}
+static int git_fetch_config(const char *k, const char *v, void *cb)
+{
+ if (!strcmp(k, "fetch.prune")) {
+ fetch_prune_config = git_config_bool(k, v);
+ return 0;
+ }
+ return 0;
+}
+
static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
- OPT_BOOLEAN(0, "all", &all,
- N_("fetch from all remotes")),
- OPT_BOOLEAN('a', "append", &append,
- N_("append to .git/FETCH_HEAD instead of overwriting")),
+ OPT_BOOL(0, "all", &all,
+ N_("fetch from all remotes")),
+ OPT_BOOL('a', "append", &append,
+ N_("append to .git/FETCH_HEAD instead of overwriting")),
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 branch")),
- OPT_BOOLEAN('m', "multiple", &multiple,
- N_("fetch from multiple remotes")),
+ OPT_BOOL('m', "multiple", &multiple,
+ N_("fetch from multiple remotes")),
OPT_SET_INT('t', "tags", &tags,
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_BOOLEAN('p', "prune", &prune,
- N_("prune remote-tracking branches no longer on remote")),
+ OPT_BOOL('p', "prune", &prune,
+ N_("prune remote-tracking branches no longer on remote")),
{ OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"),
N_("control recursive fetching of submodules"),
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
- OPT_BOOLEAN(0, "dry-run", &dry_run,
- N_("dry run")),
- OPT_BOOLEAN('k', "keep", &keep, N_("keep downloaded pack")),
- OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
+ OPT_BOOL(0, "dry-run", &dry_run,
+ N_("dry run")),
+ OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")),
+ OPT_BOOL('u', "update-head-ok", &update_head_ok,
N_("allow updating of HEAD ref")),
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
OPT_STRING(0, "depth", &depth, N_("depth"),
@@ -90,13 +105,17 @@ static struct option builtin_fetch_options[] = {
{ OPTION_STRING, 0, "recurse-submodules-default",
&recurse_submodules_default, NULL,
N_("default mode for recursion"), PARSE_OPT_HIDDEN },
+ OPT_BOOL(0, "update-shallow", &update_shallow,
+ N_("accept refs that update .git/shallow")),
OPT_END()
};
static void unlock_pack(void)
{
- if (transport)
- transport_unlock_pack(transport);
+ if (gtransport)
+ transport_unlock_pack(gtransport);
+ if (gsecondary)
+ transport_unlock_pack(gsecondary);
}
static void unlock_pack_on_signal(int signo)
@@ -119,7 +138,7 @@ static void add_merge_config(struct ref **head,
for (rm = *head; rm; rm = rm->next) {
if (branch_merge_matches(branch, i, rm->name)) {
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
break;
}
}
@@ -140,34 +159,158 @@ static void add_merge_config(struct ref **head,
refspec.src = branch->merge[i]->src;
get_fetch_map(remote_refs, &refspec, tail, 1);
for (rm = *old_tail; rm; rm = rm->next)
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
}
}
+static int add_existing(const char *refname, const unsigned char *sha1,
+ int flag, void *cbdata)
+{
+ struct string_list *list = (struct string_list *)cbdata;
+ struct string_list_item *item = string_list_insert(list, refname);
+ item->util = xmalloc(20);
+ hashcpy(item->util, sha1);
+ return 0;
+}
+
+static int will_fetch(struct ref **head, const unsigned char *sha1)
+{
+ struct ref *rm = *head;
+ while (rm) {
+ if (!hashcmp(rm->old_sha1, sha1))
+ return 1;
+ rm = rm->next;
+ }
+ return 0;
+}
+
static void find_non_local_tags(struct transport *transport,
struct ref **head,
- struct ref ***tail);
+ struct ref ***tail)
+{
+ struct string_list existing_refs = STRING_LIST_INIT_DUP;
+ struct string_list remote_refs = STRING_LIST_INIT_NODUP;
+ const struct ref *ref;
+ struct string_list_item *item = NULL;
+
+ for_each_ref(add_existing, &existing_refs);
+ for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
+ if (!starts_with(ref->name, "refs/tags/"))
+ continue;
+
+ /*
+ * The peeled ref always follows the matching base
+ * ref, so if we see a peeled ref that we don't want
+ * to fetch then we can mark the ref entry in the list
+ * as one to ignore by setting util to NULL.
+ */
+ if (ends_with(ref->name, "^{}")) {
+ if (item && !has_sha1_file(ref->old_sha1) &&
+ !will_fetch(head, ref->old_sha1) &&
+ !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+ item = NULL;
+ continue;
+ }
+
+ /*
+ * If item is non-NULL here, then we previously saw a
+ * ref not followed by a peeled reference, so we need
+ * to check if it is a lightweight tag that we want to
+ * fetch.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ item = NULL;
+
+ /* skip duplicates and refs that we already have */
+ if (string_list_has_string(&remote_refs, ref->name) ||
+ string_list_has_string(&existing_refs, ref->name))
+ continue;
+
+ item = string_list_insert(&remote_refs, ref->name);
+ item->util = (void *)ref->old_sha1;
+ }
+ string_list_clear(&existing_refs, 1);
+
+ /*
+ * We may have a final lightweight tag that needs to be
+ * checked to see if it needs fetching.
+ */
+ if (item && !has_sha1_file(item->util) &&
+ !will_fetch(head, item->util))
+ item->util = NULL;
+
+ /*
+ * For all the tags in the remote_refs string list,
+ * add them to the list of refs to be fetched
+ */
+ for_each_string_list_item(item, &remote_refs) {
+ /* Unless we have already decided to ignore this item... */
+ if (item->util)
+ {
+ struct ref *rm = alloc_ref(item->string);
+ rm->peer_ref = alloc_ref(item->string);
+ hashcpy(rm->old_sha1, item->util);
+ **tail = rm;
+ *tail = &rm->next;
+ }
+ }
+
+ string_list_clear(&remote_refs, 0);
+}
static struct ref *get_ref_map(struct transport *transport,
- struct refspec *refs, int ref_count, int tags,
- int *autotags)
+ struct refspec *refspecs, int refspec_count,
+ int tags, int *autotags)
{
int i;
struct ref *rm;
struct ref *ref_map = NULL;
struct ref **tail = &ref_map;
+ /* opportunistically-updated references: */
+ struct ref *orefs = NULL, **oref_tail = &orefs;
+
const struct ref *remote_refs = transport_get_remote_refs(transport);
- if (ref_count || tags == TAGS_SET) {
- for (i = 0; i < ref_count; i++) {
- get_fetch_map(remote_refs, &refs[i], &tail, 0);
- if (refs[i].dst && refs[i].dst[0])
+ if (refspec_count) {
+ for (i = 0; i < refspec_count; i++) {
+ get_fetch_map(remote_refs, &refspecs[i], &tail, 0);
+ if (refspecs[i].dst && refspecs[i].dst[0])
*autotags = 1;
}
- /* Merge everything on the command line, but not --tags */
+ /* Merge everything on the command line (but not --tags) */
for (rm = ref_map; rm; rm = rm->next)
- rm->merge = 1;
+ rm->fetch_head_status = FETCH_HEAD_MERGE;
+
+ /*
+ * For any refs that we happen to be fetching via
+ * command-line arguments, the destination ref might
+ * have been missing or have been different than the
+ * remote-tracking ref that would be derived from the
+ * configured refspec. In these cases, we want to
+ * take the opportunity to update their configured
+ * remote-tracking reference. However, we do not want
+ * to mention these entries in FETCH_HEAD at all, as
+ * they would simply be duplicates of existing
+ * entries, so we set them FETCH_HEAD_IGNORE below.
+ *
+ * We compute these entries now, based only on the
+ * refspecs specified on the command line. But we add
+ * them to the list following the refspecs resulting
+ * from the tags option so that one of the latter,
+ * which has FETCH_HEAD_NOT_FOR_MERGE, is not removed
+ * by ref_remove_duplicates() in favor of one of these
+ * opportunistic entries with FETCH_HEAD_IGNORE.
+ */
+ for (i = 0; i < transport->remote->fetch_refspec_nr; i++)
+ get_fetch_map(ref_map, &transport->remote->fetch[i],
+ &oref_tail, 1);
+
if (tags == TAGS_SET)
get_fetch_map(remote_refs, tag_refspec, &tail, 0);
} else {
@@ -186,7 +329,7 @@ static struct ref *get_ref_map(struct transport *transport,
*autotags = 1;
if (!i && !has_merge && ref_map &&
!remote->fetch[0].pattern)
- ref_map->merge = 1;
+ ref_map->fetch_head_status = FETCH_HEAD_MERGE;
}
/*
* if the remote we're fetching from is the same
@@ -202,15 +345,25 @@ static struct ref *get_ref_map(struct transport *transport,
ref_map = get_remote_ref(remote_refs, "HEAD");
if (!ref_map)
die(_("Couldn't find remote ref HEAD"));
- ref_map->merge = 1;
+ ref_map->fetch_head_status = FETCH_HEAD_MERGE;
tail = &ref_map->next;
}
}
- if (tags == TAGS_DEFAULT && *autotags)
+
+ if (tags == TAGS_SET)
+ /* also fetch all tags */
+ get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+ else if (tags == TAGS_DEFAULT && *autotags)
find_non_local_tags(transport, &ref_map, &tail);
- ref_remove_duplicates(ref_map);
- return ref_map;
+ /* Now append any refs to be updated opportunistically: */
+ *tail = orefs;
+ for (rm = orefs; rm; rm = rm->next) {
+ rm->fetch_head_status = FETCH_HEAD_IGNORE;
+ tail = &rm->next;
+ }
+
+ return ref_remove_duplicates(ref_map);
}
#define STORE_REF_ERROR_OTHER 1
@@ -230,7 +383,8 @@ static int s_update_ref(const char *action,
rla = default_rla.buf;
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
lock = lock_any_ref_for_update(ref->name,
- check_old ? ref->old_sha1 : NULL, 0);
+ check_old ? ref->old_sha1 : NULL,
+ 0, NULL);
if (!lock)
return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
STORE_REF_ERROR_OTHER;
@@ -280,7 +434,7 @@ static int update_local_ref(struct ref *ref,
}
if (!is_null_sha1(ref->old_sha1) &&
- !prefixcmp(ref->name, "refs/tags/")) {
+ starts_with(ref->name, "refs/tags/")) {
int r;
r = s_update_ref("updating tag", ref, 0);
strbuf_addf(display, "%c %-*s %-*s -> %s%s",
@@ -303,10 +457,10 @@ static int update_local_ref(struct ref *ref,
* more likely to follow a standard layout.
*/
const char *name = remote_ref ? remote_ref->name : "";
- if (!prefixcmp(name, "refs/tags/")) {
+ if (starts_with(name, "refs/tags/")) {
msg = "storing tag";
what = _("[new tag]");
- } else if (!prefixcmp(name, "refs/heads/")) {
+ } else if (starts_with(name, "refs/heads/")) {
msg = "storing head";
what = _("[new branch]");
} else {
@@ -372,6 +526,8 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
struct ref **rm = cb_data;
struct ref *ref = *rm;
+ while (ref && ref->status == REF_STATUS_REJECT_SHALLOW)
+ ref = ref->next;
if (!ref)
return -1; /* end of the list */
*rm = ref->next;
@@ -384,12 +540,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
{
FILE *fp;
struct commit *commit;
- int url_len, i, shown_url = 0, rc = 0;
+ int url_len, i, rc = 0;
struct strbuf note = STRBUF_INIT;
const char *what, *kind;
struct ref *rm;
char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
- int want_merge;
+ int want_status;
fp = fopen(filename, "a");
if (!fp)
@@ -407,19 +563,29 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
}
/*
- * The first pass writes objects to be merged and then the
- * second pass writes the rest, in order to allow using
- * FETCH_HEAD as a refname to refer to the ref to be merged.
+ * We do a pass for each fetch_head_status type in their enum order, so
+ * merged entries are written before not-for-merge. That lets readers
+ * use FETCH_HEAD as a refname to refer to the ref to be merged.
*/
- for (want_merge = 1; 0 <= want_merge; want_merge--) {
+ for (want_status = FETCH_HEAD_MERGE;
+ want_status <= FETCH_HEAD_IGNORE;
+ 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"),
+ rm->peer_ref ? rm->peer_ref->name : rm->name);
+ continue;
+ }
commit = lookup_commit_reference_gently(rm->old_sha1, 1);
if (!commit)
- rm->merge = 0;
+ rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
- if (rm->merge != want_merge)
+ if (rm->fetch_head_status != want_status)
continue;
if (rm->peer_ref) {
@@ -435,15 +601,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
kind = "";
what = "";
}
- else if (!prefixcmp(rm->name, "refs/heads/")) {
+ else if (starts_with(rm->name, "refs/heads/")) {
kind = "branch";
what = rm->name + 11;
}
- else if (!prefixcmp(rm->name, "refs/tags/")) {
+ else if (starts_with(rm->name, "refs/tags/")) {
kind = "tag";
what = rm->name + 10;
}
- else if (!prefixcmp(rm->name, "refs/remotes/")) {
+ else if (starts_with(rm->name, "refs/remotes/")) {
kind = "remote-tracking branch";
what = rm->name + 13;
}
@@ -465,16 +631,26 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
strbuf_addf(&note, "%s ", kind);
strbuf_addf(&note, "'%s' of ", what);
}
- fprintf(fp, "%s\t%s\t%s",
- sha1_to_hex(rm->old_sha1),
- rm->merge ? "" : "not-for-merge",
- note.buf);
- for (i = 0; i < url_len; ++i)
- if ('\n' == url[i])
- fputs("\\n", fp);
- else
- fputc(url[i], fp);
- fputc('\n', fp);
+ 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",
+ sha1_to_hex(rm->old_sha1),
+ 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;
+ }
strbuf_reset(&note);
if (ref) {
@@ -544,17 +720,36 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
return ret;
}
-static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
+static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
+ const char *raw_url)
{
- int result = 0;
+ int url_len, i, result = 0;
struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
+ char *url;
const char *dangling_msg = dry_run
? _(" (%s will become dangling)")
: _(" (%s has become dangling)");
+ if (raw_url)
+ url = transport_anonymize_url(raw_url);
+ else
+ url = xstrdup("foreign");
+
+ url_len = strlen(url);
+ for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
+ ;
+
+ url_len = i + 1;
+ if (4 < i && !strncmp(".git", url + i - 3, 4))
+ url_len = i - 3;
+
for (ref = stale_refs; ref; ref = ref->next) {
if (!dry_run)
result |= delete_ref(ref->name, NULL, 0);
+ if (verbosity >= 0 && !shown_url) {
+ fprintf(stderr, _("From %.*s\n"), url_len, url);
+ shown_url = 1;
+ }
if (verbosity >= 0) {
fprintf(stderr, " x %-*s %-*s -> %s\n",
TRANSPORT_SUMMARY(_("[deleted]")),
@@ -562,109 +757,11 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
warn_dangling_symref(stderr, dangling_msg, ref->name);
}
}
+ free(url);
free_refs(stale_refs);
return result;
}
-static int add_existing(const char *refname, const unsigned char *sha1,
- int flag, void *cbdata)
-{
- struct string_list *list = (struct string_list *)cbdata;
- struct string_list_item *item = string_list_insert(list, refname);
- item->util = (void *)sha1;
- return 0;
-}
-
-static int will_fetch(struct ref **head, const unsigned char *sha1)
-{
- struct ref *rm = *head;
- while (rm) {
- if (!hashcmp(rm->old_sha1, sha1))
- return 1;
- rm = rm->next;
- }
- return 0;
-}
-
-static void find_non_local_tags(struct transport *transport,
- struct ref **head,
- struct ref ***tail)
-{
- struct string_list existing_refs = STRING_LIST_INIT_NODUP;
- struct string_list remote_refs = STRING_LIST_INIT_NODUP;
- const struct ref *ref;
- struct string_list_item *item = NULL;
-
- for_each_ref(add_existing, &existing_refs);
- for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
- if (prefixcmp(ref->name, "refs/tags/"))
- continue;
-
- /*
- * The peeled ref always follows the matching base
- * ref, so if we see a peeled ref that we don't want
- * to fetch then we can mark the ref entry in the list
- * as one to ignore by setting util to NULL.
- */
- if (!suffixcmp(ref->name, "^{}")) {
- if (item && !has_sha1_file(ref->old_sha1) &&
- !will_fetch(head, ref->old_sha1) &&
- !has_sha1_file(item->util) &&
- !will_fetch(head, item->util))
- item->util = NULL;
- item = NULL;
- continue;
- }
-
- /*
- * If item is non-NULL here, then we previously saw a
- * ref not followed by a peeled reference, so we need
- * to check if it is a lightweight tag that we want to
- * fetch.
- */
- if (item && !has_sha1_file(item->util) &&
- !will_fetch(head, item->util))
- item->util = NULL;
-
- item = NULL;
-
- /* skip duplicates and refs that we already have */
- if (string_list_has_string(&remote_refs, ref->name) ||
- string_list_has_string(&existing_refs, ref->name))
- continue;
-
- item = string_list_insert(&remote_refs, ref->name);
- item->util = (void *)ref->old_sha1;
- }
- string_list_clear(&existing_refs, 0);
-
- /*
- * We may have a final lightweight tag that needs to be
- * checked to see if it needs fetching.
- */
- if (item && !has_sha1_file(item->util) &&
- !will_fetch(head, item->util))
- item->util = NULL;
-
- /*
- * For all the tags in the remote_refs string list,
- * add them to the list of refs to be fetched
- */
- for_each_string_list_item(item, &remote_refs) {
- /* Unless we have already decided to ignore this item... */
- if (item->util)
- {
- struct ref *rm = alloc_ref(item->string);
- rm->peer_ref = alloc_ref(item->string);
- hashcpy(rm->old_sha1, item->util);
- **tail = rm;
- *tail = &rm->next;
- }
- }
-
- string_list_clear(&remote_refs, 0);
-}
-
static void check_not_current_branch(struct ref *ref_map)
{
struct branch *current_branch = branch_get(NULL);
@@ -690,14 +787,58 @@ static int truncate_fetch_head(void)
return 0;
}
+static void set_option(struct transport *transport, const char *name, const char *value)
+{
+ int r = transport_set_option(transport, name, value);
+ if (r < 0)
+ die(_("Option \"%s\" value \"%s\" is not valid for %s"),
+ name, value, transport->url);
+ if (r > 0)
+ warning(_("Option \"%s\" is ignored for %s\n"),
+ name, transport->url);
+}
+
+static struct transport *prepare_transport(struct remote *remote)
+{
+ struct transport *transport;
+ transport = transport_get(remote, NULL);
+ transport_set_verbosity(transport, verbosity, progress);
+ if (upload_pack)
+ set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack);
+ if (keep)
+ set_option(transport, TRANS_OPT_KEEP, "yes");
+ if (depth)
+ set_option(transport, TRANS_OPT_DEPTH, depth);
+ if (update_shallow)
+ set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
+ return transport;
+}
+
+static void backfill_tags(struct transport *transport, struct ref *ref_map)
+{
+ if (transport->cannot_reuse) {
+ gsecondary = prepare_transport(transport->remote);
+ transport = gsecondary;
+ }
+
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+ transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+ fetch_refs(transport, ref_map);
+
+ if (gsecondary) {
+ transport_disconnect(gsecondary);
+ gsecondary = NULL;
+ }
+}
+
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
- struct string_list existing_refs = STRING_LIST_INIT_NODUP;
- struct string_list_item *peer_item = NULL;
+ struct string_list existing_refs = STRING_LIST_INIT_DUP;
struct ref *ref_map;
struct ref *rm;
int autotags = (transport->remote->fetch_tags == 1);
+ int retcode = 0;
for_each_ref(add_existing, &existing_refs);
@@ -713,9 +854,9 @@ static int do_fetch(struct transport *transport,
/* if not appending, truncate FETCH_HEAD */
if (!append && !dry_run) {
- int errcode = truncate_fetch_head();
- if (errcode)
- return errcode;
+ retcode = truncate_fetch_head();
+ if (retcode)
+ goto cleanup;
}
ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
@@ -724,8 +865,9 @@ static int do_fetch(struct transport *transport,
for (rm = ref_map; rm; rm = rm->next) {
if (rm->peer_ref) {
- peer_item = string_list_lookup(&existing_refs,
- rm->peer_ref->name);
+ struct string_list_item *peer_item =
+ string_list_lookup(&existing_refs,
+ rm->peer_ref->name);
if (peer_item)
hashcpy(rm->peer_ref->old_sha1,
peer_item->util);
@@ -734,35 +876,26 @@ static int do_fetch(struct transport *transport,
if (tags == TAGS_DEFAULT && autotags)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
- if (fetch_refs(transport, ref_map)) {
- free_refs(ref_map);
- return 1;
- }
if (prune) {
- /* If --tags was specified, pretend the user gave us the canonical tags refspec */
- if (tags == TAGS_SET) {
- const char *tags_str = "refs/tags/*:refs/tags/*";
- struct refspec *tags_refspec, *refspec;
-
- /* Copy the refspec and add the tags to it */
- refspec = xcalloc(ref_count + 1, sizeof(struct refspec));
- tags_refspec = parse_fetch_refspec(1, &tags_str);
- memcpy(refspec, refs, ref_count * sizeof(struct refspec));
- memcpy(&refspec[ref_count], tags_refspec, sizeof(struct refspec));
- ref_count++;
-
- prune_refs(refspec, ref_count, ref_map);
-
- ref_count--;
- /* The rest of the strings belong to fetch_one */
- free_refspec(1, tags_refspec);
- free(refspec);
- } else if (ref_count) {
- prune_refs(refs, ref_count, ref_map);
+ /*
+ * We only prune based on refspecs specified
+ * explicitly (via command line or configuration); we
+ * don't care whether --tags was specified.
+ */
+ if (ref_count) {
+ prune_refs(refs, ref_count, ref_map, transport->url);
} else {
- prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map);
+ prune_refs(transport->remote->fetch,
+ transport->remote->fetch_refspec_nr,
+ ref_map,
+ transport->url);
}
}
+ if (fetch_refs(transport, ref_map)) {
+ free_refs(ref_map);
+ retcode = 1;
+ goto cleanup;
+ }
free_refs(ref_map);
/* if neither --no-tags nor --tags was specified, do automated tag
@@ -771,26 +904,14 @@ static int do_fetch(struct transport *transport,
struct ref **tail = &ref_map;
ref_map = NULL;
find_non_local_tags(transport, &ref_map, &tail);
- if (ref_map) {
- transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
- transport_set_option(transport, TRANS_OPT_DEPTH, "0");
- fetch_refs(transport, ref_map);
- }
+ if (ref_map)
+ backfill_tags(transport, ref_map);
free_refs(ref_map);
}
- return 0;
-}
-
-static void set_option(const char *name, const char *value)
-{
- int r = transport_set_option(transport, name, value);
- if (r < 0)
- die(_("Option \"%s\" value \"%s\" is not valid for %s"),
- name, value, transport->url);
- if (r > 0)
- warning(_("Option \"%s\" is ignored for %s\n"),
- name, transport->url);
+ cleanup:
+ string_list_clear(&existing_refs, 1);
+ return retcode;
}
static int get_one_remote_for_fetch(struct remote *remote, void *priv)
@@ -810,7 +931,7 @@ static int get_remote_group(const char *key, const char *value, void *priv)
{
struct remote_group_data *g = priv;
- if (!prefixcmp(key, "remotes.") &&
+ if (starts_with(key, "remotes.") &&
!strcmp(key + 8, g->name)) {
/* split list by white space */
int space = strcspn(value, " \t\n");
@@ -848,8 +969,8 @@ static void add_options_to_argv(struct argv_array *argv)
{
if (dry_run)
argv_array_push(argv, "--dry-run");
- if (prune)
- argv_array_push(argv, "--prune");
+ if (prune != -1)
+ argv_array_push(argv, prune ? "--prune" : "--no-prune");
if (update_head_ok)
argv_array_push(argv, "--update-head-ok");
if (force)
@@ -905,7 +1026,6 @@ static int fetch_multiple(struct string_list *list)
static int fetch_one(struct remote *remote, int argc, const char **argv)
{
- int i;
static const char **refs = NULL;
struct refspec *refspec;
int ref_nr = 0;
@@ -915,17 +1035,21 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
die(_("No remote repository specified. Please, specify either a URL or a\n"
"remote name from which new revisions should be fetched."));
- transport = transport_get(remote, NULL);
- transport_set_verbosity(transport, verbosity, progress);
- if (upload_pack)
- set_option(TRANS_OPT_UPLOADPACK, upload_pack);
- if (keep)
- set_option(TRANS_OPT_KEEP, "yes");
- if (depth)
- set_option(TRANS_OPT_DEPTH, depth);
+ gtransport = prepare_transport(remote);
+
+ if (prune < 0) {
+ /* no command line request */
+ if (0 <= gtransport->remote->prune)
+ prune = gtransport->remote->prune;
+ else if (0 <= fetch_prune_config)
+ prune = fetch_prune_config;
+ else
+ prune = PRUNE_BY_DEFAULT;
+ }
if (argc > 0) {
int j = 0;
+ int i;
refs = xcalloc(argc + 1, sizeof(const char *));
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "tag")) {
@@ -949,10 +1073,10 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
sigchain_push_common(unlock_pack_on_signal);
atexit(unlock_pack);
refspec = parse_fetch_refspec(ref_nr, refs);
- exit_code = do_fetch(transport, refspec, ref_nr);
+ exit_code = do_fetch(gtransport, refspec, ref_nr);
free_refspec(ref_nr, refspec);
- transport_disconnect(transport);
- transport = NULL;
+ transport_disconnect(gtransport);
+ gtransport = NULL;
return exit_code;
}
@@ -973,6 +1097,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc; i++)
strbuf_addf(&default_rla, " %s", argv[i]);
+ git_config(git_fetch_config, NULL);
+
argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);
@@ -988,6 +1114,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
}
+ /* no need to be strict, transport_set_option() will validate it again */
+ if (depth && atoi(depth) < 1)
+ die(_("depth %s is not a positive number"), depth);
+
if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
if (recurse_submodules_default) {
int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index 1c04070869..3906eda877 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -109,7 +109,7 @@ static int handle_line(char *line, struct merge_parents *merge_parents)
if (len < 43 || line[40] != '\t')
return 1;
- if (!prefixcmp(line + 41, "not-for-merge"))
+ if (starts_with(line + 41, "not-for-merge"))
return 0;
if (line[41] != '\t')
@@ -155,16 +155,16 @@ static int handle_line(char *line, struct merge_parents *merge_parents)
if (pulling_head) {
origin = src;
src_data->head_status |= 1;
- } else if (!prefixcmp(line, "branch ")) {
+ } 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 (!prefixcmp(line, "tag ")) {
+ } else if (starts_with(line, "tag ")) {
origin = line;
string_list_append(&src_data->tag, origin + 4);
src_data->head_status |= 2;
- } else if (!prefixcmp(line, "remote-tracking branch ")) {
+ } else if (starts_with(line, "remote-tracking branch ")) {
origin = line + strlen("remote-tracking branch ");
string_list_append(&src_data->r_branch, origin);
src_data->head_status |= 2;
@@ -605,7 +605,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
resolve_refdup("HEAD", head_sha1, 1, NULL);
if (!current_branch)
die("No current branch");
- if (!prefixcmp(current_branch, "refs/heads/"))
+ if (starts_with(current_branch, "refs/heads/"))
current_branch += 11;
find_merge_parents(&merge_parents, in, head_sha1);
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 7f059c31df..51798b48b5 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -9,6 +9,7 @@
#include "quote.h"
#include "parse-options.h"
#include "remote.h"
+#include "color.h"
/* Quoting styles */
#define QUOTE_NONE 0
@@ -75,6 +76,8 @@ static struct {
{ "upstream" },
{ "symref" },
{ "flag" },
+ { "HEAD" },
+ { "color" },
};
/*
@@ -89,7 +92,8 @@ static struct {
*/
static const char **used_atom;
static cmp_type *used_atom_type;
-static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref;
+static int used_atom_cnt, need_tagged, need_symref;
+static int need_color_reset_at_eol;
/*
* Used to parse format string and sort specifiers
@@ -176,13 +180,21 @@ static const char *find_next(const char *cp)
static int verify_format(const char *format)
{
const char *cp, *sp;
+ static const char color_reset[] = "color:reset";
+
+ need_color_reset_at_eol = 0;
for (cp = format; *cp && (sp = find_next(cp)); ) {
const char *ep = strchr(sp, ')');
+ int at;
+
if (!ep)
return error("malformed format string %s", sp);
/* sp points at "%(" and ep points at the closing ")" */
- parse_atom(sp + 2, ep);
+ at = parse_atom(sp + 2, ep);
cp = ep + 1;
+
+ if (!memcmp(used_atom[at], "color:", 6))
+ need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset);
}
return 0;
}
@@ -205,6 +217,22 @@ static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned lo
return buf;
}
+static int grab_objectname(const char *name, const unsigned char *sha1,
+ struct atom_value *v)
+{
+ if (!strcmp(name, "objectname")) {
+ char *s = xmalloc(41);
+ strcpy(s, sha1_to_hex(sha1));
+ v->s = s;
+ return 1;
+ }
+ if (!strcmp(name, "objectname:short")) {
+ v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ return 1;
+ }
+ return 0;
+}
+
/* See grab_values */
static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
{
@@ -225,15 +253,8 @@ static void grab_common_values(struct atom_value *val, int deref, struct object
v->ul = sz;
v->s = s;
}
- else if (!strcmp(name, "objectname")) {
- char *s = xmalloc(41);
- strcpy(s, sha1_to_hex(obj->sha1));
- v->s = s;
- }
- else if (!strcmp(name, "objectname:short")) {
- v->s = xstrdup(find_unique_abbrev(obj->sha1,
- DEFAULT_ABBREV));
- }
+ else if (deref)
+ grab_objectname(name, obj->sha1, v);
}
}
@@ -432,7 +453,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
if (name[wholen] != 0 &&
strcmp(name + wholen, "name") &&
strcmp(name + wholen, "email") &&
- prefixcmp(name + wholen, "date"))
+ !starts_with(name + wholen, "date"))
continue;
if (!wholine)
wholine = find_wholine(who, wholen, buf, sz);
@@ -444,7 +465,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
v->s = copy_name(wholine);
else if (!strcmp(name + wholen, "email"))
v->s = copy_email(wholine);
- else if (!prefixcmp(name + wholen, "date"))
+ else if (starts_with(name + wholen, "date"))
grab_date(wholine, v, name);
}
@@ -466,7 +487,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
if (deref)
name++;
- if (!prefixcmp(name, "creatordate"))
+ if (starts_with(name, "creatordate"))
grab_date(wholine, v, name);
else if (!strcmp(name, "creator"))
v->s = copy_line(wholine);
@@ -640,20 +661,20 @@ static void populate_value(struct refinfo *ref)
int deref = 0;
const char *refname;
const char *formatp;
+ struct branch *branch = NULL;
if (*name == '*') {
deref = 1;
name++;
}
- if (!prefixcmp(name, "refname"))
+ if (starts_with(name, "refname"))
refname = ref->refname;
- else if (!prefixcmp(name, "symref"))
+ else if (starts_with(name, "symref"))
refname = ref->symref ? ref->symref : "";
- else if (!prefixcmp(name, "upstream")) {
- struct branch *branch;
+ else if (starts_with(name, "upstream")) {
/* only local branches may have an upstream */
- if (prefixcmp(ref->refname, "refs/heads/"))
+ if (!starts_with(ref->refname, "refs/heads/"))
continue;
branch = branch_get(ref->refname + 11);
@@ -661,8 +682,13 @@ static void populate_value(struct refinfo *ref)
!branch->merge[0]->dst)
continue;
refname = branch->merge[0]->dst;
- }
- else if (!strcmp(name, "flag")) {
+ } else if (starts_with(name, "color:")) {
+ char color[COLOR_MAXLEN] = "";
+
+ color_parse(name + 6, "--format", color);
+ v->s = xstrdup(color);
+ continue;
+ } else if (!strcmp(name, "flag")) {
char buf[256], *cp = buf;
if (ref->flag & REF_ISSYMREF)
cp = copy_advance(cp, ",symref");
@@ -675,18 +701,62 @@ static void populate_value(struct refinfo *ref)
v->s = xstrdup(buf + 1);
}
continue;
- }
- else
+ } else if (!deref && grab_objectname(name, ref->objectname, v)) {
+ continue;
+ } else if (!strcmp(name, "HEAD")) {
+ const char *head;
+ unsigned char sha1[20];
+
+ head = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
+ if (!strcmp(ref->refname, head))
+ v->s = "*";
+ else
+ v->s = " ";
+ continue;
+ } else
continue;
formatp = strchr(name, ':');
- /* look for "short" refname format */
if (formatp) {
+ int num_ours, num_theirs;
+
formatp++;
if (!strcmp(formatp, "short"))
refname = shorten_unambiguous_ref(refname,
warn_ambiguous_refs);
- else
+ else if (!strcmp(formatp, "track") &&
+ starts_with(name, "upstream")) {
+ char buf[40];
+
+ stat_tracking_info(branch, &num_ours, &num_theirs);
+ if (!num_ours && !num_theirs)
+ v->s = "";
+ else if (!num_ours) {
+ sprintf(buf, "[behind %d]", num_theirs);
+ v->s = xstrdup(buf);
+ } else if (!num_theirs) {
+ sprintf(buf, "[ahead %d]", num_ours);
+ v->s = xstrdup(buf);
+ } else {
+ sprintf(buf, "[ahead %d, behind %d]",
+ num_ours, num_theirs);
+ v->s = xstrdup(buf);
+ }
+ continue;
+ } else if (!strcmp(formatp, "trackshort") &&
+ starts_with(name, "upstream")) {
+ assert(branch);
+ stat_tracking_info(branch, &num_ours, &num_theirs);
+ if (!num_ours && !num_theirs)
+ v->s = "=";
+ else if (!num_ours)
+ v->s = "<";
+ else if (!num_theirs)
+ v->s = ">";
+ else
+ v->s = "<>";
+ continue;
+ } else
die("unknown %.*s format %s",
(int)(formatp - name), name, formatp);
}
@@ -864,27 +934,30 @@ static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs
qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
}
-static void print_value(struct refinfo *ref, int atom, int quote_style)
+static void print_value(struct atom_value *v, int quote_style)
{
- struct atom_value *v;
- get_value(ref, atom, &v);
+ struct strbuf sb = STRBUF_INIT;
switch (quote_style) {
case QUOTE_NONE:
fputs(v->s, stdout);
break;
case QUOTE_SHELL:
- sq_quote_print(stdout, v->s);
+ sq_quote_buf(&sb, v->s);
break;
case QUOTE_PERL:
- perl_quote_print(stdout, v->s);
+ perl_quote_buf(&sb, v->s);
break;
case QUOTE_PYTHON:
- python_quote_print(stdout, v->s);
+ python_quote_buf(&sb, v->s);
break;
case QUOTE_TCL:
- tcl_quote_print(stdout, v->s);
+ tcl_quote_buf(&sb, v->s);
break;
}
+ if (quote_style != QUOTE_NONE) {
+ fputs(sb.buf, stdout);
+ strbuf_release(&sb);
+ }
}
static int hex1(char ch)
@@ -930,15 +1003,26 @@ static void show_ref(struct refinfo *info, const char *format, int quote_style)
const char *cp, *sp, *ep;
for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) {
+ struct atom_value *atomv;
+
ep = strchr(sp, ')');
if (cp < sp)
emit(cp, sp);
- print_value(info, parse_atom(sp + 2, ep), quote_style);
+ get_value(info, parse_atom(sp + 2, ep), &atomv);
+ print_value(atomv, quote_style);
}
if (*cp) {
sp = cp + strlen(cp);
emit(cp, sp);
}
+ if (need_color_reset_at_eol) {
+ struct atom_value resetv;
+ char color[COLOR_MAXLEN] = "";
+
+ color_parse("reset", "--format", color);
+ resetv.s = color;
+ print_value(&resetv, quote_style);
+ }
putchar('\n');
}
@@ -1021,7 +1105,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
if (!sort)
sort = default_sort();
- sort_atom_limit = used_atom_cnt;
/* for warn_ambiguous_refs */
git_config(git_default_config, NULL);
diff --git a/builtin/fsck.c b/builtin/fsck.c
index bb9a2cd447..1affdd5e92 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -16,6 +16,7 @@
#define REACHABLE 0x0001
#define SEEN 0x0002
+#define HAS_OBJ 0x0004
static int show_root;
static int show_tags;
@@ -101,7 +102,7 @@ static int mark_object(struct object *obj, int type, void *data)
if (obj->flags & REACHABLE)
return 0;
obj->flags |= REACHABLE;
- if (!obj->parsed) {
+ if (!(obj->flags & HAS_OBJ)) {
if (parent && !has_sha1_file(obj->sha1)) {
printf("broken link from %7s %s\n",
typename(parent->type), sha1_to_hex(parent->sha1));
@@ -112,7 +113,7 @@ static int mark_object(struct object *obj, int type, void *data)
return 1;
}
- add_object_array(obj, (void *) parent, &pending);
+ add_object_array(obj, NULL, &pending);
return 0;
}
@@ -127,16 +128,13 @@ static int traverse_one_object(struct object *obj)
struct tree *tree = NULL;
if (obj->type == OBJ_TREE) {
- obj->parsed = 0;
tree = (struct tree *)obj;
if (parse_tree(tree) < 0)
return 1; /* error already displayed */
}
result = fsck_walk(obj, mark_object, obj);
- if (tree) {
- free(tree->buffer);
- tree->buffer = NULL;
- }
+ if (tree)
+ free_tree_buffer(tree);
return result;
}
@@ -178,7 +176,7 @@ static void check_reachable_object(struct object *obj)
* except if it was in a pack-file and we didn't
* do a full fsck
*/
- if (!obj->parsed) {
+ if (!(obj->flags & HAS_OBJ)) {
if (has_sha1_pack(obj->sha1))
return; /* it is in pack - forget about it */
printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
@@ -306,8 +304,7 @@ static int fsck_obj(struct object *obj)
if (obj->type == OBJ_TREE) {
struct tree *item = (struct tree *) obj;
- free(item->buffer);
- item->buffer = NULL;
+ free_tree_buffer(item);
}
if (obj->type == OBJ_COMMIT) {
@@ -340,6 +337,7 @@ static int fsck_sha1(const unsigned char *sha1)
return error("%s: object corrupt or missing",
sha1_to_hex(sha1));
}
+ obj->flags |= HAS_OBJ;
return fsck_obj(obj);
}
@@ -352,6 +350,7 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
errors_found |= ERROR_OBJECT;
return error("%s: object corrupt or missing", sha1_to_hex(sha1));
}
+ obj->flags = HAS_OBJ;
return fsck_obj(obj);
}
@@ -443,7 +442,7 @@ static void fsck_dir(int i, char *path)
add_sha1_list(sha1, DIRENT_SORT_HINT(de));
continue;
}
- if (!prefixcmp(de->d_name, "tmp_obj_"))
+ if (starts_with(de->d_name, "tmp_obj_"))
continue;
fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
}
@@ -485,7 +484,7 @@ static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, in
static int is_branch(const char *refname)
{
- return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
+ return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
}
static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
@@ -567,7 +566,7 @@ static int fsck_head_link(void)
if (!strcmp(head_points_at, "HEAD"))
/* detached HEAD */
null_is_error = 1;
- else if (prefixcmp(head_points_at, "refs/heads/"))
+ else if (!starts_with(head_points_at, "refs/heads/"))
return error("HEAD points to something strange (%s)",
head_points_at);
if (is_null_sha1(head_sha1)) {
@@ -611,15 +610,15 @@ static char const * const fsck_usage[] = {
static struct option fsck_opts[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
- OPT_BOOLEAN(0, "unreachable", &show_unreachable, N_("show unreachable objects")),
+ OPT_BOOL(0, "unreachable", &show_unreachable, N_("show unreachable objects")),
OPT_BOOL(0, "dangling", &show_dangling, N_("show dangling objects")),
- OPT_BOOLEAN(0, "tags", &show_tags, N_("report tags")),
- OPT_BOOLEAN(0, "root", &show_root, N_("report root nodes")),
- OPT_BOOLEAN(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
- OPT_BOOLEAN(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
- OPT_BOOLEAN(0, "full", &check_full, N_("also consider packs and alternate objects")),
- OPT_BOOLEAN(0, "strict", &check_strict, N_("enable more strict checking")),
- OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
+ OPT_BOOL(0, "tags", &show_tags, N_("report tags")),
+ OPT_BOOL(0, "root", &show_root, N_("report root nodes")),
+ OPT_BOOL(0, "cache", &keep_cache_objects, N_("make index objects head nodes")),
+ OPT_BOOL(0, "reflogs", &include_reflogs, N_("make reflogs head nodes (default)")),
+ OPT_BOOL(0, "full", &check_full, N_("also consider packs and alternate objects")),
+ OPT_BOOL(0, "strict", &check_strict, N_("enable more strict checking")),
+ OPT_BOOL(0, "lost-found", &write_lost_and_found,
N_("write dangling objects in .git/lost-found")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
OPT_END(),
diff --git a/builtin/gc.c b/builtin/gc.c
index 6be6c8d65b..63d400bcb2 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -14,7 +14,9 @@
#include "cache.h"
#include "parse-options.h"
#include "run-command.h"
+#include "sigchain.h"
#include "argv-array.h"
+#include "commit.h"
#define FAILED_RUN "failed to run %s"
@@ -27,6 +29,7 @@ static int pack_refs = 1;
static int aggressive_window = 250;
static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
+static int detach_auto = 1;
static const char *prune_expire = "2.weeks.ago";
static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
@@ -35,6 +38,21 @@ static struct argv_array repack = ARGV_ARRAY_INIT;
static struct argv_array prune = ARGV_ARRAY_INIT;
static struct argv_array rerere = ARGV_ARRAY_INIT;
+static char *pidfile;
+
+static void remove_pidfile(void)
+{
+ if (pidfile)
+ unlink(pidfile);
+}
+
+static void remove_pidfile_on_signal(int signo)
+{
+ remove_pidfile();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
static int gc_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "gc.packrefs")) {
@@ -56,6 +74,10 @@ static int gc_config(const char *var, const char *value, void *cb)
gc_auto_pack_limit = git_config_int(var, value);
return 0;
}
+ if (!strcmp(var, "gc.autodetach")) {
+ detach_auto = git_config_bool(var, value);
+ return 0;
+ }
if (!strcmp(var, "gc.pruneexpire")) {
if (value && strcmp(value, "now")) {
unsigned long now = approxidate("now");
@@ -167,19 +189,87 @@ static int need_to_gc(void)
return 1;
}
+/* return NULL on success, else hostname running the gc */
+static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
+{
+ static struct lock_file lock;
+ char my_host[128];
+ struct strbuf sb = STRBUF_INIT;
+ struct stat st;
+ uintmax_t pid;
+ FILE *fp;
+ int fd;
+
+ if (pidfile)
+ /* already locked */
+ return NULL;
+
+ if (gethostname(my_host, sizeof(my_host)))
+ strcpy(my_host, "unknown");
+
+ fd = hold_lock_file_for_update(&lock, git_path("gc.pid"),
+ LOCK_DIE_ON_ERROR);
+ if (!force) {
+ static char locking_host[128];
+ int should_exit;
+ fp = fopen(git_path("gc.pid"), "r");
+ memset(locking_host, 0, sizeof(locking_host));
+ should_exit =
+ fp != NULL &&
+ !fstat(fileno(fp), &st) &&
+ /*
+ * 12 hour limit is very generous as gc should
+ * never take that long. On the other hand we
+ * don't really need a strict limit here,
+ * running gc --auto one day late is not a big
+ * problem. --force can be used in manual gc
+ * after the user verifies that no gc is
+ * running.
+ */
+ time(NULL) - st.st_mtime <= 12 * 3600 &&
+ fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 &&
+ /* be gentle to concurrent "gc" on remote hosts */
+ (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
+ if (fp != NULL)
+ fclose(fp);
+ if (should_exit) {
+ if (fd >= 0)
+ rollback_lock_file(&lock);
+ *ret_pid = pid;
+ return locking_host;
+ }
+ }
+
+ strbuf_addf(&sb, "%"PRIuMAX" %s",
+ (uintmax_t) getpid(), my_host);
+ write_in_full(fd, sb.buf, sb.len);
+ strbuf_release(&sb);
+ commit_lock_file(&lock);
+
+ pidfile = git_pathdup("gc.pid");
+ sigchain_push_common(remove_pidfile_on_signal);
+ atexit(remove_pidfile);
+
+ return NULL;
+}
+
int cmd_gc(int argc, const char **argv, const char *prefix)
{
int aggressive = 0;
int auto_gc = 0;
int quiet = 0;
+ int force = 0;
+ const char *name;
+ pid_t pid;
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
{ OPTION_STRING, 0, "prune", &prune_expire, N_("date"),
N_("prune unreferenced objects"),
PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire },
- OPT_BOOLEAN(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
- OPT_BOOLEAN(0, "auto", &auto_gc, N_("enable auto-gc mode")),
+ OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")),
+ OPT_BOOL(0, "auto", &auto_gc, N_("enable auto-gc mode")),
+ OPT_BOOL(0, "force", &force, N_("force running gc even if there may be another gc running")),
OPT_END()
};
@@ -217,14 +307,30 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
*/
if (!need_to_gc())
return 0;
- if (!quiet)
- fprintf(stderr,
- _("Auto packing the repository for optimum performance. You may also\n"
- "run \"git gc\" manually. See "
- "\"git help gc\" for more information.\n"));
+ if (!quiet) {
+ if (detach_auto)
+ fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
+ else
+ fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
+ fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
+ }
+ if (detach_auto)
+ /*
+ * failure to daemonize is ok, we'll continue
+ * in foreground
+ */
+ daemonize();
} else
add_repack_all_option();
+ name = lock_repo_for_gc(force, &pid);
+ if (name) {
+ if (auto_gc)
+ return 0; /* be quiet on --auto */
+ die(_("gc is already running on machine '%s' pid %"PRIuMAX" (use --force if not)"),
+ name, (uintmax_t)pid);
+ }
+
if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
return error(FAILED_RUN, pack_refs_cmd.argv[0]);
diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c
new file mode 100644
index 0000000000..aa72596083
--- /dev/null
+++ b/builtin/get-tar-commit-id.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2005, 2006 Rene Scharfe
+ */
+#include "cache.h"
+#include "commit.h"
+#include "tar.h"
+#include "builtin.h"
+#include "quote.h"
+
+static const char builtin_get_tar_commit_id_usage[] =
+"git get-tar-commit-id < <tarfile>";
+
+/* ustar header + extended global header content */
+#define RECORDSIZE (512)
+#define HEADERSIZE (2 * RECORDSIZE)
+
+int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
+{
+ char buffer[HEADERSIZE];
+ struct ustar_header *header = (struct ustar_header *)buffer;
+ char *content = buffer + RECORDSIZE;
+ ssize_t n;
+
+ if (argc != 1)
+ usage(builtin_get_tar_commit_id_usage);
+
+ n = read_in_full(0, buffer, HEADERSIZE);
+ if (n < HEADERSIZE)
+ die("git get-tar-commit-id: read error");
+ if (header->typeflag[0] != 'g')
+ return 1;
+ if (memcmp(content, "52 comment=", 11))
+ return 1;
+
+ n = write_in_full(1, content + 11, 41);
+ if (n < 41)
+ die_errno("git get-tar-commit-id: write error");
+
+ return 0;
+}
diff --git a/builtin/grep.c b/builtin/grep.c
index 159e65d47a..69ac2d8797 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -17,6 +17,7 @@
#include "grep.h"
#include "quote.h"
#include "dir.h"
+#include "pathspec.h"
static char const * const grep_usage[] = {
N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@ -286,8 +287,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
struct strbuf pathbuf = STRBUF_INIT;
if (opt->relative && opt->prefix_length) {
- quote_path_relative(filename + tree_name_len, -1, &pathbuf,
- opt->prefix);
+ quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
strbuf_insert(&pathbuf, 0, filename, tree_name_len);
} else {
strbuf_addstr(&pathbuf, filename);
@@ -318,7 +318,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
struct strbuf buf = STRBUF_INIT;
if (opt->relative && opt->prefix_length)
- quote_path_relative(filename, -1, &buf, opt->prefix);
+ quote_path_relative(filename, opt->prefix, &buf);
else
strbuf_addstr(&buf, filename);
@@ -376,10 +376,10 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
read_cache();
for (nr = 0; nr < active_nr; nr++) {
- struct cache_entry *ce = active_cache[nr];
+ const struct cache_entry *ce = active_cache[nr];
if (!S_ISREG(ce->ce_mode))
continue;
- if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
+ if (!ce_path_match(ce, pathspec, NULL))
continue;
/*
* If CE_VALID is on, we assume worktree file and its cache entry
@@ -458,10 +458,10 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
}
static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
- struct object *obj, const char *name)
+ struct object *obj, const char *name, struct object_context *oc)
{
if (obj->type == OBJ_BLOB)
- return grep_sha1(opt, obj->sha1, name, 0, NULL);
+ return grep_sha1(opt, obj->sha1, name, 0, oc ? oc->path : NULL);
if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
struct tree_desc tree;
void *data;
@@ -503,7 +503,7 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
for (i = 0; i < nr; i++) {
struct object *real_obj;
real_obj = deref_tag(list->objects[i].item, NULL, 0);
- if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) {
+ if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].context)) {
hit = 1;
if (opt->status_only)
break;
@@ -522,11 +522,9 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
if (exc_std)
setup_standard_excludes(&dir);
- fill_directory(&dir, pathspec->raw);
+ fill_directory(&dir, pathspec);
for (i = 0; i < dir.nr; i++) {
- const char *name = dir.entries[i]->name;
- int namelen = strlen(name);
- if (!match_pathspec_depth(pathspec, name, namelen, 0, NULL))
+ if (!dir_path_match(dir.entries[i], pathspec, 0, NULL))
continue;
hit |= grep_file(opt, dir.entries[i]->name);
if (hit && opt->status_only)
@@ -630,7 +628,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
const char *show_in_pager = NULL, *default_pager = "dummy";
struct grep_opt opt;
struct object_array list = OBJECT_ARRAY_INIT;
- const char **paths = NULL;
struct pathspec pathspec;
struct string_list path_list = STRING_LIST_INIT_NODUP;
int i;
@@ -639,26 +636,28 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
struct option options[] = {
- OPT_BOOLEAN(0, "cached", &cached,
+ OPT_BOOL(0, "cached", &cached,
N_("search in index instead of in the work tree")),
OPT_NEGBIT(0, "no-index", &use_index,
N_("find in contents not managed by git"), 1),
- OPT_BOOLEAN(0, "untracked", &untracked,
+ OPT_BOOL(0, "untracked", &untracked,
N_("search in both tracked and untracked files")),
OPT_SET_INT(0, "exclude-standard", &opt_exclude,
N_("search also in ignored files"), 1),
OPT_GROUP(""),
- OPT_BOOLEAN('v', "invert-match", &opt.invert,
+ OPT_BOOL('v', "invert-match", &opt.invert,
N_("show non-matching lines")),
- OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
+ OPT_BOOL('i', "ignore-case", &opt.ignore_case,
N_("case insensitive matching")),
- OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
+ OPT_BOOL('w', "word-regexp", &opt.word_regexp,
N_("match patterns only at word boundaries")),
OPT_SET_INT('a', "text", &opt.binary,
N_("process binary files as text"), GREP_BINARY_TEXT),
OPT_SET_INT('I', NULL, &opt.binary,
N_("don't match patterns in binary files"),
GREP_BINARY_NOMATCH),
+ OPT_BOOL(0, "textconv", &opt.allow_textconv,
+ N_("process binary files with textconv filters")),
{ OPTION_INTEGER, 0, "max-depth", &opt.max_depth, N_("depth"),
N_("descend at most <depth> levels"), PARSE_OPT_NONEG,
NULL, 1 },
@@ -676,26 +675,26 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
N_("use Perl-compatible regular expressions"),
GREP_PATTERN_TYPE_PCRE),
OPT_GROUP(""),
- OPT_BOOLEAN('n', "line-number", &opt.linenum, N_("show line numbers")),
+ OPT_BOOL('n', "line-number", &opt.linenum, N_("show line numbers")),
OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1),
OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1),
OPT_NEGBIT(0, "full-name", &opt.relative,
N_("show filenames relative to top directory"), 1),
- OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
+ OPT_BOOL('l', "files-with-matches", &opt.name_only,
N_("show only filenames instead of matching lines")),
- OPT_BOOLEAN(0, "name-only", &opt.name_only,
+ OPT_BOOL(0, "name-only", &opt.name_only,
N_("synonym for --files-with-matches")),
- OPT_BOOLEAN('L', "files-without-match",
+ OPT_BOOL('L', "files-without-match",
&opt.unmatch_name_only,
N_("show only the names of files without match")),
- OPT_BOOLEAN('z', "null", &opt.null_following_name,
+ OPT_BOOL('z', "null", &opt.null_following_name,
N_("print NUL after filenames")),
- OPT_BOOLEAN('c', "count", &opt.count,
+ OPT_BOOL('c', "count", &opt.count,
N_("show the number of matches instead of matching lines")),
OPT__COLOR(&opt.color, N_("highlight matches")),
- OPT_BOOLEAN(0, "break", &opt.file_break,
+ OPT_BOOL(0, "break", &opt.file_break,
N_("print empty line between matches from different files")),
- OPT_BOOLEAN(0, "heading", &opt.heading,
+ OPT_BOOL(0, "heading", &opt.heading,
N_("show filename only once above matches from same file")),
OPT_GROUP(""),
OPT_CALLBACK('C', "context", &opt, N_("n"),
@@ -707,9 +706,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
N_("show <n> context lines after matches")),
OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"),
context_callback),
- OPT_BOOLEAN('p', "show-function", &opt.funcname,
+ OPT_BOOL('p', "show-function", &opt.funcname,
N_("show a line with the function name before matches")),
- OPT_BOOLEAN('W', "function-context", &opt.funcbody,
+ OPT_BOOL('W', "function-context", &opt.funcbody,
N_("show the surrounding function")),
OPT_GROUP(""),
OPT_CALLBACK('f', NULL, &opt, N_("file"),
@@ -719,7 +718,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
{ OPTION_CALLBACK, 0, "and", &opt, NULL,
N_("combine patterns specified with -e"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
- OPT_BOOLEAN(0, "or", &dummy, ""),
+ OPT_BOOL(0, "or", &dummy, ""),
{ OPTION_CALLBACK, 0, "not", &opt, NULL, "",
PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
{ OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
@@ -730,7 +729,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
close_callback },
OPT__QUIET(&opt.status_only,
N_("indicate hit with exit status without output")),
- OPT_BOOLEAN(0, "all-match", &opt.all_match,
+ OPT_BOOL(0, "all-match", &opt.all_match,
N_("show only matches from files that match all patterns")),
{ OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
N_("show parse tree for grep expression"),
@@ -739,8 +738,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
{ OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
N_("pager"), N_("show matching files in the pager"),
PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager },
- OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
- N_("allow calling of grep(1) (ignored by this build)")),
+ OPT_BOOL(0, "ext-grep", &external_grep_allowed__ignored,
+ N_("allow calling of grep(1) (ignored by this build)")),
{ OPTION_CALLBACK, 0, "help-all", &options, NULL, N_("show usage"),
PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
OPT_END()
@@ -818,12 +817,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
unsigned char sha1[20];
+ struct object_context oc;
/* Is it a rev? */
- if (!get_sha1(arg, sha1)) {
+ if (!get_sha1_with_context(arg, 0, sha1, &oc)) {
struct object *object = parse_object_or_die(sha1, arg);
if (!seen_dashdash)
verify_non_filename(prefix, arg);
- add_object_array(object, arg, &list);
+ add_object_array_with_context(object, arg, &list, xmemdupz(&oc, sizeof(struct object_context)));
continue;
}
if (!strcmp(arg, "--")) {
@@ -857,8 +857,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
verify_filename(prefix, argv[j], j == i);
}
- paths = get_pathspec(prefix, argv + i);
- init_pathspec(&pathspec, paths);
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD |
+ (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
+ prefix, argv + i);
pathspec.max_depth = opt.max_depth;
pathspec.recursive = 1;
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index 8d184f1a99..d7fcf4c13c 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -70,10 +70,10 @@ static const char *vpath;
static const struct option hash_object_options[] = {
OPT_STRING('t', NULL, &type, N_("type"), N_("object type")),
- OPT_BOOLEAN('w', NULL, &write_object, N_("write the object into the object database")),
- OPT_BOOLEAN( 0 , "stdin", &hashstdin, N_("read the object from stdin")),
- OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")),
- OPT_BOOLEAN( 0 , "no-filters", &no_filters, N_("store file as is without filters")),
+ OPT_BOOL('w', NULL, &write_object, N_("write the object into the object database")),
+ OPT_COUNTUP( 0 , "stdin", &hashstdin, N_("read the object from stdin")),
+ OPT_BOOL( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")),
+ OPT_BOOL( 0 , "no-filters", &no_filters, N_("store file as is without filters")),
OPT_STRING( 0 , "path", &vpath, N_("file"), N_("process file as it were from this path")),
OPT_END()
};
diff --git a/builtin/help.c b/builtin/help.c
index 062957f629..1fdefeb686 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -1,6 +1,4 @@
/*
- * builtin-help.c
- *
* Builtin help command
*/
#include "cache.h"
@@ -102,7 +100,7 @@ static int check_emacsclient_version(void)
*/
finish_command(&ec_process);
- if (prefixcmp(buffer.buf, "emacsclient")) {
+ if (!starts_with(buffer.buf, "emacsclient")) {
strbuf_release(&buffer);
return error(_("Failed to parse emacsclient version."));
}
@@ -260,7 +258,7 @@ static int add_man_viewer_info(const char *var, const char *value)
static int git_help_config(const char *var, const char *value, void *cb)
{
- if (!prefixcmp(var, "column."))
+ if (starts_with(var, "column."))
return git_column_config(var, value, "help", &colopts);
if (!strcmp(var, "help.format")) {
if (!value)
@@ -280,7 +278,7 @@ static int git_help_config(const char *var, const char *value, void *cb)
add_man_viewer(value);
return 0;
}
- if (!prefixcmp(var, "man."))
+ if (starts_with(var, "man."))
return add_man_viewer_info(var, value);
return git_default_config(var, value, cb);
@@ -290,6 +288,10 @@ static struct cmdnames main_cmds, other_cmds;
static int is_git_command(const char *s)
{
+ if (is_builtin(s))
+ return 1;
+
+ load_command_list("git-", &main_cmds, &other_cmds);
return is_in_cmdlist(&main_cmds, s) ||
is_in_cmdlist(&other_cmds, s);
}
@@ -308,7 +310,7 @@ static const char *cmd_to_page(const char *git_cmd)
{
if (!git_cmd)
return "git";
- else if (!prefixcmp(git_cmd, "git"))
+ else if (starts_with(git_cmd, "git"))
return git_cmd;
else if (is_git_command(git_cmd))
return prepend("git-", git_cmd);
@@ -451,7 +453,6 @@ int cmd_help(int argc, const char **argv, const char *prefix)
int nongit;
const char *alias;
enum help_format parsed_help_format;
- load_command_list("git-", &main_cmds, &other_cmds);
argc = parse_options(argc, argv, prefix, builtin_help_options,
builtin_help_usage, 0);
@@ -460,6 +461,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
if (show_all) {
git_config(git_help_config, NULL);
printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
+ load_command_list("git-", &main_cmds, &other_cmds);
list_commands(colopts, &main_cmds, &other_cmds);
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 79dfe47320..2f37a38fbc 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -77,8 +77,10 @@ static int nr_threads;
static int from_stdin;
static int strict;
+static int do_fsck_object;
static int verbose;
static int show_stat;
+static int check_self_contained_and_connected;
static struct progress *progress;
@@ -187,13 +189,13 @@ static int mark_link(struct object *obj, int type, void *data)
/* The content of each linked object must have been checked
or it must be already present in the object database */
-static void check_object(struct object *obj)
+static unsigned check_object(struct object *obj)
{
if (!obj)
- return;
+ return 0;
if (!(obj->flags & FLAG_LINK))
- return;
+ return 0;
if (!(obj->flags & FLAG_CHECKED)) {
unsigned long size;
@@ -201,17 +203,20 @@ static void check_object(struct object *obj)
if (type != obj->type || type <= 0)
die(_("object of unexpected type"));
obj->flags |= FLAG_CHECKED;
- return;
+ return 1;
}
+
+ return 0;
}
-static void check_objects(void)
+static unsigned check_objects(void)
{
- unsigned i, max;
+ unsigned i, max, foreign_nr = 0;
max = get_max_object_index();
for (i = 0; i < max; i++)
- check_object(get_indexed_object(i));
+ foreign_nr += check_object(get_indexed_object(i));
+ return foreign_nr;
}
@@ -747,8 +752,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
int eaten;
void *buf = (void *) data;
- if (!buf)
- buf = new_data = get_data_from_pack(obj_entry);
+ assert(data && "data can only be NULL for large _blobs_");
/*
* we do not need to free the memory here, as the
@@ -757,7 +761,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
obj = parse_object_buffer(sha1, type, size, buf, &eaten);
if (!obj)
die(_("invalid %s"), typename(type));
- if (fsck_object(obj, 1, fsck_error_function))
+ if (do_fsck_object &&
+ fsck_object(obj, 1, fsck_error_function))
die(_("Error in object"));
if (fsck_walk(obj, mark_link, NULL))
die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
@@ -765,6 +770,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
if (obj->type == OBJ_TREE) {
struct tree *item = (struct tree *) obj;
item->buffer = NULL;
+ obj->parsed = 0;
}
if (obj->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *) obj;
@@ -1491,6 +1497,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
struct pack_idx_entry **idx_objects;
struct pack_idx_option opts;
unsigned char pack_sha1[20];
+ unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(index_pack_usage);
@@ -1512,6 +1519,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
fix_thin_pack = 1;
} else if (!strcmp(arg, "--strict")) {
strict = 1;
+ do_fsck_object = 1;
+ } else if (!strcmp(arg, "--check-self-contained-and-connected")) {
+ strict = 1;
+ check_self_contained_and_connected = 1;
} else if (!strcmp(arg, "--verify")) {
verify = 1;
} else if (!strcmp(arg, "--verify-stat")) {
@@ -1523,9 +1534,9 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
stat_only = 1;
} else if (!strcmp(arg, "--keep")) {
keep_msg = "";
- } else if (!prefixcmp(arg, "--keep=")) {
+ } else if (starts_with(arg, "--keep=")) {
keep_msg = arg + 7;
- } else if (!prefixcmp(arg, "--threads=")) {
+ } else if (starts_with(arg, "--threads=")) {
char *end;
nr_threads = strtoul(arg+10, &end, 0);
if (!arg[10] || *end || nr_threads < 0)
@@ -1536,7 +1547,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
"ignoring %s"), arg);
nr_threads = 1;
#endif
- } else if (!prefixcmp(arg, "--pack_header=")) {
+ } else if (starts_with(arg, "--pack_header=")) {
struct pack_header *hdr;
char *c;
@@ -1555,7 +1566,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (index_name || (i+1) >= argc)
usage(index_pack_usage);
index_name = argv[++i];
- } else if (!prefixcmp(arg, "--index-version=")) {
+ } else if (starts_with(arg, "--index-version=")) {
char *c;
opts.version = strtoul(arg + 16, &c, 10);
if (opts.version > 2)
@@ -1625,7 +1636,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
free(deltas);
if (strict)
- check_objects();
+ foreign_nr = check_objects();
if (show_stat)
show_pack_info(stat_only);
@@ -1651,5 +1662,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (index_name == NULL)
free((void *) curr_index);
+ /*
+ * Let the caller know this pack is not self contained
+ */
+ if (check_self_contained_and_connected && foreign_nr)
+ return 1;
+
return 0;
}
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 78aa3872dd..c7c76bbf21 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -266,7 +266,7 @@ static int create_default_files(const char *template_path)
/* allow template config file to override the default */
if (log_all_ref_updates == -1)
git_config_set("core.logallrefupdates", "true");
- if (prefixcmp(git_dir, work_tree) ||
+ if (!starts_with(git_dir, work_tree) ||
strcmp(git_dir + strlen(work_tree), "/.git")) {
git_config_set("core.worktree", work_tree);
}
@@ -515,13 +515,14 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
saved = shared_repository;
shared_repository = 0;
switch (safe_create_leading_directories_const(argv[0])) {
- case -3:
+ case SCLD_OK:
+ case SCLD_PERMS:
+ break;
+ case SCLD_EXISTS:
errno = EEXIST;
/* fallthru */
- case -1:
- die_errno(_("cannot mkdir %s"), argv[0]);
- break;
default:
+ die_errno(_("cannot mkdir %s"), argv[0]);
break;
}
shared_repository = saved;
diff --git a/builtin/log.c b/builtin/log.c
index 6e56a50002..b97373da3b 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -19,6 +19,7 @@
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
+#include "line-log.h"
#include "branch.h"
#include "streaming.h"
#include "version.h"
@@ -42,6 +43,12 @@ static const char * const builtin_log_usage[] = {
NULL
};
+struct line_opt_callback_data {
+ struct rev_info *rev;
+ const char *prefix;
+ struct string_list args;
+};
+
static int parse_decoration_style(const char *var, const char *value)
{
switch (git_config_maybe_bool(var, value)) {
@@ -76,6 +83,19 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
return 0;
}
+static int log_line_range_callback(const struct option *option, const char *arg, int unset)
+{
+ struct line_opt_callback_data *data = option->value;
+
+ if (!arg)
+ return -1;
+
+ data->rev->line_level_traverse = 1;
+ string_list_append(&data->args, arg);
+
+ return 0;
+}
+
static void cmd_log_init_defaults(struct rev_info *rev)
{
if (fmt_pretty)
@@ -91,6 +111,7 @@ static void cmd_log_init_defaults(struct rev_info *rev)
if (default_date_mode)
rev->date_mode = parse_date_format(default_date_mode);
+ rev->diffopt.touched_flags = 0;
}
static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
@@ -98,16 +119,23 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
{
struct userformat_want w;
int quiet = 0, source = 0, mailmap = 0;
+ static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
const struct option builtin_log_options[] = {
- OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")),
+ 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")),
{ OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback},
+ OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
+ "Process line range n,m in file, counting from 1",
+ log_line_range_callback),
OPT_END()
};
+ line_cb.rev = rev;
+ line_cb.prefix = prefix;
+
mailmap = use_mailmap_config;
argc = parse_options(argc, argv, prefix,
builtin_log_options, builtin_log_usage,
@@ -161,6 +189,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
rev->show_decorations = 1;
load_ref_decorations(decoration_style);
}
+
+ if (rev->line_level_traverse)
+ line_log_init(rev, line_cb.prefix, &line_cb.args);
+
setup_pager();
}
@@ -206,7 +238,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
int i = revs->early_output;
int show_header = 1;
- sort_in_topological_order(&list, revs->lifo);
+ sort_in_topological_order(&list, revs->sort_order);
while (list && i) {
struct commit *commit = list->item;
switch (simplify_commit(revs, commit)) {
@@ -359,7 +391,7 @@ static int git_log_config(const char *var, const char *value, void *cb)
default_show_root = git_config_bool(var, value);
return 0;
}
- if (!prefixcmp(var, "color.decorate."))
+ if (starts_with(var, "color.decorate."))
return parse_decorate_color_config(var, 15, value);
if (!strcmp(var, "log.mailmap")) {
use_mailmap_config = git_config_bool(var, value);
@@ -405,10 +437,29 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
strbuf_release(&out);
}
-static int show_blob_object(const unsigned char *sha1, struct rev_info *rev)
+static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, const char *obj_name)
{
+ unsigned char sha1c[20];
+ struct object_context obj_context;
+ char *buf;
+ unsigned long size;
+
fflush(stdout);
- return stream_blob_to_fd(1, sha1, NULL, 0);
+ if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) ||
+ !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV))
+ return stream_blob_to_fd(1, sha1, NULL, 0);
+
+ if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context))
+ die("Not a valid object name %s", obj_name);
+ if (!obj_context.path[0] ||
+ !textconv_object(obj_context.path, obj_context.mode, sha1c, 1, &buf, &size))
+ return stream_blob_to_fd(1, sha1, NULL, 0);
+
+ if (!buf)
+ die("git show %s: bad file", obj_name);
+
+ write_or_die(1, buf, size);
+ return 0;
}
static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
@@ -426,7 +477,7 @@ static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
int new_offset = offset + 1;
while (new_offset < size && buf[new_offset++] != '\n')
; /* do nothing */
- if (!prefixcmp(buf + offset, "tagger "))
+ if (starts_with(buf + offset, "tagger "))
show_tagger(buf + offset + 7,
new_offset - offset - 7, rev);
offset = new_offset;
@@ -472,7 +523,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
init_grep_defaults();
git_config(git_log_config, NULL);
- init_pathspec(&match_all, NULL);
+ memset(&match_all, 0, sizeof(match_all));
init_revisions(&rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;
@@ -494,7 +545,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
const char *name = objects[i].name;
switch (o->type) {
case OBJ_BLOB:
- ret = show_blob_object(o->sha1, NULL);
+ ret = show_blob_object(o->sha1, &rev, name);
break;
case OBJ_TAG: {
struct tag *t = (struct tag *)o;
@@ -831,7 +882,7 @@ static char *find_branch_name(struct rev_info *rev)
ref = rev->cmdline.rev[positive].name;
tip_sha1 = rev->cmdline.rev[positive].item->sha1;
if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) &&
- !prefixcmp(full_ref, "refs/heads/") &&
+ starts_with(full_ref, "refs/heads/") &&
!hashcmp(tip_sha1, branch_sha1))
branch = xstrdup(full_ref + strlen("refs/heads/"));
free(full_ref);
@@ -1081,6 +1132,21 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int from_callback(const struct option *opt, const char *arg, int unset)
+{
+ char **from = opt->value;
+
+ free(*from);
+
+ if (unset)
+ *from = NULL;
+ else if (arg)
+ *from = xstrdup(arg);
+ else
+ *from = xstrdup(git_committer_info(IDENT_NO_DATE));
+ return 0;
+}
+
int cmd_format_patch(int argc, const char **argv, const char *prefix)
{
struct commit *commit;
@@ -1103,6 +1169,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int quiet = 0;
int reroll_count = -1;
char *branch_name = NULL;
+ char *from = NULL;
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
N_("use [PATCH n/m] even with a single patch"),
@@ -1132,13 +1199,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
{ OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
N_("don't strip/add [PATCH]"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
- OPT_BOOLEAN(0, "no-binary", &no_binary_diff,
- N_("don't output binary diffs")),
- OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
- N_("don't include a patch matching a commit upstream")),
- { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
+ OPT_BOOL(0, "no-binary", &no_binary_diff,
+ N_("don't output binary diffs")),
+ OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
+ N_("don't include a patch matching a commit upstream")),
+ { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL,
N_("show patch format instead of default (patch + stat)"),
- PARSE_OPT_NONEG | PARSE_OPT_NOARG },
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1},
OPT_GROUP(N_("Messaging")),
{ OPTION_CALLBACK, 0, "add-header", NULL, N_("header"),
N_("add email header"), 0, header_callback },
@@ -1146,6 +1213,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
0, to_callback },
{ OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
0, cc_callback },
+ { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
+ N_("set From address to <ident> (or committer ident if absent)"),
+ 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"),
@@ -1160,8 +1230,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, thread_callback },
OPT_STRING(0, "signature", &signature, N_("signature"),
N_("add a signature")),
- OPT_BOOLEAN(0, "quiet", &quiet,
- N_("don't print the patch filenames")),
+ OPT__QUIET(&quiet, N_("don't print the patch filenames")),
OPT_END()
};
@@ -1233,6 +1302,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.extra_headers = strbuf_detach(&buf, NULL);
+ if (from) {
+ if (split_ident_line(&rev.from_ident, from, strlen(from)))
+ die(_("invalid ident line: %s"), from);
+ }
+
if (start_number < 0)
start_number = 1;
@@ -1314,7 +1388,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
unsigned char sha1[20];
const char *ref;
ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
- if (ref && !prefixcmp(ref, "refs/heads/"))
+ if (ref && starts_with(ref, "refs/heads/"))
branch_name = xstrdup(ref + strlen("refs/heads/"));
else
branch_name = xstrdup(""); /* no branch */
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 22020729cb..47c38808a2 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -13,6 +13,7 @@
#include "parse-options.h"
#include "resolve-undo.h"
#include "string-list.h"
+#include "pathspec.h"
static int abbrev;
static int show_deleted;
@@ -30,7 +31,7 @@ static int debug_mode;
static const char *prefix;
static int max_prefix_len;
static int prefix_len;
-static const char **pathspec;
+static struct pathspec pathspec;
static int error_unmatch;
static char *ps_matched;
static const char *with_tree;
@@ -46,10 +47,14 @@ static const char *tag_modified = "";
static const char *tag_skip_worktree = "";
static const char *tag_resolve_undo = "";
-static void write_name(const char* name, size_t len)
+static void write_name(const char *name)
{
- write_name_quoted_relative(name, len, prefix, prefix_len, stdout,
- line_terminator);
+ /*
+ * With "--full-name", prefix_len=0; this caller needs to pass
+ * an empty string in that case (a NULL is good for "").
+ */
+ write_name_quoted_relative(name, prefix_len ? prefix : NULL,
+ stdout, line_terminator);
}
static void show_dir_entry(const char *tag, struct dir_entry *ent)
@@ -59,11 +64,11 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
if (len >= ent->len)
die("git ls-files: internal error - directory entry not superset of prefix");
- if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
+ if (!dir_path_match(ent, &pathspec, len, ps_matched))
return;
fputs(tag, stdout);
- write_name(ent->name, ent->len);
+ write_name(ent->name);
}
static void show_other_files(struct dir_struct *dir)
@@ -127,14 +132,16 @@ static void show_killed_files(struct dir_struct *dir)
}
}
-static void show_ce_entry(const char *tag, struct cache_entry *ce)
+static void show_ce_entry(const char *tag, const struct cache_entry *ce)
{
int len = max_prefix_len;
if (len >= ce_namelen(ce))
die("git ls-files: internal error - cache entry not superset of prefix");
- if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
+ if (!match_pathspec(&pathspec, ce->name, ce_namelen(ce),
+ len, ps_matched,
+ S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode)))
return;
if (tag && *tag && show_valid_bit &&
@@ -163,13 +170,15 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
find_unique_abbrev(ce->sha1,abbrev),
ce_stage(ce));
}
- write_name(ce->name, ce_namelen(ce));
+ write_name(ce->name);
if (debug_mode) {
- printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
- printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
- printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
- printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
- printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
+ const struct stat_data *sd = &ce->ce_stat_data;
+
+ printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
+ printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
+ printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
+ printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
+ printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
}
@@ -188,7 +197,8 @@ static void show_ru_info(void)
len = strlen(path);
if (len < max_prefix_len)
continue; /* outside of the prefix */
- if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
+ if (!match_pathspec(&pathspec, path, len,
+ max_prefix_len, ps_matched, 0))
continue; /* uninterested */
for (i = 0; i < 3; i++) {
if (!ui->mode[i])
@@ -196,12 +206,12 @@ static void show_ru_info(void)
printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
find_unique_abbrev(ui->sha1[i], abbrev),
i + 1);
- write_name(path, len);
+ write_name(path);
}
}
}
-static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce)
+static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce)
{
int dtype = ce_to_dtype(ce);
return is_excluded(dir, ce->name, &dtype);
@@ -213,15 +223,17 @@ static void show_files(struct dir_struct *dir)
/* For cached/deleted files we don't need to even do the readdir */
if (show_others || show_killed) {
- fill_directory(dir, pathspec);
+ if (!show_others)
+ dir->flags |= DIR_COLLECT_KILLED_ONLY;
+ fill_directory(dir, &pathspec);
if (show_others)
show_other_files(dir);
if (show_killed)
show_killed_files(dir);
}
- if (show_cached | show_stage) {
+ if (show_cached || show_stage) {
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
if ((dir->flags & DIR_SHOW_IGNORED) &&
!ce_excluded(dir, ce))
continue;
@@ -233,9 +245,9 @@ static void show_files(struct dir_struct *dir)
(ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
}
}
- if (show_deleted | show_modified) {
+ if (show_deleted || show_modified) {
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
struct stat st;
int err;
if ((dir->flags & DIR_SHOW_IGNORED) &&
@@ -271,7 +283,7 @@ static void prune_cache(const char *prefix)
last = active_nr;
while (last > first) {
int next = (last + first) >> 1;
- struct cache_entry *ce = active_cache[next];
+ const struct cache_entry *ce = active_cache[next];
if (!strncmp(ce->name, prefix, max_prefix_len)) {
first = next+1;
continue;
@@ -281,21 +293,6 @@ static void prune_cache(const char *prefix)
active_nr = last;
}
-static void strip_trailing_slash_from_submodules(void)
-{
- const char **p;
-
- for (p = pathspec; *p != NULL; p++) {
- int len = strlen(*p), pos;
-
- if (len < 1 || (*p)[len - 1] != '/')
- continue;
- pos = cache_name_pos(*p, len - 1);
- if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
- *p = xstrndup(*p, len - 1);
- }
-}
-
/*
* Read the tree specified with --with-tree option
* (typically, HEAD) into stage #1 and then
@@ -327,13 +324,12 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
}
if (prefix) {
- static const char *(matchbuf[2]);
- matchbuf[0] = prefix;
- matchbuf[1] = NULL;
- init_pathspec(&pathspec, matchbuf);
- pathspec.items[0].nowildcard_len = pathspec.items[0].len;
+ static const char *(matchbuf[1]);
+ matchbuf[0] = NULL;
+ parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC,
+ PATHSPEC_PREFER_CWD, prefix, matchbuf);
} else
- init_pathspec(&pathspec, NULL);
+ memset(&pathspec, 0, sizeof(pathspec));
if (read_tree(tree, 1, &pathspec))
die("unable to read tree entries %s", tree_name);
@@ -358,15 +354,16 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
}
}
-int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix)
+int report_path_error(const char *ps_matched,
+ const struct pathspec *pathspec,
+ const char *prefix)
{
/*
* Make sure all pathspec matched; otherwise it is an error.
*/
struct strbuf sb = STRBUF_INIT;
- const char *name;
int num, errors = 0;
- for (num = 0; pathspec[num]; num++) {
+ for (num = 0; num < pathspec->nr; num++) {
int other, found_dup;
if (ps_matched[num])
@@ -374,13 +371,16 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char
/*
* The caller might have fed identical pathspec
* twice. Do not barf on such a mistake.
+ * FIXME: parse_pathspec should have eliminated
+ * duplicate pathspec.
*/
for (found_dup = other = 0;
- !found_dup && pathspec[other];
+ !found_dup && other < pathspec->nr;
other++) {
if (other == num || !ps_matched[other])
continue;
- if (!strcmp(pathspec[other], pathspec[num]))
+ if (!strcmp(pathspec->items[other].original,
+ pathspec->items[num].original))
/*
* Ok, we have a match already.
*/
@@ -389,9 +389,8 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char
if (found_dup)
continue;
- name = quote_path_relative(pathspec[num], -1, &sb, prefix);
error("pathspec '%s' did not match any file(s) known to git.",
- name);
+ pathspec->items[num].original);
errors++;
}
strbuf_release(&sb);
@@ -455,24 +454,24 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
N_("paths are separated with NUL character"),
PARSE_OPT_NOARG, option_parse_z },
- OPT_BOOLEAN('t', NULL, &show_tag,
+ OPT_BOOL('t', NULL, &show_tag,
N_("identify the file status with tags")),
- OPT_BOOLEAN('v', NULL, &show_valid_bit,
+ OPT_BOOL('v', NULL, &show_valid_bit,
N_("use lowercase letters for 'assume unchanged' files")),
- OPT_BOOLEAN('c', "cached", &show_cached,
+ OPT_BOOL('c', "cached", &show_cached,
N_("show cached files in the output (default)")),
- OPT_BOOLEAN('d', "deleted", &show_deleted,
+ OPT_BOOL('d', "deleted", &show_deleted,
N_("show deleted files in the output")),
- OPT_BOOLEAN('m', "modified", &show_modified,
+ OPT_BOOL('m', "modified", &show_modified,
N_("show modified files in the output")),
- OPT_BOOLEAN('o', "others", &show_others,
+ OPT_BOOL('o', "others", &show_others,
N_("show other files in the output")),
OPT_BIT('i', "ignored", &dir.flags,
N_("show ignored files in the output"),
DIR_SHOW_IGNORED),
- OPT_BOOLEAN('s', "stage", &show_stage,
+ OPT_BOOL('s', "stage", &show_stage,
N_("show staged contents' object name in the output")),
- OPT_BOOLEAN('k', "killed", &show_killed,
+ OPT_BOOL('k', "killed", &show_killed,
N_("show files on the filesystem that need to be removed")),
OPT_BIT(0, "directory", &dir.flags,
N_("show 'other' directories' name only"),
@@ -480,9 +479,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
OPT_NEGBIT(0, "empty-directory", &dir.flags,
N_("don't show empty directories"),
DIR_HIDE_EMPTY_DIRECTORIES),
- OPT_BOOLEAN('u', "unmerged", &show_unmerged,
+ OPT_BOOL('u', "unmerged", &show_unmerged,
N_("show unmerged files in the output")),
- OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
+ OPT_BOOL(0, "resolve-undo", &show_resolve_undo,
N_("show resolve-undo information")),
{ OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"),
N_("skip files matching pattern"),
@@ -498,12 +497,12 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
N_("make the output relative to the project top directory"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
- OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
+ OPT_BOOL(0, "error-unmatch", &error_unmatch,
N_("if any <file> is not in the index, treat this as an error")),
OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
N_("pretend that paths removed since <tree-ish> are still present")),
OPT__ABBREV(&abbrev),
- OPT_BOOLEAN(0, "debug", &debug_mode, N_("show debugging data")),
+ OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")),
OPT_END()
};
@@ -549,30 +548,25 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (require_work_tree && !is_inside_work_tree())
setup_work_tree();
- pathspec = get_pathspec(prefix, argv);
-
- /* be nice with submodule paths ending in a slash */
- if (pathspec)
- strip_trailing_slash_from_submodules();
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+ prefix, argv);
/* Find common prefix for all pathspec's */
- max_prefix = common_prefix(pathspec);
+ max_prefix = common_prefix(&pathspec);
max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
/* Treat unmatching pathspec elements as errors */
- if (pathspec && error_unmatch) {
- int num;
- for (num = 0; pathspec[num]; num++)
- ;
- ps_matched = xcalloc(1, num);
- }
+ if (pathspec.nr && error_unmatch)
+ ps_matched = xcalloc(1, pathspec.nr);
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
die("ls-files --ignored needs some exclude pattern");
/* With no flags, we default to showing the cached files */
- if (!(show_stage | show_deleted | show_others | show_unmerged |
- show_killed | show_modified | show_resolve_undo))
+ if (!(show_stage || show_deleted || show_others || show_unmerged ||
+ show_killed || show_modified || show_resolve_undo))
show_cached = 1;
if (max_prefix)
@@ -592,7 +586,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (ps_matched) {
int bad;
- bad = report_path_error(ps_matched, pathspec, prefix);
+ bad = report_path_error(ps_matched, &pathspec, prefix);
if (bad)
fprintf(stderr, "Did you forget to 'git add'?\n");
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 25e83cfe9d..39e5144b9e 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -50,11 +50,11 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
const char *arg = argv[i];
if (*arg == '-') {
- if (!prefixcmp(arg, "--upload-pack=")) {
+ if (starts_with(arg, "--upload-pack=")) {
uploadpack = arg + 14;
continue;
}
- if (!prefixcmp(arg, "--exec=")) {
+ if (starts_with(arg, "--exec=")) {
uploadpack = arg + 7;
continue;
}
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index fb76e38d84..51184dfa2e 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -10,6 +10,7 @@
#include "quote.h"
#include "builtin.h"
#include "parse-options.h"
+#include "pathspec.h"
static int line_termination = '\n';
#define LS_RECURSIVE 1
@@ -35,7 +36,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
if (ls_options & LS_RECURSIVE)
return 1;
- s = pathspec.raw;
+ s = pathspec._raw;
if (!s)
return 0;
@@ -138,9 +139,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
LS_NAME_ONLY),
OPT_SET_INT(0, "full-name", &chomp_prefix,
N_("use full path names"), 0),
- OPT_BOOLEAN(0, "full-tree", &full_tree,
- N_("list entire tree; not just current directory "
- "(implies --full-name)")),
+ OPT_BOOL(0, "full-tree", &full_tree,
+ N_("list entire tree; not just current directory "
+ "(implies --full-name)")),
OPT__ABBREV(&abbrev),
OPT_END()
};
@@ -166,7 +167,15 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
if (get_sha1(argv[0], sha1))
die("Not a valid object name %s", argv[0]);
- init_pathspec(&pathspec, get_pathspec(prefix, argv + 1));
+ /*
+ * show_recursive() rolls its own matching code and is
+ * generally ignorant of 'struct pathspec'. The magic mask
+ * cannot be lifted until it is converted to use
+ * match_pathspec() or tree_entry_interesting()
+ */
+ parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE,
+ PATHSPEC_PREFER_CWD,
+ prefix, argv + 1);
for (i = 0; i < pathspec.nr; i++)
pathspec.items[i].nowildcard_len = pathspec.items[i].len;
pathspec.has_wildcard = 0;
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index 24a772d8e1..2c3cd8eab7 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -328,11 +328,11 @@ static int check_header(const struct strbuf *line,
}
/* for inbody stuff */
- if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
+ if (starts_with(line->buf, ">From") && isspace(line->buf[5])) {
ret = 1; /* Should this return 0? */
goto check_header_out;
}
- if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+ if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
for (i = 0; header[i]; i++) {
if (!memcmp("Subject", header[i], 7)) {
handle_header(&hdr_data[i], line);
@@ -361,7 +361,7 @@ static int is_rfc2822_header(const struct strbuf *line)
char *cp = line->buf;
/* Count mbox From headers as headers */
- if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From "))
+ if (starts_with(cp, "From ") || starts_with(cp, ">From "))
return 1;
while ((ch = *cp++)) {
@@ -671,11 +671,11 @@ static inline int patchbreak(const struct strbuf *line)
size_t i;
/* Beginning of a "diff -" header? */
- if (!prefixcmp(line->buf, "diff -"))
+ if (starts_with(line->buf, "diff -"))
return 1;
/* CVS "Index: " line? */
- if (!prefixcmp(line->buf, "Index: "))
+ if (starts_with(line->buf, "Index: "))
return 1;
/*
@@ -685,7 +685,7 @@ static inline int patchbreak(const struct strbuf *line)
if (line->len < 4)
return 0;
- if (!prefixcmp(line->buf, "---")) {
+ if (starts_with(line->buf, "---")) {
/* space followed by a filename? */
if (line->buf[3] == ' ' && !isspace(line->buf[4]))
return 1;
@@ -986,7 +986,7 @@ static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch)
static int git_mailinfo_config(const char *var, const char *value, void *unused)
{
- if (prefixcmp(var, "mailinfo."))
+ if (!starts_with(var, "mailinfo."))
return git_default_config(var, value, unused);
if (!strcmp(var, "mailinfo.scissors")) {
use_scissors = git_config_bool(var, value);
@@ -1020,7 +1020,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
metainfo_charset = def_charset;
else if (!strcmp(argv[1], "-n"))
metainfo_charset = NULL;
- else if (!prefixcmp(argv[1], "--encoding="))
+ else if (starts_with(argv[1], "--encoding="))
metainfo_charset = argv[1] + 11;
else if (!strcmp(argv[1], "--scissors"))
use_scissors = 1;
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 1bc7991048..0ecde8da30 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -1,6 +1,9 @@
#include "builtin.h"
#include "cache.h"
#include "commit.h"
+#include "refs.h"
+#include "diff.h"
+#include "revision.h"
#include "parse-options.h"
static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
@@ -27,6 +30,7 @@ static const char * const merge_base_usage[] = {
N_("git merge-base [-a|--all] --octopus <commit>..."),
N_("git merge-base --independent <commit>..."),
N_("git merge-base --is-ancestor <commit> <commit>"),
+ N_("git merge-base --fork-point <ref> [<commit>]"),
NULL
};
@@ -44,19 +48,36 @@ static struct commit *get_commit_reference(const char *arg)
return r;
}
-static int handle_octopus(int count, const char **args, int reduce, int show_all)
+static int handle_independent(int count, const char **args)
{
struct commit_list *revs = NULL;
struct commit_list *result;
int i;
- if (reduce)
- show_all = 1;
+ for (i = count - 1; i >= 0; i--)
+ commit_list_insert(get_commit_reference(args[i]), &revs);
+
+ result = reduce_heads(revs);
+ if (!result)
+ return 1;
+
+ while (result) {
+ printf("%s\n", sha1_to_hex(result->item->object.sha1));
+ result = result->next;
+ }
+ return 0;
+}
+
+static int handle_octopus(int count, const char **args, int show_all)
+{
+ struct commit_list *revs = NULL;
+ struct commit_list *result;
+ int i;
for (i = count - 1; i >= 0; i--)
commit_list_insert(get_commit_reference(args[i]), &revs);
- result = reduce ? reduce_heads(revs) : get_octopus_merge_bases(revs);
+ result = reduce_heads(get_octopus_merge_bases(revs));
if (!result)
return 1;
@@ -85,37 +106,151 @@ static int handle_is_ancestor(int argc, const char **argv)
return 1;
}
+struct rev_collect {
+ struct commit **commit;
+ int nr;
+ int alloc;
+ unsigned int initial : 1;
+};
+
+static void add_one_commit(unsigned char *sha1, struct rev_collect *revs)
+{
+ struct commit *commit;
+
+ if (is_null_sha1(sha1))
+ return;
+
+ commit = lookup_commit(sha1);
+ if (!commit ||
+ (commit->object.flags & TMP_MARK) ||
+ parse_commit(commit))
+ return;
+
+ ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc);
+ revs->commit[revs->nr++] = commit;
+ commit->object.flags |= TMP_MARK;
+}
+
+static int collect_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *ident, unsigned long timestamp,
+ int tz, const char *message, void *cbdata)
+{
+ struct rev_collect *revs = cbdata;
+
+ if (revs->initial) {
+ revs->initial = 0;
+ add_one_commit(osha1, revs);
+ }
+ add_one_commit(nsha1, revs);
+ return 0;
+}
+
+static int handle_fork_point(int argc, const char **argv)
+{
+ unsigned char sha1[20];
+ char *refname;
+ const char *commitname;
+ struct rev_collect revs;
+ struct commit *derived;
+ struct commit_list *bases;
+ int i, ret = 0;
+
+ switch (dwim_ref(argv[0], strlen(argv[0]), sha1, &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_sha1(commitname, sha1))
+ die("Not a valid object name: '%s'", commitname);
+
+ derived = lookup_commit_reference(sha1);
+ memset(&revs, 0, sizeof(revs));
+ revs.initial = 1;
+ for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
+
+ for (i = 0; i < revs.nr; i++)
+ revs.commit[i]->object.flags &= ~TMP_MARK;
+
+ bases = get_merge_bases_many(derived, revs.nr, revs.commit, 0);
+
+ /*
+ * There should be one and only one merge base, when we found
+ * a common ancestor among reflog entries.
+ */
+ if (!bases || bases->next) {
+ ret = 1;
+ goto cleanup_return;
+ }
+
+ /* And the found one must be one of the reflog entries */
+ for (i = 0; i < revs.nr; i++)
+ if (&bases->item->object == &revs.commit[i]->object)
+ break; /* found */
+ if (revs.nr <= i) {
+ ret = 1; /* not found */
+ goto cleanup_return;
+ }
+
+ printf("%s\n", sha1_to_hex(bases->item->object.sha1));
+
+cleanup_return:
+ free_commit_list(bases);
+ return ret;
+}
+
int cmd_merge_base(int argc, const char **argv, const char *prefix)
{
struct commit **rev;
int rev_nr = 0;
int show_all = 0;
- int octopus = 0;
- int reduce = 0;
- int is_ancestor = 0;
+ int cmdmode = 0;
struct option options[] = {
- OPT_BOOLEAN('a', "all", &show_all, N_("output all common ancestors")),
- OPT_BOOLEAN(0, "octopus", &octopus, N_("find ancestors for a single n-way merge")),
- OPT_BOOLEAN(0, "independent", &reduce, N_("list revs not reachable from others")),
- OPT_BOOLEAN(0, "is-ancestor", &is_ancestor,
- N_("is the first one ancestor of the other?")),
+ OPT_BOOL('a', "all", &show_all, N_("output all common ancestors")),
+ OPT_CMDMODE(0, "octopus", &cmdmode,
+ N_("find ancestors for a single n-way merge"), 'o'),
+ OPT_CMDMODE(0, "independent", &cmdmode,
+ N_("list revs not reachable from others"), 'r'),
+ OPT_CMDMODE(0, "is-ancestor", &cmdmode,
+ N_("is the first one ancestor of the other?"), 'a'),
+ OPT_CMDMODE(0, "fork-point", &cmdmode,
+ N_("find where <commit> forked from reflog of <ref>"), 'f'),
OPT_END()
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0);
- if (!octopus && !reduce && argc < 2)
- usage_with_options(merge_base_usage, options);
- if (is_ancestor && (show_all | octopus | reduce))
- die("--is-ancestor cannot be used with other options");
- if (is_ancestor)
+
+ if (cmdmode == 'a') {
+ if (argc < 2)
+ usage_with_options(merge_base_usage, options);
+ if (show_all)
+ die("--is-ancestor cannot be used with --all");
return handle_is_ancestor(argc, argv);
- if (reduce && (show_all || octopus))
- die("--independent cannot be used with other options");
+ }
- if (octopus || reduce)
- return handle_octopus(argc, argv, reduce, show_all);
+ if (cmdmode == 'r' && show_all)
+ die("--independent cannot be used with --all");
+
+ if (cmdmode == 'o')
+ return handle_octopus(argc, argv, show_all);
+
+ if (cmdmode == 'r')
+ return handle_independent(argc, argv);
+
+ if (cmdmode == 'f') {
+ if (argc < 1 || 2 < argc)
+ usage_with_options(merge_base_usage, options);
+ return handle_fork_point(argc, argv);
+ }
+
+ if (argc < 2)
+ usage_with_options(merge_base_usage, options);
rev = xmalloc(argc * sizeof(*rev));
while (argc-- > 0)
diff --git a/builtin/merge-file.c b/builtin/merge-file.c
index c0570f2407..844f84f40b 100644
--- a/builtin/merge-file.c
+++ b/builtin/merge-file.c
@@ -30,7 +30,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
int quiet = 0;
int prefixlen = 0;
struct option options[] = {
- OPT_BOOLEAN('p', "stdout", &to_stdout, N_("send results to standard output")),
+ OPT_BOOL('p', "stdout", &to_stdout, N_("send results to standard output")),
OPT_SET_INT(0, "diff3", &xmp.style, N_("use a diff3 based merge"), XDL_MERGE_DIFF3),
OPT_SET_INT(0, "ours", &xmp.favor, N_("for conflicts, use our version"),
XDL_MERGE_FAVOR_OURS),
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
index be5e514324..b416d92849 100644
--- a/builtin/merge-index.c
+++ b/builtin/merge-index.c
@@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path)
die("git merge-index: %s not in the cache", path);
found = 0;
do {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
int stage = ce_stage(ce);
if (strcmp(ce->name, path))
@@ -58,7 +58,7 @@ static void merge_all(void)
{
int i;
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
continue;
i += merge_entry(i, ce->name)-1;
diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
index 3a64f5d0bd..a90f28f34d 100644
--- a/builtin/merge-recursive.c
+++ b/builtin/merge-recursive.c
@@ -29,7 +29,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
struct commit *result;
init_merge_options(&o);
- if (argv[0] && !suffixcmp(argv[0], "-subtree"))
+ if (argv[0] && ends_with(argv[0], "-subtree"))
o.subtree_shift = "";
if (argc < 4)
@@ -38,7 +38,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc; ++i) {
const char *arg = argv[i];
- if (!prefixcmp(arg, "--")) {
+ if (starts_with(arg, "--")) {
if (!arg[2])
break;
if (parse_merge_opt(&o, arg + 2))
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index ed25d81b88..61cbde4094 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -251,7 +251,11 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3])
for (i = 0; i < 3; i++) {
mask |= (1 << i);
- if (n[i].mode && S_ISDIR(n[i].mode))
+ /*
+ * Treat missing entries as directories so that we return
+ * after unresolved_directory has handled this.
+ */
+ if (!n[i].mode || S_ISDIR(n[i].mode))
dirmask |= (1 << i);
}
@@ -302,7 +306,7 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3])
static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info)
{
/* Same in both? */
- if (same_entry(entry+1, entry+2) || both_empty(entry+0, entry+2)) {
+ if (same_entry(entry+1, entry+2) || both_empty(entry+1, entry+2)) {
/* Modified, added or removed identically */
resolve(info, NULL, entry+1);
return mask;
diff --git a/builtin/merge.c b/builtin/merge.c
index 3e2daa37c3..f0cf1205fa 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -47,8 +47,8 @@ static const char * const builtin_merge_usage[] = {
};
static int show_diffstat = 1, shortlog_len = -1, squash;
-static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only, option_edit = -1;
+static int option_commit = 1;
+static int option_edit = -1;
static int allow_trivial = 1, have_message, verify_signatures;
static int overwrite_ignore = 1;
static struct strbuf merge_msg = STRBUF_INIT;
@@ -76,6 +76,14 @@ static struct strategy all_strategy[] = {
static const char *pull_twohead, *pull_octopus;
+enum ff_type {
+ FF_NO,
+ FF_ALLOW,
+ FF_ONLY
+};
+
+static enum ff_type fast_forward = FF_ALLOW;
+
static int option_parse_message(const struct option *opt,
const char *arg, int unset)
{
@@ -182,22 +190,22 @@ static struct option builtin_merge_options[] = {
{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
N_("do not show a diffstat at the end of the merge"),
PARSE_OPT_NOARG, option_parse_n },
- OPT_BOOLEAN(0, "stat", &show_diffstat,
+ OPT_BOOL(0, "stat", &show_diffstat,
N_("show a diffstat at the end of the merge")),
- OPT_BOOLEAN(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
+ OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
{ OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"),
N_("add (at most <n>) entries from shortlog to merge commit message"),
PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
- OPT_BOOLEAN(0, "squash", &squash,
+ OPT_BOOL(0, "squash", &squash,
N_("create a single commit instead of doing a merge")),
- OPT_BOOLEAN(0, "commit", &option_commit,
+ OPT_BOOL(0, "commit", &option_commit,
N_("perform a commit if the merge succeeds (default)")),
OPT_BOOL('e', "edit", &option_edit,
N_("edit message before committing")),
- OPT_BOOLEAN(0, "ff", &allow_fast_forward,
- N_("allow fast-forward (default)")),
- OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
- N_("abort if fast-forward is not possible")),
+ OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW),
+ { OPTION_SET_INT, 0, "ff-only", &fast_forward, NULL,
+ N_("abort if fast-forward is not possible"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY },
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
OPT_BOOL(0, "verify-signatures", &verify_signatures,
N_("Verify that the named commit has a valid GPG signature")),
@@ -209,12 +217,12 @@ static struct option builtin_merge_options[] = {
N_("merge commit message (for a non-fast-forward merge)"),
option_parse_message),
OPT__VERBOSITY(&verbosity),
- OPT_BOOLEAN(0, "abort", &abort_current_merge,
+ OPT_BOOL(0, "abort", &abort_current_merge,
N_("abort the current in-progress merge")),
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_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
+ OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
OPT_END()
};
@@ -359,7 +367,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead
sha1_to_hex(commit->object.sha1));
pretty_print_commit(&ctx, commit, &out);
}
- if (write(fd, out.buf, out.len) < 0)
+ if (write_in_full(fd, out.buf, out.len) != out.len)
die_errno(_("Writing SQUASH_MSG"));
if (close(fd))
die_errno(_("Finishing SQUASH_MSG"));
@@ -438,17 +446,17 @@ static void merge_name(const char *remote, struct strbuf *msg)
die(_("'%s' does not point to a commit"), remote);
if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
- if (!prefixcmp(found_ref, "refs/heads/")) {
+ if (starts_with(found_ref, "refs/heads/")) {
strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
sha1_to_hex(branch_head), remote);
goto cleanup;
}
- if (!prefixcmp(found_ref, "refs/tags/")) {
+ if (starts_with(found_ref, "refs/tags/")) {
strbuf_addf(msg, "%s\t\ttag '%s' of .\n",
sha1_to_hex(branch_head), remote);
goto cleanup;
}
- if (!prefixcmp(found_ref, "refs/remotes/")) {
+ if (starts_with(found_ref, "refs/remotes/")) {
strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
sha1_to_hex(branch_head), remote);
goto cleanup;
@@ -562,8 +570,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)
{
int status;
- if (branch && !prefixcmp(k, "branch.") &&
- !prefixcmp(k + 7, branch) &&
+ if (branch && starts_with(k, "branch.") &&
+ starts_with(k + 7, branch) &&
!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
free(branch_mergeoptions);
branch_mergeoptions = xstrdup(v);
@@ -581,15 +589,17 @@ static int git_merge_config(const char *k, const char *v, void *cb)
else if (!strcmp(k, "merge.ff")) {
int boolval = git_config_maybe_bool(k, v);
if (0 <= boolval) {
- allow_fast_forward = boolval;
+ fast_forward = boolval ? FF_ALLOW : FF_NO;
} else if (v && !strcmp(v, "only")) {
- allow_fast_forward = 1;
- fast_forward_only = 1;
+ fast_forward = FF_ONLY;
} /* do not barf on values from future versions of git */
return 0;
} else if (!strcmp(k, "merge.defaulttoupstream")) {
default_to_upstream = git_config_bool(k, v);
return 0;
+ } else if (!strcmp(k, "commit.gpgsign")) {
+ sign_commit = git_config_bool(k, v) ? "" : NULL;
+ return 0;
}
status = fmt_merge_msg_config(k, v, cb);
@@ -863,7 +873,7 @@ static int finish_automerge(struct commit *head,
free_commit_list(common);
parents = remoteheads;
- if (!head_subsumed || !allow_fast_forward)
+ if (!head_subsumed || fast_forward == FF_NO)
commit_list_insert(head, &parents);
strbuf_addch(&merge_msg, '\n');
prepare_to_commit(remoteheads);
@@ -889,7 +899,7 @@ static int suggest_conflicts(int renormalizing)
die_errno(_("Could not open '%s' for writing"), filename);
fprintf(fp, "\nConflicts:\n");
for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (ce_stage(ce)) {
fprintf(fp, "\t%s\n", ce->name);
@@ -948,7 +958,7 @@ static int evaluate_result(void)
}
/*
- * Pretend as if the user told us to merge with the tracking
+ * Pretend as if the user told us to merge with the remote-tracking
* branch we have for the upstream of the current branch
*/
static int setup_with_upstream(const char ***argv)
@@ -967,7 +977,7 @@ static int setup_with_upstream(const char ***argv)
args = xcalloc(branch->merge_nr + 1, sizeof(char *));
for (i = 0; i < branch->merge_nr; i++) {
if (!branch->merge[i]->dst)
- die(_("No remote tracking branch for %s from %s"),
+ die(_("No remote-tracking branch for %s from %s"),
branch->merge[i]->src, branch->remote_name);
args[i] = branch->merge[i]->dst;
}
@@ -1008,7 +1018,7 @@ static void write_merge_state(struct commit_list *remoteheads)
if (fd < 0)
die_errno(_("Could not open '%s' for writing"), filename);
strbuf_reset(&buf);
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
strbuf_addf(&buf, "no-ff");
if (write_in_full(fd, buf.buf, buf.len) != buf.len)
die_errno(_("Could not write to '%s'"), filename);
@@ -1054,7 +1064,8 @@ static struct commit_list *collect_parents(struct commit *head_commit,
for (i = 0; i < argc; i++) {
struct commit *commit = get_merge_parent(argv[i]);
if (!commit)
- die(_("%s - not something we can merge"), argv[i]);
+ help_unknown_ref(argv[i], "merge",
+ "not something we can merge");
remotes = &commit_list_insert(commit, remotes)->next;
}
*remotes = NULL;
@@ -1098,7 +1109,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* current branch.
*/
branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
- if (branch && !prefixcmp(branch, "refs/heads/"))
+ if (branch && starts_with(branch, "refs/heads/"))
branch += 11;
if (!branch || is_null_sha1(head_sha1))
head_commit = NULL;
@@ -1156,14 +1167,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
show_diffstat = 0;
if (squash) {
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
die(_("You cannot combine --squash with --no-ff."));
option_commit = 0;
}
- if (!allow_fast_forward && fast_forward_only)
- die(_("You cannot combine --no-ff with --ff-only."));
-
if (!abort_current_merge) {
if (!argc) {
if (default_to_upstream)
@@ -1181,7 +1189,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* This could be traditional "merge <msg> HEAD <commit>..." and
* the way we can tell it is to see if the second token is HEAD,
* but some people might have misused the interface and used a
- * committish that is the same as HEAD there instead.
+ * commit-ish that is the same as HEAD there instead.
* Traditional format never would have "-m" so it is an
* additional safety measure to check for it.
*/
@@ -1205,7 +1213,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
"empty head"));
if (squash)
die(_("Squash commit into empty head not supported yet"));
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
die(_("Non-fast-forward commit does not make sense into "
"an empty head"));
remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
@@ -1293,11 +1301,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
sha1_to_hex(commit->object.sha1));
setenv(buf.buf, merge_remote_util(commit)->name, 1);
strbuf_reset(&buf);
- if (!fast_forward_only &&
+ if (fast_forward != FF_ONLY &&
merge_remote_util(commit) &&
merge_remote_util(commit)->obj &&
merge_remote_util(commit)->obj->type == OBJ_TAG)
- allow_fast_forward = 0;
+ fast_forward = FF_NO;
}
if (option_edit < 0)
@@ -1314,7 +1322,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
for (i = 0; i < use_strategies_nr; i++) {
if (use_strategies[i]->attr & NO_FAST_FORWARD)
- allow_fast_forward = 0;
+ fast_forward = FF_NO;
if (use_strategies[i]->attr & NO_TRIVIAL)
allow_trivial = 0;
}
@@ -1344,7 +1352,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
*/
finish_up_to_date("Already up-to-date.");
goto done;
- } else if (allow_fast_forward && !remoteheads->next &&
+ } else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
!hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
/* Again the most common case of merging one remote. */
@@ -1391,7 +1399,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* only one common.
*/
refresh_cache(REFRESH_QUIET);
- if (allow_trivial && !fast_forward_only) {
+ if (allow_trivial && fast_forward != FF_ONLY) {
/* See if it is really trivial. */
git_committer_info(IDENT_STRICT);
printf(_("Trying really trivial in-index merge...\n"));
@@ -1432,7 +1440,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
}
- if (fast_forward_only)
+ if (fast_forward == FF_ONLY)
die(_("Not possible to fast-forward, aborting."));
/* We are going to make a new commit. */
diff --git a/builtin/mv.c b/builtin/mv.c
index 034fec92a1..21c46d1636 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -9,14 +9,19 @@
#include "cache-tree.h"
#include "string-list.h"
#include "parse-options.h"
+#include "submodule.h"
static const char * const builtin_mv_usage[] = {
N_("git mv [options] <source>... <destination>"),
NULL
};
-static const char **copy_pathspec(const char *prefix, const char **pathspec,
- int count, int base_name)
+#define DUP_BASENAME 1
+#define KEEP_TRAILING_SLASH 2
+
+static const char **internal_copy_pathspec(const char *prefix,
+ const char **pathspec,
+ int count, unsigned flags)
{
int i;
const char **result = xmalloc((count + 1) * sizeof(const char *));
@@ -25,11 +30,12 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
for (i = 0; i < count; i++) {
int length = strlen(result[i]);
int to_copy = length;
- while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
+ while (!(flags & KEEP_TRAILING_SLASH) &&
+ to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
to_copy--;
- if (to_copy != length || base_name) {
+ if (to_copy != length || flags & DUP_BASENAME) {
char *it = xmemdupz(result[i], to_copy);
- if (base_name) {
+ if (flags & DUP_BASENAME) {
result[i] = xstrdup(basename(it));
free(it);
} else
@@ -53,23 +59,25 @@ static const char *add_slash(const char *path)
}
static struct lock_file lock_file;
+#define SUBMODULE_WITH_GITDIR ((const char *)1)
int cmd_mv(int argc, const char **argv, const char *prefix)
{
- int i, newfd;
+ int i, newfd, gitmodules_modified = 0;
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT__DRY_RUN(&show_only, N_("dry run")),
OPT__FORCE(&force, N_("force move/rename even if target exists")),
- OPT_BOOLEAN('k', NULL, &ignore_errors, N_("skip move/rename errors")),
+ OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
OPT_END(),
};
- const char **source, **destination, **dest_path;
+ const char **source, **destination, **dest_path, **submodule_gitfile;
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
struct stat st;
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
+ gitmodules_config();
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_mv_options,
@@ -81,17 +89,23 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die(_("index file corrupt"));
- source = copy_pathspec(prefix, argv, argc, 0);
+ source = internal_copy_pathspec(prefix, argv, argc, 0);
modes = xcalloc(argc, sizeof(enum update_mode));
- dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
+ /*
+ * Keep trailing slash, needed to let
+ * "git mv file no-such-dir/" error out.
+ */
+ dest_path = internal_copy_pathspec(prefix, argv + argc, 1,
+ KEEP_TRAILING_SLASH);
+ submodule_gitfile = xcalloc(argc, sizeof(char *));
if (dest_path[0][0] == '\0')
/* special case: "." was normalized to "" */
- destination = copy_pathspec(dest_path[0], argv, argc, 1);
+ destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
else if (!lstat(dest_path[0], &st) &&
S_ISDIR(st.st_mode)) {
dest_path[0] = add_slash(dest_path[0]);
- destination = copy_pathspec(dest_path[0], argv, argc, 1);
+ destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
} else {
if (argc != 1)
die("destination '%s' is not a directory", dest_path[0]);
@@ -117,55 +131,70 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
&& lstat(dst, &st) == 0)
bad = _("cannot move directory over file");
else if (src_is_dir) {
- const char *src_w_slash = add_slash(src);
- int len_w_slash = length + 1;
- int first, last;
-
- modes[i] = WORKING_DIRECTORY;
-
- first = cache_name_pos(src_w_slash, len_w_slash);
- if (first >= 0)
- die (_("Huh? %.*s is in index?"),
- len_w_slash, src_w_slash);
-
- first = -1 - first;
- for (last = first; last < active_nr; last++) {
- const char *path = active_cache[last]->name;
- if (strncmp(path, src_w_slash, len_w_slash))
- break;
- }
- free((char *)src_w_slash);
-
- if (last - first < 1)
- bad = _("source directory is empty");
- else {
- int j, dst_len;
-
- if (last - first > 0) {
- source = xrealloc(source,
- (argc + last - first)
- * sizeof(char *));
- destination = xrealloc(destination,
- (argc + last - first)
- * sizeof(char *));
- modes = xrealloc(modes,
- (argc + last - first)
- * sizeof(enum update_mode));
+ int first = cache_name_pos(src, length);
+ if (first >= 0) {
+ struct strbuf submodule_dotgit = STRBUF_INIT;
+ if (!S_ISGITLINK(active_cache[first]->ce_mode))
+ die (_("Huh? Directory %s is in index and no submodule?"), src);
+ if (!is_staging_gitmodules_ok())
+ die (_("Please, stage your changes to .gitmodules or stash them to proceed"));
+ strbuf_addf(&submodule_dotgit, "%s/.git", src);
+ submodule_gitfile[i] = read_gitfile(submodule_dotgit.buf);
+ if (submodule_gitfile[i])
+ submodule_gitfile[i] = xstrdup(submodule_gitfile[i]);
+ else
+ submodule_gitfile[i] = SUBMODULE_WITH_GITDIR;
+ strbuf_release(&submodule_dotgit);
+ } else {
+ const char *src_w_slash = add_slash(src);
+ int last, len_w_slash = length + 1;
+
+ modes[i] = WORKING_DIRECTORY;
+
+ first = cache_name_pos(src_w_slash, len_w_slash);
+ if (first >= 0)
+ die (_("Huh? %.*s is in index?"),
+ len_w_slash, src_w_slash);
+
+ first = -1 - first;
+ for (last = first; last < active_nr; last++) {
+ const char *path = active_cache[last]->name;
+ if (strncmp(path, src_w_slash, len_w_slash))
+ break;
}
+ free((char *)src_w_slash);
+
+ if (last - first < 1)
+ bad = _("source directory is empty");
+ else {
+ int j, dst_len;
- dst = add_slash(dst);
- dst_len = strlen(dst);
-
- for (j = 0; j < last - first; j++) {
- const char *path =
- active_cache[first + j]->name;
- source[argc + j] = path;
- destination[argc + j] =
- prefix_path(dst, dst_len,
- path + length + 1);
- modes[argc + j] = INDEX;
+ if (last - first > 0) {
+ source = xrealloc(source,
+ (argc + last - first)
+ * sizeof(char *));
+ destination = xrealloc(destination,
+ (argc + last - first)
+ * sizeof(char *));
+ modes = xrealloc(modes,
+ (argc + last - first)
+ * sizeof(enum update_mode));
+ }
+
+ dst = add_slash(dst);
+ dst_len = strlen(dst);
+
+ for (j = 0; j < last - first; j++) {
+ const char *path =
+ active_cache[first + j]->name;
+ source[argc + j] = path;
+ destination[argc + j] =
+ prefix_path(dst, dst_len,
+ path + length + 1);
+ modes[argc + j] = INDEX;
+ }
+ argc += last - first;
}
- argc += last - first;
}
} else if (cache_name_pos(src, length) < 0)
bad = _("not under version control");
@@ -185,6 +214,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
}
} else if (string_list_has_string(&src_for_dst, dst))
bad = _("multiple sources for the same target");
+ else if (is_dir_sep(dst[strlen(dst) - 1]))
+ bad = _("destination directory does not exist");
else
string_list_insert(&src_for_dst, dst);
@@ -210,9 +241,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
int pos;
if (show_only || verbose)
printf(_("Renaming %s to %s\n"), src, dst);
- if (!show_only && mode != INDEX &&
- rename(src, dst) < 0 && !ignore_errors)
- die_errno (_("renaming '%s' failed"), src);
+ if (!show_only && mode != INDEX) {
+ if (rename(src, dst) < 0 && !ignore_errors)
+ die_errno (_("renaming '%s' failed"), src);
+ if (submodule_gitfile[i]) {
+ if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR)
+ connect_work_tree_and_git_dir(dst, submodule_gitfile[i]);
+ if (!update_path_in_gitmodules(src, dst))
+ gitmodules_modified = 1;
+ }
+ }
if (mode == WORKING_DIRECTORY)
continue;
@@ -223,6 +261,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
rename_cache_entry_at(pos, dst);
}
+ if (gitmodules_modified)
+ stage_updated_gitmodules();
+
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 6238247974..0b21d7e5b2 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -4,6 +4,7 @@
#include "tag.h"
#include "refs.h"
#include "parse-options.h"
+#include "sha1-lookup.h"
#define CUTOFF_DATE_SLOP 86400 /* one day */
@@ -26,8 +27,7 @@ static void name_rev(struct commit *commit,
struct commit_list *parents;
int parent_number = 1;
- if (!commit->object.parsed)
- parse_commit(commit);
+ parse_commit(commit);
if (commit->date < cutoff)
return;
@@ -82,23 +82,88 @@ copy_data:
}
}
+static int subpath_matches(const char *path, const char *filter)
+{
+ const char *subpath = path;
+
+ while (subpath) {
+ if (!fnmatch(filter, subpath, 0))
+ return subpath - path;
+ subpath = strchr(subpath, '/');
+ if (subpath)
+ subpath++;
+ }
+ return -1;
+}
+
+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;
+ return refname;
+}
+
struct name_ref_data {
int tags_only;
int name_only;
const char *ref_filter;
};
+static struct tip_table {
+ struct tip_table_entry {
+ unsigned char sha1[20];
+ const char *refname;
+ } *table;
+ int nr;
+ int alloc;
+ int sorted;
+} tip_table;
+
+static void add_to_tip_table(const unsigned char *sha1, const char *refname,
+ int shorten_unambiguous)
+{
+ refname = name_ref_abbrev(refname, shorten_unambiguous);
+
+ ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc);
+ hashcpy(tip_table.table[tip_table.nr].sha1, sha1);
+ tip_table.table[tip_table.nr].refname = xstrdup(refname);
+ tip_table.nr++;
+ tip_table.sorted = 0;
+}
+
+static int tipcmp(const void *a_, const void *b_)
+{
+ const struct tip_table_entry *a = a_, *b = b_;
+ return hashcmp(a->sha1, b->sha1);
+}
+
static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
{
struct object *o = parse_object(sha1);
struct name_ref_data *data = cb_data;
+ int can_abbreviate_output = data->tags_only && data->name_only;
int deref = 0;
- if (data->tags_only && prefixcmp(path, "refs/tags/"))
+ if (data->tags_only && !starts_with(path, "refs/tags/"))
return 0;
- if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
- return 0;
+ if (data->ref_filter) {
+ switch (subpath_matches(path, data->ref_filter)) {
+ case -1: /* did not match */
+ return 0;
+ case 0: /* matched fully */
+ break;
+ default: /* matched subpath */
+ can_abbreviate_output = 1;
+ break;
+ }
+ }
+
+ add_to_tip_table(sha1, path, can_abbreviate_output);
while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
@@ -110,20 +175,38 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
if (o && o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
- if (!prefixcmp(path, "refs/heads/"))
- path = path + 11;
- else if (data->tags_only
- && data->name_only
- && !prefixcmp(path, "refs/tags/"))
- path = path + 10;
- else if (!prefixcmp(path, "refs/"))
- path = path + 5;
-
+ path = name_ref_abbrev(path, can_abbreviate_output);
name_rev(commit, xstrdup(path), 0, 0, deref);
}
return 0;
}
+static const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
+{
+ struct tip_table_entry *table = table_;
+ return table[ix].sha1;
+}
+
+static const char *get_exact_ref_match(const struct object *o)
+{
+ int found;
+
+ if (!tip_table.table || !tip_table.nr)
+ return NULL;
+
+ if (!tip_table.sorted) {
+ qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table),
+ tipcmp);
+ tip_table.sorted = 1;
+ }
+
+ found = sha1_pos(o->sha1, tip_table.table, tip_table.nr,
+ nth_tip_table_ent);
+ if (0 <= found)
+ return tip_table.table[found].refname;
+ return NULL;
+}
+
/* returns a static buffer */
static const char *get_rev_name(const struct object *o)
{
@@ -132,7 +215,7 @@ static const char *get_rev_name(const struct object *o)
struct commit *c;
if (o->type != OBJ_COMMIT)
- return NULL;
+ return get_exact_ref_match(o);
c = (struct commit *) o;
n = c->util;
if (!n)
@@ -223,25 +306,31 @@ static void name_rev_line(char *p, struct name_ref_data *data)
int cmd_name_rev(int argc, const char **argv, const char *prefix)
{
struct object_array revs = OBJECT_ARRAY_INIT;
- int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0;
+ int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
struct name_ref_data data = { 0, 0, NULL };
struct option opts[] = {
- OPT_BOOLEAN(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
- OPT_BOOLEAN(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
+ OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
+ OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"),
N_("only use refs matching <pattern>")),
OPT_GROUP(""),
- OPT_BOOLEAN(0, "all", &all, N_("list all commits reachable from all refs")),
- OPT_BOOLEAN(0, "stdin", &transform_stdin, N_("read from stdin")),
- OPT_BOOLEAN(0, "undefined", &allow_undefined, N_("allow to print `undefined` names")),
- OPT_BOOLEAN(0, "always", &always,
+ OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
+ OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
+ OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")),
+ OPT_BOOL(0, "always", &always,
N_("show abbreviated commit object as fallback")),
+ {
+ /* A Hidden OPT_BOOL */
+ OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL,
+ N_("dereference tags in the input (internal use)"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1,
+ },
OPT_END(),
};
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
- if (!!all + !!transform_stdin + !!argc > 1) {
+ if (all + transform_stdin + !!argc > 1) {
error("Specify either a list, or --all, not both!");
usage_with_options(name_rev_usage, opts);
}
@@ -250,7 +339,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
for (; argc; argc--, argv++) {
unsigned char sha1[20];
- struct object *o;
+ struct object *object;
struct commit *commit;
if (get_sha1(*argv, sha1)) {
@@ -259,17 +348,34 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
continue;
}
- o = deref_tag(parse_object(sha1), *argv, 0);
- if (!o || o->type != OBJ_COMMIT) {
- fprintf(stderr, "Could not get commit for %s. Skipping.\n",
+ commit = NULL;
+ object = parse_object(sha1);
+ if (object) {
+ struct object *peeled = deref_tag(object, *argv, 0);
+ if (peeled && peeled->type == OBJ_COMMIT)
+ commit = (struct commit *)peeled;
+ }
+
+ if (!object) {
+ fprintf(stderr, "Could not get object for %s. Skipping.\n",
*argv);
continue;
}
- commit = (struct commit *)o;
- if (cutoff > commit->date)
- cutoff = commit->date;
- add_object_array((struct object *)commit, *argv, &revs);
+ if (commit) {
+ if (cutoff > commit->date)
+ cutoff = commit->date;
+ }
+
+ if (peel_tag) {
+ if (!commit) {
+ fprintf(stderr, "Could not get commit for %s. Skipping.\n",
+ *argv);
+ continue;
+ }
+ object = (struct object *)commit;
+ }
+ add_object_array(object, *argv, &revs);
}
if (cutoff)
diff --git a/builtin/notes.c b/builtin/notes.c
index 57748a6fb6..2b24d059b5 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -4,7 +4,7 @@
* Copyright (c) 2010 Johan Herland <johan@herland.net>
*
* Based on git-notes.sh by Johannes Schindelin,
- * and builtin-tag.c by Kristian Høgsberg and Carlos Rica.
+ * and builtin/tag.c by Kristian Høgsberg and Carlos Rica.
*/
#include "cache.h"
@@ -18,9 +18,7 @@
#include "parse-options.h"
#include "string-list.h"
#include "notes-merge.h"
-
-static void commit_notes(struct notes_tree *t, const char *msg);
-static combine_notes_fn parse_combine_notes_fn(const char *v);
+#include "notes-utils.h"
static const char * const git_notes_usage[] = {
N_("git notes [--ref <notes_ref>] [list [<object>]]"),
@@ -287,139 +285,13 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
return parse_reuse_arg(opt, arg, unset);
}
-static void commit_notes(struct notes_tree *t, const char *msg)
-{
- struct strbuf buf = STRBUF_INIT;
- unsigned char commit_sha1[20];
-
- if (!t)
- t = &default_notes_tree;
- if (!t->initialized || !t->ref || !*t->ref)
- die(_("Cannot commit uninitialized/unreferenced notes tree"));
- if (!t->dirty)
- return; /* don't have to commit an unchanged tree */
-
- /* Prepare commit message and reflog message */
- strbuf_addstr(&buf, msg);
- if (buf.buf[buf.len - 1] != '\n')
- strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
-
- create_notes_commit(t, NULL, &buf, commit_sha1);
- strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
- update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
-
- strbuf_release(&buf);
-}
-
-static combine_notes_fn parse_combine_notes_fn(const char *v)
-{
- if (!strcasecmp(v, "overwrite"))
- return combine_notes_overwrite;
- else if (!strcasecmp(v, "ignore"))
- return combine_notes_ignore;
- else if (!strcasecmp(v, "concatenate"))
- return combine_notes_concatenate;
- else if (!strcasecmp(v, "cat_sort_uniq"))
- return combine_notes_cat_sort_uniq;
- else
- return NULL;
-}
-
-static int notes_rewrite_config(const char *k, const char *v, void *cb)
-{
- struct notes_rewrite_cfg *c = cb;
- if (!prefixcmp(k, "notes.rewrite.") && !strcmp(k+14, c->cmd)) {
- c->enabled = git_config_bool(k, v);
- return 0;
- } else if (!c->mode_from_env && !strcmp(k, "notes.rewritemode")) {
- if (!v)
- config_error_nonbool(k);
- c->combine = parse_combine_notes_fn(v);
- if (!c->combine) {
- error(_("Bad notes.rewriteMode value: '%s'"), v);
- return 1;
- }
- return 0;
- } else if (!c->refs_from_env && !strcmp(k, "notes.rewriteref")) {
- /* note that a refs/ prefix is implied in the
- * underlying for_each_glob_ref */
- if (!prefixcmp(v, "refs/notes/"))
- string_list_add_refs_by_glob(c->refs, v);
- else
- warning(_("Refusing to rewrite notes in %s"
- " (outside of refs/notes/)"), v);
- return 0;
- }
-
- return 0;
-}
-
-
-struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd)
-{
- struct notes_rewrite_cfg *c = xmalloc(sizeof(struct notes_rewrite_cfg));
- const char *rewrite_mode_env = getenv(GIT_NOTES_REWRITE_MODE_ENVIRONMENT);
- const char *rewrite_refs_env = getenv(GIT_NOTES_REWRITE_REF_ENVIRONMENT);
- c->cmd = cmd;
- c->enabled = 1;
- c->combine = combine_notes_concatenate;
- c->refs = xcalloc(1, sizeof(struct string_list));
- c->refs->strdup_strings = 1;
- c->refs_from_env = 0;
- c->mode_from_env = 0;
- if (rewrite_mode_env) {
- c->mode_from_env = 1;
- c->combine = parse_combine_notes_fn(rewrite_mode_env);
- if (!c->combine)
- /* TRANSLATORS: The first %s is the name of the
- environment variable, the second %s is its value */
- error(_("Bad %s value: '%s'"), GIT_NOTES_REWRITE_MODE_ENVIRONMENT,
- rewrite_mode_env);
- }
- if (rewrite_refs_env) {
- c->refs_from_env = 1;
- string_list_add_refs_from_colon_sep(c->refs, rewrite_refs_env);
- }
- git_config(notes_rewrite_config, c);
- if (!c->enabled || !c->refs->nr) {
- string_list_clear(c->refs, 0);
- free(c->refs);
- free(c);
- return NULL;
- }
- c->trees = load_notes_trees(c->refs);
- string_list_clear(c->refs, 0);
- free(c->refs);
- return c;
-}
-
-int copy_note_for_rewrite(struct notes_rewrite_cfg *c,
- const unsigned char *from_obj, const unsigned char *to_obj)
-{
- int ret = 0;
- int i;
- for (i = 0; c->trees[i]; i++)
- ret = copy_note(c->trees[i], from_obj, to_obj, 1, c->combine) || ret;
- return ret;
-}
-
-void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c)
-{
- int i;
- for (i = 0; c->trees[i]; i++) {
- commit_notes(c->trees[i], "Notes added by 'git notes copy'");
- free_notes(c->trees[i]);
- }
- free(c->trees);
- free(c);
-}
-
static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
{
struct strbuf buf = STRBUF_INIT;
struct notes_rewrite_cfg *c = NULL;
struct notes_tree *t = NULL;
int ret = 0;
+ const char *msg = "Notes added by 'git notes copy'";
if (rewrite_cmd) {
c = init_copy_notes_for_rewrite(rewrite_cmd);
@@ -461,10 +333,10 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
}
if (!rewrite_cmd) {
- commit_notes(t, "Notes added by 'git notes copy'");
+ commit_notes(t, msg);
free_notes(t);
} else {
- finish_copy_notes_for_rewrite(c);
+ finish_copy_notes_for_rewrite(c, msg);
}
return ret;
}
@@ -475,7 +347,7 @@ static struct notes_tree *init_notes_check(const char *subcommand)
init_notes(NULL, NULL, NULL, 0);
t = &default_notes_tree;
- if (prefixcmp(t->ref, "refs/notes/"))
+ if (!starts_with(t->ref, "refs/notes/"))
die("Refusing to %s notes in %s (outside of refs/notes/)",
subcommand, t->ref);
return t;
@@ -611,7 +483,7 @@ static int copy(int argc, const char **argv, const char *prefix)
const char *rewrite_cmd = NULL;
struct option options[] = {
OPT__FORCE(&force, N_("replace existing notes")),
- OPT_BOOLEAN(0, "stdin", &from_stdin, N_("read objects from stdin")),
+ OPT_BOOL(0, "stdin", &from_stdin, N_("read objects from stdin")),
OPT_STRING(0, "for-rewrite", &rewrite_cmd, N_("command"),
N_("load rewriting config for <command> (implies "
"--stdin)")),
@@ -867,13 +739,13 @@ static int merge(int argc, const char **argv, const char *prefix)
N_("resolve notes conflicts using the given strategy "
"(manual/ours/theirs/union/cat_sort_uniq)")),
OPT_GROUP(N_("Committing unmerged notes")),
- { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
+ { OPTION_SET_INT, 0, "commit", &do_commit, NULL,
N_("finalize notes merge by committing unmerged notes"),
- PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
OPT_GROUP(N_("Aborting notes merge resolution")),
- { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL,
+ { OPTION_SET_INT, 0, "abort", &do_abort, NULL,
N_("abort notes merge"),
- PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
OPT_END()
};
@@ -981,7 +853,7 @@ static int remove_cmd(int argc, const char **argv, const char *prefix)
OPT_BIT(0, "ignore-missing", &flag,
N_("attempt to remove non-existent note is not an error"),
IGNORE_MISSING),
- OPT_BOOLEAN(0, "stdin", &from_stdin,
+ OPT_BOOL(0, "stdin", &from_stdin,
N_("read object names from the standard input")),
OPT_END()
};
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index f069462cb0..c73337931b 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -14,10 +14,12 @@
#include "diff.h"
#include "revision.h"
#include "list-objects.h"
+#include "pack-objects.h"
#include "progress.h"
#include "refs.h"
#include "streaming.h"
#include "thread-utils.h"
+#include "pack-bitmap.h"
static const char *pack_usage[] = {
N_("git pack-objects --stdout [options...] [< ref-list | < object-list]"),
@@ -25,41 +27,15 @@ static const char *pack_usage[] = {
NULL
};
-struct object_entry {
- struct pack_idx_entry idx;
- unsigned long size; /* uncompressed size */
- struct packed_git *in_pack; /* already in pack */
- off_t in_pack_offset;
- struct object_entry *delta; /* delta base object */
- struct object_entry *delta_child; /* deltified objects who bases me */
- struct object_entry *delta_sibling; /* other deltified objects who
- * uses the same base as me
- */
- void *delta_data; /* cached delta (uncompressed) */
- unsigned long delta_size; /* delta data size (uncompressed) */
- unsigned long z_delta_size; /* delta data size (compressed) */
- unsigned int hash; /* name hint hash */
- enum object_type type;
- enum object_type in_pack_type; /* could be delta */
- unsigned char in_pack_header_size;
- unsigned char preferred_base; /* we do not pack this, but is available
- * to be used as the base object to delta
- * objects against.
- */
- unsigned char no_try_delta;
- unsigned char tagged; /* near the very tip of refs */
- unsigned char filled; /* assigned write-order */
-};
-
/*
- * Objects we are going to pack are collected in objects array (dynamically
- * expanded). nr_objects & nr_alloc controls this array. They are stored
- * in the order we see -- typically rev-list --objects order that gives us
- * nice "minimum seek" order.
+ * 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 object_entry *objects;
+static struct packing_data to_pack;
+
static struct pack_idx_entry **written_list;
-static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
+static uint32_t nr_result, nr_written;
static int non_empty;
static int reuse_delta = 1, reuse_object = 1;
@@ -82,6 +58,14 @@ static struct progress *progress_state;
static int pack_compression_level = Z_DEFAULT_COMPRESSION;
static int pack_compression_seen;
+static struct packed_git *reuse_packfile;
+static uint32_t reuse_packfile_objects;
+static off_t reuse_packfile_offset;
+
+static int use_bitmap_index = 1;
+static int write_bitmap_index;
+static uint16_t write_bitmap_options;
+
static unsigned long delta_cache_size = 0;
static unsigned long max_delta_cache_size = 256 * 1024 * 1024;
static unsigned long cache_max_small_delta_size = 1000;
@@ -89,20 +73,28 @@ static unsigned long cache_max_small_delta_size = 1000;
static unsigned long window_memory_limit = 0;
/*
- * The object names in objects array are hashed with this hashtable,
- * to help looking up the entry by object name.
- * This hashtable is built after all the objects are seen.
- */
-static int *object_ix;
-static int object_ix_hashsz;
-static struct object_entry *locate_object_entry(const unsigned char *sha1);
-
-/*
* stats
*/
static uint32_t written, written_delta;
static uint32_t reused, reused_delta;
+/*
+ * Indexed commits
+ */
+static struct commit **indexed_commits;
+static unsigned int indexed_commits_nr;
+static unsigned int indexed_commits_alloc;
+
+static void index_commit_for_bitmap(struct commit *commit)
+{
+ if (indexed_commits_nr >= indexed_commits_alloc) {
+ indexed_commits_alloc = (indexed_commits_alloc + 32) * 2;
+ indexed_commits = xrealloc(indexed_commits,
+ indexed_commits_alloc * sizeof(struct commit *));
+ }
+
+ indexed_commits[indexed_commits_nr++] = commit;
+}
static void *get_delta(struct object_entry *entry)
{
@@ -552,12 +544,12 @@ static int mark_tagged(const char *path, const unsigned char *sha1, int flag,
void *cb_data)
{
unsigned char peeled[20];
- struct object_entry *entry = locate_object_entry(sha1);
+ struct object_entry *entry = packlist_find(&to_pack, sha1, NULL);
if (entry)
entry->tagged = 1;
if (!peel_ref(path, peeled)) {
- entry = locate_object_entry(peeled);
+ entry = packlist_find(&to_pack, peeled, NULL);
if (entry)
entry->tagged = 1;
}
@@ -632,9 +624,10 @@ static struct object_entry **compute_write_order(void)
{
unsigned int i, wo_end, last_untagged;
- struct object_entry **wo = xmalloc(nr_objects * sizeof(*wo));
+ struct object_entry **wo = xmalloc(to_pack.nr_objects * sizeof(*wo));
+ struct object_entry *objects = to_pack.objects;
- for (i = 0; i < nr_objects; i++) {
+ for (i = 0; i < to_pack.nr_objects; i++) {
objects[i].tagged = 0;
objects[i].filled = 0;
objects[i].delta_child = NULL;
@@ -646,7 +639,7 @@ static struct object_entry **compute_write_order(void)
* Make sure delta_sibling is sorted in the original
* recency order.
*/
- for (i = nr_objects; i > 0;) {
+ for (i = to_pack.nr_objects; i > 0;) {
struct object_entry *e = &objects[--i];
if (!e->delta)
continue;
@@ -664,7 +657,7 @@ static struct object_entry **compute_write_order(void)
* Give the objects in the original recency order until
* we see a tagged tip.
*/
- for (i = wo_end = 0; i < nr_objects; i++) {
+ for (i = wo_end = 0; i < to_pack.nr_objects; i++) {
if (objects[i].tagged)
break;
add_to_write_order(wo, &wo_end, &objects[i]);
@@ -674,7 +667,7 @@ static struct object_entry **compute_write_order(void)
/*
* Then fill all the tagged tips.
*/
- for (; i < nr_objects; i++) {
+ for (; i < to_pack.nr_objects; i++) {
if (objects[i].tagged)
add_to_write_order(wo, &wo_end, &objects[i]);
}
@@ -682,7 +675,7 @@ static struct object_entry **compute_write_order(void)
/*
* And then all remaining commits and tags.
*/
- for (i = last_untagged; i < nr_objects; i++) {
+ for (i = last_untagged; i < to_pack.nr_objects; i++) {
if (objects[i].type != OBJ_COMMIT &&
objects[i].type != OBJ_TAG)
continue;
@@ -692,7 +685,7 @@ static struct object_entry **compute_write_order(void)
/*
* And then all the trees.
*/
- for (i = last_untagged; i < nr_objects; i++) {
+ for (i = last_untagged; i < to_pack.nr_objects; i++) {
if (objects[i].type != OBJ_TREE)
continue;
add_to_write_order(wo, &wo_end, &objects[i]);
@@ -701,17 +694,57 @@ static struct object_entry **compute_write_order(void)
/*
* Finally all the rest in really tight order
*/
- for (i = last_untagged; i < nr_objects; i++) {
+ for (i = last_untagged; i < to_pack.nr_objects; i++) {
if (!objects[i].filled)
add_family_to_write_order(wo, &wo_end, &objects[i]);
}
- if (wo_end != nr_objects)
- die("ordered %u objects, expected %"PRIu32, wo_end, nr_objects);
+ if (wo_end != to_pack.nr_objects)
+ die("ordered %u objects, expected %"PRIu32, wo_end, to_pack.nr_objects);
return wo;
}
+static off_t write_reused_pack(struct sha1file *f)
+{
+ unsigned char buffer[8192];
+ off_t to_write;
+ int fd;
+
+ if (!is_pack_valid(reuse_packfile))
+ die("packfile is invalid: %s", reuse_packfile->pack_name);
+
+ fd = git_open_noatime(reuse_packfile->pack_name);
+ if (fd < 0)
+ die_errno("unable to open packfile for reuse: %s",
+ reuse_packfile->pack_name);
+
+ if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1)
+ die_errno("unable to seek in reused packfile");
+
+ if (reuse_packfile_offset < 0)
+ reuse_packfile_offset = reuse_packfile->pack_size - 20;
+
+ to_write = reuse_packfile_offset - sizeof(struct pack_header);
+
+ while (to_write) {
+ int read_pack = xread(fd, buffer, sizeof(buffer));
+
+ if (read_pack <= 0)
+ die_errno("unable to read from reused packfile");
+
+ if (read_pack > to_write)
+ read_pack = to_write;
+
+ sha1write(f, buffer, read_pack);
+ to_write -= read_pack;
+ }
+
+ close(fd);
+ written += reuse_packfile_objects;
+ return reuse_packfile_offset - sizeof(struct pack_header);
+}
+
static void write_pack_file(void)
{
uint32_t i = 0, j;
@@ -723,7 +756,7 @@ static void write_pack_file(void)
if (progress > pack_to_stdout)
progress_state = start_progress("Writing objects", nr_result);
- written_list = xmalloc(nr_objects * sizeof(*written_list));
+ written_list = xmalloc(to_pack.nr_objects * sizeof(*written_list));
write_order = compute_write_order();
do {
@@ -736,10 +769,17 @@ static void write_pack_file(void)
f = create_tmp_packfile(&pack_tmp_name);
offset = write_pack_header(f, nr_remaining);
- if (!offset)
- die_errno("unable to write pack header");
+
+ if (reuse_packfile) {
+ off_t packfile_size;
+ assert(pack_to_stdout);
+
+ packfile_size = write_reused_pack(f);
+ offset += packfile_size;
+ }
+
nr_written = 0;
- for (; i < nr_objects; i++) {
+ for (; i < to_pack.nr_objects; i++) {
struct object_entry *e = write_order[i];
if (write_one(f, e, &offset) == WRITE_ONE_BREAK)
break;
@@ -790,9 +830,31 @@ static void write_pack_file(void)
if (sizeof(tmpname) <= strlen(base_name) + 50)
die("pack base name '%s' too long", base_name);
snprintf(tmpname, sizeof(tmpname), "%s-", base_name);
+
+ if (write_bitmap_index) {
+ bitmap_writer_set_checksum(sha1);
+ bitmap_writer_build_type_index(written_list, nr_written);
+ }
+
finish_tmp_packfile(tmpname, pack_tmp_name,
written_list, nr_written,
&pack_idx_opts, sha1);
+
+ if (write_bitmap_index) {
+ char *end_of_name_prefix = strrchr(tmpname, 0);
+ sprintf(end_of_name_prefix, "%s.bitmap", sha1_to_hex(sha1));
+
+ 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,
+ tmpname, write_bitmap_options);
+ write_bitmap_index = 0;
+ }
+
free(pack_tmp_name);
puts(sha1_to_hex(sha1));
}
@@ -802,7 +864,7 @@ static void write_pack_file(void)
written_list[j]->offset = (off_t)-1;
}
nr_remaining -= nr_written;
- } while (nr_remaining && i < nr_objects);
+ } while (nr_remaining && i < to_pack.nr_objects);
free(written_list);
free(write_order);
@@ -812,73 +874,6 @@ static void write_pack_file(void)
written, nr_result);
}
-static int locate_object_entry_hash(const unsigned char *sha1)
-{
- int i;
- unsigned int ui;
- memcpy(&ui, sha1, sizeof(unsigned int));
- i = ui % object_ix_hashsz;
- while (0 < object_ix[i]) {
- if (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1))
- return i;
- if (++i == object_ix_hashsz)
- i = 0;
- }
- return -1 - i;
-}
-
-static struct object_entry *locate_object_entry(const unsigned char *sha1)
-{
- int i;
-
- if (!object_ix_hashsz)
- return NULL;
-
- i = locate_object_entry_hash(sha1);
- if (0 <= i)
- return &objects[object_ix[i]-1];
- return NULL;
-}
-
-static void rehash_objects(void)
-{
- uint32_t i;
- struct object_entry *oe;
-
- object_ix_hashsz = nr_objects * 3;
- if (object_ix_hashsz < 1024)
- object_ix_hashsz = 1024;
- object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz);
- memset(object_ix, 0, sizeof(int) * object_ix_hashsz);
- for (i = 0, oe = objects; i < nr_objects; i++, oe++) {
- int ix = locate_object_entry_hash(oe->idx.sha1);
- if (0 <= ix)
- continue;
- ix = -1 - ix;
- object_ix[ix] = i + 1;
- }
-}
-
-static unsigned name_hash(const char *name)
-{
- unsigned c, hash = 0;
-
- if (!name)
- return 0;
-
- /*
- * This effectively just creates a sortable number from the
- * last sixteen non-whitespace characters. Last characters
- * count "most", so things that end in ".c" sort together.
- */
- while ((c = *name++) != 0) {
- if (isspace(c))
- continue;
- hash = (hash >> 2) + (c << 24);
- }
- return hash;
-}
-
static void setup_delta_attr_check(struct git_attr_check *check)
{
static struct git_attr *attr_delta;
@@ -901,42 +896,69 @@ static int no_try_delta(const char *path)
return 0;
}
-static int add_object_entry(const unsigned char *sha1, enum object_type type,
- const char *name, int exclude)
+/*
+ * When adding an object, check whether we have already added it
+ * to our packing list. If so, we can skip. However, if we are
+ * being asked to excludei t, but the previous mention was to include
+ * it, make sure to adjust its flags and tweak our numbers accordingly.
+ *
+ * As an optimization, we pass out the index position where we would have
+ * found the item, since that saves us from having to look it up again a
+ * few lines later when we want to add the new entry.
+ */
+static int have_duplicate_entry(const unsigned char *sha1,
+ int exclude,
+ uint32_t *index_pos)
{
struct object_entry *entry;
- struct packed_git *p, *found_pack = NULL;
- off_t found_offset = 0;
- int ix;
- unsigned hash = name_hash(name);
-
- ix = nr_objects ? locate_object_entry_hash(sha1) : -1;
- if (ix >= 0) {
- if (exclude) {
- entry = objects + object_ix[ix] - 1;
- if (!entry->preferred_base)
- nr_result--;
- entry->preferred_base = 1;
- }
+
+ entry = packlist_find(&to_pack, sha1, index_pos);
+ if (!entry)
return 0;
+
+ if (exclude) {
+ if (!entry->preferred_base)
+ nr_result--;
+ entry->preferred_base = 1;
}
+ 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).
+ *
+ * As a side effect of this check, we will find the packed version of this
+ * object, if any. We therefore pass out the pack information to avoid having
+ * to look it up again later.
+ */
+static int want_object_in_pack(const unsigned char *sha1,
+ int exclude,
+ struct packed_git **found_pack,
+ off_t *found_offset)
+{
+ struct packed_git *p;
+
if (!exclude && local && has_loose_object_nonlocal(sha1))
return 0;
+ *found_pack = NULL;
+ *found_offset = 0;
+
for (p = packed_git; p; p = p->next) {
off_t offset = find_pack_entry_one(sha1, p);
if (offset) {
- if (!found_pack) {
+ if (!*found_pack) {
if (!is_pack_valid(p)) {
warning("packfile %s cannot be accessed", p->pack_name);
continue;
}
- found_offset = offset;
- found_pack = p;
+ *found_offset = offset;
+ *found_pack = p;
}
if (exclude)
- break;
+ return 1;
if (incremental)
return 0;
if (local && !p->pack_local)
@@ -946,14 +968,21 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
}
}
- if (nr_objects >= nr_alloc) {
- nr_alloc = (nr_alloc + 1024) * 3 / 2;
- objects = xrealloc(objects, nr_alloc * sizeof(*entry));
- }
+ return 1;
+}
+
+static void create_object_entry(const unsigned char *sha1,
+ enum object_type type,
+ 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 = objects + nr_objects++;
- memset(entry, 0, sizeof(*entry));
- hashcpy(entry->idx.sha1, sha1);
+ entry = packlist_alloc(&to_pack, sha1, index_pos);
entry->hash = hash;
if (type)
entry->type = type;
@@ -966,16 +995,43 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
entry->in_pack_offset = found_offset;
}
- if (object_ix_hashsz * 3 <= nr_objects * 4)
- rehash_objects();
- else
- object_ix[-1 - ix] = nr_objects;
+ entry->no_try_delta = no_try_delta;
+}
+
+static int add_object_entry(const unsigned char *sha1, enum object_type type,
+ const char *name, int exclude)
+{
+ struct packed_git *found_pack;
+ off_t found_offset;
+ uint32_t index_pos;
- display_progress(progress_state, nr_objects);
+ if (have_duplicate_entry(sha1, exclude, &index_pos))
+ return 0;
+
+ if (!want_object_in_pack(sha1, exclude, &found_pack, &found_offset))
+ return 0;
- if (name && no_try_delta(name))
- entry->no_try_delta = 1;
+ create_object_entry(sha1, type, pack_name_hash(name),
+ exclude, name && no_try_delta(name),
+ index_pos, found_pack, found_offset);
+ display_progress(progress_state, to_pack.nr_objects);
+ return 1;
+}
+
+static int add_object_entry_from_bitmap(const unsigned char *sha1,
+ enum object_type type,
+ int flags, uint32_t name_hash,
+ struct packed_git *pack, off_t offset)
+{
+ uint32_t index_pos;
+
+ if (have_duplicate_entry(sha1, 0, &index_pos))
+ return 0;
+
+ create_object_entry(sha1, type, name_hash, 0, 0, index_pos, pack, offset);
+
+ display_progress(progress_state, to_pack.nr_objects);
return 1;
}
@@ -1176,7 +1232,7 @@ static void add_preferred_base_object(const char *name)
{
struct pbase_tree *it;
int cmplen;
- unsigned hash = name_hash(name);
+ unsigned hash = pack_name_hash(name);
if (!num_preferred_base || check_pbase_path(hash))
return;
@@ -1328,7 +1384,7 @@ static void check_object(struct object_entry *entry)
break;
}
- if (base_ref && (base_entry = locate_object_entry(base_ref))) {
+ if (base_ref && (base_entry = packlist_find(&to_pack, base_ref, NULL))) {
/*
* If base_ref was set above that means we wish to
* reuse delta data, and we even found that base
@@ -1402,12 +1458,12 @@ static void get_object_details(void)
uint32_t i;
struct object_entry **sorted_by_offset;
- sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *));
- for (i = 0; i < nr_objects; i++)
- sorted_by_offset[i] = objects + i;
- qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort);
+ sorted_by_offset = xcalloc(to_pack.nr_objects, sizeof(struct object_entry *));
+ 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, sizeof(*sorted_by_offset), pack_offset_sort);
- for (i = 0; i < nr_objects; i++) {
+ for (i = 0; i < to_pack.nr_objects; i++) {
struct object_entry *entry = sorted_by_offset[i];
check_object(entry);
if (big_file_threshold < entry->size)
@@ -1809,7 +1865,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
static void try_to_free_from_threads(size_t size)
{
read_lock();
- release_pack_memory(size, -1);
+ release_pack_memory(size);
read_unlock();
}
@@ -2031,9 +2087,9 @@ static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, vo
{
unsigned char peeled[20];
- if (!prefixcmp(path, "refs/tags/") && /* is a tag? */
+ if (starts_with(path, "refs/tags/") && /* is a tag? */
!peel_ref(path, peeled) && /* peelable? */
- locate_object_entry(peeled)) /* object packed? */
+ packlist_find(&to_pack, peeled, NULL)) /* object packed? */
add_object_entry(sha1, OBJ_TAG, NULL, 0);
return 0;
}
@@ -2056,14 +2112,14 @@ static void prepare_pack(int window, int depth)
if (!pack_to_stdout)
do_check_packed_object_crc = 1;
- if (!nr_objects || !window || !depth)
+ if (!to_pack.nr_objects || !window || !depth)
return;
- delta_list = xmalloc(nr_objects * sizeof(*delta_list));
+ delta_list = xmalloc(to_pack.nr_objects * sizeof(*delta_list));
nr_deltas = n = 0;
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *entry = objects + i;
+ for (i = 0; i < to_pack.nr_objects; i++) {
+ struct object_entry *entry = to_pack.objects + i;
if (entry->delta)
/* This happens if we decided to reuse existing
@@ -2141,6 +2197,20 @@ static int git_pack_config(const char *k, const char *v, void *cb)
cache_max_small_delta_size = git_config_int(k, v);
return 0;
}
+ if (!strcmp(k, "pack.writebitmaps")) {
+ write_bitmap_index = git_config_bool(k, v);
+ return 0;
+ }
+ if (!strcmp(k, "pack.writebitmaphashcache")) {
+ if (git_config_bool(k, v))
+ write_bitmap_options |= BITMAP_OPT_HASH_CACHE;
+ else
+ write_bitmap_options &= ~BITMAP_OPT_HASH_CACHE;
+ }
+ if (!strcmp(k, "pack.usebitmaps")) {
+ use_bitmap_index = git_config_bool(k, v);
+ return 0;
+ }
if (!strcmp(k, "pack.threads")) {
delta_search_threads = git_config_int(k, v);
if (delta_search_threads < 0)
@@ -2199,6 +2269,9 @@ static void show_commit(struct commit *commit, void *data)
{
add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
commit->object.flags |= OBJECT_ADDED;
+
+ if (write_bitmap_index)
+ index_commit_for_bitmap(commit);
}
static void show_object(struct object *obj,
@@ -2341,7 +2414,7 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
for (i = 0; i < p->num_objects; i++) {
sha1 = nth_packed_object_sha1(p, i);
- if (!locate_object_entry(sha1) &&
+ if (!packlist_find(&to_pack, sha1, NULL) &&
!has_sha1_pack_kept_or_nonlocal(sha1))
if (force_object_loose(sha1, p->mtime))
die("unable to force loose object");
@@ -2349,6 +2422,29 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
}
}
+static int get_object_list_from_bitmap(struct rev_info *revs)
+{
+ if (prepare_bitmap_walk(revs) < 0)
+ return -1;
+
+ if (!reuse_partial_packfile_from_bitmap(
+ &reuse_packfile,
+ &reuse_packfile_objects,
+ &reuse_packfile_offset)) {
+ assert(reuse_packfile_objects);
+ nr_result += reuse_packfile_objects;
+
+ if (progress) {
+ fprintf(stderr, "Reusing existing pack: %d, done.\n",
+ reuse_packfile_objects);
+ fflush(stderr);
+ }
+ }
+
+ traverse_bitmap_commit_list(&add_object_entry_from_bitmap);
+ return 0;
+}
+
static void get_object_list(int ac, const char **av)
{
struct rev_info revs;
@@ -2368,6 +2464,7 @@ static void get_object_list(int ac, const char **av)
if (*line == '-') {
if (!strcmp(line, "--not")) {
flags ^= UNINTERESTING;
+ write_bitmap_index = 0;
continue;
}
die("not a rev '%s'", line);
@@ -2376,9 +2473,12 @@ static void get_object_list(int ac, const char **av)
die("bad revision '%s'", line);
}
+ if (use_bitmap_index && !get_object_list_from_bitmap(&revs))
+ return;
+
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
- mark_edges_uninteresting(revs.commits, &revs, show_edge);
+ mark_edges_uninteresting(&revs, show_edge);
traverse_commit_list(&revs, show_commit, show_object, NULL);
if (keep_unreachable)
@@ -2505,6 +2605,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
N_("pack compression level")),
OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents,
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_END(),
};
@@ -2571,6 +2675,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (keep_unreachable && unpack_unreachable)
die("--keep-unreachable and --unpack-unreachable are incompatible.");
+ if (!use_internal_rev_list || !pack_to_stdout || is_repository_shallow())
+ use_bitmap_index = 0;
+
+ if (pack_to_stdout || !rev_list_all)
+ write_bitmap_index = 0;
+
if (progress && all_progress_implied)
progress = 2;
diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c
index b5a0f88eb8..b20b1ec4c1 100644
--- a/builtin/pack-refs.c
+++ b/builtin/pack-refs.c
@@ -1,6 +1,6 @@
#include "builtin.h"
#include "parse-options.h"
-#include "pack-refs.h"
+#include "refs.h"
static char const * const pack_refs_usage[] = {
N_("git pack-refs [options]"),
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
index 83382c1fe1..fcf5fb6129 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -8,73 +8,76 @@ static const char * const prune_packed_usage[] = {
NULL
};
-#define DRY_RUN 01
-#define VERBOSE 02
-
static struct progress *progress;
-static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
+static void prune_dir(int i, DIR *dir, struct strbuf *pathname, int opts)
{
struct dirent *de;
char hex[40];
+ int top_len = pathname->len;
sprintf(hex, "%02x", i);
while ((de = readdir(dir)) != NULL) {
unsigned char sha1[20];
if (strlen(de->d_name) != 38)
continue;
- memcpy(hex+2, de->d_name, 38);
+ memcpy(hex + 2, de->d_name, 38);
if (get_sha1_hex(hex, sha1))
continue;
if (!has_sha1_pack(sha1))
continue;
- memcpy(pathname + len, de->d_name, 38);
- if (opts & DRY_RUN)
- printf("rm -f %s\n", pathname);
+
+ strbuf_add(pathname, de->d_name, 38);
+ if (opts & PRUNE_PACKED_DRY_RUN)
+ printf("rm -f %s\n", pathname->buf);
else
- unlink_or_warn(pathname);
+ unlink_or_warn(pathname->buf);
display_progress(progress, i + 1);
+ strbuf_setlen(pathname, top_len);
}
}
void prune_packed_objects(int opts)
{
int i;
- static char pathname[PATH_MAX];
const char *dir = get_object_directory();
- int len = strlen(dir);
+ struct strbuf pathname = STRBUF_INIT;
+ int top_len;
- if (opts == VERBOSE)
+ strbuf_addstr(&pathname, dir);
+ if (opts & PRUNE_PACKED_VERBOSE)
progress = start_progress_delay("Removing duplicate objects",
256, 95, 2);
- if (len > PATH_MAX - 42)
- die("impossible object directory");
- memcpy(pathname, dir, len);
- if (len && pathname[len-1] != '/')
- pathname[len++] = '/';
+ if (pathname.len && pathname.buf[pathname.len - 1] != '/')
+ strbuf_addch(&pathname, '/');
+
+ top_len = pathname.len;
for (i = 0; i < 256; i++) {
DIR *d;
display_progress(progress, i + 1);
- sprintf(pathname + len, "%02x/", i);
- d = opendir(pathname);
+ strbuf_setlen(&pathname, top_len);
+ strbuf_addf(&pathname, "%02x/", i);
+ d = opendir(pathname.buf);
if (!d)
continue;
- prune_dir(i, d, pathname, len + 3, opts);
+ prune_dir(i, d, &pathname, opts);
closedir(d);
- pathname[len + 2] = '\0';
- rmdir(pathname);
+ strbuf_setlen(&pathname, top_len + 2);
+ rmdir(pathname.buf);
}
stop_progress(&progress);
}
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
- int opts = isatty(2) ? VERBOSE : 0;
+ int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0;
const struct option prune_packed_options[] = {
- OPT_BIT('n', "dry-run", &opts, N_("dry run"), DRY_RUN),
- OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"), VERBOSE),
+ OPT_BIT('n', "dry-run", &opts, N_("dry run"),
+ PRUNE_PACKED_DRY_RUN),
+ OPT_NEGBIT('q', "quiet", &opts, N_("be quiet"),
+ PRUNE_PACKED_VERBOSE),
OPT_END()
};
diff --git a/builtin/prune.c b/builtin/prune.c
index 85843d4f17..de43b26cfe 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -17,9 +17,8 @@ static int verbose;
static unsigned long expire;
static int show_progress = -1;
-static int prune_tmp_object(const char *path, const char *filename)
+static int prune_tmp_file(const char *fullpath)
{
- const char *fullpath = mkpath("%s/%s", path, filename);
struct stat st;
if (lstat(fullpath, &st))
return error("Could not stat '%s'", fullpath);
@@ -32,9 +31,8 @@ static int prune_tmp_object(const char *path, const char *filename)
return 0;
}
-static int prune_object(char *path, const char *filename, const unsigned char *sha1)
+static int prune_object(const char *fullpath, const unsigned char *sha1)
{
- const char *fullpath = mkpath("%s/%s", path, filename);
struct stat st;
if (lstat(fullpath, &st))
return error("Could not stat '%s'", fullpath);
@@ -50,9 +48,10 @@ static int prune_object(char *path, const char *filename, const unsigned char *s
return 0;
}
-static int prune_dir(int i, char *path)
+static int prune_dir(int i, struct strbuf *path)
{
- DIR *dir = opendir(path);
+ size_t baselen = path->len;
+ DIR *dir = opendir(path->buf);
struct dirent *de;
if (!dir)
@@ -77,28 +76,39 @@ static int prune_dir(int i, char *path)
if (lookup_object(sha1))
continue;
- prune_object(path, de->d_name, sha1);
+ strbuf_addf(path, "/%s", de->d_name);
+ prune_object(path->buf, sha1);
+ strbuf_setlen(path, baselen);
continue;
}
- if (!prefixcmp(de->d_name, "tmp_obj_")) {
- prune_tmp_object(path, de->d_name);
+ if (starts_with(de->d_name, "tmp_obj_")) {
+ strbuf_addf(path, "/%s", de->d_name);
+ prune_tmp_file(path->buf);
+ strbuf_setlen(path, baselen);
continue;
}
- fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+ fprintf(stderr, "bad sha1 file: %s/%s\n", path->buf, de->d_name);
}
closedir(dir);
if (!show_only)
- rmdir(path);
+ rmdir(path->buf);
return 0;
}
static void prune_object_dir(const char *path)
{
+ struct strbuf buf = STRBUF_INIT;
+ size_t baselen;
int i;
+
+ strbuf_addstr(&buf, path);
+ strbuf_addch(&buf, '/');
+ baselen = buf.len;
+
for (i = 0; i < 256; i++) {
- static char dir[4096];
- sprintf(dir, "%s/%02x", path, i);
- prune_dir(i, dir);
+ strbuf_addf(&buf, "%02x", i);
+ prune_dir(i, &buf);
+ strbuf_setlen(&buf, baselen);
}
}
@@ -119,8 +129,8 @@ static void remove_temporary_files(const char *path)
return;
}
while ((de = readdir(dir)) != NULL)
- if (!prefixcmp(de->d_name, "tmp_"))
- prune_tmp_object(path, de->d_name);
+ if (starts_with(de->d_name, "tmp_"))
+ prune_tmp_file(mkpath("%s/%s", path, de->d_name));
closedir(dir);
}
@@ -132,8 +142,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned objects")),
OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
- OPT_DATE(0, "expire", &expire,
- N_("expire objects older than <time>")),
+ OPT_EXPIRY_DATE(0, "expire", &expire,
+ N_("expire objects older than <time>")),
OPT_END()
};
char *s;
@@ -165,10 +175,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
stop_progress(&progress);
prune_object_dir(get_object_directory());
- prune_packed_objects(show_only);
+ prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
remove_temporary_files(get_object_directory());
s = mkpathdup("%s/pack", get_object_directory());
remove_temporary_files(s);
free(s);
+
+ if (is_repository_shallow())
+ prune_shallow(show_only);
+
return 0;
}
diff --git a/builtin/push.c b/builtin/push.c
index 909c34dfda..3dd160c6b6 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -15,16 +15,17 @@ static const char * const push_usage[] = {
NULL,
};
-static int thin;
+static int thin = 1;
static int deleterefs;
static const char *receivepack;
static int verbosity;
static int progress = -1;
+static struct push_cas_option cas;
+
static const char **refspec;
static int refspec_nr;
static int refspec_alloc;
-static int default_matching_used;
static void add_refspec(const char *ref)
{
@@ -33,35 +34,75 @@ static void add_refspec(const char *ref)
refspec[refspec_nr-1] = ref;
}
-static void set_refspecs(const char **refs, int nr)
+static const char *map_refspec(const char *ref,
+ struct remote *remote, struct ref *local_refs)
+{
+ struct ref *matched = NULL;
+
+ /* Does "ref" uniquely name our ref? */
+ if (count_refspec_match(ref, local_refs, &matched) != 1)
+ return ref;
+
+ if (remote->push) {
+ struct refspec query;
+ memset(&query, 0, sizeof(struct refspec));
+ query.src = matched->name;
+ if (!query_refspecs(remote->push, remote->push_refspec_nr, &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);
+ }
+ }
+
+ if (push_default == PUSH_DEFAULT_UPSTREAM &&
+ !prefixcmp(matched->name, "refs/heads/")) {
+ struct branch *branch = branch_get(matched->name + 11);
+ 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);
+ }
+ }
+
+ return ref;
+}
+
+static void set_refspecs(const char **refs, int nr, const char *repo)
{
+ struct remote *remote = NULL;
+ struct ref *local_refs = NULL;
int i;
+
for (i = 0; i < nr; i++) {
const char *ref = refs[i];
if (!strcmp("tag", ref)) {
- char *tag;
- int len;
+ struct strbuf tagref = STRBUF_INIT;
if (nr <= ++i)
die(_("tag shorthand without <tag>"));
- len = strlen(refs[i]) + 11;
- if (deleterefs) {
- tag = xmalloc(len+1);
- strcpy(tag, ":refs/tags/");
- } else {
- tag = xmalloc(len);
- strcpy(tag, "refs/tags/");
+ ref = refs[i];
+ if (deleterefs)
+ strbuf_addf(&tagref, ":refs/tags/%s", ref);
+ else
+ strbuf_addf(&tagref, "refs/tags/%s", ref);
+ ref = strbuf_detach(&tagref, NULL);
+ } else if (deleterefs) {
+ struct strbuf delref = STRBUF_INIT;
+ if (strchr(ref, ':'))
+ die(_("--delete only accepts plain target ref names"));
+ strbuf_addf(&delref, ":%s", ref);
+ ref = strbuf_detach(&delref, NULL);
+ } else if (!strchr(ref, ':')) {
+ if (!remote) {
+ /* lazily grab remote and local_refs */
+ remote = remote_get(repo);
+ local_refs = get_local_heads();
}
- strcat(tag, refs[i]);
- ref = tag;
- } else if (deleterefs && !strchr(ref, ':')) {
- char *delref;
- int len = strlen(ref)+1;
- delref = xmalloc(len+1);
- strcpy(delref, ":");
- strcat(delref, ref);
- ref = delref;
- } else if (deleterefs)
- die(_("--delete only accepts plain target ref names"));
+ ref = map_refspec(ref, remote, local_refs);
+ }
add_refspec(ref);
}
}
@@ -92,7 +133,7 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote
if (!short_upstream)
short_upstream = branch->merge[0]->src;
/*
- * Don't show advice for people who explicitely set
+ * Don't show advice for people who explicitly set
* push.default.
*/
if (push_default == PUSH_DEFAULT_UNSPECIFIED)
@@ -113,17 +154,20 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote
remote->name, branch->name, advice_maybe);
}
-static void setup_push_upstream(struct remote *remote, int simple)
+static const char message_detached_head_die[] =
+ N_("You are not currently on a branch.\n"
+ "To push the history leading to the current (detached HEAD)\n"
+ "state now, use\n"
+ "\n"
+ " git push %s HEAD:<name-of-remote-branch>\n");
+
+static void setup_push_upstream(struct remote *remote, struct branch *branch,
+ int triangular)
{
struct strbuf refspec = STRBUF_INIT;
- struct branch *branch = branch_get(NULL);
+
if (!branch)
- die(_("You are not currently on a branch.\n"
- "To push the history leading to the current (detached HEAD)\n"
- "state now, use\n"
- "\n"
- " git push %s HEAD:<name-of-remote-branch>\n"),
- remote->name);
+ 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"
@@ -135,22 +179,33 @@ static void setup_push_upstream(struct remote *remote, int simple)
if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name);
- if (strcmp(branch->remote_name, remote->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 && strcmp(branch->refname, branch->merge[0]->src))
- die_push_simple(branch, remote);
+
+ if (push_default == PUSH_DEFAULT_SIMPLE) {
+ /* Additional safety */
+ if (strcmp(branch->refname, branch->merge[0]->src))
+ die_push_simple(branch, remote);
+ }
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf);
}
+static void setup_push_current(struct remote *remote, struct branch *branch)
+{
+ if (!branch)
+ die(_(message_detached_head_die), remote->name);
+ add_refspec(branch->name);
+}
+
static char warn_unspecified_push_default_msg[] =
-N_("push.default is unset; its implicit value is changing in\n"
+N_("push.default is unset; its implicit value has changed in\n"
"Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
- "and maintain the current behavior after the default changes, use:\n"
+ "and maintain the traditional behavior, use:\n"
"\n"
" git config --global push.default matching\n"
"\n"
@@ -158,6 +213,13 @@ N_("push.default is unset; its implicit value is changing in\n"
"\n"
" git config --global push.default simple\n"
"\n"
+ "When push.default is set to 'matching', git will push local branches\n"
+ "to the remote branches that already exist with the same name.\n"
+ "\n"
+ "In Git 2.0, Git will default to the more conservative 'simple'\n"
+ "behavior, which only pushes the current branch to the corresponding\n"
+ "remote branch that 'git pull' uses to update the current branch.\n"
+ "\n"
"See 'git help config' and search for 'push.default' for further information.\n"
"(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode\n"
"'current' instead of 'simple' if you sometimes use older versions of Git)");
@@ -171,28 +233,40 @@ static void warn_unspecified_push_default_configuration(void)
warning("%s\n", _(warn_unspecified_push_default_msg));
}
+static int is_workflow_triangular(struct remote *remote)
+{
+ struct remote *fetch_remote = remote_get(NULL);
+ return (fetch_remote && fetch_remote != remote);
+}
+
static void setup_default_push_refspecs(struct remote *remote)
{
+ struct branch *branch = branch_get(NULL);
+ int triangular = is_workflow_triangular(remote);
+
switch (push_default) {
default:
- case PUSH_DEFAULT_UNSPECIFIED:
- default_matching_used = 1;
- warn_unspecified_push_default_configuration();
- /* fallthru */
case PUSH_DEFAULT_MATCHING:
add_refspec(":");
break;
+ case PUSH_DEFAULT_UNSPECIFIED:
+ warn_unspecified_push_default_configuration();
+ /* fallthru */
+
case PUSH_DEFAULT_SIMPLE:
- setup_push_upstream(remote, 1);
+ if (triangular)
+ setup_push_current(remote, branch);
+ else
+ setup_push_upstream(remote, branch, triangular);
break;
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote, 0);
+ setup_push_upstream(remote, branch, triangular);
break;
case PUSH_DEFAULT_CURRENT:
- add_refspec("HEAD");
+ setup_push_current(remote, branch);
break;
case PUSH_DEFAULT_NOTHING:
@@ -204,27 +278,21 @@ static void setup_default_push_refspecs(struct remote *remote)
static const char message_advice_pull_before_push[] =
N_("Updates were rejected because the tip of your current branch is behind\n"
- "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
- "before pushing again.\n"
+ "its remote counterpart. Integrate the remote changes (e.g.\n"
+ "'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
-static const char message_advice_use_upstream[] =
- N_("Updates were rejected because a pushed branch tip is behind its remote\n"
- "counterpart. If you did not intend to push that branch, you may want to\n"
- "specify branches to push or set the 'push.default' configuration variable\n"
- "to 'simple', 'current' or 'upstream' to push only the current branch.");
-
static const char message_advice_checkout_pull_push[] =
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
- "counterpart. Check out this branch and merge the remote changes\n"
- "(e.g. 'git pull') before pushing again.\n"
+ "counterpart. Check out this branch and integrate the remote changes\n"
+ "(e.g. 'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_ref_fetch_first[] =
N_("Updates were rejected because the remote contains work that you do\n"
"not have locally. This is usually caused by another repository pushing\n"
- "to the same ref. You may want to first merge the remote changes (e.g.,\n"
- "'git pull') before pushing again.\n"
+ "to the same ref. You may want to first integrate the remote changes\n"
+ "(e.g., 'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_ref_already_exists[] =
@@ -242,13 +310,6 @@ static void advise_pull_before_push(void)
advise(_(message_advice_pull_before_push));
}
-static void advise_use_upstream(void)
-{
- if (!advice_push_non_ff_default || !advice_push_update_rejected)
- return;
- advise(_(message_advice_use_upstream));
-}
-
static void advise_checkout_pull_push(void)
{
if (!advice_push_non_ff_matching || !advice_push_update_rejected)
@@ -287,8 +348,14 @@ static int push_with_options(struct transport *transport, int flags)
if (receivepack)
transport_set_option(transport,
TRANS_OPT_RECEIVEPACK, receivepack);
- if (thin)
- transport_set_option(transport, TRANS_OPT_THIN, "yes");
+ transport_set_option(transport, TRANS_OPT_THIN, thin ? "yes" : NULL);
+
+ if (!is_empty_cas(&cas)) {
+ if (!transport->smart_options)
+ die("underlying transport does not support --%s option",
+ CAS_OPT_NAME);
+ transport->smart_options->cas = &cas;
+ }
if (verbosity > 0)
fprintf(stderr, _("Pushing to %s\n"), transport->url);
@@ -304,10 +371,7 @@ static int push_with_options(struct transport *transport, int flags)
if (reject_reasons & REJECT_NON_FF_HEAD) {
advise_pull_before_push();
} else if (reject_reasons & REJECT_NON_FF_OTHER) {
- if (default_matching_used)
- advise_use_upstream();
- else
- advise_checkout_pull_push();
+ advise_checkout_pull_push();
} else if (reject_reasons & REJECT_ALREADY_EXISTS) {
advise_ref_already_exists();
} else if (reject_reasons & REJECT_FETCH_FIRST) {
@@ -420,15 +484,19 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT( 0 , "all", &flags, N_("push all refs"), TRANSPORT_PUSH_ALL),
OPT_BIT( 0 , "mirror", &flags, N_("mirror all refs"),
(TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)),
- OPT_BOOLEAN( 0, "delete", &deleterefs, N_("delete refs")),
- OPT_BOOLEAN( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")),
+ OPT_BOOL( 0, "delete", &deleterefs, N_("delete refs")),
+ OPT_BOOL( 0 , "tags", &tags, N_("push tags (can't be used with --all or --mirror)")),
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, parseopt_push_cas_option },
{ OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"),
N_("control recursive pushing of submodules"),
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
- OPT_BOOLEAN( 0 , "thin", &thin, N_("use thin pack")),
+ OPT_BOOL( 0 , "thin", &thin, N_("use thin pack")),
OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")),
OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")),
OPT_BIT('u', "set-upstream", &flags, N_("set upstream for git pull/status"),
@@ -456,7 +524,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
if (argc > 0) {
repo = argv[0];
- set_refspecs(argv + 1, argc - 1);
+ set_refspecs(argv + 1, argc - 1, repo);
}
rc = do_push(repo, flags);
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 042ac1b84f..0d7ef847a7 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -66,7 +66,7 @@ static int exclude_per_directory_cb(const struct option *opt, const char *arg,
return 0;
}
-static void debug_stage(const char *label, struct cache_entry *ce,
+static void debug_stage(const char *label, const struct cache_entry *ce,
struct unpack_trees_options *o)
{
printf("%s ", label);
@@ -80,7 +80,8 @@ static void debug_stage(const char *label, struct cache_entry *ce,
sha1_to_hex(ce->sha1));
}
-static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+static int debug_merge(const struct cache_entry * const *stages,
+ struct unpack_trees_options *o)
{
int i;
@@ -177,7 +178,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
if (1 < opts.index_only + opts.update)
die("-u and -i at the same time makes no sense");
- if ((opts.update||opts.index_only) && !opts.merge)
+ if ((opts.update || opts.index_only) && !opts.merge)
die("%s is meaningless without -m, --reset, or --prefix",
opts.update ? "-u" : "-i");
if ((opts.dir && !opts.update))
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e3eb5fc058..85bba356fa 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -8,10 +8,12 @@
#include "commit.h"
#include "object.h"
#include "remote.h"
+#include "connect.h"
#include "transport.h"
#include "string-list.h"
#include "sha1-array.h"
#include "connected.h"
+#include "argv-array.h"
#include "version.h"
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
@@ -38,9 +40,12 @@ static int quiet;
static int prefer_ofs_delta = 1;
static int auto_update_server_info;
static int auto_gc = 1;
+static int fix_thin = 1;
static const char *head_name;
static void *head_name_to_free;
static int sent_capabilities;
+static int shallow_update;
+static const char *alt_shallow_file;
static enum deny_action parse_deny_action(const char *var, const char *value)
{
@@ -119,6 +124,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "receive.shallowupdate") == 0) {
+ shallow_update = git_config_bool(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -176,6 +186,8 @@ static void write_head_info(void)
if (!sent_capabilities)
show_ref("capabilities^{}", null_sha1);
+ advertise_shallow_grafts(1);
+
/* EOF */
packet_flush(1);
}
@@ -185,6 +197,7 @@ struct command {
const char *error_string;
unsigned int skip_update:1,
did_not_exist:1;
+ int index;
unsigned char old_sha1[20];
unsigned char new_sha1[20];
char ref_name[FLEX_ARRAY]; /* more */
@@ -416,7 +429,46 @@ static void refuse_unconfigured_deny_delete_current(void)
rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
}
-static const char *update(struct command *cmd)
+static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]);
+static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
+{
+ static struct lock_file shallow_lock;
+ struct sha1_array extra = SHA1_ARRAY_INIT;
+ const char *alt_file;
+ uint32_t mask = 1 << (cmd->index % 32);
+ int i;
+
+ trace_printf_key("GIT_TRACE_SHALLOW",
+ "shallow: update_shallow_ref %s\n", cmd->ref_name);
+ for (i = 0; i < si->shallow->nr; i++)
+ if (si->used_shallow[i] &&
+ (si->used_shallow[i][cmd->index / 32] & mask) &&
+ !delayed_reachability_test(si, i))
+ sha1_array_append(&extra, si->shallow->sha1[i]);
+
+ setup_alternate_shallow(&shallow_lock, &alt_file, &extra);
+ if (check_shallow_connected(command_singleton_iterator,
+ 0, cmd, alt_file)) {
+ rollback_lock_file(&shallow_lock);
+ sha1_array_clear(&extra);
+ return -1;
+ }
+
+ commit_lock_file(&shallow_lock);
+
+ /*
+ * Make sure setup_alternate_shallow() for the next ref does
+ * not lose these new roots..
+ */
+ for (i = 0; i < extra.nr; i++)
+ register_shallow(extra.sha1[i]);
+
+ si->shallow_ref[cmd->index] = 0;
+ sha1_array_clear(&extra);
+ return 0;
+}
+
+static const char *update(struct command *cmd, struct shallow_info *si)
{
const char *name = cmd->ref_name;
struct strbuf namespaced_name_buf = STRBUF_INIT;
@@ -426,7 +478,7 @@ static const char *update(struct command *cmd)
struct ref_lock *lock;
/* only refs/... are allowed */
- if (prefixcmp(name, "refs/") || check_refname_format(name + 5, 0)) {
+ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
rp_error("refusing to create funny ref '%s' remotely", name);
return "funny refname";
}
@@ -457,7 +509,7 @@ static const char *update(struct command *cmd)
}
if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
- if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
+ if (deny_deletes && starts_with(name, "refs/heads/")) {
rp_error("denying ref deletion for %s", name);
return "deletion prohibited";
}
@@ -481,7 +533,7 @@ static const char *update(struct command *cmd)
if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
!is_null_sha1(old_sha1) &&
- !prefixcmp(name, "refs/heads/")) {
+ starts_with(name, "refs/heads/")) {
struct object *old_object, *new_object;
struct commit *old_commit, *new_commit;
@@ -524,7 +576,12 @@ static const char *update(struct command *cmd)
return NULL; /* good */
}
else {
- lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
+ if (shallow_update && si->shallow_ref[cmd->index] &&
+ update_shallow_ref(cmd, si))
+ return "shallow error";
+
+ lock = lock_any_ref_for_update(namespaced_name, old_sha1,
+ 0, NULL);
if (!lock) {
rp_error("failed to lock %s", name);
return "failed to lock";
@@ -663,12 +720,16 @@ static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
return 0;
}
-static void set_connectivity_errors(struct command *commands)
+static void set_connectivity_errors(struct command *commands,
+ struct shallow_info *si)
{
struct command *cmd;
for (cmd = commands; cmd; cmd = cmd->next) {
struct command *singleton = cmd;
+ if (shallow_update && si->shallow_ref[cmd->index])
+ /* to be checked in update_shallow_ref() */
+ continue;
if (!check_everything_connected(command_singleton_iterator,
0, &singleton))
continue;
@@ -676,18 +737,26 @@ static void set_connectivity_errors(struct command *commands)
}
}
+struct iterate_data {
+ struct command *cmds;
+ struct shallow_info *si;
+};
+
static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
{
- struct command **cmd_list = cb_data;
+ struct iterate_data *data = cb_data;
+ struct command **cmd_list = &data->cmds;
struct command *cmd = *cmd_list;
- while (cmd) {
- if (!is_null_sha1(cmd->new_sha1)) {
+ for (; cmd; cmd = cmd->next) {
+ if (shallow_update && data->si->shallow_ref[cmd->index])
+ /* to be checked in update_shallow_ref() */
+ continue;
+ if (!is_null_sha1(cmd->new_sha1) && !cmd->skip_update) {
hashcpy(sha1, cmd->new_sha1);
*cmd_list = cmd->next;
return 0;
}
- cmd = cmd->next;
}
*cmd_list = NULL;
return -1; /* end of list */
@@ -707,10 +776,14 @@ static void reject_updates_to_hidden(struct command *commands)
}
}
-static void execute_commands(struct command *commands, const char *unpacker_error)
+static void execute_commands(struct command *commands,
+ const char *unpacker_error,
+ struct shallow_info *si)
{
+ int checked_connectivity;
struct command *cmd;
unsigned char sha1[20];
+ struct iterate_data data;
if (unpacker_error) {
for (cmd = commands; cmd; cmd = cmd->next)
@@ -718,10 +791,10 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
return;
}
- cmd = commands;
- if (check_everything_connected(iterate_receive_command_list,
- 0, &cmd))
- set_connectivity_errors(commands);
+ data.cmds = commands;
+ data.si = si;
+ if (check_everything_connected(iterate_receive_command_list, 0, &data))
+ set_connectivity_errors(commands, si);
reject_updates_to_hidden(commands);
@@ -738,6 +811,7 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
free(head_name_to_free);
head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
+ checked_connectivity = 1;
for (cmd = commands; cmd; cmd = cmd->next) {
if (cmd->error_string)
continue;
@@ -745,11 +819,26 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
if (cmd->skip_update)
continue;
- cmd->error_string = update(cmd);
+ cmd->error_string = update(cmd, si);
+ if (shallow_update && !cmd->error_string &&
+ si->shallow_ref[cmd->index]) {
+ error("BUG: connectivity check has not been run on ref %s",
+ cmd->ref_name);
+ checked_connectivity = 0;
+ }
+ }
+
+ if (shallow_update) {
+ if (!checked_connectivity)
+ error("BUG: run 'git fsck' for safety.\n"
+ "If there are errors, try to remove "
+ "the reported refs above");
+ if (alt_shallow_file && *alt_shallow_file)
+ unlink(alt_shallow_file);
}
}
-static struct command *read_head_info(void)
+static struct command *read_head_info(struct sha1_array *shallow)
{
struct command *commands = NULL;
struct command **p = &commands;
@@ -763,6 +852,14 @@ static struct command *read_head_info(void)
line = packet_read_line(0, &len);
if (!line)
break;
+
+ if (len == 48 && starts_with(line, "shallow ")) {
+ if (get_sha1_hex(line + 8, old_sha1))
+ die("protocol error: expected shallow sha, got '%s'", line + 8);
+ sha1_array_append(shallow, old_sha1);
+ continue;
+ }
+
if (len < 83 ||
line[40] != ' ' ||
line[81] != ' ' ||
@@ -814,11 +911,14 @@ static const char *parse_pack_header(struct pack_header *hdr)
static const char *pack_lockfile;
-static const char *unpack(int err_fd)
+static const char *unpack(int err_fd, struct shallow_info *si)
{
struct pack_header hdr;
+ struct argv_array av = ARGV_ARRAY_INIT;
const char *hdr_err;
+ int status;
char hdr_arg[38];
+ struct child_process child;
int fsck_objects = (receive_fsck_objects >= 0
? receive_fsck_objects
: transfer_fsck_objects >= 0
@@ -835,71 +935,63 @@ static const char *unpack(int err_fd)
"--pack_header=%"PRIu32",%"PRIu32,
ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
+ if (si->nr_ours || si->nr_theirs) {
+ alt_shallow_file = setup_temporary_shallow(si->shallow);
+ argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL);
+ }
+
+ memset(&child, 0, sizeof(child));
if (ntohl(hdr.hdr_entries) < unpack_limit) {
- int code, i = 0;
- struct child_process child;
- const char *unpacker[5];
- unpacker[i++] = "unpack-objects";
+ argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL);
if (quiet)
- unpacker[i++] = "-q";
+ argv_array_push(&av, "-q");
if (fsck_objects)
- unpacker[i++] = "--strict";
- unpacker[i++] = hdr_arg;
- unpacker[i++] = NULL;
- memset(&child, 0, sizeof(child));
- child.argv = unpacker;
+ argv_array_push(&av, "--strict");
+ child.argv = av.argv;
child.no_stdout = 1;
child.err = err_fd;
child.git_cmd = 1;
- code = run_command(&child);
- if (!code)
- return NULL;
- return "unpack-objects abnormal exit";
+ status = run_command(&child);
+ if (status)
+ return "unpack-objects abnormal exit";
} else {
- const char *keeper[7];
- int s, status, i = 0;
+ int s;
char keep_arg[256];
- struct child_process ip;
s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
strcpy(keep_arg + s, "localhost");
- keeper[i++] = "index-pack";
- keeper[i++] = "--stdin";
+ argv_array_pushl(&av, "index-pack",
+ "--stdin", hdr_arg, keep_arg, NULL);
if (fsck_objects)
- keeper[i++] = "--strict";
- keeper[i++] = "--fix-thin";
- keeper[i++] = hdr_arg;
- keeper[i++] = keep_arg;
- keeper[i++] = NULL;
- memset(&ip, 0, sizeof(ip));
- ip.argv = keeper;
- ip.out = -1;
- ip.err = err_fd;
- ip.git_cmd = 1;
- status = start_command(&ip);
- if (status) {
+ argv_array_push(&av, "--strict");
+ if (fix_thin)
+ argv_array_push(&av, "--fix-thin");
+ child.argv = av.argv;
+ 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(ip.out);
- close(ip.out);
- status = finish_command(&ip);
- if (!status) {
- reprepare_packed_git();
- return NULL;
- }
- return "index-pack abnormal exit";
+ pack_lockfile = index_pack_lockfile(child.out);
+ close(child.out);
+ status = finish_command(&child);
+ if (status)
+ return "index-pack abnormal exit";
+ reprepare_packed_git();
}
+ return NULL;
}
-static const char *unpack_with_sideband(void)
+static const char *unpack_with_sideband(struct shallow_info *si)
{
struct async muxer;
const char *ret;
if (!use_sideband)
- return unpack(0);
+ return unpack(0, si);
memset(&muxer, 0, sizeof(muxer));
muxer.proc = copy_to_sideband;
@@ -907,12 +999,101 @@ static const char *unpack_with_sideband(void)
if (start_async(&muxer))
return NULL;
- ret = unpack(muxer.in);
+ ret = unpack(muxer.in, si);
finish_async(&muxer);
return ret;
}
+static void prepare_shallow_update(struct command *commands,
+ struct shallow_info *si)
+{
+ int i, j, k, bitmap_size = (si->ref->nr + 31) / 32;
+
+ si->used_shallow = xmalloc(sizeof(*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));
+
+ for (i = 0; i < si->nr_ours; i++)
+ si->need_reachability_test[si->ours[i]] = 1;
+
+ for (i = 0; i < si->shallow->nr; i++) {
+ if (!si->used_shallow[i])
+ continue;
+ for (j = 0; j < bitmap_size; j++) {
+ if (!si->used_shallow[i][j])
+ continue;
+ si->need_reachability_test[i]++;
+ for (k = 0; k < 32; k++)
+ if (si->used_shallow[i][j] & (1 << k))
+ si->shallow_ref[j * 32 + k]++;
+ }
+
+ /*
+ * true for those associated with some refs and belong
+ * in "ours" list aka "step 7 not done yet"
+ */
+ si->need_reachability_test[i] =
+ si->need_reachability_test[i] > 1;
+ }
+
+ /*
+ * keep hooks happy by forcing a temporary shallow file via
+ * env variable because we can't add --shallow-file to every
+ * command. check_everything_connected() will be done with
+ * true .git/shallow though.
+ */
+ setenv(GIT_SHALLOW_FILE_ENVIRONMENT, alt_shallow_file, 1);
+}
+
+static void update_shallow_info(struct command *commands,
+ struct shallow_info *si,
+ struct sha1_array *ref)
+{
+ struct command *cmd;
+ int *ref_status;
+ remove_nonexistent_theirs_shallow(si);
+ if (!si->nr_ours && !si->nr_theirs) {
+ shallow_update = 0;
+ return;
+ }
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (is_null_sha1(cmd->new_sha1))
+ continue;
+ sha1_array_append(ref, cmd->new_sha1);
+ cmd->index = ref->nr - 1;
+ }
+ si->ref = ref;
+
+ if (shallow_update) {
+ prepare_shallow_update(commands, si);
+ return;
+ }
+
+ ref_status = xmalloc(sizeof(*ref_status) * ref->nr);
+ assign_shallow_commits_to_refs(si, NULL, ref_status);
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (is_null_sha1(cmd->new_sha1))
+ continue;
+ if (ref_status[cmd->index]) {
+ cmd->error_string = "shallow update not allowed";
+ cmd->skip_update = 1;
+ }
+ }
+ if (alt_shallow_file && *alt_shallow_file) {
+ unlink(alt_shallow_file);
+ alt_shallow_file = NULL;
+ }
+ free(ref_status);
+}
+
static void report(struct command *commands, const char *unpack_status)
{
struct command *cmd;
@@ -954,6 +1135,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
int i;
char *dir = NULL;
struct command *commands;
+ struct sha1_array shallow = SHA1_ARRAY_INIT;
+ struct sha1_array ref = SHA1_ARRAY_INIT;
+ struct shallow_info si;
packet_trace_identity("receive-pack");
@@ -975,6 +1159,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
stateless_rpc = 1;
continue;
}
+ if (!strcmp(arg, "--reject-thin-pack-for-testing")) {
+ fix_thin = 0;
+ continue;
+ }
usage(receive_pack_usage);
}
@@ -990,9 +1178,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if (!enter_repo(dir, 0))
die("'%s' does not appear to be a git repository", dir);
- if (is_repository_shallow())
- die("attempt to push into a shallow repository");
-
git_config(receive_pack_config, NULL);
if (0 <= transfer_unpack_limit)
@@ -1006,12 +1191,17 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if (advertise_refs)
return 0;
- if ((commands = read_head_info()) != NULL) {
+ if ((commands = read_head_info(&shallow)) != NULL) {
const char *unpack_status = NULL;
- if (!delete_only(commands))
- unpack_status = unpack_with_sideband();
- execute_commands(commands, unpack_status);
+ prepare_shallow_info(&si, &shallow);
+ if (!si.nr_ours && !si.nr_theirs)
+ shallow_update = 0;
+ if (!delete_only(commands)) {
+ unpack_status = unpack_with_sideband(&si);
+ update_shallow_info(commands, &si, &ref);
+ }
+ execute_commands(commands, unpack_status, &si);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
if (report_status)
@@ -1027,8 +1217,11 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
}
if (auto_update_server_info)
update_server_info(0);
+ clear_shallow_info(&si);
}
if (use_sideband)
packet_flush(1);
+ sha1_array_clear(&shallow);
+ sha1_array_clear(&ref);
return 0;
}
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 72a0af70c3..852cff60b7 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -94,8 +94,7 @@ static int tree_is_complete(const unsigned char *sha1)
complete = 0;
}
}
- free(tree->buffer);
- tree->buffer = NULL;
+ free_tree_buffer(tree);
if (complete)
tree->object.flags |= SEEN;
@@ -366,7 +365,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
* we take the lock for the ref itself to prevent it from
* getting updated.
*/
- lock = lock_any_ref_for_update(ref, sha1, 0);
+ lock = lock_any_ref_for_update(ref, sha1, 0, NULL);
if (!lock)
return error("cannot lock ref '%s'", ref);
log_file = git_pathdup("logs/%s", ref);
@@ -496,11 +495,9 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l
{
if (!value)
return config_error_nonbool(var);
- if (!strcmp(value, "never") || !strcmp(value, "false")) {
- *expire = 0;
- return 0;
- }
- *expire = approxidate(value);
+ if (parse_expiry_date(value, expire))
+ return error(_("%s' for '%s' is not a valid timestamp"),
+ value, var);
return 0;
}
@@ -613,12 +610,14 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
const char *arg = argv[i];
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
cb.dry_run = 1;
- else if (!prefixcmp(arg, "--expire=")) {
- cb.expire_total = approxidate(arg + 9);
+ else if (starts_with(arg, "--expire=")) {
+ if (parse_expiry_date(arg + 9, &cb.expire_total))
+ die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_TOTAL;
}
- else if (!prefixcmp(arg, "--expire-unreachable=")) {
- cb.expire_unreachable = approxidate(arg + 21);
+ else if (starts_with(arg, "--expire-unreachable=")) {
+ if (parse_expiry_date(arg + 21, &cb.expire_unreachable))
+ die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_UNREACH;
}
else if (!strcmp(arg, "--stale-fix"))
diff --git a/builtin/remote.c b/builtin/remote.c
index 937484d7c7..b3ab4cf8f6 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -6,13 +6,14 @@
#include "strbuf.h"
#include "run-command.h"
#include "refs.h"
+#include "argv-array.h"
static const char * const builtin_remote_usage[] = {
N_("git remote [-v | --verbose]"),
N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>"),
N_("git remote rename <old> <new>"),
N_("git remote remove <name>"),
- N_("git remote set-head <name> (-a | -d | <branch>)"),
+ N_("git remote set-head <name> (-a | --auto | -d | --delete |<branch>)"),
N_("git remote [-v | --verbose] show [-n] <name>"),
N_("git remote prune [-n | --dry-run] <name>"),
N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"),
@@ -39,7 +40,7 @@ static const char * const builtin_remote_rm_usage[] = {
};
static const char * const builtin_remote_sethead_usage[] = {
- N_("git remote set-head <name> (-a | -d | <branch>)"),
+ N_("git remote set-head <name> (-a | --auto | -d | --delete | <branch>)"),
NULL
};
@@ -77,17 +78,6 @@ static const char * const builtin_remote_seturl_usage[] = {
static int verbose;
-static int show_all(void);
-static int prune_remote(const char *remote, int dry_run);
-
-static inline int postfixcmp(const char *string, const char *postfix)
-{
- int len1 = strlen(string), len2 = strlen(postfix);
- if (len1 < len2)
- return 1;
- return strcmp(string + len1 - len2, postfix);
-}
-
static int fetch_remote(const char *name)
{
const char *argv[] = { "fetch", name, NULL, NULL };
@@ -160,7 +150,7 @@ static int add(int argc, const char **argv)
int i;
struct option options[] = {
- OPT_BOOLEAN('f', "fetch", &fetch, N_("fetch the remote branches")),
+ OPT_BOOL('f', "fetch", &fetch, N_("fetch the remote branches")),
OPT_SET_INT(0, "tags", &fetch_tags,
N_("import all tags and associated objects when fetching"),
TAGS_SET),
@@ -178,7 +168,7 @@ static int add(int argc, const char **argv)
argc = parse_options(argc, argv, NULL, options, builtin_remote_add_usage,
0);
- if (argc < 2)
+ if (argc != 2)
usage_with_options(builtin_remote_add_usage, options);
if (mirror && master)
@@ -269,7 +259,7 @@ 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 (!prefixcmp(key, "branch.")) {
+ if (starts_with(key, "branch.")) {
const char *orig_key = key;
char *name;
struct string_list_item *item;
@@ -277,13 +267,13 @@ static int config_read_branches(const char *key, const char *value, void *cb)
enum { REMOTE, MERGE, REBASE } type;
key += 7;
- if (!postfixcmp(key, ".remote")) {
+ if (ends_with(key, ".remote")) {
name = xstrndup(key, strlen(key) - 7);
type = REMOTE;
- } else if (!postfixcmp(key, ".merge")) {
+ } else if (ends_with(key, ".merge")) {
name = xstrndup(key, strlen(key) - 6);
type = MERGE;
- } else if (!postfixcmp(key, ".rebase")) {
+ } else if (ends_with(key, ".rebase")) {
name = xstrndup(key, strlen(key) - 7);
type = REBASE;
} else
@@ -309,8 +299,13 @@ static int config_read_branches(const char *key, const char *value, void *cb)
space = strchr(value, ' ');
}
string_list_append(&info->merge, xstrdup(value));
- } else
- info->rebase = git_config_bool(orig_key, value);
+ } else {
+ int v = git_config_maybe_bool(orig_key, value);
+ if (v >= 0)
+ info->rebase = v;
+ else if (!strcmp(value, "preserve"))
+ info->rebase = 1;
+ }
}
return 0;
}
@@ -534,9 +529,9 @@ static int add_branch_for_removal(const char *refname,
}
/* don't delete non-remote-tracking refs */
- if (prefixcmp(refname, "refs/remotes/")) {
+ if (!starts_with(refname, "refs/remotes/")) {
/* advise user how to delete local branches */
- if (!prefixcmp(refname, "refs/heads/"))
+ if (starts_with(refname, "refs/heads/"))
string_list_append(branches->skipped,
abbrev_branch(refname));
/* silently skip over other non-remote refs */
@@ -571,7 +566,7 @@ static int read_remote_branches(const char *refname,
const char *symref;
strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
- if (!prefixcmp(refname, buf.buf)) {
+ if (starts_with(refname, buf.buf)) {
item = string_list_append(rename->remote_branches, xstrdup(refname));
symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag);
if (flag & REF_ISSYMREF)
@@ -1084,11 +1079,69 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data)
return 0;
}
+static int get_one_entry(struct remote *remote, void *priv)
+{
+ struct string_list *list = priv;
+ struct strbuf url_buf = STRBUF_INIT;
+ const char **url;
+ int i, url_nr;
+
+ if (remote->url_nr > 0) {
+ strbuf_addf(&url_buf, "%s (fetch)", remote->url[0]);
+ string_list_append(list, remote->name)->util =
+ strbuf_detach(&url_buf, NULL);
+ } else
+ string_list_append(list, remote->name)->util = NULL;
+ if (remote->pushurl_nr) {
+ url = remote->pushurl;
+ url_nr = remote->pushurl_nr;
+ } else {
+ url = remote->url;
+ url_nr = remote->url_nr;
+ }
+ for (i = 0; i < url_nr; i++)
+ {
+ strbuf_addf(&url_buf, "%s (push)", url[i]);
+ string_list_append(list, remote->name)->util =
+ strbuf_detach(&url_buf, NULL);
+ }
+
+ return 0;
+}
+
+static int show_all(void)
+{
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ int result;
+
+ list.strdup_strings = 1;
+ result = for_each_remote(get_one_entry, &list);
+
+ if (!result) {
+ int i;
+
+ sort_string_list(&list);
+ for (i = 0; i < list.nr; i++) {
+ struct string_list_item *item = list.items + i;
+ if (verbose)
+ printf("%s\t%s\n", item->string,
+ item->util ? (const char *)item->util : "");
+ else {
+ if (i && !strcmp((item - 1)->string, item->string))
+ continue;
+ printf("%s\n", item->string);
+ }
+ }
+ }
+ string_list_clear(&list, 1);
+ return result;
+}
+
static int show(int argc, const char **argv)
{
int no_query = 0, result = 0, query_flag = 0;
struct option options[] = {
- OPT_BOOLEAN('n', NULL, &no_query, N_("do not query remotes")),
+ OPT_BOOL('n', NULL, &no_query, N_("do not query remotes")),
OPT_END()
};
struct ref_states states;
@@ -1195,10 +1248,10 @@ static int set_head(int argc, const char **argv)
char *head_name = NULL;
struct option options[] = {
- OPT_BOOLEAN('a', "auto", &opt_a,
- N_("set refs/remotes/<name>/HEAD according to remote")),
- OPT_BOOLEAN('d', "delete", &opt_d,
- N_("delete refs/remotes/<name>/HEAD")),
+ OPT_BOOL('a', "auto", &opt_a,
+ N_("set refs/remotes/<name>/HEAD according to remote")),
+ OPT_BOOL('d', "delete", &opt_d,
+ N_("delete refs/remotes/<name>/HEAD")),
OPT_END()
};
argc = parse_options(argc, argv, NULL, options, builtin_remote_sethead_usage,
@@ -1246,26 +1299,6 @@ static int set_head(int argc, const char **argv)
return result;
}
-static int prune(int argc, const char **argv)
-{
- int dry_run = 0, result = 0;
- struct option options[] = {
- OPT__DRY_RUN(&dry_run, N_("dry run")),
- OPT_END()
- };
-
- argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
- 0);
-
- if (argc < 1)
- usage_with_options(builtin_remote_prune_usage, options);
-
- for (; argc; argc--, argv++)
- result |= prune_remote(*argv, dry_run);
-
- return result;
-}
-
static int prune_remote(const char *remote, int dry_run)
{
int result = 0, i;
@@ -1304,6 +1337,26 @@ static int prune_remote(const char *remote, int dry_run)
return result;
}
+static int prune(int argc, const char **argv)
+{
+ int dry_run = 0, result = 0;
+ struct option options[] = {
+ OPT__DRY_RUN(&dry_run, N_("dry run")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_prune_usage,
+ 0);
+
+ if (argc < 1)
+ usage_with_options(builtin_remote_prune_usage, options);
+
+ for (; argc; argc--, argv++)
+ result |= prune_remote(*argv, dry_run);
+
+ return result;
+}
+
static int get_remote_default(const char *key, const char *value, void *priv)
{
if (strcmp(key, "remotes.default") == 0) {
@@ -1315,42 +1368,42 @@ static int get_remote_default(const char *key, const char *value, void *priv)
static int update(int argc, const char **argv)
{
- int i, prune = 0;
+ int i, prune = -1;
struct option options[] = {
- OPT_BOOLEAN('p', "prune", &prune,
- N_("prune remotes after fetching")),
+ OPT_BOOL('p', "prune", &prune,
+ N_("prune remotes after fetching")),
OPT_END()
};
- const char **fetch_argv;
- int fetch_argc = 0;
+ struct argv_array fetch_argv = ARGV_ARRAY_INIT;
int default_defined = 0;
-
- fetch_argv = xmalloc(sizeof(char *) * (argc+5));
+ int retval;
argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
PARSE_OPT_KEEP_ARGV0);
- fetch_argv[fetch_argc++] = "fetch";
+ argv_array_push(&fetch_argv, "fetch");
- if (prune)
- fetch_argv[fetch_argc++] = "--prune";
+ if (prune != -1)
+ argv_array_push(&fetch_argv, prune ? "--prune" : "--no-prune");
if (verbose)
- fetch_argv[fetch_argc++] = "-v";
- fetch_argv[fetch_argc++] = "--multiple";
+ argv_array_push(&fetch_argv, "-v");
+ argv_array_push(&fetch_argv, "--multiple");
if (argc < 2)
- fetch_argv[fetch_argc++] = "default";
+ argv_array_push(&fetch_argv, "default");
for (i = 1; i < argc; i++)
- fetch_argv[fetch_argc++] = argv[i];
+ argv_array_push(&fetch_argv, argv[i]);
- if (strcmp(fetch_argv[fetch_argc-1], "default") == 0) {
+ if (strcmp(fetch_argv.argv[fetch_argv.argc-1], "default") == 0) {
git_config(get_remote_default, &default_defined);
- if (!default_defined)
- fetch_argv[fetch_argc-1] = "--all";
+ if (!default_defined) {
+ argv_array_pop(&fetch_argv);
+ argv_array_push(&fetch_argv, "--all");
+ }
}
- fetch_argv[fetch_argc] = NULL;
-
- return run_command_v_opt(fetch_argv, RUN_GIT_CMD);
+ retval = run_command_v_opt(fetch_argv.argv, RUN_GIT_CMD);
+ argv_array_clear(&fetch_argv);
+ return retval;
}
static int remove_all_fetch_refspecs(const char *remote, const char *key)
@@ -1404,7 +1457,7 @@ static int set_branches(int argc, const char **argv)
{
int add_mode = 0;
struct option options[] = {
- OPT_BOOLEAN('\0', "add", &add_mode, N_("add branch")),
+ OPT_BOOL('\0', "add", &add_mode, N_("add branch")),
OPT_END()
};
@@ -1432,11 +1485,11 @@ static int set_url(int argc, const char **argv)
int urlset_nr;
struct strbuf name_buf = STRBUF_INIT;
struct option options[] = {
- OPT_BOOLEAN('\0', "push", &push_mode,
- N_("manipulate push URLs")),
- OPT_BOOLEAN('\0', "add", &add_mode,
- N_("add URL")),
- OPT_BOOLEAN('\0', "delete", &delete_mode,
+ OPT_BOOL('\0', "push", &push_mode,
+ N_("manipulate push URLs")),
+ OPT_BOOL('\0', "add", &add_mode,
+ N_("add URL")),
+ OPT_BOOL('\0', "delete", &delete_mode,
N_("delete URLs")),
OPT_END()
};
@@ -1505,64 +1558,6 @@ static int set_url(int argc, const char **argv)
return 0;
}
-static int get_one_entry(struct remote *remote, void *priv)
-{
- struct string_list *list = priv;
- struct strbuf url_buf = STRBUF_INIT;
- const char **url;
- int i, url_nr;
-
- if (remote->url_nr > 0) {
- strbuf_addf(&url_buf, "%s (fetch)", remote->url[0]);
- string_list_append(list, remote->name)->util =
- strbuf_detach(&url_buf, NULL);
- } else
- string_list_append(list, remote->name)->util = NULL;
- if (remote->pushurl_nr) {
- url = remote->pushurl;
- url_nr = remote->pushurl_nr;
- } else {
- url = remote->url;
- url_nr = remote->url_nr;
- }
- for (i = 0; i < url_nr; i++)
- {
- strbuf_addf(&url_buf, "%s (push)", url[i]);
- string_list_append(list, remote->name)->util =
- strbuf_detach(&url_buf, NULL);
- }
-
- return 0;
-}
-
-static int show_all(void)
-{
- struct string_list list = STRING_LIST_INIT_NODUP;
- int result;
-
- list.strdup_strings = 1;
- result = for_each_remote(get_one_entry, &list);
-
- if (!result) {
- int i;
-
- sort_string_list(&list);
- for (i = 0; i < list.nr; i++) {
- struct string_list_item *item = list.items + i;
- if (verbose)
- printf("%s\t%s\n", item->string,
- item->util ? (const char *)item->util : "");
- else {
- if (i && !strcmp((item - 1)->string, item->string))
- continue;
- printf("%s\n", item->string);
- }
- }
- }
- string_list_clear(&list, 1);
- return result;
-}
-
int cmd_remote(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
diff --git a/builtin/repack.c b/builtin/repack.c
new file mode 100644
index 0000000000..49f5857627
--- /dev/null
+++ b/builtin/repack.c
@@ -0,0 +1,403 @@
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "sigchain.h"
+#include "strbuf.h"
+#include "string-list.h"
+#include "argv-array.h"
+
+static int delta_base_offset = 1;
+static char *packdir, *packtmp;
+
+static const char *const git_repack_usage[] = {
+ N_("git repack [options]"),
+ NULL
+};
+
+static int repack_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "repack.usedeltabaseoffset")) {
+ delta_base_offset = git_config_bool(var, value);
+ return 0;
+ }
+ return git_default_config(var, value, cb);
+}
+
+/*
+ * Remove temporary $GIT_OBJECT_DIRECTORY/pack/.tmp-$$-pack-* files.
+ */
+static void remove_temporary_files(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ size_t dirlen, prefixlen;
+ DIR *dir;
+ struct dirent *e;
+
+ dir = opendir(packdir);
+ if (!dir)
+ return;
+
+ /* Point at the slash at the end of ".../objects/pack/" */
+ dirlen = strlen(packdir) + 1;
+ strbuf_addstr(&buf, packtmp);
+ /* Hold the length of ".tmp-%d-pack-" */
+ prefixlen = buf.len - dirlen;
+
+ while ((e = readdir(dir))) {
+ if (strncmp(e->d_name, buf.buf + dirlen, prefixlen))
+ continue;
+ strbuf_setlen(&buf, dirlen);
+ strbuf_addstr(&buf, e->d_name);
+ unlink(buf.buf);
+ }
+ closedir(dir);
+ strbuf_release(&buf);
+}
+
+static void remove_pack_on_signal(int signo)
+{
+ remove_temporary_files();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+/*
+ * Adds all packs hex strings to the fname list, which do not
+ * have a corresponding .keep file.
+ */
+static void get_non_kept_pack_filenames(struct string_list *fname_list)
+{
+ DIR *dir;
+ struct dirent *e;
+ char *fname;
+ size_t len;
+
+ if (!(dir = opendir(packdir)))
+ return;
+
+ while ((e = readdir(dir)) != NULL) {
+ if (!ends_with(e->d_name, ".pack"))
+ continue;
+
+ len = strlen(e->d_name) - strlen(".pack");
+ fname = xmemdupz(e->d_name, len);
+
+ if (!file_exists(mkpath("%s/%s.keep", packdir, fname)))
+ string_list_append_nodup(fname_list, fname);
+ else
+ free(fname);
+ }
+ closedir(dir);
+}
+
+static void remove_redundant_pack(const char *dir_name, const char *base_name)
+{
+ const char *exts[] = {".pack", ".idx", ".keep", ".bitmap"};
+ 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);
+ }
+ strbuf_release(&buf);
+}
+
+#define ALL_INTO_ONE 1
+#define LOOSEN_UNREACHABLE 2
+
+int cmd_repack(int argc, const char **argv, const char *prefix)
+{
+ struct {
+ const char *name;
+ unsigned optional:1;
+ } exts[] = {
+ {".pack"},
+ {".idx"},
+ {".bitmap", 1},
+ };
+ struct child_process cmd;
+ struct string_list_item *item;
+ struct argv_array cmd_args = ARGV_ARRAY_INIT;
+ struct string_list names = STRING_LIST_INIT_DUP;
+ struct string_list rollback = STRING_LIST_INIT_NODUP;
+ struct string_list existing_packs = STRING_LIST_INIT_DUP;
+ struct strbuf line = STRBUF_INIT;
+ int ext, ret, failed;
+ FILE *out;
+
+ /* variables to be filled by option parsing */
+ int pack_everything = 0;
+ int delete_redundant = 0;
+ const char *unpack_unreachable = NULL;
+ const char *window = NULL, *window_memory = NULL;
+ const char *depth = NULL;
+ const char *max_pack_size = NULL;
+ int no_reuse_delta = 0, no_reuse_object = 0;
+ int no_update_server_info = 0;
+ int quiet = 0;
+ int local = 0;
+ int write_bitmap = -1;
+
+ struct option builtin_repack_options[] = {
+ OPT_BIT('a', NULL, &pack_everything,
+ N_("pack everything in a single pack"), ALL_INTO_ONE),
+ OPT_BIT('A', NULL, &pack_everything,
+ N_("same as -a, and turn unreachable objects loose"),
+ LOOSEN_UNREACHABLE | ALL_INTO_ONE),
+ OPT_BOOL('d', NULL, &delete_redundant,
+ N_("remove redundant packs, and run git-prune-packed")),
+ OPT_BOOL('f', NULL, &no_reuse_delta,
+ N_("pass --no-reuse-delta to git-pack-objects")),
+ OPT_BOOL('F', NULL, &no_reuse_object,
+ N_("pass --no-reuse-object to git-pack-objects")),
+ OPT_BOOL('n', NULL, &no_update_server_info,
+ N_("do not run git-update-server-info")),
+ OPT__QUIET(&quiet, N_("be quiet")),
+ OPT_BOOL('l', "local", &local,
+ N_("pass --local to git-pack-objects")),
+ OPT_BOOL('b', "write-bitmap-index", &write_bitmap,
+ N_("write bitmap index")),
+ OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"),
+ N_("with -A, do not loosen objects older than this")),
+ OPT_STRING(0, "window", &window, N_("n"),
+ N_("size of the window used for delta compression")),
+ OPT_STRING(0, "window-memory", &window_memory, N_("bytes"),
+ N_("same as the above, but limit memory size instead of entries count")),
+ OPT_STRING(0, "depth", &depth, N_("n"),
+ N_("limits the maximum delta depth")),
+ OPT_STRING(0, "max-pack-size", &max_pack_size, N_("bytes"),
+ N_("maximum size of each packfile")),
+ OPT_END()
+ };
+
+ git_config(repack_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, builtin_repack_options,
+ git_repack_usage, 0);
+
+ packdir = mkpathdup("%s/pack", get_object_directory());
+ packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid());
+
+ sigchain_push_common(remove_pack_on_signal);
+
+ argv_array_push(&cmd_args, "pack-objects");
+ argv_array_push(&cmd_args, "--keep-true-parents");
+ argv_array_push(&cmd_args, "--honor-pack-keep");
+ argv_array_push(&cmd_args, "--non-empty");
+ argv_array_push(&cmd_args, "--all");
+ argv_array_push(&cmd_args, "--reflog");
+ if (window)
+ argv_array_pushf(&cmd_args, "--window=%s", window);
+ if (window_memory)
+ argv_array_pushf(&cmd_args, "--window-memory=%s", window_memory);
+ if (depth)
+ argv_array_pushf(&cmd_args, "--depth=%s", depth);
+ if (max_pack_size)
+ argv_array_pushf(&cmd_args, "--max-pack-size=%s", max_pack_size);
+ if (no_reuse_delta)
+ argv_array_pushf(&cmd_args, "--no-reuse-delta");
+ if (no_reuse_object)
+ argv_array_pushf(&cmd_args, "--no-reuse-object");
+ if (write_bitmap >= 0)
+ argv_array_pushf(&cmd_args, "--%swrite-bitmap-index",
+ write_bitmap ? "" : "no-");
+
+ if (pack_everything & ALL_INTO_ONE) {
+ get_non_kept_pack_filenames(&existing_packs);
+
+ if (existing_packs.nr && delete_redundant) {
+ if (unpack_unreachable)
+ argv_array_pushf(&cmd_args,
+ "--unpack-unreachable=%s",
+ unpack_unreachable);
+ else if (pack_everything & LOOSEN_UNREACHABLE)
+ argv_array_push(&cmd_args,
+ "--unpack-unreachable");
+ }
+ } else {
+ argv_array_push(&cmd_args, "--unpacked");
+ argv_array_push(&cmd_args, "--incremental");
+ }
+
+ if (local)
+ argv_array_push(&cmd_args, "--local");
+ if (quiet)
+ argv_array_push(&cmd_args, "--quiet");
+ if (delta_base_offset)
+ argv_array_push(&cmd_args, "--delta-base-offset");
+
+ argv_array_push(&cmd_args, packtmp);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.argv = cmd_args.argv;
+ cmd.git_cmd = 1;
+ cmd.out = -1;
+ cmd.no_stdin = 1;
+
+ ret = start_command(&cmd);
+ if (ret)
+ return ret;
+
+ out = xfdopen(cmd.out, "r");
+ while (strbuf_getline(&line, out, '\n') != EOF) {
+ if (line.len != 40)
+ die("repack: Expecting 40 character sha1 lines only from pack-objects.");
+ string_list_append(&names, line.buf);
+ }
+ fclose(out);
+ ret = finish_command(&cmd);
+ if (ret)
+ return ret;
+ argv_array_clear(&cmd_args);
+
+ if (!names.nr && !quiet)
+ printf("Nothing new to pack.\n");
+
+ /*
+ * 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;
+ fname = mkpathdup("%s/pack-%s%s", packdir,
+ item->string, exts[ext].name);
+ if (!file_exists(fname)) {
+ free(fname);
+ continue;
+ }
+
+ fname_old = mkpath("%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);
+ failed = 1;
+ break;
+ } else {
+ string_list_append(&rollback, fname);
+ }
+ }
+ 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 = mkpath("%s/old-%s", packdir, item->string);
+ if (rename(fname_old, fname))
+ string_list_append(&rollback_failure, fname);
+ free(fname);
+ }
+
+ 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 (rename(fname_old, fname))
+ die_errno(_("renaming '%s' failed"), fname_old);
+ }
+ free(fname);
+ free(fname_old);
+ }
+ }
+
+ /* Remove the "old-" files */
+ for_each_string_list_item(item, &names) {
+ for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
+ char *fname;
+ fname = mkpath("%s/old-%s%s",
+ packdir,
+ item->string,
+ exts[ext].name);
+ if (remove_path(fname))
+ warning(_("removing '%s' failed"), fname);
+ }
+ }
+
+ /* End of pack replacement. */
+
+ if (delete_redundant) {
+ sort_string_list(&names);
+ for_each_string_list_item(item, &existing_packs) {
+ char *sha1;
+ size_t len = strlen(item->string);
+ if (len < 40)
+ continue;
+ sha1 = item->string + len - 40;
+ if (!string_list_has_string(&names, sha1))
+ remove_redundant_pack(packdir, item->string);
+ }
+ argv_array_push(&cmd_args, "prune-packed");
+ if (quiet)
+ argv_array_push(&cmd_args, "--quiet");
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.argv = cmd_args.argv;
+ cmd.git_cmd = 1;
+ run_command(&cmd);
+ argv_array_clear(&cmd_args);
+ }
+
+ if (!no_update_server_info) {
+ argv_array_push(&cmd_args, "update-server-info");
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.argv = cmd_args.argv;
+ cmd.git_cmd = 1;
+ run_command(&cmd);
+ argv_array_clear(&cmd_args);
+ }
+ remove_temporary_files();
+ string_list_clear(&names, 0);
+ string_list_clear(&rollback, 0);
+ string_list_clear(&existing_packs, 0);
+ strbuf_release(&line);
+
+ return 0;
+}
diff --git a/builtin/replace.c b/builtin/replace.c
index 398ccd5eaa..2336325ce3 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
*
- * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com>
+ * Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com>
* and Carlos Rica <jasampler@gmail.com> that was itself based on
* git-tag.sh and mktag.c by Linus Torvalds.
*/
@@ -16,27 +16,69 @@
static const char * const git_replace_usage[] = {
N_("git replace [-f] <object> <replacement>"),
N_("git replace -d <object>..."),
- N_("git replace -l [<pattern>]"),
+ N_("git replace [--format=<format>] [-l [<pattern>]]"),
NULL
};
+enum replace_format {
+ REPLACE_FORMAT_SHORT,
+ REPLACE_FORMAT_MEDIUM,
+ REPLACE_FORMAT_LONG
+};
+
+struct show_data {
+ const char *pattern;
+ enum replace_format format;
+};
+
static int show_reference(const char *refname, const unsigned char *sha1,
int flag, void *cb_data)
{
- const char *pattern = cb_data;
+ struct show_data *data = cb_data;
- if (!fnmatch(pattern, refname, 0))
- printf("%s\n", refname);
+ if (!fnmatch(data->pattern, refname, 0)) {
+ if (data->format == REPLACE_FORMAT_SHORT)
+ printf("%s\n", refname);
+ else if (data->format == REPLACE_FORMAT_MEDIUM)
+ printf("%s -> %s\n", refname, sha1_to_hex(sha1));
+ else { /* data->format == REPLACE_FORMAT_LONG */
+ unsigned char object[20];
+ enum object_type obj_type, repl_type;
+
+ if (get_sha1(refname, object))
+ return error("Failed to resolve '%s' as a valid ref.", refname);
+
+ obj_type = sha1_object_info(object, NULL);
+ repl_type = sha1_object_info(sha1, NULL);
+
+ printf("%s (%s) -> %s (%s)\n", refname, typename(obj_type),
+ sha1_to_hex(sha1), typename(repl_type));
+ }
+ }
return 0;
}
-static int list_replace_refs(const char *pattern)
+static int list_replace_refs(const char *pattern, const char *format)
{
+ struct show_data data;
+
if (pattern == NULL)
pattern = "*";
+ data.pattern = pattern;
- for_each_replace_ref(show_reference, (void *) pattern);
+ if (format == NULL || *format == '\0' || !strcmp(format, "short"))
+ data.format = REPLACE_FORMAT_SHORT;
+ else if (!strcmp(format, "medium"))
+ data.format = REPLACE_FORMAT_MEDIUM;
+ else if (!strcmp(format, "long"))
+ data.format = REPLACE_FORMAT_LONG;
+ else
+ die("invalid replace format '%s'\n"
+ "valid formats are 'short', 'medium' and 'long'\n",
+ format);
+
+ for_each_replace_ref(show_reference, (void *) &data);
return 0;
}
@@ -85,6 +127,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,
int force)
{
unsigned char object[20], prev[20], repl[20];
+ enum object_type obj_type, repl_type;
char ref[PATH_MAX];
struct ref_lock *lock;
@@ -100,12 +143,21 @@ static int replace_object(const char *object_ref, const char *replace_ref,
if (check_refname_format(ref, 0))
die("'%s' is not a valid ref name.", ref);
+ obj_type = sha1_object_info(object, NULL);
+ repl_type = sha1_object_info(repl, NULL);
+ if (!force && obj_type != repl_type)
+ die("Objects must be of the same type.\n"
+ "'%s' points to a replaced object of type '%s'\n"
+ "while '%s' points to a replacement object of type '%s'.",
+ object_ref, typename(obj_type),
+ replace_ref, typename(repl_type));
+
if (read_ref(ref, prev))
hashclr(prev);
else if (!force)
die("replace ref '%s' already exists", ref);
- lock = lock_any_ref_for_update(ref, prev, 0);
+ lock = lock_any_ref_for_update(ref, prev, 0, NULL);
if (!lock)
die("%s: cannot lock the ref", ref);
if (write_ref_sha1(lock, repl, NULL) < 0)
@@ -117,19 +169,27 @@ static int replace_object(const char *object_ref, const char *replace_ref,
int cmd_replace(int argc, const char **argv, const char *prefix)
{
int list = 0, delete = 0, force = 0;
+ const char *format = NULL;
struct option options[] = {
- OPT_BOOLEAN('l', NULL, &list, N_("list replace refs")),
- OPT_BOOLEAN('d', NULL, &delete, N_("delete replace refs")),
- OPT_BOOLEAN('f', NULL, &force, N_("replace the ref if it exists")),
+ OPT_BOOL('l', "list", &list, N_("list replace refs")),
+ OPT_BOOL('d', "delete", &delete, N_("delete replace refs")),
+ OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")),
+ OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
OPT_END()
};
+ read_replace_refs = 0;
+
argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
if (list && delete)
usage_msg_opt("-l and -d cannot be used together",
git_replace_usage, options);
+ if (format && delete)
+ usage_msg_opt("--format and -d cannot be used together",
+ git_replace_usage, options);
+
if (force && (list || delete))
usage_msg_opt("-f cannot be used with -d or -l",
git_replace_usage, options);
@@ -147,6 +207,9 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
if (argc != 2)
usage_msg_opt("bad number of arguments",
git_replace_usage, options);
+ if (format)
+ usage_msg_opt("--format cannot be used when not listing",
+ git_replace_usage, options);
return replace_object(argv[0], argv[1], force);
}
@@ -158,5 +221,5 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
usage_msg_opt("-f needs some arguments",
git_replace_usage, options);
- return list_replace_refs(argv[0]);
+ return list_replace_refs(argv[0], format);
}
diff --git a/builtin/rerere.c b/builtin/rerere.c
index dc1708e6d6..4e51addb3e 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -6,6 +6,7 @@
#include "rerere.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
+#include "pathspec.h"
static const char * const rerere_usage[] = {
N_("git rerere [clear | forget path... | status | remaining | diff | gc]"),
@@ -68,11 +69,12 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
return rerere(flags);
if (!strcmp(argv[0], "forget")) {
- const char **pathspec;
+ struct pathspec pathspec;
if (argc < 2)
warning("'git rerere forget' without paths is deprecated");
- pathspec = get_pathspec(prefix, argv + 1);
- return rerere_forget(pathspec);
+ parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
+ prefix, argv + 1);
+ return rerere_forget(&pathspec);
}
fd = setup_rerere(&merge_rr, flags);
diff --git a/builtin/reset.c b/builtin/reset.c
index 6032131a90..4fd1c6c51d 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -93,10 +93,12 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
static void print_new_head_line(struct commit *commit)
{
const char *hex, *body;
+ char *msg;
hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
printf(_("HEAD is now at %s"), hex);
- body = strstr(commit->buffer, "\n\n");
+ msg = logmsg_reencode(commit, NULL, get_log_output_encoding());
+ body = strstr(msg, "\n\n");
if (body) {
const char *eol;
size_t len;
@@ -107,44 +109,55 @@ static void print_new_head_line(struct commit *commit)
}
else
printf("\n");
+ logmsg_free(msg, commit);
}
static void update_index_from_diff(struct diff_queue_struct *q,
struct diff_options *opt, void *data)
{
int i;
+ int intent_to_add = *(int *)data;
for (i = 0; i < q->nr; i++) {
struct diff_filespec *one = q->queue[i]->one;
- if (one->mode && !is_null_sha1(one->sha1)) {
- struct cache_entry *ce;
- ce = make_cache_entry(one->mode, one->sha1, one->path,
- 0, 0);
- if (!ce)
- die(_("make_cache_entry failed for path '%s'"),
- one->path);
- add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
- ADD_CACHE_OK_TO_REPLACE);
- } else
+ int is_missing = !(one->mode && !is_null_sha1(one->sha1));
+ struct cache_entry *ce;
+
+ if (is_missing && !intent_to_add) {
remove_file_from_cache(one->path);
+ continue;
+ }
+
+ ce = make_cache_entry(one->mode, one->sha1, one->path,
+ 0, 0);
+ if (!ce)
+ die(_("make_cache_entry failed for path '%s'"),
+ one->path);
+ if (is_missing) {
+ ce->ce_flags |= CE_INTENT_TO_ADD;
+ set_object_name_for_intent_to_add_entry(ce);
+ }
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
}
}
-static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
+static int read_from_tree(const struct pathspec *pathspec,
+ unsigned char *tree_sha1,
+ int intent_to_add)
{
struct diff_options opt;
memset(&opt, 0, sizeof(opt));
- diff_tree_setup_paths(pathspec, &opt);
+ copy_pathspec(&opt.pathspec, pathspec);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.format_callback = update_index_from_diff;
+ opt.format_callback_data = &intent_to_add;
- read_cache();
if (do_diff_cache(tree_sha1, &opt))
return 1;
diffcore_std(&opt);
diff_flush(&opt);
- diff_tree_release_paths(&opt);
+ free_pathspec(&opt.pathspec);
return 0;
}
@@ -165,13 +178,16 @@ static void set_reflog_message(struct strbuf *sb, const char *action,
static void die_if_unmerged_cache(int reset_type)
{
- if (is_merge() || read_cache() < 0 || unmerged_cache())
+ if (is_merge() || unmerged_cache())
die(_("Cannot do a %s reset in the middle of a merge."),
_(reset_type_names[reset_type]));
}
-static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret)
+static void parse_args(struct pathspec *pathspec,
+ const char **argv, const char *prefix,
+ int patch_mode,
+ const char **rev_ret)
{
const char *rev = "HEAD";
unsigned char unused[20];
@@ -213,10 +229,18 @@ static const char **parse_args(const char **argv, const char *prefix, const char
}
}
*rev_ret = rev;
- return argv[0] ? get_pathspec(prefix, argv) : NULL;
+
+ if (read_cache() < 0)
+ die(_("index file corrupt"));
+
+ parse_pathspec(pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP |
+ (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0),
+ prefix, argv);
}
-static int update_refs(const char *rev, const unsigned char *sha1)
+static int reset_refs(const char *rev, const unsigned char *sha1)
{
int update_ref_status;
struct strbuf msg = STRBUF_INIT;
@@ -243,7 +267,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
int patch_mode = 0, unborn;
const char *rev;
unsigned char sha1[20];
- const char **pathspec = NULL;
+ struct pathspec pathspec;
+ int intent_to_add = 0;
const struct option options[] = {
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
OPT_SET_INT(0, "mixed", &reset_type,
@@ -255,7 +280,9 @@ 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),
- OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")),
+ 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_END()
};
@@ -263,13 +290,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, git_reset_usage,
PARSE_OPT_KEEP_DASHDASH);
- pathspec = parse_args(argv, prefix, &rev);
+ parse_args(&pathspec, argv, prefix, patch_mode, &rev);
unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
- } else if (!pathspec) {
+ } else if (!pathspec.nr) {
struct commit *commit;
if (get_sha1_committish(rev, sha1))
die(_("Failed to resolve '%s' as a valid revision."), rev);
@@ -290,13 +317,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (patch_mode) {
if (reset_type != NONE)
die(_("--patch is incompatible with --{hard,mixed,soft}"));
- return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
+ return run_add_interactive(rev, "--patch=reset", &pathspec);
}
/* git reset tree [--] paths... can be used to
* load chosen paths from the tree into the index without
* affecting the working tree nor HEAD. */
- if (pathspec) {
+ if (pathspec.nr) {
if (reset_type == MIXED)
warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
else if (reset_type != NONE)
@@ -313,6 +340,9 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
die(_("%s reset is not allowed in a bare repository"),
_(reset_type_names[reset_type]));
+ if (intent_to_add && reset_type != MIXED)
+ die(_("-N can only be used with --mixed"));
+
/* Soft reset does not touch the index file nor the working tree
* at all, but requires them in a good order. Other resets reset
* the index file to the tree object we are switching to. */
@@ -320,11 +350,14 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
die_if_unmerged_cache(reset_type);
if (reset_type != SOFT) {
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ struct lock_file *lock = xcalloc(1, sizeof(*lock));
int newfd = hold_locked_index(lock, 1);
if (reset_type == MIXED) {
- if (read_from_tree(pathspec, sha1))
+ int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
+ if (read_from_tree(&pathspec, sha1, intent_to_add))
return 1;
+ refresh_index(&the_index, flags, NULL, NULL,
+ _("Unstaged changes after reset:"));
} else {
int err = reset_index(sha1, reset_type, quiet);
if (reset_type == KEEP && !err)
@@ -333,26 +366,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
die(_("Could not reset index file to revision '%s'."), rev);
}
- if (reset_type == MIXED) { /* Report what has not been updated. */
- int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
- refresh_index(&the_index, flags, NULL, NULL,
- _("Unstaged changes after reset:"));
- }
-
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock))
die(_("Could not write new index file."));
}
- if (!pathspec && !unborn) {
+ if (!pathspec.nr && !unborn) {
/* Any resets without paths update HEAD to the head being
* switched to, saving the previous head in ORIG_HEAD before. */
- update_ref_status = update_refs(rev, sha1);
+ update_ref_status = reset_refs(rev, sha1);
if (reset_type == HARD && !update_ref_status && !quiet)
print_new_head_line(lookup_commit_reference(sha1));
}
- if (!pathspec)
+ if (!pathspec.nr)
remove_branch_state();
return update_ref_status;
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 67701be551..9f92905379 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -3,6 +3,8 @@
#include "diff.h"
#include "revision.h"
#include "list-objects.h"
+#include "pack.h"
+#include "pack-bitmap.h"
#include "builtin.h"
#include "log-tree.h"
#include "graph.h"
@@ -111,6 +113,7 @@ static void show_commit(struct commit *commit, void *data)
ctx.date_mode = revs->date_mode;
ctx.date_mode_explicit = revs->date_mode_explicit;
ctx.fmt = revs->commit_format;
+ ctx.output_encoding = get_log_output_encoding();
pretty_print_commit(&ctx, commit, &buf);
if (revs->graph) {
if (buf.len) {
@@ -256,6 +259,18 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
return 0;
}
+static int show_object_fast(
+ const unsigned char *sha1,
+ enum object_type type,
+ int exclude,
+ uint32_t name_hash,
+ struct packed_git *found_pack,
+ off_t found_offset)
+{
+ fprintf(stdout, "%s\n", sha1_to_hex(sha1));
+ return 1;
+}
+
int cmd_rev_list(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -264,6 +279,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
int bisect_list = 0;
int bisect_show_vars = 0;
int bisect_find_all = 0;
+ int use_bitmap_index = 0;
git_config(git_default_config, NULL);
init_revisions(&revs, prefix);
@@ -305,6 +321,14 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
bisect_show_vars = 1;
continue;
}
+ if (!strcmp(arg, "--use-bitmap-index")) {
+ use_bitmap_index = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--test-bitmap")) {
+ test_bitmap_walk(&revs);
+ return 0;
+ }
usage(rev_list_usage);
}
@@ -321,7 +345,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
revs.commit_format = CMIT_FMT_RAW;
if ((!revs.commits &&
- (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
+ (!(revs.tag_objects || revs.tree_objects || revs.blob_objects) &&
!revs.pending.nr)) ||
revs.diff)
usage(rev_list_usage);
@@ -332,10 +356,26 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (bisect_list)
revs.limited = 1;
+ if (use_bitmap_index) {
+ if (revs.count && !revs.left_right && !revs.cherry_mark) {
+ uint32_t commit_count;
+ if (!prepare_bitmap_walk(&revs)) {
+ count_bitmap_commit_list(&commit_count, NULL, NULL, NULL);
+ printf("%d\n", commit_count);
+ return 0;
+ }
+ } else if (revs.tag_objects && revs.tree_objects && revs.blob_objects) {
+ if (!prepare_bitmap_walk(&revs)) {
+ traverse_bitmap_commit_list(&show_object_fast);
+ return 0;
+ }
+ }
+ }
+
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
if (revs.tree_objects)
- mark_edges_uninteresting(revs.commits, &revs, show_edge);
+ mark_edges_uninteresting(&revs, show_edge);
if (bisect_list) {
int reaches = reaches, all = all;
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index f267a1d3b5..45901df371 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -9,6 +9,8 @@
#include "quote.h"
#include "builtin.h"
#include "parse-options.h"
+#include "diff.h"
+#include "revision.h"
#define DO_REVS 1
#define DO_NOREV 2
@@ -30,6 +32,9 @@ static int abbrev_ref;
static int abbrev_ref_strict;
static int output_sq;
+static int stuck_long;
+static struct string_list *ref_excludes;
+
/*
* Some arguments are relevant "revision" arguments,
* others are about output format or other details.
@@ -185,6 +190,8 @@ static int show_default(void)
static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
+ if (ref_excluded(ref_excludes, refname))
+ return 0;
show_rev(NORMAL, sha1, refname);
return 0;
}
@@ -212,11 +219,17 @@ static void show_datestring(const char *flag, const char *datestr)
show(buffer);
}
-static int show_file(const char *arg)
+static int show_file(const char *arg, int output_prefix)
{
show_default();
if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
- show(arg);
+ if (output_prefix) {
+ const char *prefix = startup_info->prefix;
+ show(prefix_filename(prefix,
+ prefix ? strlen(prefix) : 0,
+ arg));
+ } else
+ show(arg);
return 1;
}
return 0;
@@ -273,6 +286,7 @@ static int try_difference(const char *arg)
exclude = n;
}
}
+ *dotdot = '.';
return 1;
}
*dotdot = '.';
@@ -296,8 +310,10 @@ static int try_parent_shorthands(const char *arg)
return 0;
*dotdot = 0;
- if (get_sha1_committish(arg, sha1))
+ if (get_sha1_committish(arg, sha1)) {
+ *dotdot = '^';
return 0;
+ }
if (!parents_only)
show_rev(NORMAL, sha1, arg);
@@ -306,6 +322,7 @@ static int try_parent_shorthands(const char *arg)
show_rev(parents_only ? NORMAL : REVERSED,
parents->item->object.sha1, arg);
+ *dotdot = '^';
return 1;
}
@@ -314,12 +331,15 @@ static int parseopt_dump(const struct option *o, const char *arg, int unset)
struct strbuf *parsed = o->value;
if (unset)
strbuf_addf(parsed, " --no-%s", o->long_name);
- else if (o->short_name)
+ else if (o->short_name && (o->long_name == NULL || !stuck_long))
strbuf_addf(parsed, " -%c", o->short_name);
else
strbuf_addf(parsed, " --%s", o->long_name);
if (arg) {
- strbuf_addch(parsed, ' ');
+ if (!stuck_long)
+ strbuf_addch(parsed, ' ');
+ else if (o->long_name)
+ strbuf_addch(parsed, '=');
sq_quote_buf(parsed, arg);
}
return 0;
@@ -340,11 +360,13 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
NULL
};
static struct option parseopt_opts[] = {
- OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
+ OPT_BOOL(0, "keep-dashdash", &keep_dashdash,
N_("keep the `--` passed as an arg")),
- OPT_BOOLEAN(0, "stop-at-non-option", &stop_at_non_option,
+ OPT_BOOL(0, "stop-at-non-option", &stop_at_non_option,
N_("stop parsing after the "
"first non-option argument")),
+ OPT_BOOL(0, "stuck-long", &stuck_long,
+ N_("output in stuck long form")),
OPT_END(),
};
@@ -470,6 +492,8 @@ N_("git rev-parse --parseopt [options] -- [<args>...]\n"
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;
+ int has_dashdash = 0;
+ int output_prefix = 0;
unsigned char sha1[20];
const char *name = NULL;
@@ -479,31 +503,23 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (argc > 1 && !strcmp("--sq-quote", argv[1]))
return cmd_sq_quote(argc - 2, argv + 2);
- if (argc == 2 && !strcmp("--local-env-vars", argv[1])) {
- int i;
- for (i = 0; local_repo_env[i]; i++)
- printf("%s\n", local_repo_env[i]);
- return 0;
- }
-
- if (argc > 2 && !strcmp(argv[1], "--resolve-git-dir")) {
- const char *gitdir = resolve_gitdir(argv[2]);
- if (!gitdir)
- die("not a gitdir '%s'", argv[2]);
- puts(gitdir);
- return 0;
- }
-
if (argc > 1 && !strcmp("-h", argv[1]))
usage(builtin_rev_parse_usage);
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ has_dashdash = 1;
+ break;
+ }
+ }
+
prefix = setup_git_directory();
git_config(git_default_config, NULL);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (as_is) {
- if (show_file(arg) && as_is < 2)
+ if (show_file(arg, output_prefix) && as_is < 2)
verify_filename(prefix, arg, 0);
continue;
}
@@ -516,7 +532,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
}
continue;
}
- if (!prefixcmp(arg, "-n")) {
+ if (starts_with(arg, "-n")) {
if ((filter & DO_FLAGS) && (filter & DO_REVS))
show(arg);
continue;
@@ -527,12 +543,21 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
as_is = 2;
/* Pass on the "--" if we show anything but files.. */
if (filter & (DO_FLAGS | DO_REVS))
- show_file(arg);
+ show_file(arg, 0);
continue;
}
if (!strcmp(arg, "--default")) {
- def = argv[i+1];
- i++;
+ def = argv[++i];
+ if (!def)
+ die("--default requires an argument");
+ continue;
+ }
+ if (!strcmp(arg, "--prefix")) {
+ prefix = argv[++i];
+ if (!prefix)
+ die("--prefix requires an argument");
+ startup_info->prefix = prefix;
+ output_prefix = 1;
continue;
}
if (!strcmp(arg, "--revs-only")) {
@@ -561,7 +586,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--short") ||
- !prefixcmp(arg, "--short=")) {
+ starts_with(arg, "--short=")) {
filter &= ~(DO_FLAGS|DO_NOREV);
verify = 1;
abbrev = DEFAULT_ABBREV;
@@ -589,7 +614,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
symbolic = SHOW_SYMBOLIC_FULL;
continue;
}
- if (!prefixcmp(arg, "--abbrev-ref") &&
+ if (starts_with(arg, "--abbrev-ref") &&
(!arg[12] || arg[12] == '=')) {
abbrev_ref = 1;
abbrev_ref_strict = warn_ambiguous_refs;
@@ -607,7 +632,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
for_each_ref(show_reference, NULL);
continue;
}
- if (!prefixcmp(arg, "--disambiguate=")) {
+ if (starts_with(arg, "--disambiguate=")) {
for_each_abbrev(arg + 15, show_abbrev, NULL);
continue;
}
@@ -616,35 +641,52 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
for_each_ref_in("refs/bisect/good", anti_reference, NULL);
continue;
}
- if (!prefixcmp(arg, "--branches=")) {
+ if (starts_with(arg, "--branches=")) {
for_each_glob_ref_in(show_reference, arg + 11,
"refs/heads/", NULL);
+ clear_ref_exclusion(&ref_excludes);
continue;
}
if (!strcmp(arg, "--branches")) {
for_each_branch_ref(show_reference, NULL);
+ clear_ref_exclusion(&ref_excludes);
continue;
}
- if (!prefixcmp(arg, "--tags=")) {
+ if (starts_with(arg, "--tags=")) {
for_each_glob_ref_in(show_reference, arg + 7,
"refs/tags/", NULL);
+ clear_ref_exclusion(&ref_excludes);
continue;
}
if (!strcmp(arg, "--tags")) {
for_each_tag_ref(show_reference, NULL);
+ clear_ref_exclusion(&ref_excludes);
continue;
}
- if (!prefixcmp(arg, "--glob=")) {
+ if (starts_with(arg, "--glob=")) {
for_each_glob_ref(show_reference, arg + 7, NULL);
+ clear_ref_exclusion(&ref_excludes);
continue;
}
- if (!prefixcmp(arg, "--remotes=")) {
+ if (starts_with(arg, "--remotes=")) {
for_each_glob_ref_in(show_reference, arg + 10,
"refs/remotes/", NULL);
+ clear_ref_exclusion(&ref_excludes);
continue;
}
if (!strcmp(arg, "--remotes")) {
for_each_remote_ref(show_reference, NULL);
+ clear_ref_exclusion(&ref_excludes);
+ continue;
+ }
+ if (starts_with(arg, "--exclude=")) {
+ add_ref_exclusion(&ref_excludes, arg + 10);
+ continue;
+ }
+ 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, "--show-toplevel")) {
@@ -697,6 +739,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
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 (!strcmp(arg, "--is-inside-git-dir")) {
printf("%s\n", is_inside_git_dir() ? "true"
: "false");
@@ -712,19 +764,19 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
: "false");
continue;
}
- if (!prefixcmp(arg, "--since=")) {
+ if (starts_with(arg, "--since=")) {
show_datestring("--max-age=", arg+8);
continue;
}
- if (!prefixcmp(arg, "--after=")) {
+ if (starts_with(arg, "--after=")) {
show_datestring("--max-age=", arg+8);
continue;
}
- if (!prefixcmp(arg, "--before=")) {
+ if (starts_with(arg, "--before=")) {
show_datestring("--min-age=", arg+9);
continue;
}
- if (!prefixcmp(arg, "--until=")) {
+ if (starts_with(arg, "--until=")) {
show_datestring("--min-age=", arg+8);
continue;
}
@@ -753,8 +805,10 @@ 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);
as_is = 1;
- if (!show_file(arg))
+ if (!show_file(arg, output_prefix))
continue;
verify_filename(prefix, arg, 1);
}
diff --git a/builtin/revert.c b/builtin/revert.c
index 0401fdb02c..065d88dd05 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -54,6 +54,7 @@ static int option_parse_x(const struct option *opt,
return 0;
}
+LAST_ARG_MUST_BE_NULL
static void verify_opt_compatible(const char *me, const char *base_opt, ...)
{
const char *this_opt;
@@ -70,48 +71,26 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...)
die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
}
-static void verify_opt_mutually_compatible(const char *me, ...)
-{
- const char *opt1, *opt2 = NULL;
- va_list ap;
-
- va_start(ap, me);
- while ((opt1 = va_arg(ap, const char *))) {
- if (va_arg(ap, int))
- break;
- }
- if (opt1) {
- while ((opt2 = va_arg(ap, const char *))) {
- if (va_arg(ap, int))
- break;
- }
- }
- va_end(ap);
-
- if (opt1 && opt2)
- die(_("%s: %s cannot be used with %s"), me, opt1, opt2);
-}
-
static void parse_args(int argc, const char **argv, struct replay_opts *opts)
{
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
const char *me = action_name(opts);
- int remove_state = 0;
- int contin = 0;
- int rollback = 0;
+ int cmd = 0;
struct option options[] = {
- OPT_BOOLEAN(0, "quit", &remove_state, N_("end revert or cherry-pick sequence")),
- OPT_BOOLEAN(0, "continue", &contin, N_("resume revert or cherry-pick sequence")),
- OPT_BOOLEAN(0, "abort", &rollback, N_("cancel revert or cherry-pick sequence")),
- OPT_BOOLEAN('n', "no-commit", &opts->no_commit, N_("don't automatically commit")),
- OPT_BOOLEAN('e', "edit", &opts->edit, N_("edit the commit message")),
+ 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_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_BOOLEAN('s', "signoff", &opts->signoff, N_("add Signed-off-by:")),
+ OPT_BOOL('s', "signoff", &opts->signoff, N_("add Signed-off-by:")),
OPT_INTEGER('m', "mainline", &opts->mainline, N_("parent number")),
OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
OPT_STRING(0, "strategy", &opts->strategy, N_("strategy"), N_("merge strategy")),
OPT_CALLBACK('X', "strategy-option", &opts, N_("option"),
N_("option for merge strategy"), option_parse_x),
+ { OPTION_STRING, 'S', "gpg-sign", &opts->gpg_sign, N_("key id"),
+ N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
OPT_END(),
OPT_END(),
OPT_END(),
@@ -122,11 +101,11 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
if (opts->action == REPLAY_PICK) {
struct option cp_extra[] = {
- OPT_BOOLEAN('x', NULL, &opts->record_origin, N_("append commit name")),
- OPT_BOOLEAN(0, "ff", &opts->allow_ff, N_("allow fast-forward")),
- OPT_BOOLEAN(0, "allow-empty", &opts->allow_empty, N_("preserve initially empty commits")),
- OPT_BOOLEAN(0, "allow-empty-message", &opts->allow_empty_message, N_("allow commits with empty messages")),
- OPT_BOOLEAN(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("keep redundant, empty commits")),
+ OPT_BOOL('x', NULL, &opts->record_origin, N_("append commit name")),
+ OPT_BOOL(0, "ff", &opts->allow_ff, N_("allow fast-forward")),
+ OPT_BOOL(0, "allow-empty", &opts->allow_empty, N_("preserve initially empty commits")),
+ OPT_BOOL(0, "allow-empty-message", &opts->allow_empty_message, N_("allow commits with empty messages")),
+ OPT_BOOL(0, "keep-redundant-commits", &opts->keep_redundant_commits, N_("keep redundant, empty commits")),
OPT_END(),
};
if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
@@ -137,23 +116,16 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
PARSE_OPT_KEEP_ARGV0 |
PARSE_OPT_KEEP_UNKNOWN);
- /* Check for incompatible subcommands */
- verify_opt_mutually_compatible(me,
- "--quit", remove_state,
- "--continue", contin,
- "--abort", rollback,
- NULL);
-
/* implies allow_empty */
if (opts->keep_redundant_commits)
opts->allow_empty = 1;
/* Set the subcommand */
- if (remove_state)
+ if (cmd == 'q')
opts->subcommand = REPLAY_REMOVE_STATE;
- else if (contin)
+ else if (cmd == 'c')
opts->subcommand = REPLAY_CONTINUE;
- else if (rollback)
+ else if (cmd == 'a')
opts->subcommand = REPLAY_ROLLBACK;
else
opts->subcommand = REPLAY_NONE;
@@ -198,6 +170,8 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
if (argc < 2)
usage_with_options(usage_str, options);
+ if (!strcmp(argv[1], "-"))
+ argv[1] = "@{-1}";
memset(&s_r_opt, 0, sizeof(s_r_opt));
s_r_opt.assume_dashdash = 1;
argc = setup_revisions(argc, argv, opts->revs, &s_r_opt);
diff --git a/builtin/rm.c b/builtin/rm.c
index 7b91d52f39..960634dd0c 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -9,7 +9,9 @@
#include "cache-tree.h"
#include "tree-walk.h"
#include "parse-options.h"
+#include "string-list.h"
#include "submodule.h"
+#include "pathspec.h"
static const char * const builtin_rm_usage[] = {
N_("git rm [options] [--] <file>..."),
@@ -36,15 +38,52 @@ static int get_ours_cache_pos(const char *path, int pos)
return -1;
}
+static void print_error_files(struct string_list *files_list,
+ const char *main_msg,
+ const char *hints_msg,
+ int *errs)
+{
+ if (files_list->nr) {
+ int i;
+ struct strbuf err_msg = STRBUF_INIT;
+
+ strbuf_addstr(&err_msg, main_msg);
+ for (i = 0; i < files_list->nr; i++)
+ strbuf_addf(&err_msg,
+ "\n %s",
+ files_list->items[i].string);
+ if (advice_rm_hints)
+ strbuf_addstr(&err_msg, hints_msg);
+ *errs = error("%s", err_msg.buf);
+ strbuf_release(&err_msg);
+ }
+}
+
+static void error_removing_concrete_submodules(struct string_list *files, int *errs)
+{
+ print_error_files(files,
+ Q_("the following submodule (or one of its nested "
+ "submodules)\n"
+ "uses a .git directory:",
+ "the following submodules (or one of its nested "
+ "submodules)\n"
+ "use a .git directory:", files->nr),
+ _("\n(use 'rm -rf' if you really want to remove "
+ "it including all of its history)"),
+ errs);
+ string_list_clear(files, 0);
+}
+
static int check_submodules_use_gitfiles(void)
{
int i;
int errs = 0;
+ struct string_list files = STRING_LIST_INIT_NODUP;
for (i = 0; i < list.nr; i++) {
const char *name = list.entry[i].name;
int pos;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
struct stat st;
pos = cache_name_pos(name, strlen(name));
@@ -61,12 +100,11 @@ static int check_submodules_use_gitfiles(void)
continue;
if (!submodule_uses_gitfile(name))
- errs = error(_("submodule '%s' (or one of its nested "
- "submodules) uses a .git directory\n"
- "(use 'rm -rf' if you really want to remove "
- "it including all of its history)"), name);
+ string_list_append(&files, name);
}
+ error_removing_concrete_submodules(&files, &errs);
+
return errs;
}
@@ -81,12 +119,16 @@ static int check_local_mod(unsigned char *head, int index_only)
*/
int i, no_head;
int errs = 0;
+ struct string_list files_staged = STRING_LIST_INIT_NODUP;
+ struct string_list files_cached = STRING_LIST_INIT_NODUP;
+ struct string_list files_submodule = STRING_LIST_INIT_NODUP;
+ struct string_list files_local = STRING_LIST_INIT_NODUP;
no_head = is_null_sha1(head);
for (i = 0; i < list.nr; i++) {
struct stat st;
int pos;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
const char *name = list.entry[i].name;
unsigned char sha1[20];
unsigned mode;
@@ -171,29 +213,50 @@ static int check_local_mod(unsigned char *head, int index_only)
*/
if (local_changes && staged_changes) {
if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
- errs = error(_("'%s' has staged content different "
- "from both the file and the HEAD\n"
- "(use -f to force removal)"), name);
+ string_list_append(&files_staged, name);
}
else if (!index_only) {
if (staged_changes)
- errs = error(_("'%s' has changes staged in the index\n"
- "(use --cached to keep the file, "
- "or -f to force removal)"), name);
+ string_list_append(&files_cached, name);
if (local_changes) {
if (S_ISGITLINK(ce->ce_mode) &&
- !submodule_uses_gitfile(name)) {
- errs = error(_("submodule '%s' (or one of its nested "
- "submodules) uses a .git directory\n"
- "(use 'rm -rf' if you really want to remove "
- "it including all of its history)"), name);
- } else
- errs = error(_("'%s' has local modifications\n"
- "(use --cached to keep the file, "
- "or -f to force removal)"), name);
+ !submodule_uses_gitfile(name))
+ string_list_append(&files_submodule, name);
+ else
+ string_list_append(&files_local, name);
}
}
}
+ print_error_files(&files_staged,
+ Q_("the following file has staged content different "
+ "from both the\nfile and the HEAD:",
+ "the following files have staged content different"
+ " from both the\nfile and the HEAD:",
+ files_staged.nr),
+ _("\n(use -f to force removal)"),
+ &errs);
+ string_list_clear(&files_staged, 0);
+ print_error_files(&files_cached,
+ Q_("the following file has changes "
+ "staged in the index:",
+ "the following files have changes "
+ "staged in the index:", files_cached.nr),
+ _("\n(use --cached to keep the file,"
+ " or -f to force removal)"),
+ &errs);
+ string_list_clear(&files_cached, 0);
+
+ error_removing_concrete_submodules(&files_submodule, &errs);
+
+ print_error_files(&files_local,
+ Q_("the following file has local modifications:",
+ "the following files have local modifications:",
+ files_local.nr),
+ _("\n(use --cached to keep the file,"
+ " or -f to force removal)"),
+ &errs);
+ string_list_clear(&files_local, 0);
+
return errs;
}
@@ -205,10 +268,10 @@ static int ignore_unmatch = 0;
static struct option builtin_rm_options[] = {
OPT__DRY_RUN(&show_only, N_("dry run")),
OPT__QUIET(&quiet, N_("do not list removed files")),
- OPT_BOOLEAN( 0 , "cached", &index_only, N_("only remove from the index")),
+ OPT_BOOL( 0 , "cached", &index_only, N_("only remove from the index")),
OPT__FORCE(&force, N_("override the up-to-date check")),
- OPT_BOOLEAN('r', NULL, &recursive, N_("allow recursive removal")),
- OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
+ 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_END(),
};
@@ -216,9 +279,10 @@ static struct option builtin_rm_options[] = {
int cmd_rm(int argc, const char **argv, const char *prefix)
{
int i, newfd;
- const char **pathspec;
+ struct pathspec pathspec;
char *seen;
+ gitmodules_config();
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_rm_options,
@@ -234,46 +298,35 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (read_cache() < 0)
die(_("index file corrupt"));
- /*
- * Drop trailing directory separators from directories so we'll find
- * submodules in the index.
- */
- for (i = 0; i < argc; i++) {
- size_t pathlen = strlen(argv[i]);
- if (pathlen && is_dir_sep(argv[i][pathlen - 1]) &&
- is_directory(argv[i])) {
- do {
- pathlen--;
- } while (pathlen && is_dir_sep(argv[i][pathlen - 1]));
- argv[i] = xmemdupz(argv[i], pathlen);
- }
- }
-
- pathspec = get_pathspec(prefix, argv);
- refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+ prefix, argv);
+ refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL);
- seen = NULL;
- for (i = 0; pathspec[i] ; i++)
- /* nothing */;
- seen = xcalloc(i, 1);
+ seen = xcalloc(pathspec.nr, 1);
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+ const struct cache_entry *ce = active_cache[i];
+ if (!ce_path_match(ce, &pathspec, seen))
continue;
ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
- list.entry[list.nr].name = ce->name;
- list.entry[list.nr++].is_submodule = S_ISGITLINK(ce->ce_mode);
+ list.entry[list.nr].name = xstrdup(ce->name);
+ list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode);
+ if (list.entry[list.nr++].is_submodule &&
+ !is_staging_gitmodules_ok())
+ die (_("Please, stage your changes to .gitmodules or stash them to proceed"));
}
- if (pathspec) {
- const char *match;
+ if (pathspec.nr) {
+ const char *original;
int seen_any = 0;
- for (i = 0; (match = pathspec[i]) != NULL ; i++) {
+ 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"),
- match);
+ original);
}
}
else {
@@ -281,10 +334,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
}
if (!recursive && seen[i] == MATCHED_RECURSIVELY)
die(_("not removing '%s' recursively without -r"),
- *match ? match : ".");
+ *original ? original : ".");
}
- if (! seen_any)
+ if (!seen_any)
exit(0);
}
@@ -334,13 +387,15 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
* in the middle)
*/
if (!index_only) {
- int removed = 0;
+ int removed = 0, gitmodules_modified = 0;
for (i = 0; i < list.nr; i++) {
const char *path = list.entry[i].name;
if (list.entry[i].is_submodule) {
if (is_empty_dir(path)) {
if (!rmdir(path)) {
removed = 1;
+ if (!remove_path_from_gitmodules(path))
+ gitmodules_modified = 1;
continue;
}
} else {
@@ -348,9 +403,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
strbuf_addstr(&buf, path);
if (!remove_dir_recursively(&buf, 0)) {
removed = 1;
+ if (!remove_path_from_gitmodules(path))
+ gitmodules_modified = 1;
strbuf_release(&buf);
continue;
- }
+ } else if (!file_exists(path))
+ /* Submodule was removed by user */
+ if (!remove_path_from_gitmodules(path))
+ gitmodules_modified = 1;
strbuf_release(&buf);
/* Fallthrough and let remove_path() fail. */
}
@@ -362,6 +422,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (!removed)
die_errno("git rm: '%s'", path);
}
+ if (gitmodules_modified)
+ stage_updated_gitmodules();
}
if (active_cache_changed) {
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 152c4ea092..f420b74665 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -5,10 +5,12 @@
#include "sideband.h"
#include "run-command.h"
#include "remote.h"
+#include "connect.h"
#include "send-pack.h"
#include "quote.h"
#include "transport.h"
#include "version.h"
+#include "sha1-array.h"
static const char send_pack_usage[] =
"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
@@ -54,6 +56,11 @@ static void print_helper_status(struct ref *ref)
msg = "needs force";
break;
+ case REF_STATUS_REJECT_STALE:
+ res = "error";
+ msg = "stale info";
+ break;
+
case REF_STATUS_REJECT_ALREADY_EXISTS:
res = "error";
msg = "already exists";
@@ -93,7 +100,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
const char *dest = NULL;
int fd[2];
struct child_process *conn;
- struct extra_have_objects extra_have;
+ struct sha1_array extra_have = SHA1_ARRAY_INIT;
+ struct sha1_array shallow = SHA1_ARRAY_INIT;
struct ref *remote_refs, *local_refs;
int ret;
int helper_status = 0;
@@ -102,21 +110,22 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
int flags;
unsigned int reject_reasons;
int progress = -1;
+ struct push_cas_option cas = {0};
argv++;
for (i = 1; i < argc; i++, argv++) {
const char *arg = *argv;
if (*arg == '-') {
- if (!prefixcmp(arg, "--receive-pack=")) {
+ if (starts_with(arg, "--receive-pack=")) {
receivepack = arg + 15;
continue;
}
- if (!prefixcmp(arg, "--exec=")) {
+ if (starts_with(arg, "--exec=")) {
receivepack = arg + 7;
continue;
}
- if (!prefixcmp(arg, "--remote=")) {
+ if (starts_with(arg, "--remote=")) {
remote_name = arg + 9;
continue;
}
@@ -164,6 +173,22 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
helper_status = 1;
continue;
}
+ if (!strcmp(arg, "--" CAS_OPT_NAME)) {
+ if (parse_push_cas_option(&cas, NULL, 0) < 0)
+ exit(1);
+ continue;
+ }
+ if (!strcmp(arg, "--no-" CAS_OPT_NAME)) {
+ if (parse_push_cas_option(&cas, NULL, 1) < 0)
+ exit(1);
+ continue;
+ }
+ if (starts_with(arg, "--" CAS_OPT_NAME "=")) {
+ if (parse_push_cas_option(&cas,
+ strchr(arg, '=') + 1, 0) < 0)
+ exit(1);
+ continue;
+ }
usage(send_pack_usage);
}
if (!dest) {
@@ -205,9 +230,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.verbose ? CONNECT_VERBOSE : 0);
}
- memset(&extra_have, 0, sizeof(extra_have));
-
- get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, &extra_have);
+ get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL,
+ &extra_have, &shallow);
transport_verify_remote_names(nr_refspecs, refspecs);
@@ -224,6 +248,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
return -1;
+ if (!is_empty_cas(&cas))
+ apply_push_cas(&cas, remote, remote_refs);
+
set_ref_status_for_push(remote_refs, args.send_mirror,
args.force_update);
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 1fd6f8ac59..4b7e53623f 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -65,7 +65,7 @@ static void insert_one_record(struct shortlog *log,
eol = strchr(oneline, '\n');
if (!eol)
eol = oneline + strlen(oneline);
- if (!prefixcmp(oneline, "[PATCH")) {
+ if (starts_with(oneline, "[PATCH")) {
char *eob = strchr(oneline, ']');
if (eob && (!eol || eob < eol))
oneline = eob + 1;
@@ -95,7 +95,7 @@ static void read_from_stdin(struct shortlog *log)
while (fgets(author, sizeof(author), stdin) != NULL) {
if (!(author[0] == 'A' || author[0] == 'a') ||
- prefixcmp(author + 1, "uthor: "))
+ !starts_with(author + 1, "uthor: "))
continue;
while (fgets(oneline, sizeof(oneline), stdin) &&
oneline[0] != '\n')
@@ -123,13 +123,15 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
else
eol++;
- if (!prefixcmp(buffer, "author "))
+ if (starts_with(buffer, "author "))
author = buffer + 7;
buffer = eol;
}
- if (!author)
- die(_("Missing author: %s"),
+ if (!author) {
+ warning(_("Missing author: %s"),
sha1_to_hex(commit->object.sha1));
+ return;
+ }
if (log->user_format) {
struct pretty_print_context ctx = {0};
ctx.fmt = CMIT_FMT_USERFORMAT;
@@ -137,6 +139,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
ctx.subject = "";
ctx.after_subject = "";
ctx.date_mode = DATE_NORMAL;
+ ctx.output_encoding = get_log_output_encoding();
pretty_print_commit(&ctx, commit, &ufbuf);
buffer = ufbuf.buf;
} else if (*buffer) {
@@ -223,12 +226,12 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
int nongit = !startup_info->have_repository;
static const struct option options[] = {
- OPT_BOOLEAN('n', "numbered", &log.sort_by_number,
- N_("sort output according to the number of commits per author")),
- OPT_BOOLEAN('s', "summary", &log.summary,
- N_("Suppress commit descriptions, only provides commit count")),
- OPT_BOOLEAN('e', "email", &log.email,
- N_("Show the email address of each author")),
+ OPT_BOOL('n', "numbered", &log.sort_by_number,
+ N_("sort output according to the number of commits per author")),
+ OPT_BOOL('s', "summary", &log.summary,
+ N_("Suppress commit descriptions, only provides commit count")),
+ OPT_BOOL('e', "email", &log.email,
+ N_("Show the email address of each author")),
{ OPTION_CALLBACK, 'w', NULL, &log, N_("w[,i1[,i2]]"),
N_("Linewrap output"), PARSE_OPT_OPTARG, &parse_wrap_args },
OPT_END(),
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 90fc6b1b9d..d9217ce1e1 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -227,8 +227,7 @@ static void join_revs(struct commit_list **list_p,
parents = parents->next;
if ((this_flag & flags) == flags)
continue;
- if (!p->object.parsed)
- parse_commit(p);
+ parse_commit(p);
if (mark_seen(p, seen_p) && !still_interesting)
extra--;
p->object.flags |= flags;
@@ -285,7 +284,7 @@ static void show_one_commit(struct commit *commit, int no_name)
pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
pretty_str = pretty.buf;
}
- if (!prefixcmp(pretty_str, "[PATCH] "))
+ if (starts_with(pretty_str, "[PATCH] "))
pretty_str += 8;
if (!no_name) {
@@ -396,7 +395,7 @@ static int append_head_ref(const char *refname, const unsigned char *sha1, int f
{
unsigned char tmp[20];
int ofs = 11;
- if (prefixcmp(refname, "refs/heads/"))
+ if (!starts_with(refname, "refs/heads/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
@@ -410,7 +409,7 @@ static int append_remote_ref(const char *refname, const unsigned char *sha1, int
{
unsigned char tmp[20];
int ofs = 13;
- if (prefixcmp(refname, "refs/remotes/"))
+ if (!starts_with(refname, "refs/remotes/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
@@ -422,7 +421,7 @@ static int append_remote_ref(const char *refname, const unsigned char *sha1, int
static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
- if (prefixcmp(refname, "refs/tags/"))
+ if (!starts_with(refname, "refs/tags/"))
return 0;
return append_ref(refname + 5, sha1, 0);
}
@@ -453,9 +452,9 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i
return 0;
if (fnmatch(match_ref_pattern, tail, 0))
return 0;
- if (!prefixcmp(refname, "refs/heads/"))
+ if (starts_with(refname, "refs/heads/"))
return append_head_ref(refname, sha1, flag, cb_data);
- if (!prefixcmp(refname, "refs/tags/"))
+ if (starts_with(refname, "refs/tags/"))
return append_tag_ref(refname, sha1, flag, cb_data);
return append_ref(refname, sha1, 0);
}
@@ -480,11 +479,11 @@ static int rev_is_head(char *head, int headlen, char *name,
if ((!head[0]) ||
(head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
return 0;
- if (!prefixcmp(head, "refs/heads/"))
+ if (starts_with(head, "refs/heads/"))
head += 11;
- if (!prefixcmp(name, "refs/heads/"))
+ if (starts_with(name, "refs/heads/"))
name += 11;
- else if (!prefixcmp(name, "heads/"))
+ else if (starts_with(name, "heads/"))
name += 6;
return !strcmp(head, name);
}
@@ -630,7 +629,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int num_rev, i, extra = 0;
int all_heads = 0, all_remotes = 0;
int all_mask, all_revs;
- int lifo = 1;
+ enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
char head[128];
const char *head_p;
int head_len;
@@ -646,34 +645,36 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int dense = 1;
const char *reflog_base = NULL;
struct option builtin_show_branch_options[] = {
- OPT_BOOLEAN('a', "all", &all_heads,
- N_("show remote-tracking and local branches")),
- OPT_BOOLEAN('r', "remotes", &all_remotes,
- N_("show remote-tracking branches")),
+ OPT_BOOL('a', "all", &all_heads,
+ N_("show remote-tracking and local branches")),
+ OPT_BOOL('r', "remotes", &all_remotes,
+ N_("show remote-tracking branches")),
OPT__COLOR(&showbranch_use_color,
N_("color '*!+-' corresponding to the branch")),
{ OPTION_INTEGER, 0, "more", &extra, N_("n"),
N_("show <n> more commits after the common ancestor"),
PARSE_OPT_OPTARG, NULL, (intptr_t)1 },
OPT_SET_INT(0, "list", &extra, N_("synonym to more=-1"), -1),
- OPT_BOOLEAN(0, "no-name", &no_name, N_("suppress naming strings")),
- OPT_BOOLEAN(0, "current", &with_current_branch,
- N_("include the current branch")),
- OPT_BOOLEAN(0, "sha1-name", &sha1_name,
- N_("name commits with their object names")),
- OPT_BOOLEAN(0, "merge-base", &merge_base,
- N_("show possible merge bases")),
- OPT_BOOLEAN(0, "independent", &independent,
+ OPT_BOOL(0, "no-name", &no_name, N_("suppress naming strings")),
+ OPT_BOOL(0, "current", &with_current_branch,
+ N_("include the current branch")),
+ OPT_BOOL(0, "sha1-name", &sha1_name,
+ N_("name commits with their object names")),
+ OPT_BOOL(0, "merge-base", &merge_base,
+ N_("show possible merge bases")),
+ OPT_BOOL(0, "independent", &independent,
N_("show refs unreachable from any other ref")),
- OPT_BOOLEAN(0, "topo-order", &lifo,
- N_("show commits in topological order")),
- OPT_BOOLEAN(0, "topics", &topics,
- N_("show only commits not on the first branch")),
+ OPT_SET_INT(0, "topo-order", &sort_order,
+ N_("show commits in topological order"),
+ REV_SORT_IN_GRAPH_ORDER),
+ OPT_BOOL(0, "topics", &topics,
+ N_("show only commits not on the first branch")),
OPT_SET_INT(0, "sparse", &dense,
N_("show merges reachable from only one tip"), 0),
- OPT_SET_INT(0, "date-order", &lifo,
- N_("show commits where no parent comes before its "
- "children"), 0),
+ OPT_SET_INT(0, "date-order", &sort_order,
+ N_("topologically sort, maintaining date order "
+ "where possible"),
+ REV_SORT_BY_COMMIT_DATE),
{ OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
N_("show <n> most recent ref-log entries starting at "
"base"),
@@ -811,7 +812,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
has_head++;
}
if (!has_head) {
- int offset = !prefixcmp(head, "refs/heads/") ? 11 : 0;
+ int offset = starts_with(head, "refs/heads/") ? 11 : 0;
append_one_rev(head + offset);
}
}
@@ -900,7 +901,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
exit(0);
/* Sort topologically */
- sort_in_topological_order(&seen, lifo);
+ sort_in_topological_order(&seen, sort_order);
/* Give names to commits */
if (!sha1_name && !no_name)
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 8d9b76a02f..5ba1f30838 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -31,11 +31,14 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
const char *hex;
unsigned char peeled[20];
+ if (show_head && !strcmp(refname, "HEAD"))
+ goto match;
+
if (tags_only || heads_only) {
int match;
- match = heads_only && !prefixcmp(refname, "refs/heads/");
- match |= tags_only && !prefixcmp(refname, "refs/tags/");
+ match = heads_only && starts_with(refname, "refs/heads/");
+ match |= tags_only && starts_with(refname, "refs/tags/");
if (!match)
return 0;
}
@@ -103,7 +106,7 @@ static int add_existing(const char *refname, const unsigned char *sha1, int flag
*/
static int exclude_existing(const char *match)
{
- static struct string_list existing_refs = STRING_LIST_INIT_NODUP;
+ static struct string_list existing_refs = STRING_LIST_INIT_DUP;
char buf[1024];
int matchlen = match ? strlen(match) : 0;
@@ -162,15 +165,15 @@ static int help_callback(const struct option *opt, const char *arg, int unset)
}
static const struct option show_ref_options[] = {
- OPT_BOOLEAN(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
- OPT_BOOLEAN(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
- OPT_BOOLEAN(0, "verify", &verify, N_("stricter reference checking, "
+ OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
+ OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
+ OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
"requires exact ref path")),
- { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL,
- N_("show the HEAD reference"),
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- OPT_BOOLEAN(0, "head", &show_head, N_("show the HEAD reference")),
- OPT_BOOLEAN('d', "dereference", &deref_tags,
+ OPT_HIDDEN_BOOL('h', NULL, &show_head,
+ N_("show the HEAD reference, even if it would be filtered out")),
+ OPT_BOOL(0, "head", &show_head,
+ 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"),
@@ -207,7 +210,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
while (*pattern) {
unsigned char sha1[20];
- if (!prefixcmp(*pattern, "refs/") &&
+ if (starts_with(*pattern, "refs/") &&
!read_ref(*pattern, sha1)) {
if (!quiet)
show_one(*pattern, sha1);
diff --git a/builtin/stripspace.c b/builtin/stripspace.c
index e981dfb9f0..1259ed708b 100644
--- a/builtin/stripspace.c
+++ b/builtin/stripspace.c
@@ -89,11 +89,11 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix)
if (argc == 2) {
if (!strcmp(argv[1], "-s") ||
- !strcmp(argv[1], "--strip-comments")) {
- strip_comments = 1;
+ !strcmp(argv[1], "--strip-comments")) {
+ strip_comments = 1;
} else if (!strcmp(argv[1], "-c") ||
- !strcmp(argv[1], "--comment-lines")) {
- mode = COMMENT_LINES;
+ !strcmp(argv[1], "--comment-lines")) {
+ mode = COMMENT_LINES;
} else {
mode = INVAL;
}
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index f481959421..b6a711d319 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -47,7 +47,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options,
git_symbolic_ref_usage, 0);
- if (msg &&!*msg)
+ if (msg && !*msg)
die("Refusing to perform update with empty message");
if (delete) {
@@ -65,7 +65,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
break;
case 2:
if (!strcmp(argv[0], "HEAD") &&
- prefixcmp(argv[1], "refs/"))
+ !starts_with(argv[1], "refs/"))
die("Refusing to point HEAD outside of refs/");
create_symref(argv[0], argv[1], msg);
break;
diff --git a/builtin/tag.c b/builtin/tag.c
index af3af3f649..74d3780b77 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -259,7 +259,7 @@ static int git_tag_config(const char *var, const char *value, void *cb)
int status = git_gpg_config(var, value, cb);
if (status)
return status;
- if (!prefixcmp(var, "column."))
+ if (starts_with(var, "column."))
return git_column_config(var, value, "tag", &colopts);
return git_default_config(var, value, cb);
}
@@ -436,26 +436,26 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
struct ref_lock *lock;
struct create_tag_options opt;
char *cleanup_arg = NULL;
- int annotate = 0, force = 0, lines = -1, list = 0,
- delete = 0, verify = 0;
+ int annotate = 0, force = 0, lines = -1;
+ int cmdmode = 0;
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT };
struct commit_list *with_commit = NULL;
struct option options[] = {
- OPT_BOOLEAN('l', "list", &list, N_("list tag names")),
+ OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
{ OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
N_("print <n> lines of each tag message"),
PARSE_OPT_OPTARG, NULL, 1 },
- OPT_BOOLEAN('d', "delete", &delete, N_("delete tags")),
- OPT_BOOLEAN('v', "verify", &verify, N_("verify tags")),
+ OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
+ OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'),
OPT_GROUP(N_("Tag creation options")),
- OPT_BOOLEAN('a', "annotate", &annotate,
+ OPT_BOOL('a', "annotate", &annotate,
N_("annotated tag, needs a message")),
OPT_CALLBACK('m', "message", &msg, N_("message"),
N_("tag message"), parse_msg_arg),
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
- OPT_BOOLEAN('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
+ OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
N_("how to strip spaces and #comments from message")),
OPT_STRING('u', "local-user", &keyid, N_("key id"),
@@ -489,22 +489,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
}
if (opt.sign)
annotate = 1;
- if (argc == 0 && !(delete || verify))
- list = 1;
+ if (argc == 0 && !cmdmode)
+ cmdmode = 'l';
- if ((annotate || msg.given || msgfile || force) &&
- (list || delete || verify))
+ if ((annotate || msg.given || msgfile || force) && (cmdmode != 0))
usage_with_options(git_tag_usage, options);
- if (list + delete + verify > 1)
- usage_with_options(git_tag_usage, options);
finalize_colopts(&colopts, -1);
- if (list && lines != -1) {
+ if (cmdmode == 'l' && lines != -1) {
if (explicitly_enable_column(colopts))
die(_("--column and -n are incompatible"));
colopts = 0;
}
- if (list) {
+ if (cmdmode == 'l') {
int ret;
if (column_active(colopts)) {
struct column_options copts;
@@ -523,9 +520,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
die(_("--contains option is only allowed with -l."));
if (points_at.nr)
die(_("--points-at option is only allowed with -l."));
- if (delete)
+ if (cmdmode == 'd')
return for_each_tag_name(argv, delete_tag);
- if (verify)
+ if (cmdmode == 'v')
return for_each_tag_name(argv, verify_tag);
if (msg.given || msgfile) {
@@ -577,7 +574,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (annotate)
create_tag(object, tag, &buf, &opt, prev, object);
- lock = lock_any_ref_for_update(ref.buf, prev, 0);
+ lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
if (!lock)
die(_("%s: cannot lock the ref"), ref.buf);
if (write_ref_sha1(lock, object, NULL) < 0)
diff --git a/builtin/tar-tree.c b/builtin/tar-tree.c
deleted file mode 100644
index 3f1e7012db..0000000000
--- a/builtin/tar-tree.c
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (c) 2005, 2006 Rene Scharfe
- */
-#include "cache.h"
-#include "commit.h"
-#include "tar.h"
-#include "builtin.h"
-#include "quote.h"
-
-static const char tar_tree_usage[] =
-"git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
-"*** Note that this command is now deprecated; use \"git archive\" instead.";
-
-static const char builtin_get_tar_commit_id_usage[] =
-"git get-tar-commit-id < <tarfile>";
-
-int cmd_tar_tree(int argc, const char **argv, const char *prefix)
-{
- /*
- * "git tar-tree" is now a wrapper around "git archive --format=tar"
- *
- * $0 --remote=<repo> arg... ==>
- * git archive --format=tar --remote=<repo> arg...
- * $0 tree-ish ==>
- * git archive --format=tar tree-ish
- * $0 tree-ish basedir ==>
- * git archive --format-tar --prefix=basedir tree-ish
- */
- int i;
- const char **nargv = xcalloc(sizeof(*nargv), argc + 3);
- char *basedir_arg;
- int nargc = 0;
-
- nargv[nargc++] = "archive";
- nargv[nargc++] = "--format=tar";
-
- if (2 <= argc && !prefixcmp(argv[1], "--remote=")) {
- nargv[nargc++] = argv[1];
- argv++;
- argc--;
- }
-
- /*
- * Because it's just a compatibility wrapper, tar-tree supports only
- * the old behaviour of reading attributes from the work tree.
- */
- nargv[nargc++] = "--worktree-attributes";
-
- switch (argc) {
- default:
- usage(tar_tree_usage);
- break;
- case 3:
- /* base-path */
- basedir_arg = xmalloc(strlen(argv[2]) + 11);
- sprintf(basedir_arg, "--prefix=%s/", argv[2]);
- nargv[nargc++] = basedir_arg;
- /* fallthru */
- case 2:
- /* tree-ish */
- nargv[nargc++] = argv[1];
- }
- nargv[nargc] = NULL;
-
- fprintf(stderr,
- "*** \"git tar-tree\" is now deprecated.\n"
- "*** Running \"git archive\" instead.\n***");
- for (i = 0; i < nargc; i++) {
- fputc(' ', stderr);
- sq_quote_print(stderr, nargv[i]);
- }
- fputc('\n', stderr);
- return cmd_archive(nargc, nargv, prefix);
-}
-
-/* ustar header + extended global header content */
-#define RECORDSIZE (512)
-#define HEADERSIZE (2 * RECORDSIZE)
-
-int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
-{
- char buffer[HEADERSIZE];
- struct ustar_header *header = (struct ustar_header *)buffer;
- char *content = buffer + RECORDSIZE;
- ssize_t n;
-
- if (argc != 1)
- usage(builtin_get_tar_commit_id_usage);
-
- n = read_in_full(0, buffer, HEADERSIZE);
- if (n < HEADERSIZE)
- die("git get-tar-commit-id: read error");
- if (header->typeflag[0] != 'g')
- return 1;
- if (memcmp(content, "52 comment=", 11))
- return 1;
-
- n = write_in_full(1, content + 11, 41);
- if (n < 41)
- die_errno("git get-tar-commit-id: write error");
-
- return 0;
-}
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 2217d7b3ae..62ff673f68 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -523,7 +523,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
strict = 1;
continue;
}
- if (!prefixcmp(arg, "--pack_header=")) {
+ if (starts_with(arg, "--pack_header=")) {
struct pack_header *hdr;
char *c;
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 5c7762eef4..d12ad95f3e 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -11,6 +11,8 @@
#include "refs.h"
#include "resolve-undo.h"
#include "parse-options.h"
+#include "pathspec.h"
+#include "dir.h"
/*
* Default to not allowing changes to the list of files. The
@@ -83,7 +85,7 @@ static int process_lstat_error(const char *path, int err)
return error("lstat(\"%s\"): %s", path, strerror(errno));
}
-static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
+static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st)
{
int option, size;
struct cache_entry *ce;
@@ -142,7 +144,7 @@ static int process_directory(const char *path, int len, struct stat *st)
/* Exact match: file or existing gitlink */
if (pos >= 0) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (S_ISGITLINK(ce->ce_mode)) {
/* Do nothing to the index if there is no HEAD! */
@@ -158,7 +160,7 @@ static int process_directory(const char *path, int len, struct stat *st)
/* Inexact match: is there perhaps a subdirectory match? */
pos = -pos-1;
while (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos++];
+ const struct cache_entry *ce = active_cache[pos++];
if (strncmp(ce->name, path, len))
break;
@@ -183,7 +185,7 @@ static int process_path(const char *path)
{
int pos, len;
struct stat st;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
len = strlen(path);
if (has_symlink_leading_path(path, len))
@@ -273,36 +275,32 @@ static void chmod_path(int flip, const char *path)
die("git update-index: cannot chmod %cx '%s'", flip, path);
}
-static void update_one(const char *path, const char *prefix, int prefix_length)
+static void update_one(const char *path)
{
- const char *p = prefix_path(prefix, prefix_length, path);
- if (!verify_path(p)) {
+ if (!verify_path(path)) {
fprintf(stderr, "Ignoring path %s\n", path);
- goto free_return;
+ return;
}
if (mark_valid_only) {
- if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
+ if (mark_ce_flags(path, CE_VALID, mark_valid_only == MARK_FLAG))
die("Unable to mark file %s", path);
- goto free_return;
+ return;
}
if (mark_skip_worktree_only) {
- if (mark_ce_flags(p, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG))
+ if (mark_ce_flags(path, CE_SKIP_WORKTREE, mark_skip_worktree_only == MARK_FLAG))
die("Unable to mark file %s", path);
- goto free_return;
+ return;
}
if (force_remove) {
- if (remove_file_from_cache(p))
+ if (remove_file_from_cache(path))
die("git update-index: unable to remove %s", path);
report("remove '%s'", path);
- goto free_return;
+ return;
}
- if (process_path(p))
+ if (process_path(path))
die("Unable to process path %s", path);
report("add '%s'", path);
- free_return:
- if (p < path || p > path + strlen(path))
- free((char *)p);
}
static void read_index_info(int line_termination)
@@ -448,7 +446,7 @@ static int unresolve_one(const char *path)
/* already merged */
pos = unmerge_cache_entry_at(pos);
if (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (ce_stage(ce) &&
ce_namelen(ce) == namelen &&
!memcmp(ce->name, path, namelen))
@@ -462,7 +460,7 @@ static int unresolve_one(const char *path)
*/
pos = -pos-1;
if (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (ce_namelen(ce) == namelen &&
!memcmp(ce->name, path, namelen)) {
fprintf(stderr,
@@ -546,10 +544,11 @@ static int do_reupdate(int ac, const char **av,
*/
int pos;
int has_head = 1;
- const char **paths = get_pathspec(prefix, av + 1);
struct pathspec pathspec;
- init_pathspec(&pathspec, paths);
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD,
+ prefix, av + 1);
if (read_ref("HEAD", head_sha1))
/* If there is no HEAD, that means it is an initial
@@ -558,11 +557,12 @@ static int do_reupdate(int ac, const char **av,
has_head = 0;
redo:
for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
struct cache_entry *old = NULL;
int save_nr;
+ char *path;
- if (ce_stage(ce) || !ce_path_match(ce, &pathspec))
+ if (ce_stage(ce) || !ce_path_match(ce, &pathspec, NULL))
continue;
if (has_head)
old = read_one_ent(NULL, head_sha1,
@@ -577,7 +577,9 @@ static int do_reupdate(int ac, const char **av,
* or worse yet 'allow_replace', active_nr may decrease.
*/
save_nr = active_nr;
- update_one(ce->name + prefix_length, prefix, prefix_length);
+ path = xstrdup(ce->name);
+ update_one(path);
+ free(path);
if (save_nr != active_nr)
goto redo;
}
@@ -834,11 +836,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
setup_work_tree();
p = prefix_path(prefix, prefix_length, path);
- update_one(p, NULL, 0);
+ update_one(p);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
- if (p < path || p > path + strlen(path))
- free((char *)p);
+ free((char *)p);
ctx.argc--;
ctx.argv++;
break;
@@ -877,11 +878,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
strbuf_swap(&buf, &nbuf);
}
p = prefix_path(prefix, prefix_length, buf.buf);
- update_one(p, NULL, 0);
+ update_one(p);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
- if (p < buf.buf || p > buf.buf + buf.len)
- free((char *)p);
+ free((char *)p);
}
strbuf_release(&nbuf);
strbuf_release(&buf);
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 51d2684859..1292cfea11 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -2,23 +2,261 @@
#include "refs.h"
#include "builtin.h"
#include "parse-options.h"
+#include "quote.h"
+#include "argv-array.h"
static const char * const git_update_ref_usage[] = {
N_("git update-ref [options] -d <refname> [<oldval>]"),
N_("git update-ref [options] <refname> <newval> [<oldval>]"),
+ N_("git update-ref [options] --stdin [-z]"),
NULL
};
+static int updates_alloc;
+static int updates_count;
+static const struct ref_update **updates;
+
+static char line_termination = '\n';
+static int update_flags;
+
+static struct ref_update *update_alloc(void)
+{
+ struct ref_update *update;
+
+ /* Allocate and zero-init a struct ref_update */
+ update = xcalloc(1, sizeof(*update));
+ ALLOC_GROW(updates, updates_count + 1, updates_alloc);
+ updates[updates_count++] = update;
+
+ /* Store and reset accumulated options */
+ update->flags = update_flags;
+ update_flags = 0;
+
+ return update;
+}
+
+static void update_store_ref_name(struct ref_update *update,
+ const char *ref_name)
+{
+ if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
+ die("invalid ref format: %s", ref_name);
+ update->ref_name = xstrdup(ref_name);
+}
+
+static void update_store_new_sha1(struct ref_update *update,
+ const char *newvalue)
+{
+ if (*newvalue && get_sha1(newvalue, update->new_sha1))
+ die("invalid new value for ref %s: %s",
+ update->ref_name, newvalue);
+}
+
+static void update_store_old_sha1(struct ref_update *update,
+ const char *oldvalue)
+{
+ if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
+ die("invalid old value for ref %s: %s",
+ update->ref_name, oldvalue);
+
+ /* We have an old value if non-empty, or if empty without -z */
+ update->have_old = *oldvalue || line_termination;
+}
+
+static const char *parse_arg(const char *next, struct strbuf *arg)
+{
+ /* Parse SP-terminated, possibly C-quoted argument */
+ if (*next != '"')
+ while (*next && !isspace(*next))
+ strbuf_addch(arg, *next++);
+ else if (unquote_c_style(arg, next, &next))
+ die("badly quoted argument: %s", next);
+
+ /* Return position after the argument */
+ return next;
+}
+
+static const char *parse_first_arg(const char *next, struct strbuf *arg)
+{
+ /* Parse argument immediately after "command SP" */
+ strbuf_reset(arg);
+ if (line_termination) {
+ /* Without -z, use the next argument */
+ next = parse_arg(next, arg);
+ } else {
+ /* With -z, use rest of first NUL-terminated line */
+ strbuf_addstr(arg, next);
+ next = next + arg->len;
+ }
+ return next;
+}
+
+static const char *parse_next_arg(const char *next, struct strbuf *arg)
+{
+ /* Parse next SP-terminated or NUL-terminated argument, if any */
+ strbuf_reset(arg);
+ if (line_termination) {
+ /* Without -z, consume SP and use next argument */
+ if (!*next)
+ return NULL;
+ if (*next != ' ')
+ die("expected SP but got: %s", next);
+ next = parse_arg(next + 1, arg);
+ } else {
+ /* With -z, read the next NUL-terminated line */
+ if (*next)
+ die("expected NUL but got: %s", next);
+ if (strbuf_getline(arg, stdin, '\0') == EOF)
+ return NULL;
+ next = arg->buf + arg->len;
+ }
+ return next;
+}
+
+static void parse_cmd_update(const char *next)
+{
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf newvalue = STRBUF_INIT;
+ struct strbuf oldvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("update line missing <ref>");
+
+ if ((next = parse_next_arg(next, &newvalue)) != NULL)
+ update_store_new_sha1(update, newvalue.buf);
+ else
+ die("update %s missing <newvalue>", ref.buf);
+
+ if ((next = parse_next_arg(next, &oldvalue)) != NULL)
+ update_store_old_sha1(update, oldvalue.buf);
+ else if(!line_termination)
+ die("update %s missing [<oldvalue>] NUL", ref.buf);
+
+ if (next && *next)
+ die("update %s has extra input: %s", ref.buf, next);
+}
+
+static void parse_cmd_create(const char *next)
+{
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf newvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("create line missing <ref>");
+
+ if ((next = parse_next_arg(next, &newvalue)) != NULL)
+ update_store_new_sha1(update, newvalue.buf);
+ else
+ die("create %s missing <newvalue>", ref.buf);
+ if (is_null_sha1(update->new_sha1))
+ die("create %s given zero new value", ref.buf);
+
+ if (next && *next)
+ die("create %s has extra input: %s", ref.buf, next);
+}
+
+static void parse_cmd_delete(const char *next)
+{
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf oldvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("delete line missing <ref>");
+
+ if ((next = parse_next_arg(next, &oldvalue)) != NULL)
+ update_store_old_sha1(update, oldvalue.buf);
+ else if(!line_termination)
+ die("delete %s missing [<oldvalue>] NUL", ref.buf);
+ if (update->have_old && is_null_sha1(update->old_sha1))
+ die("delete %s given zero old value", ref.buf);
+
+ if (next && *next)
+ die("delete %s has extra input: %s", ref.buf, next);
+}
+
+static void parse_cmd_verify(const char *next)
+{
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf value = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("verify line missing <ref>");
+
+ if ((next = parse_next_arg(next, &value)) != NULL) {
+ update_store_old_sha1(update, value.buf);
+ update_store_new_sha1(update, value.buf);
+ } else if(!line_termination)
+ die("verify %s missing [<oldvalue>] NUL", ref.buf);
+
+ if (next && *next)
+ die("verify %s has extra input: %s", ref.buf, next);
+}
+
+static void parse_cmd_option(const char *next)
+{
+ if (!strcmp(next, "no-deref"))
+ update_flags |= REF_NODEREF;
+ else
+ die("option unknown: %s", next);
+}
+
+static void update_refs_stdin(void)
+{
+ struct strbuf cmd = STRBUF_INIT;
+
+ /* Read each line dispatch its command */
+ while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
+ if (!cmd.buf[0])
+ die("empty command in input");
+ else if (isspace(*cmd.buf))
+ die("whitespace before command: %s", cmd.buf);
+ else if (starts_with(cmd.buf, "update "))
+ parse_cmd_update(cmd.buf + 7);
+ else if (starts_with(cmd.buf, "create "))
+ parse_cmd_create(cmd.buf + 7);
+ else if (starts_with(cmd.buf, "delete "))
+ parse_cmd_delete(cmd.buf + 7);
+ else if (starts_with(cmd.buf, "verify "))
+ parse_cmd_verify(cmd.buf + 7);
+ else if (starts_with(cmd.buf, "option "))
+ parse_cmd_option(cmd.buf + 7);
+ else
+ die("unknown command: %s", cmd.buf);
+
+ strbuf_release(&cmd);
+}
+
int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
const char *refname, *oldval, *msg = NULL;
unsigned char sha1[20], oldsha1[20];
- int delete = 0, no_deref = 0, flags = 0;
+ int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
- OPT_BOOLEAN('d', NULL, &delete, N_("delete the reference")),
- OPT_BOOLEAN( 0 , "no-deref", &no_deref,
+ OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
+ OPT_BOOL( 0 , "no-deref", &no_deref,
N_("update <refname> not the one it points to")),
+ OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
+ OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
OPT_END(),
};
@@ -28,6 +266,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
if (msg && !*msg)
die("Refusing to perform update with empty message.");
+ if (read_stdin) {
+ if (delete || no_deref || argc > 0)
+ usage_with_options(git_update_ref_usage, options);
+ if (end_null)
+ line_termination = '\0';
+ update_refs_stdin();
+ return update_refs(msg, updates, updates_count, DIE_ON_ERR);
+ }
+
+ if (end_null)
+ usage_with_options(git_update_ref_usage, options);
+
if (delete) {
if (argc < 1 || argc > 2)
usage_with_options(git_update_ref_usage, options);
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index af2da35e7d..32ab94cd06 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -37,7 +37,7 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
if (sent_argv.argc > MAX_ARGS)
die("Too many options (>%d)", MAX_ARGS - 1);
- if (prefixcmp(buf, arg_cmd))
+ if (!starts_with(buf, arg_cmd))
die("'argument' token or flush expected");
argv_array_push(&sent_argv, buf + strlen(arg_cmd));
}