summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/add.c318
-rw-r--r--builtin/annotate.c12
-rw-r--r--builtin/apply.c1622
-rw-r--r--builtin/archive.c29
-rw-r--r--builtin/bisect--helper.c10
-rw-r--r--builtin/blame.c1700
-rw-r--r--builtin/branch.c638
-rw-r--r--builtin/bundle.c2
-rw-r--r--builtin/cat-file.c397
-rw-r--r--builtin/check-attr.c38
-rw-r--r--builtin/check-ignore.c187
-rw-r--r--builtin/check-mailmap.c66
-rw-r--r--builtin/check-ref-format.c61
-rw-r--r--builtin/checkout-index.c56
-rw-r--r--builtin/checkout.c822
-rw-r--r--builtin/clean.c989
-rw-r--r--builtin/clone.c693
-rw-r--r--builtin/column.c59
-rw-r--r--builtin/commit-tree.c106
-rw-r--r--builtin/commit.c1102
-rw-r--r--builtin/config.c489
-rw-r--r--builtin/count-objects.c157
-rw-r--r--builtin/credential.c31
-rw-r--r--builtin/describe.c179
-rw-r--r--builtin/diff-files.c2
-rw-r--r--builtin/diff-index.c8
-rw-r--r--builtin/diff-tree.c12
-rw-r--r--builtin/diff.c194
-rw-r--r--builtin/fast-export.c565
-rw-r--r--builtin/fetch-pack.c1121
-rw-r--r--builtin/fetch.c1023
-rw-r--r--builtin/fmt-merge-msg.c478
-rw-r--r--builtin/for-each-ref.c319
-rw-r--r--builtin/fsck.c163
-rw-r--r--builtin/gc.c280
-rw-r--r--builtin/get-tar-commit-id.c41
-rw-r--r--builtin/grep.c574
-rw-r--r--builtin/hash-object.c103
-rw-r--r--builtin/help.c181
-rw-r--r--builtin/index-pack.c987
-rw-r--r--builtin/init-db.c92
-rw-r--r--builtin/interpret-trailers.c44
-rw-r--r--builtin/log.c532
-rw-r--r--builtin/ls-files.c252
-rw-r--r--builtin/ls-remote.c13
-rw-r--r--builtin/ls-tree.c57
-rw-r--r--builtin/mailinfo.c122
-rw-r--r--builtin/mailsplit.c39
-rw-r--r--builtin/merge-base.c195
-rw-r--r--builtin/merge-file.c27
-rw-r--r--builtin/merge-index.c8
-rw-r--r--builtin/merge-recursive.c4
-rw-r--r--builtin/merge-tree.c104
-rw-r--r--builtin/merge.c881
-rw-r--r--builtin/mktree.c14
-rw-r--r--builtin/mv.c209
-rw-r--r--builtin/name-rev.c185
-rw-r--r--builtin/notes.c561
-rw-r--r--builtin/pack-objects.c1514
-rw-r--r--builtin/pack-redundant.c6
-rw-r--r--builtin/pack-refs.c8
-rw-r--r--builtin/patch-id.c80
-rw-r--r--builtin/prune-packed.c83
-rw-r--r--builtin/prune.c124
-rw-r--r--builtin/push.c397
-rw-r--r--builtin/read-tree.c64
-rw-r--r--builtin/receive-pack.c1160
-rw-r--r--builtin/reflog.c54
-rw-r--r--builtin/remote-ext.c13
-rw-r--r--builtin/remote.c677
-rw-r--r--builtin/repack.c407
-rw-r--r--builtin/replace.c455
-rw-r--r--builtin/rerere.c14
-rw-r--r--builtin/reset.c352
-rw-r--r--builtin/rev-list.c160
-rw-r--r--builtin/rev-parse.c211
-rw-r--r--builtin/revert.c668
-rw-r--r--builtin/rm.c275
-rw-r--r--builtin/send-pack.c434
-rw-r--r--builtin/shortlog.c92
-rw-r--r--builtin/show-branch.c138
-rw-r--r--builtin/show-ref.c72
-rw-r--r--builtin/stripspace.c51
-rw-r--r--builtin/symbolic-ref.c44
-rw-r--r--builtin/tag.c560
-rw-r--r--builtin/tar-tree.c103
-rw-r--r--builtin/unpack-objects.c24
-rw-r--r--builtin/update-index.c217
-rw-r--r--builtin/update-ref.c382
-rw-r--r--builtin/update-server-info.c5
-rw-r--r--builtin/upload-archive.c88
-rw-r--r--builtin/var.c4
-rw-r--r--builtin/verify-commit.c93
-rw-r--r--builtin/verify-pack.c16
-rw-r--r--builtin/verify-tag.c51
-rw-r--r--builtin/write-tree.c10
96 files changed, 17784 insertions, 10475 deletions
diff --git a/builtin/add.c b/builtin/add.c
index c59b0c9..1074e32 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -5,7 +5,9 @@
*/
#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "dir.h"
+#include "pathspec.h"
#include "exec_cmd.h"
#include "cache-tree.h"
#include "run-command.h"
@@ -13,9 +15,11 @@
#include "diff.h"
#include "diffcore.h"
#include "revision.h"
+#include "bulk-checkin.h"
+#include "argv-array.h"
static const char * const builtin_add_usage[] = {
- "git add [options] [--] <filepattern>...",
+ N_("git add [options] [--] <pathspec>..."),
NULL
};
static int patch_interactive, add_interactive, edit_interactive;
@@ -79,190 +83,105 @@ static void update_callback(struct diff_queue_struct *q,
}
}
-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;
struct rev_info rev;
+
+ memset(&data, 0, sizeof(data));
+ data.flags = flags;
+
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;
- data.flags = flags;
- data.add_errors = 0;
rev.diffopt.format_callback_data = &data;
rev.max_count = 0; /* do not compare unmerged paths with stage #2 */
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
return !!data.add_errors;
}
-static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
-{
- int num_unmatched = 0, i;
-
- /*
- * Since we are walking the index as if we were walking the directory,
- * we have to mark the matched pathspec as seen; otherwise we will
- * mistakenly think that the user gave a pathspec that did not match
- * anything.
- */
- for (i = 0; i < specs; i++)
- if (!seen[i])
- num_unmatched++;
- if (!num_unmatched)
- return;
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
- }
-}
-
-static char *find_used_pathspec(const char **pathspec)
+static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
{
char *seen;
int i;
-
- for (i = 0; pathspec[i]; i++)
- ; /* just counting */
- seen = xcalloc(i, 1);
- fill_pathspec_matches(pathspec, seen, i);
- return seen;
-}
-
-static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
-{
- char *seen;
- int i, specs;
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;
- fill_pathspec_matches(pathspec, seen, specs);
+ add_pathspec_matches_against_index(pathspec, seen);
return seen;
}
-static void treat_gitlinks(const char **pathspec)
-{
- int i;
-
- if (!pathspec || !*pathspec)
- return;
-
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (S_ISGITLINK(ce->ce_mode)) {
- int len = ce_namelen(ce), j;
- for (j = 0; pathspec[j]; j++) {
- int len2 = strlen(pathspec[j]);
- if (len2 <= len || pathspec[j][len] != '/' ||
- memcmp(ce->name, pathspec[j], len))
- continue;
- if (len2 == len + 1)
- /* strip trailing slash */
- pathspec[j] = xstrndup(ce->name, len);
- else
- die (_("Path '%s' is in submodule '%.*s'"),
- pathspec[j], len, ce->name);
- }
- }
- }
-}
-
-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);
}
-static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
-{
- const char **pathspec = get_pathspec(prefix, argv);
-
- if (pathspec) {
- const char **p;
- for (p = pathspec; *p; p++) {
- if (has_symlink_leading_path(*p, strlen(*p))) {
- int len = prefix ? strlen(prefix) : 0;
- die(_("'%s' is beyond a symbolic link"), *p + len);
- }
- }
- }
-
- 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;
- const char **args;
-
- if (pathspec)
- while (pathspec[pc])
- pc++;
+ int status, i;
+ struct argv_array argv = ARGV_ARRAY_INIT;
- args = xcalloc(sizeof(const char *), (pc + 5));
- ac = 0;
- args[ac++] = "add--interactive";
+ argv_array_push(&argv, "add--interactive");
if (patch_mode)
- args[ac++] = patch_mode;
+ argv_array_push(&argv, patch_mode);
if (revision)
- args[ac++] = revision;
- args[ac++] = "--";
- if (pc) {
- memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
- ac += pc;
- }
- args[ac] = NULL;
-
- status = run_command_v_opt(args, RUN_GIT_CMD);
- free(args);
+ argv_array_push(&argv, revision);
+ argv_array_push(&argv, "--");
+ for (i = 0; i < pathspec->nr; i++)
+ /* pass original pathspec, to be re-parsed */
+ argv_array_push(&argv, pathspec->items[i].original);
+
+ status = run_command_v_opt(argv.argv, RUN_GIT_CMD);
+ argv_array_clear(&argv);
return status;
}
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(argc, 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)
{
- char *file = xstrdup(git_path("ADD_EDIT.patch"));
+ char *file = git_pathdup("ADD_EDIT.patch");
const char *apply_argv[] = { "apply", "--recount", "--cached",
NULL, NULL };
- struct child_process child;
+ struct child_process child = CHILD_PROCESS_INIT;
struct rev_info rev;
int out;
struct stat st;
@@ -272,20 +191,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;
- out = open(file, O_CREAT | O_WRONLY, 0644);
+ 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);
@@ -294,13 +215,13 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
if (!st.st_size)
die(_("Empty patch. Aborted."));
- memset(&child, 0, sizeof(child));
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);
return 0;
}
@@ -309,23 +230,38 @@ static struct lock_file lock_file;
static const char ignore_error[] =
N_("The following paths are ignored by one of your .gitignore files:\n");
-static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
-static int ignore_add_errors, addremove, intent_to_add, ignore_missing = 0;
+static int verbose, show_only, ignored_too, refresh_only;
+static int ignore_add_errors, intent_to_add, ignore_missing;
+
+#define ADDREMOVE_DEFAULT 1
+static int addremove = ADDREMOVE_DEFAULT;
+static int addremove_explicit = -1; /* unspecified */
+
+static int ignore_removal_cb(const struct option *opt, const char *arg, int unset)
+{
+ /* if we are told to ignore, we are not adding removals */
+ *(int *)opt->value = !unset ? 0 : 1;
+ return 0;
+}
static struct option builtin_add_options[] = {
- OPT__DRY_RUN(&show_only, "dry run"),
- OPT__VERBOSE(&verbose, "be verbose"),
+ OPT__DRY_RUN(&show_only, N_("dry run")),
+ OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_GROUP(""),
- OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
- OPT_BOOLEAN('p', "patch", &patch_interactive, "select hunks interactively"),
- OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"),
- OPT__FORCE(&ignored_too, "allow adding otherwise ignored files"),
- OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"),
- OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"),
- OPT_BOOLEAN('A', "all", &addremove, "add changes from all tracked and untracked files"),
- OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
- OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
- OPT_BOOLEAN( 0 , "ignore-missing", &ignore_missing, "check if - even missing - files are ignored in dry run"),
+ OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")),
+ OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")),
+ OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")),
+ OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")),
+ OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")),
+ OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")),
+ OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")),
+ { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit,
+ NULL /* takes no arguments */,
+ N_("ignore paths removed in the working tree (same as --no-all)"),
+ PARSE_OPT_NOARG, ignore_removal_cb },
+ OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
+ OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
+ OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
OPT_END(),
};
@@ -348,7 +284,7 @@ static int add_files(struct dir_struct *dir, int flags)
for (i = 0; i < dir->ignored_nr; i++)
fprintf(stderr, "%s\n", dir->ignored[i]->name);
fprintf(stderr, _("Use -f if you really want to add them.\n"));
- die(_("no files added"));
+ exit_status = 1;
}
for (i = 0; i < dir->nr; i++)
@@ -363,8 +299,7 @@ static int add_files(struct dir_struct *dir, int flags)
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;
@@ -385,20 +320,31 @@ int cmd_add(int argc, const char **argv, const char *prefix)
argc--;
argv++;
+ if (0 <= addremove_explicit)
+ addremove = addremove_explicit;
+ else if (take_worktree_changes && ADDREMOVE_DEFAULT)
+ addremove = 0; /* "-u" was given but not "-A" */
+
if (addremove && take_worktree_changes)
die(_("-A and -u are mutually incompatible"));
+
+ if (!take_worktree_changes && addremove_explicit < 0 && argc)
+ /* Turn "git add pathspec..." to "git add -A pathspec..." */
+ addremove = 1;
+
if (!show_only && ignore_missing)
die(_("Option --ignore-missing can only be used together with --dry-run"));
- if ((addremove || take_worktree_changes) && !argc) {
- static const char *here[2] = { ".", NULL };
+
+ if ((0 < addremove_explicit || take_worktree_changes) && !argc) {
+ static const char *whole[2] = { ":/", NULL };
argc = 1;
- argv = here;
+ argv = whole;
}
add_new_files = !take_worktree_changes && !refresh_only;
require_pathspec = !take_worktree_changes;
- newfd = hold_locked_index(&lock_file, 1);
+ hold_locked_index(&lock_file, 1);
flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
(show_only ? ADD_CACHE_PRETEND : 0) |
@@ -412,14 +358,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(argc, 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));
@@ -428,45 +383,66 @@ 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_used_pathspec(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 (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);
}
- exit_status |= add_files_to_cache(prefix, pathspec, flags);
+ plug_bulk_checkin();
+
+ exit_status |= add_files_to_cache(prefix, &pathspec, flags);
if (add_new_files)
exit_status |= add_files(&dir, flags);
- finish:
+ unplug_bulk_checkin();
+
+finish:
if (active_cache_changed) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
+ if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("Unable to write new index file"));
}
diff --git a/builtin/annotate.c b/builtin/annotate.c
index fc43eed..da413ae 100644
--- a/builtin/annotate.c
+++ b/builtin/annotate.c
@@ -5,20 +5,18 @@
*/
#include "git-compat-util.h"
#include "builtin.h"
+#include "argv-array.h"
int cmd_annotate(int argc, const char **argv, const char *prefix)
{
- const char **nargv;
+ struct argv_array args = ARGV_ARRAY_INIT;
int i;
- nargv = xmalloc(sizeof(char *) * (argc + 2));
- nargv[0] = "annotate";
- nargv[1] = "-c";
+ argv_array_pushl(&args, "annotate", "-c", NULL);
for (i = 1; i < argc; i++) {
- nargv[i+1] = argv[i];
+ argv_array_push(&args, argv[i]);
}
- nargv[argc + 1] = NULL;
- return cmd_blame(argc + 1, nargv, prefix);
+ return cmd_blame(args.argc, args.argv, prefix);
}
diff --git a/builtin/apply.c b/builtin/apply.c
index f2edc52..0ca687f 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -7,6 +7,7 @@
*
*/
#include "cache.h"
+#include "lockfile.h"
#include "cache-tree.h"
#include "quote.h"
#include "blob.h"
@@ -14,7 +15,11 @@
#include "builtin.h"
#include "string-list.h"
#include "dir.h"
+#include "diff.h"
#include "parse-options.h"
+#include "xdiff-interface.h"
+#include "ll-merge.h"
+#include "rerere.h"
/*
* --check turns on checking that the working tree matches the
@@ -45,11 +50,13 @@ static int apply_with_reject;
static int apply_verbosely;
static int allow_overlap;
static int no_add;
+static int threeway;
+static int unsafe_paths;
static const char *fake_ancestor;
static int line_termination = '\n';
static unsigned int p_context = UINT_MAX;
static const char * const apply_usage[] = {
- "git apply [options] [<patch>...]",
+ N_("git apply [options] [<patch>...]"),
NULL
};
@@ -102,7 +109,7 @@ static void parse_whitespace_option(const char *option)
ws_error_action = correct_ws_error;
return;
}
- die("unrecognized whitespace option '%s'", option);
+ die(_("unrecognized whitespace option '%s'"), option);
}
static void parse_ignorewhitespace_option(const char *option)
@@ -117,7 +124,7 @@ static void parse_ignorewhitespace_option(const char *option)
ws_ignore_action = ignore_ws_change;
return;
}
- die("unrecognized whitespace ignore option '%s'", option);
+ die(_("unrecognized whitespace ignore option '%s'"), option);
}
static void set_default_whitespace_mode(const char *whitespace_option)
@@ -151,9 +158,14 @@ struct fragment {
unsigned long leading, trailing;
unsigned long oldpos, oldlines;
unsigned long newpos, newlines;
+ /*
+ * 'patch' is usually borrowed from buf in apply_patch(),
+ * but some codepaths store an allocated buffer.
+ */
const char *patch;
+ unsigned free_patch:1,
+ rejected:1;
int size;
- int rejected;
int linenr;
struct fragment *next;
};
@@ -178,7 +190,6 @@ struct patch {
int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
int rejected;
unsigned ws_rule;
- unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
unsigned int is_toplevel_relative:1;
@@ -187,14 +198,49 @@ struct patch {
unsigned int is_copy:1;
unsigned int is_rename:1;
unsigned int recount:1;
+ unsigned int conflicted_threeway:1;
+ unsigned int direct_to_threeway:1;
struct fragment *fragments;
char *result;
size_t resultsize;
char old_sha1_prefix[41];
char new_sha1_prefix[41];
struct patch *next;
+
+ /* three-way fallback result */
+ unsigned char threeway_stage[3][20];
};
+static void free_fragment_list(struct fragment *list)
+{
+ while (list) {
+ struct fragment *next = list->next;
+ if (list->free_patch)
+ free((char *)list->patch);
+ free(list);
+ list = next;
+ }
+}
+
+static void free_patch(struct patch *patch)
+{
+ free_fragment_list(patch->fragments);
+ free(patch->def_name);
+ free(patch->old_name);
+ free(patch->new_name);
+ free(patch->result);
+ free(patch);
+}
+
+static void free_patch_list(struct patch *list)
+{
+ while (list) {
+ struct patch *next = list->next;
+ free_patch(list);
+ list = next;
+ }
+}
+
/*
* A line in a file, len-bytes long (includes the terminating LF,
* except for an incomplete line at the end if the file ends with
@@ -250,20 +296,19 @@ static int fuzzy_matchlines(const char *s1, size_t n1,
const char *last2 = s2 + n2 - 1;
int result = 0;
- if (n1 < 0 || n2 < 0)
- return 0;
-
/* ignore line endings */
while ((*last1 == '\r') || (*last1 == '\n'))
last1--;
while ((*last2 == '\r') || (*last2 == '\n'))
last2--;
- /* skip leading whitespace */
- while (isspace(*s1) && (s1 <= last1))
- s1++;
- while (isspace(*s2) && (s2 <= last2))
- s2++;
+ /* skip leading whitespaces, if both begin with whitespace */
+ if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) {
+ while (isspace(*s1) && (s1 <= last1))
+ s1++;
+ while (isspace(*s2) && (s2 <= last2))
+ s2++;
+ }
/* early return if both lines are empty */
if ((s1 > last1) && (s2 > last2))
return 1;
@@ -304,6 +349,11 @@ static void add_line_info(struct image *img, const char *bol, size_t len, unsign
img->nr++;
}
+/*
+ * "buf" has the file contents to be patched (read from various sources).
+ * attach it to "image" and add line-based index to it.
+ * "image" now owns the "buf".
+ */
static void prepare_image(struct image *image, char *buf, size_t len,
int prepare_linetable)
{
@@ -333,29 +383,31 @@ static void prepare_image(struct image *image, char *buf, size_t len,
static void clear_image(struct image *image)
{
free(image->buf);
- image->buf = NULL;
- image->len = 0;
+ free(image->line_allocated);
+ memset(image, 0, sizeof(*image));
}
-static void say_patch_name(FILE *output, const char *pre,
- struct patch *patch, const char *post)
+/* fmt must contain _one_ %s and no other substitution */
+static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
{
- fputs(pre, output);
+ struct strbuf sb = STRBUF_INIT;
+
if (patch->old_name && patch->new_name &&
strcmp(patch->old_name, patch->new_name)) {
- quote_c_style(patch->old_name, NULL, output, 0);
- fputs(" => ", output);
- quote_c_style(patch->new_name, NULL, output, 0);
+ quote_c_style(patch->old_name, &sb, NULL, 0);
+ strbuf_addstr(&sb, " => ");
+ quote_c_style(patch->new_name, &sb, NULL, 0);
} else {
const char *n = patch->new_name;
if (!n)
n = patch->old_name;
- quote_c_style(n, NULL, output, 0);
+ quote_c_style(n, &sb, NULL, 0);
}
- fputs(post, output);
+ fprintf(output, fmt, sb.buf);
+ fputc('\n', output);
+ strbuf_release(&sb);
}
-#define CHUNKSIZE (8192)
#define SLOP (16)
static void read_patch_file(struct strbuf *sb, int fd)
@@ -385,7 +437,7 @@ static unsigned long linelen(const char *buffer, unsigned long size)
static int is_dev_null(const char *str)
{
- return !memcmp("/dev/null", str, 9) && isspace(str[9]);
+ return skip_prefix(str, "/dev/null", &str) && isspace(*str);
}
#define TERM_SPACE 1
@@ -418,14 +470,14 @@ static char *squash_slash(char *name)
return name;
}
-static char *find_name_gnu(const char *line, char *def, int p_value)
+static char *find_name_gnu(const char *line, const char *def, int p_value)
{
struct strbuf name = STRBUF_INIT;
char *cp;
/*
* 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);
@@ -441,11 +493,7 @@ static char *find_name_gnu(const char *line, char *def, int p_value)
cp++;
}
- /* name can later be freed, so we need
- * to memmove, not just return cp
- */
strbuf_remove(&name, 0, cp - name.buf);
- free(def);
if (root)
strbuf_insert(&name, 0, root, root_len);
return squash_slash(strbuf_detach(&name, NULL));
@@ -610,8 +658,8 @@ static size_t diff_timestamp_len(const char *line, size_t len)
return line + len - end;
}
-static char *find_name_common(const char *line, char *def, int p_value,
- const char *end, int terminate)
+static char *find_name_common(const char *line, const char *def,
+ int p_value, const char *end, int terminate)
{
int len;
const char *start = NULL;
@@ -632,10 +680,10 @@ static char *find_name_common(const char *line, char *def, int p_value,
start = line;
}
if (!start)
- return squash_slash(def);
+ return squash_slash(xstrdup_or_null(def));
len = line - start;
if (!len)
- return squash_slash(def);
+ return squash_slash(xstrdup_or_null(def));
/*
* Generally we prefer the shorter name, especially
@@ -646,8 +694,7 @@ static char *find_name_common(const char *line, char *def, int p_value,
if (def) {
int deflen = strlen(def);
if (deflen < len && !strncmp(start, def, deflen))
- return squash_slash(def);
- free(def);
+ return squash_slash(xstrdup(def));
}
if (root) {
@@ -674,7 +721,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 == '"') {
@@ -772,7 +819,7 @@ static int has_epoch_timestamp(const char *nameline)
if (!stamp) {
stamp = xmalloc(sizeof(*stamp));
if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
- warning("Cannot prepare timestamp regexp %s",
+ warning(_("Cannot prepare timestamp regexp %s"),
stamp_regexp);
return 0;
}
@@ -781,7 +828,7 @@ static int has_epoch_timestamp(const char *nameline)
status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
if (status) {
if (status != REG_NOMATCH)
- warning("regexec returned %d for input: %s",
+ warning(_("regexec returned %d for input: %s"),
status, timestamp);
return 0;
}
@@ -844,8 +891,10 @@ static void parse_traditional_patch(const char *first, const char *second, struc
name = find_name_traditional(first, NULL, p_value);
patch->old_name = name;
} else {
- name = find_name_traditional(first, NULL, p_value);
- name = find_name_traditional(second, name, p_value);
+ char *first_name;
+ first_name = find_name_traditional(first, NULL, p_value);
+ name = find_name_traditional(second, first_name, p_value);
+ free(first_name);
if (has_epoch_timestamp(first)) {
patch->is_new = 1;
patch->is_delete = 0;
@@ -855,11 +904,12 @@ static void parse_traditional_patch(const char *first, const char *second, struc
patch->is_delete = 1;
patch->old_name = name;
} else {
- patch->old_name = patch->new_name = name;
+ patch->old_name = name;
+ patch->new_name = xstrdup_or_null(name);
}
}
if (!name)
- die("unable to find filename in patch at line %d", linenr);
+ die(_("unable to find filename in patch at line %d"), linenr);
}
static int gitdiff_hdrend(const char *line, struct patch *patch)
@@ -876,7 +926,10 @@ static int gitdiff_hdrend(const char *line, struct patch *patch)
* their names against any previous information, just
* to make sure..
*/
-static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
+#define DIFF_OLD_NAME 0
+#define DIFF_NEW_NAME 1
+
+static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, int side)
{
if (!orig_name && !isnull)
return find_name(line, NULL, p_value, TERM_TAB);
@@ -888,30 +941,40 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
name = orig_name;
len = strlen(name);
if (isnull)
- die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
+ die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr);
another = find_name(line, NULL, p_value, TERM_TAB);
if (!another || memcmp(another, name, len + 1))
- die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+ die((side == DIFF_NEW_NAME) ?
+ _("git apply: bad git-diff - inconsistent new filename on line %d") :
+ _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr);
free(another);
return orig_name;
}
else {
/* expect "/dev/null" */
if (memcmp("/dev/null", line, 9) || line[9] != '\n')
- die("git apply: bad git-diff - expected /dev/null on line %d", linenr);
+ die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr);
return NULL;
}
}
static int gitdiff_oldname(const char *line, struct patch *patch)
{
- patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
+ char *orig = patch->old_name;
+ patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name,
+ DIFF_OLD_NAME);
+ if (orig != patch->old_name)
+ free(orig);
return 0;
}
static int gitdiff_newname(const char *line, struct patch *patch)
{
- patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
+ char *orig = patch->new_name;
+ patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name,
+ DIFF_NEW_NAME);
+ if (orig != patch->new_name)
+ free(orig);
return 0;
}
@@ -930,20 +993,23 @@ static int gitdiff_newmode(const char *line, struct patch *patch)
static int gitdiff_delete(const char *line, struct patch *patch)
{
patch->is_delete = 1;
- patch->old_name = patch->def_name;
+ free(patch->old_name);
+ patch->old_name = xstrdup_or_null(patch->def_name);
return gitdiff_oldmode(line, patch);
}
static int gitdiff_newfile(const char *line, struct patch *patch)
{
patch->is_new = 1;
- patch->new_name = patch->def_name;
+ free(patch->new_name);
+ patch->new_name = xstrdup_or_null(patch->def_name);
return gitdiff_newmode(line, patch);
}
static int gitdiff_copysrc(const char *line, struct patch *patch)
{
patch->is_copy = 1;
+ free(patch->old_name);
patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -951,6 +1017,7 @@ static int gitdiff_copysrc(const char *line, struct patch *patch)
static int gitdiff_copydst(const char *line, struct patch *patch)
{
patch->is_copy = 1;
+ free(patch->new_name);
patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -958,6 +1025,7 @@ static int gitdiff_copydst(const char *line, struct patch *patch)
static int gitdiff_renamesrc(const char *line, struct patch *patch)
{
patch->is_rename = 1;
+ free(patch->old_name);
patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -965,21 +1033,24 @@ static int gitdiff_renamesrc(const char *line, struct patch *patch)
static int gitdiff_renamedst(const char *line, struct patch *patch)
{
patch->is_rename = 1;
+ free(patch->new_name);
patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_similarity(const char *line, struct patch *patch)
{
- if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
- patch->score = 0;
+ unsigned long val = strtoul(line, NULL, 10);
+ if (val <= 100)
+ patch->score = val;
return 0;
}
static int gitdiff_dissimilarity(const char *line, struct patch *patch)
{
- if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
- patch->score = 0;
+ unsigned long val = strtoul(line, NULL, 10);
+ if (val <= 100)
+ patch->score = val;
return 0;
}
@@ -1001,7 +1072,7 @@ static int gitdiff_index(const char *line, struct patch *patch)
line = ptr + 2;
ptr = strchr(line, ' ');
- eol = strchr(line, '\n');
+ eol = strchrnul(line, '\n');
if (!ptr || eol < ptr)
ptr = eol;
@@ -1025,15 +1096,23 @@ static int gitdiff_unrecognized(const char *line, struct patch *patch)
return -1;
}
-static const char *stop_at_slash(const char *line, int llen)
+/*
+ * Skip p_value leading components from "line"; as we do not accept
+ * absolute paths, return NULL in that case.
+ */
+static const char *skip_tree_prefix(const char *line, int llen)
{
- int nslash = p_value;
+ int nslash;
int i;
+ if (!p_value)
+ return (llen && line[0] == '/') ? NULL : line;
+
+ nslash = p_value;
for (i = 0; i < llen; i++) {
int ch = line[i];
if (ch == '/' && --nslash <= 0)
- return &line[i];
+ return (i == 0) ? NULL : &line[i + 1];
}
return NULL;
}
@@ -1046,7 +1125,7 @@ static const char *stop_at_slash(const char *line, int llen)
* creation or deletion of an empty file. In any of these cases,
* both sides are the same name under a/ and b/ respectively.
*/
-static char *git_header_name(char *line, int llen)
+static char *git_header_name(const char *line, int llen)
{
const char *name;
const char *second = NULL;
@@ -1063,12 +1142,11 @@ static char *git_header_name(char *line, int llen)
if (unquote_c_style(&first, line, &second))
goto free_and_fail1;
- /* advance to the first slash */
- cp = stop_at_slash(first.buf, first.len);
- /* we do not accept absolute paths */
- if (!cp || cp == first.buf)
+ /* strip the a/b prefix including trailing slash */
+ cp = skip_tree_prefix(first.buf, first.len);
+ if (!cp)
goto free_and_fail1;
- strbuf_remove(&first, 0, cp + 1 - first.buf);
+ strbuf_remove(&first, 0, cp - first.buf);
/*
* second points at one past closing dq of name.
@@ -1082,22 +1160,21 @@ static char *git_header_name(char *line, int llen)
if (*second == '"') {
if (unquote_c_style(&sp, second, NULL))
goto free_and_fail1;
- cp = stop_at_slash(sp.buf, sp.len);
- if (!cp || cp == sp.buf)
+ cp = skip_tree_prefix(sp.buf, sp.len);
+ if (!cp)
goto free_and_fail1;
/* They must match, otherwise ignore */
- if (strcmp(cp + 1, first.buf))
+ if (strcmp(cp, first.buf))
goto free_and_fail1;
strbuf_release(&sp);
return strbuf_detach(&first, NULL);
}
/* unquoted second */
- cp = stop_at_slash(second, line + llen - second);
- if (!cp || cp == second)
+ cp = skip_tree_prefix(second, line + llen - second);
+ if (!cp)
goto free_and_fail1;
- cp++;
- if (line + llen - cp != first.len + 1 ||
+ if (line + llen - cp != first.len ||
memcmp(first.buf, cp, first.len))
goto free_and_fail1;
return strbuf_detach(&first, NULL);
@@ -1109,10 +1186,9 @@ static char *git_header_name(char *line, int llen)
}
/* unquoted first name */
- name = stop_at_slash(line, llen);
- if (!name || name == line)
+ name = skip_tree_prefix(line, llen);
+ if (!name)
return NULL;
- name++;
/*
* since the first name is unquoted, a dq if exists must be
@@ -1126,10 +1202,9 @@ static char *git_header_name(char *line, int llen)
if (unquote_c_style(&sp, second, NULL))
goto free_and_fail2;
- np = stop_at_slash(sp.buf, sp.len);
- if (!np || np == sp.buf)
+ np = skip_tree_prefix(sp.buf, sp.len);
+ if (!np)
goto free_and_fail2;
- np++;
len = sp.buf + sp.len - np;
if (len < second - name &&
@@ -1161,19 +1236,33 @@ static char *git_header_name(char *line, int llen)
case '\n':
return NULL;
case '\t': case ' ':
- second = stop_at_slash(name + len, line_len - len);
+ /*
+ * Is this the separator between the preimage
+ * and the postimage pathname? Again, we are
+ * only interested in the case where there is
+ * no rename, as this is only to set def_name
+ * and a rename patch has the names elsewhere
+ * in an unambiguous form.
+ */
+ if (!name[len + 1])
+ return NULL; /* no postimage name */
+ second = skip_tree_prefix(name + len + 1,
+ line_len - (len + 1));
if (!second)
return NULL;
- second++;
- if (second[len] == '\n' && !strncmp(name, second, len)) {
+ /*
+ * Does len bytes starting at "name" and "second"
+ * (that are separated by one HT or SP we just
+ * found) exactly match?
+ */
+ if (second[len] == '\n' && !strncmp(name, second, len))
return xmemdupz(name, len);
- }
}
}
}
/* Verify that we recognize the lines following a git header */
-static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
+static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch)
{
unsigned long offset;
@@ -1189,9 +1278,7 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
*/
patch->def_name = git_header_name(line, len);
if (patch->def_name && root) {
- char *s = xmalloc(root_len + strlen(patch->def_name) + 1);
- strcpy(s, root);
- strcpy(s + root_len, patch->def_name);
+ char *s = xstrfmt("%s%s", root, patch->def_name);
free(patch->def_name);
patch->def_name = s;
}
@@ -1289,7 +1376,7 @@ static int parse_range(const char *line, int len, int offset, const char *expect
return offset + ex;
}
-static void recount_diff(char *line, int size, struct fragment *fragment)
+static void recount_diff(const char *line, int size, struct fragment *fragment)
{
int oldlines = 0, newlines = 0, ret = 0;
@@ -1319,17 +1406,17 @@ static void recount_diff(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;
break;
}
if (ret) {
- warning("recount: unexpected line: %.*s",
+ warning(_("recount: unexpected line: %.*s"),
(int)linelen(line, size), line);
return;
}
@@ -1343,7 +1430,7 @@ static void recount_diff(char *line, int size, struct fragment *fragment)
* Parse a unified diff fragment header of the
* form "@@ -a,b +c,d @@"
*/
-static int parse_fragment_header(char *line, int len, struct fragment *fragment)
+static int parse_fragment_header(const char *line, int len, struct fragment *fragment)
{
int offset;
@@ -1357,7 +1444,7 @@ static int parse_fragment_header(char *line, int len, struct fragment *fragment)
return offset;
}
-static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
+static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch)
{
unsigned long offset, len;
@@ -1386,7 +1473,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
struct fragment dummy;
if (parse_fragment_header(line, len, &dummy) < 0)
continue;
- die("patch fragment without header at line %d: %.*s",
+ die(_("patch fragment without header at line %d: %.*s"),
linenr, (int)len-1, line);
}
@@ -1403,10 +1490,18 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
continue;
if (!patch->old_name && !patch->new_name) {
if (!patch->def_name)
- die("git diff header lacks filename information when removing "
- "%d leading pathname components (line %d)" , p_value, linenr);
- patch->old_name = patch->new_name = patch->def_name;
+ die(Q_("git diff header lacks filename information when removing "
+ "%d leading pathname component (line %d)",
+ "git diff header lacks filename information when removing "
+ "%d leading pathname components (line %d)",
+ p_value),
+ p_value, linenr);
+ patch->old_name = xstrdup(patch->def_name);
+ patch->new_name = xstrdup(patch->def_name);
}
+ if (!patch->is_delete && !patch->new_name)
+ die("git diff header lacks filename information "
+ "(line %d)", linenr);
patch->is_toplevel_relative = 1;
*hdrsize = git_hdr_len;
return offset;
@@ -1465,7 +1560,7 @@ static void check_whitespace(const char *line, int len, unsigned ws_rule)
* between a "---" that is part of a patch, and a "---" that starts
* the next patch is to look at the line counts..
*/
-static int parse_fragment(char *line, unsigned long size,
+static int parse_fragment(const char *line, unsigned long size,
struct patch *patch, struct fragment *fragment)
{
int added, deleted;
@@ -1555,13 +1650,21 @@ static int parse_fragment(char *line, unsigned long size,
patch->lines_deleted += deleted;
if (0 < patch->is_new && oldlines)
- return error("new file depends on old contents");
+ return error(_("new file depends on old contents"));
if (0 < patch->is_delete && newlines)
- return error("deleted file still has contents");
+ return error(_("deleted file still has contents"));
return offset;
}
-static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+/*
+ * We have seen "diff --git a/... b/..." header (or a traditional patch
+ * header). Read hunks that belong to this patch into fragments and hang
+ * them to the given patch structure.
+ *
+ * The (fragment->patch, fragment->size) pair points into the memory given
+ * by the caller, not a copy, when we return.
+ */
+static int parse_single_patch(const char *line, unsigned long size, struct patch *patch)
{
unsigned long offset = 0;
unsigned long oldlines = 0, newlines = 0, context = 0;
@@ -1575,7 +1678,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
fragment->linenr = linenr;
len = parse_fragment(line, size, patch, fragment);
if (len <= 0)
- die("corrupt patch at line %d", linenr);
+ die(_("corrupt patch at line %d"), linenr);
fragment->patch = line;
fragment->size = len;
oldlines += fragment->oldlines;
@@ -1611,12 +1714,14 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
patch->is_delete = 0;
if (0 < patch->is_new && oldlines)
- die("new file %s depends on old contents", patch->new_name);
+ die(_("new file %s depends on old contents"), patch->new_name);
if (0 < patch->is_delete && newlines)
- die("deleted file %s still has contents", patch->old_name);
+ die(_("deleted file %s still has contents"), patch->old_name);
if (!patch->is_delete && !newlines && context)
- fprintf(stderr, "** warning: file %s becomes empty but "
- "is not deleted\n", patch->new_name);
+ fprintf_ln(stderr,
+ _("** warning: "
+ "file %s becomes empty but is not deleted"),
+ patch->new_name);
return offset;
}
@@ -1654,6 +1759,11 @@ static char *inflate_it(const void *data, unsigned long size,
return out;
}
+/*
+ * Read a binary hunk and return a new fragment; fragment->patch
+ * points at an allocated memory that the caller must free, so
+ * it is marked as "->free_patch = 1".
+ */
static struct fragment *parse_binary_hunk(char **buf_p,
unsigned long *sz_p,
int *status_p,
@@ -1685,11 +1795,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);
}
@@ -1741,6 +1851,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
frag = xcalloc(1, sizeof(*frag));
frag->patch = inflate_it(data, hunk_size, origlen);
+ frag->free_patch = 1;
if (!frag->patch)
goto corrupt;
free(data);
@@ -1754,7 +1865,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
corrupt:
free(data);
*status_p = -1;
- error("corrupt binary patch at line %d: %.*s",
+ error(_("corrupt binary patch at line %d: %.*s"),
linenr-1, llen-1, buffer);
return NULL;
}
@@ -1783,7 +1894,7 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
forward = parse_binary_hunk(&buffer, &size, &status, &used);
if (!forward && !status)
/* there has to be one hunk (forward hunk) */
- return error("unrecognized binary patch at line %d", linenr-1);
+ return error(_("unrecognized binary patch at line %d"), linenr-1);
if (status)
/* otherwise we already gave an error message */
return status;
@@ -1806,6 +1917,73 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
return used;
}
+static void prefix_one(char **name)
+{
+ char *old_name = *name;
+ if (!old_name)
+ return;
+ *name = xstrdup(prefix_filename(prefix, prefix_length, *name));
+ free(old_name);
+}
+
+static void prefix_patch(struct patch *p)
+{
+ if (!prefix || p->is_toplevel_relative)
+ return;
+ prefix_one(&p->new_name);
+ prefix_one(&p->old_name);
+}
+
+/*
+ * include/exclude
+ */
+
+static struct string_list limit_by_name;
+static int has_include;
+static void add_name_limit(const char *name, int exclude)
+{
+ struct string_list_item *it;
+
+ it = string_list_append(&limit_by_name, name);
+ it->util = exclude ? NULL : (void *) 1;
+}
+
+static int use_patch(struct patch *p)
+{
+ const char *pathname = p->new_name ? p->new_name : p->old_name;
+ int i;
+
+ /* Paths outside are not touched regardless of "--include" */
+ if (0 < prefix_length) {
+ int pathlen = strlen(pathname);
+ if (pathlen <= prefix_length ||
+ memcmp(prefix, pathname, prefix_length))
+ return 0;
+ }
+
+ /* See if it matches any of exclude/include rule */
+ for (i = 0; i < limit_by_name.nr; i++) {
+ struct string_list_item *it = &limit_by_name.items[i];
+ if (!wildmatch(it->string, pathname, 0, NULL))
+ return (it->util != NULL);
+ }
+
+ /*
+ * If we had any include, a path that does not match any rule is
+ * not used. Otherwise, we saw bunch of exclude rules (or none)
+ * and such a path is used.
+ */
+ return !has_include;
+}
+
+
+/*
+ * Read the patch text in "buffer" that extends for "size" bytes; stop
+ * reading after seeing a single patch (i.e. changes to a single file).
+ * Create fragments (i.e. patch hunks) and hang them to the given patch.
+ * Return the number of bytes consumed, so that the caller can call us
+ * again for the next patch.
+ */
static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
{
int hdrsize, patchsize;
@@ -1814,21 +1992,20 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
if (offset < 0)
return offset;
- patch->ws_rule = whitespace_rule(patch->new_name
- ? patch->new_name
- : patch->old_name);
+ prefix_patch(patch);
+
+ if (!use_patch(patch))
+ patch->ws_rule = 0;
+ else
+ patch->ws_rule = whitespace_rule(patch->new_name
+ ? patch->new_name
+ : patch->old_name);
patchsize = parse_single_patch(buffer + offset + hdrsize,
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);
@@ -1844,6 +2021,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 &&
@@ -1862,7 +2045,7 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
*/
if ((apply || check) &&
(!patch->is_binary && !metadata_changes(patch)))
- die("patch with only garbage at line %d", linenr);
+ die(_("patch with only garbage at line %d"), linenr);
}
return offset + hdrsize + patchsize;
@@ -1952,11 +2135,11 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
switch (st->st_mode & S_IFMT) {
case S_IFLNK:
if (strbuf_readlink(buf, path, st->st_size) < 0)
- return error("unable to read symlink %s", path);
+ return error(_("unable to read symlink %s"), path);
return 0;
case S_IFREG:
if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
- return error("unable to open or read %s", path);
+ return error(_("unable to open or read %s"), path);
convert_to_git(path, buf->buf, buf->len, buf, 0);
return 0;
default:
@@ -1976,7 +2159,7 @@ static void update_pre_post_images(struct image *preimage,
char *buf,
size_t len, size_t postlen)
{
- int i, ctx;
+ int i, ctx, reduced;
char *new, *old, *fixed;
struct image fixed_preimage;
@@ -1986,18 +2169,20 @@ static void update_pre_post_images(struct image *preimage,
* free "oldlines".
*/
prepare_image(&fixed_preimage, buf, len, 1);
- assert(fixed_preimage.nr == preimage->nr);
- for (i = 0; i < preimage->nr; i++)
+ assert(postlen
+ ? fixed_preimage.nr == preimage->nr
+ : fixed_preimage.nr <= preimage->nr);
+ for (i = 0; i < fixed_preimage.nr; i++)
fixed_preimage.line[i].flag = preimage->line[i].flag;
free(preimage->line_allocated);
*preimage = fixed_preimage;
/*
* Adjust the common context lines in postimage. This can be
- * done in-place when we are just doing whitespace fixing,
- * which does not make the string grow, but needs a new buffer
- * when ignoring whitespace causes the update, since in this case
- * we could have e.g. tabs converted to multiple spaces.
+ * done in-place when we are shrinking it with whitespace
+ * fixing, but needs a new buffer when ignoring whitespace or
+ * expanding leading tabs to spaces.
+ *
* We trust the caller to tell us if the update can be done
* in place (postlen==0) or not.
*/
@@ -2007,7 +2192,8 @@ static void update_pre_post_images(struct image *preimage,
else
new = old;
fixed = preimage->buf;
- for (i = ctx = 0; i < postimage->nr; i++) {
+
+ for (i = reduced = ctx = 0; i < postimage->nr; i++) {
size_t len = postimage->line[i].len;
if (!(postimage->line[i].flag & LINE_COMMON)) {
/* an added line -- no counterparts in preimage */
@@ -2026,8 +2212,15 @@ static void update_pre_post_images(struct image *preimage,
fixed += preimage->line[ctx].len;
ctx++;
}
- if (preimage->nr <= ctx)
- die("oops");
+
+ /*
+ * preimage is expected to run out, if the caller
+ * fixed addition of trailing blank lines.
+ */
+ if (preimage->nr <= ctx) {
+ reduced++;
+ continue;
+ }
/* and copy it in, while fixing the line length */
len = preimage->line[ctx].len;
@@ -2038,8 +2231,15 @@ static void update_pre_post_images(struct image *preimage,
ctx++;
}
+ if (postlen
+ ? postlen < new - postimage->buf
+ : postimage->len < new - postimage->buf)
+ die("BUG: caller miscounted postlen: asked %d, orig = %d, used = %d",
+ (int)postlen, (int) postimage->len, (int)(new - postimage->buf));
+
/* Fix the length of the whole thing */
postimage->len = new - postimage->buf;
+ postimage->nr -= reduced;
}
static int match_fragment(struct image *img,
@@ -2053,7 +2253,7 @@ static int match_fragment(struct image *img,
int i;
char *fixed_buf, *buf, *orig, *target;
struct strbuf fixed;
- size_t fixed_len;
+ size_t fixed_len, postlen;
int preimage_limit;
if (preimage->nr + try_lno <= img->nr) {
@@ -2192,10 +2392,27 @@ static int match_fragment(struct image *img,
/*
* The hunk does not apply byte-by-byte, but the hash says
- * it might with whitespace fuzz. We haven't been asked to
+ * it might with whitespace fuzz. We weren't asked to
* ignore whitespace, we were asked to correct whitespace
* errors, so let's try matching after whitespace correction.
*
+ * While checking the preimage against the target, whitespace
+ * errors in both fixed, we count how large the corresponding
+ * postimage needs to be. The postimage prepared by
+ * apply_one_fragment() has whitespace errors fixed on added
+ * lines already, but the common lines were propagated as-is,
+ * which may become longer when their whitespace errors are
+ * fixed.
+ */
+
+ /* First count added lines in postimage */
+ postlen = 0;
+ for (i = 0; i < postimage->nr; i++) {
+ if (!(postimage->line[i].flag & LINE_COMMON))
+ postlen += postimage->line[i].len;
+ }
+
+ /*
* The preimage may extend beyond the end of the file,
* but in this loop we will only handle the part of the
* preimage that falls within the file.
@@ -2231,6 +2448,10 @@ static int match_fragment(struct image *img,
!memcmp(tgtfix.buf, fixed.buf + fixstart,
fixed.len - fixstart));
+ /* Add the length if this is common with the postimage */
+ if (preimage->line[i].flag & LINE_COMMON)
+ postlen += tgtfix.len;
+
strbuf_release(&tgtfix);
if (!match)
goto unmatch_exit;
@@ -2267,8 +2488,10 @@ static int match_fragment(struct image *img,
* hunk match. Update the context lines in the postimage.
*/
fixed_buf = strbuf_detach(&fixed, &fixed_len);
+ if (postlen < postimage->len)
+ postlen = 0;
update_pre_post_images(preimage, postimage,
- fixed_buf, fixed_len, 0);
+ fixed_buf, fixed_len, postlen);
return 1;
unmatch_exit:
@@ -2366,6 +2589,11 @@ static void remove_last_line(struct image *img)
img->len -= img->line[--img->nr].len;
}
+/*
+ * The change from "preimage" and "postimage" has been found to
+ * apply at applied_pos (counts in line numbers) in "img".
+ * Update "img" to remove "preimage" and replace it with "postimage".
+ */
static void update_image(struct image *img,
int applied_pos,
struct image *preimage,
@@ -2420,7 +2648,7 @@ static void update_image(struct image *img,
* NOTE: this knows that we never call remove_first_line()
* on anything other than pre/post image.
*/
- img->line = xrealloc(img->line, nr * sizeof(*img->line));
+ REALLOC_ARRAY(img->line, nr);
img->line_allocated = img->line;
}
if (preimage_limit != postimage->nr)
@@ -2437,6 +2665,11 @@ static void update_image(struct image *img,
img->nr = nr;
}
+/*
+ * Use the patch-hunk text in "frag" to prepare two images (preimage and
+ * postimage) for the hunk. Find lines that match "preimage" in "img" and
+ * replace the part of "img" with "postimage" text.
+ */
static int apply_one_fragment(struct image *img, struct fragment *frag,
int inaccurate_eof, unsigned ws_rule,
int nth_fragment)
@@ -2447,6 +2680,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
char *old, *oldlines;
struct strbuf newlines;
int new_blank_lines_at_end = 0;
+ int found_new_blank_lines_at_end = 0;
+ int hunk_linenr = frag->linenr;
unsigned long leading, trailing;
int pos, applied_pos;
struct image preimage;
@@ -2537,17 +2772,21 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
break;
default:
if (apply_verbosely)
- error("invalid start of line: '%c'", first);
+ error(_("invalid start of line: '%c'"), first);
return -1;
}
- if (added_blank_line)
+ if (added_blank_line) {
+ if (!new_blank_lines_at_end)
+ found_new_blank_lines_at_end = hunk_linenr;
new_blank_lines_at_end++;
+ }
else if (is_blank_context)
;
else
new_blank_lines_at_end = 0;
patch += len;
size -= len;
+ hunk_linenr++;
}
if (inaccurate_eof &&
old > oldlines && old[-1] == '\n' &&
@@ -2629,7 +2868,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
preimage.nr + applied_pos >= img->nr &&
(ws_rule & WS_BLANK_AT_EOF) &&
ws_error_action != nowarn_ws_error) {
- record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr);
+ record_ws_error(WS_BLANK_AT_EOF, "+", 1,
+ found_new_blank_lines_at_end);
if (ws_error_action == correct_ws_error) {
while (new_blank_lines_at_end--)
remove_last_line(&postimage);
@@ -2649,9 +2889,11 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
int offset = applied_pos - pos;
if (apply_in_reverse)
offset = 0 - offset;
- fprintf(stderr,
- "Hunk #%d succeeded at %d (offset %d lines).\n",
- nth_fragment, applied_pos + 1, offset);
+ fprintf_ln(stderr,
+ Q_("Hunk #%d succeeded at %d (offset %d line).",
+ "Hunk #%d succeeded at %d (offset %d lines).",
+ offset),
+ nth_fragment, applied_pos + 1, offset);
}
/*
@@ -2660,13 +2902,13 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
*/
if ((leading != frag->leading) ||
(trailing != frag->trailing))
- fprintf(stderr, "Context reduced to (%ld/%ld)"
- " to apply fragment at %d\n",
- leading, trailing, applied_pos+1);
+ fprintf_ln(stderr, _("Context reduced to (%ld/%ld)"
+ " to apply fragment at %d"),
+ leading, trailing, applied_pos+1);
update_image(img, applied_pos, &preimage, &postimage);
} else {
if (apply_verbosely)
- error("while searching for:\n%.*s",
+ error(_("while searching for:\n%.*s"),
(int)(old - oldlines), oldlines);
}
@@ -2685,7 +2927,7 @@ static int apply_binary_fragment(struct image *img, struct patch *patch)
void *dst;
if (!fragment)
- return error("missing binary patch data for '%s'",
+ return error(_("missing binary patch data for '%s'"),
patch->new_name ?
patch->new_name :
patch->old_name);
@@ -2712,14 +2954,18 @@ static int apply_binary_fragment(struct image *img, struct patch *patch)
case BINARY_LITERAL_DEFLATED:
clear_image(img);
img->len = fragment->size;
- img->buf = xmalloc(img->len+1);
- memcpy(img->buf, fragment->patch, img->len);
- img->buf[img->len] = '\0';
+ img->buf = xmemdupz(fragment->patch, img->len);
return 0;
}
return -1;
}
+/*
+ * Replace "img" with the result of applying the binary patch.
+ * The binary patch data itself in patch->fragment is still kept
+ * but the preimage prepared by the caller in "img" is freed here
+ * or in the helper function apply_binary_fragment() this calls.
+ */
static int apply_binary(struct image *img, struct patch *patch)
{
const char *name = patch->old_name ? patch->old_name : patch->new_name;
@@ -2782,13 +3028,13 @@ static int apply_binary(struct image *img, struct patch *patch)
* in the patch->fragments->{patch,size}.
*/
if (apply_binary_fragment(img, patch))
- return error("binary patch does not apply to '%s'",
+ return error(_("binary patch does not apply to '%s'"),
name);
/* verify that the result matches */
hash_sha1_file(img->buf, img->len, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
- return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
+ return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
name, patch->new_sha1_prefix, sha1_to_hex(sha1));
}
@@ -2809,7 +3055,7 @@ static int apply_fragments(struct image *img, struct patch *patch)
while (frag) {
nth++;
if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) {
- error("patch failed: %s:%ld", name, frag->oldpos);
+ error(_("patch failed: %s:%ld"), name, frag->oldpos);
if (!apply_with_reject)
return -1;
frag->rejected = 1;
@@ -2819,20 +3065,17 @@ static int apply_fragments(struct image *img, struct patch *patch)
return 0;
}
-static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode)
{
- if (!ce)
- return 0;
-
- if (S_ISGITLINK(ce->ce_mode)) {
+ if (S_ISGITLINK(mode)) {
strbuf_grow(buf, 100);
- strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
+ strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(sha1));
} else {
enum object_type type;
unsigned long sz;
char *result;
- result = read_sha1_file(ce->sha1, &type, &sz);
+ result = read_sha1_file(sha1, &type, &sz);
if (!result)
return -1;
/* XXX read_sha1_file NUL-terminates */
@@ -2841,6 +3084,13 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
return 0;
}
+static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf)
+{
+ if (!ce)
+ return 0;
+ return read_blob_object(buf, ce->sha1, ce->ce_mode);
+}
+
static struct patch *in_fn_table(const char *name)
{
struct string_list_item *item;
@@ -2859,9 +3109,15 @@ static struct patch *in_fn_table(const char *name)
* item->util in the filename table records the status of the path.
* Usually it points at a patch (whose result records the contents
* of it after applying it), but it could be PATH_WAS_DELETED for a
- * path that a previously applied patch has already removed.
+ * path that a previously applied patch has already removed, or
+ * PATH_TO_BE_DELETED for a path that a later patch would remove.
+ *
+ * The latter is needed to deal with a case where two paths A and B
+ * are swapped by first renaming A to B and then renaming B to A;
+ * moving A to B should not be prevented due to presence of B as we
+ * will remove it in a later patch.
*/
- #define PATH_TO_BE_DELETED ((struct patch *) -2)
+#define PATH_TO_BE_DELETED ((struct patch *) -2)
#define PATH_WAS_DELETED ((struct patch *) -1)
static int to_be_deleted(struct patch *patch)
@@ -2913,152 +3169,351 @@ static void prepare_fn_table(struct patch *patch)
}
}
-static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
+static int checkout_target(struct index_state *istate,
+ struct cache_entry *ce, struct stat *st)
+{
+ struct checkout costate;
+
+ memset(&costate, 0, sizeof(costate));
+ costate.base_dir = "";
+ costate.refresh_cache = 1;
+ costate.istate = istate;
+ if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st))
+ return error(_("cannot checkout %s"), ce->name);
+ return 0;
+}
+
+static struct patch *previous_patch(struct patch *patch, int *gone)
+{
+ struct patch *previous;
+
+ *gone = 0;
+ if (patch->is_copy || patch->is_rename)
+ return NULL; /* "git" patches do not depend on the order */
+
+ previous = in_fn_table(patch->old_name);
+ if (!previous)
+ return NULL;
+
+ if (to_be_deleted(previous))
+ return NULL; /* the deletion hasn't happened yet */
+
+ if (was_deleted(previous))
+ *gone = 1;
+
+ return previous;
+}
+
+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))
+ return -1;
+ return 0;
+ }
+ return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+}
+
+#define SUBMODULE_PATCH_WITHOUT_INDEX 1
+
+static int load_patch_target(struct strbuf *buf,
+ const struct cache_entry *ce,
+ struct stat *st,
+ const char *name,
+ unsigned expected_mode)
+{
+ if (cached || check_index) {
+ if (read_file_or_gitlink(ce, buf))
+ return error(_("read of %s failed"), name);
+ } else if (name) {
+ if (S_ISGITLINK(expected_mode)) {
+ if (ce)
+ return read_file_or_gitlink(ce, buf);
+ else
+ return SUBMODULE_PATCH_WITHOUT_INDEX;
+ } else if (has_symlink_leading_path(name, strlen(name))) {
+ return error(_("reading from '%s' beyond a symbolic link"), name);
+ } else {
+ if (read_old_data(st, name, buf))
+ return error(_("read of %s failed"), name);
+ }
+ }
+ return 0;
+}
+
+/*
+ * We are about to apply "patch"; populate the "image" with the
+ * current version we have, from the working tree or from the index,
+ * depending on the situation e.g. --cached/--index. If we are
+ * applying a non-git patch that incrementally updates the tree,
+ * we read from the result of a previous diff.
+ */
+static int load_preimage(struct image *image,
+ struct patch *patch, struct stat *st,
+ const struct cache_entry *ce)
{
struct strbuf buf = STRBUF_INIT;
- struct image image;
size_t len;
char *img;
- struct patch *tpatch;
+ struct patch *previous;
+ int status;
- if (!(patch->is_copy || patch->is_rename) &&
- (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
- if (was_deleted(tpatch)) {
- return error("patch %s has been renamed/deleted",
- patch->old_name);
- }
- /* We have a patched copy in memory use that */
- strbuf_add(&buf, tpatch->result, tpatch->resultsize);
- } else if (cached) {
- if (read_file_or_gitlink(ce, &buf))
- return error("read of %s failed", patch->old_name);
- } else if (patch->old_name) {
- if (S_ISGITLINK(patch->old_mode)) {
- if (ce) {
- read_file_or_gitlink(ce, &buf);
- } else {
- /*
- * There is no way to apply subproject
- * patch without looking at the index.
- */
- patch->fragments = NULL;
- }
- } else {
- if (read_old_data(st, patch->old_name, &buf))
- return error("read of %s failed", patch->old_name);
+ previous = previous_patch(patch, &status);
+ if (status)
+ return error(_("path %s has been renamed/deleted"),
+ patch->old_name);
+ if (previous) {
+ /* We have a patched copy in memory; use that. */
+ strbuf_add(&buf, previous->result, previous->resultsize);
+ } else {
+ status = load_patch_target(&buf, ce, st,
+ patch->old_name, patch->old_mode);
+ if (status < 0)
+ return status;
+ else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) {
+ /*
+ * There is no way to apply subproject
+ * patch without looking at the index.
+ * NEEDSWORK: shouldn't this be flagged
+ * as an error???
+ */
+ free_fragment_list(patch->fragments);
+ patch->fragments = NULL;
+ } else if (status) {
+ return error(_("read of %s failed"), patch->old_name);
}
}
img = strbuf_detach(&buf, &len);
- prepare_image(&image, img, len, !patch->is_binary);
+ prepare_image(image, img, len, !patch->is_binary);
+ return 0;
+}
- if (apply_fragments(&image, patch) < 0)
- return -1; /* note with --reject this succeeds. */
- patch->result = image.buf;
- patch->resultsize = image.len;
- add_to_fn_table(patch);
- free(image.line_allocated);
+static int three_way_merge(struct image *image,
+ char *path,
+ const unsigned char *base,
+ const unsigned char *ours,
+ const unsigned char *theirs)
+{
+ mmfile_t base_file, our_file, their_file;
+ mmbuffer_t result = { NULL };
+ int status;
- if (0 < patch->is_delete && patch->resultsize)
- return error("removal patch leaves file contents");
+ read_mmblob(&base_file, base);
+ read_mmblob(&our_file, ours);
+ read_mmblob(&their_file, theirs);
+ status = ll_merge(&result, path,
+ &base_file, "base",
+ &our_file, "ours",
+ &their_file, "theirs", NULL);
+ free(base_file.ptr);
+ free(our_file.ptr);
+ free(their_file.ptr);
+ if (status < 0 || !result.ptr) {
+ free(result.ptr);
+ return -1;
+ }
+ clear_image(image);
+ image->buf = result.ptr;
+ image->len = result.size;
+ return status;
+}
+
+/*
+ * When directly falling back to add/add three-way merge, we read from
+ * the current contents of the new_name. In no cases other than that
+ * this function will be called.
+ */
+static int load_current(struct image *image, struct patch *patch)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int status, pos;
+ size_t len;
+ char *img;
+ struct stat st;
+ struct cache_entry *ce;
+ char *name = patch->new_name;
+ unsigned mode = patch->new_mode;
+
+ if (!patch->is_new)
+ die("BUG: patch to %s is not a creation", patch->old_name);
+
+ pos = cache_name_pos(name, strlen(name));
+ if (pos < 0)
+ return error(_("%s: does not exist in index"), name);
+ ce = active_cache[pos];
+ if (lstat(name, &st)) {
+ if (errno != ENOENT)
+ return error(_("%s: %s"), name, strerror(errno));
+ if (checkout_target(&the_index, ce, &st))
+ return -1;
+ }
+ if (verify_index_match(ce, &st))
+ return error(_("%s: does not match index"), name);
+
+ status = load_patch_target(&buf, ce, &st, name, mode);
+ if (status < 0)
+ return status;
+ else if (status)
+ return -1;
+ img = strbuf_detach(&buf, &len);
+ prepare_image(image, img, len, !patch->is_binary);
return 0;
}
-static int check_to_create_blob(const char *new_name, int ok_if_exists)
+static int try_threeway(struct image *image, struct patch *patch,
+ struct stat *st, const struct cache_entry *ce)
{
- struct stat nst;
- if (!lstat(new_name, &nst)) {
- if (S_ISDIR(nst.st_mode) || ok_if_exists)
- return 0;
- /*
- * A leading component of new_name might be a symlink
- * that is going to be removed with this patch, but
- * still pointing at somewhere that has the path.
- * In such a case, path "new_name" does not exist as
- * far as git is concerned.
- */
- if (has_symlink_leading_path(new_name, strlen(new_name)))
- return 0;
+ unsigned char pre_sha1[20], post_sha1[20], our_sha1[20];
+ struct strbuf buf = STRBUF_INIT;
+ size_t len;
+ int status;
+ char *img;
+ struct image tmp_image;
+
+ /* No point falling back to 3-way merge in these cases */
+ if (patch->is_delete ||
+ S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode))
+ return -1;
+
+ /* Preimage the patch was prepared for */
+ if (patch->is_new)
+ write_sha1_file("", 0, blob_type, pre_sha1);
+ else if (get_sha1(patch->old_sha1_prefix, pre_sha1) ||
+ read_blob_object(&buf, pre_sha1, patch->old_mode))
+ return error("repository lacks the necessary blob to fall back on 3-way merge.");
+
+ fprintf(stderr, "Falling back to three-way merge...\n");
+
+ img = strbuf_detach(&buf, &len);
+ prepare_image(&tmp_image, img, len, 1);
+ /* Apply the patch to get the post image */
+ if (apply_fragments(&tmp_image, patch) < 0) {
+ clear_image(&tmp_image);
+ return -1;
+ }
+ /* post_sha1[] is theirs */
+ write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_sha1);
+ clear_image(&tmp_image);
+
+ /* our_sha1[] is ours */
+ if (patch->is_new) {
+ if (load_current(&tmp_image, patch))
+ return error("cannot read the current contents of '%s'",
+ patch->new_name);
+ } else {
+ if (load_preimage(&tmp_image, patch, st, ce))
+ return error("cannot read the current contents of '%s'",
+ patch->old_name);
+ }
+ write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_sha1);
+ clear_image(&tmp_image);
+
+ /* in-core three-way merge between post and our using pre as base */
+ status = three_way_merge(image, patch->new_name,
+ pre_sha1, our_sha1, post_sha1);
+ if (status < 0) {
+ fprintf(stderr, "Failed to fall back on three-way merge...\n");
+ return status;
+ }
- return error("%s: already exists in working directory", new_name);
+ if (status) {
+ patch->conflicted_threeway = 1;
+ if (patch->is_new)
+ hashclr(patch->threeway_stage[0]);
+ else
+ hashcpy(patch->threeway_stage[0], pre_sha1);
+ hashcpy(patch->threeway_stage[1], our_sha1);
+ hashcpy(patch->threeway_stage[2], post_sha1);
+ fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name);
+ } else {
+ fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name);
}
- else if ((errno != ENOENT) && (errno != ENOTDIR))
- return error("%s: %s", new_name, strerror(errno));
return 0;
}
-static int verify_index_match(struct cache_entry *ce, struct stat *st)
+static int apply_data(struct patch *patch, struct stat *st, const struct cache_entry *ce)
{
- if (S_ISGITLINK(ce->ce_mode)) {
- if (!S_ISDIR(st->st_mode))
+ struct image image;
+
+ if (load_preimage(&image, patch, st, ce) < 0)
+ return -1;
+
+ if (patch->direct_to_threeway ||
+ apply_fragments(&image, patch) < 0) {
+ /* Note: with --reject, apply_fragments() returns 0 */
+ if (!threeway || try_threeway(&image, patch, st, ce) < 0)
return -1;
- return 0;
}
- return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
+ patch->result = image.buf;
+ patch->resultsize = image.len;
+ add_to_fn_table(patch);
+ free(image.line_allocated);
+
+ if (0 < patch->is_delete && patch->resultsize)
+ return error(_("removal patch leaves file contents"));
+
+ return 0;
}
+/*
+ * If "patch" that we are looking at modifies or deletes what we have,
+ * we would want it not to lose any local modification we have, either
+ * in the working tree or in the index.
+ *
+ * This also decides if a non-git patch is a creation patch or a
+ * modification to an existing empty file. We do not check the state
+ * of the current tree for a creation patch in this function; the caller
+ * check_patch() separately makes sure (and errors out otherwise) that
+ * the path the patch creates does not exist in the current tree.
+ */
static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
{
const char *old_name = patch->old_name;
- struct patch *tpatch = NULL;
- int stat_ret = 0;
+ struct patch *previous = NULL;
+ int stat_ret = 0, status;
unsigned st_mode = 0;
- /*
- * Make sure that we do not have local modifications from the
- * index when we are looking at the index. Also make sure
- * we have the preimage file to be patched in the work tree,
- * unless --cached, which tells git to apply only in the index.
- */
if (!old_name)
return 0;
assert(patch->is_new <= 0);
+ previous = previous_patch(patch, &status);
- if (!(patch->is_copy || patch->is_rename) &&
- (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
- if (was_deleted(tpatch))
- return error("%s: has been deleted/renamed", old_name);
- st_mode = tpatch->new_mode;
+ if (status)
+ return error(_("path %s has been renamed/deleted"), old_name);
+ if (previous) {
+ st_mode = previous->new_mode;
} else if (!cached) {
stat_ret = lstat(old_name, st);
if (stat_ret && errno != ENOENT)
- return error("%s: %s", old_name, strerror(errno));
+ return error(_("%s: %s"), old_name, strerror(errno));
}
- if (to_be_deleted(tpatch))
- tpatch = NULL;
-
- if (check_index && !tpatch) {
+ if (check_index && !previous) {
int pos = cache_name_pos(old_name, strlen(old_name));
if (pos < 0) {
if (patch->is_new < 0)
goto is_new;
- return error("%s: does not exist in index", old_name);
+ return error(_("%s: does not exist in index"), old_name);
}
*ce = active_cache[pos];
if (stat_ret < 0) {
- struct checkout costate;
- /* checkout */
- memset(&costate, 0, sizeof(costate));
- costate.base_dir = "";
- costate.refresh_cache = 1;
- if (checkout_entry(*ce, &costate, NULL) ||
- lstat(old_name, st))
+ if (checkout_target(&the_index, *ce, st))
return -1;
}
if (!cached && verify_index_match(*ce, st))
- return error("%s: does not match index", old_name);
+ return error(_("%s: does not match index"), old_name);
if (cached)
st_mode = (*ce)->ce_mode;
} else if (stat_ret < 0) {
if (patch->is_new < 0)
goto is_new;
- return error("%s: %s", old_name, strerror(errno));
+ return error(_("%s: %s"), old_name, strerror(errno));
}
- if (!cached && !tpatch)
+ if (!cached && !previous)
st_mode = ce_mode_from_stat(*ce, st->st_mode);
if (patch->is_new < 0)
@@ -3066,9 +3521,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
if (!patch->old_mode)
patch->old_mode = st_mode;
if ((st_mode ^ patch->old_mode) & S_IFMT)
- return error("%s: wrong type", old_name);
+ return error(_("%s: wrong type"), old_name);
if (st_mode != patch->old_mode)
- warning("%s has type %o, expected %o",
+ warning(_("%s has type %o, expected %o"),
old_name, st_mode, patch->old_mode);
if (!patch->new_mode && !patch->is_delete)
patch->new_mode = st_mode;
@@ -3077,10 +3532,165 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
is_new:
patch->is_new = 1;
patch->is_delete = 0;
+ free(patch->old_name);
patch->old_name = NULL;
return 0;
}
+
+#define EXISTS_IN_INDEX 1
+#define EXISTS_IN_WORKTREE 2
+
+static int check_to_create(const char *new_name, int ok_if_exists)
+{
+ struct stat nst;
+
+ if (check_index &&
+ cache_name_pos(new_name, strlen(new_name)) >= 0 &&
+ !ok_if_exists)
+ return EXISTS_IN_INDEX;
+ if (cached)
+ return 0;
+
+ if (!lstat(new_name, &nst)) {
+ if (S_ISDIR(nst.st_mode) || ok_if_exists)
+ return 0;
+ /*
+ * A leading component of new_name might be a symlink
+ * that is going to be removed with this patch, but
+ * still pointing at somewhere that has the path.
+ * In such a case, path "new_name" does not exist as
+ * far as git is concerned.
+ */
+ if (has_symlink_leading_path(new_name, strlen(new_name)))
+ return 0;
+
+ return EXISTS_IN_WORKTREE;
+ } else if ((errno != ENOENT) && (errno != ENOTDIR)) {
+ return error("%s: %s", new_name, strerror(errno));
+ }
+ return 0;
+}
+
+/*
+ * We need to keep track of how symlinks in the preimage are
+ * manipulated by the patches. A patch to add a/b/c where a/b
+ * is a symlink should not be allowed to affect the directory
+ * the symlink points at, but if the same patch removes a/b,
+ * it is perfectly fine, as the patch removes a/b to make room
+ * to create a directory a/b so that a/b/c can be created.
+ */
+static struct string_list symlink_changes;
+#define SYMLINK_GOES_AWAY 01
+#define SYMLINK_IN_RESULT 02
+
+static uintptr_t register_symlink_changes(const char *path, uintptr_t what)
+{
+ struct string_list_item *ent;
+
+ ent = string_list_lookup(&symlink_changes, path);
+ if (!ent) {
+ ent = string_list_insert(&symlink_changes, path);
+ ent->util = (void *)0;
+ }
+ ent->util = (void *)(what | ((uintptr_t)ent->util));
+ return (uintptr_t)ent->util;
+}
+
+static uintptr_t check_symlink_changes(const char *path)
+{
+ struct string_list_item *ent;
+
+ ent = string_list_lookup(&symlink_changes, path);
+ if (!ent)
+ return 0;
+ return (uintptr_t)ent->util;
+}
+
+static void prepare_symlink_changes(struct patch *patch)
+{
+ for ( ; patch; patch = patch->next) {
+ if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
+ (patch->is_rename || patch->is_delete))
+ /* the symlink at patch->old_name is removed */
+ register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY);
+
+ if (patch->new_name && S_ISLNK(patch->new_mode))
+ /* the symlink at patch->new_name is created or remains */
+ register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT);
+ }
+}
+
+static int path_is_beyond_symlink_1(struct strbuf *name)
+{
+ do {
+ unsigned int change;
+
+ while (--name->len && name->buf[name->len] != '/')
+ ; /* scan backwards */
+ if (!name->len)
+ break;
+ name->buf[name->len] = '\0';
+ change = check_symlink_changes(name->buf);
+ if (change & SYMLINK_IN_RESULT)
+ return 1;
+ if (change & SYMLINK_GOES_AWAY)
+ /*
+ * This cannot be "return 0", because we may
+ * see a new one created at a higher level.
+ */
+ continue;
+
+ /* otherwise, check the preimage */
+ if (check_index) {
+ struct cache_entry *ce;
+
+ ce = cache_file_exists(name->buf, name->len, ignore_case);
+ if (ce && S_ISLNK(ce->ce_mode))
+ return 1;
+ } else {
+ struct stat st;
+ if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode))
+ return 1;
+ }
+ } while (1);
+ return 0;
+}
+
+static int path_is_beyond_symlink(const char *name_)
+{
+ int ret;
+ struct strbuf name = STRBUF_INIT;
+
+ assert(*name_ != '\0');
+ strbuf_addstr(&name, name_);
+ ret = path_is_beyond_symlink_1(&name);
+ strbuf_release(&name);
+
+ return ret;
+}
+
+static void die_on_unsafe_path(struct patch *patch)
+{
+ const char *old_name = NULL;
+ const char *new_name = NULL;
+ if (patch->is_delete)
+ old_name = patch->old_name;
+ else if (!patch->is_new && !patch->is_copy)
+ old_name = patch->old_name;
+ if (!patch->is_delete)
+ new_name = patch->new_name;
+
+ if (old_name && !verify_path(old_name))
+ die(_("invalid path '%s'"), old_name);
+ if (new_name && !verify_path(new_name))
+ die(_("invalid path '%s'"), new_name);
+}
+
+/*
+ * Check and apply the patch in-core; leave the result in patch->result
+ * for the caller to write it out to the final destination.
+ */
static int check_patch(struct patch *patch)
{
struct stat st;
@@ -3099,31 +3709,45 @@ static int check_patch(struct patch *patch)
return status;
old_name = patch->old_name;
+ /*
+ * A type-change diff is always split into a patch to delete
+ * old, immediately followed by a patch to create new (see
+ * diff.c::run_diff()); in such a case it is Ok that the entry
+ * to be deleted by the previous patch is still in the working
+ * tree and in the index.
+ *
+ * A patch to swap-rename between A and B would first rename A
+ * to B and then rename B to A. While applying the first one,
+ * the presence of B should not stop A from getting renamed to
+ * B; ask to_be_deleted() about the later rename. Removal of
+ * B and rename from A to B is handled the same way by asking
+ * was_deleted().
+ */
if ((tpatch = in_fn_table(new_name)) &&
- (was_deleted(tpatch) || to_be_deleted(tpatch)))
- /*
- * A type-change diff is always split into a patch to
- * delete old, immediately followed by a patch to
- * create new (see diff.c::run_diff()); in such a case
- * it is Ok that the entry to be deleted by the
- * previous patch is still in the working tree and in
- * the index.
- */
+ (was_deleted(tpatch) || to_be_deleted(tpatch)))
ok_if_exists = 1;
else
ok_if_exists = 0;
if (new_name &&
- ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
- if (check_index &&
- cache_name_pos(new_name, strlen(new_name)) >= 0 &&
- !ok_if_exists)
- return error("%s: already exists in index", new_name);
- if (!cached) {
- int err = check_to_create_blob(new_name, ok_if_exists);
- if (err)
- return err;
+ ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) {
+ int err = check_to_create(new_name, ok_if_exists);
+
+ if (err && threeway) {
+ patch->direct_to_threeway = 1;
+ } else switch (err) {
+ case 0:
+ break; /* happy */
+ case EXISTS_IN_INDEX:
+ return error(_("%s: already exists in index"), new_name);
+ break;
+ case EXISTS_IN_WORKTREE:
+ return error(_("%s: already exists in working directory"),
+ new_name);
+ default:
+ return err;
}
+
if (!patch->new_mode) {
if (0 < patch->is_new)
patch->new_mode = S_IFREG | 0644;
@@ -3136,14 +3760,38 @@ static int check_patch(struct patch *patch)
int same = !strcmp(old_name, new_name);
if (!patch->new_mode)
patch->new_mode = patch->old_mode;
- if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
- return error("new mode (%o) of %s does not match old mode (%o)%s%s",
- patch->new_mode, new_name, patch->old_mode,
- same ? "" : " of ", same ? "" : old_name);
+ if ((patch->old_mode ^ patch->new_mode) & S_IFMT) {
+ if (same)
+ return error(_("new mode (%o) of %s does not "
+ "match old mode (%o)"),
+ patch->new_mode, new_name,
+ patch->old_mode);
+ else
+ return error(_("new mode (%o) of %s does not "
+ "match old mode (%o) of %s"),
+ patch->new_mode, new_name,
+ patch->old_mode, old_name);
+ }
}
+ if (!unsafe_paths)
+ die_on_unsafe_path(patch);
+
+ /*
+ * An attempt to read from or delete a path that is beyond a
+ * symbolic link will be prevented by load_patch_target() that
+ * is called at the beginning of apply_data() so we do not
+ * have to worry about a patch marked with "is_delete" bit
+ * here. We however need to make sure that the patch result
+ * is not deposited to a path that is beyond a symbolic link
+ * here.
+ */
+ if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
+ return error(_("affected file '%s' is beyond a symbolic link"),
+ patch->new_name);
+
if (apply_data(patch, &st, ce) < 0)
- return error("%s: patch does not apply", name);
+ return error(_("%s: patch does not apply"), name);
patch->rejected = 0;
return 0;
}
@@ -3152,11 +3800,12 @@ static int check_patch_list(struct patch *patch)
{
int err = 0;
+ prepare_symlink_changes(patch);
prepare_fn_table(patch);
while (patch) {
if (apply_verbosely)
say_patch_name(stderr,
- "Checking patch ", patch, "...\n");
+ _("Checking patch %s..."), patch);
err |= check_patch(patch);
patch = patch->next;
}
@@ -3177,18 +3826,51 @@ static int get_current_sha1(const char *path, unsigned char *sha1)
return 0;
}
+static int preimage_sha1_in_gitlink_patch(struct patch *p, unsigned char sha1[20])
+{
+ /*
+ * A usable gitlink patch has only one fragment (hunk) that looks like:
+ * @@ -1 +1 @@
+ * -Subproject commit <old sha1>
+ * +Subproject commit <new sha1>
+ * or
+ * @@ -1 +0,0 @@
+ * -Subproject commit <old sha1>
+ * for a removal patch.
+ */
+ struct fragment *hunk = p->fragments;
+ static const char heading[] = "-Subproject commit ";
+ char *preimage;
+
+ if (/* does the patch have only one hunk? */
+ hunk && !hunk->next &&
+ /* is its preimage one line? */
+ hunk->oldpos == 1 && hunk->oldlines == 1 &&
+ /* does preimage begin with the heading? */
+ (preimage = memchr(hunk->patch, '\n', hunk->size)) != NULL &&
+ 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? */
+ 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 */
+ return get_sha1_hex(p->old_sha1_prefix, sha1);
+}
+
/* Build an index that contains the just the files needed for a 3way merge */
static void build_fake_ancestor(struct patch *list, const char *filename)
{
struct patch *patch;
struct index_state result = { NULL };
- int fd;
+ static struct lock_file lock;
/* Once we start supporting the reverse patch, it may be
* worth showing the new sha1 prefix, but until then...
*/
for (patch = list; patch; patch = patch->next) {
- const unsigned char *sha1_ptr;
unsigned char sha1[20];
struct cache_entry *ce;
const char *name;
@@ -3196,28 +3878,33 @@ static void build_fake_ancestor(struct patch *list, const char *filename)
name = patch->old_name ? patch->old_name : patch->new_name;
if (0 < patch->is_new)
continue;
- else if (get_sha1(patch->old_sha1_prefix, sha1))
- /* git diff has no index line for mode/type changes */
- if (!patch->lines_added && !patch->lines_deleted) {
- if (get_current_sha1(patch->old_name, sha1))
- die("mode change for %s, which is not "
- "in current HEAD", name);
- sha1_ptr = sha1;
- } else
- die("sha1 information is lacking or useless "
- "(%s).", name);
- else
- sha1_ptr = sha1;
- ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
+ if (S_ISGITLINK(patch->old_mode)) {
+ if (!preimage_sha1_in_gitlink_patch(patch, sha1))
+ ; /* ok, the textual part looks sane */
+ else
+ die("sha1 information is lacking or useless for submodule %s",
+ name);
+ } else if (!get_sha1_blob(patch->old_sha1_prefix, sha1)) {
+ ; /* ok */
+ } else if (!patch->lines_added && !patch->lines_deleted) {
+ /* mode-only change: update the current */
+ if (get_current_sha1(patch->old_name, sha1))
+ die("mode change for %s, which is not "
+ "in current HEAD", name);
+ } else
+ die("sha1 information is lacking or useless "
+ "(%s).", name);
+
+ ce = make_cache_entry(patch->old_mode, sha1, name, 0, 0);
if (!ce)
- die("make_cache_entry failed for path '%s'", name);
+ die(_("make_cache_entry failed for path '%s'"), name);
if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
die ("Could not add %s to temporary index", name);
}
- fd = open(filename, O_WRONLY | O_CREAT, 0666);
- if (fd < 0 || write_index(&result, fd) || close(fd))
+ hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
+ if (write_locked_index(&result, &lock, COMMIT_LOCK))
die ("Could not write temporary index to %s", filename);
discard_index(&result);
@@ -3234,7 +3921,7 @@ static void stat_patch_list(struct patch *patch)
show_stats(patch);
}
- printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+ print_stat_summary(stdout, files, adds, dels);
}
static void numstat_patch_list(struct patch *patch)
@@ -3354,7 +4041,7 @@ static void remove_file(struct patch *patch, int rmdir_empty)
{
if (update_index) {
if (remove_file_from_cache(patch->old_name) < 0)
- die("unable to remove %s from index", patch->old_name);
+ die(_("unable to remove %s from index"), patch->old_name);
}
if (!cached) {
if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
@@ -3376,24 +4063,26 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
ce = xcalloc(1, ce_size);
memcpy(ce->name, path, namelen);
ce->ce_mode = create_ce_mode(mode);
- ce->ce_flags = namelen;
+ ce->ce_flags = create_ce_flags(0);
+ ce->ce_namelen = namelen;
if (S_ISGITLINK(mode)) {
- const char *s = buf;
+ const char *s;
- if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
- die("corrupt patch for subproject %s", path);
+ if (!skip_prefix(buf, "Subproject commit ", &s) ||
+ get_sha1_hex(s, ce->sha1))
+ die(_("corrupt patch for submodule %s"), path);
} else {
if (!cached) {
if (lstat(path, &st) < 0)
- die_errno("unable to stat newly created file '%s'",
+ die_errno(_("unable to stat newly created file '%s'"),
path);
fill_stat_cache_info(ce, &st);
}
if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
- die("unable to create backing store for newly created file %s", path);
+ die(_("unable to create backing store for newly created file %s"), path);
}
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
- die("unable to add cache entry for %s", path);
+ die(_("unable to add cache entry for %s"), path);
}
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
@@ -3426,7 +4115,7 @@ static int try_create_file(const char *path, unsigned int mode, const char *buf,
strbuf_release(&nbuf);
if (close(fd) < 0)
- die_errno("closing file '%s'", path);
+ die_errno(_("closing file '%s'"), path);
return 0;
}
@@ -3475,7 +4164,34 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
++nr;
}
}
- die_errno("unable to write file '%s' mode %o", path, mode);
+ die_errno(_("unable to write file '%s' mode %o"), path, mode);
+}
+
+static void add_conflicted_stages_file(struct patch *patch)
+{
+ int stage, namelen;
+ unsigned ce_size, mode;
+ struct cache_entry *ce;
+
+ if (!update_index)
+ return;
+ namelen = strlen(patch->new_name);
+ ce_size = cache_entry_size(namelen);
+ mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644);
+
+ remove_file_from_cache(patch->new_name);
+ for (stage = 1; stage < 4; stage++) {
+ if (is_null_sha1(patch->threeway_stage[stage - 1]))
+ continue;
+ ce = xcalloc(1, ce_size);
+ memcpy(ce->name, patch->new_name, namelen);
+ ce->ce_mode = create_ce_mode(mode);
+ ce->ce_flags = create_ce_flags(stage);
+ ce->ce_namelen = namelen;
+ hashcpy(ce->sha1, patch->threeway_stage[stage - 1]);
+ if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
+ die(_("unable to add cache entry for %s"), patch->new_name);
+ }
}
static void create_file(struct patch *patch)
@@ -3488,7 +4204,11 @@ static void create_file(struct patch *patch)
if (!mode)
mode = S_IFREG | 0644;
create_one_file(path, mode, buf, size);
- add_index_file(path, mode, buf, size);
+
+ if (patch->conflicted_threeway)
+ add_conflicted_stages_file(patch);
+ else
+ add_index_file(path, mode, buf, size);
}
/* phase zero is to remove, phase one is to create */
@@ -3520,6 +4240,7 @@ static int write_out_one_reject(struct patch *patch)
char namebuf[PATH_MAX];
struct fragment *frag;
int cnt = 0;
+ struct strbuf sb = STRBUF_INIT;
for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
if (!frag->rejected)
@@ -3530,7 +4251,7 @@ static int write_out_one_reject(struct patch *patch)
if (!cnt) {
if (apply_verbosely)
say_patch_name(stderr,
- "Applied patch ", patch, " cleanly.\n");
+ _("Applied patch %s cleanly."), patch);
return 0;
}
@@ -3538,16 +4259,20 @@ static int write_out_one_reject(struct patch *patch)
* contents are marked "rejected" at the patch level.
*/
if (!patch->new_name)
- die("internal error");
+ die(_("internal error"));
/* Say this even without --verbose */
- say_patch_name(stderr, "Applying patch ", patch, " with");
- fprintf(stderr, " %d rejects...\n", cnt);
+ strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...",
+ "Applying patch %%s with %d rejects...",
+ cnt),
+ cnt);
+ say_patch_name(stderr, sb.buf, patch);
+ strbuf_release(&sb);
cnt = strlen(patch->new_name);
if (ARRAY_SIZE(namebuf) <= cnt + 5) {
cnt = ARRAY_SIZE(namebuf) - 5;
- warning("truncating .rej filename to %.*s.rej",
+ warning(_("truncating .rej filename to %.*s.rej"),
cnt - 1, patch->new_name);
}
memcpy(namebuf, patch->new_name, cnt);
@@ -3555,10 +4280,10 @@ static int write_out_one_reject(struct patch *patch)
rej = fopen(namebuf, "w");
if (!rej)
- return error("cannot open %s: %s", namebuf, strerror(errno));
+ return error(_("cannot open %s: %s"), namebuf, strerror(errno));
/* Normal git tools never deal with .rej, so do not pretend
- * this is a git patch by saying --git nor give extended
+ * this is a git patch by saying --git or giving extended
* headers. While at it, maybe please "kompare" that wants
* the trailing TAB and some garbage at the end of line ;-).
*/
@@ -3568,10 +4293,10 @@ static int write_out_one_reject(struct patch *patch)
frag;
cnt++, frag = frag->next) {
if (!frag->rejected) {
- fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+ fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt);
continue;
}
- fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+ fprintf_ln(stderr, _("Rejected hunk #%d."), cnt);
fprintf(rej, "%.*s", frag->size, frag->patch);
if (frag->patch[frag->size-1] != '\n')
fputc('\n', rej);
@@ -3580,14 +4305,12 @@ static int write_out_one_reject(struct patch *patch)
return -1;
}
-static int write_out_results(struct patch *list, int skipped_patch)
+static int write_out_results(struct patch *list)
{
int phase;
int errs = 0;
struct patch *l;
-
- if (!list && !skipped_patch)
- return error("No changes");
+ struct string_list cpath = STRING_LIST_INIT_DUP;
for (phase = 0; phase < 2; phase++) {
l = list;
@@ -3596,81 +4319,34 @@ static int write_out_results(struct patch *list, int skipped_patch)
errs = 1;
else {
write_out_one_result(l, phase);
- if (phase == 1 && write_out_one_reject(l))
- errs = 1;
+ if (phase == 1) {
+ if (write_out_one_reject(l))
+ errs = 1;
+ if (l->conflicted_threeway) {
+ string_list_append(&cpath, l->new_name);
+ errs = 1;
+ }
+ }
}
l = l->next;
}
}
- return errs;
-}
-
-static struct lock_file lock_file;
-
-static struct string_list limit_by_name;
-static int has_include;
-static void add_name_limit(const char *name, int exclude)
-{
- struct string_list_item *it;
- it = string_list_append(&limit_by_name, name);
- it->util = exclude ? NULL : (void *) 1;
-}
-
-static int use_patch(struct patch *p)
-{
- const char *pathname = p->new_name ? p->new_name : p->old_name;
- int i;
+ if (cpath.nr) {
+ struct string_list_item *item;
- /* Paths outside are not touched regardless of "--include" */
- if (0 < prefix_length) {
- int pathlen = strlen(pathname);
- if (pathlen <= prefix_length ||
- memcmp(prefix, pathname, prefix_length))
- return 0;
- }
+ string_list_sort(&cpath);
+ for_each_string_list_item(item, &cpath)
+ fprintf(stderr, "U %s\n", item->string);
+ string_list_clear(&cpath, 0);
- /* See if it matches any of exclude/include rule */
- for (i = 0; i < limit_by_name.nr; i++) {
- struct string_list_item *it = &limit_by_name.items[i];
- if (!fnmatch(it->string, pathname, 0))
- return (it->util != NULL);
+ rerere(0);
}
- /*
- * If we had any include, a path that does not match any rule is
- * not used. Otherwise, we saw bunch of exclude rules (or none)
- * and such a path is used.
- */
- return !has_include;
-}
-
-
-static void prefix_one(char **name)
-{
- char *old_name = *name;
- if (!old_name)
- return;
- *name = xstrdup(prefix_filename(prefix, prefix_length, *name));
- free(old_name);
+ return errs;
}
-static void prefix_patches(struct patch *p)
-{
- if (!prefix || p->is_toplevel_relative)
- return;
- for ( ; p; p = p->next) {
- if (p->new_name == p->old_name) {
- char *prefixed = p->new_name;
- prefix_one(&prefixed);
- p->new_name = p->old_name = prefixed;
- }
- else {
- prefix_one(&p->new_name);
- prefix_one(&p->old_name);
- }
- }
-}
+static struct lock_file lock_file;
#define INACCURATE_EOF (1<<0)
#define RECOUNT (1<<1)
@@ -3678,12 +4354,10 @@ static void prefix_patches(struct patch *p)
static int apply_patch(int fd, const char *filename, int options)
{
size_t offset;
- struct strbuf buf = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT; /* owns the patch text */
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
- /* FIXME - memory leak when using multiple patch files as inputs */
- memset(&fn_table, 0, sizeof(struct string_list));
patch_input_file = filename;
read_patch_file(&buf, fd);
offset = 0;
@@ -3699,21 +4373,21 @@ static int apply_patch(int fd, const char *filename, int options)
break;
if (apply_in_reverse)
reverse_patches(patch);
- if (prefix)
- prefix_patches(patch);
if (use_patch(patch)) {
patch_stats(patch);
*listp = patch;
listp = &patch->next;
}
else {
- /* perhaps free it a bit better? */
- free(patch);
+ free_patch(patch);
skipped_patch++;
}
offset += nr;
}
+ if (!list && !skipped_patch)
+ die(_("unrecognized input"));
+
if (whitespace_error && (ws_error_action == die_on_ws_error))
apply = 0;
@@ -3723,7 +4397,7 @@ static int apply_patch(int fd, const char *filename, int options)
if (check_index) {
if (read_cache() < 0)
- die("unable to read index file");
+ die(_("unable to read index file"));
}
if ((check || apply) &&
@@ -3731,8 +4405,12 @@ static int apply_patch(int fd, const char *filename, int options)
!apply_with_reject)
exit(1);
- if (apply && write_out_results(list, skipped_patch))
- exit(1);
+ if (apply && write_out_results(list)) {
+ if (apply_with_reject)
+ exit(1);
+ /* with --3way, we still need to write the index out */
+ return 1;
+ }
if (fake_ancestor)
build_fake_ancestor(list, fake_ancestor);
@@ -3746,17 +4424,17 @@ static int apply_patch(int fd, const char *filename, int options)
if (summary)
summary_patch_list(list);
+ free_patch_list(list);
strbuf_release(&buf);
+ string_list_clear(&fn_table, 0);
return 0;
}
-static int git_apply_config(const char *var, const char *value, void *cb)
+static void git_apply_config(void)
{
- if (!strcmp(var, "apply.whitespace"))
- return git_config_string(&apply_default_whitespace, var, value);
- else if (!strcmp(var, "apply.ignorewhitespace"))
- return git_config_string(&apply_default_ignorewhitespace, var, value);
- return git_default_config(var, value, cb);
+ git_config_get_string_const("apply.whitespace", &apply_default_whitespace);
+ git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace);
+ git_config(git_default_config, NULL);
}
static int option_parse_exclude(const struct option *opt,
@@ -3831,83 +4509,82 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
int i;
int errs = 0;
int is_not_gitdir = !startup_info->have_repository;
- int binary;
int force_apply = 0;
const char *whitespace_option = NULL;
struct option builtin_apply_options[] = {
- { OPTION_CALLBACK, 0, "exclude", NULL, "path",
- "don't apply changes matching the given path",
+ { OPTION_CALLBACK, 0, "exclude", NULL, N_("path"),
+ N_("don't apply changes matching the given path"),
0, option_parse_exclude },
- { OPTION_CALLBACK, 0, "include", NULL, "path",
- "apply changes matching the given path",
+ { OPTION_CALLBACK, 0, "include", NULL, N_("path"),
+ N_("apply changes matching the given path"),
0, option_parse_include },
- { OPTION_CALLBACK, 'p', NULL, NULL, "num",
- "remove <num> leading slashes from traditional diff paths",
+ { 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,
- "ignore additions made by the patch"),
- OPT_BOOLEAN(0, "stat", &diffstat,
- "instead of applying the patch, output diffstat for the input"),
- { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
- NULL, "old option, now no-op",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
- { OPTION_BOOLEAN, 0, "binary", &binary,
- NULL, "old option, now no-op",
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
- OPT_BOOLEAN(0, "numstat", &numstat,
- "shows number of added and deleted lines in decimal notation"),
- OPT_BOOLEAN(0, "summary", &summary,
- "instead of applying the patch, output a summary for the input"),
- OPT_BOOLEAN(0, "check", &check,
- "instead of applying the patch, see if the patch is applicable"),
- OPT_BOOLEAN(0, "index", &check_index,
- "make sure the patch is applicable to the current index"),
- OPT_BOOLEAN(0, "cached", &cached,
- "apply a patch without touching the working tree"),
- OPT_BOOLEAN(0, "apply", &force_apply,
- "also apply the patch (use with --stat/--summary/--check)"),
+ OPT_BOOL(0, "no-add", &no_add,
+ N_("ignore additions made by the patch")),
+ 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_BOOL(0, "numstat", &numstat,
+ N_("show number of added and deleted lines in decimal notation")),
+ OPT_BOOL(0, "summary", &summary,
+ N_("instead of applying the patch, output a summary for the input")),
+ OPT_BOOL(0, "check", &check,
+ N_("instead of applying the patch, see if the patch is applicable")),
+ OPT_BOOL(0, "index", &check_index,
+ N_("make sure the patch is applicable to the current index")),
+ OPT_BOOL(0, "cached", &cached,
+ N_("apply a patch without touching the working tree")),
+ OPT_BOOL(0, "unsafe-paths", &unsafe_paths,
+ N_("accept a patch that touches outside the working area")),
+ 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")),
OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
- "build a temporary index based on embedded index information"),
+ N_("build a temporary index based on embedded index information")),
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- "paths are separated with NUL character",
+ N_("paths are separated with NUL character"),
PARSE_OPT_NOARG, option_parse_z },
OPT_INTEGER('C', NULL, &p_context,
- "ensure at least <n> lines of context match"),
- { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
- "detect new or modified lines that have whitespace errors",
+ N_("ensure at least <n> lines of context match")),
+ { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, N_("action"),
+ N_("detect new or modified lines that have whitespace errors"),
0, option_parse_whitespace },
{ OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
- "ignore changes in whitespace when finding context",
+ N_("ignore changes in whitespace when finding context"),
PARSE_OPT_NOARG, option_parse_space_change },
{ OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
- "ignore changes in whitespace when finding context",
+ N_("ignore changes in whitespace when finding context"),
PARSE_OPT_NOARG, option_parse_space_change },
- OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
- "apply the patch in reverse"),
- OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
- "don't expect at least one line of context"),
- OPT_BOOLEAN(0, "reject", &apply_with_reject,
- "leave the rejected hunks in corresponding *.rej files"),
- OPT_BOOLEAN(0, "allow-overlap", &allow_overlap,
- "allow overlapping hunks"),
- OPT__VERBOSE(&apply_verbosely, "be verbose"),
+ OPT_BOOL('R', "reverse", &apply_in_reverse,
+ N_("apply the patch in reverse")),
+ OPT_BOOL(0, "unidiff-zero", &unidiff_zero,
+ N_("don't expect at least one line of context")),
+ OPT_BOOL(0, "reject", &apply_with_reject,
+ N_("leave the rejected hunks in corresponding *.rej files")),
+ OPT_BOOL(0, "allow-overlap", &allow_overlap,
+ N_("allow overlapping hunks")),
+ OPT__VERBOSE(&apply_verbosely, N_("be verbose")),
OPT_BIT(0, "inaccurate-eof", &options,
- "tolerate incorrectly detected missing new-line at the end of file",
+ N_("tolerate incorrectly detected missing new-line at the end of file"),
INACCURATE_EOF),
OPT_BIT(0, "recount", &options,
- "do not trust the line counts in the hunk headers",
+ N_("do not trust the line counts in the hunk headers"),
RECOUNT),
- { OPTION_CALLBACK, 0, "directory", NULL, "root",
- "prepend <root> to all filenames",
+ { OPTION_CALLBACK, 0, "directory", NULL, N_("root"),
+ N_("prepend <root> to all filenames"),
0, option_parse_directory },
OPT_END()
};
prefix = prefix_;
prefix_length = prefix ? strlen(prefix) : 0;
- git_config(git_apply_config, NULL);
+ git_apply_config();
if (apply_default_whitespace)
parse_whitespace_option(apply_default_whitespace);
if (apply_default_ignorewhitespace)
@@ -3916,17 +4593,29 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
argc = parse_options(argc, argv, prefix, builtin_apply_options,
apply_usage, 0);
+ if (apply_with_reject && threeway)
+ die("--reject and --3way cannot be used together.");
+ if (cached && threeway)
+ die("--cached and --3way cannot be used together.");
+ if (threeway) {
+ if (is_not_gitdir)
+ die(_("--3way outside a repository"));
+ check_index = 1;
+ }
if (apply_with_reject)
apply = apply_verbosely = 1;
if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
apply = 0;
if (check_index && is_not_gitdir)
- die("--index outside a repository");
+ die(_("--index outside a repository"));
if (cached) {
if (is_not_gitdir)
- die("--cached outside a repository");
+ die(_("--cached outside a repository"));
check_index = 1;
}
+ if (check_index)
+ unsafe_paths = 0;
+
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
int fd;
@@ -3940,7 +4629,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
fd = open(arg, O_RDONLY);
if (fd < 0)
- die_errno("can't open patch '%s'", arg);
+ die_errno(_("can't open patch '%s'"), arg);
read_stdin = 0;
set_default_whitespace_mode(whitespace_option);
errs |= apply_patch(fd, arg, options);
@@ -3954,32 +4643,31 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
squelch_whitespace_errors < whitespace_error) {
int squelched =
whitespace_error - squelch_whitespace_errors;
- warning("squelched %d "
- "whitespace error%s",
- squelched,
- squelched == 1 ? "" : "s");
+ warning(Q_("squelched %d whitespace error",
+ "squelched %d whitespace errors",
+ squelched),
+ squelched);
}
if (ws_error_action == die_on_ws_error)
- die("%d line%s add%s whitespace errors.",
- whitespace_error,
- whitespace_error == 1 ? "" : "s",
- whitespace_error == 1 ? "s" : "");
+ die(Q_("%d line adds whitespace errors.",
+ "%d lines add whitespace errors.",
+ whitespace_error),
+ whitespace_error);
if (applied_after_fixing_ws && apply)
warning("%d line%s applied after"
" fixing whitespace errors.",
applied_after_fixing_ws,
applied_after_fixing_ws == 1 ? "" : "s");
else if (whitespace_error)
- warning("%d line%s add%s whitespace errors.",
- whitespace_error,
- whitespace_error == 1 ? "" : "s",
- whitespace_error == 1 ? "s" : "");
+ warning(Q_("%d line adds whitespace errors.",
+ "%d lines add whitespace errors.",
+ whitespace_error),
+ whitespace_error);
}
if (update_index) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
- die("Unable to write new index file");
+ if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+ die(_("Unable to write new index file"));
}
return !!errs;
diff --git a/builtin/archive.c b/builtin/archive.c
index 883c009..a1e3b94 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -27,8 +27,8 @@ static int run_remote_archiver(int argc, const char **argv,
const char *remote, const char *exec,
const char *name_hint)
{
- char buf[LARGE_PACKET_MAX];
- int fd[2], i, len, rv;
+ char *buf;
+ int fd[2], i, rv;
struct transport *transport;
struct remote *_remote;
@@ -53,19 +53,18 @@ static int run_remote_archiver(int argc, const char **argv,
packet_write(fd[1], "argument %s\n", argv[i]);
packet_flush(fd[1]);
- len = packet_read_line(fd[0], buf, sizeof(buf));
- if (!len)
+ buf = packet_read_line(fd[0], NULL);
+ if (!buf)
die(_("git archive: expected ACK/NAK, got EOF"));
- if (buf[len-1] == '\n')
- buf[--len] = 0;
if (strcmp(buf, "ACK")) {
- if (len > 5 && !prefixcmp(buf, "NACK "))
+ if (starts_with(buf, "NACK "))
die(_("git archive: NACK %s"), buf + 5);
+ if (starts_with(buf, "ERR "))
+ die(_("remote error: %s"), buf + 4);
die(_("git archive: protocol error"));
}
- len = packet_read_line(fd[0], buf, sizeof(buf));
- if (len)
+ if (packet_read_line(fd[0], NULL))
die(_("git archive: expected a flush"));
/* Now, start reading from fd[0] and spit it out to stdout */
@@ -86,12 +85,12 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
const char *output = NULL;
const char *remote = NULL;
struct option local_opts[] = {
- OPT_STRING('o', "output", &output, "file",
- "write the archive to this file"),
- OPT_STRING(0, "remote", &remote, "repo",
- "retrieve the archive from remote repository <repo>"),
- OPT_STRING(0, "exec", &exec, "cmd",
- "path to the remote git-upload-archive command"),
+ OPT_STRING('o', "output", &output, N_("file"),
+ N_("write the archive to this file")),
+ OPT_STRING(0, "remote", &remote, N_("repo"),
+ N_("retrieve the archive from remote repository <repo>")),
+ OPT_STRING(0, "exec", &exec, N_("command"),
+ N_("path to the remote git-upload-archive command")),
OPT_END()
};
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c
index 8d325a5..3324229 100644
--- a/builtin/bisect--helper.c
+++ b/builtin/bisect--helper.c
@@ -4,7 +4,7 @@
#include "bisect.h"
static const char * const git_bisect_helper_usage[] = {
- "git bisect--helper --next-all [--no-checkout]",
+ N_("git bisect--helper --next-all [--no-checkout]"),
NULL
};
@@ -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,
- "perform 'git bisect next'"),
- OPT_BOOLEAN(0, "no-checkout", &no_checkout,
- "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 26a5d42..2b1f9dd 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -1,7 +1,8 @@
/*
* Blame
*
- * Copyright (c) 2006, Junio C Hamano
+ * Copyright (c) 2006, 2014 by its authors
+ * See COPYING for licensing conditions
*/
#include "cache.h"
@@ -18,16 +19,20 @@
#include "cache-tree.h"
#include "string-list.h"
#include "mailmap.h"
+#include "mergesort.h"
#include "parse-options.h"
+#include "prio-queue.h"
#include "utf8.h"
#include "userdiff.h"
+#include "line-range.h"
+#include "line-log.h"
-static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
+static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
static const char *blame_opt_usage[] = {
blame_usage,
"",
- "[rev-opts] are documented in git-rev-list(1)",
+ N_("[rev-opts] are documented in git-rev-list(1)"),
NULL
};
@@ -42,6 +47,7 @@ static int blank_boundary;
static int incremental;
static int xdl_opts;
static int abbrev = -1;
+static int no_whole_file_rename;
static enum date_mode blame_date_mode = DATE_ISO8601;
static size_t blame_date_width;
@@ -71,7 +77,7 @@ static unsigned blame_copy_score;
#define BLAME_DEFAULT_MOVE_SCORE 20
#define BLAME_DEFAULT_COPY_SCORE 40
-/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
+/* Remember to update object flag allocation in object.h */
#define METAINFO_SHOWN (1u<<12)
#define MORE_THAN_ONE_PATH (1u<<13)
@@ -80,14 +86,59 @@ static unsigned blame_copy_score;
*/
struct origin {
int refcnt;
+ /* Record preceding blame record for this blob */
struct origin *previous;
+ /* origins are put in a list linked via `next' hanging off the
+ * corresponding commit's util field in order to make finding
+ * them fast. The presence in this chain does not count
+ * towards the origin's reference count. It is tempting to
+ * let it count as long as the commit is pending examination,
+ * but even under circumstances where the commit will be
+ * present multiple times in the priority queue of unexamined
+ * commits, processing the first instance will not leave any
+ * work requiring the origin data for the second instance. An
+ * interspersed commit changing that would have to be
+ * preexisting with a different ancestry and with the same
+ * commit date in order to wedge itself between two instances
+ * of the same commit in the priority queue _and_ produce
+ * blame entries relevant for it. While we don't want to let
+ * us get tripped up by this case, it certainly does not seem
+ * worth optimizing for.
+ */
+ struct origin *next;
struct commit *commit;
+ /* `suspects' contains blame entries that may be attributed to
+ * this origin's commit or to parent commits. When a commit
+ * is being processed, all suspects will be moved, either by
+ * assigning them to an origin in a different commit, or by
+ * shipping them to the scoreboard's ent list because they
+ * cannot be attributed to a different commit.
+ */
+ struct blame_entry *suspects;
mmfile_t file;
unsigned char blob_sha1[20];
unsigned mode;
+ /* guilty gets set when shipping any suspects to the final
+ * blame list instead of other commits
+ */
+ char guilty;
char path[FLEX_ARRAY];
};
+static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen,
+ xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
+{
+ xpparam_t xpp = {0};
+ xdemitconf_t xecfg = {0};
+ xdemitcb_t ecb = {NULL};
+
+ xpp.flags = xdl_opts;
+ xecfg.ctxlen = ctxlen;
+ xecfg.hunk_func = hunk_func;
+ ecb.priv = cb_data;
+ return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
+}
+
/*
* Prepare diff_filespec and convert it using diff textconv API
* if the textconv driver exists.
@@ -96,6 +147,7 @@ struct origin {
int textconv_object(const char *path,
unsigned mode,
const unsigned char *sha1,
+ int sha1_valid,
char **buf,
unsigned long *buf_size)
{
@@ -103,7 +155,7 @@ int textconv_object(const char *path,
struct userdiff_driver *textconv;
df = alloc_filespec(path);
- fill_filespec(df, sha1, mode);
+ fill_filespec(df, sha1, sha1_valid, mode);
textconv = get_textconv(df);
if (!textconv) {
free_filespec(df);
@@ -128,7 +180,7 @@ static void fill_origin_blob(struct diff_options *opt,
num_read_blob++;
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
- textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size))
+ textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size))
;
else
file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
@@ -158,10 +210,22 @@ static inline struct origin *origin_incref(struct origin *o)
static void origin_decref(struct origin *o)
{
if (o && --o->refcnt <= 0) {
+ struct origin *p, *l = NULL;
if (o->previous)
origin_decref(o->previous);
free(o->file.ptr);
- free(o);
+ /* Should be present exactly once in commit chain */
+ for (p = o->commit->util; p; l = p, p = p->next) {
+ if (p == o) {
+ if (l)
+ l->next = p->next;
+ else
+ o->commit->util = p->next;
+ free(o);
+ return;
+ }
+ }
+ die("internal error in blame::origin_decref");
}
}
@@ -175,11 +239,14 @@ static void drop_origin_blob(struct origin *o)
/*
* Each group of lines is described by a blame_entry; it can be split
- * as we pass blame to the parents. They form a linked list in the
- * scoreboard structure, sorted by the target line number.
+ * as we pass blame to the parents. They are arranged in linked lists
+ * kept as `suspects' of some unprocessed origin, or entered (when the
+ * blame origin has been finalized) into the scoreboard structure.
+ * While the scoreboard structure is only sorted at the end of
+ * processing (according to final image line number), the lists
+ * attached to an origin are 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;
@@ -193,15 +260,6 @@ struct blame_entry {
/* the commit that introduced this group into the final image */
struct origin *suspect;
- /* true if the suspect is truly guilty; false while we have not
- * checked if the group came from one of its parents.
- */
- char guilty;
-
- /* true if the entry has been scanned for copies in the current parent
- */
- char scanned;
-
/* the line number of the first line of this group in the
* suspect's file; internally all line numbers are 0 based.
*/
@@ -214,11 +272,112 @@ struct blame_entry {
};
/*
+ * Any merge of blames happens on lists of blames that arrived via
+ * different parents in a single suspect. In this case, we want to
+ * sort according to the suspect line numbers as opposed to the final
+ * image line numbers. The function body is somewhat longish because
+ * it avoids unnecessary writes.
+ */
+
+static struct blame_entry *blame_merge(struct blame_entry *list1,
+ struct blame_entry *list2)
+{
+ struct blame_entry *p1 = list1, *p2 = list2,
+ **tail = &list1;
+
+ if (!p1)
+ return p2;
+ if (!p2)
+ return p1;
+
+ if (p1->s_lno <= p2->s_lno) {
+ do {
+ tail = &p1->next;
+ if ((p1 = *tail) == NULL) {
+ *tail = p2;
+ return list1;
+ }
+ } while (p1->s_lno <= p2->s_lno);
+ }
+ for (;;) {
+ *tail = p2;
+ do {
+ tail = &p2->next;
+ if ((p2 = *tail) == NULL) {
+ *tail = p1;
+ return list1;
+ }
+ } while (p1->s_lno > p2->s_lno);
+ *tail = p1;
+ do {
+ tail = &p1->next;
+ if ((p1 = *tail) == NULL) {
+ *tail = p2;
+ return list1;
+ }
+ } while (p1->s_lno <= p2->s_lno);
+ }
+}
+
+static void *get_next_blame(const void *p)
+{
+ return ((struct blame_entry *)p)->next;
+}
+
+static void set_next_blame(void *p1, void *p2)
+{
+ ((struct blame_entry *)p1)->next = p2;
+}
+
+/*
+ * Final image line numbers are all different, so we don't need a
+ * three-way comparison here.
+ */
+
+static int compare_blame_final(const void *p1, const void *p2)
+{
+ return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno
+ ? 1 : -1;
+}
+
+static int compare_blame_suspect(const void *p1, const void *p2)
+{
+ const struct blame_entry *s1 = p1, *s2 = p2;
+ /*
+ * to allow for collating suspects, we sort according to the
+ * respective pointer value as the primary sorting criterion.
+ * The actual relation is pretty unimportant as long as it
+ * establishes a total order. Comparing as integers gives us
+ * that.
+ */
+ if (s1->suspect != s2->suspect)
+ return (intptr_t)s1->suspect > (intptr_t)s2->suspect ? 1 : -1;
+ if (s1->s_lno == s2->s_lno)
+ return 0;
+ return s1->s_lno > s2->s_lno ? 1 : -1;
+}
+
+static struct blame_entry *blame_sort(struct blame_entry *head,
+ int (*compare_fn)(const void *, const void *))
+{
+ return llist_mergesort (head, get_next_blame, set_next_blame, compare_fn);
+}
+
+static int compare_commits_by_reverse_commit_date(const void *a,
+ const void *b,
+ void *c)
+{
+ return -compare_commits_by_commit_date(a, b, c);
+}
+
+/*
* The current state of the blame assignment.
*/
struct scoreboard {
/* the final commit (i.e. where we started digging from) */
struct commit *final;
+ /* Priority queue for commits with unassigned blame records */
+ struct prio_queue commits;
struct rev_info *revs;
const char *path;
@@ -238,15 +397,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 *);
/*
@@ -259,13 +409,10 @@ 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) &&
- ent->guilty == next->guilty &&
+ if (ent->suspect == next->suspect &&
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;
@@ -278,6 +425,30 @@ static void coalesce(struct scoreboard *sb)
}
/*
+ * Merge the given sorted list of blames into a preexisting origin.
+ * If there were no previous blames to that commit, it is entered into
+ * the commit priority queue of the score board.
+ */
+
+static void queue_blames(struct scoreboard *sb, struct origin *porigin,
+ struct blame_entry *sorted)
+{
+ if (porigin->suspects)
+ porigin->suspects = blame_merge(porigin->suspects, sorted);
+ else {
+ struct origin *o;
+ for (o = porigin->commit->util; o; o = o->next) {
+ if (o->suspects) {
+ porigin->suspects = sorted;
+ return;
+ }
+ }
+ porigin->suspects = sorted;
+ prio_queue_put(&sb->commits, porigin->commit);
+ }
+}
+
+/*
* Given a commit and a path in it, create a new origin structure.
* The callers that add blame to the scoreboard should use
* get_origin() to obtain shared, refcounted copy instead of calling
@@ -289,23 +460,32 @@ static struct origin *make_origin(struct commit *commit, const char *path)
o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
o->commit = commit;
o->refcnt = 1;
+ o->next = commit->util;
+ commit->util = o;
strcpy(o->path, path);
return o;
}
/*
* Locate an existing origin or create a new one.
+ * This moves the origin to front position in the commit util list.
*/
static struct origin *get_origin(struct scoreboard *sb,
struct commit *commit,
const char *path)
{
- struct blame_entry *e;
+ struct origin *o, *l;
- for (e = sb->ent; e; e = e->next) {
- if (e->suspect->commit == commit &&
- !strcmp(e->suspect->path, path))
- return origin_incref(e->suspect);
+ for (o = commit->util, l = NULL; o; l = o, o = o->next) {
+ if (!strcmp(o->path, path)) {
+ /* bump to front */
+ if (l) {
+ l->next = o->next;
+ o->next = commit->util;
+ commit->util = o;
+ }
+ return origin_incref(o);
+ }
}
return make_origin(commit, path);
}
@@ -344,41 +524,19 @@ static struct origin *find_origin(struct scoreboard *sb,
struct commit *parent,
struct origin *origin)
{
- struct origin *porigin = NULL;
+ struct origin *porigin;
struct diff_options diff_opts;
const char *paths[2];
- if (parent->util) {
- /*
- * Each commit object can cache one origin in that
- * commit. This is a freestanding copy of origin and
- * not refcounted.
- */
- struct origin *cached = parent->util;
- if (!strcmp(cached->path, origin->path)) {
+ /* First check any existing origins */
+ for (porigin = parent->util; porigin; porigin = porigin->next)
+ if (!strcmp(porigin->path, origin->path)) {
/*
* The same path between origin and its parent
* without renaming -- the most common case.
*/
- porigin = get_origin(sb, parent, cached->path);
-
- /*
- * If the origin was newly created (i.e. get_origin
- * would call make_origin if none is found in the
- * scoreboard), it does not know the blob_sha1/mode,
- * so copy it. Otherwise porigin was in the
- * scoreboard and already knows blob_sha1/mode.
- */
- if (porigin->refcnt == 1) {
- hashcpy(porigin->blob_sha1, cached->blob_sha1);
- porigin->mode = cached->mode;
- }
- return porigin;
+ return origin_incref (porigin);
}
- /* otherwise it was not very useful; free it */
- free(parent->util);
- parent->util = NULL;
- }
/* See if the origin->path is different between parent
* and origin first. Most of the time they are the
@@ -391,9 +549,10 @@ static struct origin *find_origin(struct scoreboard *sb,
paths[0] = origin->path;
paths[1] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ 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))
do_diff_cache(parent->tree->object.sha1, &diff_opts);
@@ -442,20 +601,7 @@ static struct origin *find_origin(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
- if (porigin) {
- /*
- * Create a freestanding copy that is not part of
- * the refcounted origin found in the scoreboard, and
- * cache it in the commit.
- */
- struct origin *cached;
-
- cached = make_origin(porigin->commit, porigin->path);
- hashcpy(cached->blob_sha1, porigin->blob_sha1);
- cached->mode = porigin->mode;
- parent->util = cached;
- }
+ free_pathspec(&diff_opts.pathspec);
return porigin;
}
@@ -470,17 +616,13 @@ 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);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
do_diff_cache(parent->tree->object.sha1, &diff_opts);
@@ -501,62 +643,48 @@ 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;
}
/*
- * Link in a new blame entry to the scoreboard. Entries that cover the
- * same line range have been removed from the scoreboard previously.
+ * Append a new blame entry to a given output queue.
*/
-static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
+static void add_blame_entry(struct blame_entry ***queue, struct blame_entry *e)
{
- struct blame_entry *ent, *prev = NULL;
-
origin_incref(e->suspect);
- for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next)
- 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;
- }
- else {
- e->next = sb->ent;
- sb->ent = e;
- }
- if (e->next)
- e->next->prev = e;
+ e->next = **queue;
+ **queue = e;
+ *queue = &e->next;
}
/*
* src typically is on-stack; we want to copy the information in it to
- * a malloced blame_entry that is already on the linked list of the
- * scoreboard. The origin of dst loses a refcnt while the origin of src
- * gains one.
+ * a malloced blame_entry that gets added to the given queue. The
+ * origin of dst loses a refcnt.
*/
-static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
+static void dup_entry(struct blame_entry ***queue,
+ struct blame_entry *dst, struct blame_entry *src)
{
- struct blame_entry *p, *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;
+ dst->next = **queue;
+ **queue = dst;
+ *queue = &dst->next;
}
-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
@@ -616,10 +744,11 @@ static void split_overlap(struct blame_entry *split,
/*
* split_overlap() divided an existing blame e into up to three parts
- * in split. Adjust the linked list of blames in the scoreboard to
+ * in split. Any assigned blame is moved to queue to
* reflect the split.
*/
-static void split_blame(struct scoreboard *sb,
+static void split_blame(struct blame_entry ***blamed,
+ struct blame_entry ***unblamed,
struct blame_entry *split,
struct blame_entry *e)
{
@@ -627,61 +756,39 @@ static void split_blame(struct scoreboard *sb,
if (split[0].suspect && split[2].suspect) {
/* The first part (reuse storage for the existing entry e) */
- dup_entry(e, &split[0]);
+ dup_entry(unblamed, e, &split[0]);
/* The last part -- me */
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
+ add_blame_entry(unblamed, new_entry);
/* ... and the middle part -- parent */
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
+ add_blame_entry(blamed, new_entry);
}
else if (!split[0].suspect && !split[2].suspect)
/*
* The parent covers the entire area; reuse storage for
* e and replace it with the parent.
*/
- dup_entry(e, &split[1]);
+ dup_entry(blamed, e, &split[1]);
else if (split[0].suspect) {
/* me and then parent */
- dup_entry(e, &split[0]);
+ dup_entry(unblamed, e, &split[0]);
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
+ add_blame_entry(blamed, new_entry);
}
else {
/* parent and then me */
- dup_entry(e, &split[1]);
+ dup_entry(blamed, e, &split[1]);
new_entry = xmalloc(sizeof(*new_entry));
memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
- add_blame_entry(sb, new_entry);
- }
-
- if (DEBUG) { /* sanity */
- struct blame_entry *ent;
- int lno = sb->ent->lno, corrupt = 0;
-
- for (ent = sb->ent; ent; ent = ent->next) {
- if (lno != ent->lno)
- corrupt = 1;
- if (ent->s_lno < 0)
- corrupt = 1;
- lno += ent->num_lines;
- }
- if (corrupt) {
- lno = sb->ent->lno;
- for (ent = sb->ent; ent; ent = ent->next) {
- printf("L %8d l %8d n %8d\n",
- lno, ent->lno, ent->num_lines);
- lno = ent->lno + ent->num_lines;
- }
- die("oops");
- }
+ add_blame_entry(unblamed, new_entry);
}
}
@@ -698,73 +805,147 @@ static void decref_split(struct blame_entry *split)
}
/*
- * Helper for blame_chunk(). blame_entry e is known to overlap with
- * the patch hunk; split it and pass blame to the parent.
+ * reverse_blame reverses the list given in head, appending tail.
+ * That allows us to build lists in reverse order, then reverse them
+ * afterwards. This can be faster than building the list in proper
+ * order right away. The reason is that building in proper order
+ * requires writing a link in the _previous_ element, while building
+ * in reverse order just requires placing the list head into the
+ * _current_ element.
*/
-static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
- int tlno, int plno, int same,
- struct origin *parent)
-{
- struct blame_entry split[3];
- split_overlap(split, e, tlno, plno, same, parent);
- if (split[1].suspect)
- split_blame(sb, split, e);
- decref_split(split);
-}
-
-/*
- * Find the line number of the last line the target is suspected for.
- */
-static int find_last_in_target(struct scoreboard *sb, struct origin *target)
+static struct blame_entry *reverse_blame(struct blame_entry *head,
+ struct blame_entry *tail)
{
- struct blame_entry *e;
- int last_in_target = -1;
-
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target))
- continue;
- if (last_in_target < e->s_lno + e->num_lines)
- last_in_target = e->s_lno + e->num_lines;
+ while (head) {
+ struct blame_entry *next = head->next;
+ head->next = tail;
+ tail = head;
+ head = next;
}
- return last_in_target;
+ return tail;
}
/*
* Process one hunk from the patch between the current suspect for
- * blame_entry e and its parent. Find and split the overlap, and
- * pass blame to the overlapping part to the parent.
+ * blame_entry e and its parent. This first blames any unfinished
+ * entries before the chunk (which is where target and parent start
+ * differing) on the parent, and then splits blame entries at the
+ * start and at the end of the difference region. Since use of -M and
+ * -C options may lead to overlapping/duplicate source line number
+ * ranges, all we can rely on from sorting/merging is the order of the
+ * first suspect line number.
*/
-static void blame_chunk(struct scoreboard *sb,
- int tlno, int plno, int same,
- struct origin *target, struct origin *parent)
+static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
+ int tlno, int offset, int same,
+ struct origin *parent)
{
- struct blame_entry *e;
+ struct blame_entry *e = **srcq;
+ struct blame_entry *samep = NULL, *diffp = NULL;
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target))
- continue;
- if (same <= e->s_lno)
- continue;
- if (tlno < e->s_lno + e->num_lines)
- blame_overlap(sb, e, tlno, plno, same, parent);
+ while (e && e->s_lno < tlno) {
+ struct blame_entry *next = e->next;
+ /*
+ * current record starts before differing portion. If
+ * it reaches into it, we need to split it up and
+ * examine the second part separately.
+ */
+ if (e->s_lno + e->num_lines > tlno) {
+ /* Move second half to a new record */
+ int len = tlno - e->s_lno;
+ struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
+ n->suspect = e->suspect;
+ n->lno = e->lno + len;
+ n->s_lno = e->s_lno + len;
+ n->num_lines = e->num_lines - len;
+ e->num_lines = len;
+ e->score = 0;
+ /* Push new record to diffp */
+ n->next = diffp;
+ diffp = n;
+ } else
+ origin_decref(e->suspect);
+ /* Pass blame for everything before the differing
+ * chunk to the parent */
+ e->suspect = origin_incref(parent);
+ e->s_lno += offset;
+ e->next = samep;
+ samep = e;
+ e = next;
+ }
+ /*
+ * As we don't know how much of a common stretch after this
+ * diff will occur, the currently blamed parts are all that we
+ * can assign to the parent for now.
+ */
+
+ if (samep) {
+ **dstq = reverse_blame(samep, **dstq);
+ *dstq = &samep->next;
+ }
+ /*
+ * Prepend the split off portions: everything after e starts
+ * after the blameable portion.
+ */
+ e = reverse_blame(diffp, e);
+
+ /*
+ * Now retain records on the target while parts are different
+ * from the parent.
+ */
+ samep = NULL;
+ diffp = NULL;
+ while (e && e->s_lno < same) {
+ struct blame_entry *next = e->next;
+
+ /*
+ * If current record extends into sameness, need to split.
+ */
+ if (e->s_lno + e->num_lines > same) {
+ /*
+ * Move second half to a new record to be
+ * processed by later chunks
+ */
+ int len = same - e->s_lno;
+ struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
+ n->suspect = origin_incref(e->suspect);
+ n->lno = e->lno + len;
+ n->s_lno = e->s_lno + len;
+ n->num_lines = e->num_lines - len;
+ e->num_lines = len;
+ e->score = 0;
+ /* Push new record to samep */
+ n->next = samep;
+ samep = n;
+ }
+ e->next = diffp;
+ diffp = e;
+ e = next;
}
+ **srcq = reverse_blame(diffp, reverse_blame(samep, e));
+ /* Move across elements that are in the unblamable portion */
+ if (diffp)
+ *srcq = &diffp->next;
}
struct blame_chunk_cb_data {
- struct scoreboard *sb;
- struct origin *target;
struct origin *parent;
- long plno;
- long tlno;
+ long offset;
+ struct blame_entry **dstq;
+ struct blame_entry **srcq;
};
-static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
+/* diff chunks are from parent to target */
+static int blame_chunk_cb(long start_a, long count_a,
+ long start_b, long count_b, void *data)
{
struct blame_chunk_cb_data *d = data;
- blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
- d->plno = p_next;
- d->tlno = t_next;
+ if (start_a - start_b != d->offset)
+ die("internal error in blame::blame_chunk_cb");
+ blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b,
+ start_b + count_b, d->parent);
+ d->offset = start_a + count_a - (start_b + count_b);
+ return 0;
}
/*
@@ -772,34 +953,32 @@ static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
* for the lines it is suspected to its parent. Run diff to find
* which lines came from parent and pass blame for them.
*/
-static int pass_blame_to_parent(struct scoreboard *sb,
- struct origin *target,
- struct origin *parent)
+static void pass_blame_to_parent(struct scoreboard *sb,
+ struct origin *target,
+ struct origin *parent)
{
- int last_in_target;
mmfile_t file_p, file_o;
struct blame_chunk_cb_data d;
- xpparam_t xpp;
- xdemitconf_t xecfg;
- memset(&d, 0, sizeof(d));
- d.sb = sb; d.target = target; d.parent = parent;
- last_in_target = find_last_in_target(sb, target);
- if (last_in_target < 0)
- return 1; /* nothing remains for this target */
+ struct blame_entry *newdest = NULL;
+
+ if (!target->suspects)
+ return; /* nothing remains for this target */
+
+ d.parent = parent;
+ d.offset = 0;
+ d.dstq = &newdest; d.srcq = &target->suspects;
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
fill_origin_blob(&sb->revs->diffopt, target, &file_o);
num_get_patch++;
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 0;
- xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
- /* The rest (i.e. anything after tlno) are the same as the parent */
- blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
+ diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
+ /* The rest are the same as the parent */
+ blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
+ *d.dstq = NULL;
+ queue_blames(sb, parent, newdest);
- return 0;
+ return;
}
/*
@@ -899,12 +1078,15 @@ struct handle_split_cb_data {
long tlno;
};
-static void handle_split_cb(void *data, long same, long p_next, long t_next)
+static int handle_split_cb(long start_a, long count_a,
+ long start_b, long count_b, void *data)
{
struct handle_split_cb_data *d = data;
- handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
- d->plno = p_next;
- d->tlno = t_next;
+ handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent,
+ d->split);
+ d->plno = start_a + count_a;
+ d->tlno = start_b + count_b;
+ return 0;
}
/*
@@ -919,11 +1101,9 @@ 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;
- xpparam_t xpp;
- xdemitconf_t xecfg;
+
memset(&d, 0, sizeof(d));
d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
/*
@@ -931,65 +1111,92 @@ 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.
* file_p partially may match that image.
*/
- memset(&xpp, 0, sizeof(xpp));
- xpp.flags = xdl_opts;
- memset(&xecfg, 0, sizeof(xecfg));
- xecfg.ctxlen = 1;
memset(split, 0, sizeof(struct blame_entry [3]));
- xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
+ diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
/* remainder, if any, all match the preimage */
handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
}
+/* Move all blame entries from list *source that have a score smaller
+ * than score_min to the front of list *small.
+ * Returns a pointer to the link pointing to the old head of the small list.
+ */
+
+static struct blame_entry **filter_small(struct scoreboard *sb,
+ struct blame_entry **small,
+ struct blame_entry **source,
+ unsigned score_min)
+{
+ struct blame_entry *p = *source;
+ struct blame_entry *oldsmall = *small;
+ while (p) {
+ if (ent_score(sb, p) <= score_min) {
+ *small = p;
+ small = &p->next;
+ p = *small;
+ } else {
+ *source = p;
+ source = &p->next;
+ p = *source;
+ }
+ }
+ *small = oldsmall;
+ *source = NULL;
+ return small;
+}
+
/*
* See if lines currently target is suspected for can be attributed to
* parent.
*/
-static int find_move_in_parent(struct scoreboard *sb,
- struct origin *target,
- struct origin *parent)
+static void find_move_in_parent(struct scoreboard *sb,
+ struct blame_entry ***blamed,
+ struct blame_entry **toosmall,
+ struct origin *target,
+ struct origin *parent)
{
- int last_in_target, made_progress;
struct blame_entry *e, split[3];
+ struct blame_entry *unblamed = target->suspects;
+ struct blame_entry *leftover = NULL;
mmfile_t file_p;
- last_in_target = find_last_in_target(sb, target);
- if (last_in_target < 0)
- return 1; /* nothing remains for this target */
+ if (!unblamed)
+ return; /* nothing remains for this target */
fill_origin_blob(&sb->revs->diffopt, parent, &file_p);
if (!file_p.ptr)
- return 0;
+ return;
- made_progress = 1;
- while (made_progress) {
- made_progress = 0;
- for (e = sb->ent; e; e = e->next) {
- if (e->guilty || !same_suspect(e->suspect, target) ||
- ent_score(sb, e) < blame_move_score)
- continue;
+ /* At each iteration, unblamed has a NULL-terminated list of
+ * entries that have not yet been tested for blame. leftover
+ * contains the reversed list of entries that have been tested
+ * without being assignable to the parent.
+ */
+ do {
+ struct blame_entry **unblamedtail = &unblamed;
+ struct blame_entry *next;
+ for (e = unblamed; e; e = next) {
+ next = e->next;
find_copy_in_blob(sb, e, parent, split, &file_p);
if (split[1].suspect &&
blame_move_score < ent_score(sb, &split[1])) {
- split_blame(sb, split, e);
- made_progress = 1;
+ split_blame(blamed, &unblamedtail, split, e);
+ } else {
+ e->next = leftover;
+ leftover = e;
}
decref_split(split);
}
- }
- return 0;
+ *unblamedtail = NULL;
+ toosmall = filter_small(sb, toosmall, &unblamed, blame_move_score);
+ } while (unblamed);
+ target->suspects = reverse_blame(leftover, NULL);
}
struct blame_list {
@@ -1001,72 +1208,52 @@ struct blame_list {
* Count the number of entries the target is suspected for,
* and prepare a list of entry and the best split.
*/
-static struct blame_list *setup_blame_list(struct scoreboard *sb,
- struct origin *target,
- int min_score,
+static struct blame_list *setup_blame_list(struct blame_entry *unblamed,
int *num_ents_p)
{
struct blame_entry *e;
int num_ents, i;
struct blame_list *blame_list = NULL;
- for (e = sb->ent, num_ents = 0; e; e = e->next)
- if (!e->scanned && !e->guilty &&
- same_suspect(e->suspect, target) &&
- min_score < ent_score(sb, e))
- num_ents++;
+ for (e = unblamed, num_ents = 0; e; e = e->next)
+ 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) &&
- min_score < ent_score(sb, e))
- blame_list[i++].ent = e;
+ for (e = unblamed, i = 0; e; e = e->next)
+ blame_list[i++].ent = e;
}
*num_ents_p = num_ents;
return blame_list;
}
/*
- * Reset the scanned status on all entries.
- */
-static void reset_scanned_flag(struct scoreboard *sb)
-{
- struct blame_entry *e;
- for (e = sb->ent; e; e = e->next)
- e->scanned = 0;
-}
-
-/*
* For lines target is suspected for, see if we can find code movement
* across file boundary from the parent commit. porigin is the path
* in the parent we already tried.
*/
-static int find_copy_in_parent(struct scoreboard *sb,
- struct origin *target,
- struct commit *parent,
- struct origin *porigin,
- int opt)
+static void find_copy_in_parent(struct scoreboard *sb,
+ struct blame_entry ***blamed,
+ struct blame_entry **toosmall,
+ struct origin *target,
+ struct commit *parent,
+ struct origin *porigin,
+ int opt)
{
struct diff_options diff_opts;
- const char *paths[1];
int i, j;
- int retval;
struct blame_list *blame_list;
int num_ents;
+ struct blame_entry *unblamed = target->suspects;
+ struct blame_entry *leftover = NULL;
- blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
- if (!blame_list)
- return 1; /* nothing remains for this target */
+ if (!unblamed)
+ return; /* nothing remains for this target */
diff_setup(&diff_opts);
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
- if (diff_setup_done(&diff_opts) < 0)
- die("diff-setup");
+ diff_setup_done(&diff_opts);
/* Try "find copies harder" on new path if requested;
* we do not want to use diffcore_rename() actually to
@@ -1090,9 +1277,9 @@ static int find_copy_in_parent(struct scoreboard *sb,
if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
diffcore_std(&diff_opts);
- retval = 0;
- while (1) {
- int made_progress = 0;
+ do {
+ struct blame_entry **unblamedtail = &unblamed;
+ blame_list = setup_blame_list(unblamed, &num_ents);
for (i = 0; i < diff_queued_diff.nr; i++) {
struct diff_filepair *p = diff_queued_diff.queue[i];
@@ -1129,27 +1316,21 @@ static int find_copy_in_parent(struct scoreboard *sb,
struct blame_entry *split = blame_list[j].split;
if (split[1].suspect &&
blame_copy_score < ent_score(sb, &split[1])) {
- split_blame(sb, split, blame_list[j].ent);
- made_progress = 1;
+ split_blame(blamed, &unblamedtail, split,
+ blame_list[j].ent);
+ } else {
+ blame_list[j].ent->next = leftover;
+ leftover = blame_list[j].ent;
}
- else
- blame_list[j].ent->scanned = 1;
decref_split(split);
}
free(blame_list);
-
- if (!made_progress)
- break;
- blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
- if (!blame_list) {
- retval = 1;
- break;
- }
- }
- reset_scanned_flag(sb);
+ *unblamedtail = NULL;
+ toosmall = filter_small(sb, toosmall, &unblamed, blame_copy_score);
+ } while (unblamed);
+ target->suspects = reverse_blame(leftover, NULL);
diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
- return retval;
+ free_pathspec(&diff_opts.pathspec);
}
/*
@@ -1159,20 +1340,21 @@ static int find_copy_in_parent(struct scoreboard *sb,
static void pass_whole_blame(struct scoreboard *sb,
struct origin *origin, struct origin *porigin)
{
- struct blame_entry *e;
+ struct blame_entry *e, *suspects;
if (!porigin->file.ptr && origin->file.ptr) {
/* Steal its file */
porigin->file = origin->file;
origin->file.ptr = NULL;
}
- for (e = sb->ent; e; e = e->next) {
- if (!same_suspect(e->suspect, origin))
- continue;
+ suspects = origin->suspects;
+ origin->suspects = NULL;
+ for (e = suspects; e; e = e->next) {
origin_incref(porigin);
origin_decref(e->suspect);
e->suspect = porigin;
}
+ queue_blames(sb, porigin, suspects);
}
/*
@@ -1189,11 +1371,29 @@ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit
static int num_scapegoats(struct rev_info *revs, struct commit *commit)
{
- int cnt;
struct commit_list *l = first_scapegoat(revs, commit);
- for (cnt = 0; l; l = l->next)
- cnt++;
- return cnt;
+ return commit_list_count(l);
+}
+
+/* Distribute collected unsorted blames to the respected sorted lists
+ * in the various origins.
+ */
+static void distribute_blame(struct scoreboard *sb, struct blame_entry *blamed)
+{
+ blamed = blame_sort(blamed, compare_blame_suspect);
+ while (blamed)
+ {
+ struct origin *porigin = blamed->suspect;
+ struct blame_entry *suspects = NULL;
+ do {
+ struct blame_entry *next = blamed->next;
+ blamed->next = suspects;
+ suspects = blamed;
+ blamed = next;
+ } while (blamed && blamed->suspect == porigin);
+ suspects = reverse_blame(suspects, NULL);
+ queue_blames(sb, porigin, suspects);
+ }
}
#define MAXSG 16
@@ -1206,6 +1406,8 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
struct commit_list *sg;
struct origin *sg_buf[MAXSG];
struct origin *porigin, **sg_origin = sg_buf;
+ struct blame_entry *toosmall = NULL;
+ struct blame_entry *blames, **blametail = &blames;
num_sg = num_scapegoats(revs, commit);
if (!num_sg)
@@ -1219,7 +1421,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* The first pass looks for unrenamed path to optimize for
* common cases, then we look for renames in the second pass.
*/
- for (pass = 0; pass < 2; pass++) {
+ for (pass = 0; pass < 2 - no_whole_file_rename; pass++) {
struct origin *(*find)(struct scoreboard *,
struct commit *, struct origin *);
find = pass ? find_rename : find_origin;
@@ -1267,38 +1469,71 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
origin_incref(porigin);
origin->previous = porigin;
}
- if (pass_blame_to_parent(sb, origin, porigin))
+ pass_blame_to_parent(sb, origin, porigin);
+ if (!origin->suspects)
goto finish;
}
/*
* Optionally find moves in parents' files.
*/
- if (opt & PICKAXE_BLAME_MOVE)
- for (i = 0, sg = first_scapegoat(revs, commit);
- i < num_sg && sg;
- sg = sg->next, i++) {
- struct origin *porigin = sg_origin[i];
- if (!porigin)
- continue;
- if (find_move_in_parent(sb, origin, porigin))
- goto finish;
+ if (opt & PICKAXE_BLAME_MOVE) {
+ filter_small(sb, &toosmall, &origin->suspects, blame_move_score);
+ if (origin->suspects) {
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
+ if (!porigin)
+ continue;
+ find_move_in_parent(sb, &blametail, &toosmall, origin, porigin);
+ if (!origin->suspects)
+ break;
+ }
}
+ }
/*
* Optionally find copies from parents' files.
*/
- if (opt & PICKAXE_BLAME_COPY)
+ if (opt & PICKAXE_BLAME_COPY) {
+ if (blame_copy_score > blame_move_score)
+ filter_small(sb, &toosmall, &origin->suspects, blame_copy_score);
+ else if (blame_copy_score < blame_move_score) {
+ origin->suspects = blame_merge(origin->suspects, toosmall);
+ toosmall = NULL;
+ filter_small(sb, &toosmall, &origin->suspects, blame_copy_score);
+ }
+ if (!origin->suspects)
+ goto finish;
+
for (i = 0, sg = first_scapegoat(revs, commit);
i < num_sg && sg;
sg = sg->next, i++) {
struct origin *porigin = sg_origin[i];
- if (find_copy_in_parent(sb, origin, sg->item,
- porigin, opt))
+ find_copy_in_parent(sb, &blametail, &toosmall,
+ origin, sg->item, porigin, opt);
+ if (!origin->suspects)
goto finish;
}
+ }
- finish:
+finish:
+ *blametail = NULL;
+ distribute_blame(sb, blames);
+ /*
+ * prepend toosmall to origin->suspects
+ *
+ * There is no point in sorting: this ends up on a big
+ * unsorted list in the caller anyway.
+ */
+ if (toosmall) {
+ struct blame_entry **tail = &toosmall;
+ while (*tail)
+ tail = &(*tail)->next;
+ *tail = origin->suspects;
+ origin->suspects = toosmall;
+ }
for (i = 0; i < num_sg; i++) {
if (sg_origin[i]) {
drop_origin_blob(sg_origin[i]);
@@ -1314,30 +1549,31 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* Information on commits, used for output.
*/
struct commit_info {
- const char *author;
- const char *author_mail;
+ struct strbuf author;
+ struct strbuf author_mail;
unsigned long author_time;
- const char *author_tz;
+ struct strbuf author_tz;
/* filled only when asked for details */
- const char *committer;
- const char *committer_mail;
+ struct strbuf committer;
+ struct strbuf committer_mail;
unsigned long committer_time;
- const char *committer_tz;
+ struct strbuf committer_tz;
- const char *summary;
+ struct strbuf summary;
};
/*
* Parse author/committer line in the commit object buffer
*/
static void get_ac_line(const char *inbuf, const char *what,
- int person_len, char *person,
- int mail_len, char *mail,
- unsigned long *time, const char **tz)
+ struct strbuf *name, struct strbuf *mail,
+ unsigned long *time, struct strbuf *tz)
{
- int len, tzlen, maillen;
- char *tmp, *endp, *timepos, *mailpos;
+ struct ident_split ident;
+ size_t len, maillen, namelen;
+ char *tmp, *endp;
+ const char *namebuf, *mailbuf;
tmp = strstr(inbuf, what);
if (!tmp)
@@ -1348,69 +1584,66 @@ static void get_ac_line(const char *inbuf, const char *what,
len = strlen(tmp);
else
len = endp - tmp;
- if (person_len <= len) {
+
+ if (split_ident_line(&ident, tmp, len)) {
error_out:
/* Ugh */
- *tz = "(unknown)";
- strcpy(person, *tz);
- strcpy(mail, *tz);
+ tmp = "(unknown)";
+ strbuf_addstr(name, tmp);
+ strbuf_addstr(mail, tmp);
+ strbuf_addstr(tz, tmp);
*time = 0;
return;
}
- memcpy(person, tmp, len);
-
- tmp = person;
- tmp += len;
- *tmp = 0;
- while (person < tmp && *tmp != ' ')
- tmp--;
- if (tmp <= person)
- goto error_out;
- *tz = tmp+1;
- tzlen = (person+len)-(tmp+1);
- *tmp = 0;
- while (person < tmp && *tmp != ' ')
- tmp--;
- if (tmp <= person)
- goto error_out;
- *time = strtoul(tmp, NULL, 10);
- timepos = tmp;
+ namelen = ident.name_end - ident.name_begin;
+ namebuf = ident.name_begin;
- *tmp = 0;
- while (person < tmp && !(*tmp == ' ' && tmp[1] == '<'))
- tmp--;
- if (tmp <= person)
- return;
- mailpos = tmp + 1;
- *tmp = 0;
- maillen = timepos - tmp;
- memcpy(mail, mailpos, maillen);
+ maillen = ident.mail_end - ident.mail_begin;
+ mailbuf = ident.mail_begin;
- if (!mailmap.nr)
- return;
+ if (ident.date_begin && ident.date_end)
+ *time = strtoul(ident.date_begin, NULL, 10);
+ else
+ *time = 0;
- /*
- * mailmap expansion may make the name longer.
- * make room by pushing stuff down.
- */
- tmp = person + person_len - (tzlen + 1);
- memmove(tmp, *tz, tzlen);
- tmp[tzlen] = 0;
- *tz = tmp;
+ if (ident.tz_begin && ident.tz_end)
+ strbuf_add(tz, ident.tz_begin, ident.tz_end - ident.tz_begin);
+ else
+ strbuf_addstr(tz, "(unknown)");
/*
* Now, convert both name and e-mail using mailmap
*/
- if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
- /* Add a trailing '>' to email, since map_user returns plain emails
- Note: It already has '<', since we replace from mail+1 */
- mailpos = memchr(mail, '\0', mail_len);
- if (mailpos && mailpos-mail < mail_len - 1) {
- *mailpos = '>';
- *(mailpos+1) = '\0';
- }
- }
+ map_user(&mailmap, &mailbuf, &maillen,
+ &namebuf, &namelen);
+
+ strbuf_addf(mail, "<%.*s>", (int)maillen, mailbuf);
+ strbuf_add(name, namebuf, namelen);
+}
+
+static void commit_info_init(struct commit_info *ci)
+{
+
+ strbuf_init(&ci->author, 0);
+ strbuf_init(&ci->author_mail, 0);
+ strbuf_init(&ci->author_tz, 0);
+ strbuf_init(&ci->committer, 0);
+ strbuf_init(&ci->committer_mail, 0);
+ strbuf_init(&ci->committer_tz, 0);
+ strbuf_init(&ci->summary, 0);
+}
+
+static void commit_info_destroy(struct commit_info *ci)
+{
+
+ strbuf_release(&ci->author);
+ strbuf_release(&ci->author_mail);
+ strbuf_release(&ci->author_tz);
+ strbuf_release(&ci->committer);
+ strbuf_release(&ci->committer_mail);
+ strbuf_release(&ci->committer_tz);
+ strbuf_release(&ci->summary);
}
static void get_commit_info(struct commit *commit,
@@ -1418,57 +1651,33 @@ static void get_commit_info(struct commit *commit,
int detailed)
{
int len;
- const char *subject;
- char *reencoded, *message;
- static char author_name[1024];
- static char author_mail[1024];
- static char committer_name[1024];
- static char committer_mail[1024];
- static char summary_buf[1024];
+ const char *subject, *encoding;
+ const char *message;
- /*
- * We've operated without save_commit_buffer, so
- * we now need to populate them for output.
- */
- if (!commit->buffer) {
- enum object_type type;
- unsigned long size;
- commit->buffer =
- read_sha1_file(commit->object.sha1, &type, &size);
- if (!commit->buffer)
- die("Cannot read commit %s",
- sha1_to_hex(commit->object.sha1));
- }
- reencoded = reencode_commit_message(commit, NULL);
- message = reencoded ? reencoded : commit->buffer;
- ret->author = author_name;
- ret->author_mail = author_mail;
+ commit_info_init(ret);
+
+ encoding = get_log_output_encoding();
+ message = logmsg_reencode(commit, NULL, encoding);
get_ac_line(message, "\nauthor ",
- sizeof(author_name), author_name,
- sizeof(author_mail), author_mail,
+ &ret->author, &ret->author_mail,
&ret->author_time, &ret->author_tz);
if (!detailed) {
- free(reencoded);
+ unuse_commit_buffer(commit, message);
return;
}
- ret->committer = committer_name;
- ret->committer_mail = committer_mail;
get_ac_line(message, "\ncommitter ",
- sizeof(committer_name), committer_name,
- sizeof(committer_mail), committer_mail,
+ &ret->committer, &ret->committer_mail,
&ret->committer_time, &ret->committer_tz);
- ret->summary = summary_buf;
len = find_commit_subject(message, &subject);
- if (len && len < sizeof(summary_buf)) {
- memcpy(summary_buf, subject, len);
- summary_buf[len] = 0;
- } else {
- sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
- }
- free(reencoded);
+ if (len)
+ strbuf_add(&ret->summary, subject, len);
+ else
+ strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1));
+
+ unuse_commit_buffer(commit, message);
}
/*
@@ -1496,15 +1705,15 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
- printf("author %s\n", ci.author);
- printf("author-mail %s\n", ci.author_mail);
+ printf("author %s\n", ci.author.buf);
+ printf("author-mail %s\n", ci.author_mail.buf);
printf("author-time %lu\n", ci.author_time);
- printf("author-tz %s\n", ci.author_tz);
- printf("committer %s\n", ci.committer);
- printf("committer-mail %s\n", ci.committer_mail);
+ printf("author-tz %s\n", ci.author_tz.buf);
+ printf("committer %s\n", ci.committer.buf);
+ printf("committer-mail %s\n", ci.committer_mail.buf);
printf("committer-time %lu\n", ci.committer_time);
- printf("committer-tz %s\n", ci.committer_tz);
- printf("summary %s\n", ci.summary);
+ printf("committer-tz %s\n", ci.committer_tz.buf);
+ printf("summary %s\n", ci.summary.buf);
if (suspect->commit->object.flags & UNINTERESTING)
printf("boundary\n");
if (suspect->previous) {
@@ -1512,18 +1721,18 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
write_name_quoted(prev->path, stdout, '\n');
}
+
+ commit_info_destroy(&ci);
+
return 1;
}
/*
- * The blame_entry is found to be guilty for the range. Mark it
- * as such, and show it in incremental output.
+ * The blame_entry is found to be guilty for the range.
+ * Show it in incremental output.
*/
static void found_guilty_entry(struct blame_entry *ent)
{
- if (ent->guilty)
- return;
- ent->guilty = 1;
if (incremental) {
struct origin *suspect = ent->suspect;
@@ -1537,34 +1746,35 @@ static void found_guilty_entry(struct blame_entry *ent)
}
/*
- * The main loop -- while the scoreboard has lines whose true origin
- * is still unknown, pick one blame_entry, and allow its current
- * suspect to pass blames to its parents.
- */
+ * The main loop -- while we have blobs with lines whose true origin
+ * is still unknown, pick one blob, and allow its lines to pass blames
+ * to its parents. */
static void assign_blame(struct scoreboard *sb, int opt)
{
struct rev_info *revs = sb->revs;
+ struct commit *commit = prio_queue_get(&sb->commits);
- while (1) {
+ while (commit) {
struct blame_entry *ent;
- struct commit *commit;
- struct origin *suspect = NULL;
+ struct origin *suspect = commit->util;
/* find one suspect to break down */
- for (ent = sb->ent; !suspect && ent; ent = ent->next)
- if (!ent->guilty)
- suspect = ent->suspect;
- if (!suspect)
- return; /* all done */
+ while (suspect && !suspect->suspects)
+ suspect = suspect->next;
+
+ if (!suspect) {
+ commit = prio_queue_get(&sb->commits);
+ continue;
+ }
+
+ assert(commit == suspect->commit);
/*
* We will use this suspect later in the loop,
* so hold onto it in the meantime.
*/
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)))
@@ -1579,9 +1789,22 @@ static void assign_blame(struct scoreboard *sb, int opt)
commit->object.flags |= UNINTERESTING;
/* Take responsibility for the remaining entries */
- for (ent = sb->ent; ent; ent = ent->next)
- if (same_suspect(ent->suspect, suspect))
+ ent = suspect->suspects;
+ if (ent) {
+ suspect->guilty = 1;
+ for (;;) {
+ struct blame_entry *next = ent->next;
found_guilty_entry(ent);
+ if (next) {
+ ent = next;
+ continue;
+ }
+ ent->next = sb->ent;
+ sb->ent = suspect->suspects;
+ suspect->suspects = NULL;
+ break;
+ }
+ }
origin_decref(suspect);
if (DEBUG) /* sanity */
@@ -1592,22 +1815,29 @@ static void assign_blame(struct scoreboard *sb, int opt)
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;
+ static struct strbuf time_buf = STRBUF_INIT;
+ strbuf_reset(&time_buf);
if (show_raw_time) {
- sprintf(time_buf, "%lu %s", time, tz_str);
+ strbuf_addf(&time_buf, "%lu %s", time, tz_str);
}
else {
+ const char *time_str;
+ size_t time_width;
+ int tz;
tz = atoi(tz_str);
time_str = show_date(time, tz, blame_date_mode);
- time_len = strlen(time_str);
- memcpy(time_buf, time_str, time_len);
- memset(time_buf + time_len, ' ', blame_date_width - time_len);
+ strbuf_addstr(&time_buf, time_str);
+ /*
+ * Add space paddings to time_buf to display a fixed width
+ * string, and use time_width for display width calibration.
+ */
+ for (time_width = utf8_strwidth(time_str);
+ time_width < blame_date_width;
+ time_width++)
+ strbuf_addch(&time_buf, ' ');
}
- return time_buf;
+ return time_buf.buf;
}
#define OUTPUT_ANNOTATE_COMPAT 001
@@ -1638,9 +1868,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
char hex[41];
strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
- printf("%s%c%d %d %d\n",
+ printf("%s %d %d %d\n",
hex,
- ent->guilty ? ' ' : '*', /* purely for debugging */
ent->s_lno + 1,
ent->lno + 1,
ent->num_lines);
@@ -1698,11 +1927,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
if (opt & OUTPUT_ANNOTATE_COMPAT) {
const char *name;
if (opt & OUTPUT_SHOW_EMAIL)
- name = ci.author_mail;
+ name = ci.author_mail.buf;
else
- name = ci.author;
+ name = ci.author.buf;
printf("\t(%10s\t%10s\t%d)", name,
- format_time(ci.author_time, ci.author_tz,
+ format_time(ci.author_time, ci.author_tz.buf,
show_raw_time),
ent->lno + 1 + cnt);
} else {
@@ -1721,14 +1950,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
const char *name;
int pad;
if (opt & OUTPUT_SHOW_EMAIL)
- name = ci.author_mail;
+ name = ci.author_mail.buf;
else
- name = ci.author;
+ name = ci.author.buf;
pad = longest_author - utf8_strwidth(name);
printf(" (%s%*s %10s",
name, pad, "",
format_time(ci.author_time,
- ci.author_tz,
+ ci.author_tz.buf,
show_raw_time));
}
printf(" %*d) ",
@@ -1743,6 +1972,8 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
if (sb->final_buf_size && cp[-1] != '\n')
putchar('\n');
+
+ commit_info_destroy(&ci);
}
static void output(struct scoreboard *sb, int option)
@@ -1751,17 +1982,16 @@ static void output(struct scoreboard *sb, int option)
if (option & OUTPUT_PORCELAIN) {
for (ent = sb->ent; ent; ent = ent->next) {
- struct blame_entry *oth;
- struct origin *suspect = ent->suspect;
- struct commit *commit = suspect->commit;
+ int count = 0;
+ struct origin *suspect;
+ struct commit *commit = ent->suspect->commit;
if (commit->object.flags & MORE_THAN_ONE_PATH)
continue;
- for (oth = ent->next; oth; oth = oth->next) {
- if ((oth->suspect->commit != commit) ||
- !strcmp(oth->suspect->path, suspect->path))
- continue;
- commit->object.flags |= MORE_THAN_ONE_PATH;
- break;
+ for (suspect = commit->util; suspect; suspect = suspect->next) {
+ if (suspect->guilty && count++) {
+ commit->object.flags |= MORE_THAN_ONE_PATH;
+ break;
+ }
}
}
}
@@ -1775,6 +2005,12 @@ static void output(struct scoreboard *sb, int option)
}
}
+static const char *get_next_line(const char *start, const char *end)
+{
+ const char *nl = memchr(start, '\n', end - start);
+ return nl ? nl + 1 : end;
+}
+
/*
* To allow quick access to the contents of nth line in the
* final image, prepare an index in the scoreboard.
@@ -1783,26 +2019,22 @@ 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;
-
- 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') {
- num++;
- bol = 1;
- }
- }
- sb->lineno = xrealloc(sb->lineno,
- sizeof(int *) * (num + incomplete + 1));
- sb->lineno[num + incomplete] = buf - sb->final_buf;
- sb->num_lines = num + incomplete;
+ const char *end = buf + len;
+ const char *p;
+ int *lineno;
+ int num = 0;
+
+ for (p = buf; p < end; p = get_next_line(p, end))
+ num++;
+
+ sb->lineno = lineno = xmalloc(sizeof(*sb->lineno) * (num + 1));
+
+ for (p = buf; p < end; p = get_next_line(p, end))
+ *lineno++ = p - buf;
+
+ *lineno = len;
+
+ sb->num_lines = num;
return sb->num_lines;
}
@@ -1814,30 +2046,28 @@ 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;
}
-/*
- * How many columns do we need to show line numbers in decimal?
- */
-static int lineno_width(int lines)
+static int update_auto_abbrev(int auto_abbrev, struct origin *suspect)
{
- int i, width;
-
- for (width = 1, i = 10; i <= lines; width++)
- i *= 10;
- return width;
+ const char *uniq = find_unique_abbrev(suspect->commit->object.sha1,
+ auto_abbrev);
+ int len = strlen(uniq);
+ if (auto_abbrev < len)
+ return len;
+ return auto_abbrev;
}
/*
@@ -1850,26 +2080,31 @@ static void find_alignment(struct scoreboard *sb, int *option)
int longest_dst_lines = 0;
unsigned largest_score = 0;
struct blame_entry *e;
+ int compute_auto_abbrev = (abbrev < 0);
+ int auto_abbrev = default_abbrev;
for (e = sb->ent; e; e = e->next) {
struct origin *suspect = e->suspect;
- struct commit_info ci;
int num;
+ if (compute_auto_abbrev)
+ auto_abbrev = update_auto_abbrev(auto_abbrev, suspect);
if (strcmp(suspect->path, sb->path))
*option |= OUTPUT_SHOW_NAME;
num = strlen(suspect->path);
if (longest_file < num)
longest_file = num;
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+ struct commit_info ci;
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
if (*option & OUTPUT_SHOW_EMAIL)
- num = utf8_strwidth(ci.author_mail);
+ num = utf8_strwidth(ci.author_mail.buf);
else
- num = utf8_strwidth(ci.author);
+ num = utf8_strwidth(ci.author.buf);
if (longest_author < num)
longest_author = num;
+ commit_info_destroy(&ci);
}
num = e->s_lno + e->num_lines;
if (longest_src_lines < num)
@@ -1880,9 +2115,13 @@ static void find_alignment(struct scoreboard *sb, int *option)
if (largest_score < ent_score(sb, e))
largest_score = ent_score(sb, e);
}
- max_orig_digits = lineno_width(longest_src_lines);
- max_digits = lineno_width(longest_dst_lines);
- max_score_digits = lineno_width(largest_score);
+ max_orig_digits = decimal_width(longest_src_lines);
+ max_digits = decimal_width(longest_dst_lines);
+ max_score_digits = decimal_width(largest_score);
+
+ if (compute_auto_abbrev)
+ /* one more abbrev length is needed for the boundary commit */
+ abbrev = auto_abbrev + 1;
}
/*
@@ -1936,103 +2175,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")) {
@@ -2050,18 +2192,73 @@ static int git_blame_config(const char *var, const char *value, void *cb)
return 0;
}
- switch (userdiff_config(var, value)) {
- case 0:
- break;
- case -1:
+ if (userdiff_config(var, value) < 0)
return -1;
- default:
- return 0;
- }
return git_default_config(var, value, cb);
}
+static void verify_working_tree_path(struct commit *work_tree, const char *path)
+{
+ struct commit_list *parents;
+
+ for (parents = work_tree->parents; parents; parents = parents->next) {
+ const unsigned char *commit_sha1 = parents->item->object.sha1;
+ unsigned char blob_sha1[20];
+ unsigned mode;
+
+ if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) &&
+ sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+ return;
+ }
+ die("no such path '%s' in HEAD", path);
+}
+
+static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1)
+{
+ struct commit *parent;
+
+ parent = lookup_commit_reference(sha1);
+ if (!parent)
+ die("no such commit %s", sha1_to_hex(sha1));
+ return &commit_list_insert(parent, tail)->next;
+}
+
+static void append_merge_parents(struct commit_list **tail)
+{
+ int merge_head;
+ const char *merge_head_file = git_path("MERGE_HEAD");
+ struct strbuf line = STRBUF_INIT;
+
+ merge_head = open(merge_head_file, O_RDONLY);
+ if (merge_head < 0) {
+ if (errno == ENOENT)
+ return;
+ die("cannot open '%s' for reading", merge_head_file);
+ }
+
+ while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
+ unsigned char sha1[20];
+ if (line.len < 40 || get_sha1_hex(line.buf, sha1))
+ die("unknown line in '%s': %s", merge_head_file, line.buf);
+ tail = append_parent(tail, sha1);
+ }
+ close(merge_head);
+ strbuf_release(&line);
+}
+
+/*
+ * This isn't as simple as passing sb->buf and sb->len, because we
+ * want to transfer ownership of the buffer to the commit (so we
+ * must use detach).
+ */
+static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb)
+{
+ size_t len;
+ void *buf = strbuf_detach(sb, &len);
+ set_commit_buffer(c, buf, len);
+}
+
/*
* Prepare a dummy commit that represents the work tree (or staged) item.
* Note that annotating work tree item never works in the reverse.
@@ -2072,6 +2269,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
{
struct commit *commit;
struct origin *origin;
+ struct commit_list **parent_tail, *parent;
unsigned char head_sha1[20];
struct strbuf buf = STRBUF_INIT;
const char *ident;
@@ -2079,23 +2277,41 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
int size, len;
struct cache_entry *ce;
unsigned mode;
-
- if (get_sha1("HEAD", head_sha1))
- die("No such ref: HEAD");
+ struct strbuf msg = STRBUF_INIT;
time(&now);
- commit = xcalloc(1, sizeof(*commit));
- commit->parents = xcalloc(1, sizeof(*commit->parents));
- commit->parents->item = lookup_commit_reference(head_sha1);
+ commit = alloc_commit_node();
commit->object.parsed = 1;
commit->date = now;
- commit->object.type = OBJ_COMMIT;
+ parent_tail = &commit->parents;
+
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
+ die("no such ref: HEAD");
+
+ parent_tail = append_parent(parent_tail, head_sha1);
+ append_merge_parents(parent_tail);
+ verify_working_tree_path(commit, path);
origin = make_origin(commit, path);
+ ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+ strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
+ for (parent = commit->parents; parent; parent = parent->next)
+ strbuf_addf(&msg, "parent %s\n",
+ sha1_to_hex(parent->item->object.sha1));
+ strbuf_addf(&msg,
+ "author %s\n"
+ "committer %s\n\n"
+ "Version of %s from %s\n",
+ ident, ident, path,
+ (!contents_from ? path :
+ (!strcmp(contents_from, "-") ? "standard input" : contents_from)));
+ set_commit_buffer_from_strbuf(commit, &msg);
+
if (!contents_from || strcmp("-", contents_from)) {
struct stat st;
const char *read_from;
+ char *buf_ptr;
unsigned long buf_len;
if (contents_from) {
@@ -2113,8 +2329,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
switch (st.st_mode & S_IFMT) {
case S_IFREG:
if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
- textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len))
- buf.len = buf_len;
+ textconv_object(read_from, mode, null_sha1, 0, &buf_ptr, &buf_len))
+ strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
die_errno("cannot open or read '%s'", read_from);
break;
@@ -2128,16 +2344,13 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
}
else {
/* Reading from stdin */
- contents_from = "standard input";
mode = 0;
if (strbuf_read(&buf, 0, 0) < 0)
die_errno("failed to read from stdin");
}
- convert_to_git(path, buf.buf, buf.len, &buf, 0);
origin->file.ptr = buf.buf;
origin->file.size = buf.len;
pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
- commit->util = origin;
/*
* Read the current index, replace the path entry with
@@ -2161,7 +2374,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
ce = xcalloc(1, size);
hashcpy(ce->sha1, origin->blob_sha1);
memcpy(ce->name, path, len);
- ce->ce_flags = create_ce_flags(len, 0);
+ ce->ce_flags = create_ce_flags(0);
+ ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
@@ -2170,22 +2384,12 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
* right now, but someday we might optimize diff-index --cached
* with cache-tree information.
*/
- cache_tree_invalidate_path(active_cache_tree, path);
+ cache_tree_invalidate_path(&the_index, path);
- commit->buffer = xmalloc(400);
- ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
- snprintf(commit->buffer, 400,
- "tree 0000000000000000000000000000000000000000\n"
- "parent %s\n"
- "author %s\n"
- "committer %s\n\n"
- "Version of %s from %s\n",
- sha1_to_hex(head_sha1),
- ident, ident, path, contents_from ? contents_from : path);
return commit;
}
-static const char *prepare_final(struct scoreboard *sb)
+static char *prepare_final(struct scoreboard *sb)
{
int i;
const char *final_commit_name = NULL;
@@ -2210,10 +2414,10 @@ static const char *prepare_final(struct scoreboard *sb)
sb->final = (struct commit *) obj;
final_commit_name = revs->pending.objects[i].name;
}
- return final_commit_name;
+ return xstrdup_or_null(final_commit_name);
}
-static const char *prepare_initial(struct scoreboard *sb)
+static char *prepare_initial(struct scoreboard *sb)
{
int i;
const char *final_commit_name = NULL;
@@ -2240,7 +2444,7 @@ static const char *prepare_initial(struct scoreboard *sb)
}
if (!final_commit_name)
die("No commit to dig down to?");
- return final_commit_name;
+ return xstrdup(final_commit_name);
}
static int blame_copy_callback(const struct option *option, const char *arg, int unset)
@@ -2276,65 +2480,59 @@ 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;
- const char *final_commit_name = NULL;
+ struct blame_entry *ent = NULL;
+ long dashdash_pos, lno;
+ 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, "Show blame entries as we find them, incrementally"),
- OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
- OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
- OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
- OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
- OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
- OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
- OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
- OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
- OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
- OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
- OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
- OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
- OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
- OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
- OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
- OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
- { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
- { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
- OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
+ 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),
+ OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN),
+ OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
+ OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT),
+ OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP),
+ OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME),
+ OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
+ OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
+ OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
+ OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
+ OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),
+ OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
+ { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
+ { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
+ OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
OPT__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);
revs.date_mode = blame_date_mode;
DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
+ DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES);
save_commit_buffer = 0;
dashdash_pos = 0;
@@ -2358,12 +2556,13 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
}
parse_done:
+ no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES);
+ DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES);
argc = parse_options_end(&ctx);
- if (abbrev == -1)
- abbrev = default_abbrev;
- /* one more abbrev length is needed for the boundary commit */
- abbrev++;
+ if (0 < abbrev)
+ /* one more abbrev length is needed for the boundary commit */
+ abbrev++;
if (revs_file && read_ancestry(revs_file))
die_errno("reading graft file '%s' failed", revs_file);
@@ -2380,6 +2579,9 @@ parse_done:
case DATE_RFC2822:
blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
break;
+ case DATE_ISO8601_STRICT:
+ blame_date_width = sizeof("2006-10-19T16:00:04-07:00");
+ break;
case DATE_ISO8601:
blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
break;
@@ -2390,7 +2592,14 @@ parse_done:
blame_date_width = sizeof("2006-10-19");
break;
case DATE_RELATIVE:
- /* "normal" is used as the fallback for "relative" */
+ /* TRANSLATORS: This string is used to tell us the maximum
+ display width for a relative timestamp in "git blame"
+ output. For C locale, "4 years, 11 months ago", which
+ takes 22 places, is the longest among various forms of
+ relative timestamps, but your language may need more or
+ fewer display columns. */
+ blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
+ break;
case DATE_LOCAL:
case DATE_NORMAL:
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
@@ -2462,12 +2671,16 @@ parse_done:
memset(&sb, 0, sizeof(sb));
sb.revs = &revs;
- if (!reverse)
+ if (!reverse) {
final_commit_name = prepare_final(&sb);
+ sb.commits.compare = compare_commits_by_commit_date;
+ }
else if (contents_from)
die("--contents and --children do not blend well.");
- else
+ else {
final_commit_name = prepare_initial(&sb);
+ sb.commits.compare = compare_commits_by_reverse_commit_date;
+ }
if (!sb.final) {
/*
@@ -2489,14 +2702,11 @@ parse_done:
* uninteresting.
*/
if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
if (is_null_sha1(sb.final->object.sha1)) {
- char *buf;
o = sb.final->util;
- buf = xmalloc(o->file.size + 1);
- memcpy(buf, o->file.ptr, o->file.size + 1);
- sb.final_buf = buf;
+ sb.final_buf = xmemdupz(o->file.ptr, o->file.size);
sb.final_buf_size = o->file.size;
}
else {
@@ -2505,7 +2715,7 @@ parse_done:
die("no such path %s in %s", path, final_commit_name);
if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
- textconv_object(path, o->mode, o->blob_sha1, (char **) &sb.final_buf,
+ textconv_object(path, o->mode, o->blob_sha1, 1, (char **) &sb.final_buf,
&sb.final_buf_size))
;
else
@@ -2520,28 +2730,52 @@ 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;
-
- sb.ent = ent;
+ 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);
+ }
+
+ o->suspects = ent;
+ prio_queue_put(&sb.commits, o->commit);
+
+ origin_decref(o);
+
+ range_set_release(&ranges);
+ string_list_clear(&range_list, 0);
+
+ sb.ent = NULL;
sb.path = path;
read_mailmap(&mailmap, NULL);
@@ -2551,9 +2785,13 @@ parse_done:
assign_blame(&sb, opt);
+ free(final_commit_name);
+
if (incremental)
return 0;
+ sb.ent = blame_sort(sb.ent, compare_blame_final);
+
coalesce(&sb);
if (!(output_option & OUTPUT_PORCELAIN))
diff --git a/builtin/branch.c b/builtin/branch.c
index f49596f..dc6f0b2 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -15,12 +15,16 @@
#include "branch.h"
#include "diff.h"
#include "revision.h"
+#include "string-list.h"
+#include "column.h"
+#include "utf8.h"
+#include "wt-status.h"
static const char * const builtin_branch_usage[] = {
- "git branch [options] [-r | -a] [--merged | --no-merged]",
- "git branch [options] [-l] [-f] <branchname> [<start-point>]",
- "git branch [options] [-r] (-d | -D) <branchname>...",
- "git branch [options] (-m | -M) [<oldbranch>] <newbranch>",
+ N_("git branch [options] [-r | -a] [--merged | --no-merged]"),
+ N_("git branch [options] [-l] [-f] <branchname> [<start-point>]"),
+ N_("git branch [options] [-r] (-d | -D) <branchname>..."),
+ N_("git branch [options] (-m | -M) [<oldbranch>] <newbranch>"),
NULL
};
@@ -37,13 +41,15 @@ static char branch_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RED, /* REMOTE */
GIT_COLOR_NORMAL, /* LOCAL */
GIT_COLOR_GREEN, /* CURRENT */
+ GIT_COLOR_BLUE, /* UPSTREAM */
};
enum color_branch {
BRANCH_COLOR_RESET = 0,
BRANCH_COLOR_PLAIN = 1,
BRANCH_COLOR_REMOTE = 2,
BRANCH_COLOR_LOCAL = 3,
- BRANCH_COLOR_CURRENT = 4
+ BRANCH_COLOR_CURRENT = 4,
+ BRANCH_COLOR_UPSTREAM = 5
};
static enum merge_filter {
@@ -53,35 +59,43 @@ static enum merge_filter {
} merge_filter;
static unsigned char merge_filter_ref[20];
-static int parse_branch_color_slot(const char *var, int ofs)
+static struct string_list output = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
+
+static int parse_branch_color_slot(const char *slot)
{
- if (!strcasecmp(var+ofs, "plain"))
+ if (!strcasecmp(slot, "plain"))
return BRANCH_COLOR_PLAIN;
- if (!strcasecmp(var+ofs, "reset"))
+ if (!strcasecmp(slot, "reset"))
return BRANCH_COLOR_RESET;
- if (!strcasecmp(var+ofs, "remote"))
+ if (!strcasecmp(slot, "remote"))
return BRANCH_COLOR_REMOTE;
- if (!strcasecmp(var+ofs, "local"))
+ if (!strcasecmp(slot, "local"))
return BRANCH_COLOR_LOCAL;
- if (!strcasecmp(var+ofs, "current"))
+ if (!strcasecmp(slot, "current"))
return BRANCH_COLOR_CURRENT;
+ if (!strcasecmp(slot, "upstream"))
+ return BRANCH_COLOR_UPSTREAM;
return -1;
}
static int git_branch_config(const char *var, const char *value, void *cb)
{
+ const char *slot_name;
+
+ 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.")) {
- int slot = parse_branch_color_slot(var, 13);
+ if (skip_prefix(var, "color.branch.", &slot_name)) {
+ int slot = parse_branch_color_slot(slot_name);
if (slot < 0)
return 0;
if (!value)
return config_error_nonbool(var);
- color_parse(value, var, branch_colors[slot]);
- return 0;
+ return color_parse(value, branch_colors[slot]);
}
return git_color_default_config(var, value, cb);
}
@@ -104,6 +118,7 @@ static int branch_merged(int kind, const char *name,
*/
struct commit *reference_rev = NULL;
const char *reference_name = NULL;
+ void *reference_name_to_free = NULL;
int merged;
if (kind == REF_LOCAL_BRANCH) {
@@ -114,14 +129,15 @@ static int branch_merged(int kind, const char *name,
branch->merge &&
branch->merge[0] &&
branch->merge[0]->dst &&
- (reference_name =
- resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+ (reference_name = reference_name_to_free =
+ resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,
+ sha1, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1);
}
if (!reference_rev)
reference_rev = head_rev;
- merged = in_merge_bases(rev, &reference_rev, 1);
+ merged = in_merge_bases(rev, reference_rev);
/*
* After the safety valve is fully redefined to "check with
@@ -131,7 +147,7 @@ static int branch_merged(int kind, const char *name,
* a gentle reminder is in order.
*/
if ((head_rev != reference_rev) &&
- in_merge_bases(rev, &head_rev, 1) != merged) {
+ in_merge_bases(rev, head_rev) != merged) {
if (merged)
warning(_("deleting branch '%s' that has been merged to\n"
" '%s', but not yet merged to HEAD."),
@@ -141,29 +157,59 @@ static int branch_merged(int kind, const char *name,
" '%s', even though it is merged to HEAD."),
name, reference_name);
}
+ free(reference_name_to_free);
return merged;
}
-static int delete_branches(int argc, const char **argv, int force, int kinds)
+static int check_branch_commit(const char *branchname, const char *refname,
+ unsigned char *sha1, struct commit *head_rev,
+ int kinds, int force)
{
- struct commit *rev, *head_rev = NULL;
+ struct commit *rev = lookup_commit_reference(sha1);
+ if (!rev) {
+ error(_("Couldn't look up commit object for '%s'"), refname);
+ return -1;
+ }
+ if (!force && !branch_merged(kinds, branchname, rev, head_rev)) {
+ error(_("The branch '%s' is not fully merged.\n"
+ "If you are sure you want to delete it, "
+ "run 'git branch -D %s'."), branchname, branchname);
+ return -1;
+ }
+ return 0;
+}
+
+static void delete_branch_config(const char *branchname)
+{
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf, "branch.%s", branchname);
+ if (git_config_rename_section(buf.buf, NULL) < 0)
+ warning(_("Update of config-file failed"));
+ strbuf_release(&buf);
+}
+
+static int delete_branches(int argc, const char **argv, int force, int kinds,
+ int quiet)
+{
+ struct commit *head_rev = NULL;
unsigned char sha1[20];
char *name = NULL;
- const char *fmt, *remote;
+ const char *fmt;
int i;
int ret = 0;
+ int remote_branch = 0;
struct strbuf bname = STRBUF_INIT;
switch (kinds) {
case REF_REMOTE_BRANCH:
fmt = "refs/remotes/%s";
- /* TRANSLATORS: This is "remote " in "remote branch '%s' not found" */
- remote = _("remote ");
+ /* For subsequent UI messages */
+ remote_branch = 1;
+
force = 1;
break;
case REF_LOCAL_BRANCH:
fmt = "refs/heads/%s";
- remote = "";
break;
default:
die(_("cannot use -a with -d"));
@@ -175,6 +221,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
die(_("Couldn't look up commit object for HEAD"));
}
for (i = 0; i < argc; i++, strbuf_release(&bname)) {
+ const char *target;
+ int flags = 0;
+
strbuf_branchname(&bname, argv[i]);
if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
error(_("Cannot delete the branch '%s' "
@@ -185,43 +234,45 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
free(name);
- name = xstrdup(mkpath(fmt, bname.buf));
- if (!resolve_ref(name, sha1, 1, NULL)) {
- error(_("%sbranch '%s' not found."),
- remote, bname.buf);
+ name = mkpathdup(fmt, bname.buf);
+ target = resolve_ref_unsafe(name,
+ RESOLVE_REF_READING
+ | RESOLVE_REF_NO_RECURSE
+ | RESOLVE_REF_ALLOW_BAD_NAME,
+ sha1, &flags);
+ if (!target) {
+ error(remote_branch
+ ? _("remote branch '%s' not found.")
+ : _("branch '%s' not found."), bname.buf);
ret = 1;
continue;
}
- rev = lookup_commit_reference(sha1);
- if (!rev) {
- error(_("Couldn't look up commit object for '%s'"), name);
+ if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
+ check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
+ force)) {
ret = 1;
continue;
}
- if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) {
- error(_("The branch '%s' is not fully merged.\n"
- "If you are sure you want to delete it, "
- "run 'git branch -D %s'."), bname.buf, bname.buf);
+ if (delete_ref(name, sha1, REF_NODEREF)) {
+ error(remote_branch
+ ? _("Error deleting remote branch '%s'")
+ : _("Error deleting branch '%s'"),
+ bname.buf);
ret = 1;
continue;
}
-
- if (delete_ref(name, sha1, 0)) {
- error(_("Error deleting %sbranch '%s'"), remote,
- bname.buf);
- ret = 1;
- } else {
- struct strbuf buf = STRBUF_INIT;
- printf(_("Deleted %sbranch %s (was %s).\n"), remote,
+ if (!quiet) {
+ printf(remote_branch
+ ? _("Deleted remote branch %s (was %s).\n")
+ : _("Deleted branch %s (was %s).\n"),
bname.buf,
- find_unique_abbrev(sha1, DEFAULT_ABBREV));
- strbuf_addf(&buf, "branch.%s", bname.buf);
- if (git_config_rename_section(buf.buf, NULL) < 0)
- warning(_("Update of config-file failed"));
- strbuf_release(&buf);
+ (flags & REF_ISBROKEN) ? "broken"
+ : (flags & REF_ISSYMREF) ? target
+ : find_unique_abbrev(sha1, DEFAULT_ABBREV));
}
+ delete_branch_config(bname.buf);
}
free(name);
@@ -232,8 +283,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
struct ref_item {
char *name;
char *dest;
- unsigned int kind, len;
+ unsigned int kind, width;
struct commit *commit;
+ int ignore;
};
struct ref_list {
@@ -248,21 +300,34 @@ static char *resolve_symref(const char *src, const char *prefix)
{
unsigned char sha1[20];
int flag;
- const char *dst, *cp;
+ const char *dst;
- dst = resolve_ref(src, sha1, 0, &flag);
+ dst = resolve_ref_unsafe(src, 0, sha1, &flag);
if (!(dst && (flag & REF_ISSYMREF)))
return NULL;
- if (prefix && (cp = skip_prefix(dst, prefix)))
- dst = cp;
+ if (prefix)
+ skip_prefix(dst, prefix, &dst);
return xstrdup(dst);
}
struct append_ref_cb {
struct ref_list *ref_list;
+ const char **pattern;
int ret;
};
+static int match_patterns(const char **pattern, const char *refname)
+{
+ if (!*pattern)
+ return 1; /* no pattern always matches */
+ while (*pattern) {
+ if (!wildmatch(*pattern, refname, 0, NULL))
+ return 1;
+ pattern++;
+ }
+ return 0;
+}
+
static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
{
struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
@@ -275,20 +340,18 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
static struct {
int kind;
const char *prefix;
- int pfxlen;
} ref_kind[] = {
- { REF_LOCAL_BRANCH, "refs/heads/", 11 },
- { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
+ { REF_LOCAL_BRANCH, "refs/heads/" },
+ { REF_REMOTE_BRANCH, "refs/remotes/" },
};
/* Detect kind */
for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
prefix = ref_kind[i].prefix;
- if (strncmp(refname, prefix, ref_kind[i].pfxlen))
- continue;
- kind = ref_kind[i].kind;
- refname += ref_kind[i].pfxlen;
- break;
+ if (skip_prefix(refname, prefix, &refname)) {
+ kind = ref_kind[i].kind;
+ break;
+ }
}
if (ARRAY_SIZE(ref_kind) <= i)
return 0;
@@ -297,6 +360,9 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
if ((kind & ref_list->kinds) == 0)
return 0;
+ if (!match_patterns(cb->pattern, refname))
+ return 0;
+
commit = NULL;
if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
commit = lookup_commit_reference_gently(sha1, 1);
@@ -321,14 +387,15 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
newitem->name = xstrdup(refname);
newitem->kind = kind;
newitem->commit = commit;
- newitem->len = strlen(refname);
+ newitem->width = utf8_strwidth(refname);
newitem->dest = resolve_symref(orig_refname, prefix);
+ newitem->ignore = 0;
/* adjust for "remotes/" */
if (newitem->kind == REF_REMOTE_BRANCH &&
ref_list->kinds != REF_REMOTE_BRANCH)
- newitem->len += 8;
- if (newitem->len > ref_list->maxwidth)
- ref_list->maxwidth = newitem->len;
+ newitem->width += 8;
+ if (newitem->width > ref_list->maxwidth)
+ ref_list->maxwidth = newitem->width;
return 0;
}
@@ -358,47 +425,78 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
int show_upstream_ref)
{
int ours, theirs;
+ 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)
- strbuf_addf(stat, "[%s] ",
- shorten_unambiguous_ref(branch->merge[0]->dst, 0));
+ 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;
}
- strbuf_addch(stat, '[');
- if (show_upstream_ref)
- strbuf_addf(stat, "%s: ",
- shorten_unambiguous_ref(branch->merge[0]->dst, 0));
- if (!ours)
- strbuf_addf(stat, _("behind %d] "), theirs);
- else if (!theirs)
- strbuf_addf(stat, _("ahead %d] "), ours);
- else
- strbuf_addf(stat, _("ahead %d, behind %d] "), ours, theirs);
-}
-
-static int matches_merge_filter(struct commit *commit)
-{
- int is_merged;
+ if (show_upstream_ref) {
+ ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
+ if (want_color(branch_use_color))
+ strbuf_addf(&fancy, "%s%s%s",
+ branch_get_color(BRANCH_COLOR_UPSTREAM),
+ ref, branch_get_color(BRANCH_COLOR_RESET));
+ else
+ strbuf_addstr(&fancy, ref);
+ }
- if (merge_filter == NO_FILTER)
- return 1;
+ 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);
- is_merged = !!(commit->object.flags & UNINTERESTING);
- return (is_merged == (merge_filter == SHOW_MERGED));
+ } else if (!theirs) {
+ if (show_upstream_ref)
+ strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
+ else
+ strbuf_addf(stat, _("[ahead %d]"), ours);
+ } else {
+ if (show_upstream_ref)
+ strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
+ fancy.buf, ours, theirs);
+ else
+ strbuf_addf(stat, _("[ahead %d, behind %d]"),
+ ours, theirs);
+ }
+ strbuf_release(&fancy);
+ if (added_decoration)
+ strbuf_addch(stat, ' ');
+ free(ref);
}
static void add_verbose_info(struct strbuf *out, struct ref_item *item,
int verbose, int abbrev)
{
struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
- const char *sub = " **** invalid ref ****";
+ 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;
}
@@ -418,10 +516,9 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
{
char c;
int color;
- struct commit *commit = item->commit;
struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
- if (!matches_merge_filter(commit))
+ if (item->ignore)
return;
switch (item->kind) {
@@ -443,11 +540,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
}
strbuf_addf(&name, "%s%s", prefix, item->name);
- if (verbose)
+ if (verbose) {
+ int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
- maxwidth, name.buf,
+ maxwidth + utf8_compensation, name.buf,
branch_get_color(BRANCH_COLOR_RESET));
- else
+ } else
strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
name.buf, branch_get_color(BRANCH_COLOR_RESET));
@@ -456,7 +554,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
else if (verbose)
/* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
add_verbose_info(&out, item, verbose, abbrev);
- printf("%s\n", out.buf);
+ if (column_active(colopts)) {
+ assert(!verbose && "--column and --verbose are incompatible");
+ string_list_append(&output, out.buf);
+ } else {
+ printf("%s\n", out.buf);
+ }
strbuf_release(&name);
strbuf_release(&out);
}
@@ -465,14 +568,37 @@ static int calc_maxwidth(struct ref_list *refs)
{
int i, w = 0;
for (i = 0; i < refs->index; i++) {
- if (!matches_merge_filter(refs->list[i].commit))
+ if (refs->list[i].ignore)
continue;
- if (refs->list[i].len > w)
- w = refs->list[i].len;
+ if (refs->list[i].width > w)
+ w = refs->list[i].width;
}
return w;
}
+static char *get_head_description(void)
+{
+ struct strbuf desc = STRBUF_INIT;
+ struct wt_status_state state;
+ memset(&state, 0, sizeof(state));
+ wt_status_get_state(&state, 1);
+ if (state.rebase_in_progress ||
+ state.rebase_interactive_in_progress)
+ strbuf_addf(&desc, _("(no branch, rebasing %s)"),
+ state.branch);
+ else if (state.bisect_in_progress)
+ strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
+ state.branch);
+ else if (state.detached_from)
+ strbuf_addf(&desc, _("(detached from %s)"),
+ state.detached_from);
+ else
+ strbuf_addstr(&desc, _("(no branch)"));
+ free(state.branch);
+ free(state.onto);
+ free(state.detached_from);
+ return strbuf_detach(&desc, NULL);
+}
static void show_detached(struct ref_list *ref_list)
{
@@ -480,19 +606,20 @@ static void show_detached(struct ref_list *ref_list)
if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
struct ref_item item;
- item.name = xstrdup(_("(no branch)"));
- item.len = strlen(item.name);
+ item.name = get_head_description();
+ item.width = utf8_strwidth(item.name);
item.kind = REF_LOCAL_BRANCH;
item.dest = NULL;
item.commit = head_commit;
- if (item.len > ref_list->maxwidth)
- ref_list->maxwidth = item.len;
+ item.ignore = 0;
+ if (item.width > ref_list->maxwidth)
+ ref_list->maxwidth = item.width;
print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, "");
free(item.name);
}
}
-static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
{
int i;
struct append_ref_cb cb;
@@ -506,16 +633,37 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
if (merge_filter != NO_FILTER)
init_revisions(&ref_list.revs, NULL);
cb.ref_list = &ref_list;
+ cb.pattern = pattern;
cb.ret = 0;
for_each_rawref(append_ref, &cb);
if (merge_filter != NO_FILTER) {
struct commit *filter;
filter = lookup_commit_reference_gently(merge_filter_ref, 0);
+ if (!filter)
+ die(_("object '%s' does not point to a commit"),
+ sha1_to_hex(merge_filter_ref));
+
filter->object.flags |= UNINTERESTING;
add_pending_object(&ref_list.revs,
(struct object *) filter, "");
ref_list.revs.limited = 1;
- prepare_revision_walk(&ref_list.revs);
+
+ if (prepare_revision_walk(&ref_list.revs))
+ die(_("revision walk setup failed"));
+
+ for (i = 0; i < ref_list.index; i++) {
+ struct ref_item *item = &ref_list.list[i];
+ struct commit *commit = item->commit;
+ int is_merged = !!(commit->object.flags & UNINTERESTING);
+ item->ignore = is_merged != (merge_filter == SHOW_MERGED);
+ }
+
+ for (i = 0; i < ref_list.index; i++) {
+ struct ref_item *item = &ref_list.list[i];
+ clear_commit_marks(item->commit, ALL_REV_FLAGS);
+ }
+ clear_commit_marks(filter, ALL_REV_FLAGS);
+
if (verbose)
ref_list.maxwidth = calc_maxwidth(&ref_list);
}
@@ -523,7 +671,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
detached = (detached && (kinds & REF_LOCAL_BRANCH));
- if (detached)
+ if (detached && match_patterns(pattern, "HEAD"))
show_detached(&ref_list);
for (i = 0; i < ref_list.index; i++) {
@@ -548,9 +696,9 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
static void rename_branch(const char *oldname, const char *newname, int force)
{
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
- unsigned char sha1[20];
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
int recovery = 0;
+ int clobber_head_ok;
if (!oldname)
die(_("cannot rename the current branch while not on any."));
@@ -560,13 +708,19 @@ static void rename_branch(const char *oldname, const char *newname, int force)
* Bad name --- this could be an attempt to rename a
* ref that we used to allow to be created by accident.
*/
- if (resolve_ref(oldref.buf, sha1, 1, NULL))
+ if (ref_exists(oldref.buf))
recovery = 1;
else
die(_("Invalid branch name: '%s'"), oldname);
}
- validate_new_branchname(newname, &newref, force, 0);
+ /*
+ * A command like "git branch -M currentbranch currentbranch" cannot
+ * cause the worktree to become inconsistent with HEAD, so allow it.
+ */
+ clobber_head_ok = !strcmp(oldname, newname);
+
+ validate_new_branchname(newname, &newref, force, clobber_head_ok);
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
oldref.buf, newref.buf);
@@ -606,61 +760,108 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int
return 0;
}
+static const char edit_description[] = "BRANCH_DESCRIPTION";
+
+static int edit_branch_description(const char *branch_name)
+{
+ FILE *fp;
+ int status;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf name = STRBUF_INIT;
+
+ read_branch_desc(&buf, branch_name);
+ if (!buf.len || buf.buf[buf.len-1] != '\n')
+ strbuf_addch(&buf, '\n');
+ strbuf_commented_addf(&buf,
+ "Please edit the description for the branch\n"
+ " %s\n"
+ "Lines starting with '%c' will be stripped.\n",
+ branch_name, comment_line_char);
+ fp = fopen(git_path(edit_description), "w");
+ if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) {
+ strbuf_release(&buf);
+ return error(_("could not write branch description template: %s"),
+ strerror(errno));
+ }
+ strbuf_reset(&buf);
+ if (launch_editor(git_path(edit_description), &buf, NULL)) {
+ strbuf_release(&buf);
+ return -1;
+ }
+ stripspace(&buf, 1);
+
+ strbuf_addf(&name, "branch.%s.description", branch_name);
+ status = git_config_set(name.buf, buf.len ? buf.buf : NULL);
+ strbuf_release(&name);
+ strbuf_release(&buf);
+
+ return status;
+}
+
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, rename = 0, force_create = 0;
+ int delete = 0, rename = 0, force = 0, list = 0;
int verbose = 0, abbrev = -1, detached = 0;
- int reflog = 0;
+ int reflog = 0, edit_description = 0;
+ int quiet = 0, unset_upstream = 0;
+ const char *new_upstream = NULL;
enum branch_track track;
int kinds = REF_LOCAL_BRANCH;
struct commit_list *with_commit = NULL;
struct option options[] = {
- OPT_GROUP("Generic options"),
+ OPT_GROUP(N_("Generic options")),
OPT__VERBOSE(&verbose,
- "show hash and subject, give twice for upstream branch"),
- OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))",
+ N_("show hash and subject, give twice for upstream branch")),
+ OPT__QUIET(&quiet, N_("suppress informational messages")),
+ OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"),
BRANCH_TRACK_EXPLICIT),
- OPT_SET_INT( 0, "set-upstream", &track, "change upstream info",
+ OPT_SET_INT( 0, "set-upstream", &track, N_("change upstream info"),
BRANCH_TRACK_OVERRIDE),
- OPT__COLOR(&branch_use_color, "use colored output"),
- OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
+ OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change 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),
{
- OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
- "print only branches that contain the commit",
+ OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
+ N_("print only branches that contain the commit"),
PARSE_OPT_LASTARG_DEFAULT,
parse_opt_with_commit, (intptr_t)"HEAD",
},
{
- OPTION_CALLBACK, 0, "with", &with_commit, "commit",
- "print only branches that contain the commit",
+ OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
+ N_("print only branches that contain the commit"),
PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
parse_opt_with_commit, (intptr_t) "HEAD",
},
OPT__ABBREV(&abbrev),
- OPT_GROUP("Specific git-branch actions:"),
- OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+ OPT_GROUP(N_("Specific git-branch actions:")),
+ OPT_SET_INT('a', "all", &kinds, N_("list both remote-tracking and local branches"),
REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
- OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
- OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
- OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
- OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
- OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
- OPT__FORCE(&force_create, "force creation (when already exists)"),
+ OPT_BIT('d', "delete", &delete, N_("delete fully merged branch"), 1),
+ 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_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, N_("force creation, move/rename, deletion")),
{
OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
- "commit", "print only not merged branches",
+ N_("commit"), N_("print only not merged branches"),
PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
opt_parse_merge_filter, (intptr_t) "HEAD",
},
{
OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
- "commit", "print only merged branches",
+ N_("commit"), N_("print only merged branches"),
PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
opt_parse_merge_filter, (intptr_t) "HEAD",
},
+ OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
OPT_END(),
};
@@ -671,40 +872,173 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
track = git_branch_track;
- head = resolve_ref("HEAD", head_sha1, 0, NULL);
+ head = resolve_refdup("HEAD", 0, head_sha1, NULL);
if (!head)
die(_("Failed to resolve HEAD as a valid ref."));
- head = xstrdup(head);
- if (!strcmp(head, "HEAD")) {
+ if (!strcmp(head, "HEAD"))
detached = 1;
- } else {
- if (prefixcmp(head, "refs/heads/"))
- die(_("HEAD not found below refs/heads!"));
- head += 11;
- }
+ else if (!skip_prefix(head, "refs/heads/", &head))
+ die(_("HEAD not found below refs/heads!"));
hashcpy(merge_filter_ref, head_sha1);
+
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
- if (!!delete + !!rename + !!force_create > 1)
+
+ if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
+ list = 1;
+
+ if (with_commit || merge_filter != NO_FILTER)
+ list = 1;
+
+ if (!!delete + !!rename + !!new_upstream +
+ list + unset_upstream > 1)
usage_with_options(builtin_branch_usage, options);
if (abbrev == -1)
abbrev = DEFAULT_ABBREV;
+ finalize_colopts(&colopts, -1);
+ if (verbose) {
+ if (explicitly_enable_column(colopts))
+ die(_("--column and --verbose are incompatible"));
+ colopts = 0;
+ }
+
+ if (force) {
+ delete *= 2;
+ rename *= 2;
+ }
+
+ if (delete) {
+ if (!argc)
+ die(_("branch name required"));
+ return delete_branches(argc, argv, delete > 1, kinds, quiet);
+ } else if (list) {
+ int ret = print_ref_list(kinds, detached, verbose, abbrev,
+ with_commit, argv);
+ print_columns(&output, colopts, NULL);
+ string_list_clear(&output, 0);
+ return ret;
+ }
+ else if (edit_description) {
+ const char *branch_name;
+ struct strbuf branch_ref = STRBUF_INIT;
+
+ if (!argc) {
+ if (detached)
+ die(_("Cannot give description to detached HEAD"));
+ branch_name = head;
+ } else if (argc == 1)
+ branch_name = argv[0];
+ else
+ die(_("cannot edit description of more than one branch"));
+
+ strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
+ if (!ref_exists(branch_ref.buf)) {
+ strbuf_release(&branch_ref);
+
+ if (!argc)
+ return error(_("No commit on branch '%s' yet."),
+ branch_name);
+ else
+ return error(_("No branch named '%s'."),
+ branch_name);
+ }
+ strbuf_release(&branch_ref);
+
+ if (edit_branch_description(branch_name))
+ return 1;
+ } else if (rename) {
+ if (!argc)
+ die(_("branch name required"));
+ else if (argc == 1)
+ rename_branch(head, argv[0], rename > 1);
+ else if (argc == 2)
+ rename_branch(argv[0], argv[1], rename > 1);
+ else
+ die(_("too many branches for a rename operation"));
+ } else if (new_upstream) {
+ struct branch *branch = branch_get(argv[0]);
+
+ if (argc > 1)
+ die(_("too many branches to set new upstream"));
+
+ if (!branch) {
+ if (!argc || !strcmp(argv[0], "HEAD"))
+ die(_("could not set upstream of HEAD to %s when "
+ "it does not point to any branch."),
+ new_upstream);
+ die(_("no such branch '%s'"), argv[0]);
+ }
+
+ if (!ref_exists(branch->refname))
+ die(_("branch '%s' does not exist"), branch->name);
+
+ /*
+ * create_branch takes care of setting up the tracking
+ * info and making sure new_upstream is correct
+ */
+ create_branch(head, branch->name, new_upstream, 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
+ } else if (unset_upstream) {
+ struct branch *branch = branch_get(argv[0]);
+ struct strbuf buf = STRBUF_INIT;
+
+ if (argc > 1)
+ die(_("too many branches to unset upstream"));
+
+ if (!branch) {
+ if (!argc || !strcmp(argv[0], "HEAD"))
+ die(_("could not unset upstream of HEAD when "
+ "it does not point to any branch."));
+ die(_("no such branch '%s'"), argv[0]);
+ }
+
+ 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);
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "branch.%s.merge", branch->name);
+ git_config_set_multivar(buf.buf, NULL, NULL, 1);
+ strbuf_release(&buf);
+ } else if (argc > 0 && argc <= 2) {
+ struct branch *branch = branch_get(argv[0]);
+ int branch_existed = 0, remote_tracking = 0;
+ struct strbuf buf = STRBUF_INIT;
+
+ if (!strcmp(argv[0], "HEAD"))
+ die(_("it does not make sense to create 'HEAD' manually"));
+
+ if (!branch)
+ die(_("no such branch '%s'"), argv[0]);
- if (delete)
- return delete_branches(argc, argv, delete > 1, kinds);
- else if (argc == 0)
- return print_ref_list(kinds, detached, verbose, abbrev, with_commit);
- else if (rename && (argc == 1))
- rename_branch(head, argv[0], rename > 1);
- else if (rename && (argc == 2))
- rename_branch(argv[0], argv[1], rename > 1);
- else if (argc <= 2) {
if (kinds != REF_LOCAL_BRANCH)
die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
+
+ if (track == BRANCH_TRACK_OVERRIDE)
+ fprintf(stderr, _("The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to\n"));
+
+ strbuf_addf(&buf, "refs/remotes/%s", branch->name);
+ remote_tracking = ref_exists(buf.buf);
+ strbuf_release(&buf);
+
+ branch_existed = ref_exists(branch->refname);
create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
- force_create, reflog, track);
+ force, reflog, 0, quiet, track);
+
+ /*
+ * We only show the instructions if the user gave us
+ * one branch which doesn't exist locally, but is the
+ * name of a remote-tracking branch.
+ */
+ if (argc == 1 && track == BRANCH_TRACK_OVERRIDE &&
+ !branch_existed && remote_tracking) {
+ fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name);
+ fprintf(stderr, _(" git branch -d %s\n"), branch->name);
+ fprintf(stderr, _(" git branch --set-upstream-to %s\n"), branch->name);
+ }
+
} else
usage_with_options(builtin_branch_usage, options);
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 81046a9..92a8a60 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -58,7 +58,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
} else if (!strcmp(cmd, "unbundle")) {
if (!startup_info->have_repository)
die(_("Need a repository to unbundle."));
- return !!unbundle(&header, bundle_fd) ||
+ return !!unbundle(&header, bundle_fd, 0) ||
list_bundle_refs(&header, argc, argv);
} else
usage(builtin_bundle_usage);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 07bd984..31b133b 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -4,83 +4,10 @@
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
-#include "exec_cmd.h"
-#include "tag.h"
-#include "tree.h"
#include "builtin.h"
#include "parse-options.h"
-#include "diff.h"
#include "userdiff.h"
-
-#define BATCH 1
-#define BATCH_CHECK 2
-
-static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
-{
- /* the parser in tag.c is useless here. */
- const char *endp = buf + size;
- const char *cp = buf;
-
- while (cp < endp) {
- char c = *cp++;
- if (c != '\n')
- continue;
- if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
- const char *tagger = cp;
-
- /* Found the tagger line. Copy out the contents
- * of the buffer so far.
- */
- write_or_die(1, buf, cp - buf);
-
- /*
- * Do something intelligent, like pretty-printing
- * the date.
- */
- while (cp < endp) {
- if (*cp++ == '\n') {
- /* tagger to cp is a line
- * that has ident and time.
- */
- const char *sp = tagger;
- char *ep;
- unsigned long date;
- long tz;
- while (sp < cp && *sp != '>')
- sp++;
- if (sp == cp) {
- /* give up */
- write_or_die(1, tagger,
- cp - tagger);
- break;
- }
- while (sp < cp &&
- !('0' <= *sp && *sp <= '9'))
- sp++;
- write_or_die(1, tagger, sp - tagger);
- date = strtoul(sp, &ep, 10);
- tz = strtol(ep, NULL, 10);
- sp = show_date(date, tz, 0);
- write_or_die(1, sp, strlen(sp));
- xwrite(1, "\n", 1);
- break;
- }
- }
- break;
- }
- if (cp < endp && *cp == '\n')
- /* end of header */
- break;
- }
- /* At this point, we have copied out the header up to the end of
- * the tagger line and cp points at one past \n. It could be the
- * next header line after the tagger line, or it could be another
- * \n that marks the end of the headers. We need to copy out the
- * remainder as is.
- */
- if (cp < endp)
- write_or_die(1, cp, endp - cp);
-}
+#include "streaming.h"
static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
{
@@ -90,7 +17,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
unsigned long size;
struct object_context obj_context;
- if (get_sha1_with_context(obj_name, sha1, &obj_context))
+ if (get_sha1_with_context(obj_name, 0, sha1, &obj_context))
die("Not a valid object name %s", obj_name);
buf = NULL;
@@ -114,6 +41,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)
@@ -127,28 +62,37 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
return cmd_ls_tree(2, ls_args, NULL);
}
+ if (type == OBJ_BLOB)
+ return stream_blob_to_fd(1, sha1, NULL, 0);
buf = read_sha1_file(sha1, &type, &size);
if (!buf)
die("Cannot read object %s", obj_name);
- if (type == OBJ_TAG) {
- pprint_tag(sha1, buf, size);
- return 0;
- }
/* 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, &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];
+ if (sha1_object_info(sha1, NULL) == OBJ_TAG) {
+ char *buffer = read_sha1_file(sha1, &type, &size);
+ const char *target;
+ if (!skip_prefix(buffer, "object ", &target) ||
+ get_sha1_hex(target, blob_sha1))
+ die("%s not a valid tag", sha1_to_hex(sha1));
+ free(buffer);
+ } else
+ hashcpy(blob_sha1, sha1);
+
+ if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+ return stream_blob_to_fd(1, blob_sha1, NULL, 0);
+ /*
+ * we attempted to dereference a tag to a blob
+ * and failed; there may be new dereference
+ * mechanisms this code is not aware of.
+ * fall-back to the usual case.
+ */
+ }
buf = read_object_with_reference(sha1, exp_type, &size, NULL);
break;
@@ -163,101 +107,274 @@ 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 = contents;
+ 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;
+ int save_warning;
+ int retval = 0;
+
+ 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.
+ */
+ save_warning = warn_on_object_refname_ambiguity;
+ warn_on_object_refname_ambiguity = 0;
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
- int error = batch_one_object(buf.buf, print_contents);
- if (error)
- return 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;
+ }
+
+ retval = batch_one_object(buf.buf, opt, &data);
+ if (retval)
+ break;
}
- return 0;
+ strbuf_release(&buf);
+ warn_on_object_refname_ambiguity = save_warning;
+ return retval;
}
static const char * const cat_file_usage[] = {
- "git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>",
- "git cat-file (--batch|--batch-check) < <list_of_objects>",
+ N_("git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"),
+ N_("git cat-file (--batch|--batch-check) < <list_of_objects>"),
NULL
};
static int git_cat_file_config(const char *var, const char *value, void *cb)
{
- switch (userdiff_config(var, value)) {
- case 0:
- break;
- case -1:
+ if (userdiff_config(var, value) < 0)
return -1;
- default:
+
+ 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;
}
- return git_default_config(var, value, cb);
+ 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("<type> can be one of: blob, tree, commit, tag"),
- OPT_SET_INT('t', NULL, &opt, "show object type", 't'),
- OPT_SET_INT('s', NULL, &opt, "show object size", 's'),
+ OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
+ OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'),
+ OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'),
OPT_SET_INT('e', NULL, &opt,
- "exit with zero when there's no error", 'e'),
- OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'),
+ N_("exit with zero when there's no error"), 'e'),
+ OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
OPT_SET_INT(0, "textconv", &opt,
- "for blob objects, run textconv on object's content", 'c'),
- OPT_SET_INT(0, "batch", &batch,
- "show info and content of objects fed from the standard input",
- BATCH),
- OPT_SET_INT(0, "batch-check", &batch,
- "show info about objects fed from the standard input",
- BATCH_CHECK),
+ N_("for blob objects, run textconv on object's content"), 'c'),
+ { 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()
};
@@ -274,19 +391,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 708988a..5600ec3 100644
--- a/builtin/check-attr.c
+++ b/builtin/check-attr.c
@@ -5,20 +5,22 @@
#include "parse-options.h"
static int all_attrs;
+static int cached_attrs;
static int stdin_paths;
static const char * const check_attr_usage[] = {
-"git check-attr [-a | --all | attr...] [--] pathname...",
-"git check-attr --stdin [-a | --all | attr...] < <list-of-paths>",
+N_("git check-attr [-a | --all | attr...] [--] pathname..."),
+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, "report all attributes set on file"),
- OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
- OPT_BOOLEAN('z', NULL, &null_term_line,
- "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()
};
@@ -36,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);
+ }
+
}
}
@@ -63,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);
@@ -92,6 +102,11 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
struct git_attr_check *check;
int cnt, i, doubledash, filei;
+ if (!is_bare_repository())
+ setup_work_tree();
+
+ git_config(git_default_config, NULL);
+
argc = parse_options(argc, argv, prefix, check_attr_options,
check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
@@ -99,6 +114,9 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
die("invalid cache");
}
+ if (cached_attrs)
+ git_attr_set_direction(GIT_ATTR_INDEX, NULL);
+
doubledash = -1;
for (i = 0; doubledash < 0 && i < argc; i++) {
if (!strcmp(argv[i], "--"))
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
new file mode 100644
index 0000000..594463a
--- /dev/null
+++ b/builtin/check-ignore.c
@@ -0,0 +1,187 @@
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "quote.h"
+#include "pathspec.h"
+#include "parse-options.h"
+
+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 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_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 && 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 {
+ 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);
+ }
+ } else {
+ if (!verbose) {
+ printf("%s%c", path, '\0');
+ } else {
+ 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(struct dir_struct *dir,
+ const char *prefix, int argc, const char **argv)
+{
+ const char *full_path;
+ char *seen;
+ int num_ignored = 0, dtype = DT_UNKNOWN, i;
+ struct exclude *exclude;
+ struct 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; i < pathspec.nr; i++) {
+ full_path = pathspec.items[i].match;
+ exclude = NULL;
+ if (!seen[i]) {
+ 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);
+
+ return num_ignored;
+}
+
+static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
+{
+ struct strbuf buf, nbuf;
+ 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);
+ while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+ if (line_termination && buf.buf[0] == '"') {
+ strbuf_reset(&nbuf);
+ if (unquote_c_style(&nbuf, buf.buf, NULL))
+ die("line is badly quoted");
+ strbuf_swap(&buf, &nbuf);
+ }
+ pathspec[0] = buf.buf;
+ num_ignored += check_ignore(dir, prefix,
+ 1, (const char **)pathspec);
+ maybe_flush_or_die(stdout, "check-ignore to stdout");
+ }
+ strbuf_release(&buf);
+ strbuf_release(&nbuf);
+ 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);
+
+ argc = parse_options(argc, argv, prefix, check_ignore_options,
+ check_ignore_usage, 0);
+
+ if (stdin_paths) {
+ if (argc > 0)
+ die(_("cannot specify pathnames with --stdin"));
+ } else {
+ if (nul_term_line)
+ die(_("-z only makes sense with --stdin"));
+ if (argc == 0)
+ die(_("no path specified"));
+ }
+ if (quiet) {
+ if (argc > 1)
+ die(_("--quiet is only valid with a single pathname"));
+ 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(&dir, prefix);
+ } else {
+ 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 0000000..8f4d809
--- /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/check-ref-format.c b/builtin/check-ref-format.c
index 0723cf2..28a7320 100644
--- a/builtin/check-ref-format.c
+++ b/builtin/check-ref-format.c
@@ -8,29 +8,32 @@
#include "strbuf.h"
static const char builtin_check_ref_format_usage[] =
-"git check-ref-format [--print] <refname>\n"
+"git check-ref-format [--normalize] [options] <refname>\n"
" or: git check-ref-format --branch <branchname-shorthand>";
/*
- * Remove leading slashes and replace each run of adjacent slashes in
- * src with a single slash, and write the result to dst.
+ * Return a copy of refname but with leading slashes removed and runs
+ * of adjacent slashes replaced with single slashes.
*
* This function is similar to normalize_path_copy(), but stripped down
* to meet check_ref_format's simpler needs.
*/
-static void collapse_slashes(char *dst, const char *src)
+static char *collapse_slashes(const char *refname)
{
+ char *ret = xmalloc(strlen(refname) + 1);
char ch;
char prev = '/';
+ char *cp = ret;
- while ((ch = *src++) != '\0') {
+ while ((ch = *refname++) != '\0') {
if (prev == '/' && ch == prev)
continue;
- *dst++ = ch;
+ *cp++ = ch;
prev = ch;
}
- *dst = '\0';
+ *cp = '\0';
+ return ret;
}
static int check_ref_format_branch(const char *arg)
@@ -45,27 +48,41 @@ static int check_ref_format_branch(const char *arg)
return 0;
}
-static int check_ref_format_print(const char *arg)
-{
- char *refname = xmalloc(strlen(arg) + 1);
-
- if (check_ref_format(arg))
- return 1;
- collapse_slashes(refname, arg);
- printf("%s\n", refname);
- return 0;
-}
-
int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
{
+ int i;
+ int normalize = 0;
+ int flags = 0;
+ const char *refname;
+
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(builtin_check_ref_format_usage);
if (argc == 3 && !strcmp(argv[1], "--branch"))
return check_ref_format_branch(argv[2]);
- if (argc == 3 && !strcmp(argv[1], "--print"))
- return check_ref_format_print(argv[2]);
- if (argc != 2)
+
+ for (i = 1; i < argc && argv[i][0] == '-'; i++) {
+ if (!strcmp(argv[i], "--normalize") || !strcmp(argv[i], "--print"))
+ normalize = 1;
+ else if (!strcmp(argv[i], "--allow-onelevel"))
+ flags |= REFNAME_ALLOW_ONELEVEL;
+ else if (!strcmp(argv[i], "--no-allow-onelevel"))
+ flags &= ~REFNAME_ALLOW_ONELEVEL;
+ else if (!strcmp(argv[i], "--refspec-pattern"))
+ flags |= REFNAME_REFSPEC_PATTERN;
+ else
+ usage(builtin_check_ref_format_usage);
+ }
+ if (! (i == argc - 1))
usage(builtin_check_ref_format_usage);
- return !!check_ref_format(argv[1]);
+
+ refname = argv[i];
+ if (normalize)
+ refname = collapse_slashes(refname);
+ if (check_refname_format(refname, flags))
+ return 1;
+ if (normalize)
+ printf("%s\n", refname);
+
+ return 0;
}
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index c16d82b..031780f 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -5,7 +5,7 @@
*
*/
#include "builtin.h"
-#include "cache.h"
+#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
#include "parse-options.h"
@@ -14,11 +14,11 @@
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;
-static void write_tempfile_record(const char *name, int prefix_length)
+static void write_tempfile_record(const char *name, const char *prefix)
{
int i;
@@ -35,14 +35,14 @@ static void write_tempfile_record(const char *name, int prefix_length)
fputs(topath[checkout_stage], stdout);
putchar('\t');
- write_name_quoted(name + prefix_length, stdout, line_termination);
+ write_name_quoted_relative(name, prefix, stdout, line_termination);
for (i = 0; i < 4; i++) {
topath[i][0] = 0;
}
}
-static int checkout_file(const char *name, int prefix_length)
+static int checkout_file(const char *name, const char *prefix)
{
int namelen = strlen(name);
int pos = cache_name_pos(name, namelen);
@@ -71,7 +71,7 @@ static int checkout_file(const char *name, int prefix_length)
if (did_checkout) {
if (to_tempfile)
- write_tempfile_record(name, prefix_length);
+ write_tempfile_record(name, prefix);
return errs > 0 ? -1 : 0;
}
@@ -106,7 +106,7 @@ static void checkout_all(const char *prefix, int prefix_length)
if (last_ce && to_tempfile) {
if (ce_namelen(last_ce) != ce_namelen(ce)
|| memcmp(last_ce->name, ce->name, ce_namelen(ce)))
- write_tempfile_record(last_ce->name, prefix_length);
+ write_tempfile_record(last_ce->name, prefix);
}
if (checkout_entry(ce, &state,
to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
@@ -114,7 +114,7 @@ static void checkout_all(const char *prefix, int prefix_length)
last_ce = ce;
}
if (last_ce && to_tempfile)
- write_tempfile_record(last_ce->name, prefix_length);
+ write_tempfile_record(last_ce->name, prefix);
if (errs)
/* we have already done our error reporting.
* exit with the same code as die().
@@ -123,7 +123,7 @@ static void checkout_all(const char *prefix, int prefix_length)
}
static const char * const builtin_checkout_index_usage[] = {
- "git checkout-index [options] [--] [<file>...]",
+ N_("git checkout-index [options] [--] [<file>...]"),
NULL
};
@@ -135,6 +135,7 @@ static int option_parse_u(const struct option *opt,
int *newfd = opt->value;
state.refresh_cache = 1;
+ state.istate = &the_index;
if (*newfd < 0)
*newfd = hold_locked_index(&lock_file, 1);
return 0;
@@ -183,28 +184,28 @@ 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,
- "checks out all files in the index"),
- OPT__FORCE(&force, "forces overwrite of existing files"),
+ 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,
- "no warning for existing files and files not in index"),
- OPT_BOOLEAN('n', "no-create", &not_new,
- "don't checkout new files"),
+ N_("no warning for existing files and files not in index")),
+ OPT_BOOL('n', "no-create", &not_new,
+ N_("don't checkout new files")),
{ OPTION_CALLBACK, 'u', "index", &newfd, NULL,
- "update stat information in the index file",
+ N_("update stat information in the index file"),
PARSE_OPT_NOARG, option_parse_u },
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- "paths are separated with NUL character",
+ N_("paths are separated with NUL character"),
PARSE_OPT_NOARG, option_parse_z },
- OPT_BOOLEAN(0, "stdin", &read_from_stdin,
- "read list of paths from the standard input"),
- OPT_BOOLEAN(0, "temp", &to_tempfile,
- "write the content to temporary files"),
- OPT_CALLBACK(0, "prefix", NULL, "string",
- "when creating files, prepend <string>",
+ OPT_BOOL(0, "stdin", &read_from_stdin,
+ N_("read list of paths from the standard input")),
+ 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>"),
option_parse_prefix),
OPT_CALLBACK(0, "stage", NULL, NULL,
- "copy out the files from named stage",
+ N_("copy out the files from named stage"),
option_parse_stage),
OPT_END()
};
@@ -247,7 +248,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
if (read_from_stdin)
die("git checkout-index: don't mix '--stdin' and explicit filenames");
p = prefix_path(prefix, prefix_length, arg);
- checkout_file(p, prefix_length);
+ checkout_file(p, prefix);
if (p < arg || p > arg + strlen(arg))
free((char *)p);
}
@@ -267,7 +268,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
strbuf_swap(&buf, &nbuf);
}
p = prefix_path(prefix, prefix_length, buf.buf);
- checkout_file(p, prefix_length);
+ checkout_file(p, prefix);
if (p < buf.buf || p > buf.buf + buf.len)
free((char *)p);
}
@@ -279,8 +280,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
checkout_all(prefix, prefix_length);
if (0 <= newfd &&
- (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file)))
+ write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die("Unable to write new index file");
return 0;
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5e356a6..52d6cbb 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1,5 +1,5 @@
-#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "parse-options.h"
#include "refs.h"
#include "commit.h"
@@ -19,23 +19,23 @@
#include "ll-merge.h"
#include "resolve-undo.h"
#include "submodule.h"
+#include "argv-array.h"
static const char * const checkout_usage[] = {
- "git checkout [options] <branch>",
- "git checkout [options] [<branch>] -- <file>...",
+ N_("git checkout [options] <branch>"),
+ N_("git checkout [options] [<branch>] -- <file>..."),
NULL,
};
struct checkout_opts {
+ int patch_mode;
int quiet;
int merge;
int force;
int force_detach;
int writeout_stage;
- int writeout_error;
-
- /* not set by parse_options */
- int branch_exists;
+ int overwrite_ignore;
+ int ignore_skipworktree;
const char *new_branch;
const char *new_branch_force;
@@ -43,46 +43,67 @@ struct checkout_opts {
int new_branch_log;
enum branch_track track;
struct diff_options diff_options;
+
+ int branch_exists;
+ const char *prefix;
+ struct pathspec pathspec;
+ struct tree *source_tree;
};
static int post_checkout_hook(struct commit *old, struct commit *new,
int changed)
{
- return run_hook(NULL, "post-checkout",
- sha1_to_hex(old ? old->object.sha1 : null_sha1),
- sha1_to_hex(new ? new->object.sha1 : null_sha1),
- changed ? "1" : "0", NULL);
+ return run_hook_le(NULL, "post-checkout",
+ sha1_to_hex(old ? old->object.sha1 : null_sha1),
+ sha1_to_hex(new ? new->object.sha1 : null_sha1),
+ changed ? "1" : "0", NULL);
/* "new" can be NULL when checking out from the index before
a commit exists. */
}
-static int update_some(const unsigned char *sha1, const char *base, int baselen,
+static int update_some(const unsigned char *sha1, struct strbuf *base,
const char *pathname, unsigned mode, int stage, void *context)
{
int len;
struct cache_entry *ce;
+ int pos;
if (S_ISDIR(mode))
return READ_TREE_RECURSIVE;
- len = baselen + strlen(pathname);
+ len = base->len + strlen(pathname);
ce = xcalloc(1, cache_entry_size(len));
hashcpy(ce->sha1, sha1);
- memcpy(ce->name, base, baselen);
- memcpy(ce->name + baselen, pathname, len - baselen);
- ce->ce_flags = create_ce_flags(len, 0);
+ memcpy(ce->name, base->buf, base->len);
+ memcpy(ce->name + base->len, pathname, len - base->len);
+ ce->ce_flags = create_ce_flags(0) | CE_UPDATE;
+ ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
+
+ /*
+ * If the entry is the same as the current index, we can leave the old
+ * entry in place. Whether it is UPTODATE or not, checkout_entry will
+ * do the right thing.
+ */
+ pos = cache_name_pos(ce->name, ce->ce_namelen);
+ if (pos >= 0) {
+ struct cache_entry *old = active_cache[pos];
+ if (ce->ce_mode == old->ce_mode &&
+ !hashcmp(ce->sha1, old->sha1)) {
+ old->ce_flags |= CE_UPDATE;
+ free(ce);
+ return 0;
+ }
+ }
+
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
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
@@ -91,7 +112,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))
@@ -99,7 +120,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)) {
@@ -113,16 +134,21 @@ 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_all_stages(struct cache_entry *ce, int pos)
+static int check_stages(unsigned stages, const struct cache_entry *ce, int pos)
{
- if (ce_stage(ce) != 1 ||
- active_nr <= pos + 2 ||
- strcmp(active_cache[pos+1]->name, ce->name) ||
- ce_stage(active_cache[pos+1]) != 2 ||
- strcmp(active_cache[pos+2]->name, ce->name) ||
- ce_stage(active_cache[pos+2]) != 3)
- return error(_("path '%s' does not have all three versions"),
- ce->name);
+ unsigned seen = 0;
+ const char *name = ce->name;
+
+ while (pos < active_nr) {
+ ce = active_cache[pos];
+ if (strcmp(name, ce->name))
+ break;
+ seen |= (1 << ce_stage(ce));
+ pos++;
+ }
+ if ((stages & seen) != stages)
+ return error(_("path '%s' does not have all necessary versions"),
+ name);
return 0;
}
@@ -149,18 +175,27 @@ static int checkout_merged(int pos, struct checkout *state)
int status;
unsigned char sha1[20];
mmbuffer_t result_buf;
+ unsigned char threeway[3][20];
+ unsigned mode = 0;
+
+ memset(threeway, 0, sizeof(threeway));
+ while (pos < active_nr) {
+ int stage;
+ stage = ce_stage(ce);
+ if (!stage || strcmp(path, ce->name))
+ break;
+ hashcpy(threeway[stage - 1], ce->sha1);
+ if (stage == 2)
+ mode = create_ce_mode(ce->ce_mode);
+ pos++;
+ ce = active_cache[pos];
+ }
+ if (is_null_sha1(threeway[1]) || is_null_sha1(threeway[2]))
+ return error(_("path '%s' does not have necessary versions"), path);
- if (ce_stage(ce) != 1 ||
- active_nr <= pos + 2 ||
- strcmp(active_cache[pos+1]->name, path) ||
- ce_stage(active_cache[pos+1]) != 2 ||
- strcmp(active_cache[pos+2]->name, path) ||
- ce_stage(active_cache[pos+2]) != 3)
- return error(_("path '%s' does not have all 3 versions"), path);
-
- read_mmblob(&ancestor, active_cache[pos]->sha1);
- read_mmblob(&ours, active_cache[pos+1]->sha1);
- read_mmblob(&theirs, active_cache[pos+2]->sha1);
+ read_mmblob(&ancestor, threeway[0]);
+ read_mmblob(&ours, threeway[1]);
+ read_mmblob(&theirs, threeway[2]);
/*
* NEEDSWORK: re-create conflicts from merges with
@@ -191,17 +226,15 @@ static int checkout_merged(int pos, struct checkout *state)
if (write_sha1_file(result_buf.ptr, result_buf.size,
blob_type, sha1))
die(_("Unable to add merge result for '%s'"), path);
- ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode),
- sha1,
- path, 2, 0);
+ ce = make_cache_entry(mode, sha1, path, 2, 0);
if (!ce)
die(_("make_cache_entry failed for path '%s'"), path);
status = checkout_entry(ce, state, NULL);
return status;
}
-static int checkout_paths(struct tree *source_tree, const char **pathspec,
- const char *prefix, struct checkout_opts *opts)
+static int checkout_paths(const struct checkout_opts *opts,
+ const char *revision)
{
int pos;
struct checkout state;
@@ -210,46 +243,103 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
int flag;
struct commit *head;
int errs = 0;
- int stage = opts->writeout_stage;
- int merge = opts->merge;
- int newfd;
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+ struct lock_file *lock_file;
+
+ if (opts->track != BRANCH_TRACK_UNSPECIFIED)
+ die(_("'%s' cannot be used with updating paths"), "--track");
+
+ if (opts->new_branch_log)
+ die(_("'%s' cannot be used with updating paths"), "-l");
+
+ if (opts->force && opts->patch_mode)
+ die(_("'%s' cannot be used with updating paths"), "-f");
+
+ if (opts->force_detach)
+ die(_("'%s' cannot be used with updating paths"), "--detach");
+
+ if (opts->merge && opts->patch_mode)
+ die(_("'%s' cannot be used with %s"), "--merge", "--patch");
+
+ if (opts->force && opts->merge)
+ die(_("'%s' cannot be used with %s"), "-f", "-m");
- newfd = hold_locked_index(lock_file, 1);
- if (read_cache_preload(pathspec) < 0)
+ if (opts->new_branch)
+ die(_("Cannot update paths and switch to branch '%s' at the same time."),
+ opts->new_branch);
+
+ if (opts->patch_mode)
+ return run_add_interactive(revision, "--patch=checkout",
+ &opts->pathspec);
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ hold_locked_index(lock_file, 1);
+ if (read_cache_preload(&opts->pathspec) < 0)
return error(_("corrupt index file"));
- if (source_tree)
- read_tree_some(source_tree, pathspec);
+ if (opts->source_tree)
+ read_tree_some(opts->source_tree, &opts->pathspec);
- for (pos = 0; pathspec[pos]; pos++)
- ;
- ps_matched = xcalloc(1, pos);
+ ps_matched = xcalloc(1, opts->pathspec.nr);
+ /*
+ * Make sure all pathspecs participated in locating the paths
+ * to be checked out.
+ */
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
- match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
+ ce->ce_flags &= ~CE_MATCHED;
+ if (!opts->ignore_skipworktree && ce_skip_worktree(ce))
+ continue;
+ if (opts->source_tree && !(ce->ce_flags & CE_UPDATE))
+ /*
+ * "git checkout tree-ish -- path", but this entry
+ * is in the original index; it will not be checked
+ * out to the working tree and it does not matter
+ * if pathspec matched this entry. We will not do
+ * anything to this entry at all.
+ */
+ continue;
+ /*
+ * Either this entry came from the tree-ish we are
+ * checking the paths out of, or we are checking out
+ * of the index.
+ *
+ * If it comes from the tree-ish, we already know it
+ * matches the pathspec and could just stamp
+ * CE_MATCHED to it from update_some(). But we still
+ * need ps_matched and read_tree_recursive (and
+ * eventually tree_entry_interesting) cannot fill
+ * ps_matched yet. Once it can, we can avoid calling
+ * match_pathspec() for _all_ entries when
+ * opts->source_tree != NULL.
+ */
+ if (ce_path_match(ce, &opts->pathspec, ps_matched))
+ ce->ce_flags |= CE_MATCHED;
}
- if (report_path_error(ps_matched, pathspec, prefix))
+ if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
+ free(ps_matched);
return 1;
+ }
+ free(ps_matched);
/* "checkout -m path" to recreate conflicted state */
if (opts->merge)
- unmerge_cache(pathspec);
+ unmerge_marked_index(&the_index);
/* Any unmerged paths? */
for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
- if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
+ 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_all_stages(ce, pos);
+ errs |= check_stages((1<<2) | (1<<3), ce, pos);
} else {
errs = 1;
error(_("path '%s' is unmerged"), ce->name);
@@ -264,41 +354,41 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
memset(&state, 0, sizeof(state));
state.force = 1;
state.refresh_cache = 1;
+ state.istate = &the_index;
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
- if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
+ if (ce->ce_flags & CE_MATCHED) {
if (!ce_stage(ce)) {
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;
}
}
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
- resolve_ref("HEAD", rev, 0, &flag);
+ read_ref_full("HEAD", 0, rev, &flag);
head = lookup_commit_reference_gently(rev, 1);
errs |= post_checkout_hook(head, head, 0);
return errs;
}
-static void show_local_changes(struct object *head, struct diff_options *opts)
+static void show_local_changes(struct object *head,
+ const struct diff_options *opts)
{
struct rev_info rev;
/* I think we want full paths, even if we're in a subdirectory. */
init_revisions(&rev, NULL);
rev.diffopt.flags = opts->flags;
rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
- if (diff_setup_done(&rev.diffopt) < 0)
- die(_("diff_setup_done failed"));
+ diff_setup_done(&rev.diffopt);
add_pending_object(&rev, head, NULL);
run_diff_index(&rev, 0);
}
@@ -306,14 +396,15 @@ static void show_local_changes(struct object *head, struct diff_options *opts)
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);
}
-static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
+static int reset_tree(struct tree *tree, const struct checkout_opts *o,
+ int worktree, int *writeout_error)
{
struct unpack_trees_options opts;
struct tree_desc tree_desc;
@@ -325,14 +416,14 @@ static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
opts.reset = 1;
opts.merge = 1;
opts.fn = oneway_merge;
- opts.verbose_update = !o->quiet;
+ opts.verbose_update = !o->quiet && isatty(2);
opts.src_index = &the_index;
opts.dst_index = &the_index;
parse_tree(tree);
init_tree_desc(&tree_desc, tree->buffer, tree->size);
switch (unpack_trees(1, &tree_desc, &opts)) {
case -2:
- o->writeout_error = 1;
+ *writeout_error = 1;
/*
* We return 0 nevertheless, as the index is all right
* and more importantly we have made best efforts to
@@ -363,19 +454,21 @@ static void setup_branch_path(struct branch_info *branch)
branch->path = strbuf_detach(&buf, NULL);
}
-static int merge_working_tree(struct checkout_opts *opts,
- struct branch_info *old, struct branch_info *new)
+static int merge_working_tree(const struct checkout_opts *opts,
+ struct branch_info *old,
+ struct branch_info *new,
+ int *writeout_error)
{
int ret;
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
- int newfd = hold_locked_index(lock_file, 1);
+ hold_locked_index(lock_file, 1);
if (read_cache_preload(NULL) < 0)
return error(_("corrupt index file"));
resolve_undo_clear();
if (opts->force) {
- ret = reset_tree(new->commit->tree, opts, 1);
+ ret = reset_tree(new->commit->tree, opts, 1, writeout_error);
if (ret)
return ret;
} else {
@@ -402,11 +495,13 @@ static int merge_working_tree(struct checkout_opts *opts,
topts.update = 1;
topts.merge = 1;
topts.gently = opts->merge && old->commit;
- topts.verbose_update = !opts->quiet;
+ topts.verbose_update = !opts->quiet && isatty(2);
topts.fn = twoway_merge;
- topts.dir = xcalloc(1, sizeof(*topts.dir));
- topts.dir->flags |= DIR_SHOW_IGNORED;
- topts.dir->exclude_per_dir = ".gitignore";
+ if (opts->overwrite_ignore) {
+ topts.dir = xcalloc(1, sizeof(*topts.dir));
+ topts.dir->flags |= DIR_SHOW_IGNORED;
+ setup_standard_excludes(topts.dir);
+ }
tree = parse_tree_indirect(old->commit ?
old->commit->object.sha1 :
EMPTY_TREE_SHA1_BIN);
@@ -459,7 +554,8 @@ static int merge_working_tree(struct checkout_opts *opts,
o.verbosity = 0;
work = write_tree_from_memory(&o);
- ret = reset_tree(new->commit->tree, opts, 1);
+ ret = reset_tree(new->commit->tree, opts, 1,
+ writeout_error);
if (ret)
return ret;
o.ancestor = old->name;
@@ -467,14 +563,20 @@ static int merge_working_tree(struct checkout_opts *opts,
o.branch2 = "local";
merge_trees(&o, new->commit->tree, work,
old->commit->tree, &result);
- ret = reset_tree(new->commit->tree, opts, 0);
+ ret = reset_tree(new->commit->tree, opts, 0,
+ writeout_error);
if (ret)
return ret;
}
}
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
+ if (!active_cache_tree)
+ active_cache_tree = cache_tree();
+
+ if (!cache_tree_fully_valid(active_cache_tree))
+ cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
+
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
if (!opts->force && !opts->quiet)
@@ -494,26 +596,12 @@ static void report_tracking(struct branch_info *new)
strbuf_release(&sb);
}
-static void detach_advice(const char *old_path, const char *new_name)
-{
- const char fmt[] =
- "Note: checking out '%s'.\n\n"
- "You are in 'detached HEAD' state. You can look around, make experimental\n"
- "changes and commit them, and you can discard any commits you make in this\n"
- "state without impacting any branches by performing another checkout.\n\n"
- "If you want to create a new branch to retain commits you create, you may\n"
- "do so (now or later) by using -b with the checkout command again. Example:\n\n"
- " git checkout -b new_branch_name\n\n";
-
- fprintf(stderr, fmt, new_name);
-}
-
-static void update_refs_for_switch(struct checkout_opts *opts,
+static void update_refs_for_switch(const struct checkout_opts *opts,
struct branch_info *old,
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) {
@@ -535,7 +623,10 @@ static void update_refs_for_switch(struct checkout_opts *opts,
else
create_branch(old->name, opts->new_branch, new->name,
opts->new_branch_force ? 1 : 0,
- opts->new_branch_log, opts->track);
+ opts->new_branch_log,
+ opts->new_branch_force ? 1 : 0,
+ opts->quiet,
+ opts->track);
new->name = opts->new_branch;
setup_branch_path(new);
}
@@ -543,25 +634,34 @@ static void update_refs_for_switch(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. */
} else if (opts->force_detach || !new->path) { /* No longer on any branch. */
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
- REF_NODEREF, DIE_ON_ERR);
+ REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
if (!opts->quiet) {
if (old->path && advice_detached_head)
- detach_advice(old->path, new->name);
+ detach_advice(new->name);
describe_detached_head(_("HEAD is now at"), new->commit);
}
} else if (new->path) { /* Switch branches. */
create_symref("HEAD", new->path, msg.buf);
if (!opts->quiet) {
if (old->path && !strcmp(new->path, old->path)) {
- fprintf(stderr, _("Already on '%s'\n"),
- new->name);
+ if (opts->new_branch_force)
+ fprintf(stderr, _("Reset branch '%s'\n"),
+ new->name);
+ else
+ fprintf(stderr, _("Already on '%s'\n"),
+ new->name);
} else if (opts->new_branch) {
if (opts->branch_exists)
fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name);
@@ -573,12 +673,8 @@ static void update_refs_for_switch(struct checkout_opts *opts,
}
}
if (old->path && old->name) {
- char log_file[PATH_MAX], ref_file[PATH_MAX];
-
- git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
- git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
- if (!file_exists(ref_file) && file_exists(log_file))
- remove_path(log_file);
+ if (!ref_exists(old->path) && reflog_exists(old->path))
+ delete_reflog(old->path);
}
}
remove_branch_state();
@@ -588,46 +684,22 @@ static void update_refs_for_switch(struct checkout_opts *opts,
report_tracking(new);
}
-struct rev_list_args {
- int argc;
- int alloc;
- const char **argv;
-};
-
-static void add_one_rev_list_arg(struct rev_list_args *args, const char *s)
-{
- ALLOC_GROW(args->argv, args->argc + 1, args->alloc);
- args->argv[args->argc++] = s;
-}
-
-static int add_one_ref_to_rev_list_arg(const char *refname,
- const unsigned char *sha1,
- int flags,
- void *cb_data)
-{
- add_one_rev_list_arg(cb_data, refname);
- return 0;
-}
-
-static int clear_commit_marks_from_one_ref(const char *refname,
- const unsigned char *sha1,
- int flags,
- void *cb_data)
+static int add_pending_uninteresting_ref(const char *refname,
+ const unsigned char *sha1,
+ int flags, void *cb_data)
{
- struct commit *commit = lookup_commit_reference_gently(sha1, 1);
- if (commit)
- clear_commit_marks(commit, -1);
+ add_pending_sha1(cb_data, refname, sha1, UNINTERESTING);
return 0;
}
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');
}
@@ -675,7 +747,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
"If you want to keep them by creating a new branch, "
"this may be a good time\nto do so with:\n\n"
" git branch new_branch_name %s\n\n"),
- sha1_to_hex(commit->object.sha1));
+ find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
}
/*
@@ -683,69 +755,74 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
* HEAD. If it is not reachable from any ref, this is the last chance
* for the user to do so without resorting to reflog.
*/
-static void orphaned_commit_warning(struct commit *commit)
+static void orphaned_commit_warning(struct commit *old, struct commit *new)
{
- struct rev_list_args args = { 0, 0, NULL };
struct rev_info revs;
-
- add_one_rev_list_arg(&args, "(internal)");
- add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1));
- add_one_rev_list_arg(&args, "--not");
- for_each_ref(add_one_ref_to_rev_list_arg, &args);
- add_one_rev_list_arg(&args, "--");
- add_one_rev_list_arg(&args, NULL);
+ struct object *object = &old->object;
+ struct object_array refs;
init_revisions(&revs, NULL);
- if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1)
- die(_("internal error: only -- alone should have been left"));
+ setup_revisions(0, NULL, &revs, NULL);
+
+ object->flags &= ~UNINTERESTING;
+ add_pending_object(&revs, object, sha1_to_hex(object->sha1));
+
+ for_each_ref(add_pending_uninteresting_ref, &revs);
+ add_pending_sha1(&revs, "HEAD", new->object.sha1, UNINTERESTING);
+
+ refs = revs.pending;
+ revs.leak_pending = 1;
+
if (prepare_revision_walk(&revs))
die(_("internal error in revision walk"));
- if (!(commit->object.flags & UNINTERESTING))
- suggest_reattach(commit, &revs);
+ if (!(old->object.flags & UNINTERESTING))
+ suggest_reattach(old, &revs);
else
- describe_detached_head(_("Previous HEAD position was"), commit);
+ describe_detached_head(_("Previous HEAD position was"), old);
- clear_commit_marks(commit, -1);
- for_each_ref(clear_commit_marks_from_one_ref, NULL);
+ clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
+ free(refs.objects);
}
-static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
+static int switch_branches(const struct checkout_opts *opts,
+ struct branch_info *new)
{
int ret = 0;
struct branch_info old;
+ void *path_to_free;
unsigned char rev[20];
- int flag;
+ int flag, writeout_error = 0;
memset(&old, 0, sizeof(old));
- old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag));
+ old.path = path_to_free = resolve_refdup("HEAD", 0, rev, &flag);
old.commit = lookup_commit_reference_gently(rev, 1);
- if (!(flag & REF_ISSYMREF)) {
- free((char *)old.path);
+ if (!(flag & REF_ISSYMREF))
old.path = NULL;
- }
- if (old.path && !prefixcmp(old.path, "refs/heads/"))
- old.name = old.path + strlen("refs/heads/");
+ if (old.path)
+ skip_prefix(old.path, "refs/heads/", &old.name);
if (!new->name) {
new->name = "HEAD";
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);
- if (ret)
+ ret = merge_working_tree(opts, &old, new, &writeout_error);
+ if (ret) {
+ free(path_to_free);
return ret;
+ }
if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
- orphaned_commit_warning(old.commit);
+ orphaned_commit_warning(old.commit, new->commit);
update_refs_for_switch(opts, &old, new);
ret = post_checkout_hook(old.commit, new->commit, 1);
- free((char *)old.path);
- return ret || opts->writeout_error;
+ free(path_to_free);
+ return ret || writeout_error;
}
static int git_checkout_config(const char *var, const char *value, void *cb)
@@ -756,51 +833,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);
}
-static int interactive_checkout(const char *revision, const char **pathspec,
- struct checkout_opts *opts)
-{
- return run_add_interactive(revision, "--patch=checkout", pathspec);
-}
-
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;
}
@@ -814,7 +890,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>]
@@ -826,20 +904,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> [--]
+ *
+ * (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 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.
+ * (b) If <something> is _not_ a commit, either "--" is present
+ * or <something> is not a path, no -t or -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.
*
- * 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.
+ * (c) Otherwise, if "--" is present, treat it like case (1).
*
- * Otherwise <something> shall not be ambiguous.
+ * (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.
@@ -848,28 +936,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;
}
}
@@ -882,8 +1001,8 @@ static int parse_branchname_arg(int argc, const char **argv,
new->name = arg;
setup_branch_path(new);
- if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
- resolve_ref(new->path, branch_rev, 1, NULL))
+ if (!check_refname_format(new->path, 0) &&
+ !read_ref(new->path, branch_rev))
hashcpy(rev, branch_rev);
else
new->path = NULL; /* not an existing branch */
@@ -893,13 +1012,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
@@ -917,43 +1036,105 @@ static int parse_branchname_arg(int argc, const char **argv,
return argcount;
}
+static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
+{
+ int status;
+ struct strbuf branch_ref = STRBUF_INIT;
+
+ if (!opts->new_branch)
+ die(_("You are on a branch yet to be born"));
+ strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
+ status = create_symref("HEAD", branch_ref.buf, "checkout -b");
+ strbuf_release(&branch_ref);
+ if (!opts->quiet)
+ fprintf(stderr, _("Switched to a new branch '%s'\n"),
+ opts->new_branch);
+ return status;
+}
+
+static int checkout_branch(struct checkout_opts *opts,
+ struct branch_info *new)
+{
+ if (opts->pathspec.nr)
+ die(_("paths cannot be used with switching branches"));
+
+ if (opts->patch_mode)
+ die(_("'%s' cannot be used with switching branches"),
+ "--patch");
+
+ if (opts->writeout_stage)
+ die(_("'%s' cannot be used with switching branches"),
+ "--ours/--theirs");
+
+ if (opts->force && opts->merge)
+ die(_("'%s' cannot be used with '%s'"), "-f", "-m");
+
+ if (opts->force_detach && opts->new_branch)
+ die(_("'%s' cannot be used with '%s'"),
+ "--detach", "-b/-B/--orphan");
+
+ if (opts->new_orphan_branch) {
+ if (opts->track != BRANCH_TRACK_UNSPECIFIED)
+ die(_("'%s' cannot be used with '%s'"), "--orphan", "-t");
+ } else if (opts->force_detach) {
+ if (opts->track != BRANCH_TRACK_UNSPECIFIED)
+ die(_("'%s' cannot be used with '%s'"), "--detach", "-t");
+ } else if (opts->track == BRANCH_TRACK_UNSPECIFIED)
+ opts->track = git_branch_track;
+
+ if (new->name && !new->commit)
+ die(_("Cannot switch branch to a non-commit '%s'"),
+ new->name);
+
+ if (!new->commit && opts->new_branch) {
+ unsigned char rev[20];
+ int flag;
+
+ if (!read_ref_full("HEAD", 0, rev, &flag) &&
+ (flag & REF_ISSYMREF) && is_null_sha1(rev))
+ return switch_unborn_to_new_branch(opts);
+ }
+ return switch_branches(opts, new);
+}
+
int cmd_checkout(int argc, const char **argv, const char *prefix)
{
struct checkout_opts opts;
- unsigned char rev[20];
struct branch_info new;
- struct tree *source_tree = NULL;
char *conflict_style = NULL;
- int patch_mode = 0;
int dwim_new_local_branch = 1;
struct option options[] = {
- OPT__QUIET(&opts.quiet, "suppress progress reporting"),
- OPT_STRING('b', NULL, &opts.new_branch, "branch",
- "create and checkout a new branch"),
- OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
- "create/reset and checkout a branch"),
- OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"),
- OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"),
- OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch",
+ OPT__QUIET(&opts.quiet, N_("suppress progress reporting")),
+ OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+ N_("create and checkout a new branch")),
+ OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
+ N_("create/reset and checkout a branch")),
+ OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
+ OPT_BOOL(0, "detach", &opts.force_detach, N_("detach 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, "new branch", "new unparented branch"),
- OPT_SET_INT('2', "ours", &opts.writeout_stage, "checkout our version for unmerged files",
+ OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
+ OPT_SET_INT('2', "ours", &opts.writeout_stage, N_("checkout our version for unmerged files"),
2),
- OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files",
+ OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"),
3),
- OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"),
- OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"),
- OPT_STRING(0, "conflict", &conflict_style, "style",
- "conflict style (merge or diff3)"),
- OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
- { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
- "second guess 'git checkout no-such-branch'",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
+ OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)")),
+ 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_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
+ OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
+ N_("do not limit pathspecs to sparse entries only")),
+ OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
+ N_("second guess 'git checkout no-such-branch'")),
OPT_END(),
};
memset(&opts, 0, sizeof(opts));
memset(&new, 0, sizeof(new));
+ opts.overwrite_ignore = 1;
+ opts.prefix = prefix;
gitmodules_config();
git_config(git_checkout_config, &opts);
@@ -963,55 +1144,38 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
- /* we can assume from now on new_branch = !new_branch_force */
- if (opts.new_branch && opts.new_branch_force)
- die(_("-B cannot be used with -b"));
+ if (conflict_style) {
+ opts.merge = 1; /* implied */
+ git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+ }
+
+ if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1)
+ die(_("-b, -B and --orphan are mutually exclusive"));
- /* copy -B over to -b, so that we can just check the latter */
+ /*
+ * From here on, new_branch will contain the branch to be checked out,
+ * and new_branch_force and new_orphan_branch will tell us which one of
+ * -b/-B/--orphan is being used.
+ */
if (opts.new_branch_force)
opts.new_branch = opts.new_branch_force;
- if (patch_mode && (opts.track > 0 || opts.new_branch
- || opts.new_branch_log || opts.merge || opts.force
- || opts.force_detach))
- die (_("--patch is incompatible with all other options"));
-
- if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch))
- die(_("--detach cannot be used with -b/-B/--orphan"));
- if (opts.force_detach && 0 < opts.track)
- die(_("--detach cannot be used with -t"));
+ if (opts.new_orphan_branch)
+ opts.new_branch = opts.new_orphan_branch;
- /* --track without -b should DWIM */
- if (0 < opts.track && !opts.new_branch) {
+ /* --track without -b/-B/--orphan should DWIM */
+ if (opts.track != BRANCH_TRACK_UNSPECIFIED && !opts.new_branch) {
const char *argv0 = argv[0];
if (!argc || !strcmp(argv0, "--"))
die (_("--track needs a branch name"));
- if (!prefixcmp(argv0, "refs/"))
- argv0 += 5;
- if (!prefixcmp(argv0, "remotes/"))
- argv0 += 8;
+ skip_prefix(argv0, "refs/", &argv0);
+ skip_prefix(argv0, "remotes/", &argv0);
argv0 = strchr(argv0, '/');
if (!argv0 || !argv0[1])
die (_("Missing branch name; try -b"));
opts.new_branch = argv0 + 1;
}
- if (opts.new_orphan_branch) {
- if (opts.new_branch)
- die(_("--orphan and -b|-B are mutually exclusive"));
- if (opts.track > 0)
- die(_("--orphan cannot be used with -t"));
- opts.new_branch = opts.new_orphan_branch;
- }
-
- if (conflict_style) {
- opts.merge = 1; /* implied */
- git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
- }
-
- if (opts.force && opts.merge)
- die(_("git checkout: -f and -m are incompatible"));
-
/*
* Extract branch name from command line arguments, so
* all that is left is pathspecs.
@@ -1026,64 +1190,58 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
* remote branches, erroring out for invalid or ambiguous cases.
*/
if (argc) {
+ unsigned char rev[20];
int dwim_ok =
- !patch_mode &&
+ !opts.patch_mode &&
dwim_new_local_branch &&
opts.track == BRANCH_TRACK_UNSPECIFIED &&
!opts.new_branch;
int n = parse_branchname_arg(argc, argv, dwim_ok,
- &new, &source_tree, rev, &opts.new_branch);
+ &new, &opts.source_tree,
+ rev, &opts.new_branch);
argv += n;
argc -= n;
}
- if (opts.track == BRANCH_TRACK_UNSPECIFIED)
- opts.track = git_branch_track;
-
if (argc) {
- const char **pathspec = get_pathspec(prefix, argv);
+ parse_pathspec(&opts.pathspec, 0,
+ opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
+ prefix, argv);
- if (!pathspec)
+ if (!opts.pathspec.nr)
die(_("invalid path specification"));
- if (patch_mode)
- return interactive_checkout(new.name, pathspec, &opts);
-
- /* Checkout paths */
- if (opts.new_branch) {
- if (argc == 1) {
- die(_("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?"), argv[0]);
- } else {
- die(_("git checkout: updating paths is incompatible with switching branches."));
- }
- }
+ /*
+ * Try to give more helpful suggestion.
+ * new_branch && argc > 1 will be caught later.
+ */
+ if (opts.new_branch && argc == 1)
+ die(_("Cannot update paths and switch to branch '%s' at the same time.\n"
+ "Did you intend to checkout '%s' which can not be resolved as commit?"),
+ opts.new_branch, argv[0]);
if (opts.force_detach)
- die(_("git checkout: --detach does not take a path argument"));
+ die(_("git checkout: --detach does not take a path argument '%s'"),
+ argv[0]);
if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
- die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."));
-
- return checkout_paths(source_tree, pathspec, prefix, &opts);
+ die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n"
+ "checking out of the index."));
}
- if (patch_mode)
- return interactive_checkout(new.name, NULL, &opts);
-
if (opts.new_branch) {
struct strbuf buf = STRBUF_INIT;
- opts.branch_exists = validate_new_branchname(opts.new_branch, &buf,
- !!opts.new_branch_force, 0);
+ opts.branch_exists =
+ validate_new_branchname(opts.new_branch, &buf,
+ !!opts.new_branch_force,
+ !!opts.new_branch_force);
strbuf_release(&buf);
}
- if (new.name && !new.commit) {
- die(_("Cannot switch branch to a non-commit."));
- }
- if (opts.writeout_stage)
- die(_("--ours/--theirs is incompatible with switching branches."));
-
- return switch_branches(&opts, &new);
+ 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 0c7b3d0..7e7fdcf 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -10,21 +10,135 @@
#include "cache.h"
#include "dir.h"
#include "parse-options.h"
+#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[] = {
- "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
};
+static const char *msg_remove = N_("Removing %s\n");
+static const char *msg_would_remove = N_("Would remove %s\n");
+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)(void);
+};
+
+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"))
+ const char *slot_name;
+
+ 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 (skip_prefix(var, "color.interactive.", &slot_name)) {
+ int slot = parse_clean_color_slot(slot_name);
+ if (slot < 0)
+ return 0;
+ if (!value)
+ return config_error_nonbool(var);
+ return color_parse(value, clean_colors[slot]);
+ }
+
+ 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)
@@ -34,30 +148,740 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset)
return 0;
}
-int cmd_clean(int argc, const char **argv, const char *prefix)
+static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
+ int dry_run, int quiet, int *dir_gone)
+{
+ DIR *dir;
+ struct strbuf quoted = STRBUF_INIT;
+ struct dirent *e;
+ int res = 0, ret = 0, gone = 1, original_len = path->len, len;
+ unsigned char submodule_head[20];
+ struct string_list dels = STRING_LIST_INIT_DUP;
+
+ *dir_gone = 1;
+
+ if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
+ !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
+ if (!quiet) {
+ quote_path_relative(path->buf, prefix, &quoted);
+ printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
+ quoted.buf);
+ }
+
+ *dir_gone = 0;
+ return 0;
+ }
+
+ dir = opendir(path->buf);
+ if (!dir) {
+ /* 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, prefix, &quoted);
+ warning(_(msg_warn_remove_failed), quoted.buf);
+ *dir_gone = 0;
+ }
+ return res;
+ }
+
+ if (path->buf[original_len - 1] != '/')
+ strbuf_addch(path, '/');
+
+ len = path->len;
+ while ((e = readdir(dir)) != NULL) {
+ struct stat st;
+ if (is_dot_or_dotdot(e->d_name))
+ continue;
+
+ strbuf_setlen(path, len);
+ strbuf_addstr(path, e->d_name);
+ if (lstat(path->buf, &st))
+ ; /* fall thru */
+ else if (S_ISDIR(st.st_mode)) {
+ if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
+ ret = 1;
+ if (gone) {
+ quote_path_relative(path->buf, prefix, &quoted);
+ string_list_append(&dels, quoted.buf);
+ } else
+ *dir_gone = 0;
+ continue;
+ } else {
+ res = dry_run ? 0 : unlink(path->buf);
+ if (!res) {
+ quote_path_relative(path->buf, prefix, &quoted);
+ string_list_append(&dels, quoted.buf);
+ } else {
+ quote_path_relative(path->buf, prefix, &quoted);
+ warning(_(msg_warn_remove_failed), quoted.buf);
+ *dir_gone = 0;
+ ret = 1;
+ }
+ continue;
+ }
+
+ /* path too long, stat fails, or non-directory still exists */
+ *dir_gone = 0;
+ ret = 1;
+ break;
+ }
+ closedir(dir);
+
+ strbuf_setlen(path, original_len);
+
+ if (*dir_gone) {
+ res = dry_run ? 0 : rmdir(path->buf);
+ if (!res)
+ *dir_gone = 1;
+ else {
+ quote_path_relative(path->buf, prefix, &quoted);
+ warning(_(msg_warn_remove_failed), quoted.buf);
+ *dir_gone = 0;
+ ret = 1;
+ }
+ }
+
+ 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);
+ }
+ string_list_clear(&dels, 0);
+ 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_stuff 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;
- int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
- int ignored_only = 0, config_set = 0, errors = 0;
+
+ 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 = xcalloc(nr + 1, sizeof(int));
+ 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, "do not print names of files removed"),
- OPT__DRY_RUN(&show_only, "dry run"),
- OPT__FORCE(&force, "force"),
- OPT_BOOLEAN('d', NULL, &remove_directories,
- "remove whole directories"),
- { OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern",
- "add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb },
- OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
- OPT_BOOLEAN('X', NULL, &ignored_only,
- "remove only ignored files"),
+ 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_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_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
+ OPT_BOOL('X', NULL, &ignored_only,
+ N_("remove only ignored files")),
OPT_END()
};
@@ -77,13 +901,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (ignored && ignored_only)
die(_("-x and -X cannot be used together"));
- if (!show_only && !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; "
- "refusing to clean"));
+ die(_("clean.requireForce defaults to true and neither -i, -n, nor -f given;"
+ " refusing to clean"));
}
if (force > 1)
@@ -97,96 +921,85 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (!ignored)
setup_standard_excludes(&dir);
+ el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
for (i = 0; i < exclude_list.nr; i++)
- add_exclude(exclude_list.items[i].string, "", 0,
- &dir.exclude_list[EXC_CMDL]);
-
- pathspec = get_pathspec(prefix, argv);
+ add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
- 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 (pathspec.nr && !matches)
+ continue;
+
+ if (S_ISDIR(st.st_mode) && !remove_directories &&
+ matches != MATCHED_EXACTLY)
+ 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);
- qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
- if (show_only && (remove_directories ||
- (matches == MATCHED_EXACTLY))) {
- printf(_("Would remove %s\n"), qname);
- } else if (remove_directories ||
- (matches == MATCHED_EXACTLY)) {
- if (!quiet)
- printf(_("Removing %s\n"), qname);
- if (remove_dir_recursively(&directory,
- rm_flags) != 0) {
- warning(_("failed to remove %s"), qname);
- errors++;
- }
- } else if (show_only) {
- printf(_("Would not remove %s\n"), qname);
- } else {
- printf(_("Not removing %s\n"), 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;
- qname = quote_path_relative(ent->name, -1, &buf, prefix);
- if (show_only) {
- printf(_("Would remove %s\n"), qname);
- continue;
- } else if (!quiet) {
- printf(_("Removing %s\n"), qname);
- }
- if (unlink(ent->name) != 0) {
- warning(_("failed to remove %s"), qname);
+ res = dry_run ? 0 : unlink(abs_path.buf);
+ if (res) {
+ qname = quote_path_relative(item->string, NULL, &buf);
+ warning(_(msg_warn_remove_failed), qname);
errors++;
+ } else if (!quiet) {
+ 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 488f48e..2d77a2d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -9,6 +9,7 @@
*/
#include "builtin.h"
+#include "lockfile.h"
#include "parse-options.h"
#include "fetch-pack.h"
#include "refs.h"
@@ -18,11 +19,11 @@
#include "transport.h"
#include "strbuf.h"
#include "dir.h"
-#include "pack-refs.h"
#include "sigchain.h"
#include "branch.h"
#include "remote.h"
#include "run-command.h"
+#include "connected.h"
/*
* Overall FIXMEs:
@@ -33,21 +34,22 @@
*
*/
static const char * const builtin_clone_usage[] = {
- "git clone [options] [--] <repo> [<dir>]",
+ N_("git clone [options] [--] <repo> [<dir>]"),
NULL
};
-static int option_no_checkout, option_bare, option_mirror;
-static int option_local, option_no_hardlinks, option_shared, option_recursive;
+static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
+static int option_local = -1, option_no_hardlinks, option_shared, option_recursive;
static char *option_template, *option_depth;
static char *option_origin = NULL;
static char *option_branch = NULL;
static const char *real_git_dir;
static char *option_upload_pack = "git-upload-pack";
static int option_verbosity;
-static int option_progress;
+static int option_progress = -1;
static struct string_list option_config;
static struct string_list option_reference;
+static int option_dissociate;
static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
{
@@ -60,42 +62,45 @@ static int opt_parse_reference(const struct option *opt, const char *arg, int un
static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
- OPT_BOOLEAN(0, "progress", &option_progress,
- "force progress reporting"),
- OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
- "don't create a checkout"),
- OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
- { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL,
- "create a bare repository",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- OPT_BOOLEAN(0, "mirror", &option_mirror,
- "create a mirror repository (implies bare)"),
- OPT_BOOLEAN('l', "local", &option_local,
- "to clone from a local repository"),
- OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
- "don't use local hardlinks, always copy"),
- OPT_BOOLEAN('s', "shared", &option_shared,
- "setup as shared repository"),
- OPT_BOOLEAN(0, "recursive", &option_recursive,
- "initialize submodules in the clone"),
- OPT_BOOLEAN(0, "recurse-submodules", &option_recursive,
- "initialize submodules in the clone"),
- OPT_STRING(0, "template", &option_template, "template-directory",
- "directory from which templates will be used"),
- OPT_CALLBACK(0 , "reference", &option_reference, "repo",
- "reference repository", &opt_parse_reference),
- OPT_STRING('o', "origin", &option_origin, "branch",
- "use <branch> instead of 'origin' to track upstream"),
- OPT_STRING('b', "branch", &option_branch, "branch",
- "checkout <branch> instead of the remote's HEAD"),
- OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
- "path to git-upload-pack on the remote"),
- OPT_STRING(0, "depth", &option_depth, "depth",
- "create a shallow clone of that depth"),
- OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
- "separate git dir from working tree"),
- OPT_STRING_LIST('c', "config", &option_config, "key=value",
- "set config inside the new repository"),
+ OPT_BOOL(0, "progress", &option_progress,
+ N_("force progress reporting")),
+ 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_BOOL(0, "no-hardlinks", &option_no_hardlinks,
+ N_("don't use local hardlinks, always copy")),
+ OPT_BOOL('s', "shared", &option_shared,
+ N_("setup as shared repository")),
+ OPT_BOOL(0, "recursive", &option_recursive,
+ N_("initialize submodules in the clone")),
+ 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")),
+ OPT_CALLBACK(0 , "reference", &option_reference, N_("repo"),
+ N_("reference repository"), &opt_parse_reference),
+ OPT_STRING('o', "origin", &option_origin, N_("name"),
+ N_("use <name> instead of 'origin' to track upstream")),
+ OPT_STRING('b', "branch", &option_branch, N_("branch"),
+ N_("checkout <branch> instead of the remote's HEAD")),
+ OPT_STRING('u', "upload-pack", &option_upload_pack, N_("path"),
+ N_("path to git-upload-pack on the remote")),
+ OPT_STRING(0, "depth", &option_depth, N_("depth"),
+ N_("create a shallow clone of that depth")),
+ OPT_BOOL(0, "single-branch", &option_single_branch,
+ N_("clone only one branch, HEAD or --branch")),
+ OPT_BOOL(0, "dissociate", &option_dissociate,
+ N_("use --reference only while cloning")),
+ OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
+ N_("separate git dir from working tree")),
+ OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
+ N_("set config inside the new repository")),
OPT_END()
};
@@ -105,7 +110,7 @@ static const char *argv_submodule[] = {
static char *get_repo_path(const char *repo, int *is_bundle)
{
- static char *suffix[] = { "/.git", ".git", "" };
+ static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
static char *bundle_suffix[] = { ".bundle", "" };
struct stat st;
int i;
@@ -115,7 +120,7 @@ static char *get_repo_path(const char *repo, int *is_bundle)
path = mkpath("%s%s", repo, suffix[i]);
if (stat(path, &st))
continue;
- if (S_ISDIR(st.st_mode)) {
+ if (S_ISDIR(st.st_mode) && is_git_directory(path)) {
*is_bundle = 0;
return xstrdup(absolute_path(path));
} else if (S_ISREG(st.st_mode) && st.st_size > 8) {
@@ -229,32 +234,37 @@ static void strip_trailing_slashes(char *dir)
static int add_one_reference(struct string_list_item *item, void *cb_data)
{
char *ref_git;
+ const char *repo;
struct strbuf alternate = STRBUF_INIT;
- struct remote *remote;
- struct transport *transport;
- const struct ref *extra;
- /* Beware: real_path() and mkpath() return static buffer */
+ /* Beware: read_gitfile(), real_path() and mkpath() return static buffer */
ref_git = xstrdup(real_path(item->string));
- if (is_directory(mkpath("%s/.git/objects", ref_git))) {
- char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git));
+
+ repo = read_gitfile(ref_git);
+ if (!repo)
+ repo = read_gitfile(mkpath("%s/.git", ref_git));
+ if (repo) {
+ free(ref_git);
+ ref_git = xstrdup(repo);
+ }
+
+ if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) {
+ char *ref_git_git = mkpathdup("%s/.git", ref_git);
free(ref_git);
ref_git = ref_git_git;
} else if (!is_directory(mkpath("%s/objects", ref_git)))
- die(_("reference repository '%s' is not a local directory."),
+ 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);
-
- remote = remote_get(ref_git);
- transport = transport_get(remote, ref_git);
- for (extra = transport_get_remote_refs(transport); extra;
- extra = extra->next)
- add_extra_ref(extra->name, extra->old_sha1, 0);
-
- transport_disconnect(transport);
free(ref_git);
return 0;
}
@@ -351,7 +361,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
if (!option_no_hardlinks) {
if (!link(src->buf, dest->buf))
continue;
- if (option_local)
+ if (option_local > 0)
die_errno(_("failed to create link '%s'"), dest->buf);
option_no_hardlinks = 1;
}
@@ -361,13 +371,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
closedir(dir);
}
-static const struct ref *clone_local(const char *src_repo,
- const char *dest_repo)
+static void clone_local(const char *src_repo, const char *dest_repo)
{
- const struct ref *ret;
- struct remote *remote;
- struct transport *transport;
-
if (option_shared) {
struct strbuf alt = STRBUF_INIT;
strbuf_addf(&alt, "%s/objects", src_repo);
@@ -383,24 +388,38 @@ static const struct ref *clone_local(const char *src_repo,
strbuf_release(&dest);
}
- remote = remote_get(src_repo);
- transport = transport_get(remote, src_repo);
- ret = transport_get_remote_refs(transport);
- transport_disconnect(transport);
if (0 <= option_verbosity)
- printf(_("done.\n"));
- return ret;
+ fprintf(stderr, _("done.\n"));
}
static const char *junk_work_tree;
static const char *junk_git_dir;
-static pid_t junk_pid;
+static enum {
+ JUNK_LEAVE_NONE,
+ JUNK_LEAVE_REPO,
+ JUNK_LEAVE_ALL
+} junk_mode = JUNK_LEAVE_NONE;
+
+static const char junk_leave_repo_msg[] =
+N_("Clone succeeded, but checkout failed.\n"
+ "You can inspect what was checked out with 'git status'\n"
+ "and retry the checkout with 'git checkout -f HEAD'\n");
static void remove_junk(void)
{
struct strbuf sb = STRBUF_INIT;
- if (getpid() != junk_pid)
+
+ switch (junk_mode) {
+ case JUNK_LEAVE_REPO:
+ warning("%s", _(junk_leave_repo_msg));
+ /* fall-through */
+ case JUNK_LEAVE_ALL:
return;
+ default:
+ /* proceed to removal */
+ break;
+ }
+
if (junk_git_dir) {
strbuf_addstr(&sb, junk_git_dir);
remove_dir_recursively(&sb, 0);
@@ -420,6 +439,26 @@ static void remove_junk_on_signal(int signo)
raise(signo);
}
+static struct ref *find_remote_branch(const struct ref *refs, const char *branch)
+{
+ struct ref *ref;
+ struct strbuf head = STRBUF_INIT;
+ strbuf_addstr(&head, "refs/heads/");
+ strbuf_addstr(&head, branch);
+ ref = find_ref_by_name(refs, head.buf);
+ strbuf_release(&head);
+
+ if (ref)
+ return ref;
+
+ strbuf_addstr(&head, "refs/tags/");
+ strbuf_addstr(&head, branch);
+ ref = find_ref_by_name(refs, head.buf);
+ strbuf_release(&head);
+
+ return ref;
+}
+
static struct ref *wanted_peer_refs(const struct ref *refs,
struct refspec *refspec)
{
@@ -427,8 +466,30 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
struct ref *local_refs = head;
struct ref **tail = head ? &head->next : &local_refs;
- get_fetch_map(refs, refspec, &tail, 0);
- if (!option_mirror)
+ if (option_single_branch) {
+ struct ref *remote_head = NULL;
+
+ if (!option_branch)
+ remote_head = guess_remote_head(head, refs, 0);
+ else {
+ local_refs = NULL;
+ tail = &local_refs;
+ remote_head = copy_ref(find_remote_branch(refs, option_branch));
+ }
+
+ if (!remote_head && option_branch)
+ warning(_("Could not find remote branch %s to clone."),
+ option_branch);
+ else {
+ get_fetch_map(remote_head, refspec, &tail, 0);
+
+ /* if --branch=tag, pull the requested tag explicitly */
+ get_fetch_map(remote_head, tag_refspec, &tail, 0);
+ }
+ } else
+ get_fetch_map(refs, refspec, &tail, 0);
+
+ if (!option_mirror && !option_single_branch)
get_fetch_map(refs, tag_refspec, &tail, 0);
return local_refs;
@@ -438,14 +499,175 @@ 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_extra_ref(r->peer_ref->name, r->old_sha1, 0);
+ add_packed_ref(r->peer_ref->name, r->old_sha1);
+ }
+
+ 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 (!starts_with(ref->name, "refs/tags/"))
+ continue;
+ if (ends_with(ref->name, "^{}"))
+ continue;
+ if (!has_sha1_file(ref->old_sha1))
+ continue;
+ update_ref(msg, ref->name, ref->old_sha1,
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+ }
+}
+
+static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
+{
+ struct ref **rm = cb_data;
+ struct ref *ref = *rm;
+
+ /*
+ * Skip anything missing a peer_ref, which we are not
+ * actually going to write a ref for.
+ */
+ while (ref && !ref->peer_ref)
+ ref = ref->next;
+ /* Returning -1 notes "end of list" to the caller. */
+ if (!ref)
+ return -1;
+
+ hashcpy(sha1, ref->old_sha1);
+ *rm = ref->next;
+ return 0;
+}
+
+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,
+ struct transport *transport,
+ int check_connectivity)
+{
+ const struct ref *rm = mapped_refs;
+
+ 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"));
}
- pack_refs(PACK_REFS_ALL);
- clear_extra_refs();
+ if (refs) {
+ write_remote_refs(mapped_refs);
+ if (option_single_branch)
+ write_followtags(refs, msg);
+ }
+
+ if (remote_head_points_at && !option_bare) {
+ struct strbuf head_ref = STRBUF_INIT;
+ strbuf_addstr(&head_ref, branch_top);
+ strbuf_addstr(&head_ref, "HEAD");
+ create_symref(head_ref.buf,
+ remote_head_points_at->peer_ref->name,
+ msg);
+ }
+}
+
+static void update_head(const struct ref *our, const struct ref *remote,
+ const char *msg)
+{
+ const char *head;
+ if (our && skip_prefix(our->name, "refs/heads/", &head)) {
+ /* Local default branch link */
+ create_symref("HEAD", our->name, NULL);
+ if (!option_bare) {
+ update_ref(msg, "HEAD", our->old_sha1, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
+ install_branch_config(0, head, option_origin, our->name);
+ }
+ } else if (our) {
+ struct commit *c = lookup_commit_reference(our->old_sha1);
+ /* --branch specifies a non-branch (i.e. tags), detach HEAD */
+ update_ref(msg, "HEAD", c->object.sha1,
+ NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
+ } else if (remote) {
+ /*
+ * We know remote HEAD points to a non-branch, or
+ * HEAD points to a branch but we don't know which one.
+ * Detach HEAD in all these cases.
+ */
+ update_ref(msg, "HEAD", remote->old_sha1,
+ NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
+ }
+}
+
+static int checkout(void)
+{
+ unsigned char sha1[20];
+ char *head;
+ struct lock_file *lock_file;
+ struct unpack_trees_options opts;
+ struct tree *tree;
+ struct tree_desc t;
+ int err = 0;
+
+ if (option_no_checkout)
+ return 0;
+
+ head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL);
+ if (!head) {
+ warning(_("remote HEAD refers to nonexistent ref, "
+ "unable to checkout.\n"));
+ return 0;
+ }
+ if (!strcmp(head, "HEAD")) {
+ if (advice_detached_head)
+ detach_advice(sha1_to_hex(sha1));
+ } else {
+ if (!starts_with(head, "refs/heads/"))
+ die(_("HEAD not found below refs/heads!"));
+ }
+ free(head);
+
+ /* We need to be in the new work tree for the checkout */
+ setup_work_tree();
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+ hold_locked_index(lock_file, 1);
+
+ memset(&opts, 0, sizeof opts);
+ opts.update = 1;
+ opts.merge = 1;
+ opts.fn = oneway_merge;
+ opts.verbose_update = (option_verbosity >= 0);
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+
+ tree = parse_tree_indirect(sha1);
+ parse_tree(tree);
+ init_tree_desc(&t, tree->buffer, tree->size);
+ if (unpack_trees(1, &t, &opts) < 0)
+ die(_("unable to checkout working tree"));
+
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write new index file"));
+
+ err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
+ sha1_to_hex(sha1), "1", NULL);
+
+ if (!err && option_recursive)
+ err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+
+ return err;
}
static int write_one_config(const char *key, const char *value, void *data)
@@ -464,6 +686,68 @@ static void write_config(struct string_list *config)
}
}
+static void write_refspec_config(const char *src_ref_prefix,
+ const struct ref *our_head_points_at,
+ const struct ref *remote_head_points_at,
+ struct strbuf *branch_top)
+{
+ struct strbuf key = STRBUF_INIT;
+ struct strbuf value = STRBUF_INIT;
+
+ if (option_mirror || !option_bare) {
+ if (option_single_branch && !option_mirror) {
+ if (option_branch) {
+ if (starts_with(our_head_points_at->name, "refs/tags/"))
+ strbuf_addf(&value, "+%s:%s", our_head_points_at->name,
+ our_head_points_at->name);
+ else
+ strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name,
+ branch_top->buf, option_branch);
+ } else if (remote_head_points_at) {
+ const char *head = remote_head_points_at->name;
+ if (!skip_prefix(head, "refs/heads/", &head))
+ die("BUG: remote HEAD points at non-head?");
+
+ strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name,
+ branch_top->buf, head);
+ }
+ /*
+ * otherwise, the next "git fetch" will
+ * simply fetch from HEAD without updating
+ * any remote-tracking branch, which is what
+ * we want.
+ */
+ } else {
+ strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top->buf);
+ }
+ /* Configure the remote */
+ if (value.len) {
+ strbuf_addf(&key, "remote.%s.fetch", option_origin);
+ git_config_set_multivar(key.buf, value.buf, "^$", 0);
+ strbuf_reset(&key);
+
+ if (option_mirror) {
+ strbuf_addf(&key, "remote.%s.mirror", option_origin);
+ git_config_set(key.buf, "true");
+ strbuf_reset(&key);
+ }
+ }
+ }
+
+ strbuf_release(&key);
+ strbuf_release(&value);
+}
+
+static void dissociate_from_references(void)
+{
+ static const char* argv[] = { "repack", "-a", "-d", NULL };
+
+ if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN))
+ die(_("cannot repack to clean up"));
+ if (unlink(git_path("objects/info/alternates")) && errno != ENOENT)
+ die_errno(_("cannot unlink temporary alternates file"));
+}
+
int cmd_clone(int argc, const char **argv, const char *prefix)
{
int is_bundle = 0, is_local;
@@ -475,17 +759,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
const struct ref *remote_head_points_at;
const struct ref *our_head_points_at;
struct ref *mapped_refs;
+ const struct ref *ref;
struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
struct transport *transport = NULL;
- char *src_ref_prefix = "refs/heads/";
- int err = 0;
+ const char *src_ref_prefix = "refs/heads/";
+ struct remote *remote;
+ int err = 0, complete_refs_before_fetch = 1;
struct refspec *refspec;
const char *fetch_pattern;
- junk_pid = getpid();
-
packet_trace_identity("clone");
argc = parse_options(argc, argv, prefix, builtin_clone_options,
builtin_clone_usage, 0);
@@ -498,6 +782,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
usage_msg_opt(_("You must specify a repository to clone."),
builtin_clone_usage, builtin_clone_options);
+ if (option_single_branch == -1)
+ option_single_branch = option_depth ? 1 : 0;
+
if (option_mirror)
option_bare = 1;
@@ -505,6 +792,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_origin)
die(_("--bare and --origin %s options are incompatible."),
option_origin);
+ if (real_git_dir)
+ die(_("--bare and --separate-git-dir are incompatible."));
option_no_checkout = 1;
}
@@ -520,9 +809,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
die(_("repository '%s' does not exist"), repo_name);
else
repo = repo_name;
- is_local = path && !is_bundle;
- if (is_local && option_depth)
- warning(_("--depth is ignored in local clones; use file:// instead."));
+
+ /* 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]);
@@ -549,48 +839,42 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
git_dir = xstrdup(dir);
else {
work_tree = dir;
- git_dir = xstrdup(mkpath("%s/.git", dir));
+ git_dir = mkpathdup("%s/.git", dir);
}
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
+
if (!option_bare) {
- junk_work_tree = work_tree;
if (safe_create_leading_directories_const(work_tree) < 0)
die_errno(_("could not create leading directories of '%s'"),
work_tree);
- if (!dest_exists && mkdir(work_tree, 0755))
- die_errno(_("could not create work tree dir '%s'."),
+ if (!dest_exists && mkdir(work_tree, 0777))
+ die_errno(_("could not create work tree dir '%s'"),
work_tree);
+ junk_work_tree = work_tree;
set_git_work_tree(work_tree);
}
- junk_git_dir = git_dir;
- atexit(remove_junk);
- sigchain_push_common(remove_junk_on_signal);
-
- setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
+ junk_git_dir = git_dir;
if (safe_create_leading_directories_const(git_dir) < 0)
die(_("could not create leading directories of '%s'"), git_dir);
set_git_dir_init(git_dir, real_git_dir, 0);
- if (real_git_dir)
+ if (real_git_dir) {
git_dir = real_git_dir;
+ junk_git_dir = real_git_dir;
+ }
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);
- /*
- * At this point, the config exists, so we do not need the
- * environment variable. We actually need to unset it, too, to
- * re-enable parsing of the global configs.
- */
- unsetenv(CONFIG_ENVIRONMENT);
-
git_config(git_default_config, NULL);
if (option_bare) {
@@ -604,90 +888,104 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
-
- if (option_mirror || !option_bare) {
- /* Configure the remote */
- strbuf_addf(&key, "remote.%s.fetch", option_origin);
- git_config_set_multivar(key.buf, value.buf, "^$", 0);
- strbuf_reset(&key);
-
- if (option_mirror) {
- strbuf_addf(&key, "remote.%s.mirror", option_origin);
- git_config_set(key.buf, "true");
- strbuf_reset(&key);
- }
- }
-
strbuf_addf(&key, "remote.%s.url", option_origin);
git_config_set(key.buf, repo);
strbuf_reset(&key);
if (option_reference.nr)
setup_reference();
+ else if (option_dissociate) {
+ warning(_("--dissociate given, but there is no --reference"));
+ option_dissociate = 0;
+ }
fetch_pattern = value.buf;
refspec = parse_fetch_refspec(1, &fetch_pattern);
strbuf_reset(&value);
+ remote = remote_get(option_origin);
+ transport = transport_get(remote, remote->url[0]);
+ path = get_repo_path(remote->url[0], &is_bundle);
+ is_local = option_local != 0 && path && !is_bundle;
if (is_local) {
- refs = clone_local(path, git_dir);
- mapped_refs = wanted_peer_refs(refs, refspec);
- } else {
- struct remote *remote = remote_get(option_origin);
- transport = transport_get(remote, remote->url[0]);
+ 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"));
+ transport->cloning = 1;
- 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_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);
- refs = transport_get_remote_refs(transport);
- if (refs) {
- mapped_refs = wanted_peer_refs(refs, refspec);
- transport_fetch_refs(transport, mapped_refs);
- }
- }
+ if (transport->smart_options && !option_depth)
+ transport->smart_options->check_self_contained_and_connected = 1;
+
+ refs = transport_get_remote_refs(transport);
if (refs) {
- clear_extra_refs();
+ mapped_refs = wanted_peer_refs(refs, refspec);
+ /*
+ * transport_get_remote_refs() may return refs with null sha-1
+ * in mapped_refs (see struct transport->get_refs_list
+ * comment). In that case we need fetch it early because
+ * remote_head code below relies on it.
+ *
+ * for normal clones, transport_get_remote_refs() should
+ * return reliable ref set, we can delay cloning until after
+ * remote HEAD check.
+ */
+ for (ref = refs; ref; ref = ref->next)
+ if (is_null_sha1(ref->old_sha1)) {
+ complete_refs_before_fetch = 0;
+ break;
+ }
- write_remote_refs(mapped_refs);
+ if (!is_local && !complete_refs_before_fetch)
+ transport_fetch_refs(transport, mapped_refs);
remote_head = find_ref_by_name(refs, "HEAD");
remote_head_points_at =
guess_remote_head(remote_head, mapped_refs, 0);
if (option_branch) {
- struct strbuf head = STRBUF_INIT;
- strbuf_addstr(&head, src_ref_prefix);
- strbuf_addstr(&head, option_branch);
our_head_points_at =
- find_ref_by_name(mapped_refs, head.buf);
- strbuf_release(&head);
+ find_remote_branch(mapped_refs, option_branch);
- if (!our_head_points_at) {
- warning(_("Remote branch %s not found in "
- "upstream %s, using HEAD instead"),
- option_branch, option_origin);
- our_head_points_at = remote_head_points_at;
- }
+ if (!our_head_points_at)
+ die(_("Remote branch %s not found in upstream %s"),
+ option_branch, option_origin);
}
else
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;
remote_head_points_at = NULL;
remote_head = NULL;
@@ -697,89 +995,34 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
"refs/heads/master");
}
- if (remote_head_points_at && !option_bare) {
- struct strbuf head_ref = STRBUF_INIT;
- strbuf_addstr(&head_ref, branch_top.buf);
- strbuf_addstr(&head_ref, "HEAD");
- create_symref(head_ref.buf,
- remote_head_points_at->peer_ref->name,
- reflog_msg.buf);
- }
+ write_refspec_config(src_ref_prefix, our_head_points_at,
+ remote_head_points_at, &branch_top);
- if (our_head_points_at) {
- /* Local default branch link */
- create_symref("HEAD", our_head_points_at->name, NULL);
- if (!option_bare) {
- const char *head = skip_prefix(our_head_points_at->name,
- "refs/heads/");
- update_ref(reflog_msg.buf, "HEAD",
- our_head_points_at->old_sha1,
- NULL, 0, DIE_ON_ERR);
- install_branch_config(0, head, option_origin,
- our_head_points_at->name);
- }
- } else if (remote_head) {
- /* Source had detached HEAD pointing somewhere. */
- if (!option_bare) {
- update_ref(reflog_msg.buf, "HEAD",
- remote_head->old_sha1,
- NULL, REF_NODEREF, DIE_ON_ERR);
- our_head_points_at = remote_head;
- }
- } else {
- /* Nothing to checkout out */
- if (!option_no_checkout)
- warning(_("remote HEAD refers to nonexistent ref, "
- "unable to checkout.\n"));
- option_no_checkout = 1;
- }
-
- if (transport) {
- transport_unlock_pack(transport);
- transport_disconnect(transport);
- }
-
- if (!option_no_checkout) {
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
- struct unpack_trees_options opts;
- struct tree *tree;
- struct tree_desc t;
- int fd;
+ if (is_local)
+ clone_local(path, git_dir);
+ else if (refs && complete_refs_before_fetch)
+ transport_fetch_refs(transport, mapped_refs);
- /* We need to be in the new work tree for the checkout */
- setup_work_tree();
+ update_remote_refs(refs, mapped_refs, remote_head_points_at,
+ branch_top.buf, reflog_msg.buf, transport, !is_local);
- fd = hold_locked_index(lock_file, 1);
+ update_head(our_head_points_at, remote_head, reflog_msg.buf);
- memset(&opts, 0, sizeof opts);
- opts.update = 1;
- opts.merge = 1;
- opts.fn = oneway_merge;
- opts.verbose_update = (option_verbosity > 0);
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
-
- tree = parse_tree_indirect(our_head_points_at->old_sha1);
- parse_tree(tree);
- init_tree_desc(&t, tree->buffer, tree->size);
- unpack_trees(1, &t, &opts);
+ transport_unlock_pack(transport);
+ transport_disconnect(transport);
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
- die(_("unable to write new index file"));
+ if (option_dissociate)
+ dissociate_from_references();
- err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
- sha1_to_hex(our_head_points_at->old_sha1), "1",
- NULL);
-
- if (!err && option_recursive)
- err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
- }
+ junk_mode = JUNK_LEAVE_REPO;
+ err = checkout();
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
strbuf_release(&key);
strbuf_release(&value);
- junk_pid = 0;
+ junk_mode = JUNK_LEAVE_ALL;
+
+ free(refspec);
return err;
}
diff --git a/builtin/column.c b/builtin/column.c
new file mode 100644
index 0000000..7581852
--- /dev/null
+++ b/builtin/column.c
@@ -0,0 +1,59 @@
+#include "builtin.h"
+#include "cache.h"
+#include "strbuf.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "column.h"
+
+static const char * const builtin_column_usage[] = {
+ N_("git column [options]"),
+ NULL
+};
+static unsigned int colopts;
+
+static int column_config(const char *var, const char *value, void *cb)
+{
+ return git_column_config(var, value, cb, &colopts);
+}
+
+int cmd_column(int argc, const char **argv, const char *prefix)
+{
+ struct string_list list = STRING_LIST_INIT_DUP;
+ struct strbuf sb = STRBUF_INIT;
+ struct column_options copts;
+ const char *command = NULL, *real_command = NULL;
+ struct option options[] = {
+ OPT_STRING(0, "command", &real_command, N_("name"), N_("lookup config vars")),
+ OPT_COLUMN(0, "mode", &colopts, N_("layout to use")),
+ OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")),
+ OPT_INTEGER(0, "width", &copts.width, N_("Maximum width")),
+ OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("Padding space on left border")),
+ OPT_INTEGER(0, "nl", &copts.nl, N_("Padding space on right border")),
+ OPT_INTEGER(0, "padding", &copts.padding, N_("Padding space between columns")),
+ OPT_END()
+ };
+
+ /* This one is special and must be the first one */
+ if (argc > 1 && starts_with(argv[1], "--command=")) {
+ command = argv[1] + 10;
+ git_config(column_config, (void *)command);
+ } else
+ git_config(column_config, NULL);
+
+ memset(&copts, 0, sizeof(copts));
+ copts.width = term_columns();
+ copts.padding = 1;
+ argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
+ if (argc)
+ usage_with_options(builtin_column_usage, options);
+ if (real_command || command) {
+ if (!real_command || !command || strcmp(real_command, command))
+ die(_("--command must be the first argument"));
+ }
+ finalize_colopts(&colopts, -1);
+ while (!strbuf_getline(&sb, stdin, '\n'))
+ string_list_append(&list, sb.buf);
+
+ print_columns(&list, colopts, &copts);
+ return 0;
+}
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index d083795..25aa2cd 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -8,8 +8,11 @@
#include "tree.h"
#include "builtin.h"
#include "utf8.h"
+#include "gpg-interface.h"
-static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
+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)
{
@@ -25,38 +28,101 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
commit_list_insert(parent, parents_p);
}
+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);
+}
+
int cmd_commit_tree(int argc, const char **argv, const char *prefix)
{
- int i;
+ int i, got_tree = 0;
struct commit_list *parents = NULL;
unsigned char tree_sha1[20];
unsigned char commit_sha1[20];
struct strbuf buffer = STRBUF_INIT;
- git_config(git_default_config, NULL);
+ git_config(commit_tree_config, NULL);
if (argc < 2 || !strcmp(argv[1], "-h"))
usage(commit_tree_usage);
- if (get_sha1(argv[1], tree_sha1))
- die("Not a valid object name %s", argv[1]);
-
- for (i = 2; i < argc; i += 2) {
- unsigned char sha1[20];
- const char *a, *b;
- a = argv[i]; b = argv[i+1];
- if (!b || strcmp(a, "-p"))
- usage(commit_tree_usage);
-
- if (get_sha1(b, sha1))
- die("Not a valid object name %s", b);
- assert_sha1_type(sha1, OBJ_COMMIT);
- new_parent(lookup_commit(sha1), &parents);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "-p")) {
+ unsigned char sha1[20];
+ if (argc <= ++i)
+ usage(commit_tree_usage);
+ if (get_sha1_commit(argv[i], sha1))
+ die("Not a valid object name %s", argv[i]);
+ assert_sha1_type(sha1, OBJ_COMMIT);
+ new_parent(lookup_commit(sha1), &parents);
+ continue;
+ }
+
+ if (skip_prefix(arg, "-S", &sign_commit))
+ continue;
+
+ if (!strcmp(arg, "--no-gpg-sign")) {
+ sign_commit = NULL;
+ continue;
+ }
+
+ if (!strcmp(arg, "-m")) {
+ if (argc <= ++i)
+ usage(commit_tree_usage);
+ if (buffer.len)
+ strbuf_addch(&buffer, '\n');
+ strbuf_addstr(&buffer, argv[i]);
+ strbuf_complete_line(&buffer);
+ continue;
+ }
+
+ if (!strcmp(arg, "-F")) {
+ int fd;
+
+ if (argc <= ++i)
+ usage(commit_tree_usage);
+ if (buffer.len)
+ strbuf_addch(&buffer, '\n');
+ if (!strcmp(argv[i], "-"))
+ fd = 0;
+ else {
+ fd = open(argv[i], O_RDONLY);
+ if (fd < 0)
+ die_errno("git commit-tree: failed to open '%s'",
+ argv[i]);
+ }
+ if (strbuf_read(&buffer, fd, 0) < 0)
+ die_errno("git commit-tree: failed to read '%s'",
+ argv[i]);
+ if (fd && close(fd))
+ die_errno("git commit-tree: failed to close '%s'",
+ argv[i]);
+ strbuf_complete_line(&buffer);
+ continue;
+ }
+
+ if (get_sha1_tree(arg, tree_sha1))
+ die("Not a valid object name %s", arg);
+ if (got_tree)
+ die("Cannot give more than one trees");
+ got_tree = 1;
}
- if (strbuf_read(&buffer, 0, 0) < 0)
- die_errno("git commit-tree: failed to read");
+ if (!buffer.len) {
+ if (strbuf_read(&buffer, 0, 0) < 0)
+ die_errno("git commit-tree: failed to read");
+ }
- if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+ if (commit_tree(buffer.buf, buffer.len, tree_sha1, parents,
+ commit_sha1, NULL, sign_commit)) {
strbuf_release(&buffer);
return 1;
}
diff --git a/builtin/commit.c b/builtin/commit.c
index cbc9613..714638c 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -6,6 +6,7 @@
*/
#include "cache.h"
+#include "lockfile.h"
#include "cache-tree.h"
#include "color.h"
#include "dir.h"
@@ -26,18 +27,36 @@
#include "unpack-trees.h"
#include "quote.h"
#include "submodule.h"
+#include "gpg-interface.h"
+#include "column.h"
+#include "sequencer.h"
+#include "notes-utils.h"
+#include "mailmap.h"
static const char * const builtin_commit_usage[] = {
- "git commit [options] [--] <filepattern>...",
+ N_("git commit [options] [--] <pathspec>..."),
NULL
};
static const char * const builtin_status_usage[] = {
- "git status [options] [--] <filepattern>...",
+ N_("git status [options] [--] <pathspec>..."),
NULL
};
-static const char implicit_ident_advice[] =
+static const char implicit_ident_advice_noconfig[] =
+N_("Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly. Run the\n"
+"following command and follow the instructions in your editor to edit\n"
+"your configuration file:\n"
+"\n"
+" git config --global --edit\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n");
+
+static const char implicit_ident_advice_config[] =
N_("Your name and email address were configured automatically based\n"
"on your username and hostname. Please check that they are accurate.\n"
"You can suppress this message by setting them explicitly:\n"
@@ -59,8 +78,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";
@@ -81,10 +110,13 @@ static const char *template_file;
static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message;
static char *fixup_message, *squash_message;
-static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff;
+static int all, also, interactive, patch_interactive, only, amend, signoff;
+static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int no_post_rewrite, allow_empty_message;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *sign_commit;
+
/*
* The default commit message cleanup mode will remove the lines
* beginning with # (shell comments) and leading and trailing
@@ -95,115 +127,65 @@ static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
static enum {
CLEANUP_SPACE,
CLEANUP_NONE,
+ CLEANUP_SCISSORS,
CLEANUP_ALL
} cleanup_mode;
-static char *cleanup_arg;
+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;
+static struct strbuf message = STRBUF_INIT;
-static int null_termination;
-static enum {
+static enum status_format {
+ STATUS_FORMAT_NONE = 0,
STATUS_FORMAT_LONG,
STATUS_FORMAT_SHORT,
- STATUS_FORMAT_PORCELAIN
-} status_format = STATUS_FORMAT_LONG;
-static int status_show_branch;
+ 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);
- strbuf_addstr(buf, "\n\n");
+ strbuf_complete_line(buf);
}
return 0;
}
-static struct option builtin_commit_options[] = {
- OPT__QUIET(&quiet, "suppress summary after successful commit"),
- OPT__VERBOSE(&verbose, "show diff in commit message template"),
-
- OPT_GROUP("Commit message options"),
- OPT_FILENAME('F', "file", &logfile, "read message from file"),
- OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
- OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
- OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
- OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
- OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
- OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
- OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
- OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
- OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
- OPT_FILENAME('t', "template", &template_file, "use specified template file"),
- OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
- OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
- OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
- /* end commit message options */
-
- OPT_GROUP("Commit contents options"),
- OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
- OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"),
- OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
- OPT_BOOLEAN('p', "patch", &patch_interactive, "interactively add changes"),
- OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
- OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
- OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
- OPT_SET_INT(0, "short", &status_format, "show status concisely",
- STATUS_FORMAT_SHORT),
- OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"),
- OPT_SET_INT(0, "porcelain", &status_format,
- "machine-readable output", STATUS_FORMAT_PORCELAIN),
- OPT_BOOLEAN('z', "null", &null_termination,
- "terminate entries with NUL"),
- OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
- OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
- { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "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,
- "ok to record an empty change",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL,
- "ok to record a change with an empty message",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
-
- OPT_END()
-};
-
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 const char *whence_s(void)
+static void status_init_config(struct wt_status *s, config_fn_t fn)
{
- char *s = "";
-
- switch (whence) {
- case FROM_COMMIT:
- break;
- case FROM_MERGE:
- s = "merge";
- break;
- case FROM_CHERRY_PICK:
- s = "cherry-pick";
- break;
- }
-
- return s;
+ 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)
@@ -245,27 +227,29 @@ 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;
- for (i = 0; pattern[i]; i++)
- ;
- m = xcalloc(1, i);
+ if (!pattern->nr)
+ return 0;
+
+ m = xcalloc(1, pattern->nr);
if (with_tree) {
- const char *max_prefix = pathspec_prefix(prefix, pattern);
- overlay_tree_on_cache(with_tree, max_prefix);
+ char *max_prefix = common_prefix(pattern);
+ overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix);
+ free(max_prefix);
}
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))
@@ -332,35 +316,33 @@ static void refresh_cache_or_die(int refresh_flags)
die_resolve_conflict("commit");
}
-static char *prepare_index(int argc, const char **argv, const char *prefix,
- const struct commit *current_head, int is_status)
+static const char *prepare_index(int argc, const char **argv, const char *prefix,
+ const struct commit *current_head, int is_status)
{
- 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) {
- fd = hold_locked_index(&index_lock, 1);
+ char *old_index_env = NULL;
+ hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
- if (write_cache(fd, active_cache, active_nr) ||
- close_lock_file(&index_lock))
+ if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
die(_("unable to create temporary index"));
old_index_env = getenv(INDEX_ENVIRONMENT);
- setenv(INDEX_ENVIRONMENT, index_lock.filename, 1);
+ setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1);
if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
die(_("interactive add failed"));
@@ -371,10 +353,17 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
unsetenv(INDEX_ENVIRONMENT);
discard_cache();
- read_cache_from(index_lock.filename);
+ read_cache_from(index_lock.filename.buf);
+ if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
+ if (reopen_lock_file(&index_lock) < 0)
+ die(_("unable to write index file"));
+ if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
+ die(_("unable to update temporary index"));
+ } else
+ warning(_("Failed to update main cache tree"));
commit_style = COMMIT_NORMAL;
- return index_lock.filename;
+ return index_lock.filename.buf;
}
/*
@@ -389,15 +378,15 @@ 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)) {
- fd = hold_locked_index(&index_lock, 1);
- add_files_to_cache(also ? prefix : NULL, pathspec, 0);
+ if (all || (also && pathspec.nr)) {
+ hold_locked_index(&index_lock, 1);
+ add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
refresh_cache_or_die(refresh_flags);
- if (write_cache(fd, active_cache, active_nr) ||
- close_lock_file(&index_lock))
+ update_main_cache_tree(WRITE_TREE_SILENT);
+ if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
die(_("unable to write new_index file"));
commit_style = COMMIT_NORMAL;
- return index_lock.filename;
+ return index_lock.filename.buf;
}
/*
@@ -409,12 +398,17 @@ 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 (!pathspec || !*pathspec) {
- fd = hold_locked_index(&index_lock, 1);
+ if (!only && !pathspec.nr) {
+ hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
+ if (active_cache_changed
+ || !cache_tree_fully_valid(active_cache_tree)) {
+ update_main_cache_tree(WRITE_TREE_SILENT);
+ active_cache_changed = 1;
+ }
if (active_cache_changed) {
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(&index_lock))
+ if (write_locked_index(&the_index, &index_lock,
+ COMMIT_LOCK))
die(_("unable to write new_index file"));
} else {
rollback_lock_file(&index_lock);
@@ -444,42 +438,44 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
*/
commit_style = COMMIT_PARTIAL;
- if (whence != FROM_COMMIT)
- die(_("cannot do a partial commit during a %s."), whence_s());
+ if (whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("cannot do a partial commit during a merge."));
+ else if (whence == FROM_CHERRY_PICK)
+ die(_("cannot do a partial commit during a cherry-pick."));
+ }
- memset(&partial, 0, sizeof(partial));
- partial.strdup_strings = 1;
- if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
+ string_list_init(&partial, 1);
+ if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
exit(1);
discard_cache();
if (read_cache() < 0)
die(_("cannot read the index"));
- fd = hold_locked_index(&index_lock, 1);
+ hold_locked_index(&index_lock, 1);
add_remove_files(&partial);
refresh_cache(REFRESH_QUIET);
- if (write_cache(fd, active_cache, active_nr) ||
- close_lock_file(&index_lock))
+ update_main_cache_tree(WRITE_TREE_SILENT);
+ if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
die(_("unable to write new_index file"));
- fd = hold_lock_file_for_update(&false_lock,
- git_path("next-index-%"PRIuMAX,
- (uintmax_t) getpid()),
- LOCK_DIE_ON_ERROR);
+ hold_lock_file_for_update(&false_lock,
+ git_path("next-index-%"PRIuMAX,
+ (uintmax_t) getpid()),
+ LOCK_DIE_ON_ERROR);
create_base_index(current_head);
add_remove_files(&partial);
refresh_cache(REFRESH_QUIET);
- if (write_cache(fd, active_cache, active_nr) ||
- close_lock_file(&false_lock))
+ if (write_locked_index(&the_index, &false_lock, CLOSE_LOCK))
die(_("unable to write temporary index file"));
discard_cache();
- read_cache_from(false_lock.filename);
+ read_cache_from(false_lock.filename.buf);
- return false_lock.filename;
+ return false_lock.filename.buf;
}
static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
@@ -504,11 +500,15 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
switch (status_format) {
case STATUS_FORMAT_SHORT:
- wt_shortstatus_print(s, null_termination, status_show_branch);
+ wt_shortstatus_print(s);
break;
case STATUS_FORMAT_PORCELAIN:
- wt_porcelain_print(s, null_termination);
+ 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);
break;
@@ -522,104 +522,135 @@ static int is_a_merge(const struct commit *current_head)
return !!(current_head->parents && current_head->parents->next);
}
-static const char sign_off_header[] = "Signed-off-by: ";
+static void assert_split_ident(struct ident_split *id, const struct strbuf *buf)
+{
+ if (split_ident_line(id, buf->buf, buf->len) || !id->date_begin)
+ die("BUG: unable to parse our own ident: %s", buf->buf);
+}
+
+static void export_one(const char *var, const char *s, const char *e, int hack)
+{
+ struct strbuf buf = STRBUF_INIT;
+ if (hack)
+ strbuf_addch(&buf, hack);
+ strbuf_addf(&buf, "%.*s", (int)(e - s), s);
+ setenv(var, buf.buf, 1);
+ strbuf_release(&buf);
+}
+
+static int parse_force_date(const char *in, struct strbuf *out)
+{
+ strbuf_addch(out, '@');
+
+ if (parse_date(in, out) < 0) {
+ int errors = 0;
+ unsigned long t = approxidate_careful(in, &errors);
+ if (errors)
+ return -1;
+ strbuf_addf(out, "%lu", t);
+ }
+
+ return 0;
+}
+
+static void set_ident_var(char **buf, char *val)
+{
+ free(*buf);
+ *buf = val;
+}
static void determine_author_info(struct strbuf *author_ident)
{
char *name, *email, *date;
+ struct ident_split author;
- name = getenv("GIT_AUTHOR_NAME");
- email = getenv("GIT_AUTHOR_EMAIL");
- date = getenv("GIT_AUTHOR_DATE");
+ name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME"));
+ email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL"));
+ date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE"));
if (author_message) {
- const char *a, *lb, *rb, *eol;
+ struct ident_split ident;
+ size_t len;
+ const char *a;
- a = strstr(author_message_buffer, "\nauthor ");
+ a = find_commit_header(author_message_buffer, "author", &len);
if (!a)
- die(_("invalid commit: %s"), author_message);
-
- lb = strchrnul(a + strlen("\nauthor "), '<');
- rb = strchrnul(lb, '>');
- eol = strchrnul(rb, '\n');
- if (!*lb || !*rb || !*eol)
- die(_("invalid commit: %s"), author_message);
-
- if (lb == a + strlen("\nauthor "))
- /* \nauthor <foo@example.com> */
- name = xcalloc(1, 1);
- else
- name = xmemdupz(a + strlen("\nauthor "),
- (lb - strlen(" ") -
- (a + strlen("\nauthor "))));
- email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<")));
- date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> ")));
+ die(_("commit '%s' lacks author header"), author_message);
+ if (split_ident_line(&ident, a, len) < 0)
+ die(_("commit '%s' has malformed author line"), author_message);
+
+ set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+ set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+
+ if (ident.date_begin) {
+ struct strbuf date_buf = STRBUF_INIT;
+ strbuf_addch(&date_buf, '@');
+ strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+ strbuf_addch(&date_buf, ' ');
+ strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+ set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+ }
}
if (force_author) {
- const char *lb = strstr(force_author, " <");
- const char *rb = strchr(force_author, '>');
+ struct ident_split ident;
- if (!lb || !rb)
+ if (split_ident_line(&ident, force_author, strlen(force_author)) < 0)
die(_("malformed --author parameter"));
- name = xstrndup(force_author, lb - force_author);
- email = xstrndup(lb + 2, rb - (lb + 2));
+ set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+ set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
}
- if (force_date)
- date = force_date;
- strbuf_addstr(author_ident, fmt_ident(name, email, date,
- IDENT_ERROR_ON_NO_NAME));
-}
-
-static int ends_rfc2822_footer(struct strbuf *sb)
-{
- int ch;
- int hit = 0;
- int i, j, k;
- int len = sb->len;
- int first = 1;
- const char *buf = sb->buf;
-
- for (i = len - 1; i > 0; i--) {
- if (hit && buf[i] == '\n')
- break;
- hit = (buf[i] == '\n');
+ if (force_date) {
+ struct strbuf date_buf = STRBUF_INIT;
+ if (parse_force_date(force_date, &date_buf))
+ die(_("invalid date format: %s"), force_date);
+ set_ident_var(&date, strbuf_detach(&date_buf, NULL));
}
- while (i < len - 1 && buf[i] == '\n')
- i++;
+ strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT));
+ assert_split_ident(&author, author_ident);
+ export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
+ export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
+ export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
+ free(name);
+ free(email);
+ free(date);
+}
- for (; i < len; i = k) {
- for (k = i; k < len && buf[k] != '\n'; k++)
- ; /* do nothing */
- k++;
+static int author_date_is_interesting(void)
+{
+ return author_message || force_date;
+}
- if ((buf[k] == ' ' || buf[k] == '\t') && !first)
- continue;
+static void adjust_comment_line_char(const struct strbuf *sb)
+{
+ char candidates[] = "#;@!$%^&|:";
+ char *candidate;
+ const char *p;
- first = 0;
+ comment_line_char = candidates[0];
+ if (!memchr(sb->buf, comment_line_char, sb->len))
+ return;
- for (j = 0; i + j < len; j++) {
- ch = buf[i + j];
- if (ch == ':')
- break;
- if (isalnum(ch) ||
- (ch == '-'))
- continue;
- return 0;
+ p = sb->buf;
+ candidate = strchr(candidates, *p);
+ if (candidate)
+ *candidate = ' ';
+ for (p = sb->buf; *p; p++) {
+ if ((p[0] == '\n' || p[0] == '\r') && p[1]) {
+ candidate = strchr(candidates, p[1]);
+ if (candidate)
+ *candidate = ' ';
}
}
- return 1;
-}
-static char *cut_ident_timestamp_part(char *string)
-{
- char *ket = strrchr(string, '>');
- if (!ket || ket[1] != ' ')
- die(_("Malformed ident string: '%s'"), string);
- *++ket = '\0';
- return ket;
+ for (p = candidates; *p == ' '; p++)
+ ;
+ if (!*p)
+ die(_("unable to select a comment character that is not used\n"
+ "in the current commit message"));
+ comment_line_char = *p;
}
static int prepare_to_commit(const char *index_file, const char *prefix,
@@ -629,15 +660,17 @@ 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);
- if (!no_verify && run_hook(index_file, "pre-commit", NULL))
+ if (!no_verify && run_commit_hook(use_editor, index_file, "pre-commit", NULL))
return 0;
if (squash_message) {
@@ -674,10 +707,10 @@ 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 (!buffer || buffer[2] == '\0')
- die(_("commit has empty message"));
- strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+ if (buffer)
+ strbuf_addstr(&sb, buffer + 2);
hook_arg1 = "commit";
hook_arg2 = use_message;
} else if (fixup_message) {
@@ -730,96 +763,114 @@ 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);
- if (signoff) {
- struct strbuf sob = STRBUF_INIT;
- int i;
-
- strbuf_addstr(&sob, sign_off_header);
- strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
- getenv("GIT_COMMITTER_EMAIL")));
- strbuf_addch(&sob, '\n');
- for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
- ; /* do nothing */
- if (prefixcmp(sb.buf + i, sob.buf)) {
- if (!i || !ends_rfc2822_footer(&sb))
- strbuf_addch(&sb, '\n');
- strbuf_addbuf(&sb, &sob);
- }
- strbuf_release(&sob);
- }
+ if (signoff)
+ append_signoff(&sb, ignore_non_trailer(&sb), 0);
if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
die_errno(_("could not write commit template"));
+ if (auto_comment_line_char)
+ adjust_comment_line_char(&sb);
strbuf_release(&sb);
- /* This checks and barfs if author is badly specified */
- determine_author_info(author_ident);
-
/* This checks if committer ident is explicitly given */
- strbuf_addstr(&committer_ident, git_committer_info(0));
+ strbuf_addstr(&committer_ident, git_committer_info(IDENT_STRICT));
if (use_editor && include_status) {
- char *ai_tmp, *ci_tmp;
- if (whence != FROM_COMMIT)
+ int ident_shown = 0;
+ int saved_color_setting;
+ struct ident_split ci, ai;
+
+ if (whence != FROM_COMMIT) {
+ if (cleanup_mode == CLEANUP_SCISSORS)
+ wt_status_add_cut_line(s->fp);
status_printf_ln(s, GIT_COLOR_NORMAL,
- _("\n"
- "It looks like you may be committing a %s.\n"
- "If this is not correct, please remove the file\n"
- " %s\n"
- "and try again.\n"
- ""),
- whence_s(),
+ whence == FROM_MERGE
+ ? _("\n"
+ "It looks like you may be committing a merge.\n"
+ "If this is not correct, please remove the file\n"
+ " %s\n"
+ "and try again.\n")
+ : _("\n"
+ "It looks like you may be committing a cherry-pick.\n"
+ "If this is not correct, please remove the file\n"
+ " %s\n"
+ "and try again.\n"),
git_path(whence == FROM_MERGE
? "MERGE_HEAD"
: "CHERRY_PICK_HEAD"));
+ }
fprintf(s->fp, "\n");
- status_printf(s, GIT_COLOR_NORMAL,
- _("Please enter the commit message for your changes."));
if (cleanup_mode == CLEANUP_ALL)
- status_printf_more(s, GIT_COLOR_NORMAL,
- _(" Lines starting\n"
- "with '#' will be ignored, and an empty"
- " message aborts the commit.\n"));
+ status_printf(s, GIT_COLOR_NORMAL,
+ _("Please enter the commit message for your changes."
+ " Lines starting\nwith '%c' will be ignored, and an empty"
+ " message aborts the commit.\n"), comment_line_char);
+ else if (cleanup_mode == CLEANUP_SCISSORS && whence == FROM_COMMIT)
+ wt_status_add_cut_line(s->fp);
else /* CLEANUP_SPACE, that is. */
- status_printf_more(s, GIT_COLOR_NORMAL,
- _(" Lines starting\n"
- "with '#' will be kept; you may remove them"
- " yourself if you want to.\n"
- "An empty message aborts the commit.\n"));
+ status_printf(s, GIT_COLOR_NORMAL,
+ _("Please enter the commit message for your changes."
+ " Lines starting\n"
+ "with '%c' will be kept; you may remove them"
+ " yourself if you want to.\n"
+ "An empty message aborts the commit.\n"), comment_line_char);
if (only_include_assumed)
status_printf_ln(s, GIT_COLOR_NORMAL,
"%s", only_include_assumed);
- ai_tmp = cut_ident_timestamp_part(author_ident->buf);
- ci_tmp = cut_ident_timestamp_part(committer_ident.buf);
- if (strcmp(author_ident->buf, committer_ident.buf))
+ /*
+ * These should never fail because they come from our own
+ * fmt_ident. They may fail the sane_ident test, but we know
+ * that the name and mail pointers will at least be valid,
+ * which is enough for our tests and printing here.
+ */
+ assert_split_ident(&ai, author_ident);
+ assert_split_ident(&ci, &committer_ident);
+
+ if (ident_cmp(&ai, &ci))
+ status_printf_ln(s, GIT_COLOR_NORMAL,
+ _("%s"
+ "Author: %.*s <%.*s>"),
+ ident_shown++ ? "" : "\n",
+ (int)(ai.name_end - ai.name_begin), ai.name_begin,
+ (int)(ai.mail_end - ai.mail_begin), ai.mail_begin);
+
+ if (author_date_is_interesting())
status_printf_ln(s, GIT_COLOR_NORMAL,
_("%s"
- "Author: %s"),
+ "Date: %s"),
ident_shown++ ? "" : "\n",
- author_ident->buf);
+ show_ident_date(&ai, DATE_NORMAL));
- if (!user_ident_sufficiently_given())
+ if (!committer_ident_sufficiently_given())
status_printf_ln(s, GIT_COLOR_NORMAL,
_("%s"
- "Committer: %s"),
+ "Committer: %.*s <%.*s>"),
ident_shown++ ? "" : "\n",
- committer_ident.buf);
+ (int)(ci.name_end - ci.name_begin), ci.name_begin,
+ (int)(ci.mail_end - ci.mail_begin), ci.mail_begin);
if (ident_shown)
- status_printf_ln(s, GIT_COLOR_NORMAL, "");
+ status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
saved_color_setting = s->use_color;
s->use_color = 0;
commitable = run_status(s->fp, index_file, prefix, 1, s);
s->use_color = saved_color_setting;
-
- *ai_tmp = ' ';
- *ci_tmp = ' ';
} else {
unsigned char sha1[20];
const char *parent = "HEAD";
@@ -832,8 +883,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (get_sha1(parent, sha1))
commitable = !!active_nr;
- else
- commitable = index_differs_from(parent, 0);
+ else {
+ /*
+ * Unless the user did explicitly request a submodule
+ * ignore mode by passing a command line option we do
+ * not ignore any changed submodule SHA-1s when
+ * comparing index and parent, no matter what is
+ * configured. Otherwise we won't commit any
+ * submodules which were manually staged, which would
+ * be really confusing.
+ */
+ int diff_flags = DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
+ if (ignore_submodule_arg &&
+ !strcmp(ignore_submodule_arg, "all"))
+ diff_flags |= DIFF_OPT_IGNORE_SUBMODULES;
+ commitable = index_differs_from(parent, diff_flags);
+ }
}
strbuf_release(&committer_ident);
@@ -846,11 +911,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;
}
@@ -861,16 +932,13 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
*/
discard_cache();
read_cache_from(index_file);
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
- if (cache_tree_update(active_cache_tree,
- active_cache, active_nr, 0, 0) < 0) {
+ if (update_main_cache_tree(0)) {
error(_("Error building trees"));
return 0;
}
- if (run_hook(index_file, "prepare-commit-msg",
- git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
+ if (run_commit_hook(use_editor, index_file, "prepare-commit-msg",
+ git_path(commit_editmsg), hook_arg1, hook_arg2, NULL))
return 0;
if (use_editor) {
@@ -886,34 +954,17 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
}
if (!no_verify &&
- run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
+ run_commit_hook(use_editor, index_file, "commit-msg", git_path(commit_editmsg), NULL)) {
return 0;
}
return 1;
}
-/*
- * Find out if the message in the strbuf contains only whitespace and
- * Signed-off-by lines.
- */
-static int message_is_empty(struct strbuf *sb)
+static int rest_is_empty(struct strbuf *sb, int start)
{
- struct strbuf tmpl = STRBUF_INIT;
+ int i, eol;
const char *nl;
- int eol, i, start = 0;
-
- if (cleanup_mode == CLEANUP_NONE && sb->len)
- return 0;
-
- /* See if the template is just a prefix of the message. */
- if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
- stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
- if (start + tmpl.len <= sb->len &&
- memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
- start += tmpl.len;
- }
- strbuf_release(&tmpl);
/* Check if the rest is just whitespace and Signed-of-by's. */
for (i = start; i < sb->len; i++) {
@@ -924,7 +975,7 @@ static int message_is_empty(struct strbuf *sb)
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;
}
@@ -936,11 +987,45 @@ static int message_is_empty(struct strbuf *sb)
return 1;
}
+/*
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
+ */
+static int message_is_empty(struct strbuf *sb)
+{
+ if (cleanup_mode == CLEANUP_NONE && sb->len)
+ return 0;
+ return rest_is_empty(sb, 0);
+}
+
+/*
+ * See if the user edited the message in the editor or left what
+ * was in the template intact
+ */
+static int template_untouched(struct strbuf *sb)
+{
+ struct strbuf tmpl = STRBUF_INIT;
+ const char *start;
+
+ if (cleanup_mode == CLEANUP_NONE && sb->len)
+ return 0;
+
+ if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
+ return 0;
+
+ stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
+ if (!skip_prefix(sb->buf, tmpl.buf, &start))
+ start = sb->buf;
+ strbuf_release(&tmpl);
+ return rest_is_empty(sb, start - sb->buf);
+}
+
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;
@@ -951,16 +1036,21 @@ static const char *find_author_by_nickname(const char *name)
av[++ac] = buf.buf;
av[++ac] = NULL;
setup_revisions(ac, av, &revs, NULL);
- prepare_revision_walk(&revs);
+ revs.mailmap = &mailmap;
+ read_mailmap(revs.mailmap, NULL);
+
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
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);
+ die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
}
@@ -980,27 +1070,54 @@ static void handle_untracked_files_arg(struct wt_status *s)
static const char *read_commit_message(const char *name)
{
- const char *out_enc, *out;
+ const char *out_enc;
struct commit *commit;
commit = lookup_commit_reference_by_name(name);
if (!commit)
die(_("could not lookup commit %s"), name);
out_enc = get_commit_output_encoding();
- out = logmsg_reencode(commit, out_enc);
+ return logmsg_reencode(commit, NULL, out_enc);
+}
- /*
- * If we failed to reencode the buffer, just copy it
- * byte for byte so the user can try to fix it up.
- * This also handles the case where input and output
- * encodings are identical.
- */
- if (out == NULL)
- out = xstrdup(commit->buffer);
- return out;
+/*
+ * 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[],
const char *prefix,
struct commit *current_head,
@@ -1008,8 +1125,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
{
int f = 0;
- argc = parse_options(argc, argv, prefix, builtin_commit_options, usage,
- 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);
@@ -1017,18 +1134,20 @@ 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 (edit_flag)
- use_editor = 1;
- if (!use_editor)
- setenv("GIT_EDITOR", ":", 1);
+ if (0 <= edit_flag)
+ use_editor = edit_flag;
/* Sanity check options */
if (amend && !current_head)
die(_("You have nothing to amend."));
- if (amend && whence != FROM_COMMIT)
- die(_("You are in the middle of a %s -- cannot amend."), whence_s());
+ if (amend && whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("You are in the middle of a merge -- cannot amend."));
+ else if (whence == FROM_CHERRY_PICK)
+ die(_("You are in the middle of a cherry-pick -- cannot amend."));
+ }
if (fixup_message && squash_message)
die(_("Options --squash and --fixup cannot be used together"));
if (use_message)
@@ -1043,6 +1162,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
die(_("Only one of -c/-C/-F/--fixup can be used."));
if (message.len && f > 0)
die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
+ if (f || message.len)
+ template_file = NULL;
if (edit_message)
use_message = edit_message;
if (amend && !use_message && !fixup_message)
@@ -1064,14 +1185,14 @@ 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."));
if (argc == 0 && only && amend)
only_include_assumed = _("Clever... amending the last one with dirty index.");
if (argc > 0 && !also && !only)
- only_include_assumed = _("Explicit paths specified without -i nor -o; assuming --only paths...");
+ only_include_assumed = _("Explicit paths specified without -i or -o; assuming --only paths...");
if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
else if (!strcmp(cleanup_arg, "verbatim"))
@@ -1080,6 +1201,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
cleanup_mode = CLEANUP_SPACE;
else if (!strcmp(cleanup_arg, "strip"))
cleanup_mode = CLEANUP_ALL;
+ else if (!strcmp(cleanup_arg, "scissors"))
+ cleanup_mode = use_editor ? CLEANUP_SCISSORS : CLEANUP_SPACE;
else
die(_("Invalid cleanup mode %s"), cleanup_arg);
@@ -1088,9 +1211,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (all && argc > 0)
die(_("Paths with -a does not make sense."));
- if (null_termination && status_format == STATUS_FORMAT_LONG)
- status_format = STATUS_FORMAT_PORCELAIN;
- if (status_format != STATUS_FORMAT_LONG)
+ if (status_format != STATUS_FORMAT_NONE)
dry_run = 1;
return argc;
@@ -1109,22 +1230,21 @@ static int dry_run_commit(int argc, const char **argv, const char *prefix,
return commitable ? 0 : 1;
}
-static int parse_status_slot(const char *var, int offset)
+static int parse_status_slot(const char *slot)
{
- if (!strcasecmp(var+offset, "header"))
+ if (!strcasecmp(slot, "header"))
return WT_STATUS_HEADER;
- if (!strcasecmp(var+offset, "branch"))
+ if (!strcasecmp(slot, "branch"))
return WT_STATUS_ONBRANCH;
- if (!strcasecmp(var+offset, "updated")
- || !strcasecmp(var+offset, "added"))
+ if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added"))
return WT_STATUS_UPDATED;
- if (!strcasecmp(var+offset, "changed"))
+ if (!strcasecmp(slot, "changed"))
return WT_STATUS_CHANGED;
- if (!strcasecmp(var+offset, "untracked"))
+ if (!strcasecmp(slot, "untracked"))
return WT_STATUS_UNTRACKED;
- if (!strcasecmp(var+offset, "nobranch"))
+ if (!strcasecmp(slot, "nobranch"))
return WT_STATUS_NOBRANCH;
- if (!strcasecmp(var+offset, "unmerged"))
+ if (!strcasecmp(slot, "unmerged"))
return WT_STATUS_UNMERGED;
return -1;
}
@@ -1132,7 +1252,10 @@ static int parse_status_slot(const char *var, int offset)
static int git_status_config(const char *k, const char *v, void *cb)
{
struct wt_status *s = cb;
+ const char *slot_name;
+ if (starts_with(k, "column."))
+ return git_column_config(k, v, "status", &s->colopts);
if (!strcmp(k, "status.submodulesummary")) {
int is_bool;
s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
@@ -1140,18 +1263,33 @@ 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.")) {
- int slot = parse_status_slot(k, 13);
+ if (!strcmp(k, "status.displaycommentprefix")) {
+ s->display_comment_prefix = git_config_bool(k, v);
+ return 0;
+ }
+ if (skip_prefix(k, "status.color.", &slot_name) ||
+ skip_prefix(k, "color.status.", &slot_name)) {
+ int slot = parse_status_slot(slot_name);
if (slot < 0)
return 0;
if (!v)
return config_error_nonbool(k);
- color_parse(v, k, s->color_palette[slot]);
- return 0;
+ return color_parse(v, s->color_palette[slot]);
}
if (!strcmp(k, "status.relativepaths")) {
s->relative_paths = git_config_bool(k, v);
@@ -1175,54 +1313,55 @@ static int git_status_config(const char *k, const char *v, void *cb)
int cmd_status(int argc, const char **argv, const char *prefix)
{
- struct wt_status s;
+ static struct wt_status s;
int fd;
unsigned char sha1[20];
static struct option builtin_status_options[] = {
- OPT__VERBOSE(&verbose, "be verbose"),
+ OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_SET_INT('s', "short", &status_format,
- "show status concisely", STATUS_FORMAT_SHORT),
- OPT_BOOLEAN('b', "branch", &status_show_branch,
- "show branch information"),
+ N_("show status concisely"), STATUS_FORMAT_SHORT),
+ OPT_BOOL('b', "branch", &s.show_branch,
+ N_("show branch information")),
OPT_SET_INT(0, "porcelain", &status_format,
- "machine-readable output",
+ N_("machine-readable output"),
STATUS_FORMAT_PORCELAIN),
- OPT_BOOLEAN('z', "null", &null_termination,
- "terminate entries with NUL"),
+ OPT_SET_INT(0, "long", &status_format,
+ N_("show status in long format (default)"),
+ STATUS_FORMAT_LONG),
+ OPT_BOOL('z', "null", &s.null_termination,
+ N_("terminate entries with NUL")),
{ OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
- "mode",
- "show untracked files, optional modes: all, normal, no. (Default: all)",
+ 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,
- "show ignored files"),
- { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when",
- "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)",
+ 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" },
+ OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")),
OPT_END(),
};
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);
-
- if (null_termination && status_format == STATUS_FORMAT_LONG)
- status_format = STATUS_FORMAT_PORCELAIN;
+ finalize_colopts(&s.colopts, -1);
+ 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)
@@ -1237,11 +1376,15 @@ int cmd_status(int argc, const char **argv, const char *prefix)
switch (status_format) {
case STATUS_FORMAT_SHORT:
- wt_shortstatus_print(&s, null_termination, status_show_branch);
+ wt_shortstatus_print(&s);
break;
case STATUS_FORMAT_PORCELAIN:
- wt_porcelain_print(&s, null_termination);
+ 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;
s.ignore_submodule_arg = ignore_submodule_arg;
@@ -1251,6 +1394,24 @@ int cmd_status(int argc, const char **argv, const char *prefix)
return 0;
}
+static const char *implicit_ident_advice(void)
+{
+ char *user_config = NULL;
+ char *xdg_config = NULL;
+ int config_exists;
+
+ home_config_paths(&user_config, &xdg_config, "config");
+ config_exists = file_exists(user_config) || file_exists(xdg_config);
+ free(user_config);
+ free(xdg_config);
+
+ if (config_exists)
+ return _(implicit_ident_advice_config);
+ else
+ return _(implicit_ident_advice_noconfig);
+
+}
+
static void print_summary(const char *prefix, const unsigned char *sha1,
int initial_commit)
{
@@ -1258,7 +1419,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
struct commit *commit;
struct strbuf format = STRBUF_INIT;
unsigned char junk_sha1[20];
- const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+ const char *head;
struct pretty_print_context pctx = {0};
struct strbuf author_ident = STRBUF_INIT;
struct strbuf committer_ident = STRBUF_INIT;
@@ -1266,7 +1427,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");
@@ -1277,12 +1438,19 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
strbuf_addstr(&format, "\n Author: ");
strbuf_addbuf_percentquote(&format, &author_ident);
}
- if (!user_ident_sufficiently_given()) {
+ if (author_date_is_interesting()) {
+ struct strbuf date = STRBUF_INIT;
+ format_commit_message(commit, "%ad", &date, &pctx);
+ strbuf_addstr(&format, "\n Date: ");
+ strbuf_addbuf_percentquote(&format, &date);
+ strbuf_release(&date);
+ }
+ if (!committer_ident_sufficiently_given()) {
strbuf_addstr(&format, "\n Committer: ");
strbuf_addbuf_percentquote(&format, &committer_ident);
if (advice_implicit_identity) {
strbuf_addch(&format, '\n');
- strbuf_addstr(&format, _(implicit_ident_advice));
+ strbuf_addstr(&format, implicit_ident_advice());
}
}
strbuf_release(&author_ident);
@@ -1303,13 +1471,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
- printf("[%s%s ",
- !prefixcmp(head, "refs/heads/") ?
- head + 11 :
- !strcmp(head, "HEAD") ?
- _("detached HEAD") :
- head,
- initial_commit ? _(" (root-commit)") : "");
+ head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL);
+ if (!strcmp(head, "HEAD"))
+ head = _("detached HEAD");
+ else
+ skip_prefix(head, "refs/heads/", &head);
+ printf("[%s%s ", head, initial_commit ? _(" (root-commit)") : "");
if (!log_tree_commit(&rev, commit)) {
rev.always_show_header = 1;
@@ -1323,6 +1490,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
static int git_commit_config(const char *k, const char *v, void *cb)
{
struct wt_status *s = cb;
+ int status;
if (!strcmp(k, "commit.template"))
return git_config_pathname(&template_file, k, v);
@@ -1330,30 +1498,36 @@ static int git_commit_config(const char *k, const char *v, void *cb)
include_status = git_config_bool(k, v);
return 0;
}
+ 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)
+ return status;
return git_status_config(k, v, s);
}
-static const char post_rewrite_hook[] = "hooks/post-rewrite";
-
static int run_rewrite_hook(const unsigned char *oldsha1,
const unsigned char *newsha1)
{
/* oldsha1 SP newsha1 LF NUL */
static char buf[2*40 + 3];
- struct child_process proc;
+ struct child_process proc = CHILD_PROCESS_INIT;
const char *argv[3];
int code;
size_t n;
- if (access(git_path(post_rewrite_hook), X_OK) < 0)
+ argv[0] = find_hook("post-rewrite");
+ if (!argv[0])
return 0;
- argv[0] = git_path(post_rewrite_hook);
argv[1] = "amend";
argv[2] = NULL;
- memset(&proc, 0, sizeof(proc));
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
@@ -1368,35 +1542,114 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
return finish_command(&proc);
}
+int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
+{
+ const char *hook_env[3] = { NULL };
+ char index[PATH_MAX];
+ va_list args;
+ int ret;
+
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+ hook_env[0] = index;
+
+ /*
+ * Let the hook know that no editor will be launched.
+ */
+ if (!editor_is_used)
+ hook_env[1] = "GIT_EDITOR=:";
+
+ va_start(args, name);
+ ret = run_hook_ve(hook_env, name, args);
+ va_end(args);
+
+ return ret;
+}
+
int cmd_commit(int argc, const char **argv, const char *prefix)
{
+ static struct wt_status s;
+ static struct option builtin_commit_options[] = {
+ OPT__QUIET(&quiet, N_("suppress summary after successful commit")),
+ OPT__VERBOSE(&verbose, N_("show diff in commit message template")),
+
+ OPT_GROUP(N_("Commit message options")),
+ OPT_FILENAME('F', "file", &logfile, N_("read message from file")),
+ OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")),
+ OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")),
+ OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
+ OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
+ OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
+ OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
+ OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
+ OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
+ OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")),
+ OPT_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_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_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_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_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 */
+
+ 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()
+ };
+
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 wt_status s;
struct commit *current_head = NULL;
+ struct commit_extra_header *extra = NULL;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_usage, builtin_commit_options);
- wt_status_prepare(&s);
- 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(sha1);
- if (!current_head || parse_commit(current_head))
+ current_head = lookup_commit_or_die(sha1, "HEAD");
+ if (parse_commit(current_head))
die(_("could not parse HEAD commit"));
}
- argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
+ argc = parse_and_validate_options(argc, argv, builtin_commit_options,
+ builtin_commit_usage,
prefix, current_head, &s);
if (dry_run)
return dry_run_commit(argc, argv, prefix, current_head, &s);
@@ -1425,6 +1678,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)";
@@ -1434,10 +1688,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
die_errno(_("could not open '%s' for reading"),
git_path("MERGE_HEAD"));
while (strbuf_getline(&m, fp, '\n') != EOF) {
- unsigned char sha1[20];
- if (get_sha1_hex(m.buf, sha1) < 0)
+ struct commit *parent;
+
+ parent = get_merge_parent(m.buf);
+ if (!parent)
die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
- pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
+ pptr = &commit_list_insert(parent, pptr)->next;
}
fclose(fp);
strbuf_release(&m);
@@ -1465,33 +1721,38 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
die(_("could not read commit message: %s"), strerror(saved_errno));
}
- /* 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 || /* Truncate the message just before the diff, if any. */
+ cleanup_mode == CLEANUP_SCISSORS)
+ wt_status_truncate_message_at_cut_line(&sb);
if (cleanup_mode != CLEANUP_NONE)
stripspace(&sb, cleanup_mode == CLEANUP_ALL);
+ if (template_untouched(&sb) && !allow_empty_message) {
+ rollback_index_files();
+ fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
+ exit(1);
+ }
if (message_is_empty(&sb) && !allow_empty_message) {
rollback_index_files();
fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
exit(1);
}
- if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
- author_ident.buf)) {
+ if (amend) {
+ const char *exclude_gpgsig[2] = { "gpgsig", NULL };
+ extra = read_commit_extra_headers(current_head, exclude_gpgsig);
+ } else {
+ struct commit_extra_header **tail = &extra;
+ append_merge_tag_headers(parents, &tail);
+ }
+
+ if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1,
+ parents, sha1, author_ident.buf, sign_commit, extra)) {
rollback_index_files();
die(_("failed to write commit object"));
}
strbuf_release(&author_ident);
-
- ref_lock = lock_any_ref_for_update("HEAD",
- !current_head
- ? NULL
- : current_head->object.sha1,
- 0);
+ free_commit_extra_headers(extra);
nl = strchr(sb.buf, '\n');
if (nl)
@@ -1501,16 +1762,20 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
- if (!ref_lock) {
- rollback_index_files();
- die(_("cannot lock HEAD ref"));
- }
- if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, "HEAD", sha1,
+ current_head
+ ? current_head->object.sha1 : NULL,
+ 0, !!current_head, sb.buf, &err) ||
+ ref_transaction_commit(transaction, &err)) {
rollback_index_files();
- die(_("cannot update HEAD ref"));
+ die("%s", err.buf);
}
+ ref_transaction_free(transaction);
unlink(git_path("CHERRY_PICK_HEAD"));
+ unlink(git_path("REVERT_HEAD"));
unlink(git_path("MERGE_HEAD"));
unlink(git_path("MERGE_MSG"));
unlink(git_path("MERGE_MODE"));
@@ -1518,23 +1783,24 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (commit_index_files())
die (_("Repository has been updated, but unable to write\n"
- "new_index file. Check that disk is not full or quota is\n"
+ "new_index file. Check that disk is not full and quota is\n"
"not exceeded, and then \"git reset HEAD\" to recover."));
rerere(0);
- run_hook(get_index_file(), "post-commit", NULL);
+ run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
if (amend && !no_post_rewrite) {
struct notes_rewrite_cfg *cfg;
cfg = init_copy_notes_for_rewrite("amend");
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);
}
if (!quiet)
print_summary(prefix, sha1, !current_head);
+ strbuf_release(&err);
return 0;
}
diff --git a/builtin/config.c b/builtin/config.c
index 0b4ecac..15a7bea 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -2,9 +2,10 @@
#include "cache.h"
#include "color.h"
#include "parse-options.h"
+#include "urlmatch.h"
static const char *const builtin_config_usage[] = {
- "git config [options]",
+ N_("git config [options]"),
NULL
};
@@ -15,16 +16,16 @@ static int show_keys;
static int use_key_regexp;
static int do_all;
static int do_not_match;
-static int seen;
static char delim = '=';
static char key_delim = ' ';
static char term = '\n';
static int use_global_config, use_system_config, use_local_config;
-static const char *given_config_file;
+static struct git_config_source given_config_source;
static int actions, types;
static const char *get_color_slot, *get_colorbool_slot;
static int end_null;
+static int respect_includes = -1;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
@@ -41,6 +42,7 @@ static int end_null;
#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)
@@ -48,32 +50,35 @@ static int end_null;
#define TYPE_PATH (1<<3)
static struct option builtin_config_options[] = {
- OPT_GROUP("Config file location"),
- OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
- OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
- OPT_BOOLEAN(0, "local", &use_local_config, "use repository config file"),
- OPT_STRING('f', "file", &given_config_file, "file", "use given config file"),
- OPT_GROUP("Action"),
- OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
- OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
- OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP),
- OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL),
- OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD),
- OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET),
- OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL),
- OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION),
- OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION),
- OPT_BIT('l', "list", &actions, "list all", ACTION_LIST),
- OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT),
- OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"),
- OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"),
- OPT_GROUP("Type"),
- OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
- OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
- OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
- OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH),
- OPT_GROUP("Other"),
- OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
+ OPT_GROUP(N_("Config file location")),
+ 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_source.file, N_("file"), N_("use given config file")),
+ OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
+ OPT_GROUP(N_("Action")),
+ OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
+ OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL),
+ OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP),
+ OPT_BIT(0, "get-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),
+ OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL),
+ OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
+ OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
+ OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST),
+ OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
+ OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR),
+ OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL),
+ OPT_GROUP(N_("Type")),
+ OPT_BIT(0, "bool", &types, N_("value is \"true\" or \"false\""), TYPE_BOOL),
+ OPT_BIT(0, "int", &types, N_("value is decimal number"), TYPE_INT),
+ 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_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(),
};
@@ -93,31 +98,28 @@ static int show_all_config(const char *key_, const char *value_, void *cb)
return 0;
}
-static int show_config(const char *key_, const char *value_, void *cb)
+struct strbuf_list {
+ struct strbuf *items;
+ int nr;
+ int alloc;
+};
+
+static int format_config(struct strbuf *buf, const char *key_, const char *value_)
{
+ int must_free_vptr = 0;
+ int must_print_delim = 0;
char value[256];
const char *vptr = value;
- int must_free_vptr = 0;
- int dup_error = 0;
- 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;
+ strbuf_init(buf, 0);
if (show_keys) {
- if (value_)
- printf("%s%c", key_, key_delim);
- else
- printf("%s", key_);
+ strbuf_addstr(buf, key_);
+ must_print_delim = 1;
}
- if (seen && !do_all)
- dup_error = 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) {
@@ -128,43 +130,49 @@ static int show_config(const char *key_, const char *value_, void *cb)
else
sprintf(value, "%d", v);
} else if (types == TYPE_PATH) {
- git_config_pathname(&vptr, key_, value_);
+ if (git_config_pathname(&vptr, key_, value_) < 0)
+ return -1;
must_free_vptr = 1;
+ } else if (value_) {
+ vptr = value_;
+ } else {
+ /* Just show the key name */
+ vptr = "";
+ must_print_delim = 0;
}
- else
- vptr = value_?value_:"";
- seen++;
- if (dup_error) {
- error("More than one value for the key %s: %s",
- key_, vptr);
- }
- else
- printf("%s%c", vptr, term);
+
+ if (must_print_delim)
+ strbuf_addch(buf, key_delim);
+ strbuf_addstr(buf, vptr);
+ 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 get_value(const char *key_, const char *regex_)
+static int collect_config(const char *key_, const char *value_, void *cb)
{
- int ret = -1;
- char *global = NULL, *repo_config = NULL;
- const char *system_wide = NULL, *local;
+ struct strbuf_list *values = cb;
- local = config_exclusive_filename;
- if (!local) {
- const char *home = getenv("HOME");
- local = repo_config = git_pathdup("config");
- if (home)
- global = xstrdup(mkpath("%s/.gitconfig", home));
- if (git_config_system())
- system_wide = git_etc_gitconfig();
- }
+ 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;
+ struct strbuf_list values = {NULL};
+ int i;
if (use_key_regexp) {
char *tl;
@@ -186,12 +194,16 @@ static int get_value(const char *key_, const char *regex_)
key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(key_regexp, key, REG_EXTENDED)) {
fprintf(stderr, "Invalid key pattern: %s\n", key_);
- free(key);
+ free(key_regexp);
+ key_regexp = NULL;
+ ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
} else {
- if (git_config_parse_key(key_, &key, NULL))
+ if (git_config_parse_key(key_, &key, NULL)) {
+ ret = CONFIG_INVALID_KEY;
goto free_strings;
+ }
}
if (regex_) {
@@ -203,38 +215,37 @@ static int get_value(const char *key_, const char *regex_)
regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(regexp, regex_, REG_EXTENDED)) {
fprintf(stderr, "Invalid pattern: %s\n", regex_);
+ free(regexp);
+ regexp = NULL;
+ ret = CONFIG_INVALID_PATTERN;
goto free_strings;
}
}
- if (do_all && system_wide)
- git_config_from_file(show_config, system_wide, NULL);
- if (do_all && global)
- git_config_from_file(show_config, global, NULL);
- if (do_all)
- git_config_from_file(show_config, local, NULL);
- git_config_from_parameters(show_config, NULL);
- if (!do_all && !seen)
- git_config_from_file(show_config, local, NULL);
- if (!do_all && !seen && global)
- git_config_from_file(show_config, global, NULL);
- if (!do_all && !seen && system_wide)
- git_config_from_file(show_config, system_wide, NULL);
+ git_config_with_options(collect_config, &values,
+ &given_config_source, respect_includes);
+
+ ret = !values.nr;
+ for (i = 0; i < values.nr; i++) {
+ struct strbuf *buf = values.items + i;
+ if (do_all || i == values.nr - 1)
+ fwrite(buf->buf, 1, buf->len, stdout);
+ strbuf_release(buf);
+ }
+ free(values.items);
+
+free_strings:
free(key);
+ if (key_regexp) {
+ regfree(key_regexp);
+ free(key_regexp);
+ }
if (regexp) {
regfree(regexp);
free(regexp);
}
- if (do_all)
- ret = !seen;
- else
- ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
-
-free_strings:
- free(repo_config);
- free(global);
return ret;
}
@@ -256,8 +267,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",
@@ -285,20 +296,25 @@ static int git_get_color_config(const char *var, const char *value, void *cb)
if (!strcmp(var, get_color_slot)) {
if (!value)
config_error_nonbool(var);
- color_parse(value, var, parsed_color);
+ if (color_parse(value, parsed_color) < 0)
+ return -1;
get_color_found = 1;
}
return 0;
}
-static void get_color(const char *def_color)
+static void get_color(const char *var, const char *def_color)
{
+ get_color_slot = var;
get_color_found = 0;
parsed_color[0] = '\0';
- git_config(git_get_color_config, NULL);
+ git_config_with_options(git_get_color_config, NULL,
+ &given_config_source, respect_includes);
- if (!get_color_found && def_color)
- color_parse(def_color, "command line", parsed_color);
+ if (!get_color_found && def_color) {
+ if (color_parse(def_color, parsed_color) < 0)
+ die(_("unable to parse default color value"));
+ }
fputs(parsed_color, stdout);
}
@@ -318,11 +334,14 @@ static int git_get_colorbool_config(const char *var, const char *value,
return 0;
}
-static int get_colorbool(int print)
+static int get_colorbool(const char *var, int print)
{
+ get_colorbool_slot = var;
get_colorbool_found = -1;
get_diff_color_found = -1;
- git_config(git_get_colorbool_config, NULL);
+ get_color_ui_found = -1;
+ git_config_with_options(git_get_colorbool_config, NULL,
+ &given_config_source, respect_includes);
if (get_colorbool_found < 0) {
if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -331,6 +350,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) {
@@ -340,44 +363,166 @@ static int get_colorbool(int print)
return get_colorbool_found ? 0 : 1;
}
+static void check_write(void)
+{
+ if (given_config_source.use_stdin)
+ die("writing to stdin is not supported");
+
+ if (given_config_source.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 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 = xstrdup_tolower(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_source, 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;
+}
+
+static char *default_user_config(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf,
+ _("# This is Git's per-user configuration file.\n"
+ "[core]\n"
+ "# Please adapt and uncomment the following lines:\n"
+ "# user = %s\n"
+ "# email = %s\n"),
+ ident_default_name(),
+ ident_default_email());
+ return strbuf_detach(&buf, NULL);
+}
+
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = !startup_info->have_repository;
char *value;
- config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
+ given_config_source.file = getenv(CONFIG_ENVIRONMENT);
argc = parse_options(argc, argv, prefix, builtin_config_options,
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_source.file + !!given_config_source.blob > 1) {
error("only one config file at a time.");
usage_with_options(builtin_config_usage, builtin_config_options);
}
+ if (given_config_source.file &&
+ !strcmp(given_config_source.file, "-")) {
+ given_config_source.file = NULL;
+ given_config_source.use_stdin = 1;
+ }
+
if (use_global_config) {
- char *home = getenv("HOME");
- if (home) {
- char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
- config_exclusive_filename = user_config;
- } else {
+ char *user_config = NULL;
+ char *xdg_config = NULL;
+
+ home_config_paths(&user_config, &xdg_config, "config");
+
+ if (!user_config)
+ /*
+ * It is unknown if HOME/.gitconfig exists, so
+ * we do not know if we should write to XDG
+ * location; error out even if XDG_CONFIG_HOME
+ * is set and points at a sane location.
+ */
die("$HOME not set");
- }
+
+ if (access_or_warn(user_config, R_OK, 0) &&
+ xdg_config && !access_or_warn(xdg_config, R_OK, 0))
+ given_config_source.file = xdg_config;
+ else
+ given_config_source.file = user_config;
}
else if (use_system_config)
- config_exclusive_filename = git_etc_gitconfig();
+ given_config_source.file = git_etc_gitconfig();
else if (use_local_config)
- config_exclusive_filename = git_pathdup("config");
- else if (given_config_file) {
- if (!is_absolute_path(given_config_file) && prefix)
- config_exclusive_filename = prefix_filename(prefix,
- strlen(prefix),
- given_config_file);
- else
- config_exclusive_filename = given_config_file;
+ given_config_source.file = git_pathdup("config");
+ else if (given_config_source.file) {
+ if (!is_absolute_path(given_config_source.file) && prefix)
+ given_config_source.file =
+ xstrdup(prefix_filename(prefix,
+ strlen(prefix),
+ given_config_source.file));
}
+ if (respect_includes == -1)
+ respect_includes = !given_config_source.file;
+
if (end_null) {
term = '\0';
delim = '\n';
@@ -389,12 +534,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_config_usage, builtin_config_options);
}
- if (get_color_slot)
- actions |= ACTION_GET_COLOR;
- if (get_colorbool_slot)
- actions |= ACTION_GET_COLORBOOL;
-
- if ((get_color_slot || get_colorbool_slot) && types) {
+ if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && types) {
error("--get-color and variable type are incoherent");
usage_with_options(builtin_config_usage, builtin_config_options);
}
@@ -414,47 +554,75 @@ int cmd_config(int argc, const char **argv, const char *prefix)
if (actions == ACTION_LIST) {
check_argc(argc, 0, 0);
- if (git_config(show_all_config, NULL) < 0) {
- if (config_exclusive_filename)
+ if (git_config_with_options(show_all_config, NULL,
+ &given_config_source,
+ respect_includes) < 0) {
+ if (given_config_source.file)
die_errno("unable to read config file '%s'",
- config_exclusive_filename);
+ given_config_source.file);
else
die("error processing config file(s)");
}
}
else if (actions == ACTION_EDIT) {
+ char *config_file;
+
check_argc(argc, 0, 0);
- if (!config_exclusive_filename && nongit)
+ if (!given_config_source.file && nongit)
die("not in a git directory");
+ if (given_config_source.use_stdin)
+ die("editing stdin is not supported");
+ if (given_config_source.blob)
+ die("editing blobs is not supported");
git_config(git_default_config, NULL);
- launch_editor(config_exclusive_filename ?
- config_exclusive_filename : git_path("config"),
- NULL, NULL);
+ config_file = xstrdup(given_config_source.file ?
+ given_config_source.file : git_path("config"));
+ if (use_global_config) {
+ int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ if (fd) {
+ char *content = default_user_config();
+ write_str_in_full(fd, content);
+ free(content);
+ close(fd);
+ }
+ else if (errno != EEXIST)
+ die_errno(_("cannot create configuration file %s"), config_file);
+ }
+ launch_editor(config_file, NULL, NULL);
+ free(config_file);
}
else if (actions == ACTION_SET) {
int ret;
+ check_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
- ret = git_config_set(argv[0], value);
+ ret = git_config_set_in_file(given_config_source.file, argv[0], value);
if (ret == CONFIG_NOTHING_SET)
error("cannot overwrite multiple values with a single value\n"
- " Use a regexp, --add or --set-all to change %s.", argv[0]);
+ " Use a regexp, --add or --replace-all to change %s.", argv[0]);
return ret;
}
else if (actions == ACTION_SET_ALL) {
+ check_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, argv[2], 0);
+ return git_config_set_multivar_in_file(given_config_source.file,
+ argv[0], value, argv[2], 0);
}
else if (actions == ACTION_ADD) {
+ check_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, "^$", 0);
+ return git_config_set_multivar_in_file(given_config_source.file,
+ argv[0], value,
+ CONFIG_REGEX_NONE, 0);
}
else if (actions == ACTION_REPLACE_ALL) {
+ check_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar(argv[0], value, argv[2], 1);
+ return git_config_set_multivar_in_file(given_config_source.file,
+ argv[0], value, argv[2], 1);
}
else if (actions == ACTION_GET) {
check_argc(argc, 1, 2);
@@ -472,21 +640,32 @@ 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_write();
check_argc(argc, 1, 2);
if (argc == 2)
- return git_config_set_multivar(argv[0], NULL, argv[1], 0);
+ return git_config_set_multivar_in_file(given_config_source.file,
+ argv[0], NULL, argv[1], 0);
else
- return git_config_set(argv[0], NULL);
+ return git_config_set_in_file(given_config_source.file,
+ argv[0], NULL);
}
else if (actions == ACTION_UNSET_ALL) {
+ check_write();
check_argc(argc, 1, 2);
- return git_config_set_multivar(argv[0], NULL, argv[1], 1);
+ return git_config_set_multivar_in_file(given_config_source.file,
+ argv[0], NULL, argv[1], 1);
}
else if (actions == ACTION_RENAME_SECTION) {
int ret;
+ check_write();
check_argc(argc, 2, 2);
- ret = git_config_rename_section(argv[0], argv[1]);
+ ret = git_config_rename_section_in_file(given_config_source.file,
+ argv[0], argv[1]);
if (ret < 0)
return ret;
if (ret == 0)
@@ -494,27 +673,25 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_REMOVE_SECTION) {
int ret;
+ check_write();
check_argc(argc, 1, 1);
- ret = git_config_rename_section(argv[0], NULL);
+ ret = git_config_rename_section_in_file(given_config_source.file,
+ argv[0], NULL);
if (ret < 0)
return ret;
if (ret == 0)
die("No such section!");
}
else if (actions == ACTION_GET_COLOR) {
- get_color(argv[0]);
+ check_argc(argc, 1, 2);
+ get_color(argv[0], argv[1]);
}
else if (actions == ACTION_GET_COLORBOOL) {
- if (argc == 1)
- color_stdout_is_tty = git_config_bool("command line", argv[0]);
- return get_colorbool(argc != 0);
+ check_argc(argc, 1, 2);
+ if (argc == 2)
+ color_stdout_is_tty = git_config_bool("command line", argv[1]);
+ return get_colorbool(argv[0], argc == 2);
}
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/count-objects.c b/builtin/count-objects.c
index c37cb98..e47ef0b 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -9,77 +9,60 @@
#include "builtin.h"
#include "parse-options.h"
-static void count_objects(DIR *d, char *path, int len, int verbose,
- unsigned long *loose,
- off_t *loose_size,
- unsigned long *packed_loose,
- unsigned long *garbage)
+static unsigned long garbage;
+static off_t size_garbage;
+static int verbose;
+static unsigned long loose, packed, packed_loose;
+static off_t loose_size;
+
+static void real_report_garbage(const char *desc, const char *path)
{
- struct dirent *ent;
- while ((ent = readdir(d)) != NULL) {
- char hex[41];
- unsigned char sha1[20];
- const char *cp;
- int bad = 0;
+ struct stat st;
+ if (!stat(path, &st))
+ size_garbage += st.st_size;
+ warning("%s: %s", desc, path);
+ garbage++;
+}
- if (is_dot_or_dotdot(ent->d_name))
- continue;
- for (cp = ent->d_name; *cp; cp++) {
- int ch = *cp;
- if (('0' <= ch && ch <= '9') ||
- ('a' <= ch && ch <= 'f'))
- continue;
- bad = 1;
- break;
- }
- if (cp - ent->d_name != 38)
- bad = 1;
- else {
- struct stat st;
- memcpy(path + len + 3, ent->d_name, 38);
- path[len + 2] = '/';
- path[len + 41] = 0;
- if (lstat(path, &st) || !S_ISREG(st.st_mode))
- bad = 1;
- else
- (*loose_size) += xsize_t(on_disk_bytes(st));
- }
- if (bad) {
- if (verbose) {
- error("garbage found: %.*s/%s",
- len + 2, path, ent->d_name);
- (*garbage)++;
- }
- continue;
- }
- (*loose)++;
- if (!verbose)
- continue;
- memcpy(hex, path+len, 2);
- memcpy(hex+2, ent->d_name, 38);
- hex[40] = 0;
- if (get_sha1_hex(hex, sha1))
- die("internal error");
- if (has_sha1_pack(sha1))
- (*packed_loose)++;
+static void loose_garbage(const char *path)
+{
+ if (verbose)
+ report_garbage("garbage found", path);
+}
+
+static int count_loose(const unsigned char *sha1, const char *path, void *data)
+{
+ struct stat st;
+
+ if (lstat(path, &st) || !S_ISREG(st.st_mode))
+ loose_garbage(path);
+ else {
+ loose_size += on_disk_bytes(st);
+ loose++;
+ if (verbose && has_sha1_pack(sha1))
+ packed_loose++;
}
+ return 0;
+}
+
+static int count_cruft(const char *basename, const char *path, void *data)
+{
+ loose_garbage(path);
+ return 0;
}
static char const * const count_objects_usage[] = {
- "git count-objects [-v]",
+ N_("git count-objects [-v] [-H | --human-readable]"),
NULL
};
int cmd_count_objects(int argc, const char **argv, const char *prefix)
{
- int i, verbose = 0;
- const char *objdir = get_object_directory();
- int len = strlen(objdir);
- char *path = xmalloc(len + 50);
- unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
- off_t loose_size = 0;
+ int human_readable = 0;
struct option opts[] = {
- OPT__VERBOSE(&verbose, "be verbose"),
+ OPT__VERBOSE(&verbose, N_("be verbose")),
+ OPT_BOOL('H', "human-readable", &human_readable,
+ N_("print sizes in human readable format")),
OPT_END(),
};
@@ -87,23 +70,19 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
/* we do not take arguments other than flags for now */
if (argc)
usage_with_options(count_objects_usage, opts);
- memcpy(path, objdir, len);
- if (len && objdir[len-1] != '/')
- path[len++] = '/';
- for (i = 0; i < 256; i++) {
- DIR *d;
- sprintf(path + len, "%02x", i);
- d = opendir(path);
- if (!d)
- continue;
- count_objects(d, path, len, verbose,
- &loose, &loose_size, &packed_loose, &garbage);
- closedir(d);
- }
+ if (verbose)
+ report_garbage = real_report_garbage;
+
+ for_each_loose_file_in_objdir(get_object_directory(),
+ count_loose, count_cruft, NULL, NULL);
+
if (verbose) {
struct packed_git *p;
unsigned long num_pack = 0;
off_t size_pack = 0;
+ struct strbuf loose_buf = STRBUF_INIT;
+ struct strbuf pack_buf = STRBUF_INIT;
+ struct strbuf garbage_buf = STRBUF_INIT;
if (!packed_git)
prepare_packed_git();
for (p = packed_git; p; p = p->next) {
@@ -115,16 +94,40 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
size_pack += p->pack_size + p->index_size;
num_pack++;
}
+
+ if (human_readable) {
+ strbuf_humanise_bytes(&loose_buf, loose_size);
+ strbuf_humanise_bytes(&pack_buf, size_pack);
+ strbuf_humanise_bytes(&garbage_buf, size_garbage);
+ } else {
+ strbuf_addf(&loose_buf, "%lu",
+ (unsigned long)(loose_size / 1024));
+ strbuf_addf(&pack_buf, "%lu",
+ (unsigned long)(size_pack / 1024));
+ strbuf_addf(&garbage_buf, "%lu",
+ (unsigned long)(size_garbage / 1024));
+ }
+
printf("count: %lu\n", loose);
- printf("size: %lu\n", (unsigned long) (loose_size / 1024));
+ printf("size: %s\n", loose_buf.buf);
printf("in-pack: %lu\n", packed);
printf("packs: %lu\n", num_pack);
- printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024));
+ printf("size-pack: %s\n", pack_buf.buf);
printf("prune-packable: %lu\n", packed_loose);
printf("garbage: %lu\n", garbage);
+ printf("size-garbage: %s\n", garbage_buf.buf);
+ strbuf_release(&loose_buf);
+ strbuf_release(&pack_buf);
+ strbuf_release(&garbage_buf);
+ } else {
+ struct strbuf buf = STRBUF_INIT;
+ if (human_readable)
+ strbuf_humanise_bytes(&buf, loose_size);
+ else
+ strbuf_addf(&buf, "%lu kilobytes",
+ (unsigned long)(loose_size / 1024));
+ printf("%lu objects, %s\n", loose, buf.buf);
+ strbuf_release(&buf);
}
- else
- printf("%lu objects, %lu kilobytes\n",
- loose, (unsigned long) (loose_size / 1024));
return 0;
}
diff --git a/builtin/credential.c b/builtin/credential.c
new file mode 100644
index 0000000..0412fa0
--- /dev/null
+++ b/builtin/credential.c
@@ -0,0 +1,31 @@
+#include "git-compat-util.h"
+#include "credential.h"
+#include "builtin.h"
+
+static const char usage_msg[] =
+ "git credential [fill|approve|reject]";
+
+int cmd_credential(int argc, const char **argv, const char *prefix)
+{
+ const char *op;
+ struct credential c = CREDENTIAL_INIT;
+
+ op = argv[1];
+ if (!op)
+ usage(usage_msg);
+
+ if (credential_read(&c, stdin) < 0)
+ die("unable to read credential from stdin");
+
+ if (!strcmp(op, "fill")) {
+ credential_fill(&c);
+ credential_write(&c, stdout);
+ } else if (!strcmp(op, "approve")) {
+ credential_approve(&c);
+ } else if (!strcmp(op, "reject")) {
+ credential_reject(&c);
+ } else {
+ usage(usage_msg);
+ }
+ return 0;
+}
diff --git a/builtin/describe.c b/builtin/describe.c
index 9f63067..9103193 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -1,4 +1,5 @@
#include "cache.h"
+#include "lockfile.h"
#include "commit.h"
#include "tag.h"
#include "refs.h"
@@ -6,14 +7,15 @@
#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[] = {
- "git describe [options] <committish>*",
- "git describe [options] --dirty",
+ N_("git describe [options] <commit-ish>*"),
+ N_("git describe [options] --dirty"),
NULL
};
@@ -21,9 +23,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,44 +37,29 @@ 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 inline unsigned int hash_sha1(const unsigned char *sha1)
+static int commit_name_cmp(const struct commit_name *cn1,
+ const struct commit_name *cn2, const void *peeled)
{
- unsigned int hash;
- memcpy(&hash, sha1, sizeof(hash));
- return hash;
+ return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled);
}
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;
+ return hashmap_get_from_hash(&names, sha1hash(peeled), peeled);
}
static int replace_name(struct commit_name *e,
@@ -116,61 +104,56 @@ 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, sha1hash(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 might_be_tag = !prefixcmp(path, "refs/tags/");
+ int is_tag = starts_with(path, "refs/tags/");
unsigned char peeled[20];
- int is_tag, prio;
+ int is_annotated, prio;
- if (!all && !might_be_tag)
+ /* Reject anything outside refs/tags/ unless --all */
+ if (!all && !is_tag)
return 0;
- if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
- is_tag = !!hashcmp(sha1, peeled);
+ /* Accept only tags that match the pattern, if given */
+ if (pattern && (!is_tag || wildmatch(pattern, path + 10, 0, NULL)))
+ return 0;
+
+ /* Is it annotated? */
+ if (!peel_ref(path, peeled)) {
+ is_annotated = !!hashcmp(sha1, peeled);
} else {
hashcpy(peeled, sha1);
- is_tag = 0;
+ is_annotated = 0;
}
- /* If --all, then any refs are used.
- * If --tags, then any tags are used.
- * Otherwise only annotated tags are used.
+ /*
+ * By default, we only use annotated tags, but with --tags
+ * we fall back to lightweight ones (even without --tags,
+ * we still remember lightweight ones, only to give hints
+ * in an error message). --all allows any refs to be used.
*/
- if (might_be_tag) {
- if (is_tag)
- prio = 2;
- else
- prio = 1;
-
- if (pattern && fnmatch(pattern, path + 10, 0))
- prio = 0;
- }
+ if (is_annotated)
+ prio = 2;
+ else if (is_tag)
+ prio = 1;
else
prio = 0;
- if (!all) {
- if (!prio)
- return 0;
- }
add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1);
return 0;
}
@@ -289,7 +272,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;
}
@@ -337,6 +327,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;
}
}
@@ -400,23 +393,24 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
{
int contains = 0;
struct option options[] = {
- OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"),
- OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"),
- OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"),
- OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"),
- OPT_BOOLEAN(0, "long", &longformat, "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,
- "only output exact matches", 0),
+ N_("only output exact matches"), 0),
OPT_INTEGER(0, "candidates", &max_candidates,
- "consider <n> most recent tags (default: 10)"),
- OPT_STRING(0, "match", &pattern, "pattern",
- "only consider tags matching <pattern>"),
- OPT_BOOLEAN(0, "always", &always,
- "show abbreviated commit object as fallback"),
- {OPTION_STRING, 0, "dirty", &dirty, "mark",
- "append <mark> on dirty working tree (default: \"-dirty\")",
- PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"},
+ N_("consider <n> most recent tags (default: 10)")),
+ OPT_STRING(0, "match", &pattern, N_("pattern"),
+ N_("only consider tags matching <pattern>")),
+ 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"},
OPT_END(),
};
@@ -436,29 +430,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) {
@@ -479,11 +473,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 46085f8..9200069 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 2eb32bd..ce15b23 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -41,9 +41,13 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
if (rev.pending.nr != 1 ||
rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
usage(diff_cache_usage);
- if (!cached)
+ if (!cached) {
setup_work_tree();
- if (read_cache() < 0) {
+ if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
+ perror("read_cache_preload");
+ return -1;
+ }
+ } else if (read_cache() < 0) {
perror("read_cache");
return -1;
}
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
index be6417d..1c4ad62 100644
--- a/builtin/diff-tree.c
+++ b/builtin/diff-tree.c
@@ -22,14 +22,10 @@ static int stdin_diff_commit(struct commit *commit, char *line, int len)
if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
/* Graft the fake parents locally to the commit */
int pos = 41;
- struct commit_list **pptr, *parents;
+ struct commit_list **pptr;
/* Free the real parent list */
- for (parents = commit->parents; parents; ) {
- struct commit_list *tmp = parents->next;
- free(parents);
- parents = tmp;
- }
+ free_commit_list(commit->parents);
commit->parents = NULL;
pptr = &(commit->parents);
while (line[pos] && !get_sha1_hex(line + pos, sha1)) {
@@ -72,9 +68,7 @@ static int diff_tree_stdin(char *line)
line[len-1] = 0;
if (get_sha1_hex(line, sha1))
return -1;
- obj = lookup_unknown_object(sha1);
- if (!obj || !obj->parsed)
- obj = parse_object(sha1);
+ obj = parse_object(sha1);
if (!obj)
return -1;
if (obj->type == OBJ_COMMIT)
diff --git a/builtin/diff.c b/builtin/diff.c
index 1118689..4326fa5 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -4,6 +4,7 @@
* Copyright (c) 2006 Junio C Hamano
*/
#include "cache.h"
+#include "lockfile.h"
#include "color.h"
#include "commit.h"
#include "blob.h"
@@ -14,6 +15,10 @@
#include "log-tree.h"
#include "builtin.h"
#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];
@@ -28,6 +33,8 @@ static void stuff_change(struct diff_options *opt,
unsigned old_mode, unsigned new_mode,
const unsigned char *old_sha1,
const unsigned char *new_sha1,
+ int old_sha1_valid,
+ int new_sha1_valid,
const char *old_name,
const char *new_name)
{
@@ -53,23 +60,26 @@ static void stuff_change(struct diff_options *opt,
one = alloc_filespec(old_name);
two = alloc_filespec(new_name);
- fill_filespec(one, old_sha1, old_mode);
- fill_filespec(two, new_sha1, new_mode);
+ fill_filespec(one, old_sha1, old_sha1_valid, old_mode);
+ fill_filespec(two, new_sha1, new_sha1_valid, new_mode);
diff_queue(&diff_queued_diff, one, two);
}
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)))
@@ -83,6 +93,7 @@ static int builtin_diff_b_f(struct rev_info *revs,
stuff_change(&revs->diffopt,
blob[0].mode, canon_mode(st.st_mode),
blob[0].sha1, null_sha1,
+ 1, 0,
path, path);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
@@ -107,6 +118,7 @@ static int builtin_diff_blobs(struct rev_info *revs,
stuff_change(&revs->diffopt,
blob[0].mode, blob[1].mode,
blob[0].sha1, blob[1].sha1,
+ 1, 1,
blob[0].name, blob[1].name);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
@@ -125,8 +137,6 @@ static int builtin_diff_index(struct rev_info *revs,
usage(builtin_diff_usage);
argv++; argc--;
}
- if (!cached)
- setup_work_tree();
/*
* Make sure there is one revision (i.e. pending object),
* and there is no revision filtering parameters.
@@ -135,8 +145,14 @@ static int builtin_diff_index(struct rev_info *revs,
revs->max_count != -1 || revs->min_age != -1 ||
revs->max_age != -1)
usage(builtin_diff_usage);
- if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
- perror("read_cache_preload");
+ if (!cached) {
+ setup_work_tree();
+ if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
+ perror("read_cache_preload");
+ return -1;
+ }
+ } else if (read_cache() < 0) {
+ perror("read_cache");
return -1;
}
return run_diff_index(revs, cached);
@@ -144,7 +160,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;
@@ -152,13 +169,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;
@@ -169,7 +187,7 @@ static int builtin_diff_combined(struct rev_info *revs,
struct object_array_entry *ent,
int ents)
{
- const unsigned char (*parent)[20];
+ struct sha1_array parents = SHA1_ARRAY_INIT;
int i;
if (argc > 1)
@@ -177,12 +195,11 @@ static int builtin_diff_combined(struct rev_info *revs,
if (!revs->dense_combined_merges && !revs->combine_merges)
revs->dense_combined_merges = revs->combine_merges = 1;
- parent = xmalloc(ents * sizeof(*parent));
- for (i = 0; i < ents; i++)
- hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
- diff_tree_combined(parent[0], parent + 1, ents - 1,
+ for (i = 1; i < ents; i++)
+ sha1_array_append(&parents, ent[i].item->sha1);
+ diff_tree_combined(ent[0].item->sha1, &parents,
revs->dense_combined_merges, revs);
- free(parent);
+ sha1_array_clear(&parents);
return 0;
}
@@ -232,7 +249,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;
}
@@ -243,11 +260,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;
/*
@@ -273,18 +289,67 @@ 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;
+ /* Scale to real terminal size and respect statGraphWidth config */
+ rev.diffopt.stat_width = -1;
+ rev.diffopt.stat_graph_width = -1;
+
/* Default to let external and textconv be used */
DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
@@ -294,19 +359,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
argc = setup_revisions(argc, argv, &rev, NULL);
if (!rev.diffopt.output_format) {
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
- if (diff_setup_done(&rev.diffopt) < 0)
- die(_("diff_setup_done failed"));
+ diff_setup_done(&rev.diffopt);
}
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
- /*
- * If the user asked for our exit code then don't start a
- * pager or we would end up reporting its exit code instead.
- */
- if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) &&
- check_pager_config("diff") != 0)
- setup_pager();
+ setup_diff_pager(&rev.diffopt);
/*
* Do we have --cached and not have a pending object, then
@@ -323,7 +381,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
add_head_to_pending(&rev);
if (!rev.pending.nr) {
struct tree *tree;
- tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN);
+ tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
add_pending_object(&rev, &tree->object, "HEAD");
}
break;
@@ -332,9 +390,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);
@@ -343,38 +401,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);
@@ -382,7 +431,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)
@@ -395,23 +444,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 9836e6b..b8182c2 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -17,19 +17,25 @@
#include "utf8.h"
#include "parse-options.h"
#include "quote.h"
+#include "remote.h"
+#include "blob.h"
static const char *fast_export_usage[] = {
- "git fast-export [rev-list-opts]",
+ N_("git fast-export [rev-list-opts]"),
NULL
};
static int progress;
-static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
-static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
+static enum { ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = ABORT;
+static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR;
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 struct refspec *refspecs;
+static int refspecs_nr;
+static int anonymize;
static int parse_opt_signed_tag_mode(const struct option *opt,
const char *arg, int unset)
@@ -40,10 +46,12 @@ static int parse_opt_signed_tag_mode(const struct option *opt,
signed_tag_mode = VERBATIM;
else if (!strcmp(arg, "warn"))
signed_tag_mode = WARN;
+ else if (!strcmp(arg, "warn-strip"))
+ signed_tag_mode = WARN_STRIP;
else if (!strcmp(arg, "strip"))
signed_tag_mode = STRIP;
else
- return error("Unknown signed-tag mode: %s", arg);
+ return error("Unknown signed-tags mode: %s", arg);
return 0;
}
@@ -51,7 +59,7 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt,
const char *arg, int unset)
{
if (unset || !strcmp(arg, "abort"))
- tag_of_filtered_mode = ABORT;
+ tag_of_filtered_mode = ERROR;
else if (!strcmp(arg, "drop"))
tag_of_filtered_mode = DROP;
else if (!strcmp(arg, "rewrite"))
@@ -75,6 +83,76 @@ static int has_unshown_parent(struct commit *commit)
return 0;
}
+struct anonymized_entry {
+ struct hashmap_entry hash;
+ const char *orig;
+ size_t orig_len;
+ const char *anon;
+ size_t anon_len;
+};
+
+static int anonymized_entry_cmp(const void *va, const void *vb,
+ const void *data)
+{
+ const struct anonymized_entry *a = va, *b = vb;
+ return a->orig_len != b->orig_len ||
+ memcmp(a->orig, b->orig, a->orig_len);
+}
+
+/*
+ * Basically keep a cache of X->Y so that we can repeatedly replace
+ * the same anonymized string with another. The actual generation
+ * is farmed out to the generate function.
+ */
+static const void *anonymize_mem(struct hashmap *map,
+ void *(*generate)(const void *, size_t *),
+ const void *orig, size_t *len)
+{
+ struct anonymized_entry key, *ret;
+
+ if (!map->cmpfn)
+ hashmap_init(map, anonymized_entry_cmp, 0);
+
+ hashmap_entry_init(&key, memhash(orig, *len));
+ key.orig = orig;
+ key.orig_len = *len;
+ ret = hashmap_get(map, &key, NULL);
+
+ if (!ret) {
+ ret = xmalloc(sizeof(*ret));
+ hashmap_entry_init(&ret->hash, key.hash.hash);
+ ret->orig = xstrdup(orig);
+ ret->orig_len = *len;
+ ret->anon = generate(orig, len);
+ ret->anon_len = *len;
+ hashmap_put(map, ret);
+ }
+
+ *len = ret->anon_len;
+ return ret->anon;
+}
+
+/*
+ * We anonymize each component of a path individually,
+ * so that paths a/b and a/c will share a common root.
+ * The paths are cached via anonymize_mem so that repeated
+ * lookups for "a" will yield the same value.
+ */
+static void anonymize_path(struct strbuf *out, const char *path,
+ struct hashmap *map,
+ void *(*generate)(const void *, size_t *))
+{
+ while (*path) {
+ const char *end_of_component = strchrnul(path, '/');
+ size_t len = end_of_component - path;
+ const char *c = anonymize_mem(map, generate, path, &len);
+ strbuf_add(out, c, len);
+ path = end_of_component;
+ if (*path)
+ strbuf_addch(out, *path++);
+ }
+}
+
/* Since intptr_t is C99, we do not use it here */
static inline uint32_t *mark_to_ptr(uint32_t mark)
{
@@ -113,12 +191,33 @@ static void show_progress(void)
printf("progress %d objects\n", counter);
}
-static void handle_object(const unsigned char *sha1)
+/*
+ * Ideally we would want some transformation of the blob data here
+ * that is unreversible, but would still be the same size and have
+ * the same data relationship to other blobs (so that we get the same
+ * delta and packing behavior as the original). But the first and last
+ * requirements there are probably mutually exclusive, so let's take
+ * the easy way out for now, and just generate arbitrary content.
+ *
+ * There's no need to cache this result with anonymize_mem, since
+ * we already handle blob content caching with marks.
+ */
+static char *anonymize_blob(unsigned long *size)
+{
+ static int counter;
+ struct strbuf out = STRBUF_INIT;
+ strbuf_addf(&out, "anonymous blob %d", counter++);
+ *size = out.len;
+ return strbuf_detach(&out, NULL);
+}
+
+static void export_blob(const unsigned char *sha1)
{
unsigned long size;
enum object_type type;
char *buf;
struct object *object;
+ int eaten;
if (no_data)
return;
@@ -126,16 +225,25 @@ static void handle_object(const unsigned char *sha1)
if (is_null_sha1(sha1))
return;
- object = parse_object(sha1);
- if (!object)
- die ("Could not read blob %s", sha1_to_hex(sha1));
-
- if (object->flags & SHOWN)
+ object = lookup_object(sha1);
+ if (object && object->flags & SHOWN)
return;
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf)
- die ("Could not read blob %s", sha1_to_hex(sha1));
+ if (anonymize) {
+ buf = anonymize_blob(&size);
+ object = (struct object *)lookup_blob(sha1);
+ eaten = 0;
+ } else {
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ die ("Could not read blob %s", sha1_to_hex(sha1));
+ if (check_sha1_signature(sha1, buf, size, typename(type)) < 0)
+ die("sha1 mismatch in blob %s", sha1_to_hex(sha1));
+ object = parse_object_buffer(sha1, type, size, buf, &eaten);
+ }
+
+ if (!object)
+ die("Could not read blob %s", sha1_to_hex(sha1));
mark_next_object(object);
@@ -147,7 +255,8 @@ static void handle_object(const unsigned char *sha1)
show_progress();
object->flags |= SHOWN;
- free(buf);
+ if (!eaten)
+ free(buf);
}
static int depth_first(const void *a_, const void *b_)
@@ -180,15 +289,54 @@ static int depth_first(const void *a_, const void *b_)
return (a->status == 'R') - (b->status == 'R');
}
-static void print_path(const char *path)
+static void print_path_1(const char *path)
{
int need_quote = quote_c_style(path, NULL, NULL, 0);
if (need_quote)
quote_c_style(path, NULL, stdout, 0);
+ else if (strchr(path, ' '))
+ printf("\"%s\"", path);
else
printf("%s", path);
}
+static void *anonymize_path_component(const void *path, size_t *len)
+{
+ static int counter;
+ struct strbuf out = STRBUF_INIT;
+ strbuf_addf(&out, "path%d", counter++);
+ return strbuf_detach(&out, len);
+}
+
+static void print_path(const char *path)
+{
+ if (!anonymize)
+ print_path_1(path);
+ else {
+ static struct hashmap paths;
+ static struct strbuf anon = STRBUF_INIT;
+
+ anonymize_path(&anon, path, &paths, anonymize_path_component);
+ print_path_1(anon.buf);
+ strbuf_reset(&anon);
+ }
+}
+
+static void *generate_fake_sha1(const void *old, size_t *len)
+{
+ static uint32_t counter = 1; /* avoid null sha1 */
+ unsigned char *out = xcalloc(20, 1);
+ put_be32(out + 16, counter++);
+ return out;
+}
+
+static const unsigned char *anonymize_sha1(const unsigned char *sha1)
+{
+ static struct hashmap sha1s;
+ size_t len = 20;
+ return anonymize_mem(&sha1s, generate_fake_sha1, sha1, &len);
+}
+
static void show_filemodify(struct diff_queue_struct *q,
struct diff_options *options, void *data)
{
@@ -233,7 +381,9 @@ static void show_filemodify(struct diff_queue_struct *q,
*/
if (no_data || S_ISGITLINK(spec->mode))
printf("M %06o %s ", spec->mode,
- sha1_to_hex(spec->sha1));
+ sha1_to_hex(anonymize ?
+ anonymize_sha1(spec->sha1) :
+ spec->sha1));
else {
struct object *object = lookup_object(spec->sha1);
printf("M %06o :%d ", spec->mode,
@@ -267,19 +417,130 @@ static const char *find_encoding(const char *begin, const char *end)
return bol;
}
+static void *anonymize_ref_component(const void *old, size_t *len)
+{
+ static int counter;
+ struct strbuf out = STRBUF_INIT;
+ strbuf_addf(&out, "ref%d", counter++);
+ return strbuf_detach(&out, len);
+}
+
+static const char *anonymize_refname(const char *refname)
+{
+ /*
+ * If any of these prefixes is found, we will leave it intact
+ * so that tags remain tags and so forth.
+ */
+ static const char *prefixes[] = {
+ "refs/heads/",
+ "refs/tags/",
+ "refs/remotes/",
+ "refs/"
+ };
+ static struct hashmap refs;
+ static struct strbuf anon = STRBUF_INIT;
+ int i;
+
+ /*
+ * We also leave "master" as a special case, since it does not reveal
+ * anything interesting.
+ */
+ if (!strcmp(refname, "refs/heads/master"))
+ return refname;
+
+ strbuf_reset(&anon);
+ for (i = 0; i < ARRAY_SIZE(prefixes); i++) {
+ if (skip_prefix(refname, prefixes[i], &refname)) {
+ strbuf_addstr(&anon, prefixes[i]);
+ break;
+ }
+ }
+
+ anonymize_path(&anon, refname, &refs, anonymize_ref_component);
+ return anon.buf;
+}
+
+/*
+ * We do not even bother to cache commit messages, as they are unlikely
+ * to be repeated verbatim, and it is not that interesting when they are.
+ */
+static char *anonymize_commit_message(const char *old)
+{
+ static int counter;
+ return xstrfmt("subject %d\n\nbody\n", counter++);
+}
+
+static struct hashmap idents;
+static void *anonymize_ident(const void *old, size_t *len)
+{
+ static int counter;
+ struct strbuf out = STRBUF_INIT;
+ strbuf_addf(&out, "User %d <user%d@example.com>", counter, counter);
+ counter++;
+ return strbuf_detach(&out, len);
+}
+
+/*
+ * Our strategy here is to anonymize the names and email addresses,
+ * but keep timestamps intact, as they influence things like traversal
+ * order (and by themselves should not be too revealing).
+ */
+static void anonymize_ident_line(const char **beg, const char **end)
+{
+ static struct strbuf buffers[] = { STRBUF_INIT, STRBUF_INIT };
+ static unsigned which_buffer;
+
+ struct strbuf *out;
+ struct ident_split split;
+ const char *end_of_header;
+
+ out = &buffers[which_buffer++];
+ which_buffer %= ARRAY_SIZE(buffers);
+ strbuf_reset(out);
+
+ /* skip "committer", "author", "tagger", etc */
+ end_of_header = strchr(*beg, ' ');
+ if (!end_of_header)
+ die("BUG: malformed line fed to anonymize_ident_line: %.*s",
+ (int)(*end - *beg), *beg);
+ end_of_header++;
+ strbuf_add(out, *beg, end_of_header - *beg);
+
+ if (!split_ident_line(&split, end_of_header, *end - end_of_header) &&
+ split.date_begin) {
+ const char *ident;
+ size_t len;
+
+ len = split.mail_end - split.name_begin;
+ ident = anonymize_mem(&idents, anonymize_ident,
+ split.name_begin, &len);
+ strbuf_add(out, ident, len);
+ strbuf_addch(out, ' ');
+ strbuf_add(out, split.date_begin, split.tz_end - split.date_begin);
+ } else {
+ strbuf_addstr(out, "Malformed Ident <malformed@example.com> 0 -0000");
+ }
+
+ *beg = out->buf;
+ *end = out->buf + out->len;
+}
+
static void handle_commit(struct commit *commit, struct rev_info *rev)
{
int saved_output_format = rev->diffopt.output_format;
+ const char *commit_buffer;
const char *author, *author_end, *committer, *committer_end;
const char *encoding, *message;
char *reencoded = NULL;
struct commit_list *p;
+ const char *refname;
int i;
rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
- parse_commit(commit);
- author = strstr(commit->buffer, "\nauthor ");
+ parse_commit_or_die(commit);
+ commit_buffer = get_commit_buffer(commit, NULL);
+ author = strstr(commit_buffer, "\nauthor ");
if (!author)
die ("Could not find author in commit %s",
sha1_to_hex(commit->object.sha1));
@@ -299,7 +560,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);
}
@@ -310,15 +571,24 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
/* Export the referenced blobs, and remember the marks. */
for (i = 0; i < diff_queued_diff.nr; i++)
if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
- handle_object(diff_queued_diff.queue[i]->two->sha1);
+ export_blob(diff_queued_diff.queue[i]->two->sha1);
+
+ refname = commit->util;
+ if (anonymize) {
+ refname = anonymize_refname(refname);
+ anonymize_ident_line(&committer, &committer_end);
+ anonymize_ident_line(&author, &author_end);
+ }
mark_next_object(&commit->object);
- if (!is_encoding_utf8(encoding))
+ if (anonymize)
+ reencoded = anonymize_commit_message(message);
+ else if (!is_encoding_utf8(encoding))
reencoded = reencode_string(message, "UTF-8", encoding);
if (!commit->parents)
- printf("reset %s\n", (const char*)commit->util);
+ printf("reset %s\n", refname);
printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s",
- (const char *)commit->util, last_idnum,
+ refname, last_idnum,
(int)(author_end - author), author,
(int)(committer_end - committer), committer,
(unsigned)(reencoded
@@ -326,6 +596,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
? strlen(message) : 0),
reencoded ? reencoded : message ? message : "");
free(reencoded);
+ unuse_commit_buffer(commit, commit_buffer);
for (i = 0, p = commit->parents; p; p = p->next) {
int mark = get_object_mark(&p->item->object);
@@ -348,6 +619,14 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
show_progress();
}
+static void *anonymize_tag(const void *old, size_t *len)
+{
+ static int counter;
+ struct strbuf out = STRBUF_INIT;
+ strbuf_addf(&out, "tag message %d", counter++);
+ return strbuf_detach(&out, len);
+}
+
static void handle_tail(struct object_array *commits, struct rev_info *revs)
{
struct commit *commit;
@@ -371,7 +650,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.
*/
@@ -404,6 +683,17 @@ static void handle_tag(const char *name, struct tag *tag)
} else {
tagger++;
tagger_end = strchrnul(tagger, '\n');
+ if (anonymize)
+ anonymize_ident_line(&tagger, &tagger_end);
+ }
+
+ if (anonymize) {
+ name = anonymize_refname(name);
+ if (message) {
+ static struct hashmap tags;
+ message = anonymize_mem(&tags, anonymize_tag,
+ message, &message_size);
+ }
}
/* handle signed tags */
@@ -414,7 +704,7 @@ static void handle_tag(const char *name, struct tag *tag)
switch(signed_tag_mode) {
case ABORT:
die ("Encountered signed tag %s; use "
- "--signed-tag=<mode> to handle it.",
+ "--signed-tags=<mode> to handle it.",
sha1_to_hex(tag->object.sha1));
case WARN:
warning ("Exporting signed tag %s",
@@ -422,6 +712,10 @@ static void handle_tag(const char *name, struct tag *tag)
/* fallthru */
case VERBATIM:
break;
+ case WARN_STRIP:
+ warning ("Stripping signature from tag %s",
+ sha1_to_hex(tag->object.sha1));
+ /* fallthru */
case STRIP:
message_size = signature + 1 - message;
break;
@@ -463,7 +757,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,
@@ -472,76 +766,101 @@ 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 object_array *pending,
- 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 < pending->nr; i++) {
- struct object_array_entry *e = pending->objects + i;
+ for (i = 0; i < info->nr; i++) {
+ struct rev_cmdline_entry *e = info->rev + i;
unsigned char sha1[20];
- struct commit *commit = commit;
+ struct commit *commit;
char *full_name;
- if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
+ if (e->flags & UNINTERESTING)
continue;
- switch (e->item->type) {
- case OBJ_COMMIT:
- commit = (struct commit *)e->item;
- break;
- case OBJ_TAG:
- tag = (struct tag *)e->item;
+ if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
+ continue;
- /* 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:
- handle_object(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;
+ if (refspecs) {
+ char *private;
+ private = apply_refspecs(refspecs, refspecs_nr, full_name);
+ if (private) {
+ free(full_name);
+ full_name = private;
}
- 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;
}
- if (commit->util)
- /* more than one name for the same object */
- string_list_append(extra_refs, full_name)->util = commit;
- else
+
+ 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;
+ 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);
break;
case OBJ_COMMIT:
+ if (anonymize)
+ name = anonymize_refname(name);
/* create refs pointing to already seen commits */
commit = (struct commit *)object;
printf("reset %s\nfrom :%d\n\n", name,
@@ -594,6 +913,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)
@@ -602,54 +923,80 @@ 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);
- object = parse_object(sha1);
- if (!object)
- die ("Could not read blob %s", sha1_to_hex(sha1));
+ if (last_idnum < mark)
+ last_idnum = mark;
+
+ 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);
+ error("Object %s already has a mark", sha1_to_hex(sha1));
mark_object(object, mark);
- if (last_idnum < mark)
- last_idnum = mark;
object->flags |= SHOWN;
}
fclose(f);
}
+static void handle_deletes(void)
+{
+ int i;
+ for (i = 0; i < refspecs_nr; i++) {
+ struct refspec *refspec = &refspecs[i];
+ if (*refspec->src)
+ continue;
+
+ printf("reset %s\nfrom %s\n\n",
+ refspec->dst, sha1_to_hex(null_sha1));
+ }
+}
+
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;
+ struct string_list refspecs_list = STRING_LIST_INIT_NODUP;
struct option options[] = {
OPT_INTEGER(0, "progress", &progress,
- "show progress after <n> objects"),
- OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode",
- "select handling of signed tags",
+ N_("show progress after <n> objects")),
+ OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, N_("mode"),
+ N_("select handling of signed tags"),
parse_opt_signed_tag_mode),
- OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode",
- "select handling of tags that tag filtered objects",
+ OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, N_("mode"),
+ N_("select handling of tags that tag filtered objects"),
parse_opt_tag_of_filtered_mode),
- OPT_STRING(0, "export-marks", &export_filename, "file",
- "Dump marks to this file"),
- OPT_STRING(0, "import-marks", &import_filename, "file",
- "Import marks from this file"),
- OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
- "Fake a tagger when tags lack one"),
- OPT_BOOLEAN(0, "full-tree", &full_tree,
- "Output full tree for each commit"),
- OPT_BOOLEAN(0, "use-done-feature", &use_done_feature,
- "Use the done feature to terminate the stream"),
- { OPTION_NEGBIT, 0, "data", &no_data, NULL,
- "Skip output of blob data",
- PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
+ OPT_STRING(0, "export-marks", &export_filename, N_("file"),
+ N_("Dump marks to this file")),
+ OPT_STRING(0, "import-marks", &import_filename, N_("file"),
+ N_("Import marks from this file")),
+ OPT_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_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"),
+ N_("Apply refspec to exported refs")),
+ OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")),
OPT_END()
};
@@ -663,21 +1010,38 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
revs.topo_order = 1;
revs.show_source = 1;
revs.rewrite_parents = 1;
+ argc = parse_options(argc, argv, prefix, options, fast_export_usage,
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN);
argc = setup_revisions(argc, argv, &revs, NULL);
- argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0);
if (argc > 1)
usage_with_options (fast_export_usage, options);
+ if (refspecs_list.nr) {
+ const char **refspecs_str;
+ int i;
+
+ refspecs_str = xmalloc(sizeof(*refspecs_str) * refspecs_list.nr);
+ for (i = 0; i < refspecs_list.nr; i++)
+ refspecs_str[i] = refspecs_list.items[i].string;
+
+ refspecs_nr = refspecs_list.nr;
+ refspecs = parse_fetch_refspec(refspecs_nr, refspecs_str);
+
+ string_list_clear(&refspecs_list, 1);
+ free(refspecs_str);
+ }
+
if (use_done_feature)
printf("feature done\n");
if (import_filename)
import_marks(import_filename);
+ lastimportid = last_idnum;
if (import_filename && revs.prune_data.nr)
full_tree = 1;
- get_tags_and_duplicates(&revs.pending, &extra_refs);
+ get_tags_and_duplicates(&revs.cmdline);
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
@@ -693,13 +1057,16 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
}
}
- handle_tags_and_duplicates(&extra_refs);
+ handle_tags_and_duplicates();
+ handle_deletes();
- if (export_filename)
+ if (export_filename && lastimportid != last_idnum)
export_marks(export_filename);
if (use_done_feature)
printf("done\n");
+ free_refspec(refspecs_nr, refspecs);
+
return 0;
}
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index 412bd32..1262b40 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -1,989 +1,213 @@
#include "builtin.h"
-#include "refs.h"
#include "pkt-line.h"
-#include "commit.h"
-#include "tag.h"
-#include "exec_cmd.h"
-#include "pack.h"
-#include "sideband.h"
#include "fetch-pack.h"
#include "remote.h"
-#include "run-command.h"
-#include "transport.h"
-
-static int transfer_unpack_limit = -1;
-static int fetch_unpack_limit = -1;
-static int unpack_limit = 100;
-static int prefer_ofs_delta = 1;
-static int no_done = 0;
-static struct fetch_pack_args args = {
- /* .uploadpack = */ "git-upload-pack",
-};
+#include "connect.h"
+#include "sha1-array.h"
static const char fetch_pack_usage[] =
-"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
-
-#define COMPLETE (1U << 0)
-#define COMMON (1U << 1)
-#define COMMON_REF (1U << 2)
-#define SEEN (1U << 3)
-#define POPPED (1U << 4)
-
-static int marked;
-
-/*
- * After sending this many "have"s if we do not get any new ACK , we
- * give up traversing our history.
- */
-#define MAX_IN_VAIN 256
-
-static struct commit_list *rev_list;
-static int non_common_revs, multi_ack, use_sideband;
+"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
+"[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
+"[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]";
-static void rev_list_push(struct commit *commit, int mark)
+static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc,
+ const char *name, int namelen)
{
- if (!(commit->object.flags & mark)) {
- commit->object.flags |= mark;
-
- if (!(commit->object.parsed))
- if (parse_commit(commit))
- return;
-
- commit_list_insert_by_date(commit, &rev_list);
+ struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1);
+ unsigned char sha1[20];
- if (!(commit->object.flags & COMMON))
- non_common_revs++;
+ if (namelen > 41 && name[40] == ' ' && !get_sha1_hex(name, sha1)) {
+ hashcpy(ref->old_sha1, sha1);
+ name += 41;
+ namelen -= 41;
}
-}
-static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct object *o = deref_tag(parse_object(sha1), path, 0);
-
- if (o && o->type == OBJ_COMMIT)
- rev_list_push((struct commit *)o, SEEN);
-
- return 0;
+ memcpy(ref->name, name, namelen);
+ ref->name[namelen] = '\0';
+ (*nr)++;
+ ALLOC_GROW(*sought, *nr, *alloc);
+ (*sought)[*nr - 1] = ref;
}
-static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,
+ const char *string)
{
- struct object *o = deref_tag(parse_object(sha1), path, 0);
-
- if (o && o->type == OBJ_COMMIT)
- clear_commit_marks((struct commit *)o,
- COMMON | COMMON_REF | SEEN | POPPED);
- return 0;
+ add_sought_entry_mem(sought, nr, alloc, string, strlen(string));
}
-/*
- This function marks a rev and its ancestors as common.
- In some cases, it is desirable to mark only the ancestors (for example
- when only the server does not yet know that they are common).
-*/
-
-static void mark_common(struct commit *commit,
- int ancestors_only, int dont_parse)
+int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
{
- if (commit != NULL && !(commit->object.flags & COMMON)) {
- struct object *o = (struct object *)commit;
+ int i, ret;
+ struct ref *ref = NULL;
+ const char *dest = NULL;
+ struct ref **sought = NULL;
+ int nr_sought = 0, alloc_sought = 0;
+ int fd[2];
+ char *pack_lockfile = NULL;
+ char **pack_lockfile_ptr = NULL;
+ struct child_process *conn;
+ struct fetch_pack_args args;
+ struct sha1_array shallow = SHA1_ARRAY_INIT;
- if (!ancestors_only)
- o->flags |= COMMON;
+ packet_trace_identity("fetch-pack");
- if (!(o->flags & SEEN))
- rev_list_push(commit, SEEN);
- else {
- struct commit_list *parents;
+ memset(&args, 0, sizeof(args));
+ args.uploadpack = "git-upload-pack";
- if (!ancestors_only && !(o->flags & POPPED))
- non_common_revs--;
- if (!o->parsed && !dont_parse)
- if (parse_commit(commit))
- return;
+ for (i = 1; i < argc && *argv[i] == '-'; i++) {
+ const char *arg = argv[i];
- for (parents = commit->parents;
- parents;
- parents = parents->next)
- mark_common(parents->item, 0, dont_parse);
+ if (starts_with(arg, "--upload-pack=")) {
+ args.uploadpack = arg + 14;
+ continue;
}
- }
-}
-
-/*
- Get the next rev to send, ignoring the common.
-*/
-
-static const unsigned char *get_rev(void)
-{
- struct commit *commit = NULL;
-
- while (commit == NULL) {
- unsigned int mark;
- struct commit_list *parents;
-
- if (rev_list == NULL || non_common_revs == 0)
- return NULL;
-
- commit = rev_list->item;
- if (!commit->object.parsed)
- parse_commit(commit);
- parents = commit->parents;
-
- commit->object.flags |= POPPED;
- if (!(commit->object.flags & COMMON))
- non_common_revs--;
-
- if (commit->object.flags & COMMON) {
- /* do not send "have", and ignore ancestors */
- commit = NULL;
- mark = COMMON | SEEN;
- } else if (commit->object.flags & COMMON_REF)
- /* send "have", and ignore ancestors */
- mark = COMMON | SEEN;
- else
- /* send "have", also for its ancestors */
- mark = SEEN;
-
- while (parents) {
- if (!(parents->item->object.flags & SEEN))
- rev_list_push(parents->item, mark);
- if (mark & COMMON)
- mark_common(parents->item, 1, 0);
- parents = parents->next;
+ if (starts_with(arg, "--exec=")) {
+ args.uploadpack = arg + 7;
+ continue;
}
-
- rev_list = rev_list->next;
- }
-
- return commit->object.sha1;
-}
-
-enum ack_type {
- NAK = 0,
- ACK,
- ACK_continue,
- ACK_common,
- ACK_ready
-};
-
-static void consume_shallow_list(int fd)
-{
- if (args.stateless_rpc && args.depth > 0) {
- /* If we sent a depth we will get back "duplicate"
- * shallow and unshallow commands every time there
- * is a block of have lines exchanged.
- */
- char line[1000];
- while (packet_read_line(fd, line, sizeof(line))) {
- if (!prefixcmp(line, "shallow "))
- continue;
- if (!prefixcmp(line, "unshallow "))
- continue;
- die("git fetch-pack: expected shallow list");
+ if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
+ args.quiet = 1;
+ continue;
}
- }
-}
-
-struct write_shallow_data {
- struct strbuf *out;
- int use_pack_protocol;
- int count;
-};
-
-static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
-{
- struct write_shallow_data *data = cb_data;
- const char *hex = sha1_to_hex(graft->sha1);
- data->count++;
- if (data->use_pack_protocol)
- packet_buf_write(data->out, "shallow %s", hex);
- else {
- strbuf_addstr(data->out, hex);
- strbuf_addch(data->out, '\n');
- }
- return 0;
-}
-
-static int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
-{
- struct write_shallow_data data;
- data.out = out;
- data.use_pack_protocol = use_pack_protocol;
- data.count = 0;
- for_each_commit_graft(write_one_shallow, &data);
- return data.count;
-}
-
-static enum ack_type get_ack(int fd, unsigned char *result_sha1)
-{
- static char line[1000];
- int len = packet_read_line(fd, line, sizeof(line));
-
- if (!len)
- die("git fetch-pack: expected ACK/NAK, got EOF");
- if (line[len-1] == '\n')
- line[--len] = 0;
- if (!strcmp(line, "NAK"))
- return NAK;
- if (!prefixcmp(line, "ACK ")) {
- if (!get_sha1_hex(line+4, result_sha1)) {
- if (strstr(line+45, "continue"))
- return ACK_continue;
- if (strstr(line+45, "common"))
- return ACK_common;
- if (strstr(line+45, "ready"))
- return ACK_ready;
- return ACK;
+ if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
+ args.lock_pack = args.keep_pack;
+ args.keep_pack = 1;
+ continue;
}
- }
- die("git fetch_pack: expected ACK/NAK, got '%s'", line);
-}
-
-static void send_request(int fd, struct strbuf *buf)
-{
- if (args.stateless_rpc) {
- send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
- packet_flush(fd);
- } else
- safe_write(fd, buf->buf, buf->len);
-}
-
-static void insert_one_alternate_ref(const struct ref *ref, void *unused)
-{
- rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL);
-}
-
-static void insert_alternate_refs(void)
-{
- for_each_alternate_ref(insert_one_alternate_ref, NULL);
-}
-
-#define INITIAL_FLUSH 16
-#define PIPESAFE_FLUSH 32
-#define LARGE_FLUSH 1024
-
-static int next_flush(int count)
-{
- int flush_limit = args.stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH;
-
- if (count < flush_limit)
- count <<= 1;
- else
- count += flush_limit;
- return count;
-}
-
-static int find_common(int fd[2], unsigned char *result_sha1,
- struct ref *refs)
-{
- int fetching;
- int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval;
- const unsigned char *sha1;
- unsigned in_vain = 0;
- int got_continue = 0;
- int got_ready = 0;
- struct strbuf req_buf = STRBUF_INIT;
- size_t state_len = 0;
-
- if (args.stateless_rpc && multi_ack == 1)
- die("--stateless-rpc requires multi_ack_detailed");
- if (marked)
- for_each_ref(clear_marks, NULL);
- marked = 1;
-
- for_each_ref(rev_list_insert_ref, NULL);
- insert_alternate_refs();
-
- fetching = 0;
- for ( ; refs ; refs = refs->next) {
- unsigned char *remote = refs->old_sha1;
- const char *remote_hex;
- struct object *o;
-
- /*
- * If that object is complete (i.e. it is an ancestor of a
- * local ref), we tell them we have it but do not have to
- * tell them about its ancestors, which they already know
- * about.
- *
- * We use lookup_object here because we are only
- * interested in the case we *know* the object is
- * reachable and we have already scanned it.
- */
- if (((o = lookup_object(remote)) != NULL) &&
- (o->flags & COMPLETE)) {
+ if (!strcmp("--thin", arg)) {
+ args.use_thin_pack = 1;
continue;
}
-
- remote_hex = sha1_to_hex(remote);
- if (!fetching) {
- struct strbuf c = STRBUF_INIT;
- if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed");
- if (multi_ack == 1) strbuf_addstr(&c, " multi_ack");
- if (no_done) strbuf_addstr(&c, " no-done");
- if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k");
- if (use_sideband == 1) strbuf_addstr(&c, " side-band");
- if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack");
- if (args.no_progress) strbuf_addstr(&c, " no-progress");
- if (args.include_tag) strbuf_addstr(&c, " include-tag");
- if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta");
- packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
- strbuf_release(&c);
- } else
- packet_buf_write(&req_buf, "want %s\n", remote_hex);
- fetching++;
- }
-
- if (!fetching) {
- strbuf_release(&req_buf);
- packet_flush(fd[1]);
- return 1;
- }
-
- if (is_repository_shallow())
- write_shallow_commits(&req_buf, 1);
- if (args.depth > 0)
- packet_buf_write(&req_buf, "deepen %d", args.depth);
- packet_buf_flush(&req_buf);
- state_len = req_buf.len;
-
- if (args.depth > 0) {
- char line[1024];
- unsigned char sha1[20];
-
- send_request(fd[1], &req_buf);
- while (packet_read_line(fd[0], line, sizeof(line))) {
- if (!prefixcmp(line, "shallow ")) {
- if (get_sha1_hex(line + 8, sha1))
- die("invalid shallow line: %s", line);
- register_shallow(sha1);
- continue;
- }
- if (!prefixcmp(line, "unshallow ")) {
- if (get_sha1_hex(line + 10, sha1))
- die("invalid unshallow line: %s", line);
- if (!lookup_object(sha1))
- die("object not found: %s", line);
- /* make sure that it is parsed as shallow */
- if (!parse_object(sha1))
- die("error in object: %s", line);
- if (unregister_shallow(sha1))
- die("no shallow found: %s", line);
- continue;
- }
- die("expected shallow/unshallow, got %s", line);
+ if (!strcmp("--include-tag", arg)) {
+ args.include_tag = 1;
+ continue;
}
- } else if (!args.stateless_rpc)
- send_request(fd[1], &req_buf);
-
- if (!args.stateless_rpc) {
- /* If we aren't using the stateless-rpc interface
- * we don't need to retain the headers.
- */
- strbuf_setlen(&req_buf, 0);
- state_len = 0;
- }
-
- flushes = 0;
- retval = -1;
- while ((sha1 = get_rev())) {
- packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1));
- if (args.verbose)
- fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
- in_vain++;
- if (flush_at <= ++count) {
- int ack;
-
- packet_buf_flush(&req_buf);
- send_request(fd[1], &req_buf);
- strbuf_setlen(&req_buf, state_len);
- flushes++;
- flush_at = next_flush(count);
-
- /*
- * We keep one window "ahead" of the other side, and
- * will wait for an ACK only on the next one
- */
- if (!args.stateless_rpc && count == INITIAL_FLUSH)
- continue;
-
- consume_shallow_list(fd[0]);
- do {
- ack = get_ack(fd[0], result_sha1);
- if (args.verbose && ack)
- fprintf(stderr, "got ack %d %s\n", ack,
- sha1_to_hex(result_sha1));
- switch (ack) {
- case ACK:
- flushes = 0;
- multi_ack = 0;
- retval = 0;
- goto done;
- case ACK_common:
- case ACK_ready:
- case ACK_continue: {
- struct commit *commit =
- lookup_commit(result_sha1);
- if (!commit)
- die("invalid commit %s", sha1_to_hex(result_sha1));
- if (args.stateless_rpc
- && ack == ACK_common
- && !(commit->object.flags & COMMON)) {
- /* We need to replay the have for this object
- * on the next RPC request so the peer knows
- * it is in common with us.
- */
- const char *hex = sha1_to_hex(result_sha1);
- packet_buf_write(&req_buf, "have %s\n", hex);
- state_len = req_buf.len;
- }
- mark_common(commit, 0, 1);
- retval = 0;
- in_vain = 0;
- got_continue = 1;
- if (ack == ACK_ready) {
- rev_list = NULL;
- got_ready = 1;
- }
- break;
- }
- }
- } while (ack);
- flushes--;
- if (got_continue && MAX_IN_VAIN < in_vain) {
- if (args.verbose)
- fprintf(stderr, "giving up\n");
- break; /* give up */
- }
+ if (!strcmp("--all", arg)) {
+ args.fetch_all = 1;
+ continue;
}
- }
-done:
- if (!got_ready || !no_done) {
- packet_buf_write(&req_buf, "done\n");
- send_request(fd[1], &req_buf);
- }
- if (args.verbose)
- fprintf(stderr, "done\n");
- if (retval != 0) {
- multi_ack = 0;
- flushes++;
- }
- strbuf_release(&req_buf);
-
- consume_shallow_list(fd[0]);
- while (flushes || multi_ack) {
- int ack = get_ack(fd[0], result_sha1);
- if (ack) {
- if (args.verbose)
- fprintf(stderr, "got ack (%d) %s\n", ack,
- sha1_to_hex(result_sha1));
- if (ack == ACK)
- return 0;
- multi_ack = 1;
+ if (!strcmp("--stdin", arg)) {
+ args.stdin_refs = 1;
continue;
}
- flushes--;
- }
- /* it is no error to fetch into a completely empty repo */
- return count ? retval : 0;
-}
-
-static struct commit_list *complete;
-
-static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct object *o = parse_object(sha1);
-
- while (o && o->type == OBJ_TAG) {
- struct tag *t = (struct tag *) o;
- if (!t->tagged)
- break; /* broken repository */
- o->flags |= COMPLETE;
- o = parse_object(t->tagged->sha1);
- }
- if (o && o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
- if (!(commit->object.flags & COMPLETE)) {
- commit->object.flags |= COMPLETE;
- commit_list_insert_by_date(commit, &complete);
+ if (!strcmp("--diag-url", arg)) {
+ args.diag_url = 1;
+ continue;
}
- }
- return 0;
-}
-
-static void mark_recent_complete_commits(unsigned long cutoff)
-{
- while (complete && cutoff <= complete->item->date) {
- if (args.verbose)
- fprintf(stderr, "Marking %s as complete\n",
- sha1_to_hex(complete->item->object.sha1));
- pop_most_recent_commit(&complete, COMPLETE);
- }
-}
-
-static void filter_refs(struct ref **refs, int nr_match, char **match)
-{
- struct ref **return_refs;
- struct ref *newlist = NULL;
- struct ref **newtail = &newlist;
- struct ref *ref, *next;
- struct ref *fastarray[32];
-
- if (nr_match && !args.fetch_all) {
- if (ARRAY_SIZE(fastarray) < nr_match)
- return_refs = xcalloc(nr_match, sizeof(struct ref *));
- else {
- return_refs = fastarray;
- memset(return_refs, 0, sizeof(struct ref *) * nr_match);
+ if (!strcmp("-v", arg)) {
+ args.verbose = 1;
+ continue;
}
- }
- else
- return_refs = NULL;
-
- for (ref = *refs; ref; ref = next) {
- next = ref->next;
- if (!memcmp(ref->name, "refs/", 5) &&
- check_ref_format(ref->name + 5))
- ; /* trash */
- else if (args.fetch_all &&
- (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
- *newtail = ref;
- ref->next = NULL;
- newtail = &ref->next;
+ if (starts_with(arg, "--depth=")) {
+ args.depth = strtol(arg + 8, NULL, 0);
continue;
}
- else {
- int order = path_match(ref->name, nr_match, match);
- if (order) {
- return_refs[order-1] = ref;
- continue; /* we will link it later */
- }
+ if (!strcmp("--no-progress", arg)) {
+ args.no_progress = 1;
+ continue;
}
- free(ref);
- }
-
- if (!args.fetch_all) {
- int i;
- for (i = 0; i < nr_match; i++) {
- ref = return_refs[i];
- if (ref) {
- *newtail = ref;
- ref->next = NULL;
- newtail = &ref->next;
- }
+ if (!strcmp("--stateless-rpc", arg)) {
+ args.stateless_rpc = 1;
+ continue;
}
- if (return_refs != fastarray)
- free(return_refs);
- }
- *refs = newlist;
-}
-
-static int everything_local(struct ref **refs, int nr_match, char **match)
-{
- struct ref *ref;
- int retval;
- unsigned long cutoff = 0;
-
- save_commit_buffer = 0;
-
- for (ref = *refs; ref; ref = ref->next) {
- struct object *o;
-
- o = parse_object(ref->old_sha1);
- if (!o)
+ if (!strcmp("--lock-pack", arg)) {
+ args.lock_pack = 1;
+ pack_lockfile_ptr = &pack_lockfile;
continue;
-
- /* We already have it -- which may mean that we were
- * in sync with the other side at some time after
- * that (it is OK if we guess wrong here).
- */
- if (o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
- if (!cutoff || cutoff < commit->date)
- cutoff = commit->date;
}
- }
-
- if (!args.depth) {
- for_each_ref(mark_complete, NULL);
- if (cutoff)
- mark_recent_complete_commits(cutoff);
- }
-
- /*
- * Mark all complete remote refs as common refs.
- * Don't mark them common yet; the server has to be told so first.
- */
- for (ref = *refs; ref; ref = ref->next) {
- struct object *o = deref_tag(lookup_object(ref->old_sha1),
- NULL, 0);
-
- if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE))
+ if (!strcmp("--check-self-contained-and-connected", arg)) {
+ args.check_self_contained_and_connected = 1;
continue;
-
- if (!(o->flags & SEEN)) {
- rev_list_push((struct commit *)o, COMMON_REF | SEEN);
-
- mark_common((struct commit *)o, 1, 1);
}
- }
-
- filter_refs(refs, nr_match, match);
-
- for (retval = 1, ref = *refs; ref ; ref = ref->next) {
- const unsigned char *remote = ref->old_sha1;
- unsigned char local[20];
- struct object *o;
-
- o = lookup_object(remote);
- if (!o || !(o->flags & COMPLETE)) {
- retval = 0;
- if (!args.verbose)
- continue;
- fprintf(stderr,
- "want %s (%s)\n", sha1_to_hex(remote),
- ref->name);
+ if (!strcmp("--cloning", arg)) {
+ args.cloning = 1;
continue;
}
-
- hashcpy(ref->new_sha1, local);
- if (!args.verbose)
+ if (!strcmp("--update-shallow", arg)) {
+ args.update_shallow = 1;
continue;
- fprintf(stderr,
- "already have %s (%s)\n", sha1_to_hex(remote),
- ref->name);
- }
- return retval;
-}
-
-static int sideband_demux(int in, int out, void *data)
-{
- int *xd = data;
-
- int ret = recv_sideband("fetch-pack", xd[0], out);
- close(out);
- return ret;
-}
-
-static int get_pack(int xd[2], char **pack_lockfile)
-{
- struct async demux;
- const char *argv[20];
- char keep_arg[256];
- char hdr_arg[256];
- const char **av;
- int do_keep = args.keep_pack;
- struct child_process cmd;
-
- memset(&demux, 0, sizeof(demux));
- if (use_sideband) {
- /* xd[] is talking with upload-pack; subprocess reads from
- * xd[0], spits out band#2 to stderr, and feeds us band#1
- * through demux->out.
- */
- demux.proc = sideband_demux;
- demux.data = xd;
- demux.out = -1;
- if (start_async(&demux))
- die("fetch-pack: unable to fork off sideband"
- " demultiplexer");
- }
- else
- demux.out = xd[0];
-
- memset(&cmd, 0, sizeof(cmd));
- cmd.argv = argv;
- av = argv;
- *hdr_arg = 0;
- if (!args.keep_pack && unpack_limit) {
- struct pack_header header;
-
- if (read_pack_header(demux.out, &header))
- die("protocol error: bad pack header");
- snprintf(hdr_arg, sizeof(hdr_arg),
- "--pack_header=%"PRIu32",%"PRIu32,
- ntohl(header.hdr_version), ntohl(header.hdr_entries));
- if (ntohl(header.hdr_entries) < unpack_limit)
- do_keep = 0;
- else
- do_keep = 1;
- }
-
- if (do_keep) {
- if (pack_lockfile)
- cmd.out = -1;
- *av++ = "index-pack";
- *av++ = "--stdin";
- if (!args.quiet && !args.no_progress)
- *av++ = "-v";
- if (args.use_thin_pack)
- *av++ = "--fix-thin";
- if (args.lock_pack || unpack_limit) {
- int s = sprintf(keep_arg,
- "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
- if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
- strcpy(keep_arg + s, "localhost");
- *av++ = keep_arg;
}
+ usage(fetch_pack_usage);
}
- else {
- *av++ = "unpack-objects";
- if (args.quiet)
- *av++ = "-q";
- }
- if (*hdr_arg)
- *av++ = hdr_arg;
- *av++ = NULL;
-
- cmd.in = demux.out;
- cmd.git_cmd = 1;
- if (start_command(&cmd))
- die("fetch-pack: unable to fork off %s", argv[0]);
- if (do_keep && pack_lockfile) {
- *pack_lockfile = index_pack_lockfile(cmd.out);
- close(cmd.out);
- }
-
- if (finish_command(&cmd))
- die("%s failed", argv[0]);
- if (use_sideband && finish_async(&demux))
- die("error in sideband demultiplexer");
- return 0;
-}
-static struct ref *do_fetch_pack(int fd[2],
- const struct ref *orig_ref,
- int nr_match,
- char **match,
- char **pack_lockfile)
-{
- struct ref *ref = copy_ref_list(orig_ref);
- unsigned char sha1[20];
+ if (i < argc)
+ dest = argv[i++];
+ else
+ usage(fetch_pack_usage);
- if (is_repository_shallow() && !server_supports("shallow"))
- die("Server does not support shallow clients");
- if (server_supports("multi_ack_detailed")) {
- if (args.verbose)
- fprintf(stderr, "Server supports multi_ack_detailed\n");
- multi_ack = 2;
- if (server_supports("no-done")) {
- if (args.verbose)
- fprintf(stderr, "Server supports no-done\n");
- if (args.stateless_rpc)
- no_done = 1;
- }
- }
- else if (server_supports("multi_ack")) {
- if (args.verbose)
- fprintf(stderr, "Server supports multi_ack\n");
- multi_ack = 1;
- }
- if (server_supports("side-band-64k")) {
- if (args.verbose)
- fprintf(stderr, "Server supports side-band-64k\n");
- use_sideband = 2;
- }
- else if (server_supports("side-band")) {
- if (args.verbose)
- fprintf(stderr, "Server supports side-band\n");
- use_sideband = 1;
- }
- if (server_supports("ofs-delta")) {
- if (args.verbose)
- fprintf(stderr, "Server supports ofs-delta\n");
- } else
- prefer_ofs_delta = 0;
- if (everything_local(&ref, nr_match, match)) {
- packet_flush(fd[1]);
- goto all_done;
- }
- if (find_common(fd, sha1, ref) < 0)
- if (!args.keep_pack)
- /* When cloning, it is not unusual to have
- * no common commit.
+ /*
+ * Copy refs from cmdline to growable list, then append any
+ * refs from the standard input:
+ */
+ for (; i < argc; i++)
+ add_sought_entry(&sought, &nr_sought, &alloc_sought, argv[i]);
+ if (args.stdin_refs) {
+ if (args.stateless_rpc) {
+ /* in stateless RPC mode we use pkt-line to read
+ * from stdin, until we get a flush packet
*/
- warning("no common commits");
-
- if (args.stateless_rpc)
- packet_flush(fd[1]);
- if (get_pack(fd, pack_lockfile))
- die("git fetch-pack: fetch failed.");
-
- all_done:
- return ref;
-}
-
-static int remove_duplicates(int nr_heads, char **heads)
-{
- int src, dst;
-
- for (src = dst = 0; src < nr_heads; src++) {
- /* If heads[src] is different from any of
- * heads[0..dst], push it in.
- */
- int i;
- for (i = 0; i < dst; i++) {
- if (!strcmp(heads[i], heads[src]))
- break;
- }
- if (i < dst)
- continue;
- if (src != dst)
- heads[dst] = heads[src];
- dst++;
- }
- return dst;
-}
-
-static int fetch_pack_config(const char *var, const char *value, void *cb)
-{
- if (strcmp(var, "fetch.unpacklimit") == 0) {
- fetch_unpack_limit = git_config_int(var, value);
- return 0;
- }
-
- if (strcmp(var, "transfer.unpacklimit") == 0) {
- transfer_unpack_limit = git_config_int(var, value);
- return 0;
- }
-
- if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
- prefer_ofs_delta = git_config_bool(var, value);
- return 0;
- }
-
- return git_default_config(var, value, cb);
-}
-
-static struct lock_file lock;
-
-static void fetch_pack_setup(void)
-{
- static int did_setup;
- if (did_setup)
- return;
- git_config(fetch_pack_config, NULL);
- if (0 <= transfer_unpack_limit)
- unpack_limit = transfer_unpack_limit;
- else if (0 <= fetch_unpack_limit)
- unpack_limit = fetch_unpack_limit;
- did_setup = 1;
-}
-
-int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
-{
- int i, ret, nr_heads;
- struct ref *ref = NULL;
- char *dest = NULL, **heads;
- int fd[2];
- char *pack_lockfile = NULL;
- char **pack_lockfile_ptr = NULL;
- struct child_process *conn;
-
- packet_trace_identity("fetch-pack");
-
- nr_heads = 0;
- heads = NULL;
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
-
- if (*arg == '-') {
- if (!prefixcmp(arg, "--upload-pack=")) {
- args.uploadpack = arg + 14;
- continue;
- }
- if (!prefixcmp(arg, "--exec=")) {
- args.uploadpack = arg + 7;
- continue;
- }
- if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
- args.quiet = 1;
- continue;
- }
- if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
- args.lock_pack = args.keep_pack;
- args.keep_pack = 1;
- continue;
- }
- if (!strcmp("--thin", arg)) {
- args.use_thin_pack = 1;
- continue;
- }
- if (!strcmp("--include-tag", arg)) {
- args.include_tag = 1;
- continue;
- }
- if (!strcmp("--all", arg)) {
- args.fetch_all = 1;
- continue;
- }
- if (!strcmp("-v", arg)) {
- args.verbose = 1;
- continue;
- }
- if (!prefixcmp(arg, "--depth=")) {
- args.depth = strtol(arg + 8, NULL, 0);
- continue;
- }
- if (!strcmp("--no-progress", arg)) {
- args.no_progress = 1;
- continue;
- }
- if (!strcmp("--stateless-rpc", arg)) {
- args.stateless_rpc = 1;
- continue;
- }
- if (!strcmp("--lock-pack", arg)) {
- args.lock_pack = 1;
- pack_lockfile_ptr = &pack_lockfile;
- continue;
+ for (;;) {
+ char *line = packet_read_line(0, NULL);
+ if (!line)
+ break;
+ add_sought_entry(&sought, &nr_sought, &alloc_sought, line);
}
- usage(fetch_pack_usage);
}
- dest = (char *)arg;
- heads = (char **)(argv + i + 1);
- nr_heads = argc - i - 1;
- break;
+ else {
+ /* read from stdin one ref per line, until EOF */
+ struct strbuf line = STRBUF_INIT;
+ while (strbuf_getline(&line, stdin, '\n') != EOF)
+ add_sought_entry(&sought, &nr_sought, &alloc_sought, line.buf);
+ strbuf_release(&line);
+ }
}
- if (!dest)
- usage(fetch_pack_usage);
if (args.stateless_rpc) {
conn = NULL;
fd[0] = 0;
fd[1] = 1;
} else {
- conn = git_connect(fd, (char *)dest, args.uploadpack,
- args.verbose ? CONNECT_VERBOSE : 0);
- }
-
- get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
-
- ref = fetch_pack(&args, fd, conn, ref, dest,
- nr_heads, heads, pack_lockfile_ptr);
+ int flags = args.verbose ? CONNECT_VERBOSE : 0;
+ if (args.diag_url)
+ flags |= CONNECT_DIAG_URL;
+ conn = git_connect(fd, dest, args.uploadpack,
+ flags);
+ if (!conn)
+ return args.diag_url ? 0 : 1;
+ }
+ get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL, &shallow);
+
+ 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))
- ref = NULL;
+ return 1;
+
ret = !ref;
- if (!ret && nr_heads) {
- /* If the heads to pull were given, we should have
- * consumed all of them by matching the remote.
- * Otherwise, 'git fetch remote no-such-ref' would
- * silently succeed without issuing an error.
- */
- for (i = 0; i < nr_heads; i++)
- if (heads[i] && heads[i][0]) {
- error("no such remote ref %s", heads[i]);
- ret = 1;
- }
+ /*
+ * If the heads to pull were given, we should have consumed
+ * all of them by matching the remote. Otherwise, 'git fetch
+ * remote no-such-ref' would silently succeed without issuing
+ * an error.
+ */
+ for (i = 0; i < nr_sought; i++) {
+ if (!sought[i] || sought[i]->matched)
+ continue;
+ error("no such remote ref %s", sought[i]->name);
+ ret = 1;
}
+
while (ref) {
printf("%s %s\n",
sha1_to_hex(ref->old_sha1), ref->name);
@@ -992,64 +216,3 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
return ret;
}
-
-struct ref *fetch_pack(struct fetch_pack_args *my_args,
- int fd[], struct child_process *conn,
- const struct ref *ref,
- const char *dest,
- int nr_heads,
- char **heads,
- char **pack_lockfile)
-{
- struct stat st;
- struct ref *ref_cpy;
-
- fetch_pack_setup();
- if (&args != my_args)
- memcpy(&args, my_args, sizeof(args));
- if (args.depth > 0) {
- if (stat(git_path("shallow"), &st))
- st.st_mtime = 0;
- }
-
- if (heads && nr_heads)
- nr_heads = remove_duplicates(nr_heads, heads);
- if (!ref) {
- packet_flush(fd[1]);
- die("no matching remote head");
- }
- ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile);
-
- if (args.depth > 0) {
- struct cache_time mtime;
- struct strbuf sb = STRBUF_INIT;
- char *shallow = git_path("shallow");
- int fd;
-
- mtime.sec = st.st_mtime;
- mtime.nsec = ST_MTIME_NSEC(st);
- if (stat(shallow, &st)) {
- if (mtime.sec)
- die("shallow file was removed during fetch");
- } else if (st.st_mtime != mtime.sec
-#ifdef USE_NSEC
- || ST_MTIME_NSEC(st) != mtime.nsec
-#endif
- )
- die("shallow file was changed during fetch");
-
- fd = hold_lock_file_for_update(&lock, shallow,
- LOCK_DIE_ON_ERROR);
- if (!write_shallow_commits(&sb, 0)
- || write_in_full(fd, sb.buf, sb.len) != sb.len) {
- unlink_or_warn(shallow);
- rollback_lock_file(&lock);
- } else {
- commit_lock_file(&lock);
- }
- strbuf_release(&sb);
- }
-
- reprepare_packed_git();
- return ref_cpy;
-}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index e422ced..75a55e5 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -11,14 +11,15 @@
#include "run-command.h"
#include "parse-options.h"
#include "sigchain.h"
-#include "transport.h"
#include "submodule.h"
+#include "connected.h"
+#include "argv-array.h"
static const char * const builtin_fetch_usage[] = {
- "git fetch [<options>] [<repository> [<refspec>...]]",
- "git fetch [<options>] <group>",
- "git fetch --multiple [<options>] [(<repository> | <group>)...]",
- "git fetch --all [<options>]",
+ N_("git fetch [<options>] [<repository> [<refspec>...]]"),
+ N_("git fetch [<options>] <group>"),
+ N_("git fetch --multiple [<options>] [(<repository> | <group>)...]"),
+ N_("git fetch --all [<options>]"),
NULL
};
@@ -28,15 +29,23 @@ enum {
TAGS_SET = 2
};
-static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
-static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
-static int tags = TAGS_DEFAULT;
+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, 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 refmap_alloc, refmap_nr;
+static const char **refmap_array;
static int option_parse_recurse_submodules(const struct option *opt,
const char *arg, int unset)
@@ -52,46 +61,77 @@ 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 git_default_config(k, v, cb);
+}
+
+static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)
+{
+ ALLOC_GROW(refmap_array, refmap_nr + 1, refmap_alloc);
+
+ /*
+ * "git fetch --refmap='' origin foo"
+ * can be used to tell the command not to store anywhere
+ */
+ if (*arg)
+ refmap_array[refmap_nr++] = arg;
+ return 0;
+}
+
static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
- OPT_BOOLEAN(0, "all", &all,
- "fetch from all remotes"),
- OPT_BOOLEAN('a', "append", &append,
- "append to .git/FETCH_HEAD instead of overwriting"),
- OPT_STRING(0, "upload-pack", &upload_pack, "path",
- "path to upload pack on remote end"),
- OPT__FORCE(&force, "force overwrite of local branch"),
- OPT_BOOLEAN('m', "multiple", &multiple,
- "fetch from multiple remotes"),
+ 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_BOOL('m', "multiple", &multiple,
+ N_("fetch from multiple remotes")),
OPT_SET_INT('t', "tags", &tags,
- "fetch all tags and associated objects", TAGS_SET),
+ N_("fetch all tags and associated objects"), TAGS_SET),
OPT_SET_INT('n', NULL, &tags,
- "do not fetch all tags (--no-tags)", TAGS_UNSET),
- OPT_BOOLEAN('p', "prune", &prune,
- "prune remote-tracking branches no longer on remote"),
- { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "on-demand",
- "control recursive fetching of submodules",
+ N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
+ 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,
- "dry run"),
- OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"),
- OPT_BOOLEAN('u', "update-head-ok", &update_head_ok,
- "allow updating of HEAD ref"),
- OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
- OPT_STRING(0, "depth", &depth, "depth",
- "deepen history of shallow clone"),
- { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir",
- "prepend this to submodule path output", PARSE_OPT_HIDDEN },
+ 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"),
+ N_("deepen history of shallow clone")),
+ { OPTION_SET_INT, 0, "unshallow", &unshallow, NULL,
+ N_("convert to a complete repository"),
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1 },
+ { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"),
+ N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN },
{ OPTION_STRING, 0, "recurse-submodules-default",
&recurse_submodules_default, NULL,
- "default mode for recursion", PARSE_OPT_HIDDEN },
+ N_("default mode for recursion"), PARSE_OPT_HIDDEN },
+ OPT_BOOL(0, "update-shallow", &update_shallow,
+ N_("accept refs that update .git/shallow")),
+ { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"),
+ N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg },
OPT_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)
@@ -114,7 +154,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;
}
}
@@ -135,36 +175,172 @@ 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) {
+ struct refspec *fetch_refspec;
+ int fetch_refspec_nr;
+
+ 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.
+ */
+ if (refmap_array) {
+ fetch_refspec = parse_fetch_refspec(refmap_nr, refmap_array);
+ fetch_refspec_nr = refmap_nr;
+ } else {
+ fetch_refspec = transport->remote->fetch;
+ fetch_refspec_nr = transport->remote->fetch_refspec_nr;
+ }
+
+ for (i = 0; i < fetch_refspec_nr; i++)
+ get_fetch_map(ref_map, &fetch_refspec[i], &oref_tail, 1);
+
if (tags == TAGS_SET)
get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+ } else if (refmap_array) {
+ die("--refmap option is only meaningful with command-line refspec(s).");
} else {
/* Use the defaults */
struct remote *remote = transport->remote;
@@ -181,7 +357,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
@@ -197,15 +373,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
@@ -217,45 +403,60 @@ static int s_update_ref(const char *action,
{
char msg[1024];
char *rla = getenv("GIT_REFLOG_ACTION");
- static struct ref_lock *lock;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ int ret, df_conflict = 0;
if (dry_run)
return 0;
if (!rla)
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);
- if (!lock)
- return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
- STORE_REF_ERROR_OTHER;
- if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
- return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
- STORE_REF_ERROR_OTHER;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref->name, ref->new_sha1,
+ ref->old_sha1, 0, check_old, msg, &err))
+ goto fail;
+
+ ret = ref_transaction_commit(transaction, &err);
+ if (ret) {
+ df_conflict = (ret == TRANSACTION_NAME_CONFLICT);
+ goto fail;
+ }
+
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
return 0;
+fail:
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ return df_conflict ? STORE_REF_ERROR_DF_CONFLICT
+ : STORE_REF_ERROR_OTHER;
}
#define REFCOL_WIDTH 10
static int update_local_ref(struct ref *ref,
const char *remote,
- char *display)
+ const struct ref *remote_ref,
+ struct strbuf *display)
{
struct commit *current = NULL, *updated;
enum object_type type;
struct branch *current_branch = branch_get(NULL);
const char *pretty_ref = prettify_refname(ref->name);
- *display = 0;
type = sha1_object_info(ref->new_sha1, NULL);
if (type < 0)
die(_("object %s not found"), sha1_to_hex(ref->new_sha1));
if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
if (verbosity > 0)
- sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH,
- _("[up to date]"), REFCOL_WIDTH, remote,
- pretty_ref);
+ strbuf_addf(display, "= %-*s %-*s -> %s",
+ TRANSPORT_SUMMARY(_("[up to date]")),
+ REFCOL_WIDTH, remote, pretty_ref);
return 0;
}
@@ -267,19 +468,22 @@ static int update_local_ref(struct ref *ref,
* If this is the head, and it's not okay to update
* the head, and the old value of the head isn't empty...
*/
- sprintf(display, _("! %-*s %-*s -> %s (can't fetch in current branch)"),
- TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote,
- pretty_ref);
+ strbuf_addf(display,
+ _("! %-*s %-*s -> %s (can't fetch in current branch)"),
+ TRANSPORT_SUMMARY(_("[rejected]")),
+ REFCOL_WIDTH, remote, pretty_ref);
return 1;
}
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);
- sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-',
- TRANSPORT_SUMMARY_WIDTH, _("[tag update]"), REFCOL_WIDTH, remote,
- pretty_ref, r ? _(" (unable to update local ref)") : "");
+ strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+ r ? '!' : '-',
+ TRANSPORT_SUMMARY(_("[tag update]")),
+ REFCOL_WIDTH, remote, pretty_ref,
+ r ? _(" (unable to update local ref)") : "");
return r;
}
@@ -289,26 +493,36 @@ static int update_local_ref(struct ref *ref,
const char *msg;
const char *what;
int r;
- if (!strncmp(ref->name, "refs/tags/", 10)) {
+ /*
+ * Nicely describe the new ref we're fetching.
+ * Base this on the remote's ref name, as it's
+ * more likely to follow a standard layout.
+ */
+ const char *name = remote_ref ? remote_ref->name : "";
+ if (starts_with(name, "refs/tags/")) {
msg = "storing tag";
what = _("[new tag]");
- }
- else {
+ } else if (starts_with(name, "refs/heads/")) {
msg = "storing head";
what = _("[new branch]");
- if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
- (recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(ref->new_sha1);
+ } else {
+ msg = "storing ref";
+ what = _("[new ref]");
}
+ if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
+ (recurse_submodules != RECURSE_SUBMODULES_ON))
+ check_for_new_submodule_commits(ref->new_sha1);
r = s_update_ref(msg, ref, 0);
- sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*',
- TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref,
- r ? _(" (unable to update local ref)") : "");
+ strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+ r ? '!' : '*',
+ TRANSPORT_SUMMARY(what),
+ REFCOL_WIDTH, remote, pretty_ref,
+ r ? _(" (unable to update local ref)") : "");
return r;
}
- if (in_merge_bases(current, &updated, 1)) {
+ if (in_merge_bases(current, updated)) {
char quickref[83];
int r;
strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
@@ -318,9 +532,11 @@ static int update_local_ref(struct ref *ref,
(recurse_submodules != RECURSE_SUBMODULES_ON))
check_for_new_submodule_commits(ref->new_sha1);
r = s_update_ref("fast-forward", ref, 1);
- sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ',
- TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
- pretty_ref, r ? _(" (unable to update local ref)") : "");
+ strbuf_addf(display, "%c %-*s %-*s -> %s%s",
+ r ? '!' : ' ',
+ TRANSPORT_SUMMARY_WIDTH, quickref,
+ REFCOL_WIDTH, remote, pretty_ref,
+ r ? _(" (unable to update local ref)") : "");
return r;
} else if (force || ref->force) {
char quickref[84];
@@ -332,29 +548,46 @@ static int update_local_ref(struct ref *ref,
(recurse_submodules != RECURSE_SUBMODULES_ON))
check_for_new_submodule_commits(ref->new_sha1);
r = s_update_ref("forced-update", ref, 1);
- sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+',
- TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote,
- pretty_ref,
- r ? _("unable to update local ref") : _("forced update"));
+ strbuf_addf(display, "%c %-*s %-*s -> %s (%s)",
+ r ? '!' : '+',
+ TRANSPORT_SUMMARY_WIDTH, quickref,
+ REFCOL_WIDTH, remote, pretty_ref,
+ r ? _("unable to update local ref") : _("forced update"));
return r;
} else {
- sprintf(display, "! %-*s %-*s -> %s %s",
- TRANSPORT_SUMMARY_WIDTH, _("[rejected]"), REFCOL_WIDTH, remote,
- pretty_ref, _("(non-fast-forward)"));
+ strbuf_addf(display, "! %-*s %-*s -> %s %s",
+ TRANSPORT_SUMMARY(_("[rejected]")),
+ REFCOL_WIDTH, remote, pretty_ref,
+ _("(non-fast-forward)"));
return 1;
}
}
+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;
+ hashcpy(sha1, ref->old_sha1);
+ return 0;
+}
+
static int store_updated_refs(const char *raw_url, const char *remote_name,
struct ref *ref_map)
{
FILE *fp;
struct commit *commit;
- int url_len, i, note_len, shown_url = 0, rc = 0;
- char note[1024];
+ 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_status;
fp = fopen(filename, "a");
if (!fp)
@@ -364,92 +597,134 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
url = transport_anonymize_url(raw_url);
else
url = xstrdup("foreign");
- for (rm = ref_map; rm; rm = rm->next) {
- struct ref *ref = NULL;
- if (rm->peer_ref) {
- ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
- strcpy(ref->name, rm->peer_ref->name);
- hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
- hashcpy(ref->new_sha1, rm->old_sha1);
- ref->force = rm->peer_ref->force;
- }
+ rm = ref_map;
+ if (check_everything_connected(iterate_ref_map, 0, &rm)) {
+ rc = error(_("%s did not send all necessary objects\n"), url);
+ goto abort;
+ }
- commit = lookup_commit_reference_gently(rm->old_sha1, 1);
- if (!commit)
- rm->merge = 0;
+ /*
+ * 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_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;
+ }
- if (!strcmp(rm->name, "HEAD")) {
- kind = "";
- what = "";
- }
- else if (!prefixcmp(rm->name, "refs/heads/")) {
- kind = "branch";
- what = rm->name + 11;
- }
- else if (!prefixcmp(rm->name, "refs/tags/")) {
- kind = "tag";
- what = rm->name + 10;
- }
- else if (!prefixcmp(rm->name, "refs/remotes/")) {
- kind = "remote-tracking branch";
- what = rm->name + 13;
- }
- else {
- kind = "";
- what = rm->name;
- }
+ commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+ if (!commit)
+ rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
- 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;
-
- note_len = 0;
- if (*what) {
- if (*kind)
- note_len += sprintf(note + note_len, "%s ",
- kind);
- note_len += sprintf(note + note_len, "'%s' of ", what);
- }
- note[note_len] = '\0';
- fprintf(fp, "%s\t%s\t%s",
- sha1_to_hex(commit ? commit->object.sha1 :
- rm->old_sha1),
- rm->merge ? "" : "not-for-merge",
- note);
- for (i = 0; i < url_len; ++i)
- if ('\n' == url[i])
- fputs("\\n", fp);
- else
- fputc(url[i], fp);
- fputc('\n', fp);
-
- if (ref) {
- rc |= update_local_ref(ref, what, note);
- free(ref);
- } else
- sprintf(note, "* %-*s %-*s -> FETCH_HEAD",
- TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch",
- REFCOL_WIDTH, *what ? what : "HEAD");
- if (*note) {
- if (verbosity >= 0 && !shown_url) {
- fprintf(stderr, _("From %.*s\n"),
- url_len, url);
- shown_url = 1;
+ if (rm->fetch_head_status != want_status)
+ continue;
+
+ if (rm->peer_ref) {
+ ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1);
+ strcpy(ref->name, rm->peer_ref->name);
+ hashcpy(ref->old_sha1, rm->peer_ref->old_sha1);
+ hashcpy(ref->new_sha1, rm->old_sha1);
+ ref->force = rm->peer_ref->force;
+ }
+
+
+ if (!strcmp(rm->name, "HEAD")) {
+ kind = "";
+ what = "";
+ }
+ else if (starts_with(rm->name, "refs/heads/")) {
+ kind = "branch";
+ what = rm->name + 11;
+ }
+ else if (starts_with(rm->name, "refs/tags/")) {
+ kind = "tag";
+ what = rm->name + 10;
+ }
+ else if (starts_with(rm->name, "refs/remotes/")) {
+ kind = "remote-tracking branch";
+ what = rm->name + 13;
+ }
+ else {
+ kind = "";
+ what = rm->name;
+ }
+
+ 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;
+
+ strbuf_reset(&note);
+ if (*what) {
+ if (*kind)
+ strbuf_addf(&note, "%s ", kind);
+ strbuf_addf(&note, "'%s' of ", what);
+ }
+ switch (rm->fetch_head_status) {
+ case FETCH_HEAD_NOT_FOR_MERGE:
+ merge_status_marker = "not-for-merge";
+ /* fall-through */
+ case FETCH_HEAD_MERGE:
+ fprintf(fp, "%s\t%s\t%s",
+ 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) {
+ rc |= update_local_ref(ref, what, rm, &note);
+ free(ref);
+ } else
+ strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
+ TRANSPORT_SUMMARY_WIDTH,
+ *kind ? kind : "branch",
+ REFCOL_WIDTH,
+ *what ? what : "HEAD");
+ if (note.len) {
+ if (verbosity >= 0 && !shown_url) {
+ fprintf(stderr, _("From %.*s\n"),
+ url_len, url);
+ shown_url = 1;
+ }
+ if (verbosity >= 0)
+ fprintf(stderr, " %s\n", note.buf);
}
- if (verbosity >= 0)
- fprintf(stderr, " %s\n", note);
}
}
- free(url);
- fclose(fp);
+
if (rc & STORE_REF_ERROR_DF_CONFLICT)
error(_("some local refs could not be updated; try running\n"
" 'git remote prune %s' to remove any old, conflicting "
"branches"), remote_name);
+
+ abort:
+ strbuf_release(&note);
+ free(url);
+ fclose(fp);
return rc;
}
@@ -457,23 +732,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
* We would want to bypass the object transfer altogether if
* everything we are going to fetch already exists and is connected
* locally.
- *
- * The refs we are going to fetch are in ref_map. If running
- *
- * $ git rev-list --objects --stdin --not --all
- *
- * (feeding all the refs in ref_map on its standard input)
- * does not error out, that means everything reachable from the
- * refs we are going to fetch exists and is connected to some of
- * our existing refs.
*/
static int quickfetch(struct ref *ref_map)
{
- struct child_process revlist;
- struct ref *ref;
- int err;
- const char *argv[] = {"rev-list",
- "--quiet", "--objects", "--stdin", "--not", "--all", NULL};
+ struct ref *rm = ref_map;
/*
* If we are deepening a shallow clone we already have these
@@ -484,47 +746,7 @@ static int quickfetch(struct ref *ref_map)
*/
if (depth)
return -1;
-
- if (!ref_map)
- return 0;
-
- memset(&revlist, 0, sizeof(revlist));
- revlist.argv = argv;
- revlist.git_cmd = 1;
- revlist.no_stdout = 1;
- revlist.no_stderr = 1;
- revlist.in = -1;
-
- err = start_command(&revlist);
- if (err) {
- error(_("could not run rev-list"));
- return err;
- }
-
- /*
- * If rev-list --stdin encounters an unknown commit, it terminates,
- * which will cause SIGPIPE in the write loop below.
- */
- sigchain_push(SIGPIPE, SIG_IGN);
-
- for (ref = ref_map; ref; ref = ref->next) {
- if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 ||
- write_str_in_full(revlist.in, "\n") < 0) {
- if (errno != EPIPE && errno != EINVAL)
- error(_("failed write to rev-list: %s"), strerror(errno));
- err = -1;
- break;
- }
- }
-
- if (close(revlist.in)) {
- error(_("failed to close rev-list's stdin: %s"), strerror(errno));
- err = -1;
- }
-
- sigchain_pop(SIGPIPE);
-
- return finish_command(&revlist) || err;
+ return check_everything_connected(iterate_ref_map, 1, &rm);
}
static int fetch_refs(struct transport *transport, struct ref *ref_map)
@@ -540,127 +762,48 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
return ret;
}
-static int prune_refs(struct transport *transport, 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;
- struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+ 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)\n")
- : _(" (%s has become dangling)\n");
+ ? _(" (%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_WIDTH, _("[deleted]"),
+ TRANSPORT_SUMMARY(_("[deleted]")),
REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name));
warn_dangling_symref(stderr, dangling_msg, ref->name);
}
}
+ free(url);
free_refs(stale_refs);
return result;
}
-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);
@@ -686,14 +829,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);
@@ -709,9 +896,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);
@@ -720,8 +907,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);
@@ -730,12 +918,26 @@ static int do_fetch(struct transport *transport,
if (tags == TAGS_DEFAULT && autotags)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
+ if (prune) {
+ /*
+ * 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,
+ transport->url);
+ }
+ }
if (fetch_refs(transport, ref_map)) {
free_refs(ref_map);
- return 1;
+ retcode = 1;
+ goto cleanup;
}
- if (prune)
- prune_refs(transport, ref_map);
free_refs(ref_map);
/* if neither --no-tags nor --tags was specified, do automated tag
@@ -744,26 +946,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)
@@ -783,7 +973,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");
@@ -817,38 +1007,39 @@ static int add_remote_or_group(const char *name, struct string_list *list)
return 1;
}
-static void add_options_to_argv(int *argc, const char **argv)
+static void add_options_to_argv(struct argv_array *argv)
{
if (dry_run)
- argv[(*argc)++] = "--dry-run";
- if (prune)
- argv[(*argc)++] = "--prune";
+ argv_array_push(argv, "--dry-run");
+ if (prune != -1)
+ argv_array_push(argv, prune ? "--prune" : "--no-prune");
if (update_head_ok)
- argv[(*argc)++] = "--update-head-ok";
+ argv_array_push(argv, "--update-head-ok");
if (force)
- argv[(*argc)++] = "--force";
+ argv_array_push(argv, "--force");
if (keep)
- argv[(*argc)++] = "--keep";
+ argv_array_push(argv, "--keep");
if (recurse_submodules == RECURSE_SUBMODULES_ON)
- argv[(*argc)++] = "--recurse-submodules";
+ argv_array_push(argv, "--recurse-submodules");
else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
- argv[(*argc)++] = "--recurse-submodules=on-demand";
+ argv_array_push(argv, "--recurse-submodules=on-demand");
+ if (tags == TAGS_SET)
+ argv_array_push(argv, "--tags");
+ else if (tags == TAGS_UNSET)
+ argv_array_push(argv, "--no-tags");
if (verbosity >= 2)
- argv[(*argc)++] = "-v";
+ argv_array_push(argv, "-v");
if (verbosity >= 1)
- argv[(*argc)++] = "-v";
+ argv_array_push(argv, "-v");
else if (verbosity < 0)
- argv[(*argc)++] = "-q";
+ argv_array_push(argv, "-q");
}
static int fetch_multiple(struct string_list *list)
{
int i, result = 0;
- const char *argv[12] = { "fetch", "--append" };
- int argc = 2;
-
- add_options_to_argv(&argc, argv);
+ struct argv_array argv = ARGV_ARRAY_INIT;
if (!append && !dry_run) {
int errcode = truncate_fetch_head();
@@ -856,24 +1047,27 @@ static int fetch_multiple(struct string_list *list)
return errcode;
}
+ argv_array_pushl(&argv, "fetch", "--append", NULL);
+ add_options_to_argv(&argv);
+
for (i = 0; i < list->nr; i++) {
const char *name = list->items[i].string;
- argv[argc] = name;
- argv[argc + 1] = NULL;
+ argv_array_push(&argv, name);
if (verbosity >= 0)
printf(_("Fetching %s\n"), name);
- if (run_command_v_opt(argv, RUN_GIT_CMD)) {
+ if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
error(_("Could not fetch %s"), name);
result = 1;
}
+ argv_array_pop(&argv);
}
+ argv_array_clear(&argv);
return result;
}
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;
@@ -883,30 +1077,29 @@ 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")) {
- char *ref;
i++;
if (i >= argc)
die(_("You need to specify a tag name."));
- ref = xmalloc(strlen(argv[i]) * 2 + 22);
- strcpy(ref, "refs/tags/");
- strcat(ref, argv[i]);
- strcat(ref, ":refs/tags/");
- strcat(ref, argv[i]);
- refs[j++] = ref;
+ refs[j++] = xstrfmt("refs/tags/%s:refs/tags/%s",
+ argv[i], argv[i]);
} else
refs[j++] = argv[i];
}
@@ -917,10 +1110,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);
- free(refspec);
- transport_disconnect(transport);
- transport = NULL;
+ exit_code = do_fetch(gtransport, refspec, ref_nr);
+ free_refspec(ref_nr, refspec);
+ transport_disconnect(gtransport);
+ gtransport = NULL;
return exit_code;
}
@@ -930,6 +1123,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
struct string_list list = STRING_LIST_INIT_NODUP;
struct remote *remote;
int result = 0;
+ struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
packet_trace_identity("fetch");
@@ -938,9 +1132,27 @@ 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);
+ if (unshallow) {
+ if (depth)
+ die(_("--depth and --unshallow cannot be used together"));
+ else if (!is_repository_shallow())
+ die(_("--unshallow on a complete repository does not make sense"));
+ else {
+ static char inf_depth[12];
+ sprintf(inf_depth, "%d", INFINITE_DEPTH);
+ depth = inf_depth;
+ }
+ }
+
+ /* 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);
@@ -983,18 +1195,25 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
- const char *options[10];
- int num_options = 0;
- add_options_to_argv(&num_options, options);
- result = fetch_populated_submodules(num_options, options,
+ struct argv_array options = ARGV_ARRAY_INIT;
+
+ add_options_to_argv(&options);
+ result = fetch_populated_submodules(&options,
submodule_prefix,
recurse_submodules,
verbosity < 0);
+ argv_array_clear(&options);
}
/* All names were strdup()ed or strndup()ed */
list.strdup_strings = 1;
string_list_clear(&list, 0);
+ argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL);
+ if (verbosity < 0)
+ argv_array_push(&argv_gc_auto, "--quiet");
+ run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD);
+ argv_array_clear(&argv_gc_auto);
+
return result;
}
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index 7581632..af7919e 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -5,32 +5,45 @@
#include "revision.h"
#include "tag.h"
#include "string-list.h"
+#include "branch.h"
+#include "fmt-merge-msg.h"
+#include "gpg-interface.h"
static const char * const fmt_merge_msg_usage[] = {
- "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
+ N_("git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]"),
NULL
};
-static int shortlog_len;
+static int use_branch_desc;
-static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
+int fmt_merge_msg_config(const char *key, const char *value, void *cb)
{
if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) {
int is_bool;
- shortlog_len = git_config_bool_or_int(key, value, &is_bool);
- if (!is_bool && shortlog_len < 0)
+ merge_log_config = git_config_bool_or_int(key, value, &is_bool);
+ if (!is_bool && merge_log_config < 0)
return error("%s: negative length %s", key, value);
- if (is_bool && shortlog_len)
- shortlog_len = DEFAULT_MERGE_LOG_LEN;
+ if (is_bool && merge_log_config)
+ merge_log_config = DEFAULT_MERGE_LOG_LEN;
+ } else if (!strcmp(key, "merge.branchdesc")) {
+ use_branch_desc = git_config_bool(key, value);
+ } else {
+ return git_default_config(key, value, cb);
}
return 0;
}
+/* merge data per repository where the merged tips came from */
struct src_data {
struct string_list branch, tag, r_branch, generic;
int head_status;
};
+struct origin_data {
+ unsigned char sha1[20];
+ unsigned is_local_branch:1;
+};
+
static void init_src_data(struct src_data *data)
{
data->branch.strdup_strings = 1;
@@ -42,35 +55,86 @@ static void init_src_data(struct src_data *data)
static struct string_list srcs = STRING_LIST_INIT_DUP;
static struct string_list origins = STRING_LIST_INIT_DUP;
-static int handle_line(char *line)
+struct merge_parents {
+ int alloc, nr;
+ struct merge_parent {
+ unsigned char given[20];
+ unsigned char commit[20];
+ unsigned char used;
+ } *item;
+};
+
+/*
+ * I know, I know, this is inefficient, but you won't be pulling and merging
+ * hundreds of heads at a time anyway.
+ */
+static struct merge_parent *find_merge_parent(struct merge_parents *table,
+ unsigned char *given,
+ unsigned char *commit)
+{
+ int i;
+ for (i = 0; i < table->nr; i++) {
+ if (given && hashcmp(table->item[i].given, given))
+ continue;
+ if (commit && hashcmp(table->item[i].commit, commit))
+ continue;
+ return &table->item[i];
+ }
+ return NULL;
+}
+
+static void add_merge_parent(struct merge_parents *table,
+ unsigned char *given,
+ unsigned char *commit)
+{
+ if (table->nr && find_merge_parent(table, given, commit))
+ return;
+ ALLOC_GROW(table->item, table->nr + 1, table->alloc);
+ hashcpy(table->item[table->nr].given, given);
+ hashcpy(table->item[table->nr].commit, commit);
+ table->item[table->nr].used = 0;
+ table->nr++;
+}
+
+static int handle_line(char *line, struct merge_parents *merge_parents)
{
int i, len = strlen(line);
- unsigned char *sha1;
- char *src, *origin;
+ struct origin_data *origin_data;
+ char *src;
+ const char *origin;
struct src_data *src_data;
struct string_list_item *item;
int pulling_head = 0;
+ unsigned char sha1[20];
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')
return 2;
- line[40] = 0;
- sha1 = xmalloc(20);
- i = get_sha1(line, sha1);
- line[40] = '\t';
+ i = get_sha1_hex(line, sha1);
if (i)
return 3;
+ if (!find_merge_parent(merge_parents, sha1, NULL))
+ return 0; /* subsumed by other parents */
+
+ origin_data = xcalloc(1, sizeof(struct origin_data));
+ hashcpy(origin_data->sha1, sha1);
+
if (line[len - 1] == '\n')
line[len - 1] = 0;
line += 42;
+ /*
+ * At this point, line points at the beginning of comment e.g.
+ * "branch 'frotz' of git://that/repository.git".
+ * Find the repository name and point it with src.
+ */
src = strstr(line, " of ");
if (src) {
*src = 0;
@@ -92,16 +156,16 @@ static int handle_line(char *line)
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 ")) {
- origin = line + strlen("remote-tracking branch ");
+ } else if (skip_prefix(line, "remote-tracking branch ", &origin)) {
string_list_append(&src_data->r_branch, origin);
src_data->head_status |= 2;
} else {
@@ -114,12 +178,11 @@ static int handle_line(char *line)
int len = strlen(origin);
if (origin[0] == '\'' && origin[len - 1] == '\'')
origin = xmemdupz(origin + 1, len - 2);
- } else {
- char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);
- sprintf(new_origin, "%s of %s", origin, src);
- origin = new_origin;
- }
- string_list_append(&origins, origin)->util = sha1;
+ } else
+ origin = xstrfmt("%s of %s", origin, src);
+ if (strcmp(".", src))
+ origin_data->is_local_branch = 0;
+ string_list_append(&origins, origin)->util = origin_data;
return 0;
}
@@ -140,23 +203,146 @@ static void print_joined(const char *singular, const char *plural,
}
}
-static void shortlog(const char *name, unsigned char *sha1,
- struct commit *head, struct rev_info *rev, int limit,
- struct strbuf *out)
+static void add_branch_desc(struct strbuf *out, const char *name)
+{
+ struct strbuf desc = STRBUF_INIT;
+
+ if (!read_branch_desc(&desc, name)) {
+ const char *bp = desc.buf;
+ while (*bp) {
+ const char *ep = strchrnul(bp, '\n');
+ if (*ep)
+ ep++;
+ strbuf_addf(out, " : %.*s", (int)(ep - bp), bp);
+ bp = ep;
+ }
+ strbuf_complete_line(out);
+ }
+ strbuf_release(&desc);
+}
+
+#define util_as_integral(elem) ((intptr_t)((elem)->util))
+
+static void record_person(int which, struct string_list *people,
+ struct commit *commit)
+{
+ const char *buffer;
+ char *name_buf, *name, *name_end;
+ struct string_list_item *elem;
+ const char *field;
+
+ field = (which == 'a') ? "\nauthor " : "\ncommitter ";
+ buffer = get_commit_buffer(commit, NULL);
+ name = strstr(buffer, field);
+ if (!name)
+ return;
+ name += strlen(field);
+ name_end = strchrnul(name, '<');
+ if (*name_end)
+ name_end--;
+ while (isspace(*name_end) && name <= name_end)
+ name_end--;
+ if (name_end < name)
+ return;
+ name_buf = xmemdupz(name, name_end - name + 1);
+ unuse_commit_buffer(commit, buffer);
+
+ elem = string_list_lookup(people, name_buf);
+ if (!elem) {
+ elem = string_list_insert(people, name_buf);
+ elem->util = (void *)0;
+ }
+ elem->util = (void*)(util_as_integral(elem) + 1);
+ free(name_buf);
+}
+
+static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
+{
+ const struct string_list_item *a = a_, *b = b_;
+ return util_as_integral(b) - util_as_integral(a);
+}
+
+static void add_people_count(struct strbuf *out, struct string_list *people)
+{
+ if (people->nr == 1)
+ strbuf_addf(out, "%s", people->items[0].string);
+ else if (people->nr == 2)
+ strbuf_addf(out, "%s (%d) and %s (%d)",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]),
+ people->items[1].string,
+ (int)util_as_integral(&people->items[1]));
+ else if (people->nr)
+ strbuf_addf(out, "%s (%d) and others",
+ people->items[0].string,
+ (int)util_as_integral(&people->items[0]));
+}
+
+static void credit_people(struct strbuf *out,
+ struct string_list *them,
+ int kind)
+{
+ const char *label;
+ const char *me;
+
+ if (kind == 'a') {
+ label = "By";
+ me = git_author_info(IDENT_NO_DATE);
+ } else {
+ label = "Via";
+ me = git_committer_info(IDENT_NO_DATE);
+ }
+
+ if (!them->nr ||
+ (them->nr == 1 &&
+ me &&
+ skip_prefix(me, them->items->string, &me) &&
+ starts_with(me, " <")))
+ return;
+ strbuf_addf(out, "\n%c %s ", comment_line_char, label);
+ add_people_count(out, them);
+}
+
+static void add_people_info(struct strbuf *out,
+ struct string_list *authors,
+ struct string_list *committers)
+{
+ if (authors->nr)
+ qsort(authors->items,
+ authors->nr, sizeof(authors->items[0]),
+ cmp_string_list_util_as_integral);
+ if (committers->nr)
+ qsort(committers->items,
+ committers->nr, sizeof(committers->items[0]),
+ cmp_string_list_util_as_integral);
+
+ credit_people(out, authors, 'a');
+ credit_people(out, committers, 'c');
+}
+
+static void shortlog(const char *name,
+ struct origin_data *origin_data,
+ struct commit *head,
+ struct rev_info *rev,
+ struct fmt_merge_msg_opts *opts,
+ struct strbuf *out)
{
int i, count = 0;
struct commit *commit;
struct object *branch;
struct string_list subjects = STRING_LIST_INIT_DUP;
+ struct string_list authors = STRING_LIST_INIT_DUP;
+ struct string_list committers = STRING_LIST_INIT_DUP;
int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
struct strbuf sb = STRBUF_INIT;
+ const unsigned char *sha1 = origin_data->sha1;
+ int limit = opts->shortlog_len;
branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
if (!branch || branch->type != OBJ_COMMIT)
return;
setup_revisions(0, NULL, rev, NULL);
- rev->ignore_merges = 1;
add_pending_object(rev, branch, name);
add_pending_object(rev, &head->object, "^HEAD");
head->object.flags |= UNINTERESTING;
@@ -165,10 +351,17 @@ static void shortlog(const char *name, unsigned char *sha1,
while ((commit = get_revision(rev)) != NULL) {
struct pretty_print_context ctx = {0};
- /* ignore merges */
- if (commit->parents && commit->parents->next)
+ if (commit->parents && commit->parents->next) {
+ /* do not list a merge but count committer */
+ if (opts->credit_people)
+ record_person('c', &committers, commit);
continue;
-
+ }
+ if (!count && opts->credit_people)
+ /* the 'tip' committer */
+ record_person('c', &committers, commit);
+ if (opts->credit_people)
+ record_person('a', &authors, commit);
count++;
if (subjects.nr > limit)
continue;
@@ -183,11 +376,16 @@ static void shortlog(const char *name, unsigned char *sha1,
string_list_append(&subjects, strbuf_detach(&sb, NULL));
}
+ if (opts->credit_people)
+ add_people_info(out, &authors, &committers);
if (count > limit)
strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
else
strbuf_addf(out, "\n* %s:\n", name);
+ if (origin_data->is_local_branch && use_branch_desc)
+ add_branch_desc(out, name);
+
for (i = 0; i < subjects.nr; i++)
if (i >= limit)
strbuf_addf(out, " ...\n");
@@ -200,10 +398,12 @@ static void shortlog(const char *name, unsigned char *sha1,
rev->commits = NULL;
rev->pending.nr = 0;
+ string_list_clear(&authors, 0);
+ string_list_clear(&committers, 0);
string_list_clear(&subjects, 0);
}
-static void do_fmt_merge_msg_title(struct strbuf *out,
+static void fmt_merge_msg_title(struct strbuf *out,
const char *current_branch) {
int i = 0;
char *sep = "";
@@ -256,19 +456,159 @@ static void do_fmt_merge_msg_title(struct strbuf *out,
strbuf_addf(out, " into %s\n", current_branch);
}
-static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
- struct strbuf *out, int shortlog_len) {
+static void fmt_tag_signature(struct strbuf *tagbuf,
+ struct strbuf *sig,
+ const char *buf,
+ unsigned long len)
+{
+ const char *tag_body = strstr(buf, "\n\n");
+ if (tag_body) {
+ tag_body += 2;
+ strbuf_add(tagbuf, tag_body, buf + len - tag_body);
+ }
+ strbuf_complete_line(tagbuf);
+ if (sig->len) {
+ strbuf_addch(tagbuf, '\n');
+ strbuf_add_commented_lines(tagbuf, sig->buf, sig->len);
+ }
+}
+
+static void fmt_merge_msg_sigs(struct strbuf *out)
+{
+ int i, tag_number = 0, first_tag = 0;
+ struct strbuf tagbuf = STRBUF_INIT;
+
+ for (i = 0; i < origins.nr; i++) {
+ unsigned char *sha1 = origins.items[i].util;
+ enum object_type type;
+ unsigned long size, len;
+ char *buf = read_sha1_file(sha1, &type, &size);
+ struct strbuf sig = STRBUF_INIT;
+
+ if (!buf || type != OBJ_TAG)
+ goto next;
+ len = parse_signature(buf, size);
+
+ if (size == len)
+ ; /* merely annotated */
+ else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) {
+ if (!sig.len)
+ strbuf_addstr(&sig, "gpg verification failed.\n");
+ }
+
+ if (!tag_number++) {
+ fmt_tag_signature(&tagbuf, &sig, buf, len);
+ first_tag = i;
+ } else {
+ if (tag_number == 2) {
+ struct strbuf tagline = STRBUF_INIT;
+ strbuf_addch(&tagline, '\n');
+ strbuf_add_commented_lines(&tagline,
+ origins.items[first_tag].string,
+ strlen(origins.items[first_tag].string));
+ strbuf_insert(&tagbuf, 0, tagline.buf,
+ tagline.len);
+ strbuf_release(&tagline);
+ }
+ strbuf_addch(&tagbuf, '\n');
+ strbuf_add_commented_lines(&tagbuf,
+ origins.items[i].string,
+ strlen(origins.items[i].string));
+ fmt_tag_signature(&tagbuf, &sig, buf, len);
+ }
+ strbuf_release(&sig);
+ next:
+ free(buf);
+ }
+ if (tagbuf.len) {
+ strbuf_addch(out, '\n');
+ strbuf_addbuf(out, &tagbuf);
+ }
+ strbuf_release(&tagbuf);
+}
+
+static void find_merge_parents(struct merge_parents *result,
+ struct strbuf *in, unsigned char *head)
+{
+ struct commit_list *parents, *next;
+ struct commit *head_commit;
+ int pos = 0, i, j;
+
+ parents = NULL;
+ while (pos < in->len) {
+ int len;
+ char *p = in->buf + pos;
+ char *newline = strchr(p, '\n');
+ unsigned char sha1[20];
+ struct commit *parent;
+ struct object *obj;
+
+ len = newline ? newline - p : strlen(p);
+ pos += len + !!newline;
+
+ if (len < 43 ||
+ get_sha1_hex(p, sha1) ||
+ p[40] != '\t' ||
+ p[41] != '\t')
+ continue; /* skip not-for-merge */
+ /*
+ * Do not use get_merge_parent() here; we do not have
+ * "name" here and we do not want to contaminate its
+ * util field yet.
+ */
+ obj = parse_object(sha1);
+ parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT);
+ if (!parent)
+ continue;
+ commit_list_insert(parent, &parents);
+ add_merge_parent(result, obj->sha1, parent->object.sha1);
+ }
+ head_commit = lookup_commit(head);
+ if (head_commit)
+ commit_list_insert(head_commit, &parents);
+ parents = reduce_heads(parents);
+
+ while (parents) {
+ for (i = 0; i < result->nr; i++)
+ if (!hashcmp(result->item[i].commit,
+ parents->item->object.sha1))
+ result->item[i].used = 1;
+ next = parents->next;
+ free(parents);
+ parents = next;
+ }
+
+ for (i = j = 0; i < result->nr; i++) {
+ if (result->item[i].used) {
+ if (i != j)
+ result->item[j] = result->item[i];
+ j++;
+ }
+ }
+ result->nr = j;
+}
+
+int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
+ struct fmt_merge_msg_opts *opts)
+{
int i = 0, pos = 0;
unsigned char head_sha1[20];
const char *current_branch;
+ void *current_branch_to_free;
+ struct merge_parents merge_parents;
+
+ memset(&merge_parents, 0, sizeof(merge_parents));
/* get current branch */
- current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
+ current_branch = current_branch_to_free =
+ resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, 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);
+
/* get a line */
while (pos < in->len) {
int len;
@@ -279,76 +619,71 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
pos += len + !!newline;
i++;
p[len] = 0;
- if (handle_line(p))
+ if (handle_line(p, &merge_parents))
die ("Error in line %d: %.*s", i, len, p);
}
- if (!srcs.nr)
- return 0;
+ if (opts->add_title && srcs.nr)
+ fmt_merge_msg_title(out, current_branch);
- if (merge_title)
- do_fmt_merge_msg_title(out, current_branch);
+ if (origins.nr)
+ fmt_merge_msg_sigs(out);
- if (shortlog_len) {
+ if (opts->shortlog_len) {
struct commit *head;
struct rev_info rev;
- head = lookup_commit(head_sha1);
+ head = lookup_commit_or_die(head_sha1, "HEAD");
init_revisions(&rev, NULL);
rev.commit_format = CMIT_FMT_ONELINE;
rev.ignore_merges = 1;
rev.limited = 1;
- if (suffixcmp(out->buf, "\n"))
- strbuf_addch(out, '\n');
+ strbuf_complete_line(out);
for (i = 0; i < origins.nr; i++)
- shortlog(origins.items[i].string, origins.items[i].util,
- head, &rev, shortlog_len, out);
+ shortlog(origins.items[i].string,
+ origins.items[i].util,
+ head, &rev, opts, out);
}
- return 0;
-}
-int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
- int merge_title, int shortlog_len) {
- return do_fmt_merge_msg(merge_title, in, out, shortlog_len);
+ strbuf_complete_line(out);
+ free(current_branch_to_free);
+ free(merge_parents.item);
+ return 0;
}
int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
{
const char *inpath = NULL;
const char *message = NULL;
+ int shortlog_len = -1;
struct option options[] = {
- { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
- "populate log with at most <n> entries from shortlog",
+ { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"),
+ N_("populate log with at most <n> entries from shortlog"),
PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
- { OPTION_INTEGER, 0, "summary", &shortlog_len, "n",
- "alias for --log (deprecated)",
+ { OPTION_INTEGER, 0, "summary", &shortlog_len, N_("n"),
+ N_("alias for --log (deprecated)"),
PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL,
DEFAULT_MERGE_LOG_LEN },
- OPT_STRING('m', "message", &message, "text",
- "use <text> as start of message"),
- OPT_FILENAME('F', "file", &inpath, "file to read from"),
+ OPT_STRING('m', "message", &message, N_("text"),
+ N_("use <text> as start of message")),
+ OPT_FILENAME('F', "file", &inpath, N_("file to read from")),
OPT_END()
};
FILE *in = stdin;
struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
int ret;
+ struct fmt_merge_msg_opts opts;
git_config(fmt_merge_msg_config, NULL);
argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
0);
if (argc > 0)
usage_with_options(fmt_merge_msg_usage, options);
- if (message && !shortlog_len) {
- char nl = '\n';
- write_in_full(STDOUT_FILENO, message, strlen(message));
- write_in_full(STDOUT_FILENO, &nl, 1);
- return 0;
- }
if (shortlog_len < 0)
- die("Negative --log=%d", shortlog_len);
+ shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
if (inpath && strcmp(inpath, "-")) {
in = fopen(inpath, "r");
@@ -361,10 +696,13 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
if (message)
strbuf_addstr(&output, message);
- ret = fmt_merge_msg(&input, &output,
- message ? 0 : 1,
- shortlog_len);
+ memset(&opts, 0, sizeof(opts));
+ opts.add_title = !message;
+ opts.credit_people = 1;
+ opts.shortlog_len = shortlog_len;
+
+ ret = fmt_merge_msg(&input, &output, &opts);
if (ret)
return ret;
write_in_full(STDOUT_FILENO, output.buf, output.len);
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 89e75c6..008513c 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
@@ -69,9 +70,14 @@ static struct {
{ "subject" },
{ "body" },
{ "contents" },
+ { "contents:subject" },
+ { "contents:body" },
+ { "contents:signature" },
{ "upstream" },
{ "symref" },
{ "flag" },
+ { "HEAD" },
+ { "color" },
};
/*
@@ -86,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
@@ -131,10 +138,8 @@ static int parse_atom(const char *atom, const char *ep)
/* Add it in, including the deref prefix */
at = used_atom_cnt;
used_atom_cnt++;
- used_atom = xrealloc(used_atom,
- (sizeof *used_atom) * used_atom_cnt);
- used_atom_type = xrealloc(used_atom_type,
- (sizeof(*used_atom_type) * used_atom_cnt));
+ REALLOC_ARRAY(used_atom, used_atom_cnt);
+ REALLOC_ARRAY(used_atom_type, used_atom_cnt);
used_atom[at] = xmemdupz(atom, ep - atom);
used_atom_type[at] = valid_atom[i].cmp_type;
if (*atom == '*')
@@ -173,13 +178,20 @@ static const char *find_next(const char *cp)
static int verify_format(const char *format)
{
const char *cp, *sp;
+
+ need_color_reset_at_eol = 0;
for (cp = format; *cp && (sp = find_next(cp)); ) {
- const char *ep = strchr(sp, ')');
+ const char *color, *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 (skip_prefix(used_atom[at], "color:", &color))
+ need_color_reset_at_eol = !!strcmp(color, "reset");
}
return 0;
}
@@ -202,6 +214,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)
{
@@ -222,15 +250,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);
}
}
@@ -259,18 +280,6 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob
}
}
-static int num_parents(struct commit *commit)
-{
- struct commit_list *parents;
- int i;
-
- for (i = 0, parents = commit->parents;
- parents;
- parents = parents->next)
- i++;
- return i;
-}
-
/* See grab_values */
static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
{
@@ -291,12 +300,12 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
}
if (!strcmp(name, "numparent")) {
char *s = xmalloc(40);
- v->ul = num_parents(commit);
+ v->ul = commit_list_count(commit->parents);
sprintf(s, "%lu", v->ul);
v->s = s;
}
else if (!strcmp(name, "parent")) {
- int num = num_parents(commit);
+ int num = commit_list_count(commit->parents);
int i;
struct commit_list *parents;
char *s = xmalloc(41 * num + 1);
@@ -361,6 +370,18 @@ static const char *copy_email(const char *buf)
return xmemdupz(email, eoemail + 1 - email);
}
+static char *copy_subject(const char *buf, unsigned long len)
+{
+ char *r = xmemdupz(buf, len);
+ int i;
+
+ for (i = 0; i < len; i++)
+ if (r[i] == '\n')
+ r[i] = ' ';
+
+ return r;
+}
+
static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
{
const char *eoemail = strstr(buf, "> ");
@@ -417,7 +438,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);
@@ -429,7 +450,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);
}
@@ -451,45 +472,63 @@ 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);
}
}
-static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+static void find_subpos(const char *buf, unsigned long sz,
+ const char **sub, unsigned long *sublen,
+ const char **body, unsigned long *bodylen,
+ unsigned long *nonsiglen,
+ const char **sig, unsigned long *siglen)
{
- while (*buf) {
- const char *eol = strchr(buf, '\n');
- if (!eol)
- return;
- if (eol[1] == '\n') {
- buf = eol + 1;
- break; /* found end of header */
- }
- buf = eol + 1;
+ const char *eol;
+ /* skip past header until we hit empty line */
+ while (*buf && *buf != '\n') {
+ eol = strchrnul(buf, '\n');
+ if (*eol)
+ eol++;
+ buf = eol;
}
+ /* skip any empty lines */
while (*buf == '\n')
buf++;
- if (!*buf)
- return;
- *sub = buf; /* first non-empty line */
- buf = strchr(buf, '\n');
- if (!buf) {
- *body = "";
- return; /* no body */
+
+ /* parse signature first; we might not even have a subject line */
+ *sig = buf + parse_signature(buf, strlen(buf));
+ *siglen = strlen(*sig);
+
+ /* subject is first non-empty line */
+ *sub = buf;
+ /* subject goes to first empty line */
+ while (buf < *sig && *buf && *buf != '\n') {
+ eol = strchrnul(buf, '\n');
+ if (*eol)
+ eol++;
+ buf = eol;
}
+ *sublen = buf - *sub;
+ /* drop trailing newline, if present */
+ if (*sublen && (*sub)[*sublen - 1] == '\n')
+ *sublen -= 1;
+
+ /* skip any empty lines */
while (*buf == '\n')
- buf++; /* skip blank between subject and body */
+ buf++;
*body = buf;
+ *bodylen = strlen(buf);
+ *nonsiglen = *sig - buf;
}
/* See grab_values */
static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
{
int i;
- const char *subpos = NULL, *bodypos = NULL;
+ const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
+ unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
for (i = 0; i < used_atom_cnt; i++) {
const char *name = used_atom[i];
@@ -500,17 +539,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
name++;
if (strcmp(name, "subject") &&
strcmp(name, "body") &&
- strcmp(name, "contents"))
+ strcmp(name, "contents") &&
+ strcmp(name, "contents:subject") &&
+ strcmp(name, "contents:body") &&
+ strcmp(name, "contents:signature"))
continue;
if (!subpos)
- find_subpos(buf, sz, &subpos, &bodypos);
- if (!subpos)
- return;
+ find_subpos(buf, sz,
+ &subpos, &sublen,
+ &bodypos, &bodylen, &nonsiglen,
+ &sigpos, &siglen);
if (!strcmp(name, "subject"))
- v->s = copy_line(subpos);
+ v->s = copy_subject(subpos, sublen);
+ else if (!strcmp(name, "contents:subject"))
+ v->s = copy_subject(subpos, sublen);
else if (!strcmp(name, "body"))
- v->s = xstrdup(bodypos);
+ v->s = xmemdupz(bodypos, bodylen);
+ else if (!strcmp(name, "contents:body"))
+ v->s = xmemdupz(bodypos, nonsiglen);
+ else if (!strcmp(name, "contents:signature"))
+ v->s = xmemdupz(sigpos, siglen);
else if (!strcmp(name, "contents"))
v->s = xstrdup(subpos);
}
@@ -581,15 +630,13 @@ static void populate_value(struct refinfo *ref)
unsigned long size;
const unsigned char *tagged;
- ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
+ ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
unsigned char unused1[20];
- const char *symref;
- symref = resolve_ref(ref->refname, unused1, 1, NULL);
- if (symref)
- ref->symref = xstrdup(symref);
- else
+ ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
+ unused1, NULL);
+ if (!ref->symref)
ref->symref = "";
}
@@ -600,20 +647,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);
@@ -621,8 +668,14 @@ 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] = "";
+
+ if (color_parse(name + 6, color) < 0)
+ die(_("unable to parse format"));
+ 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");
@@ -635,18 +688,70 @@ 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", RESOLVE_REF_READING,
+ sha1, 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];
+
+ if (stat_tracking_info(branch, &num_ours,
+ &num_theirs) != 1)
+ continue;
+
+ 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);
+
+ if (stat_tracking_info(branch, &num_ours,
+ &num_theirs) != 1)
+ continue;
+
+ 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);
}
@@ -741,6 +846,11 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f
struct refinfo *ref;
int cnt;
+ if (flag & REF_BAD_NAME) {
+ warning("ignoring ref with broken name %s", refname);
+ return 0;
+ }
+
if (*cb->grab_pattern) {
const char **pattern;
int namelen = strlen(refname);
@@ -754,7 +864,7 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f
refname[plen] == '/' ||
p[plen-1] == '/'))
break;
- if (!fnmatch(p, refname, FNM_PATHNAME))
+ if (!wildmatch(p, refname, WM_PATHNAME, NULL))
break;
}
if (!*pattern)
@@ -772,8 +882,7 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f
ref->flag = flag;
cnt = cb->grab_cnt;
- cb->grab_array = xrealloc(cb->grab_array,
- sizeof(*cb->grab_array) * (cnt + 1));
+ REALLOC_ARRAY(cb->grab_array, cnt + 1);
cb->grab_array[cnt++] = ref;
cb->grab_cnt = cnt;
return 0;
@@ -824,27 +933,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)
@@ -890,15 +1002,27 @@ 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] = "";
+
+ if (color_parse("reset", color) < 0)
+ die("BUG: couldn't parse 'reset' as a color");
+ resetv.s = color;
+ print_value(&resetv, quote_style);
+ }
putchar('\n');
}
@@ -922,7 +1046,9 @@ static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
if (!arg) /* should --no-sort void the list ? */
return -1;
- *sort_tail = s = xcalloc(1, sizeof(*s));
+ s = xcalloc(1, sizeof(*s));
+ s->next = *sort_tail;
+ *sort_tail = s;
if (*arg == '-') {
s->reverse = 1;
@@ -934,7 +1060,7 @@ static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
}
static char const * const for_each_ref_usage[] = {
- "git for-each-ref [options] [<pattern>]",
+ N_("git for-each-ref [options] [<pattern>]"),
NULL
};
@@ -949,19 +1075,19 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
struct option opts[] = {
OPT_BIT('s', "shell", &quote_style,
- "quote placeholders suitably for shells", QUOTE_SHELL),
+ N_("quote placeholders suitably for shells"), QUOTE_SHELL),
OPT_BIT('p', "perl", &quote_style,
- "quote placeholders suitably for perl", QUOTE_PERL),
+ N_("quote placeholders suitably for perl"), QUOTE_PERL),
OPT_BIT(0 , "python", &quote_style,
- "quote placeholders suitably for python", QUOTE_PYTHON),
+ N_("quote placeholders suitably for python"), QUOTE_PYTHON),
OPT_BIT(0 , "tcl", &quote_style,
- "quote placeholders suitably for tcl", QUOTE_TCL),
+ N_("quote placeholders suitably for Tcl"), QUOTE_TCL),
OPT_GROUP(""),
- OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
- OPT_STRING( 0 , "format", &format, "format", "format to use for the output"),
- OPT_CALLBACK(0 , "sort", sort_tail, "key",
- "field name to sort on", &opt_parse_sort),
+ OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")),
+ OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
+ OPT_CALLBACK(0 , "sort", sort_tail, N_("key"),
+ N_("field name to sort on"), &opt_parse_sort),
OPT_END(),
};
@@ -979,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 5ae0366..a27515a 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -11,9 +11,12 @@
#include "fsck.h"
#include "parse-options.h"
#include "dir.h"
+#include "progress.h"
+#include "streaming.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
+#define HAS_OBJ 0x0004
static int show_root;
static int show_tags;
@@ -27,8 +30,11 @@ static const char *head_points_at;
static int errors_found;
static int write_lost_and_found;
static int verbose;
+static int show_progress = -1;
+static int show_dangling = 1;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
+#define ERROR_PACK 04
#ifdef NO_D_INO_IN_DIRENT
#define SORT_DIRENT 0
@@ -96,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));
@@ -107,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;
}
@@ -122,22 +128,23 @@ 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;
}
static int traverse_reachable(void)
{
+ struct progress *progress = NULL;
+ unsigned int nr = 0;
int result = 0;
+ if (show_progress)
+ progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2);
while (pending.nr) {
struct object_array_entry *entry;
struct object *obj;
@@ -145,7 +152,9 @@ static int traverse_reachable(void)
entry = pending.objects + --pending.nr;
obj = entry->item;
result |= traverse_one_object(obj);
+ display_progress(progress, ++nr);
}
+ stop_progress(&progress);
return !!result;
}
@@ -167,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));
@@ -212,8 +221,9 @@ static void check_unreachable_object(struct object *obj)
* start looking at, for example.
*/
if (!obj->used) {
- printf("dangling %s %s\n", typename(obj->type),
- sha1_to_hex(obj->sha1));
+ if (show_dangling)
+ printf("dangling %s %s\n", typename(obj->type),
+ sha1_to_hex(obj->sha1));
if (write_lost_and_found) {
char *filename = git_path("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
@@ -227,16 +237,8 @@ static void check_unreachable_object(struct object *obj)
if (!(f = fopen(filename, "w")))
die_errno("Could not open '%s'", filename);
if (obj->type == OBJ_BLOB) {
- enum object_type type;
- unsigned long size;
- char *buf = read_sha1_file(obj->sha1,
- &type, &size);
- if (buf) {
- if (fwrite(buf, size, 1, f) != 1)
- die_errno("Could not write '%s'",
- filename);
- free(buf);
- }
+ if (stream_blob_to_fd(fileno(f), obj->sha1, NULL, 1))
+ die_errno("Could not write '%s'", filename);
} else
fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
if (fclose(f))
@@ -284,14 +286,8 @@ static void check_connectivity(void)
}
}
-static int fsck_sha1(const unsigned char *sha1)
+static int fsck_obj(struct object *obj)
{
- struct object *obj = parse_object(sha1);
- if (!obj) {
- errors_found |= ERROR_OBJECT;
- return error("%s: object corrupt or missing",
- sha1_to_hex(sha1));
- }
if (obj->flags & SEEN)
return 0;
obj->flags |= SEEN;
@@ -302,21 +298,19 @@ static int fsck_sha1(const unsigned char *sha1)
if (fsck_walk(obj, mark_used, NULL))
objerror(obj, "broken links");
- if (fsck_object(obj, check_strict, fsck_error_func))
+ if (fsck_object(obj, NULL, 0, check_strict, fsck_error_func))
return -1;
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) {
struct commit *commit = (struct commit *) obj;
- free(commit->buffer);
- commit->buffer = NULL;
+ free_commit_buffer(commit);
if (!commit->parents && show_root)
printf("root %s\n", sha1_to_hex(commit->object.sha1));
@@ -334,6 +328,31 @@ static int fsck_sha1(const unsigned char *sha1)
return 0;
}
+static int fsck_sha1(const unsigned char *sha1)
+{
+ struct object *obj = parse_object(sha1);
+ if (!obj) {
+ errors_found |= ERROR_OBJECT;
+ return error("%s: object corrupt or missing",
+ sha1_to_hex(sha1));
+ }
+ obj->flags |= HAS_OBJ;
+ return fsck_obj(obj);
+}
+
+static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
+ unsigned long size, void *buffer, int *eaten)
+{
+ struct object *obj;
+ obj = parse_object_buffer(sha1, type, size, buffer, eaten);
+ if (!obj) {
+ errors_found |= ERROR_OBJECT;
+ return error("%s: object corrupt or missing", sha1_to_hex(sha1));
+ }
+ obj->flags = HAS_OBJ;
+ return fsck_obj(obj);
+}
+
/*
* This is the sorting chunk size: make it reasonably
* big so that we can sort well..
@@ -369,7 +388,8 @@ static void fsck_sha1_list(void)
unsigned char *sha1 = entry->sha1;
sha1_list.entry[i] = NULL;
- fsck_sha1(sha1);
+ if (fsck_sha1(sha1))
+ errors_found |= ERROR_OBJECT;
free(entry);
}
sha1_list.nr = 0;
@@ -422,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);
}
@@ -462,11 +482,6 @@ static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, in
return 0;
}
-static int is_branch(const char *refname)
-{
- return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
-}
-
static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
struct object *obj;
@@ -474,6 +489,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
obj = parse_object(sha1);
if (!obj) {
error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+ errors_found |= ERROR_REACHABLE;
/* We'll continue with the rest despite the error.. */
return 0;
}
@@ -490,7 +506,7 @@ static void get_default_heads(void)
{
if (head_points_at && !is_null_sha1(head_sha1))
fsck_handle_ref("HEAD", head_sha1, 0, NULL);
- for_each_ref(fsck_handle_ref, NULL);
+ for_each_rawref(fsck_handle_ref, NULL);
if (include_reflogs)
for_each_reflog(fsck_handle_reflog, NULL);
@@ -515,15 +531,20 @@ static void get_default_heads(void)
static void fsck_object_dir(const char *path)
{
int i;
+ struct progress *progress = NULL;
if (verbose)
fprintf(stderr, "Checking object directory\n");
+ if (show_progress)
+ progress = start_progress(_("Checking object directories"), 256);
for (i = 0; i < 256; i++) {
static char dir[4096];
sprintf(dir, "%s/%02x", path, i);
fsck_dir(i, dir);
+ display_progress(progress, i+1);
}
+ stop_progress(&progress);
fsck_sha1_list();
}
@@ -535,13 +556,13 @@ static int fsck_head_link(void)
if (verbose)
fprintf(stderr, "Checking HEAD link\n");
- head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag);
+ head_points_at = resolve_ref_unsafe("HEAD", 0, head_sha1, &flag);
if (!head_points_at)
return error("Invalid HEAD");
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)) {
@@ -579,21 +600,23 @@ static int fsck_cache_tree(struct cache_tree *it)
}
static char const * const fsck_usage[] = {
- "git fsck [options] [<object>...]",
+ N_("git fsck [options] [<object>...]"),
NULL
};
static struct option fsck_opts[] = {
- OPT__VERBOSE(&verbose, "be verbose"),
- OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
- OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
- OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
- OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"),
- OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"),
- OPT_BOOLEAN(0, "full", &check_full, "also consider packs and alternate objects"),
- OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
- OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
- "write dangling objects in .git/lost-found"),
+ OPT__VERBOSE(&verbose, N_("be verbose")),
+ OPT_BOOL(0, "unreachable", &show_unreachable, N_("show unreachable objects")),
+ OPT_BOOL(0, "dangling", &show_dangling, N_("show dangling objects")),
+ 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(),
};
@@ -603,9 +626,15 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
struct alternate_object_database *alt;
errors_found = 0;
- read_replace_refs = 0;
+ check_replace_refs = 0;
argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
+
+ if (show_progress == -1)
+ show_progress = isatty(2);
+ if (verbose)
+ show_progress = 0;
+
if (write_lost_and_found) {
check_full = 1;
include_reflogs = 0;
@@ -625,20 +654,28 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
if (check_full) {
struct packed_git *p;
+ uint32_t total = 0, count = 0;
+ struct progress *progress = NULL;
prepare_packed_git();
- for (p = packed_git; p; p = p->next)
- /* verify gives error messages itself */
- verify_pack(p);
+ if (show_progress) {
+ for (p = packed_git; p; p = p->next) {
+ if (open_pack_index(p))
+ continue;
+ total += p->num_objects;
+ }
+
+ progress = start_progress(_("Checking objects"), total);
+ }
for (p = packed_git; p; p = p->next) {
- uint32_t j, num;
- if (open_pack_index(p))
- continue;
- num = p->num_objects;
- for (j = 0; j < num; j++)
- fsck_sha1(nth_packed_object_sha1(p, j));
+ /* verify gives error messages itself */
+ if (verify_pack(p, fsck_obj_buffer,
+ progress, count))
+ errors_found |= ERROR_PACK;
+ count += p->num_objects;
}
+ stop_progress(&progress);
}
heads = 0;
diff --git a/builtin/gc.c b/builtin/gc.c
index 0498094..005adbe 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -11,73 +11,77 @@
*/
#include "builtin.h"
-#include "cache.h"
+#include "lockfile.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"
static const char * const builtin_gc_usage[] = {
- "git gc [options]",
+ N_("git gc [options]"),
NULL
};
static int pack_refs = 1;
+static int prune_reflogs = 1;
+static int aggressive_depth = 250;
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";
-#define MAX_ADD 10
-static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
-static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
-static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
-static const char *argv_rerere[] = {"rerere", "gc", NULL};
+static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
+static struct argv_array reflog = ARGV_ARRAY_INIT;
+static struct argv_array repack = ARGV_ARRAY_INIT;
+static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array rerere = ARGV_ARRAY_INIT;
-static int gc_config(const char *var, const char *value, void *cb)
+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 void gc_config(void)
{
- if (!strcmp(var, "gc.packrefs")) {
+ const char *value;
+
+ if (!git_config_get_value("gc.packrefs", &value)) {
if (value && !strcmp(value, "notbare"))
pack_refs = -1;
else
- pack_refs = git_config_bool(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.aggressivewindow")) {
- aggressive_window = git_config_int(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.auto")) {
- gc_auto_threshold = git_config_int(var, value);
- return 0;
+ pack_refs = git_config_bool("gc.packrefs", value);
}
- if (!strcmp(var, "gc.autopacklimit")) {
- gc_auto_pack_limit = git_config_int(var, value);
- return 0;
- }
- if (!strcmp(var, "gc.pruneexpire")) {
- if (value && strcmp(value, "now")) {
+
+ git_config_get_int("gc.aggressivewindow", &aggressive_window);
+ git_config_get_int("gc.aggressivedepth", &aggressive_depth);
+ git_config_get_int("gc.auto", &gc_auto_threshold);
+ git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
+ git_config_get_bool("gc.autodetach", &detach_auto);
+
+ if (!git_config_get_string_const("gc.pruneexpire", &prune_expire)) {
+ if (strcmp(prune_expire, "now")) {
unsigned long now = approxidate("now");
- if (approxidate(value) >= now)
- return error(_("Invalid %s: '%s'"), var, value);
+ if (approxidate(prune_expire) >= now) {
+ git_die_config("gc.pruneexpire", _("Invalid gc.pruneexpire: '%s'"),
+ prune_expire);
+ }
}
- return git_config_string(&prune_expire, var, value);
}
- return git_default_config(var, value, cb);
-}
-
-static void append_option(const char **cmd, const char *opt, int max_length)
-{
- int i;
-
- for (i = 0; cmd[i]; i++)
- ;
-
- if (i + 2 >= max_length)
- die(_("Too many options specified"));
- cmd[i++] = opt;
- cmd[i] = NULL;
+ git_config(git_default_config, NULL);
}
static int too_many_loose_objects(void)
@@ -144,6 +148,17 @@ static int too_many_packs(void)
return gc_auto_pack_limit <= cnt;
}
+static void add_repack_all_option(void)
+{
+ if (prune_expire && !strcmp(prune_expire, "now"))
+ argv_array_push(&repack, "-a");
+ else {
+ argv_array_push(&repack, "-A");
+ if (prune_expire)
+ argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire);
+ }
+}
+
static int need_to_gc(void)
{
/*
@@ -160,39 +175,122 @@ static int need_to_gc(void)
* there is no need.
*/
if (too_many_packs())
- append_option(argv_repack,
- prune_expire && !strcmp(prune_expire, "now") ?
- "-a" : "-A",
- MAX_ADD);
+ add_repack_all_option();
else if (!too_many_loose_objects())
return 0;
- if (run_hook(NULL, "pre-auto-gc", NULL))
+ if (run_hook_le(NULL, "pre-auto-gc", NULL))
return 0;
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;
+}
+
+static int gc_before_repack(void)
+{
+ if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, pack_refs_cmd.argv[0]);
+
+ if (prune_reflogs && run_command_v_opt(reflog.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, reflog.argv[0]);
+
+ pack_refs = 0;
+ prune_reflogs = 0;
+ return 0;
+}
+
int cmd_gc(int argc, const char **argv, const char *prefix)
{
int aggressive = 0;
int auto_gc = 0;
int quiet = 0;
- char buf[80];
+ int force = 0;
+ const char *name;
+ pid_t pid;
struct option builtin_gc_options[] = {
- OPT__QUIET(&quiet, "suppress progress reporting"),
- { OPTION_STRING, 0, "prune", &prune_expire, "date",
- "prune unreferenced objects",
+ 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, "be more thorough (increased runtime)"),
- OPT_BOOLEAN(0, "auto", &auto_gc, "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()
};
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_gc_usage, builtin_gc_options);
- git_config(gc_config, NULL);
+ argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
+ argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
+ argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
+ argv_array_pushl(&prune, "prune", "--expire", NULL );
+ argv_array_pushl(&rerere, "rerere", "gc", NULL);
+
+ gc_config();
if (pack_refs < 0)
pack_refs = !is_bare_repository();
@@ -203,15 +301,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_gc_usage, builtin_gc_options);
if (aggressive) {
- append_option(argv_repack, "-f", MAX_ADD);
- append_option(argv_repack, "--depth=250", MAX_ADD);
- if (aggressive_window > 0) {
- sprintf(buf, "--window=%d", aggressive_window);
- append_option(argv_repack, buf, MAX_ADD);
- }
+ argv_array_push(&repack, "-f");
+ if (aggressive_depth > 0)
+ argv_array_pushf(&repack, "--depth=%d", aggressive_depth);
+ if (aggressive_window > 0)
+ argv_array_pushf(&repack, "--window=%d", aggressive_window);
}
if (quiet)
- append_option(argv_repack, "-q", MAX_ADD);
+ argv_array_push(&repack, "-q");
if (auto_gc) {
/*
@@ -219,36 +316,49 @@ 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.\n"));
- else
- 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) {
+ if (gc_before_repack())
+ return -1;
+ /*
+ * failure to daemonize is ok, we'll continue
+ * in foreground
+ */
+ daemonize();
+ }
} else
- append_option(argv_repack,
- prune_expire && !strcmp(prune_expire, "now")
- ? "-a" : "-A",
- MAX_ADD);
-
- if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_pack_refs[0]);
+ 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 (run_command_v_opt(argv_reflog, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_reflog[0]);
+ if (gc_before_repack())
+ return -1;
- if (run_command_v_opt(argv_repack, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_repack[0]);
+ if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, repack.argv[0]);
if (prune_expire) {
- argv_prune[2] = prune_expire;
- if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_prune[0]);
+ argv_array_push(&prune, prune_expire);
+ if (quiet)
+ argv_array_push(&prune, "--no-progress");
+ if (run_command_v_opt(prune.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, prune.argv[0]);
}
- if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
- return error(FAILED_RUN, argv_rerere[0]);
+ if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, rerere.argv[0]);
if (auto_gc && too_many_loose_objects())
warning(_("There are too many unreachable loose objects; "
diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c
new file mode 100644
index 0000000..6f4147a
--- /dev/null
+++ b/builtin/get-tar-commit-id.c
@@ -0,0 +1,41 @@
+/*
+ * 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;
+ const char *comment;
+ 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 (!skip_prefix(content, "52 comment=", &comment))
+ return 1;
+
+ n = write_in_full(1, comment, 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 1c359c2..fe7b9fd 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -17,10 +17,10 @@
#include "grep.h"
#include "quote.h"
#include "dir.h"
-#include "thread-utils.h"
+#include "pathspec.h"
static char const * const grep_usage[] = {
- "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
+ N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
NULL
};
@@ -30,25 +30,12 @@ static int use_threads = 1;
#define THREADS 8
static pthread_t threads[THREADS];
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
- const char *name);
-static void *load_file(const char *filename, size_t *sz);
-
-enum work_type {WORK_SHA1, WORK_FILE};
-
/* We use one producer thread and THREADS consumer
* threads. The producer adds struct work_items to 'todo' and the
* consumers pick work items from the same array.
*/
struct work_item {
- enum work_type type;
- char *name;
-
- /* if type == WORK_SHA1, then 'identifier' is a SHA1,
- * otherwise type == WORK_FILE, and 'identifier' is a NUL
- * terminated filename.
- */
- void *identifier;
+ struct grep_source source;
char done;
struct strbuf out;
};
@@ -74,13 +61,17 @@ static int all_work_added;
/* This lock protects all the variables above. */
static pthread_mutex_t grep_mutex;
-/* Used to serialize calls to read_sha1_file. */
-static pthread_mutex_t read_sha1_mutex;
+static inline void grep_lock(void)
+{
+ if (use_threads)
+ pthread_mutex_lock(&grep_mutex);
+}
-#define grep_lock() pthread_mutex_lock(&grep_mutex)
-#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
-#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
-#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
+static inline void grep_unlock(void)
+{
+ if (use_threads)
+ pthread_mutex_unlock(&grep_mutex);
+}
/* Signalled when a new work_item is added to todo. */
static pthread_cond_t cond_add;
@@ -95,7 +86,8 @@ static pthread_cond_t cond_result;
static int skip_first_line;
-static void add_work(enum work_type type, char *name, void *id)
+static void add_work(struct grep_opt *opt, enum grep_source_type type,
+ const char *name, const char *path, const void *id)
{
grep_lock();
@@ -103,9 +95,9 @@ static void add_work(enum work_type type, char *name, void *id)
pthread_cond_wait(&cond_write, &grep_mutex);
}
- todo[todo_end].type = type;
- todo[todo_end].name = name;
- todo[todo_end].identifier = id;
+ grep_source_init(&todo[todo_end].source, type, name, path, id);
+ if (opt->binary != GREP_BINARY_TEXT)
+ grep_source_load_driver(&todo[todo_end].source);
todo[todo_end].done = 0;
strbuf_reset(&todo[todo_end].out);
todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
@@ -133,21 +125,6 @@ static struct work_item *get_work(void)
return ret;
}
-static void grep_sha1_async(struct grep_opt *opt, char *name,
- const unsigned char *sha1)
-{
- unsigned char *s;
- s = xmalloc(20);
- memcpy(s, sha1, 20);
- add_work(WORK_SHA1, name, s);
-}
-
-static void grep_file_async(struct grep_opt *opt, char *name,
- const char *filename)
-{
- add_work(WORK_FILE, name, xstrdup(filename));
-}
-
static void work_done(struct work_item *w)
{
int old_done;
@@ -174,8 +151,7 @@ static void work_done(struct work_item *w)
write_or_die(1, p, len);
}
- free(w->name);
- free(w->identifier);
+ grep_source_clear(&w->source);
}
if (old_done != todo_done)
@@ -198,25 +174,8 @@ static void *run(void *arg)
break;
opt->output_priv = w;
- if (w->type == WORK_SHA1) {
- unsigned long sz;
- void* data = load_sha1(w->identifier, &sz, w->name);
-
- if (data) {
- hit |= grep_buffer(opt, w->name, data, sz);
- free(data);
- }
- } else if (w->type == WORK_FILE) {
- size_t sz;
- void* data = load_file(w->identifier, &sz);
- if (data) {
- hit |= grep_buffer(opt, w->name, data, sz);
- free(data);
- }
- } else {
- assert(0);
- }
-
+ hit |= grep_source(opt, &w->source);
+ grep_source_clear_data(&w->source);
work_done(w);
}
free_grep_patterns(arg);
@@ -236,10 +195,12 @@ static void start_threads(struct grep_opt *opt)
int i;
pthread_mutex_init(&grep_mutex, NULL);
- pthread_mutex_init(&read_sha1_mutex, NULL);
+ pthread_mutex_init(&grep_read_mutex, NULL);
+ pthread_mutex_init(&grep_attr_mutex, NULL);
pthread_cond_init(&cond_add, NULL);
pthread_cond_init(&cond_write, NULL);
pthread_cond_init(&cond_result, NULL);
+ grep_use_locks = 1;
for (i = 0; i < ARRAY_SIZE(todo); i++) {
strbuf_init(&todo[i].out, 0);
@@ -249,6 +210,7 @@ static void start_threads(struct grep_opt *opt)
int err;
struct grep_opt *o = grep_opt_dup(opt);
o->output = strbuf_out;
+ o->debug = 0;
compile_grep_patterns(o);
err = pthread_create(&threads[i], NULL, run, o);
@@ -283,16 +245,16 @@ static int wait_all(void)
}
pthread_mutex_destroy(&grep_mutex);
- pthread_mutex_destroy(&read_sha1_mutex);
+ pthread_mutex_destroy(&grep_read_mutex);
+ pthread_mutex_destroy(&grep_attr_mutex);
pthread_cond_destroy(&cond_add);
pthread_cond_destroy(&cond_write);
pthread_cond_destroy(&cond_result);
+ grep_use_locks = 0;
return hit;
}
#else /* !NO_PTHREADS */
-#define read_sha1_lock()
-#define read_sha1_unlock()
static int wait_all(void)
{
@@ -300,177 +262,82 @@ static int wait_all(void)
}
#endif
-static int grep_config(const char *var, const char *value, void *cb)
+static int grep_cmd_config(const char *var, const char *value, void *cb)
{
- struct grep_opt *opt = cb;
- char *color = NULL;
-
- switch (userdiff_config(var, value)) {
- case 0: break;
- case -1: return -1;
- default: return 0;
- }
-
- if (!strcmp(var, "grep.extendedregexp")) {
- if (git_config_bool(var, value))
- opt->regflags |= REG_EXTENDED;
- else
- opt->regflags &= ~REG_EXTENDED;
- return 0;
- }
-
- if (!strcmp(var, "grep.linenumber")) {
- opt->linenum = git_config_bool(var, value);
- return 0;
- }
-
- if (!strcmp(var, "color.grep"))
- opt->color = git_config_colorbool(var, value);
- else if (!strcmp(var, "color.grep.context"))
- color = opt->color_context;
- else if (!strcmp(var, "color.grep.filename"))
- color = opt->color_filename;
- else if (!strcmp(var, "color.grep.function"))
- color = opt->color_function;
- else if (!strcmp(var, "color.grep.linenumber"))
- color = opt->color_lineno;
- else if (!strcmp(var, "color.grep.match"))
- color = opt->color_match;
- else if (!strcmp(var, "color.grep.selected"))
- color = opt->color_selected;
- else if (!strcmp(var, "color.grep.separator"))
- color = opt->color_sep;
- else
- return git_color_default_config(var, value, cb);
- if (color) {
- if (!value)
- return config_error_nonbool(var);
- color_parse(value, var, color);
- }
- return 0;
+ int st = grep_config(var, value, cb);
+ if (git_color_default_config(var, value, cb) < 0)
+ st = -1;
+ return st;
}
static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
{
void *data;
- if (use_threads) {
- read_sha1_lock();
- data = read_sha1_file(sha1, type, size);
- read_sha1_unlock();
- } else {
- data = read_sha1_file(sha1, type, size);
- }
- return data;
-}
-
-static void *load_sha1(const unsigned char *sha1, unsigned long *size,
- const char *name)
-{
- enum object_type type;
- void *data = lock_and_read_sha1_file(sha1, &type, size);
-
- if (!data)
- error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1));
-
+ grep_read_lock();
+ data = read_sha1_file(sha1, type, size);
+ grep_read_unlock();
return data;
}
static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
- const char *filename, int tree_name_len)
+ const char *filename, int tree_name_len,
+ const char *path)
{
struct strbuf pathbuf = STRBUF_INIT;
- char *name;
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);
}
- name = strbuf_detach(&pathbuf, NULL);
-
#ifndef NO_PTHREADS
if (use_threads) {
- grep_sha1_async(opt, name, sha1);
+ add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
+ strbuf_release(&pathbuf);
return 0;
} else
#endif
{
+ struct grep_source gs;
int hit;
- unsigned long sz;
- void *data = load_sha1(sha1, &sz, name);
- if (!data)
- hit = 0;
- else
- hit = grep_buffer(opt, name, data, sz);
-
- free(data);
- free(name);
- return hit;
- }
-}
-static void *load_file(const char *filename, size_t *sz)
-{
- struct stat st;
- char *data;
- int i;
+ grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
+ strbuf_release(&pathbuf);
+ hit = grep_source(opt, &gs);
- if (lstat(filename, &st) < 0) {
- err_ret:
- if (errno != ENOENT)
- error(_("'%s': %s"), filename, strerror(errno));
- return NULL;
- }
- if (!S_ISREG(st.st_mode))
- return NULL;
- *sz = xsize_t(st.st_size);
- i = open(filename, O_RDONLY);
- if (i < 0)
- goto err_ret;
- data = xmalloc(*sz + 1);
- if (st.st_size != read_in_full(i, data, *sz)) {
- error(_("'%s': short read %s"), filename, strerror(errno));
- close(i);
- free(data);
- return NULL;
+ grep_source_clear(&gs);
+ return hit;
}
- close(i);
- data[*sz] = 0;
- return data;
}
static int grep_file(struct grep_opt *opt, const char *filename)
{
struct strbuf buf = STRBUF_INIT;
- char *name;
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);
- name = strbuf_detach(&buf, NULL);
#ifndef NO_PTHREADS
if (use_threads) {
- grep_file_async(opt, name, filename);
+ add_work(opt, GREP_SOURCE_FILE, buf.buf, filename, filename);
+ strbuf_release(&buf);
return 0;
} else
#endif
{
+ struct grep_source gs;
int hit;
- size_t sz;
- void *data = load_file(filename, &sz);
- if (!data)
- hit = 0;
- else
- hit = grep_buffer(opt, name, data, sz);
- free(data);
- free(name);
+ grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename);
+ strbuf_release(&buf);
+ hit = grep_source(opt, &gs);
+
+ grep_source_clear(&gs);
return hit;
}
}
@@ -494,9 +361,7 @@ static void run_pager(struct grep_opt *opt, const char *prefix)
argv[i] = path_list->items[i].string;
argv[path_list->nr] = NULL;
- if (prefix && chdir(prefix))
- die(_("Failed to chdir: %s"), prefix);
- status = run_command_v_opt(argv, RUN_USING_SHELL);
+ status = run_command_v_opt_cd_env(argv, RUN_USING_SHELL, prefix, NULL);
if (status)
exit(status);
free(argv);
@@ -509,10 +374,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
@@ -522,7 +387,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
if (ce_stage(ce))
continue;
- hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
+ hit |= grep_sha1(opt, ce->sha1, ce->name, 0, ce->name);
}
else
hit |= grep_file(opt, ce->name);
@@ -540,27 +405,30 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
}
static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
- struct tree_desc *tree, struct strbuf *base, int tn_len)
+ struct tree_desc *tree, struct strbuf *base, int tn_len,
+ int check_attr)
{
- int hit = 0, match = 0;
+ int hit = 0;
+ enum interesting match = entry_not_interesting;
struct name_entry entry;
int old_baselen = base->len;
while (tree_entry(tree, &entry)) {
- int te_len = tree_entry_len(entry.path, entry.sha1);
+ int te_len = tree_entry_len(&entry);
- if (match != 2) {
+ if (match != all_entries_interesting) {
match = tree_entry_interesting(&entry, base, tn_len, pathspec);
- if (match < 0)
+ if (match == all_entries_not_interesting)
break;
- if (match == 0)
+ if (match == entry_not_interesting)
continue;
}
strbuf_add(base, entry.path, te_len);
if (S_ISREG(entry.mode)) {
- hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len);
+ hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len,
+ check_attr ? base->buf + tn_len : NULL);
}
else if (S_ISDIR(entry.mode)) {
enum object_type type;
@@ -575,7 +443,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
strbuf_addch(base, '/');
init_tree_desc(&sub, data, size);
- hit |= grep_tree(opt, pathspec, &sub, base, tn_len);
+ hit |= grep_tree(opt, pathspec, &sub, base, tn_len,
+ check_attr);
free(data);
}
strbuf_setlen(base, old_baselen);
@@ -587,10 +456,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, const char *path)
{
if (obj->type == OBJ_BLOB)
- return grep_sha1(opt, obj->sha1, name, 0);
+ return grep_sha1(opt, obj->sha1, name, 0, path);
if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
struct tree_desc tree;
void *data;
@@ -598,8 +467,11 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
struct strbuf base;
int hit, len;
+ grep_read_lock();
data = read_object_with_reference(obj->sha1, tree_type,
&size, NULL);
+ grep_read_unlock();
+
if (!data)
die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
@@ -610,7 +482,8 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
strbuf_addch(&base, ':');
}
init_tree_desc(&tree, data, size);
- hit = grep_tree(opt, pathspec, &tree, &base, base.len);
+ hit = grep_tree(opt, pathspec, &tree, &base, base.len,
+ obj->type == OBJ_COMMIT);
strbuf_release(&base);
free(data);
return hit;
@@ -628,7 +501,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].path)) {
hit = 1;
if (opt->status_only)
break;
@@ -637,19 +510,19 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
return hit;
}
-static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
+static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
+ int exc_std)
{
struct dir_struct dir;
int i, hit = 0;
memset(&dir, 0, sizeof(dir));
- setup_standard_excludes(&dir);
+ 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)
@@ -690,15 +563,12 @@ static int file_callback(const struct option *opt, const char *arg, int unset)
if (!patterns)
die_errno(_("cannot open '%s'"), arg);
while (strbuf_getline(&sb, patterns, '\n') == 0) {
- char *s;
- size_t len;
-
/* ignore empty line like grep does */
if (sb.len == 0)
continue;
- s = strbuf_detach(&sb, &len);
- append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN);
+ append_grep_pat(grep_opt, sb.buf, sb.len, arg, ++lno,
+ GREP_PATTERN);
}
if (!from_stdin)
fclose(patterns);
@@ -750,105 +620,103 @@ static int help_callback(const struct option *opt, const char *arg, int unset)
int cmd_grep(int argc, const char **argv, const char *prefix)
{
int hit = 0;
- int cached = 0;
+ int cached = 0, untracked = 0, opt_exclude = -1;
int seen_dashdash = 0;
int external_grep_allowed__ignored;
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;
int dummy;
int use_index = 1;
- enum {
- pattern_type_unspecified = 0,
- pattern_type_bre,
- pattern_type_ere,
- pattern_type_fixed,
- pattern_type_pcre,
- };
- int pattern_type = pattern_type_unspecified;
+ int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
struct option options[] = {
- OPT_BOOLEAN(0, "cached", &cached,
- "search in index instead of in the work tree"),
- OPT_BOOLEAN(0, "index", &use_index,
- "--no-index finds in contents not managed by git"),
+ 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_BOOL(0, "untracked", &untracked,
+ N_("search in both tracked and untracked files")),
+ OPT_SET_INT(0, "exclude-standard", &opt_exclude,
+ N_("ignore files specified via '.gitignore'"), 1),
OPT_GROUP(""),
- OPT_BOOLEAN('v', "invert-match", &opt.invert,
- "show non-matching lines"),
- OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
- "case insensitive matching"),
- OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
- "match patterns only at word boundaries"),
+ OPT_BOOL('v', "invert-match", &opt.invert,
+ N_("show non-matching lines")),
+ OPT_BOOL('i', "ignore-case", &opt.ignore_case,
+ N_("case insensitive matching")),
+ OPT_BOOL('w', "word-regexp", &opt.word_regexp,
+ N_("match patterns only at word boundaries")),
OPT_SET_INT('a', "text", &opt.binary,
- "process binary files as text", GREP_BINARY_TEXT),
+ N_("process binary files as text"), GREP_BINARY_TEXT),
OPT_SET_INT('I', NULL, &opt.binary,
- "don't match patterns in binary files",
+ N_("don't match patterns in binary files"),
GREP_BINARY_NOMATCH),
- { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth",
- "descend at most <depth> levels", PARSE_OPT_NONEG,
+ 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 },
OPT_GROUP(""),
- OPT_SET_INT('E', "extended-regexp", &pattern_type,
- "use extended POSIX regular expressions",
- pattern_type_ere),
- OPT_SET_INT('G', "basic-regexp", &pattern_type,
- "use basic POSIX regular expressions (default)",
- pattern_type_bre),
- OPT_SET_INT('F', "fixed-strings", &pattern_type,
- "interpret patterns as fixed strings",
- pattern_type_fixed),
- OPT_SET_INT('P', "perl-regexp", &pattern_type,
- "use Perl-compatible regular expressions",
- pattern_type_pcre),
+ OPT_SET_INT('E', "extended-regexp", &pattern_type_arg,
+ N_("use extended POSIX regular expressions"),
+ GREP_PATTERN_TYPE_ERE),
+ OPT_SET_INT('G', "basic-regexp", &pattern_type_arg,
+ N_("use basic POSIX regular expressions (default)"),
+ GREP_PATTERN_TYPE_BRE),
+ OPT_SET_INT('F', "fixed-strings", &pattern_type_arg,
+ N_("interpret patterns as fixed strings"),
+ GREP_PATTERN_TYPE_FIXED),
+ OPT_SET_INT('P', "perl-regexp", &pattern_type_arg,
+ N_("use Perl-compatible regular expressions"),
+ GREP_PATTERN_TYPE_PCRE),
OPT_GROUP(""),
- OPT_BOOLEAN('n', "line-number", &opt.linenum, "show line numbers"),
- OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1),
- OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1),
+ 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,
- "show filenames relative to top directory", 1),
- OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
- "show only filenames instead of matching lines"),
- OPT_BOOLEAN(0, "name-only", &opt.name_only,
- "synonym for --files-with-matches"),
- OPT_BOOLEAN('L', "files-without-match",
+ N_("show filenames relative to top directory"), 1),
+ OPT_BOOL('l', "files-with-matches", &opt.name_only,
+ N_("show only filenames instead of matching lines")),
+ OPT_BOOL(0, "name-only", &opt.name_only,
+ N_("synonym for --files-with-matches")),
+ OPT_BOOL('L', "files-without-match",
&opt.unmatch_name_only,
- "show only the names of files without match"),
- OPT_BOOLEAN('z', "null", &opt.null_following_name,
- "print NUL after filenames"),
- OPT_BOOLEAN('c', "count", &opt.count,
- "show the number of matches instead of matching lines"),
- OPT__COLOR(&opt.color, "highlight matches"),
- OPT_BOOLEAN(0, "break", &opt.file_break,
- "print empty line between matches from different files"),
- OPT_BOOLEAN(0, "heading", &opt.heading,
- "show filename only once above matches from same file"),
+ N_("show only the names of files without match")),
+ OPT_BOOL('z', "null", &opt.null_following_name,
+ N_("print NUL after filenames")),
+ OPT_BOOL('c', "count", &opt.count,
+ N_("show the number of matches instead of matching lines")),
+ OPT__COLOR(&opt.color, N_("highlight matches")),
+ OPT_BOOL(0, "break", &opt.file_break,
+ N_("print empty line between matches from different files")),
+ OPT_BOOL(0, "heading", &opt.heading,
+ N_("show filename only once above matches from same file")),
OPT_GROUP(""),
- OPT_CALLBACK('C', "context", &opt, "n",
- "show <n> context lines before and after matches",
+ OPT_CALLBACK('C', "context", &opt, N_("n"),
+ N_("show <n> context lines before and after matches"),
context_callback),
OPT_INTEGER('B', "before-context", &opt.pre_context,
- "show <n> context lines before matches"),
+ N_("show <n> context lines before matches")),
OPT_INTEGER('A', "after-context", &opt.post_context,
- "show <n> context lines after matches"),
- OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
+ 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,
- "show a line with the function name before matches"),
- OPT_BOOLEAN('W', "function-context", &opt.funcbody,
- "show the surrounding function"),
+ OPT_BOOL('p', "show-function", &opt.funcname,
+ N_("show a line with the function name before matches")),
+ OPT_BOOL('W', "function-context", &opt.funcbody,
+ N_("show the surrounding function")),
OPT_GROUP(""),
- OPT_CALLBACK('f', NULL, &opt, "file",
- "read patterns from file", file_callback),
- { OPTION_CALLBACK, 'e', NULL, &opt, "pattern",
- "match <pattern>", PARSE_OPT_NONEG, pattern_callback },
+ OPT_CALLBACK('f', NULL, &opt, N_("file"),
+ N_("read patterns from file"), file_callback),
+ { OPTION_CALLBACK, 'e', NULL, &opt, N_("pattern"),
+ N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback },
{ OPTION_CALLBACK, 0, "and", &opt, NULL,
- "combine patterns specified with -e",
+ 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, "",
@@ -858,16 +726,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
close_callback },
OPT__QUIET(&opt.status_only,
- "indicate hit with exit status without output"),
- OPT_BOOLEAN(0, "all-match", &opt.all_match,
- "show only matches from files that match all patterns"),
+ N_("indicate hit with exit status without output")),
+ OPT_BOOL(0, "all-match", &opt.all_match,
+ N_("show only matches from files that match all patterns")),
+ { OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
+ N_("show parse tree for grep expression"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1 },
OPT_GROUP(""),
{ OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
- "pager", "show matching files in the 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,
- "allow calling of grep(1) (ignored by this build)"),
- { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage",
+ 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()
};
@@ -879,25 +750,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(grep_usage, options);
- memset(&opt, 0, sizeof(opt));
- opt.prefix = prefix;
- opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
- opt.relative = 1;
- opt.pathname = 1;
- opt.pattern_tail = &opt.pattern_list;
- opt.header_tail = &opt.header_list;
- opt.regflags = REG_NEWLINE;
- opt.max_depth = -1;
-
- strcpy(opt.color_context, "");
- strcpy(opt.color_filename, "");
- strcpy(opt.color_function, "");
- strcpy(opt.color_lineno, "");
- strcpy(opt.color_match, GIT_COLOR_BOLD_RED);
- strcpy(opt.color_selected, "");
- strcpy(opt.color_sep, GIT_COLOR_CYAN);
- opt.color = -1;
- git_config(grep_config, &opt);
+ init_grep_defaults();
+ git_config(grep_cmd_config, NULL);
+ grep_init(&opt, prefix);
/*
* If there is no -- then the paths must exist in the working
@@ -913,28 +768,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_STOP_AT_NON_OPTION |
PARSE_OPT_NO_INTERNAL_HELP);
- switch (pattern_type) {
- case pattern_type_fixed:
- opt.fixed = 1;
- opt.pcre = 0;
- break;
- case pattern_type_bre:
- opt.fixed = 0;
- opt.pcre = 0;
- opt.regflags &= ~REG_EXTENDED;
- break;
- case pattern_type_ere:
- opt.fixed = 0;
- opt.pcre = 0;
- opt.regflags |= REG_EXTENDED;
- break;
- case pattern_type_pcre:
- opt.fixed = 0;
- opt.pcre = 1;
- break;
- default:
- break; /* nothing */
- }
+ grep_commit_pattern_type(pattern_type_arg, &opt);
if (use_index && !startup_info->have_repository)
/* die the same way as if we did it at the beginning */
@@ -975,32 +809,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!opt.fixed && opt.ignore_case)
opt.regflags |= REG_ICASE;
-#ifndef NO_PTHREADS
- if (online_cpus() == 1 || !grep_threads_ok(&opt))
- use_threads = 0;
-
- if (use_threads) {
- if (opt.pre_context || opt.post_context || opt.file_break ||
- opt.funcbody)
- skip_first_line = 1;
- start_threads(&opt);
- }
-#else
- use_threads = 0;
-#endif
-
compile_grep_patterns(&opt);
/* Check revs and then paths */
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)) {
- struct object *object = parse_object(sha1);
- if (!object)
- die(_("bad object %s"), arg);
- add_object_array(object, arg, &list);
+ 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_with_path(object, arg, &list, oc.mode, oc.path);
continue;
}
if (!strcmp(arg, "--")) {
@@ -1010,15 +831,34 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
break;
}
+#ifndef NO_PTHREADS
+ if (list.nr || cached || online_cpus() == 1)
+ use_threads = 0;
+#else
+ use_threads = 0;
+#endif
+
+#ifndef NO_PTHREADS
+ if (use_threads) {
+ if (!(opt.name_only || opt.unmatch_name_only || opt.count)
+ && (opt.pre_context || opt.post_context ||
+ opt.file_break || opt.funcbody))
+ skip_first_line = 1;
+ start_threads(&opt);
+ }
+#endif
+
/* The rest are paths */
if (!seen_dashdash) {
int j;
for (j = i; j < argc; j++)
- verify_filename(prefix, argv[j]);
+ 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;
@@ -1032,6 +872,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (len > 4 && is_dir_sep(pager[len - 5]))
pager += len - 4;
+ if (opt.ignore_case && !strcmp("less", pager))
+ string_list_append(&path_list, "-I");
+
if (!strcmp("less", pager) || !strcmp("vi", pager)) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "+/%s%s",
@@ -1042,16 +885,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
}
}
- if (!show_in_pager)
+ if (!show_in_pager && !opt.status_only)
setup_pager();
+ if (!use_index && (untracked || cached))
+ die(_("--cached or --untracked cannot be used with --no-index."));
- if (!use_index) {
- if (cached)
- die(_("--cached cannot be used with --no-index."));
+ if (!use_index || untracked) {
+ int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
if (list.nr)
- die(_("--no-index cannot be used with revs."));
- hit = grep_directory(&opt, &pathspec);
+ die(_("--no-index or --untracked cannot be used with revs."));
+ hit = grep_directory(&opt, &pathspec, use_exclude);
+ } else if (0 <= opt_exclude) {
+ die(_("--[no-]exclude-standard cannot be used for tracked contents."));
} else if (!list.nr) {
if (!cached)
setup_work_tree();
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index 33911fd..6158363 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -10,35 +10,55 @@
#include "parse-options.h"
#include "exec_cmd.h"
-static void hash_fd(int fd, const char *type, int write_object, const char *path)
+/*
+ * This is to create corrupt objects for debugging and as such it
+ * needs to bypass the data conversion performed by, and the type
+ * limitation imposed by, index_fd() and its callees.
+ */
+static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigned flags)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+
+ if (strbuf_read(&buf, fd, 4096) < 0)
+ ret = -1;
+ else if (flags & HASH_WRITE_OBJECT)
+ ret = write_sha1_file(buf.buf, buf.len, type, sha1);
+ else
+ ret = hash_sha1_file(buf.buf, buf.len, type, sha1);
+ strbuf_release(&buf);
+ return ret;
+}
+
+static void hash_fd(int fd, const char *type, const char *path, unsigned flags,
+ int literally)
{
struct stat st;
unsigned char sha1[20];
- unsigned flags = (HASH_FORMAT_CHECK |
- (write_object ? HASH_WRITE_OBJECT : 0));
if (fstat(fd, &st) < 0 ||
- index_fd(sha1, fd, &st, type_from_string(type), path, flags))
- die(write_object
+ (literally
+ ? hash_literally(sha1, fd, type, flags)
+ : index_fd(sha1, fd, &st, type_from_string(type), path, flags)))
+ die((flags & HASH_WRITE_OBJECT)
? "Unable to add %s to database"
: "Unable to hash %s", path);
printf("%s\n", sha1_to_hex(sha1));
maybe_flush_or_die(stdout, "hash to stdout");
}
-static void hash_object(const char *path, const char *type, int write_object,
- const char *vpath)
+static void hash_object(const char *path, const char *type, const char *vpath,
+ unsigned flags, int literally)
{
int fd;
fd = open(path, O_RDONLY);
if (fd < 0)
die_errno("Cannot open '%s'", path);
- hash_fd(fd, type, write_object, vpath);
+ hash_fd(fd, type, vpath, flags, literally);
}
-static int no_filters;
-
-static void hash_stdin_paths(const char *type, int write_objects)
+static void hash_stdin_paths(const char *type, int no_filters, unsigned flags,
+ int literally)
{
struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
@@ -49,47 +69,46 @@ static void hash_stdin_paths(const char *type, int write_objects)
die("line is badly quoted");
strbuf_swap(&buf, &nbuf);
}
- hash_object(buf.buf, type, write_objects,
- no_filters ? NULL : buf.buf);
+ hash_object(buf.buf, type, no_filters ? NULL : buf.buf, flags,
+ literally);
}
strbuf_release(&buf);
strbuf_release(&nbuf);
}
-static const char * const hash_object_usage[] = {
- "git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>...",
- "git hash-object --stdin-paths < <list-of-paths>",
- NULL
-};
-
-static const char *type;
-static int write_object;
-static int hashstdin;
-static int stdin_paths;
-static const char *vpath;
-
-static const struct option hash_object_options[] = {
- OPT_STRING('t', NULL, &type, "type", "object type"),
- OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"),
- OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"),
- OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"),
- OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"),
- OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"),
- OPT_END()
-};
-
int cmd_hash_object(int argc, const char **argv, const char *prefix)
{
+ static const char * const hash_object_usage[] = {
+ N_("git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>..."),
+ N_("git hash-object --stdin-paths < <list-of-paths>"),
+ NULL
+ };
+ const char *type = blob_type;
+ int hashstdin = 0;
+ int stdin_paths = 0;
+ int no_filters = 0;
+ int literally = 0;
+ unsigned flags = HASH_FORMAT_CHECK;
+ const char *vpath = NULL;
+ const struct option hash_object_options[] = {
+ OPT_STRING('t', NULL, &type, N_("type"), N_("object type")),
+ OPT_BIT('w', NULL, &flags, N_("write the object into the object database"),
+ HASH_WRITE_OBJECT),
+ 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_BOOL( 0, "literally", &literally, N_("just hash any random garbage to create corrupt objects for debugging Git")),
+ OPT_STRING( 0 , "path", &vpath, N_("file"), N_("process file as it were from this path")),
+ OPT_END()
+ };
int i;
int prefix_length = -1;
const char *errstr = NULL;
- type = blob_type;
-
argc = parse_options(argc, argv, NULL, hash_object_options,
hash_object_usage, 0);
- if (write_object) {
+ if (flags & HASH_WRITE_OBJECT) {
prefix = setup_git_directory();
prefix_length = prefix ? strlen(prefix) : 0;
if (vpath && prefix)
@@ -119,19 +138,19 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
}
if (hashstdin)
- hash_fd(0, type, write_object, vpath);
+ hash_fd(0, type, vpath, flags, literally);
for (i = 0 ; i < argc; i++) {
const char *arg = argv[i];
if (0 <= prefix_length)
arg = prefix_filename(prefix, prefix_length, arg);
- hash_object(arg, type, write_object,
- no_filters ? NULL : vpath ? vpath : arg);
+ hash_object(arg, type, no_filters ? NULL : vpath ? vpath : arg,
+ flags, literally);
}
if (stdin_paths)
- hash_stdin_paths(type, write_object);
+ hash_stdin_paths(type, no_filters, flags, literally);
return 0;
}
diff --git a/builtin/help.c b/builtin/help.c
index 61ff798..e78c135 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -1,16 +1,18 @@
/*
- * builtin-help.c
- *
* Builtin help command
*/
#include "cache.h"
#include "builtin.h"
#include "exec_cmd.h"
-#include "common-cmds.h"
#include "parse-options.h"
#include "run-command.h"
+#include "column.h"
#include "help.h"
+#ifndef DEFAULT_HELP_FORMAT
+#define DEFAULT_HELP_FORMAT "man"
+#endif
+
static struct man_viewer_list {
struct man_viewer_list *next;
char name[FLEX_ARRAY];
@@ -29,20 +31,25 @@ enum help_format {
HELP_FORMAT_WEB
};
+static const char *html_path;
+
static int show_all = 0;
+static int show_guides = 0;
+static unsigned int colopts;
static enum help_format help_format = HELP_FORMAT_NONE;
static struct option builtin_help_options[] = {
- OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
- OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
- OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
+ OPT_BOOL('a', "all", &show_all, N_("print all available commands")),
+ OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")),
+ OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN),
+ OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"),
HELP_FORMAT_WEB),
- OPT_SET_INT('i', "info", &help_format, "show info page",
+ OPT_SET_INT('i', "info", &help_format, N_("show info page"),
HELP_FORMAT_INFO),
OPT_END(),
};
static const char * const builtin_help_usage[] = {
- "git help [--all] [--man|--web|--info] [command]",
+ N_("git help [--all] [--guides] [--man|--web|--info] [command]"),
NULL
};
@@ -54,7 +61,7 @@ static enum help_format parse_help_format(const char *format)
return HELP_FORMAT_INFO;
if (!strcmp(format, "web") || !strcmp(format, "html"))
return HELP_FORMAT_WEB;
- die("unrecognized help format '%s'", format);
+ die(_("unrecognized help format '%s'"), format);
}
static const char *get_man_viewer_info(const char *name)
@@ -72,17 +79,16 @@ static const char *get_man_viewer_info(const char *name)
static int check_emacsclient_version(void)
{
struct strbuf buffer = STRBUF_INIT;
- struct child_process ec_process;
+ struct child_process ec_process = CHILD_PROCESS_INIT;
const char *argv_ec[] = { "emacsclient", "--version", NULL };
int version;
/* emacsclient prints its version number on stderr */
- memset(&ec_process, 0, sizeof(ec_process));
ec_process.argv = argv_ec;
ec_process.err = -1;
ec_process.stdout_to_stderr = 1;
if (start_command(&ec_process))
- return error("Failed to start emacsclient.");
+ return error(_("Failed to start emacsclient."));
strbuf_read(&buffer, ec_process.err, 20);
close(ec_process.err);
@@ -93,9 +99,9 @@ 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.");
+ return error(_("Failed to parse emacsclient version."));
}
strbuf_remove(&buffer, 0, strlen("emacsclient"));
@@ -103,7 +109,7 @@ static int check_emacsclient_version(void)
if (version < 22) {
strbuf_release(&buffer);
- return error("emacsclient version '%d' too old (< 22).",
+ return error(_("emacsclient version '%d' too old (< 22)."),
version);
}
@@ -121,7 +127,7 @@ static void exec_woman_emacs(const char *path, const char *page)
path = "emacsclient";
strbuf_addf(&man_page, "(woman \"%s\")", page);
execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
}
@@ -149,7 +155,7 @@ static void exec_man_konqueror(const char *path, const char *page)
path = "kfmclient";
strbuf_addf(&man_page, "man:%s(1)", page);
execlp(path, filename, "newTab", man_page.buf, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
}
@@ -158,7 +164,7 @@ static void exec_man_man(const char *path, const char *page)
if (!path)
path = "man";
execlp(path, "man", page, (char *)NULL);
- warning("failed to exec '%s': %s", path, strerror(errno));
+ warning(_("failed to exec '%s': %s"), path, strerror(errno));
}
static void exec_man_cmd(const char *cmd, const char *page)
@@ -166,7 +172,7 @@ static void exec_man_cmd(const char *cmd, const char *page)
struct strbuf shell_cmd = STRBUF_INIT;
strbuf_addf(&shell_cmd, "%s %s", cmd, page);
execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL);
- warning("failed to exec '%s': %s", cmd, strerror(errno));
+ warning(_("failed to exec '%s': %s"), cmd, strerror(errno));
}
static void add_man_viewer(const char *name)
@@ -206,8 +212,8 @@ static int add_man_viewer_path(const char *name,
if (supported_man_viewer(name, len))
do_add_man_viewer_info(name, len, value);
else
- warning("'%s': path for unsupported man viewer.\n"
- "Please consider using 'man.<tool>.cmd' instead.",
+ warning(_("'%s': path for unsupported man viewer.\n"
+ "Please consider using 'man.<tool>.cmd' instead."),
name);
return 0;
@@ -218,8 +224,8 @@ static int add_man_viewer_cmd(const char *name,
const char *value)
{
if (supported_man_viewer(name, len))
- warning("'%s': cmd for supported man viewer.\n"
- "Please consider using 'man.<tool>.path' instead.",
+ warning(_("'%s': cmd for supported man viewer.\n"
+ "Please consider using 'man.<tool>.path' instead."),
name);
else
do_add_man_viewer_info(name, len, value);
@@ -229,21 +235,21 @@ static int add_man_viewer_cmd(const char *name,
static int add_man_viewer_info(const char *var, const char *value)
{
- const char *name = var + 4;
- const char *subkey = strrchr(name, '.');
+ const char *name, *subkey;
+ int namelen;
- if (!subkey)
+ if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name)
return 0;
- if (!strcmp(subkey, ".path")) {
+ if (!strcmp(subkey, "path")) {
if (!value)
return config_error_nonbool(var);
- return add_man_viewer_path(name, subkey - name, value);
+ return add_man_viewer_path(name, namelen, value);
}
- if (!strcmp(subkey, ".cmd")) {
+ if (!strcmp(subkey, "cmd")) {
if (!value)
return config_error_nonbool(var);
- return add_man_viewer_cmd(name, subkey - name, value);
+ return add_man_viewer_cmd(name, namelen, value);
}
return 0;
@@ -251,19 +257,27 @@ 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 (starts_with(var, "column."))
+ return git_column_config(var, value, "help", &colopts);
if (!strcmp(var, "help.format")) {
if (!value)
return config_error_nonbool(var);
help_format = parse_help_format(value);
return 0;
}
+ if (!strcmp(var, "help.htmlpath")) {
+ if (!value)
+ return config_error_nonbool(var);
+ html_path = xstrdup(value);
+ return 0;
+ }
if (!strcmp(var, "man.viewer")) {
if (!value)
return config_error_nonbool(var);
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);
@@ -271,25 +285,12 @@ static int git_help_config(const char *var, const char *value, void *cb)
static struct cmdnames main_cmds, other_cmds;
-void list_common_cmds_help(void)
-{
- int i, longest = 0;
-
- for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
- if (longest < strlen(common_cmds[i].name))
- longest = strlen(common_cmds[i].name);
- }
-
- puts("The most commonly used git commands are:");
- for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
- printf(" %s ", common_cmds[i].name);
- mput_char(' ', longest - strlen(common_cmds[i].name));
- puts(common_cmds[i].help);
- }
-}
-
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 +309,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);
@@ -320,16 +321,18 @@ static void setup_man_path(void)
{
struct strbuf new_path = STRBUF_INIT;
const char *old_path = getenv("MANPATH");
+ char *git_man_path = system_path(GIT_MAN_PATH);
/* We should always put ':' after our path. If there is no
* old_path, the ':' at the end will let 'man' to try
* system-wide paths after ours to find the manual page. If
* there is old_path, we need ':' as delimiter. */
- strbuf_addstr(&new_path, system_path(GIT_MAN_PATH));
+ strbuf_addstr(&new_path, git_man_path);
strbuf_addch(&new_path, ':');
if (old_path)
strbuf_addstr(&new_path, old_path);
+ free(git_man_path);
setenv("MANPATH", new_path.buf, 1);
strbuf_release(&new_path);
@@ -348,7 +351,7 @@ static void exec_viewer(const char *name, const char *page)
else if (info)
exec_man_cmd(info, page);
else
- warning("'%s': unknown man viewer.", name);
+ warning(_("'%s': unknown man viewer."), name);
}
static void show_man_page(const char *git_cmd)
@@ -365,7 +368,7 @@ static void show_man_page(const char *git_cmd)
if (fallback)
exec_viewer(fallback, page);
exec_viewer("man", page);
- die("no man viewer handled the request");
+ die(_("no man viewer handled the request"));
}
static void show_info_page(const char *git_cmd)
@@ -373,21 +376,27 @@ static void show_info_page(const char *git_cmd)
const char *page = cmd_to_page(git_cmd);
setenv("INFOPATH", system_path(GIT_INFO_PATH), 1);
execlp("info", "info", "gitman", page, (char *)NULL);
- die("no info viewer handled the request");
+ die(_("no info viewer handled the request"));
}
static void get_html_page_path(struct strbuf *page_path, const char *page)
{
struct stat st;
- const char *html_path = system_path(GIT_HTML_PATH);
+ char *to_free = NULL;
+
+ if (!html_path)
+ html_path = to_free = system_path(GIT_HTML_PATH);
/* Check that we have a git documentation directory. */
- if (stat(mkpath("%s/git.html", html_path), &st)
- || !S_ISREG(st.st_mode))
- die("'%s': not a documentation directory.", html_path);
+ if (!strstr(html_path, "://")) {
+ if (stat(mkpath("%s/git.html", html_path), &st)
+ || !S_ISREG(st.st_mode))
+ die("'%s': not a documentation directory.", html_path);
+ }
strbuf_init(page_path, 0);
strbuf_addf(page_path, "%s/%s.html", html_path, page);
+ free(to_free);
}
/*
@@ -412,28 +421,70 @@ static void show_html_page(const char *git_cmd)
open_html(page_path.buf);
}
+static struct {
+ const char *name;
+ const char *help;
+} common_guides[] = {
+ { "attributes", N_("Defining attributes per path") },
+ { "everyday", N_("Everyday Git With 20 Commands Or So") },
+ { "glossary", N_("A Git glossary") },
+ { "ignore", N_("Specifies intentionally untracked files to ignore") },
+ { "modules", N_("Defining submodule properties") },
+ { "revisions", N_("Specifying revisions and ranges for Git") },
+ { "tutorial", N_("A tutorial introduction to Git (for version 1.5.1 or newer)") },
+ { "workflows", N_("An overview of recommended workflows with Git") },
+};
+
+static void list_common_guides_help(void)
+{
+ int i, longest = 0;
+
+ for (i = 0; i < ARRAY_SIZE(common_guides); i++) {
+ if (longest < strlen(common_guides[i].name))
+ longest = strlen(common_guides[i].name);
+ }
+
+ puts(_("The common Git guides are:\n"));
+ for (i = 0; i < ARRAY_SIZE(common_guides); i++) {
+ printf(" %s ", common_guides[i].name);
+ mput_char(' ', longest - strlen(common_guides[i].name));
+ puts(_(common_guides[i].help));
+ }
+ putchar('\n');
+}
+
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);
parsed_help_format = help_format;
if (show_all) {
- printf("usage: %s\n\n", git_usage_string);
- list_commands("git commands", &main_cmds, &other_cmds);
- printf("%s\n", git_more_info_string);
+ 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);
+ }
+
+ if (show_guides)
+ list_common_guides_help();
+
+ if (show_all || show_guides) {
+ printf("%s\n", _(git_more_info_string));
+ /*
+ * We're done. Ignore any remaining args
+ */
return 0;
}
if (!argv[0]) {
- printf("usage: %s\n\n", git_usage_string);
+ printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
list_common_cmds_help();
- printf("\n%s\n", git_more_info_string);
+ printf("\n%s\n", _(git_more_info_string));
return 0;
}
@@ -442,10 +493,12 @@ int cmd_help(int argc, const char **argv, const char *prefix)
if (parsed_help_format != HELP_FORMAT_NONE)
help_format = parsed_help_format;
+ if (help_format == HELP_FORMAT_NONE)
+ help_format = parse_help_format(DEFAULT_HELP_FORMAT);
alias = alias_lookup(argv[0]);
if (alias && !is_git_command(argv[0])) {
- printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+ printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
return 0;
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 0945adb..cf654df 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -9,6 +9,8 @@
#include "progress.h"
#include "fsck.h"
#include "exec_cmd.h"
+#include "streaming.h"
+#include "thread-utils.h"
static const char index_pack_usage[] =
"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
@@ -34,6 +36,17 @@ struct base_data {
struct object_entry *obj;
void *data;
unsigned long size;
+ int ref_first, ref_last;
+ int ofs_first, ofs_last;
+};
+
+struct thread_local {
+#ifndef NO_PTHREADS
+ pthread_t thread;
+#endif
+ struct base_data *base_cache;
+ size_t base_cache_used;
+ int pack_fd;
};
/*
@@ -52,15 +65,18 @@ struct delta_entry {
static struct object_entry *objects;
static struct delta_entry *deltas;
-static struct base_data *base_cache;
-static size_t base_cache_used;
+static struct thread_local nothread_data;
static int nr_objects;
static int nr_deltas;
static int nr_resolved_deltas;
+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;
@@ -71,7 +87,109 @@ static off_t consumed_bytes;
static unsigned deepest_delta;
static git_SHA_CTX input_ctx;
static uint32_t input_crc32;
-static int input_fd, output_fd, pack_fd;
+static int input_fd, output_fd;
+static const char *curr_pack;
+
+#ifndef NO_PTHREADS
+
+static struct thread_local *thread_data;
+static int nr_dispatched;
+static int threads_active;
+
+static pthread_mutex_t read_mutex;
+#define read_lock() lock_mutex(&read_mutex)
+#define read_unlock() unlock_mutex(&read_mutex)
+
+static pthread_mutex_t counter_mutex;
+#define counter_lock() lock_mutex(&counter_mutex)
+#define counter_unlock() unlock_mutex(&counter_mutex)
+
+static pthread_mutex_t work_mutex;
+#define work_lock() lock_mutex(&work_mutex)
+#define work_unlock() unlock_mutex(&work_mutex)
+
+static pthread_mutex_t deepest_delta_mutex;
+#define deepest_delta_lock() lock_mutex(&deepest_delta_mutex)
+#define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex)
+
+static pthread_mutex_t type_cas_mutex;
+#define type_cas_lock() lock_mutex(&type_cas_mutex)
+#define type_cas_unlock() unlock_mutex(&type_cas_mutex)
+
+static pthread_key_t key;
+
+static inline void lock_mutex(pthread_mutex_t *mutex)
+{
+ if (threads_active)
+ pthread_mutex_lock(mutex);
+}
+
+static inline void unlock_mutex(pthread_mutex_t *mutex)
+{
+ if (threads_active)
+ pthread_mutex_unlock(mutex);
+}
+
+/*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+static void init_thread(void)
+{
+ int i;
+ init_recursive_mutex(&read_mutex);
+ pthread_mutex_init(&counter_mutex, NULL);
+ pthread_mutex_init(&work_mutex, NULL);
+ pthread_mutex_init(&type_cas_mutex, NULL);
+ if (show_stat)
+ pthread_mutex_init(&deepest_delta_mutex, NULL);
+ pthread_key_create(&key, NULL);
+ thread_data = xcalloc(nr_threads, sizeof(*thread_data));
+ for (i = 0; i < nr_threads; i++) {
+ thread_data[i].pack_fd = open(curr_pack, O_RDONLY);
+ if (thread_data[i].pack_fd == -1)
+ die_errno(_("unable to open %s"), curr_pack);
+ }
+
+ threads_active = 1;
+}
+
+static void cleanup_thread(void)
+{
+ int i;
+ if (!threads_active)
+ return;
+ threads_active = 0;
+ pthread_mutex_destroy(&read_mutex);
+ pthread_mutex_destroy(&counter_mutex);
+ pthread_mutex_destroy(&work_mutex);
+ pthread_mutex_destroy(&type_cas_mutex);
+ if (show_stat)
+ pthread_mutex_destroy(&deepest_delta_mutex);
+ for (i = 0; i < nr_threads; i++)
+ close(thread_data[i].pack_fd);
+ pthread_key_delete(key);
+ free(thread_data);
+}
+
+#else
+
+#define read_lock()
+#define read_unlock()
+
+#define counter_lock()
+#define counter_unlock()
+
+#define work_lock()
+#define work_unlock()
+
+#define deepest_delta_lock()
+#define deepest_delta_unlock()
+
+#define type_cas_lock()
+#define type_cas_unlock()
+
+#endif
+
static int mark_link(struct object *obj, int type, void *data)
{
@@ -79,7 +197,7 @@ static int mark_link(struct object *obj, int type, void *data)
return -1;
if (type != OBJ_ANY && obj->type != type)
- die("object type mismatch at %s", sha1_to_hex(obj->sha1));
+ die(_("object type mismatch at %s"), sha1_to_hex(obj->sha1));
obj->flags |= FLAG_LINK;
return 0;
@@ -87,31 +205,39 @@ 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;
int type = sha1_object_info(obj->sha1, &size);
- if (type != obj->type || type <= 0)
- die("object of unexpected type");
+ if (type <= 0)
+ die(_("did not receive expected object %s"),
+ sha1_to_hex(obj->sha1));
+ if (type != obj->type)
+ die(_("object %s: expected type %s, found %s"),
+ sha1_to_hex(obj->sha1),
+ typename(obj->type), typename(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;
}
@@ -136,15 +262,18 @@ static void *fill(int min)
if (min <= input_len)
return input_buffer + input_offset;
if (min > sizeof(input_buffer))
- die("cannot fill %d bytes", min);
+ die(Q_("cannot fill %d byte",
+ "cannot fill %d bytes",
+ min),
+ min);
flush();
do {
ssize_t ret = xread(input_fd, input_buffer + input_len,
sizeof(input_buffer) - input_len);
if (ret <= 0) {
if (!ret)
- die("early EOF");
- die_errno("read error on input");
+ die(_("early EOF"));
+ die_errno(_("read error on input"));
}
input_len += ret;
if (from_stdin)
@@ -156,14 +285,14 @@ static void *fill(int min)
static void use(int bytes)
{
if (bytes > input_len)
- die("used more bytes than were available");
+ die(_("used more bytes than were available"));
input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes);
input_len -= bytes;
input_offset += bytes;
/* make sure off_t is sufficiently large not to wrap */
if (signed_add_overflows(consumed_bytes, bytes))
- die("pack too large for current definition of off_t");
+ die(_("pack too large for current definition of off_t"));
consumed_bytes += bytes;
}
@@ -172,21 +301,21 @@ static const char *open_pack_file(const char *pack_name)
if (from_stdin) {
input_fd = 0;
if (!pack_name) {
- static char tmpfile[PATH_MAX];
- output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+ static char tmp_file[PATH_MAX];
+ output_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
"pack/tmp_pack_XXXXXX");
- pack_name = xstrdup(tmpfile);
+ pack_name = xstrdup(tmp_file);
} else
output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
if (output_fd < 0)
- die_errno("unable to create '%s'", pack_name);
- pack_fd = output_fd;
+ die_errno(_("unable to create '%s'"), pack_name);
+ nothread_data.pack_fd = output_fd;
} else {
input_fd = open(pack_name, O_RDONLY);
if (input_fd < 0)
- die_errno("cannot open packfile '%s'", pack_name);
+ die_errno(_("cannot open packfile '%s'"), pack_name);
output_fd = -1;
- pack_fd = input_fd;
+ nothread_data.pack_fd = input_fd;
}
git_SHA1_Init(&input_ctx);
return pack_name;
@@ -198,9 +327,9 @@ static void parse_pack_header(void)
/* Header consistency check */
if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
- die("pack signature mismatch");
+ die(_("pack signature mismatch"));
if (!pack_version_ok(hdr->hdr_version))
- die("pack version %"PRIu32" unsupported",
+ die(_("pack version %"PRIu32" unsupported"),
ntohl(hdr->hdr_version));
nr_objects = ntohl(hdr->hdr_entries);
@@ -218,7 +347,34 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...)
va_start(params, format);
vsnprintf(buf, sizeof(buf), format, params);
va_end(params);
- die("pack has bad object at offset %lu: %s", offset, buf);
+ die(_("pack has bad object at offset %lu: %s"), offset, buf);
+}
+
+static inline struct thread_local *get_thread_data(void)
+{
+#ifndef NO_PTHREADS
+ if (threads_active)
+ return pthread_getspecific(key);
+ assert(!threads_active &&
+ "This should only be reached when all threads are gone");
+#endif
+ return &nothread_data;
+}
+
+#ifndef NO_PTHREADS
+static void set_thread_data(struct thread_local *data)
+{
+ if (threads_active)
+ pthread_setspecific(key, data);
+}
+#endif
+
+static struct base_data *alloc_base_data(void)
+{
+ struct base_data *base = xcalloc(1, sizeof(struct base_data));
+ base->ref_last = -1;
+ base->ofs_last = -1;
+ return base;
}
static void free_base_data(struct base_data *c)
@@ -226,15 +382,16 @@ static void free_base_data(struct base_data *c)
if (c->data) {
free(c->data);
c->data = NULL;
- base_cache_used -= c->size;
+ get_thread_data()->base_cache_used -= c->size;
}
}
static void prune_base_data(struct base_data *retain)
{
struct base_data *b;
- for (b = base_cache;
- base_cache_used > delta_base_cache_limit && b;
+ struct thread_local *data = get_thread_data();
+ for (b = data->base_cache;
+ data->base_cache_used > delta_base_cache_limit && b;
b = b->child) {
if (b->data && b != retain)
free_base_data(b);
@@ -246,12 +403,12 @@ static void link_base_data(struct base_data *base, struct base_data *c)
if (base)
base->child = c;
else
- base_cache = c;
+ get_thread_data()->base_cache = c;
c->base = base;
c->child = NULL;
if (c->data)
- base_cache_used += c->size;
+ get_thread_data()->base_cache_used += c->size;
prune_base_data(c);
}
@@ -261,34 +418,66 @@ static void unlink_base_data(struct base_data *c)
if (base)
base->child = NULL;
else
- base_cache = NULL;
+ get_thread_data()->base_cache = NULL;
free_base_data(c);
}
-static void *unpack_entry_data(unsigned long offset, unsigned long size)
+static int is_delta_type(enum object_type type)
{
+ return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+}
+
+static void *unpack_entry_data(unsigned long offset, unsigned long size,
+ enum object_type type, unsigned char *sha1)
+{
+ static char fixed_buf[8192];
int status;
git_zstream stream;
- void *buf = xmalloc(size);
+ void *buf;
+ git_SHA_CTX c;
+ char hdr[32];
+ int hdrlen;
+
+ if (!is_delta_type(type)) {
+ hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1;
+ git_SHA1_Init(&c);
+ git_SHA1_Update(&c, hdr, hdrlen);
+ } else
+ sha1 = NULL;
+ if (type == OBJ_BLOB && size > big_file_threshold)
+ buf = fixed_buf;
+ else
+ buf = xmallocz(size);
memset(&stream, 0, sizeof(stream));
git_inflate_init(&stream);
stream.next_out = buf;
- stream.avail_out = size;
+ stream.avail_out = buf == fixed_buf ? sizeof(fixed_buf) : size;
do {
+ unsigned char *last_out = stream.next_out;
stream.next_in = fill(1);
stream.avail_in = input_len;
status = git_inflate(&stream, 0);
use(input_len - stream.avail_in);
+ if (sha1)
+ git_SHA1_Update(&c, last_out, stream.next_out - last_out);
+ if (buf == fixed_buf) {
+ stream.next_out = buf;
+ stream.avail_out = sizeof(fixed_buf);
+ }
} while (status == Z_OK);
if (stream.total_out != size || status != Z_STREAM_END)
- bad_object(offset, "inflate returned %d", status);
+ bad_object(offset, _("inflate returned %d"), status);
git_inflate_end(&stream);
- return buf;
+ if (sha1)
+ git_SHA1_Final(sha1, &c);
+ return buf == fixed_buf ? NULL : buf;
}
-static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
+static void *unpack_raw_entry(struct object_entry *obj,
+ union delta_base *delta_base,
+ unsigned char *sha1)
{
unsigned char *p;
unsigned long size, c;
@@ -328,7 +517,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
while (c & 128) {
base_offset += 1;
if (!base_offset || MSB(base_offset, 7))
- bad_object(obj->idx.offset, "offset value overflow for delta base object");
+ bad_object(obj->idx.offset, _("offset value overflow for delta base object"));
p = fill(1);
c = *p;
use(1);
@@ -336,7 +525,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
}
delta_base->offset = obj->idx.offset - base_offset;
if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
- bad_object(obj->idx.offset, "delta base offset is out of bound");
+ bad_object(obj->idx.offset, _("delta base offset is out of bound"));
break;
case OBJ_COMMIT:
case OBJ_TREE:
@@ -344,16 +533,18 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
case OBJ_TAG:
break;
default:
- bad_object(obj->idx.offset, "unknown object type %d", obj->type);
+ bad_object(obj->idx.offset, _("unknown object type %d"), obj->type);
}
obj->hdr_size = consumed_bytes - obj->idx.offset;
- data = unpack_entry_data(obj->idx.offset, obj->size);
+ data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, sha1);
obj->idx.crc32 = input_crc32;
return data;
}
-static void *get_data_from_pack(struct object_entry *obj)
+static void *unpack_data(struct object_entry *obj,
+ int (*consume)(const unsigned char *, unsigned long, void *),
+ void *cb_data)
{
off_t from = obj[0].idx.offset + obj[0].hdr_size;
unsigned long len = obj[1].idx.offset - from;
@@ -361,37 +552,62 @@ static void *get_data_from_pack(struct object_entry *obj)
git_zstream stream;
int status;
- data = xmalloc(obj->size);
+ data = xmallocz(consume ? 64*1024 : obj->size);
inbuf = xmalloc((len < 64*1024) ? len : 64*1024);
memset(&stream, 0, sizeof(stream));
git_inflate_init(&stream);
stream.next_out = data;
- stream.avail_out = obj->size;
+ stream.avail_out = consume ? 64*1024 : obj->size;
do {
ssize_t n = (len < 64*1024) ? len : 64*1024;
- n = pread(pack_fd, inbuf, n, from);
+ n = xpread(get_thread_data()->pack_fd, inbuf, n, from);
if (n < 0)
- die_errno("cannot pread pack file");
+ die_errno(_("cannot pread pack file"));
if (!n)
- die("premature end of pack file, %lu bytes missing", len);
+ die(Q_("premature end of pack file, %lu byte missing",
+ "premature end of pack file, %lu bytes missing",
+ len),
+ len);
from += n;
len -= n;
stream.next_in = inbuf;
stream.avail_in = n;
- status = git_inflate(&stream, 0);
+ if (!consume)
+ status = git_inflate(&stream, 0);
+ else {
+ do {
+ status = git_inflate(&stream, 0);
+ if (consume(data, stream.next_out - data, cb_data)) {
+ free(inbuf);
+ free(data);
+ return NULL;
+ }
+ stream.next_out = data;
+ stream.avail_out = 64*1024;
+ } while (status == Z_OK && stream.avail_in);
+ }
} while (len && status == Z_OK && !stream.avail_in);
/* This has been inflated OK when first encountered, so... */
if (status != Z_STREAM_END || stream.total_out != obj->size)
- die("serious inflate inconsistency");
+ die(_("serious inflate inconsistency"));
git_inflate_end(&stream);
free(inbuf);
+ if (consume) {
+ free(data);
+ data = NULL;
+ }
return data;
}
+static void *get_data_from_pack(struct object_entry *obj)
+{
+ return unpack_data(obj, NULL, NULL);
+}
+
static int compare_delta_bases(const union delta_base *base1,
const union delta_base *base2,
enum object_type type1,
@@ -446,86 +662,207 @@ static void find_delta_children(const union delta_base *base,
*last_index = last;
}
-static void sha1_object(const void *data, unsigned long size,
- enum object_type type, unsigned char *sha1)
+struct compare_data {
+ struct object_entry *entry;
+ struct git_istream *st;
+ unsigned char *buf;
+ unsigned long buf_size;
+};
+
+static int compare_objects(const unsigned char *buf, unsigned long size,
+ void *cb_data)
{
- hash_sha1_file(data, size, typename(type), sha1);
- if (has_sha1_file(sha1)) {
+ struct compare_data *data = cb_data;
+
+ if (data->buf_size < size) {
+ free(data->buf);
+ data->buf = xmalloc(size);
+ data->buf_size = size;
+ }
+
+ while (size) {
+ ssize_t len = read_istream(data->st, data->buf, size);
+ if (len == 0)
+ die(_("SHA1 COLLISION FOUND WITH %s !"),
+ sha1_to_hex(data->entry->idx.sha1));
+ if (len < 0)
+ die(_("unable to read %s"),
+ sha1_to_hex(data->entry->idx.sha1));
+ if (memcmp(buf, data->buf, len))
+ die(_("SHA1 COLLISION FOUND WITH %s !"),
+ sha1_to_hex(data->entry->idx.sha1));
+ size -= len;
+ buf += len;
+ }
+ return 0;
+}
+
+static int check_collison(struct object_entry *entry)
+{
+ struct compare_data data;
+ enum object_type type;
+ unsigned long size;
+
+ if (entry->size <= big_file_threshold || entry->type != OBJ_BLOB)
+ return -1;
+
+ memset(&data, 0, sizeof(data));
+ data.entry = entry;
+ data.st = open_istream(entry->idx.sha1, &type, &size, NULL);
+ if (!data.st)
+ return -1;
+ if (size != entry->size || type != entry->type)
+ die(_("SHA1 COLLISION FOUND WITH %s !"),
+ sha1_to_hex(entry->idx.sha1));
+ unpack_data(entry, compare_objects, &data);
+ close_istream(data.st);
+ free(data.buf);
+ return 0;
+}
+
+static void sha1_object(const void *data, struct object_entry *obj_entry,
+ unsigned long size, enum object_type type,
+ const unsigned char *sha1)
+{
+ void *new_data = NULL;
+ int collision_test_needed;
+
+ assert(data || obj_entry);
+
+ read_lock();
+ collision_test_needed = has_sha1_file(sha1);
+ read_unlock();
+
+ if (collision_test_needed && !data) {
+ read_lock();
+ if (!check_collison(obj_entry))
+ collision_test_needed = 0;
+ read_unlock();
+ }
+ if (collision_test_needed) {
void *has_data;
enum object_type has_type;
unsigned long has_size;
+ read_lock();
+ has_type = sha1_object_info(sha1, &has_size);
+ if (has_type != type || has_size != size)
+ die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1));
has_data = read_sha1_file(sha1, &has_type, &has_size);
+ read_unlock();
+ if (!data)
+ data = new_data = get_data_from_pack(obj_entry);
if (!has_data)
- die("cannot read existing object %s", sha1_to_hex(sha1));
+ die(_("cannot read existing object %s"), sha1_to_hex(sha1));
if (size != has_size || type != has_type ||
memcmp(data, has_data, size) != 0)
- die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+ die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1));
free(has_data);
}
+
if (strict) {
+ read_lock();
if (type == OBJ_BLOB) {
struct blob *blob = lookup_blob(sha1);
if (blob)
blob->object.flags |= FLAG_CHECKED;
else
- die("invalid blob object %s", sha1_to_hex(sha1));
+ die(_("invalid blob object %s"), sha1_to_hex(sha1));
} else {
struct object *obj;
int eaten;
void *buf = (void *) data;
+ assert(data && "data can only be NULL for large _blobs_");
+
/*
* we do not need to free the memory here, as the
* buf is deleted by the caller.
*/
obj = parse_object_buffer(sha1, type, size, buf, &eaten);
if (!obj)
- die("invalid %s", typename(type));
- if (fsck_object(obj, 1, fsck_error_function))
- die("Error in object");
+ die(_("invalid %s"), typename(type));
+ if (do_fsck_object &&
+ fsck_object(obj, buf, size, 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));
+ die(_("Not all child objects of %s are reachable"), sha1_to_hex(obj->sha1));
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;
- commit->buffer = NULL;
+ if (detach_commit_buffer(commit, NULL) != data)
+ die("BUG: parse_object_buffer transmogrified our buffer");
}
obj->flags |= FLAG_CHECKED;
}
+ read_unlock();
}
-}
-static int is_delta_type(enum object_type type)
-{
- return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+ free(new_data);
}
+/*
+ * This function is part of find_unresolved_deltas(). There are two
+ * walkers going in the opposite ways.
+ *
+ * The first one in find_unresolved_deltas() traverses down from
+ * parent node to children, deflating nodes along the way. However,
+ * memory for deflated nodes is limited by delta_base_cache_limit, so
+ * at some point parent node's deflated content may be freed.
+ *
+ * The second walker is this function, which goes from current node up
+ * to top parent if necessary to deflate the node. In normal
+ * situation, its parent node would be already deflated, so it just
+ * needs to apply delta.
+ *
+ * In the worst case scenario, parent node is no longer deflated because
+ * we're running out of delta_base_cache_limit; we need to re-deflate
+ * parents, possibly up to the top base.
+ *
+ * All deflated objects here are subject to be freed if we exceed
+ * delta_base_cache_limit, just like in find_unresolved_deltas(), we
+ * just need to make sure the last node is not freed.
+ */
static void *get_base_data(struct base_data *c)
{
if (!c->data) {
struct object_entry *obj = c->obj;
+ struct base_data **delta = NULL;
+ int delta_nr = 0, delta_alloc = 0;
- if (is_delta_type(obj->type)) {
- void *base = get_base_data(c->base);
- void *raw = get_data_from_pack(obj);
+ while (is_delta_type(c->obj->type) && !c->data) {
+ ALLOC_GROW(delta, delta_nr + 1, delta_alloc);
+ delta[delta_nr++] = c;
+ c = c->base;
+ }
+ if (!delta_nr) {
+ c->data = get_data_from_pack(obj);
+ c->size = obj->size;
+ get_thread_data()->base_cache_used += c->size;
+ prune_base_data(c);
+ }
+ for (; delta_nr > 0; delta_nr--) {
+ void *base, *raw;
+ c = delta[delta_nr - 1];
+ obj = c->obj;
+ base = get_base_data(c->base);
+ raw = get_data_from_pack(obj);
c->data = patch_delta(
base, c->base->size,
raw, obj->size,
&c->size);
free(raw);
if (!c->data)
- bad_object(obj->idx.offset, "failed to apply delta");
- } else {
- c->data = get_data_from_pack(obj);
- c->size = obj->size;
+ bad_object(obj->idx.offset, _("failed to apply delta"));
+ get_thread_data()->base_cache_used += c->size;
+ prune_base_data(c);
}
-
- base_cache_used += c->size;
- prune_base_data(c);
+ free(delta);
}
return c->data;
}
@@ -535,10 +872,13 @@ static void resolve_delta(struct object_entry *delta_obj,
{
void *base_data, *delta_data;
- delta_obj->real_type = base->obj->real_type;
- delta_obj->delta_depth = base->obj->delta_depth + 1;
- if (deepest_delta < delta_obj->delta_depth)
- deepest_delta = delta_obj->delta_depth;
+ if (show_stat) {
+ delta_obj->delta_depth = base->obj->delta_depth + 1;
+ deepest_delta_lock();
+ if (deepest_delta < delta_obj->delta_depth)
+ deepest_delta = delta_obj->delta_depth;
+ deepest_delta_unlock();
+ }
delta_obj->base_object_no = base->obj - objects;
delta_data = get_data_from_pack(delta_obj);
base_data = get_base_data(base);
@@ -547,64 +887,110 @@ static void resolve_delta(struct object_entry *delta_obj,
delta_data, delta_obj->size, &result->size);
free(delta_data);
if (!result->data)
- bad_object(delta_obj->idx.offset, "failed to apply delta");
- sha1_object(result->data, result->size, delta_obj->real_type,
+ bad_object(delta_obj->idx.offset, _("failed to apply delta"));
+ hash_sha1_file(result->data, result->size,
+ typename(delta_obj->real_type), delta_obj->idx.sha1);
+ sha1_object(result->data, NULL, result->size, delta_obj->real_type,
delta_obj->idx.sha1);
+ counter_lock();
nr_resolved_deltas++;
+ counter_unlock();
}
-static void find_unresolved_deltas(struct base_data *base,
- struct base_data *prev_base)
+/*
+ * Standard boolean compare-and-swap: atomically check whether "*type" is
+ * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched
+ * and return false.
+ */
+static int compare_and_swap_type(enum object_type *type,
+ enum object_type want,
+ enum object_type set)
{
- int i, ref_first, ref_last, ofs_first, ofs_last;
+ enum object_type old;
- /*
- * This is a recursive function. Those brackets should help reducing
- * stack usage by limiting the scope of the delta_base union.
- */
- {
+ type_cas_lock();
+ old = *type;
+ if (old == want)
+ *type = set;
+ type_cas_unlock();
+
+ return old == want;
+}
+
+static struct base_data *find_unresolved_deltas_1(struct base_data *base,
+ struct base_data *prev_base)
+{
+ if (base->ref_last == -1 && base->ofs_last == -1) {
union delta_base base_spec;
hashcpy(base_spec.sha1, base->obj->idx.sha1);
find_delta_children(&base_spec,
- &ref_first, &ref_last, OBJ_REF_DELTA);
+ &base->ref_first, &base->ref_last, OBJ_REF_DELTA);
memset(&base_spec, 0, sizeof(base_spec));
base_spec.offset = base->obj->idx.offset;
find_delta_children(&base_spec,
- &ofs_first, &ofs_last, OBJ_OFS_DELTA);
- }
+ &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA);
- if (ref_last == -1 && ofs_last == -1) {
- free(base->data);
- return;
+ if (base->ref_last == -1 && base->ofs_last == -1) {
+ free(base->data);
+ return NULL;
+ }
+
+ link_base_data(prev_base, base);
}
- link_base_data(prev_base, base);
+ if (base->ref_first <= base->ref_last) {
+ struct object_entry *child = objects + deltas[base->ref_first].obj_no;
+ struct base_data *result = alloc_base_data();
- for (i = ref_first; i <= ref_last; i++) {
- struct object_entry *child = objects + deltas[i].obj_no;
- struct base_data result;
+ if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
+ base->obj->real_type))
+ die("BUG: child->real_type != OBJ_REF_DELTA");
- assert(child->real_type == OBJ_REF_DELTA);
- resolve_delta(child, base, &result);
- if (i == ref_last && ofs_last == -1)
+ resolve_delta(child, base, result);
+ if (base->ref_first == base->ref_last && base->ofs_last == -1)
free_base_data(base);
- find_unresolved_deltas(&result, base);
+
+ base->ref_first++;
+ return result;
}
- for (i = ofs_first; i <= ofs_last; i++) {
- struct object_entry *child = objects + deltas[i].obj_no;
- struct base_data result;
+ if (base->ofs_first <= base->ofs_last) {
+ struct object_entry *child = objects + deltas[base->ofs_first].obj_no;
+ struct base_data *result = alloc_base_data();
assert(child->real_type == OBJ_OFS_DELTA);
- resolve_delta(child, base, &result);
- if (i == ofs_last)
+ child->real_type = base->obj->real_type;
+ resolve_delta(child, base, result);
+ if (base->ofs_first == base->ofs_last)
free_base_data(base);
- find_unresolved_deltas(&result, base);
+
+ base->ofs_first++;
+ return result;
}
unlink_base_data(base);
+ return NULL;
+}
+
+static void find_unresolved_deltas(struct base_data *base)
+{
+ struct base_data *new_base, *prev_base = NULL;
+ for (;;) {
+ new_base = find_unresolved_deltas_1(base, prev_base);
+
+ if (new_base) {
+ prev_base = base;
+ base = new_base;
+ } else {
+ free(base);
+ base = prev_base;
+ if (!base)
+ return;
+ prev_base = base->base;
+ }
+ }
}
static int compare_delta_entry(const void *a, const void *b)
@@ -618,33 +1004,70 @@ static int compare_delta_entry(const void *a, const void *b)
objects[delta_b->obj_no].type);
}
-/* Parse all objects and return the pack content SHA1 hash */
+static void resolve_base(struct object_entry *obj)
+{
+ struct base_data *base_obj = alloc_base_data();
+ base_obj->obj = obj;
+ base_obj->data = NULL;
+ find_unresolved_deltas(base_obj);
+}
+
+#ifndef NO_PTHREADS
+static void *threaded_second_pass(void *data)
+{
+ set_thread_data(data);
+ for (;;) {
+ int i;
+ counter_lock();
+ display_progress(progress, nr_resolved_deltas);
+ counter_unlock();
+ work_lock();
+ while (nr_dispatched < nr_objects &&
+ is_delta_type(objects[nr_dispatched].type))
+ nr_dispatched++;
+ if (nr_dispatched >= nr_objects) {
+ work_unlock();
+ break;
+ }
+ i = nr_dispatched++;
+ work_unlock();
+
+ resolve_base(&objects[i]);
+ }
+ return NULL;
+}
+#endif
+
+/*
+ * First pass:
+ * - find locations of all objects;
+ * - calculate SHA1 of all non-delta objects;
+ * - remember base (SHA1 or offset) for all deltas.
+ */
static void parse_pack_objects(unsigned char *sha1)
{
- int i;
+ int i, nr_delays = 0;
struct delta_entry *delta = deltas;
struct stat st;
- /*
- * First pass:
- * - find locations of all objects;
- * - calculate SHA1 of all non-delta objects;
- * - remember base (SHA1 or offset) for all deltas.
- */
if (verbose)
progress = start_progress(
- from_stdin ? "Receiving objects" : "Indexing objects",
+ from_stdin ? _("Receiving objects") : _("Indexing objects"),
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- void *data = unpack_raw_entry(obj, &delta->base);
+ void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1);
obj->real_type = obj->type;
if (is_delta_type(obj->type)) {
nr_deltas++;
delta->obj_no = i;
delta++;
+ } else if (!data) {
+ /* large blobs, check later */
+ obj->real_type = OBJ_BAD;
+ nr_delays++;
} else
- sha1_object(data, obj->size, obj->type, obj->idx.sha1);
+ sha1_object(data, NULL, obj->size, obj->type, obj->idx.sha1);
free(data);
display_progress(progress, i+1);
}
@@ -655,15 +1078,39 @@ static void parse_pack_objects(unsigned char *sha1)
flush();
git_SHA1_Final(sha1, &input_ctx);
if (hashcmp(fill(20), sha1))
- die("pack is corrupted (SHA1 mismatch)");
+ die(_("pack is corrupted (SHA1 mismatch)"));
use(20);
/* If input_fd is a file, we should have reached its end now. */
if (fstat(input_fd, &st))
- die_errno("cannot fstat packfile");
+ die_errno(_("cannot fstat packfile"));
if (S_ISREG(st.st_mode) &&
lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size)
- die("pack has junk at the end");
+ die(_("pack has junk at the end"));
+
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = &objects[i];
+ if (obj->real_type != OBJ_BAD)
+ continue;
+ obj->real_type = obj->type;
+ sha1_object(NULL, obj, obj->size, obj->type, obj->idx.sha1);
+ nr_delays--;
+ }
+ if (nr_delays)
+ die(_("confusion beyond insanity in parse_pack_objects()"));
+}
+
+/*
+ * Second pass:
+ * - for all non-delta objects, look if it is used as a base for
+ * deltas;
+ * - if used as a base, uncompress the object and apply all deltas,
+ * recursively checking if the resulting object is used as a base
+ * for some more deltas.
+ */
+static void resolve_deltas(void)
+{
+ int i;
if (!nr_deltas)
return;
@@ -672,36 +1119,91 @@ static void parse_pack_objects(unsigned char *sha1)
qsort(deltas, nr_deltas, sizeof(struct delta_entry),
compare_delta_entry);
- /*
- * Second pass:
- * - for all non-delta objects, look if it is used as a base for
- * deltas;
- * - if used as a base, uncompress the object and apply all deltas,
- * recursively checking if the resulting object is used as a base
- * for some more deltas.
- */
if (verbose)
- progress = start_progress("Resolving deltas", nr_deltas);
+ progress = start_progress(_("Resolving deltas"), nr_deltas);
+
+#ifndef NO_PTHREADS
+ nr_dispatched = 0;
+ if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) {
+ init_thread();
+ for (i = 0; i < nr_threads; i++) {
+ int ret = pthread_create(&thread_data[i].thread, NULL,
+ threaded_second_pass, thread_data + i);
+ if (ret)
+ die(_("unable to create thread: %s"),
+ strerror(ret));
+ }
+ for (i = 0; i < nr_threads; i++)
+ pthread_join(thread_data[i].thread, NULL);
+ cleanup_thread();
+ return;
+ }
+#endif
+
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- struct base_data base_obj;
if (is_delta_type(obj->type))
continue;
- base_obj.obj = obj;
- base_obj.data = NULL;
- find_unresolved_deltas(&base_obj, NULL);
+ resolve_base(obj);
display_progress(progress, nr_resolved_deltas);
}
}
+/*
+ * Third pass:
+ * - append objects to convert thin pack to full pack if required
+ * - write the final 20-byte SHA-1
+ */
+static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved);
+static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1)
+{
+ if (nr_deltas == nr_resolved_deltas) {
+ stop_progress(&progress);
+ /* Flush remaining pack final 20-byte SHA1. */
+ flush();
+ return;
+ }
+
+ if (fix_thin_pack) {
+ struct sha1file *f;
+ unsigned char read_sha1[20], tail_sha1[20];
+ struct strbuf msg = STRBUF_INIT;
+ int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_objects_initial = nr_objects;
+ if (nr_unresolved <= 0)
+ die(_("confusion beyond insanity"));
+ REALLOC_ARRAY(objects, nr_objects + nr_unresolved + 1);
+ memset(objects + nr_objects + 1, 0,
+ nr_unresolved * sizeof(*objects));
+ f = sha1fd(output_fd, curr_pack);
+ fix_unresolved_deltas(f, nr_unresolved);
+ strbuf_addf(&msg, _("completed with %d local objects"),
+ nr_objects - nr_objects_initial);
+ stop_progress_msg(&progress, msg.buf);
+ strbuf_release(&msg);
+ sha1close(f, tail_sha1, 0);
+ hashcpy(read_sha1, pack_sha1);
+ fixup_pack_header_footer(output_fd, pack_sha1,
+ curr_pack, nr_objects,
+ read_sha1, consumed_bytes-20);
+ if (hashcmp(read_sha1, tail_sha1) != 0)
+ die(_("Unexpected tail checksum for %s "
+ "(disk corruption?)"), curr_pack);
+ }
+ if (nr_deltas != nr_resolved_deltas)
+ die(Q_("pack has %d unresolved delta",
+ "pack has %d unresolved deltas",
+ nr_deltas - nr_resolved_deltas),
+ nr_deltas - nr_resolved_deltas);
+}
+
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
{
git_zstream stream;
int status;
unsigned char outbuf[4096];
- memset(&stream, 0, sizeof(stream));
git_deflate_init(&stream, zlib_compression_level);
stream.next_in = in;
stream.avail_in = size;
@@ -714,7 +1216,7 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size)
} while (status == Z_OK);
if (status != Z_STREAM_END)
- die("unable to deflate appended object (%d)", status);
+ die(_("unable to deflate appended object (%d)"), status);
size = stream.total_out;
git_deflate_end(&stream);
return size;
@@ -783,20 +1285,20 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
for (i = 0; i < n; i++) {
struct delta_entry *d = sorted_by_pos[i];
enum object_type type;
- struct base_data base_obj;
+ struct base_data *base_obj = alloc_base_data();
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size);
- if (!base_obj.data)
+ base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size);
+ if (!base_obj->data)
continue;
- if (check_sha1_signature(d->base.sha1, base_obj.data,
- base_obj.size, typename(type)))
- die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
- base_obj.obj = append_obj_to_pack(f, d->base.sha1,
- base_obj.data, base_obj.size, type);
- find_unresolved_deltas(&base_obj, NULL);
+ if (check_sha1_signature(d->base.sha1, base_obj->data,
+ base_obj->size, typename(type)))
+ die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1));
+ base_obj->obj = append_obj_to_pack(f, d->base.sha1,
+ base_obj->data, base_obj->size, type);
+ find_unresolved_deltas(base_obj);
display_progress(progress, nr_resolved_deltas);
}
free(sorted_by_pos);
@@ -817,7 +1319,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
fsync_or_die(output_fd, curr_pack_name);
err = close(output_fd);
if (err)
- die_errno("error while closing pack file");
+ die_errno(_("error while closing pack file"));
}
if (keep_msg) {
@@ -830,16 +1332,16 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
if (keep_fd < 0) {
if (errno != EEXIST)
- die_errno("cannot write keep file '%s'",
- keep_name);
+ die_errno(_("cannot write keep file '%s'"),
+ keep_name ? keep_name : name);
} else {
if (keep_msg_len > 0) {
write_or_die(keep_fd, keep_msg, keep_msg_len);
write_or_die(keep_fd, "\n", 1);
}
if (close(keep_fd) != 0)
- die_errno("cannot close written keep file '%s'",
- keep_name);
+ die_errno(_("cannot close written keep file '%s'"),
+ keep_name ? keep_name : name);
report = "keep";
}
}
@@ -851,7 +1353,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
final_pack_name = name;
}
if (move_temp_to_file(curr_pack_name, final_pack_name))
- die("cannot store pack file");
+ die(_("cannot store pack file"));
} else if (from_stdin)
chmod(final_pack_name, 0444);
@@ -862,7 +1364,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
final_index_name = name;
}
if (move_temp_to_file(curr_index_name, final_index_name))
- die("cannot store index file");
+ die(_("cannot store index file"));
} else
chmod(final_index_name, 0444);
@@ -895,7 +1397,19 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)
if (!strcmp(k, "pack.indexversion")) {
opts->version = git_config_int(k, v);
if (opts->version > 2)
- die("bad pack.indexversion=%"PRIu32, opts->version);
+ die(_("bad pack.indexversion=%"PRIu32), opts->version);
+ return 0;
+ }
+ if (!strcmp(k, "pack.threads")) {
+ nr_threads = git_config_int(k, v);
+ if (nr_threads < 0)
+ die(_("invalid number of threads specified (%d)"),
+ nr_threads);
+#ifdef NO_PTHREADS
+ if (nr_threads != 1)
+ warning(_("no threads support, ignoring %s"), k);
+ nr_threads = 1;
+#endif
return 0;
}
return git_default_config(k, v, cb);
@@ -951,9 +1465,9 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1);
if (!p)
- die("Cannot open existing pack file '%s'", pack_name);
+ die(_("Cannot open existing pack file '%s'"), pack_name);
if (open_pack_index(p))
- die("Cannot open existing pack idx file for '%s'", pack_name);
+ die(_("Cannot open existing pack idx file for '%s'"), pack_name);
/* Read the attributes from the existing idx file */
opts->version = p->index_version;
@@ -1000,38 +1514,43 @@ static void show_pack_info(int stat_only)
}
if (baseobjects)
- printf("non delta: %d object%s\n",
- baseobjects, baseobjects > 1 ? "s" : "");
+ printf_ln(Q_("non delta: %d object",
+ "non delta: %d objects",
+ baseobjects),
+ baseobjects);
for (i = 0; i < deepest_delta; i++) {
if (!chain_histogram[i])
continue;
- printf("chain length = %d: %lu object%s\n",
- i + 1,
- chain_histogram[i],
- chain_histogram[i] > 1 ? "s" : "");
+ printf_ln(Q_("chain length = %d: %lu object",
+ "chain length = %d: %lu objects",
+ chain_histogram[i]),
+ i + 1,
+ chain_histogram[i]);
}
}
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
- int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0;
- const char *curr_pack, *curr_index;
+ int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
+ const char *curr_index;
const char *index_name = NULL, *pack_name = NULL;
const char *keep_name = NULL, *keep_msg = NULL;
- char *index_name_buf = NULL, *keep_name_buf = NULL;
+ struct strbuf index_name_buf = STRBUF_INIT,
+ keep_name_buf = STRBUF_INIT;
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);
- read_replace_refs = 0;
+ check_replace_refs = 0;
reset_pack_idx_option(&opts);
git_config(git_index_pack_config, &opts);
if (prefix && chdir(prefix))
- die("Cannot come back to cwd");
+ die(_("Cannot come back to cwd"));
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
@@ -1043,20 +1562,35 @@ 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")) {
verify = 1;
- stat = 1;
+ show_stat = 1;
} else if (!strcmp(arg, "--verify-stat-only")) {
verify = 1;
- stat = 1;
+ show_stat = 1;
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, "--pack_header=")) {
+ } else if (starts_with(arg, "--threads=")) {
+ char *end;
+ nr_threads = strtoul(arg+10, &end, 0);
+ if (!arg[10] || *end || nr_threads < 0)
+ usage(index_pack_usage);
+#ifdef NO_PTHREADS
+ if (nr_threads != 1)
+ warning(_("no threads support, "
+ "ignoring %s"), arg);
+ nr_threads = 1;
+#endif
+ } else if (starts_with(arg, "--pack_header=")) {
struct pack_header *hdr;
char *c;
@@ -1064,10 +1598,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
hdr->hdr_signature = htonl(PACK_SIGNATURE);
hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
if (*c != ',')
- die("bad %s", arg);
+ die(_("bad %s"), arg);
hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
if (*c)
- die("bad %s", arg);
+ die(_("bad %s"), arg);
input_len = sizeof(*hdr);
} else if (!strcmp(arg, "-v")) {
verbose = 1;
@@ -1075,15 +1609,15 @@ 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)
- die("bad %s", arg);
+ die(_("bad %s"), arg);
if (*c == ',')
opts.off32_limit = strtoul(c+1, &c, 0);
if (*c || opts.off32_limit & 0x80000000)
- die("bad %s", arg);
+ die(_("bad %s"), arg);
} else
usage(index_pack_usage);
continue;
@@ -1097,78 +1631,55 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (!pack_name && !from_stdin)
usage(index_pack_usage);
if (fix_thin_pack && !from_stdin)
- die("--fix-thin cannot be used without --stdin");
+ die(_("--fix-thin cannot be used without --stdin"));
if (!index_name && pack_name) {
- int len = strlen(pack_name);
- if (!has_extension(pack_name, ".pack"))
- die("packfile name '%s' does not end with '.pack'",
+ size_t len;
+ if (!strip_suffix(pack_name, ".pack", &len))
+ die(_("packfile name '%s' does not end with '.pack'"),
pack_name);
- index_name_buf = xmalloc(len);
- memcpy(index_name_buf, pack_name, len - 5);
- strcpy(index_name_buf + len - 5, ".idx");
- index_name = index_name_buf;
+ strbuf_add(&index_name_buf, pack_name, len);
+ strbuf_addstr(&index_name_buf, ".idx");
+ index_name = index_name_buf.buf;
}
if (keep_msg && !keep_name && pack_name) {
- int len = strlen(pack_name);
- if (!has_extension(pack_name, ".pack"))
- die("packfile name '%s' does not end with '.pack'",
+ size_t len;
+ if (!strip_suffix(pack_name, ".pack", &len))
+ die(_("packfile name '%s' does not end with '.pack'"),
pack_name);
- keep_name_buf = xmalloc(len);
- memcpy(keep_name_buf, pack_name, len - 5);
- strcpy(keep_name_buf + len - 5, ".keep");
- keep_name = keep_name_buf;
+ strbuf_add(&keep_name_buf, pack_name, len);
+ strbuf_addstr(&keep_name_buf, ".idx");
+ keep_name = keep_name_buf.buf;
}
if (verify) {
if (!index_name)
- die("--verify with no packfile name given");
+ die(_("--verify with no packfile name given"));
read_idx_option(&opts, index_name);
- opts.flags |= WRITE_IDX_VERIFY;
+ opts.flags |= WRITE_IDX_VERIFY | WRITE_IDX_STRICT;
}
+ if (strict)
+ opts.flags |= WRITE_IDX_STRICT;
+
+#ifndef NO_PTHREADS
+ if (!nr_threads) {
+ nr_threads = online_cpus();
+ /* An experiment showed that more threads does not mean faster */
+ if (nr_threads > 3)
+ nr_threads = 3;
+ }
+#endif
curr_pack = open_pack_file(pack_name);
parse_pack_header();
objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
parse_pack_objects(pack_sha1);
- if (nr_deltas == nr_resolved_deltas) {
- stop_progress(&progress);
- /* Flush remaining pack final 20-byte SHA1. */
- flush();
- } else {
- if (fix_thin_pack) {
- struct sha1file *f;
- unsigned char read_sha1[20], tail_sha1[20];
- char msg[48];
- int nr_unresolved = nr_deltas - nr_resolved_deltas;
- int nr_objects_initial = nr_objects;
- if (nr_unresolved <= 0)
- die("confusion beyond insanity");
- objects = xrealloc(objects,
- (nr_objects + nr_unresolved + 1)
- * sizeof(*objects));
- f = sha1fd(output_fd, curr_pack);
- fix_unresolved_deltas(f, nr_unresolved);
- sprintf(msg, "completed with %d local objects",
- nr_objects - nr_objects_initial);
- stop_progress_msg(&progress, msg);
- sha1close(f, tail_sha1, 0);
- hashcpy(read_sha1, pack_sha1);
- fixup_pack_header_footer(output_fd, pack_sha1,
- curr_pack, nr_objects,
- read_sha1, consumed_bytes-20);
- if (hashcmp(read_sha1, tail_sha1) != 0)
- die("Unexpected tail checksum for %s "
- "(disk corruption?)", curr_pack);
- }
- if (nr_deltas != nr_resolved_deltas)
- die("pack has %d unresolved deltas",
- nr_deltas - nr_resolved_deltas);
- }
+ resolve_deltas();
+ conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
free(deltas);
if (strict)
- check_objects();
+ foreign_nr = check_objects();
- if (stat)
+ if (show_stat)
show_pack_info(stat_only);
idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
@@ -1185,12 +1696,18 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
else
close(input_fd);
free(objects);
- free(index_name_buf);
- free(keep_name_buf);
+ strbuf_release(&index_name_buf);
+ strbuf_release(&keep_name_buf);
if (pack_name == NULL)
free((void *) curr_pack);
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 d07554c..9966522 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -119,15 +119,18 @@ static void copy_templates(const char *template_dir)
DIR *dir;
const char *git_dir = get_git_dir();
int len = strlen(git_dir);
+ char *to_free = NULL;
if (!template_dir)
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
if (!template_dir)
template_dir = init_db_template_dir;
if (!template_dir)
- template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
- if (!template_dir[0])
+ template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR);
+ if (!template_dir[0]) {
+ free(to_free);
return;
+ }
template_len = strlen(template_dir);
if (PATH_MAX <= (template_len+strlen("/config")))
die(_("insanely long template path %s"), template_dir);
@@ -139,7 +142,7 @@ static void copy_templates(const char *template_dir)
dir = opendir(template_path);
if (!dir) {
warning(_("templates not found %s"), template_dir);
- return;
+ goto free_return;
}
/* Make sure that template is from the correct vintage */
@@ -155,8 +158,7 @@ static void copy_templates(const char *template_dir)
"a wrong format version %d from '%s'"),
repository_format_version,
template_dir);
- closedir(dir);
- return;
+ goto close_free_return;
}
memcpy(path, git_dir, len);
@@ -166,7 +168,10 @@ static void copy_templates(const char *template_dir)
copy_templates_1(path, len,
template_path, template_len,
dir);
+close_free_return:
closedir(dir);
+free_return:
+ free(to_free);
}
static int git_init_db_config(const char *k, const char *v, void *cb)
@@ -254,7 +259,10 @@ static int create_default_files(const char *template_path)
struct stat st2;
filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) &&
!lstat(path, &st2) &&
- st1.st_mode != st2.st_mode);
+ st1.st_mode != st2.st_mode &&
+ !chmod(path, st1.st_mode));
+ if (filemode && !reinit && (st1.st_mode & S_IXUSR))
+ filemode = 0;
}
git_config_set("core.filemode", filemode ? "true" : "false");
@@ -266,7 +274,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);
}
@@ -290,6 +298,7 @@ static int create_default_files(const char *template_path)
strcpy(path + len, "CoNfIg");
if (!access(path, F_OK))
git_config_set("core.ignorecase", "true");
+ probe_utf8_pathname_composition(path, len);
}
return reinit;
@@ -329,12 +338,12 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir,
* moving the target repo later on in separate_git_dir()
*/
git_link = xstrdup(real_path(git_dir));
+ set_git_dir(real_path(real_git_dir));
}
else {
- real_git_dir = real_path(git_dir);
+ set_git_dir(real_path(git_dir));
git_link = NULL;
}
- set_git_dir(real_path(real_git_dir));
return 0;
}
@@ -351,7 +360,7 @@ static void separate_git_dir(const char *git_dir)
else if (S_ISDIR(st.st_mode))
src = git_link;
else
- die(_("unable to handle file type %d"), st.st_mode);
+ die(_("unable to handle file type %d"), (int)st.st_mode);
if (rename(src, git_dir))
die_errno(_("unable to move %s to %s"), src, git_dir);
@@ -411,11 +420,9 @@ int init_db(const char *template_dir, unsigned int flags)
if (!(flags & INIT_DB_QUIET)) {
int len = strlen(git_dir);
- /*
- * TRANSLATORS: The first '%s' is either "Reinitialized
- * existing" or "Initialized empty", the second " shared" or
- * "", and the last '%s%s' is the verbatim directory name.
- */
+ /* TRANSLATORS: The first '%s' is either "Reinitialized
+ existing" or "Initialized empty", the second " shared" or
+ "", and the last '%s%s' is the verbatim directory name. */
printf(_("%s%s Git repository in %s%s\n"),
reinit ? _("Reinitialized existing") : _("Initialized empty"),
shared_repository ? _(" shared") : "",
@@ -427,8 +434,9 @@ int init_db(const char *template_dir, unsigned int flags)
static int guess_repository_type(const char *git_dir)
{
- char cwd[PATH_MAX];
const char *slash;
+ char *cwd;
+ int cwd_is_git_dir;
/*
* "GIT_DIR=. git init" is always bare.
@@ -436,9 +444,10 @@ static int guess_repository_type(const char *git_dir)
*/
if (!strcmp(".", git_dir))
return 1;
- if (!getcwd(cwd, sizeof(cwd)))
- die_errno(_("cannot tell cwd"));
- if (!strcmp(git_dir, cwd))
+ cwd = xgetcwd();
+ cwd_is_git_dir = !strcmp(git_dir, cwd);
+ free(cwd);
+ if (cwd_is_git_dir)
return 1;
/*
* "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
@@ -463,7 +472,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset)
}
static const char *const init_db_usage[] = {
- "git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]",
+ N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]"),
NULL
};
@@ -481,17 +490,17 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
const char *template_dir = NULL;
unsigned int flags = 0;
const struct option init_db_options[] = {
- OPT_STRING(0, "template", &template_dir, "template-directory",
- "directory from which templates will be used"),
+ OPT_STRING(0, "template", &template_dir, N_("template-directory"),
+ N_("directory from which templates will be used")),
OPT_SET_INT(0, "bare", &is_bare_repository_cfg,
- "create a bare repository", 1),
+ N_("create a bare repository"), 1),
{ OPTION_CALLBACK, 0, "shared", &init_shared_repository,
- "permissions",
- "specify that the git repository is to be shared amongst several users",
+ N_("permissions"),
+ N_("specify that the git repository is to be shared amongst several users"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0},
- OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET),
- OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir",
- "separate git dir from working tree"),
+ OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
+ OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
+ N_("separate git dir from working tree")),
OPT_END()
};
@@ -514,13 +523,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;
@@ -535,10 +545,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
usage(init_db_usage[0]);
}
if (is_bare_repository_cfg == 1) {
- static char git_dir[PATH_MAX+1];
-
- setenv(GIT_DIR_ENVIRONMENT,
- getcwd(git_dir, sizeof(git_dir)), argc > 0);
+ char *cwd = xgetcwd();
+ setenv(GIT_DIR_ENVIRONMENT, cwd, argc > 0);
+ free(cwd);
}
if (init_shared_repository != -1)
@@ -572,13 +581,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
git_work_tree_cfg = xstrdup(real_path(rel));
free(rel);
}
- if (!git_work_tree_cfg) {
- git_work_tree_cfg = xcalloc(PATH_MAX, 1);
- if (!getcwd(git_work_tree_cfg, PATH_MAX))
- die_errno (_("Cannot access current working directory"));
- }
+ if (!git_work_tree_cfg)
+ git_work_tree_cfg = xgetcwd();
if (work_tree)
- set_git_work_tree(real_path(work_tree));
+ set_git_work_tree(work_tree);
else
set_git_work_tree(git_work_tree_cfg);
if (access(get_git_work_tree(), X_OK))
@@ -587,7 +593,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
}
else {
if (work_tree)
- set_git_work_tree(real_path(work_tree));
+ set_git_work_tree(work_tree);
}
set_git_dir_init(git_dir, real_git_dir, 1);
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
new file mode 100644
index 0000000..46838d2
--- /dev/null
+++ b/builtin/interpret-trailers.c
@@ -0,0 +1,44 @@
+/*
+ * Builtin "git interpret-trailers"
+ *
+ * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
+ *
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "trailer.h"
+
+static const char * const git_interpret_trailers_usage[] = {
+ N_("git interpret-trailers [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"),
+ NULL
+};
+
+int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
+{
+ int trim_empty = 0;
+ struct string_list trailers = STRING_LIST_INIT_DUP;
+
+ struct option options[] = {
+ OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")),
+ OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"),
+ N_("trailer(s) to add")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_interpret_trailers_usage, 0);
+
+ if (argc) {
+ int i;
+ for (i = 0; i < argc; i++)
+ process_trailers(argv[i], trim_empty, &trailers);
+ } else
+ process_trailers(NULL, trim_empty, &trailers);
+
+ string_list_clear(&trailers, 0);
+
+ return 0;
+}
diff --git a/builtin/log.c b/builtin/log.c
index f5d4930..a131992 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -19,6 +19,12 @@
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
+#include "line-log.h"
+#include "branch.h"
+#include "streaming.h"
+#include "version.h"
+#include "mailmap.h"
+#include "gpg-interface.h"
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
@@ -27,15 +33,22 @@ static int default_abbrev_commit;
static int default_show_root = 1;
static int decoration_style;
static int decoration_given;
+static int use_mailmap_config;
static const char *fmt_patch_subject_prefix = "PATCH";
static const char *fmt_pretty;
static const char * const builtin_log_usage[] = {
- "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
- " or: git show [options] <object>...",
+ N_("git log [<options>] [<revision range>] [[--] <path>...]"),
+ N_("git show [options] <object>..."),
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)) {
@@ -50,6 +63,8 @@ static int parse_decoration_style(const char *var, const char *value)
return DECORATE_FULL_REFS;
else if (!strcmp(value, "short"))
return DECORATE_SHORT_REFS;
+ else if (!strcmp(value, "auto"))
+ return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0;
return -1;
}
@@ -63,21 +78,34 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
decoration_style = DECORATE_SHORT_REFS;
if (decoration_style < 0)
- die("invalid --decorate option: %s", arg);
+ die(_("invalid --decorate option: %s"), arg);
decoration_given = 1;
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)
{
- rev->abbrev = DEFAULT_ABBREV;
- rev->commit_format = CMIT_FMT_DEFAULT;
if (fmt_pretty)
get_commit_format(fmt_pretty, rev);
rev->verbose_header = 1;
DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
+ rev->diffopt.stat_width = -1; /* use full terminal width */
+ rev->diffopt.stat_graph_width = -1; /* respect statGraphWidth config */
rev->abbrev_commit = default_abbrev_commit;
rev->show_root_diff = default_show_root;
rev->subject_prefix = fmt_patch_subject_prefix;
@@ -85,34 +113,44 @@ 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,
struct rev_info *rev, struct setup_revision_opt *opt)
{
struct userformat_want w;
- int quiet = 0, source = 0;
+ 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_BOOLEAN(0, "quiet", &quiet, "suppress diff output"),
- OPT_BOOLEAN(0, "source", &source, "show source"),
- { OPTION_CALLBACK, 0, "decorate", NULL, NULL, "decorate options",
+ OPT__QUIET(&quiet, N_("suppress diff output")),
+ OPT_BOOL(0, "source", &source, N_("show source")),
+ OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
+ { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback},
+ OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
+ N_("Process line range n,m in file, counting from 1"),
+ 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,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
PARSE_OPT_KEEP_DASHDASH);
- argc = setup_revisions(argc, argv, rev, opt);
if (quiet)
rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT;
+ argc = setup_revisions(argc, argv, rev, opt);
/* Any arguments at this point are not recognized */
if (argc > 1)
- die("unrecognized argument: %s", argv[1]);
+ die(_("unrecognized argument: %s"), argv[1]);
memset(&w, 0, sizeof(w));
userformat_find_requirements(NULL, &w);
@@ -122,17 +160,18 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
if (rev->show_notes)
init_display_notes(&rev->notes_opt);
- if (rev->diffopt.pickaxe || rev->diffopt.filter)
- rev->always_show_header = 0;
- if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
+ if (rev->diffopt.pickaxe || rev->diffopt.filter ||
+ DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES))
rev->always_show_header = 0;
- if (rev->diffopt.pathspec.nr != 1)
- usage("git logs can only follow renames on one pathname at a time");
- }
if (source)
rev->show_source = 1;
+ if (mailmap) {
+ rev->mailmap = xcalloc(1, sizeof(struct string_list));
+ read_mailmap(rev->mailmap, NULL);
+ }
+
if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) {
/*
* "log --pretty=raw" is special; ignore UI oriented
@@ -148,6 +187,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();
}
@@ -193,7 +236,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)) {
@@ -304,8 +347,7 @@ static int cmd_log_walk(struct rev_info *rev)
rev->max_count++;
if (!rev->reflog_info) {
/* we allow cycles in reflog ancestry */
- free(commit->buffer);
- commit->buffer = NULL;
+ free_commit_buffer(commit);
}
free_commit_list(commit->parents);
commit->parents = NULL;
@@ -326,6 +368,8 @@ static int cmd_log_walk(struct rev_info *rev)
static int git_log_config(const char *var, const char *value, void *cb)
{
+ const char *slot_name;
+
if (!strcmp(var, "format.pretty"))
return git_config_string(&fmt_pretty, var, value);
if (!strcmp(var, "format.subjectprefix"))
@@ -346,9 +390,17 @@ 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."))
- return parse_decorate_color_config(var, 15, value);
+ if (skip_prefix(var, "color.decorate.", &slot_name))
+ return parse_decorate_color_config(var, slot_name, value);
+ if (!strcmp(var, "log.mailmap")) {
+ use_mailmap_config = git_config_bool(var, value);
+ return 0;
+ }
+ if (grep_config(var, value, cb) < 0)
+ return -1;
+ if (git_gpg_config(var, value, cb) < 0)
+ return -1;
return git_diff_ui_config(var, value, cb);
}
@@ -357,6 +409,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct setup_revision_opt opt;
+ init_grep_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
@@ -364,6 +417,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
rev.simplify_history = 0;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
+ opt.revarg_opt = REVARG_COMMITTISH;
cmd_log_init(argc, argv, prefix, &rev, &opt);
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_RAW;
@@ -382,8 +436,32 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
strbuf_release(&out);
}
-static int show_object(const unsigned char *sha1, int show_tag_object,
- 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);
+ 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)
{
unsigned long size;
enum object_type type;
@@ -393,16 +471,16 @@ static int show_object(const unsigned char *sha1, int show_tag_object,
if (!buf)
return error(_("Could not read object %s"), sha1_to_hex(sha1));
- if (show_tag_object)
- while (offset < size && buf[offset] != '\n') {
- int new_offset = offset + 1;
- while (new_offset < size && buf[new_offset++] != '\n')
- ; /* do nothing */
- if (!prefixcmp(buf + offset, "tagger "))
- show_tagger(buf + offset + 7,
- new_offset - offset - 7, rev);
- offset = new_offset;
- }
+ assert(type == OBJ_TAG);
+ while (offset < size && buf[offset] != '\n') {
+ int new_offset = offset + 1;
+ while (new_offset < size && buf[new_offset++] != '\n')
+ ; /* do nothing */
+ if (starts_with(buf + offset, "tagger "))
+ show_tagger(buf + offset + 7,
+ new_offset - offset - 7, rev);
+ offset = new_offset;
+ }
if (offset < size)
fwrite(buf + offset, size - offset, 1, stdout);
@@ -411,7 +489,7 @@ static int show_object(const unsigned char *sha1, int show_tag_object,
}
static int show_tree_object(const unsigned char *sha1,
- const char *base, int baselen,
+ struct strbuf *base,
const char *pathname, unsigned mode, int stage, void *context)
{
printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
@@ -424,7 +502,7 @@ static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *
/* There was no "-m" on the command line */
rev->ignore_merges = 0;
if (!rev->first_parent_only && !rev->combine_merges) {
- /* No "--first-parent", "-c", nor "--cc" */
+ /* No "--first-parent", "-c", or "--cc" */
rev->combine_merges = 1;
rev->dense_combined_merges = 1;
}
@@ -441,18 +519,24 @@ int cmd_show(int argc, const char **argv, const char *prefix)
struct pathspec match_all;
int i, count, ret = 0;
+ 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;
- rev.no_walk = 1;
+ rev.no_walk = REVISION_WALK_NO_WALK_SORTED;
+ rev.diffopt.stat_width = -1; /* Scale to real terminal size */
+
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
opt.tweak = show_rev_tweak_rev;
cmd_log_init(argc, argv, prefix, &rev, &opt);
+ if (!rev.no_walk)
+ return cmd_log_walk(&rev);
+
count = rev.pending.nr;
objects = rev.pending.objects;
for (i = 0; i < count && !ret; i++) {
@@ -460,7 +544,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_object(o->sha1, 0, NULL);
+ ret = show_blob_object(o->sha1, &rev, name);
break;
case OBJ_TAG: {
struct tag *t = (struct tag *)o;
@@ -471,7 +555,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
t->tag,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- ret = show_object(o->sha1, 1, &rev);
+ ret = show_tag_object(o->sha1, &rev);
rev.shown_one = 1;
if (ret)
break;
@@ -516,6 +600,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct setup_revision_opt opt;
+ init_grep_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
@@ -538,12 +623,14 @@ int cmd_log(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct setup_revision_opt opt;
+ init_grep_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
rev.always_show_header = 1;
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
+ opt.revarg_opt = REVARG_COMMITTISH;
cmd_log_init(argc, argv, prefix, &rev, &opt);
return cmd_log_walk(&rev);
}
@@ -585,6 +672,15 @@ static void add_header(const char *value)
static int thread;
static int do_signoff;
static const char *signature = git_version_string;
+static const char *signature_file;
+static int config_cover_letter;
+
+enum {
+ COVER_UNSET,
+ COVER_OFF,
+ COVER_ON,
+ COVER_AUTO
+};
static int git_format_config(const char *var, const char *value, void *cb)
{
@@ -609,7 +705,7 @@ static int git_format_config(const char *var, const char *value, void *cb)
return 0;
}
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") ||
- !strcmp(var, "color.ui")) {
+ !strcmp(var, "color.ui") || !strcmp(var, "diff.submodule")) {
return 0;
}
if (!strcmp(var, "format.numbered")) {
@@ -646,6 +742,16 @@ static int git_format_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "format.signature"))
return git_config_string(&signature, var, value);
+ if (!strcmp(var, "format.signaturefile"))
+ return git_config_pathname(&signature_file, var, value);
+ if (!strcmp(var, "format.coverletter")) {
+ if (value && !strcasecmp(value, "auto")) {
+ config_cover_letter = COVER_AUTO;
+ return 0;
+ }
+ config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
+ return 0;
+ }
return git_log_config(var, value, cb);
}
@@ -654,10 +760,11 @@ static FILE *realstdout = NULL;
static const char *output_directory = NULL;
static int outdir_offset;
-static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet)
+static int reopen_stdout(struct commit *commit, const char *subject,
+ struct rev_info *rev, int quiet)
{
struct strbuf filename = STRBUF_INIT;
- int suffix_len = strlen(fmt_patch_suffix) + 1;
+ int suffix_len = strlen(rev->patch_suffix) + 1;
if (output_directory) {
strbuf_addstr(&filename, output_directory);
@@ -668,7 +775,12 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet)
strbuf_addch(&filename, '/');
}
- get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
+ if (rev->numbered_files)
+ strbuf_addf(&filename, "%d", rev->nr);
+ else if (commit)
+ fmt_output_commit(&filename, commit, rev);
+ else
+ fmt_output_subject(&filename, subject, rev);
if (!quiet)
fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
@@ -680,7 +792,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet)
return 0;
}
-static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
+static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
{
struct rev_info check_rev;
struct commit *commit;
@@ -701,7 +813,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
init_patch_ids(ids);
/* given a range a..b get all patch ids for b..a */
- init_revisions(&check_rev, prefix);
+ init_revisions(&check_rev, rev->prefix);
+ check_rev.max_parents = 1;
o1->flags ^= UNINTERESTING;
o2->flags ^= UNINTERESTING;
add_pending_object(&check_rev, o1, "o1");
@@ -710,10 +823,6 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
die(_("revision walk setup failed"));
while ((commit = get_revision(&check_rev)) != NULL) {
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
add_commit_patch_id(commit, ids);
}
@@ -728,28 +837,70 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
static void gen_message_id(struct rev_info *info, char *base)
{
- const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
- const char *email_start = strrchr(committer, '<');
- const char *email_end = strrchr(committer, '>');
struct strbuf buf = STRBUF_INIT;
- if (!email_start || !email_end || email_start > email_end - 1)
- die(_("Could not extract email from committer identity."));
- strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
+ strbuf_addf(&buf, "%s.%lu.git.%s", base,
(unsigned long) time(NULL),
- (int)(email_end - email_start - 1), email_start + 1);
+ git_committer_info(IDENT_NO_NAME|IDENT_NO_DATE|IDENT_STRICT));
info->message_id = strbuf_detach(&buf, NULL);
}
static void print_signature(void)
{
- if (signature && *signature)
- printf("-- \n%s\n\n", signature);
+ if (!signature || !*signature)
+ return;
+
+ printf("-- \n%s", signature);
+ if (signature[strlen(signature)-1] != '\n')
+ putchar('\n');
+ putchar('\n');
+}
+
+static void add_branch_description(struct strbuf *buf, const char *branch_name)
+{
+ struct strbuf desc = STRBUF_INIT;
+ if (!branch_name || !*branch_name)
+ return;
+ read_branch_desc(&desc, branch_name);
+ if (desc.len) {
+ strbuf_addch(buf, '\n');
+ strbuf_addbuf(buf, &desc);
+ strbuf_addch(buf, '\n');
+ }
+ strbuf_release(&desc);
+}
+
+static char *find_branch_name(struct rev_info *rev)
+{
+ int i, positive = -1;
+ unsigned char branch_sha1[20];
+ const unsigned char *tip_sha1;
+ const char *ref, *v;
+ char *full_ref, *branch = NULL;
+
+ for (i = 0; i < rev->cmdline.nr; i++) {
+ if (rev->cmdline.rev[i].flags & UNINTERESTING)
+ continue;
+ if (positive < 0)
+ positive = i;
+ else
+ return NULL;
+ }
+ if (positive < 0)
+ return NULL;
+ ref = rev->cmdline.rev[positive].name;
+ tip_sha1 = rev->cmdline.rev[positive].item->sha1;
+ if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) &&
+ skip_prefix(full_ref, "refs/heads/", &v) &&
+ !hashcmp(tip_sha1, branch_sha1))
+ branch = xstrdup(v);
+ free(full_ref);
+ return branch;
}
static void make_cover_letter(struct rev_info *rev, int use_stdout,
- int numbered, int numbered_files,
struct commit *origin,
- int nr, struct commit **list, struct commit *head,
+ int nr, struct commit **list,
+ const char *branch_name,
int quiet)
{
const char *committer;
@@ -761,45 +912,30 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
const char *encoding = "UTF-8";
struct diff_options opts;
int need_8bit_cte = 0;
- struct commit *commit = NULL;
struct pretty_print_context pp = {0};
+ struct commit *head = list[0];
if (rev->commit_format != CMIT_FMT_EMAIL)
die(_("Cover letter needs email format"));
committer = git_committer_info(0);
- if (!numbered_files) {
- /*
- * We fake a commit for the cover letter so we get the filename
- * desired.
- */
- commit = xcalloc(1, sizeof(*commit));
- commit->buffer = xmalloc(400);
- snprintf(commit->buffer, 400,
- "tree 0000000000000000000000000000000000000000\n"
- "parent %s\n"
- "author %s\n"
- "committer %s\n\n"
- "cover letter\n",
- sha1_to_hex(head->object.sha1), committer, committer);
- }
-
- if (!use_stdout && reopen_stdout(commit, rev, quiet))
+ if (!use_stdout &&
+ reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
return;
- if (commit) {
-
- free(commit->buffer);
- free(commit);
- }
-
log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
&need_8bit_cte);
- for (i = 0; !need_8bit_cte && i < nr; i++)
- if (has_non_ascii(list[i]->buffer))
+ for (i = 0; !need_8bit_cte && i < nr; i++) {
+ const char *buf = get_commit_buffer(list[i], NULL);
+ if (has_non_ascii(buf))
need_8bit_cte = 1;
+ unuse_commit_buffer(list[i], buf);
+ }
+
+ if (!branch_name)
+ branch_name = find_branch_name(rev);
msg = body;
pp.fmt = CMIT_FMT_EMAIL;
@@ -807,6 +943,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
pp_user_info(&pp, NULL, &sb, committer, encoding);
pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
pp_remainder(&pp, &msg, &sb, 0);
+ add_branch_description(&sb, branch_name);
printf("%s\n", sb.buf);
strbuf_release(&sb);
@@ -886,7 +1023,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory)
}
static const char * const builtin_format_patch_usage[] = {
- "git format-patch [options] [<since> | <revision range>]",
+ N_("git format-patch [options] [<since> | <revision range>]"),
NULL
};
@@ -1006,6 +1143,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;
@@ -1015,81 +1167,90 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int nr = 0, total, i;
int use_stdout = 0;
int start_number = -1;
- int numbered_files = 0; /* _just_ numbers */
+ int just_numbers = 0;
int ignore_if_in_upstream = 0;
- int cover_letter = 0;
+ int cover_letter = -1;
int boundary_count = 0;
int no_binary_diff = 0;
- struct commit *origin = NULL, *head = NULL;
+ struct commit *origin = NULL;
const char *in_reply_to = NULL;
struct patch_ids ids;
- char *add_signoff = NULL;
struct strbuf buf = STRBUF_INIT;
int use_patch_format = 0;
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,
- "use [PATCH n/m] even with a single patch",
+ N_("use [PATCH n/m] even with a single patch"),
PARSE_OPT_NOARG, numbered_callback },
{ OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
- "use [PATCH] even with multiple patches",
+ N_("use [PATCH] even with multiple patches"),
PARSE_OPT_NOARG, no_numbered_callback },
- OPT_BOOLEAN('s', "signoff", &do_signoff, "add Signed-off-by:"),
- OPT_BOOLEAN(0, "stdout", &use_stdout,
- "print patches to standard out"),
- OPT_BOOLEAN(0, "cover-letter", &cover_letter,
- "generate a cover letter"),
- OPT_BOOLEAN(0, "numbered-files", &numbered_files,
- "use simple number sequence for output file names"),
- OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx",
- "use <sfx> instead of '.patch'"),
+ OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
+ OPT_BOOL(0, "stdout", &use_stdout,
+ N_("print patches to standard out")),
+ OPT_BOOL(0, "cover-letter", &cover_letter,
+ N_("generate a cover letter")),
+ OPT_BOOL(0, "numbered-files", &just_numbers,
+ N_("use simple number sequence for output file names")),
+ OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
+ N_("use <sfx> instead of '.patch'")),
OPT_INTEGER(0, "start-number", &start_number,
- "start numbering patches at <n> instead of 1"),
- { OPTION_CALLBACK, 0, "subject-prefix", &rev, "prefix",
- "Use [<prefix>] instead of [PATCH]",
+ N_("start numbering patches at <n> instead of 1")),
+ OPT_INTEGER('v', "reroll-count", &reroll_count,
+ N_("mark the series as Nth re-roll")),
+ { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
+ N_("Use [<prefix>] instead of [PATCH]"),
PARSE_OPT_NONEG, subject_prefix_callback },
{ OPTION_CALLBACK, 'o', "output-directory", &output_directory,
- "dir", "store resulting files in <dir>",
+ N_("dir"), N_("store resulting files in <dir>"),
PARSE_OPT_NONEG, output_directory_callback },
{ OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
- "don't strip/add [PATCH]",
+ N_("don't strip/add [PATCH]"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
- OPT_BOOLEAN(0, "no-binary", &no_binary_diff,
- "don't output binary diffs"),
- OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
- "don't include a patch matching a commit upstream"),
- { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
- "show patch format instead of default (patch + stat)",
- PARSE_OPT_NONEG | PARSE_OPT_NOARG },
- OPT_GROUP("Messaging"),
- { OPTION_CALLBACK, 0, "add-header", NULL, "header",
- "add email header", 0, header_callback },
- { OPTION_CALLBACK, 0, "to", NULL, "email", "add To: header",
+ 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, NULL, 1},
+ OPT_GROUP(N_("Messaging")),
+ { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"),
+ N_("add email header"), 0, header_callback },
+ { OPTION_CALLBACK, 0, "to", NULL, N_("email"), N_("add To: header"),
0, to_callback },
- { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header",
+ { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
0, cc_callback },
- OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id",
- "make first mail a reply to <message-id>"),
- { OPTION_CALLBACK, 0, "attach", &rev, "boundary",
- "attach the patch", PARSE_OPT_OPTARG,
+ { 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"),
+ N_("attach the patch"), PARSE_OPT_OPTARG,
attach_callback },
- { OPTION_CALLBACK, 0, "inline", &rev, "boundary",
- "inline the patch",
+ { OPTION_CALLBACK, 0, "inline", &rev, N_("boundary"),
+ N_("inline the patch"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
inline_callback },
- { OPTION_CALLBACK, 0, "thread", &thread, "style",
- "enable message threading, styles: shallow, deep",
+ { OPTION_CALLBACK, 0, "thread", &thread, N_("style"),
+ N_("enable message threading, styles: shallow, deep"),
PARSE_OPT_OPTARG, thread_callback },
- OPT_STRING(0, "signature", &signature, "signature",
- "add a signature"),
- OPT_BOOLEAN(0, "quiet", &quiet,
- "don't print the patch filenames"),
+ OPT_STRING(0, "signature", &signature, N_("signature"),
+ N_("add a signature")),
+ OPT_FILENAME(0, "signature-file", &signature_file,
+ N_("add a signature from a file")),
+ OPT__QUIET(&quiet, N_("don't print the patch filenames")),
OPT_END()
};
extra_hdr.strdup_strings = 1;
extra_to.strdup_strings = 1;
extra_cc.strdup_strings = 1;
+ init_grep_defaults();
git_config(git_format_config, NULL);
init_revisions(&rev, prefix);
rev.commit_format = CMIT_FMT_EMAIL;
@@ -1100,6 +1261,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.subject_prefix = fmt_patch_subject_prefix;
memset(&s_r_opt, 0, sizeof(s_r_opt));
s_r_opt.def = "HEAD";
+ s_r_opt.revarg_opt = REVARG_COMMITTISH;
if (default_attach) {
rev.mime_boundary = default_attach;
@@ -1116,14 +1278,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
PARSE_OPT_KEEP_DASHDASH);
- if (do_signoff) {
- const char *committer;
- const char *endpos;
- committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
- endpos = strchr(committer, '>');
- if (!endpos)
- die(_("bogus committer info %s"), committer);
- add_signoff = xmemdupz(committer, endpos - committer + 1);
+ if (0 < reroll_count) {
+ struct strbuf sprefix = STRBUF_INIT;
+ strbuf_addf(&sprefix, "%s v%d",
+ rev.subject_prefix, reroll_count);
+ rev.reroll_count = reroll_count;
+ rev.subject_prefix = strbuf_detach(&sprefix, NULL);
}
for (i = 0; i < extra_hdr.nr; i++) {
@@ -1155,6 +1315,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;
@@ -1211,6 +1376,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
}
if (rev.pending.nr == 1) {
+ int check_head = 0;
+
if (rev.max_count < 0 && !rev.show_root_diff) {
/*
* This is traditional behaviour of "git format-patch
@@ -1219,12 +1386,27 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
*/
rev.pending.objects[0].item->flags |= UNINTERESTING;
add_head_to_pending(&rev);
+ check_head = 1;
}
/*
* Otherwise, it is "format-patch -22 HEAD", and/or
* "format-patch --root HEAD". The user wants
* get_revision() to do the usual traversal.
*/
+
+ if (!strcmp(rev.pending.objects[0].name, "HEAD"))
+ check_head = 1;
+
+ if (check_head) {
+ unsigned char sha1[20];
+ const char *ref, *v;
+ ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+ sha1, NULL);
+ if (ref && skip_prefix(ref, "refs/heads/", &v))
+ branch_name = xstrdup(v);
+ else
+ branch_name = xstrdup(""); /* no branch */
+ }
}
/*
@@ -1233,19 +1415,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
*/
rev.show_root_diff = 1;
- if (cover_letter) {
- /* remember the range */
- int i;
- for (i = 0; i < rev.pending.nr; i++) {
- struct object *o = rev.pending.objects[i].item;
- if (!(o->flags & UNINTERESTING))
- head = (struct commit *)o;
- }
- /* We can't generate a cover letter without any patches */
- if (!head)
- return 0;
- }
-
if (ignore_if_in_upstream) {
/* Don't say anything if head and upstream are the same. */
if (rev.pending.nr == 2) {
@@ -1253,7 +1422,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
return 0;
}
- get_patch_ids(&rev, &ids, prefix);
+ get_patch_ids(&rev, &ids);
}
if (!use_stdout)
@@ -1274,31 +1443,53 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
continue;
nr++;
- list = xrealloc(list, nr * sizeof(list[0]));
+ REALLOC_ARRAY(list, nr);
list[nr - 1] = commit;
}
+ if (nr == 0)
+ /* nothing to do */
+ return 0;
total = nr;
if (!keep_subject && auto_number && total > 1)
numbered = 1;
if (numbered)
rev.total = total + start_number - 1;
+ if (cover_letter == -1) {
+ if (config_cover_letter == COVER_AUTO)
+ cover_letter = (total > 1);
+ else
+ cover_letter = (config_cover_letter == COVER_ON);
+ }
+
+ if (!signature) {
+ ; /* --no-signature inhibits all signatures */
+ } else if (signature && signature != git_version_string) {
+ ; /* non-default signature already set */
+ } else if (signature_file) {
+ struct strbuf buf = STRBUF_INIT;
+
+ if (strbuf_read_file(&buf, signature_file, 128) < 0)
+ die_errno(_("unable to read signature file '%s'"), signature_file);
+ signature = strbuf_detach(&buf, NULL);
+ }
+
if (in_reply_to || thread || cover_letter)
rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
if (in_reply_to) {
const char *msgid = clean_message_id(in_reply_to);
string_list_append(rev.ref_message_ids, msgid);
}
- rev.numbered_files = numbered_files;
+ rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
if (cover_letter) {
if (thread)
gen_message_id(&rev, "cover");
- make_cover_letter(&rev, use_stdout, numbered, numbered_files,
- origin, nr, list, head, quiet);
+ make_cover_letter(&rev, use_stdout,
+ origin, nr, list, branch_name, quiet);
total++;
start_number--;
}
- rev.add_signoff = add_signoff;
+ rev.add_signoff = do_signoff;
while (0 <= --nr) {
int shown;
commit = list[nr];
@@ -1339,12 +1530,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
}
- if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
- &rev, quiet))
+ if (!use_stdout &&
+ reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet))
die(_("Failed to create output files"));
shown = log_tree_commit(&rev, commit);
- free(commit->buffer);
- commit->buffer = NULL;
+ free_commit_buffer(commit);
/* We put one extra blank line between formatted
* patches and this flag is used by log-tree code
@@ -1366,6 +1556,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
fclose(stdout);
}
free(list);
+ free(branch_name);
string_list_clear(&extra_to, 0);
string_list_clear(&extra_cc, 0);
string_list_clear(&extra_hdr, 0);
@@ -1389,7 +1580,7 @@ static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
}
static const char * const cherry_usage[] = {
- "git cherry [-v] [<upstream> [<head> [<limit>]]]",
+ N_("git cherry [-v] [<upstream> [<head> [<limit>]]]"),
NULL
};
@@ -1423,7 +1614,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT__ABBREV(&abbrev),
- OPT__VERBOSE(&verbose, "be verbose"),
+ OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_END()
};
@@ -1454,10 +1645,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
}
init_revisions(&revs, prefix);
- revs.diff = 1;
- revs.combine_merges = 0;
- revs.ignore_merges = 1;
- DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+ revs.max_parents = 1;
if (add_pending_commit(head, &revs, 0))
die(_("Unknown commit %s"), head);
@@ -1471,7 +1659,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
return 0;
}
- get_patch_ids(&revs, &ids, prefix);
+ get_patch_ids(&revs, &ids);
if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
die(_("Unknown commit %s"), limit);
@@ -1480,10 +1668,6 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
while ((commit = get_revision(&revs)) != NULL) {
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
commit_list_insert(commit, &list);
}
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index e8a800d..19063eb 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,11 +31,12 @@ 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;
static int exc_given;
+static int exclude_args;
static const char *tag_cached = "";
static const char *tag_unmerged = "";
@@ -45,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)
@@ -58,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)
@@ -126,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 &&
@@ -162,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);
}
}
@@ -187,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])
@@ -195,29 +206,36 @@ 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, const struct cache_entry *ce)
+{
+ int dtype = ce_to_dtype(ce);
+ return is_excluded(dir, ce->name, &dtype);
+}
+
static void show_files(struct dir_struct *dir)
{
int i;
/* 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];
- int dtype = ce_to_dtype(ce);
- if (dir->flags & DIR_SHOW_IGNORED &&
- !excluded(dir, ce->name, &dtype))
+ const struct cache_entry *ce = active_cache[i];
+ if ((dir->flags & DIR_SHOW_IGNORED) &&
+ !ce_excluded(dir, ce))
continue;
if (show_unmerged && !ce_stage(ce))
continue;
@@ -227,14 +245,13 @@ 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;
- int dtype = ce_to_dtype(ce);
- if (dir->flags & DIR_SHOW_IGNORED &&
- !excluded(dir, ce->name, &dtype))
+ if ((dir->flags & DIR_SHOW_IGNORED) &&
+ !ce_excluded(dir, ce))
continue;
if (ce->ce_flags & CE_UPDATE)
continue;
@@ -266,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;
@@ -276,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
@@ -322,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].use_wildcard = 0;
+ 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);
@@ -353,48 +354,8 @@ 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)
-{
- /*
- * 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++) {
- int other, found_dup;
-
- if (ps_matched[num])
- continue;
- /*
- * The caller might have fed identical pathspec
- * twice. Do not barf on such a mistake.
- */
- for (found_dup = other = 0;
- !found_dup && pathspec[other];
- other++) {
- if (other == num || !ps_matched[other])
- continue;
- if (!strcmp(pathspec[other], pathspec[num]))
- /*
- * Ok, we have a match already.
- */
- found_dup = 1;
- }
- 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);
- errors++;
- }
- strbuf_release(&sb);
- return errors;
-}
-
static const char * const ls_files_usage[] = {
- "git ls-files [options] [<file>...]",
+ N_("git ls-files [options] [<file>...]"),
NULL
};
@@ -409,10 +370,10 @@ static int option_parse_z(const struct option *opt,
static int option_parse_exclude(const struct option *opt,
const char *arg, int unset)
{
- struct exclude_list *list = opt->value;
+ struct string_list *exclude_list = opt->value;
exc_given = 1;
- add_exclude(arg, "", 0, list);
+ string_list_append(exclude_list, arg);
return 0;
}
@@ -441,62 +402,64 @@ static int option_parse_exclude_standard(const struct option *opt,
int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{
- int require_work_tree = 0, show_tag = 0;
+ int require_work_tree = 0, show_tag = 0, i;
const char *max_prefix;
struct dir_struct dir;
+ struct exclude_list *el;
+ struct string_list exclude_list = STRING_LIST_INIT_NODUP;
struct option builtin_ls_files_options[] = {
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- "paths are separated with NUL character",
+ N_("paths are separated with NUL character"),
PARSE_OPT_NOARG, option_parse_z },
- OPT_BOOLEAN('t', NULL, &show_tag,
- "identify the file status with tags"),
- OPT_BOOLEAN('v', NULL, &show_valid_bit,
- "use lowercase letters for 'assume unchanged' files"),
- OPT_BOOLEAN('c', "cached", &show_cached,
- "show cached files in the output (default)"),
- OPT_BOOLEAN('d', "deleted", &show_deleted,
- "show deleted files in the output"),
- OPT_BOOLEAN('m', "modified", &show_modified,
- "show modified files in the output"),
- OPT_BOOLEAN('o', "others", &show_others,
- "show other files in the output"),
+ OPT_BOOL('t', NULL, &show_tag,
+ N_("identify the file status with tags")),
+ OPT_BOOL('v', NULL, &show_valid_bit,
+ N_("use lowercase letters for 'assume unchanged' files")),
+ OPT_BOOL('c', "cached", &show_cached,
+ N_("show cached files in the output (default)")),
+ OPT_BOOL('d', "deleted", &show_deleted,
+ N_("show deleted files in the output")),
+ OPT_BOOL('m', "modified", &show_modified,
+ N_("show modified files in the output")),
+ OPT_BOOL('o', "others", &show_others,
+ N_("show other files in the output")),
OPT_BIT('i', "ignored", &dir.flags,
- "show ignored files in the output",
+ N_("show ignored files in the output"),
DIR_SHOW_IGNORED),
- OPT_BOOLEAN('s', "stage", &show_stage,
- "show staged contents' object name in the output"),
- OPT_BOOLEAN('k', "killed", &show_killed,
- "show files on the filesystem that need to be removed"),
+ OPT_BOOL('s', "stage", &show_stage,
+ N_("show staged contents' object name in the output")),
+ OPT_BOOL('k', "killed", &show_killed,
+ N_("show files on the filesystem that need to be removed")),
OPT_BIT(0, "directory", &dir.flags,
- "show 'other' directories' name only",
+ N_("show 'other' directories' names only"),
DIR_SHOW_OTHER_DIRECTORIES),
OPT_NEGBIT(0, "empty-directory", &dir.flags,
- "don't show empty directories",
+ N_("don't show empty directories"),
DIR_HIDE_EMPTY_DIRECTORIES),
- OPT_BOOLEAN('u', "unmerged", &show_unmerged,
- "show unmerged files in the output"),
- OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
- "show resolve-undo information"),
- { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
- "skip files matching pattern",
+ OPT_BOOL('u', "unmerged", &show_unmerged,
+ N_("show unmerged files in the output")),
+ OPT_BOOL(0, "resolve-undo", &show_resolve_undo,
+ N_("show resolve-undo information")),
+ { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"),
+ N_("skip files matching pattern"),
0, option_parse_exclude },
- { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
- "exclude patterns are read from <file>",
+ { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"),
+ N_("exclude patterns are read from <file>"),
0, option_parse_exclude_from },
- OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file",
- "read additional per-directory exclude patterns in <file>"),
+ OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"),
+ N_("read additional per-directory exclude patterns in <file>")),
{ OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
- "add the standard git exclusions",
+ N_("add the standard git exclusions"),
PARSE_OPT_NOARG, option_parse_exclude_standard },
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
- "make the output relative to the project top directory",
+ N_("make the output relative to the project top directory"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
- OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
- "if any <file> is not in the index, treat this as an error"),
- OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
- "pretend that paths removed since <tree-ish> are still present"),
+ 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, "show debugging data"),
+ OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")),
OPT_END()
};
@@ -514,6 +477,10 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
argc = parse_options(argc, argv, prefix, builtin_ls_files_options,
ls_files_usage, 0);
+ el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
+ for (i = 0; i < exclude_list.nr; i++) {
+ add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args);
+ }
if (show_tag || show_valid_bit) {
tag_cached = "H ";
tag_unmerged = "M ";
@@ -538,30 +505,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 = pathspec_prefix(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)
@@ -581,7 +543,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 1022309..b2a4b92 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -5,7 +5,7 @@
static const char ls_remote_usage[] =
"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n"
-" [-q|--quiet] [--exit-code] [<repository> [<refs>...]]";
+" [-q|--quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]";
/*
* Is there one among the list of patterns that match the tail part
@@ -22,7 +22,7 @@ static int tail_match(const char **pattern, const char *path)
if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf))
return error("insanely long ref %.*s...", 20, path);
while ((p = *(pattern++)) != NULL) {
- if (!fnmatch(p, pathbuf, 0))
+ if (!wildmatch(p, pathbuf, 0, NULL))
return 1;
}
return 0;
@@ -43,15 +43,18 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
struct transport *transport;
const struct ref *ref;
+ if (argc == 2 && !strcmp("-h", argv[1]))
+ usage(ls_remote_usage);
+
for (i = 1; i < argc; i++) {
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;
}
@@ -89,7 +92,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
if (argv[i]) {
int j;
- pattern = xcalloc(sizeof(const char *), argc - i + 1);
+ pattern = xcalloc(argc - i + 1, sizeof(const char *));
for (j = i; j < argc; j++) {
int len = strlen(argv[j]);
char *p = xmalloc(len + 3);
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 6b666e1..3b04a0f 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
@@ -24,7 +25,7 @@ static int chomp_prefix;
static const char *ls_tree_prefix;
static const char * const ls_tree_usage[] = {
- "git ls-tree [<options>] <tree-ish> [<path>...]",
+ N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
NULL
};
@@ -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;
@@ -60,10 +61,11 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
}
}
-static int show_tree(const unsigned char *sha1, const char *base, int baselen,
+static int show_tree(const unsigned char *sha1, struct strbuf *base,
const char *pathname, unsigned mode, int stage, void *context)
{
int retval = 0;
+ int baselen;
const char *type = blob_type;
if (S_ISGITLINK(mode)) {
@@ -78,7 +80,7 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
*/
type = commit_type;
} else if (S_ISDIR(mode)) {
- if (show_recursive(base, baselen, pathname)) {
+ if (show_recursive(base->buf, base->len, pathname)) {
retval = READ_TREE_RECURSIVE;
if (!(ls_options & LS_SHOW_TREES))
return retval;
@@ -88,10 +90,6 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
else if (ls_options & LS_TREE_ONLY)
return 0;
- if (chomp_prefix &&
- (baselen < chomp_prefix || memcmp(ls_tree_prefix, base, chomp_prefix)))
- return 0;
-
if (!(ls_options & LS_NAME_ONLY)) {
if (ls_options & LS_SHOW_SIZE) {
char size_text[24];
@@ -111,8 +109,12 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
printf("%06o %s %s\t", mode, type,
find_unique_abbrev(sha1, abbrev));
}
- write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix,
- pathname, stdout, line_termination);
+ baselen = base->len;
+ strbuf_addstr(base, pathname);
+ write_name_quoted_relative(base->buf,
+ chomp_prefix ? ls_tree_prefix : NULL,
+ stdout, line_termination);
+ strbuf_setlen(base, baselen);
return retval;
}
@@ -122,25 +124,25 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
struct tree *tree;
int i, full_tree = 0;
const struct option ls_tree_options[] = {
- OPT_BIT('d', NULL, &ls_options, "only show trees",
+ OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
LS_TREE_ONLY),
- OPT_BIT('r', NULL, &ls_options, "recurse into subtrees",
+ OPT_BIT('r', NULL, &ls_options, N_("recurse into subtrees"),
LS_RECURSIVE),
- OPT_BIT('t', NULL, &ls_options, "show trees when recursing",
+ OPT_BIT('t', NULL, &ls_options, N_("show trees when recursing"),
LS_SHOW_TREES),
OPT_SET_INT('z', NULL, &line_termination,
- "terminate entries with NUL byte", 0),
- OPT_BIT('l', "long", &ls_options, "include object size",
+ N_("terminate entries with NUL byte"), 0),
+ OPT_BIT('l', "long", &ls_options, N_("include object size"),
LS_SHOW_SIZE),
- OPT_BIT(0, "name-only", &ls_options, "list only filenames",
+ OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"),
LS_NAME_ONLY),
- OPT_BIT(0, "name-status", &ls_options, "list only filenames",
+ OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"),
LS_NAME_ONLY),
OPT_SET_INT(0, "full-name", &chomp_prefix,
- "use full path names", 0),
- OPT_BOOLEAN(0, "full-tree", &full_tree,
- "list entire tree; not just current directory "
- "(implies --full-name)"),
+ N_("use full path names"), 0),
+ OPT_BOOL(0, "full-tree", &full_tree,
+ N_("list entire tree; not just current directory "
+ "(implies --full-name)")),
OPT__ABBREV(&abbrev),
OPT_END()
};
@@ -166,9 +168,18 @@ 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_EXCLUDE,
+ PATHSPEC_PREFER_CWD,
+ prefix, argv + 1);
for (i = 0; i < pathspec.nr; i++)
- pathspec.items[i].use_wildcard = 0;
+ pathspec.items[i].nowildcard_len = pathspec.items[i].len;
pathspec.has_wildcard = 0;
tree = parse_tree_indirect(sha1);
if (!tree)
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index bfb32b7..c8a47c1 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -15,18 +15,17 @@ static const char *metainfo_charset;
static struct strbuf line = STRBUF_INIT;
static struct strbuf name = STRBUF_INIT;
static struct strbuf email = STRBUF_INIT;
+static char *message_id;
static enum {
TE_DONTCARE, TE_QP, TE_BASE64
} transfer_encoding;
-static enum {
- TYPE_TEXT, TYPE_OTHER
-} message_type;
static struct strbuf charset = STRBUF_INIT;
static int patch_lines;
static struct strbuf **p_hdr_data, **s_hdr_data;
static int use_scissors;
+static int add_message_id;
static int use_inbody_headers = 1;
#define MAX_HDR_PARSED 10
@@ -160,10 +159,9 @@ static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
const char *ends, *ap = strcasestr(line, name);
size_t sz;
- if (!ap) {
- strbuf_setlen(attr, 0);
+ strbuf_setlen(attr, 0);
+ if (!ap)
return 0;
- }
ap += strlen(name);
if (*ap == '"') {
ap++;
@@ -185,8 +183,6 @@ static void handle_content_type(struct strbuf *line)
struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
strbuf_init(boundary, line->len);
- if (!strcasestr(line->buf, "text/"))
- message_type = TYPE_OTHER;
if (slurp_attr(line->buf, "boundary=", boundary)) {
strbuf_insert(boundary, 0, "--", 2);
if (++content_top > &content[MAX_BOUNDARIES]) {
@@ -204,6 +200,12 @@ static void handle_content_type(struct strbuf *line)
}
}
+static void handle_message_id(const struct strbuf *line)
+{
+ if (add_message_id)
+ message_id = strdup(line->buf);
+}
+
static void handle_content_transfer_encoding(const struct strbuf *line)
{
if (strcasestr(line->buf, "base64"))
@@ -232,7 +234,9 @@ static void cleanup_subject(struct strbuf *subject)
case 'r': case 'R':
if (subject->len <= at + 3)
break;
- if (!memcmp(subject->buf + at + 1, "e:", 2)) {
+ if ((subject->buf[at + 1] == 'e' ||
+ subject->buf[at + 1] == 'E') &&
+ subject->buf[at + 2] == ':') {
strbuf_remove(subject, at, 3);
continue;
}
@@ -250,8 +254,17 @@ static void cleanup_subject(struct strbuf *subject)
(7 <= remove &&
memmem(subject->buf + at, remove, "PATCH", 5)))
strbuf_remove(subject, at, remove);
- else
+ else {
at += remove;
+ /*
+ * If the input had a space after the ], keep
+ * it. We don't bother with finding the end of
+ * the space, since we later normalize it
+ * anyway.
+ */
+ if (isspace(subject->buf[at]))
+ at += 1;
+ }
continue;
}
break;
@@ -283,6 +296,22 @@ static inline int cmp_header(const struct strbuf *line, const char *hdr)
line->buf[len] == ':' && isspace(line->buf[len + 1]);
}
+static int is_format_patch_separator(const char *line, int len)
+{
+ static const char SAMPLE[] =
+ "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
+ const char *cp;
+
+ if (len != strlen(SAMPLE))
+ return 0;
+ if (!skip_prefix(line, "From ", &cp))
+ return 0;
+ if (strspn(cp, "0123456789abcdef") != 40)
+ return 0;
+ cp += 40;
+ return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
+}
+
static int check_header(const struct strbuf *line,
struct strbuf *hdr_data[], int overwrite)
{
@@ -321,15 +350,23 @@ static int check_header(const struct strbuf *line,
ret = 1;
goto check_header_out;
}
+ if (cmp_header(line, "Message-Id")) {
+ len = strlen("Message-Id: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(&sb);
+ handle_message_id(&sb);
+ ret = 1;
+ goto check_header_out;
+ }
/* for inbody stuff */
- if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
- ret = 1; /* Should this return 0? */
+ if (starts_with(line->buf, ">From") && isspace(line->buf[5])) {
+ ret = is_format_patch_separator(line->buf + 1, line->len - 1);
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)) {
+ if (!strcmp("Subject", header[i])) {
handle_header(&hdr_data[i], line);
ret = 1;
goto check_header_out;
@@ -356,7 +393,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++)) {
@@ -472,37 +509,14 @@ static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
return out;
}
-/*
- * When there is no known charset, guess.
- *
- * Right now we assume that if the target is UTF-8 (the default),
- * and it already looks like UTF-8 (which includes US-ASCII as its
- * subset, of course) then that is what it is and there is nothing
- * to do.
- *
- * Otherwise, we default to assuming it is Latin1 for historical
- * reasons.
- */
-static const char *guess_charset(const struct strbuf *line, const char *target_charset)
-{
- if (is_encoding_utf8(target_charset)) {
- if (is_utf8(line->buf))
- return NULL;
- }
- return "ISO8859-1";
-}
-
static void convert_to_utf8(struct strbuf *line, const char *charset)
{
char *out;
- if (!charset || !*charset) {
- charset = guess_charset(line, metainfo_charset);
- if (!charset)
- return;
- }
+ if (!charset || !*charset)
+ return;
- if (!strcasecmp(metainfo_charset, charset))
+ if (same_encoding(metainfo_charset, charset))
return;
out = reencode_string(line->buf, metainfo_charset, charset);
if (!out)
@@ -671,7 +685,6 @@ again:
/* set some defaults */
transfer_encoding = TE_DONTCARE;
strbuf_reset(&charset);
- message_type = TYPE_TEXT;
/* slurp in this section's info */
while (read_one_header_line(&line, fin))
@@ -690,11 +703,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;
/*
@@ -704,7 +717,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;
@@ -819,6 +832,8 @@ static int handle_commit_msg(struct strbuf *line)
}
if (patchbreak(line)) {
+ if (message_id)
+ fprintf(cmitmsg, "Message-Id: %s\n", message_id);
fclose(cmitmsg);
cmitmsg = NULL;
return 1;
@@ -885,11 +900,6 @@ static void handle_body(void)
strbuf_insert(&line, 0, prev.buf, prev.len);
strbuf_reset(&prev);
- /* binary data most likely doesn't have newlines */
- if (message_type != TYPE_TEXT) {
- handle_filter(&line);
- break;
- }
/*
* This is a decoded line that may contain
* multiple new lines. Pass only one chunk
@@ -953,13 +963,13 @@ static void handle_info(void)
else
continue;
- if (!memcmp(header[i], "Subject", 7)) {
+ if (!strcmp(header[i], "Subject")) {
if (!keep_subject) {
cleanup_subject(hdr);
cleanup_space(hdr);
}
output_header_lines(fout, "Subject", hdr);
- } else if (!memcmp(header[i], "From", 4)) {
+ } else if (!strcmp(header[i], "From")) {
cleanup_space(hdr);
handle_from(hdr);
fprintf(fout, "Author: %s\n", name.buf);
@@ -1010,7 +1020,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);
@@ -1021,7 +1031,7 @@ static int git_mailinfo_config(const char *var, const char *value, void *unused)
}
static const char mailinfo_usage[] =
- "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
+ "git mailinfo [-k|-b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
{
@@ -1040,11 +1050,13 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
keep_subject = 1;
else if (!strcmp(argv[1], "-b"))
keep_non_patch_brackets_in_subject = 1;
+ else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id"))
+ add_message_id = 1;
else if (!strcmp(argv[1], "-u"))
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/mailsplit.c b/builtin/mailsplit.c
index 2d43278..8e02ea1 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -53,14 +53,15 @@ static int keep_cr;
*/
static int split_one(FILE *mbox, const char *name, int allow_bare)
{
- FILE *output = NULL;
+ FILE *output;
int fd;
int status = 0;
int is_bare = !is_from_line(buf.buf, buf.len);
- if (is_bare && !allow_bare)
- goto corrupt;
-
+ if (is_bare && !allow_bare) {
+ fprintf(stderr, "corrupt mailbox\n");
+ exit(1);
+ }
fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd < 0)
die_errno("cannot open output file '%s'", name);
@@ -91,13 +92,6 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
}
fclose(output);
return status;
-
- corrupt:
- if (output)
- fclose(output);
- unlink(name);
- fprintf(stderr, "corrupt mailbox\n");
- exit(1);
}
static int populate_maildir_list(struct string_list *list, const char *path)
@@ -130,6 +124,27 @@ static int populate_maildir_list(struct string_list *list, const char *path)
return 0;
}
+static int maildir_filename_cmp(const char *a, const char *b)
+{
+ while (*a && *b) {
+ if (isdigit(*a) && isdigit(*b)) {
+ long int na, nb;
+ na = strtol(a, (char **)&a, 10);
+ nb = strtol(b, (char **)&b, 10);
+ if (na != nb)
+ return na - nb;
+ /* strtol advanced our pointers */
+ }
+ else {
+ if (*a != *b)
+ return (unsigned char)*a - (unsigned char)*b;
+ a++;
+ b++;
+ }
+ }
+ return (unsigned char)*a - (unsigned char)*b;
+}
+
static int split_maildir(const char *maildir, const char *dir,
int nr_prec, int skip)
{
@@ -139,6 +154,8 @@ static int split_maildir(const char *maildir, const char *dir,
int i;
struct string_list list = STRING_LIST_INIT_DUP;
+ list.cmp = maildir_filename_cmp;
+
if (populate_maildir_list(&list, maildir) < 0)
goto out;
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 4f30f1b..fdebef6 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -1,13 +1,16 @@
#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)
{
struct commit_list *result;
- result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0);
+ result = get_merge_bases_many_dirty(rev[0], rev_nr - 1, rev + 1);
if (!result)
return 1;
@@ -23,9 +26,11 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
}
static const char * const merge_base_usage[] = {
- "git merge-base [-a|--all] <commit> <commit>...",
- "git merge-base [-a|--all] --octopus <commit>...",
- "git merge-base --independent <commit>...",
+ N_("git merge-base [-a|--all] <commit> <commit>..."),
+ 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
};
@@ -43,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;
@@ -70,30 +92,165 @@ static int handle_octopus(int count, const char **args, int reduce, int show_all
return 0;
}
+static int handle_is_ancestor(int argc, const char **argv)
+{
+ struct commit *one, *two;
+
+ if (argc != 2)
+ die("--is-ancestor takes exactly two commits");
+ one = get_commit_reference(argv[0]);
+ two = get_commit_reference(argv[1]);
+ if (in_merge_bases(one, two))
+ return 0;
+ else
+ 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_dirty(derived, revs.nr, revs.commit);
+
+ /*
+ * 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 cmdmode = 0;
struct option options[] = {
- OPT_BOOLEAN('a', "all", &show_all, "output all common ancestors"),
- OPT_BOOLEAN(0, "octopus", &octopus, "find ancestors for a single n-way merge"),
- OPT_BOOLEAN(0, "independent", &reduce, "list revs not reachable from others"),
+ 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 (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 == '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 (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 237abd3..232b768 100644
--- a/builtin/merge-file.c
+++ b/builtin/merge-file.c
@@ -5,7 +5,7 @@
#include "parse-options.h"
static const char *const merge_file_usage[] = {
- "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2",
+ N_("git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"),
NULL
};
@@ -30,19 +30,19 @@ 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, "send results to standard output"),
- OPT_SET_INT(0, "diff3", &xmp.style, "use a diff3 based merge", XDL_MERGE_DIFF3),
- OPT_SET_INT(0, "ours", &xmp.favor, "for conflicts, use our version",
+ 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),
- OPT_SET_INT(0, "theirs", &xmp.favor, "for conflicts, use their version",
+ OPT_SET_INT(0, "theirs", &xmp.favor, N_("for conflicts, use their version"),
XDL_MERGE_FAVOR_THEIRS),
- OPT_SET_INT(0, "union", &xmp.favor, "for conflicts, use a union version",
+ OPT_SET_INT(0, "union", &xmp.favor, N_("for conflicts, use a union version"),
XDL_MERGE_FAVOR_UNION),
OPT_INTEGER(0, "marker-size", &xmp.marker_size,
- "for conflicts, use this marker size"),
- OPT__QUIET(&quiet, "do not warn about conflicts"),
- OPT_CALLBACK('L', NULL, names, "name",
- "set labels for file1/orig_file/file2", &label_cb),
+ N_("for conflicts, use this marker size")),
+ OPT__QUIET(&quiet, N_("do not warn about conflicts")),
+ OPT_CALLBACK('L', NULL, names, N_("name"),
+ N_("set labels for file1/orig_file/file2"), &label_cb),
OPT_END(),
};
@@ -63,7 +63,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
if (quiet) {
if (!freopen("/dev/null", "w", stderr))
return error("failed to redirect stderr to /dev/null: "
- "%s\n", strerror(errno));
+ "%s", strerror(errno));
}
if (prefix)
@@ -76,7 +76,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
if (read_mmfile(mmfs + i, fname))
return -1;
if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
- return error("Cannot merge binary files: %s\n",
+ return error("Cannot merge binary files: %s",
argv[i]);
}
@@ -90,7 +90,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
if (ret >= 0) {
const char *filename = argv[0];
- FILE *f = to_stdout ? stdout : fopen(filename, "wb");
+ const char *fpath = prefix_filename(prefix, prefixlen, argv[0]);
+ FILE *f = to_stdout ? stdout : fopen(fpath, "wb");
if (!f)
ret = error("Could not open %s for writing", filename);
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
index 2338832..b416d92 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))
@@ -42,7 +42,7 @@ static int merge_entry(int pos, const char *path)
return found;
}
-static void merge_file(const char *path)
+static void merge_one_path(const char *path)
{
int pos = cache_name_pos(path, strlen(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;
@@ -102,7 +102,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)
}
die("git merge-index: unknown option %s", arg);
}
- merge_file(arg);
+ merge_one_path(arg);
}
if (err && !quiet)
die("merge program failed");
diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
index 3a64f5d..a90f28f 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 897a563..f9ab485 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -3,17 +3,15 @@
#include "xdiff-interface.h"
#include "blob.h"
#include "exec_cmd.h"
-#include "merge-file.h"
+#include "merge-blobs.h"
static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-static int resolve_directories = 1;
struct merge_list {
struct merge_list *next;
struct merge_list *link; /* other stages for this object */
- unsigned int stage : 2,
- flags : 30;
+ unsigned int stage : 2;
unsigned int mode;
const char *path;
struct blob *blob;
@@ -76,7 +74,7 @@ static void *result(struct merge_list *entry, unsigned long *size)
their = NULL;
if (entry)
their = entry->blob;
- return merge_file(path, base, our, their, size);
+ return merge_blobs(path, base, our, their, size);
}
static void *origin(struct merge_list *entry, unsigned long *size)
@@ -157,6 +155,11 @@ static int same_entry(struct name_entry *a, struct name_entry *b)
a->mode == b->mode;
}
+static int both_empty(struct name_entry *a, struct name_entry *b)
+{
+ return !(a->sha1 || b->sha1);
+}
+
static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
{
struct merge_list *res = xcalloc(1, sizeof(*res));
@@ -174,17 +177,17 @@ static char *traverse_path(const struct traverse_info *info, const struct name_e
return make_traverse_path(path, info, n);
}
-static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result)
+static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result)
{
struct merge_list *orig, *final;
const char *path;
- /* If it's already branch1, don't bother showing it */
- if (!branch1)
+ /* If it's already ours, don't bother showing it */
+ if (!ours)
return;
path = traverse_path(info, result);
- orig = create_entry(2, branch1->mode, branch1->sha1, path);
+ orig = create_entry(2, ours->mode, ours->sha1, path);
final = create_entry(0, result->mode, result->sha1, path);
final->link = orig;
@@ -192,34 +195,35 @@ static void resolve(const struct traverse_info *info, struct name_entry *branch1
add_merge_entry(final);
}
-static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3])
+static void unresolved_directory(const struct traverse_info *info,
+ struct name_entry n[3])
{
char *newbase;
struct name_entry *p;
struct tree_desc t[3];
void *buf0, *buf1, *buf2;
- if (!resolve_directories)
- return 0;
- p = n;
- if (!p->mode) {
- p++;
- if (!p->mode)
- p++;
+ for (p = n; p < n + 3; p++) {
+ if (p->mode && S_ISDIR(p->mode))
+ break;
}
- if (!S_ISDIR(p->mode))
- return 0;
+ if (n + 3 <= p)
+ return; /* there is no tree here */
+
newbase = traverse_path(info, p);
- buf0 = fill_tree_descriptor(t+0, n[0].sha1);
- buf1 = fill_tree_descriptor(t+1, n[1].sha1);
- buf2 = fill_tree_descriptor(t+2, n[2].sha1);
+
+#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->sha1 : NULL)
+ buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0));
+ buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1));
+ buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2));
+#undef ENTRY_SHA1
+
merge_trees(t, newbase);
free(buf0);
free(buf1);
free(buf2);
free(newbase);
- return 1;
}
@@ -242,18 +246,30 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info
static void unresolved(const struct traverse_info *info, struct name_entry n[3])
{
struct merge_list *entry = NULL;
+ int i;
+ unsigned dirmask = 0, mask = 0;
+
+ for (i = 0; i < 3; i++) {
+ mask |= (1 << i);
+ /*
+ * 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);
+ }
+
+ unresolved_directory(info, n);
- if (unresolved_directory(info, n))
+ if (dirmask == mask)
return;
- /*
- * Do them in reverse order so that the resulting link
- * list has the stages in order - link_entry adds new
- * links at the front.
- */
- entry = link_entry(3, info, n + 2, entry);
- entry = link_entry(2, info, n + 1, entry);
- entry = link_entry(1, info, n + 0, entry);
+ if (n[2].mode && !S_ISDIR(n[2].mode))
+ entry = link_entry(3, info, n + 2, entry);
+ if (n[1].mode && !S_ISDIR(n[1].mode))
+ entry = link_entry(2, info, n + 1, entry);
+ if (n[0].mode && !S_ISDIR(n[0].mode))
+ entry = link_entry(1, info, n + 0, entry);
add_merge_entry(entry);
}
@@ -290,25 +306,29 @@ 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)) {
- if (entry[0].sha1) {
- resolve(info, NULL, entry+1);
- return mask;
- }
+ 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;
}
if (same_entry(entry+0, entry+1)) {
if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
+ /* We did not touch, they modified -- take theirs */
resolve(info, entry+1, entry+2);
return mask;
}
+ /*
+ * If we did not touch a directory but they made it
+ * into a file, we fall through and unresolved()
+ * recurses down. Likewise for the opposite case.
+ */
}
- if (same_entry(entry+0, entry+2)) {
- if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
- resolve(info, NULL, entry+1);
- return mask;
- }
+ if (same_entry(entry+0, entry+2) || both_empty(entry+0, entry+2)) {
+ /* We added, modified or removed, they did not touch -- take ours */
+ resolve(info, NULL, entry+1);
+ return mask;
}
unresolved(info, entry);
diff --git a/builtin/merge.c b/builtin/merge.c
index ab4077f..c638fd5 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -9,6 +9,7 @@
#include "cache.h"
#include "parse-options.h"
#include "builtin.h"
+#include "lockfile.h"
#include "run-command.h"
#include "diff.h"
#include "refs.h"
@@ -26,6 +27,9 @@
#include "merge-recursive.h"
#include "resolve-undo.h"
#include "remote.h"
+#include "fmt-merge-msg.h"
+#include "gpg-interface.h"
+#include "sequencer.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -38,19 +42,18 @@ struct strategy {
};
static const char * const builtin_merge_usage[] = {
- "git merge [options] [<commit>...]",
- "git merge [options] <msg> HEAD <commit>",
- "git merge --abort",
+ N_("git merge [options] [<commit>...]"),
+ N_("git merge [options] <msg> HEAD <commit>"),
+ N_("git merge --abort"),
NULL
};
-static int show_diffstat = 1, shortlog_len, squash;
-static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only;
-static int allow_trivial = 1, have_message;
-static struct strbuf merge_msg;
-static struct commit_list *remoteheads;
-static unsigned char head[20], stash[20];
+static int show_diffstat = 1, shortlog_len = -1, squash;
+static int option_commit = 1;
+static int option_edit = -1;
+static int allow_trivial = 1, have_message, verify_signatures;
+static int overwrite_ignore = 1;
+static struct strbuf merge_msg = STRBUF_INIT;
static struct strategy **use_strategies;
static size_t use_strategies_nr, use_strategies_alloc;
static const char **xopts;
@@ -62,7 +65,8 @@ static int verbosity;
static int allow_rerere_auto;
static int abort_current_merge;
static int show_progress = -1;
-static int default_to_upstream;
+static int default_to_upstream = 1;
+static const char *sign_commit;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -74,6 +78,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)
{
@@ -178,34 +190,41 @@ static int option_parse_n(const struct option *opt,
static struct option builtin_merge_options[] = {
{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
- "do not show a diffstat at the end of the merge",
+ N_("do not show a diffstat at the end of the merge"),
PARSE_OPT_NOARG, option_parse_n },
- OPT_BOOLEAN(0, "stat", &show_diffstat,
- "show a diffstat at the end of the merge"),
- OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
- { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
- "add (at most <n>) entries from shortlog to merge commit message",
+ OPT_BOOL(0, "stat", &show_diffstat,
+ N_("show a diffstat at the end of the merge")),
+ OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")),
+ { 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,
- "create a single commit instead of doing a merge"),
- OPT_BOOLEAN(0, "commit", &option_commit,
- "perform a commit if the merge succeeds (default)"),
- OPT_BOOLEAN(0, "ff", &allow_fast_forward,
- "allow fast-forward (default)"),
- OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
- "abort if fast-forward is not possible"),
+ OPT_BOOL(0, "squash", &squash,
+ N_("create a single commit instead of doing a merge")),
+ 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_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_CALLBACK('s', "strategy", &use_strategies, "strategy",
- "merge strategy to use", option_parse_strategy),
- OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
- "option for selected merge strategy", option_parse_x),
- OPT_CALLBACK('m', "message", &merge_msg, "message",
- "merge commit message (for a non-fast-forward merge)",
+ OPT_BOOL(0, "verify-signatures", &verify_signatures,
+ N_("Verify that the named commit has a valid GPG signature")),
+ OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"),
+ N_("merge strategy to use"), option_parse_strategy),
+ OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"),
+ N_("option for selected merge strategy"), option_parse_x),
+ OPT_CALLBACK('m', "message", &merge_msg, N_("message"),
+ N_("merge commit message (for a non-fast-forward merge)"),
option_parse_message),
OPT__VERBOSITY(&verbosity),
- OPT_BOOLEAN(0, "abort", &abort_current_merge,
- "abort the current in-progress merge"),
- OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+ 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_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")),
OPT_END()
};
@@ -217,14 +236,13 @@ static void drop_save(void)
unlink(git_path("MERGE_MODE"));
}
-static void save_state(void)
+static int save_state(unsigned char *stash)
{
int len;
- struct child_process cp;
+ struct child_process cp = CHILD_PROCESS_INIT;
struct strbuf buffer = STRBUF_INIT;
const char *argv[] = {"stash", "create", NULL};
- memset(&cp, 0, sizeof(cp));
cp.argv = argv;
cp.out = -1;
cp.git_cmd = 1;
@@ -236,11 +254,12 @@ static void save_state(void)
if (finish_command(&cp) || len < 0)
die(_("stash failed"));
- else if (!len)
- return;
+ else if (!len) /* no changes */
+ return -1;
strbuf_setlen(&buffer, buffer.len-1);
if (get_sha1(buffer.buf, stash))
die(_("not a valid object: %s"), buffer.buf);
+ return 0;
}
static void read_empty(unsigned const char *sha1, int verbose)
@@ -278,7 +297,8 @@ static void reset_hard(unsigned const char *sha1, int verbose)
die(_("read-tree failed"));
}
-static void restore_state(void)
+static void restore_state(const unsigned char *head,
+ const unsigned char *stash)
{
struct strbuf sb = STRBUF_INIT;
const char *args[] = { "stash", "apply", NULL, NULL };
@@ -308,25 +328,25 @@ static void finish_up_to_date(const char *msg)
drop_save();
}
-static void squash_message(void)
+static void squash_message(struct commit *commit, struct commit_list *remoteheads)
{
struct rev_info rev;
- struct commit *commit;
struct strbuf out = STRBUF_INIT;
struct commit_list *j;
+ const char *filename;
int fd;
struct pretty_print_context ctx = {0};
printf(_("Squash commit -- not updating HEAD\n"));
- fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ filename = git_path("SQUASH_MSG");
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
if (fd < 0)
- die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG"));
+ die_errno(_("Could not write to '%s'"), filename);
init_revisions(&rev, NULL);
rev.ignore_merges = 1;
rev.commit_format = CMIT_FMT_MEDIUM;
- commit = lookup_commit(head);
commit->object.flags |= UNINTERESTING;
add_pending_object(&rev, &commit->object, NULL);
@@ -348,16 +368,19 @@ static void squash_message(void)
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"));
strbuf_release(&out);
}
-static void finish(const unsigned char *new_head, const char *msg)
+static void finish(struct commit *head_commit,
+ struct commit_list *remoteheads,
+ const unsigned char *new_head, const char *msg)
{
struct strbuf reflog_message = STRBUF_INIT;
+ const unsigned char *head = head_commit->object.sha1;
if (!msg)
strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
@@ -368,7 +391,7 @@ static void finish(const unsigned char *new_head, const char *msg)
getenv("GIT_REFLOG_ACTION"), msg);
}
if (squash) {
- squash_message();
+ squash_message(head_commit, remoteheads);
} else {
if (verbosity >= 0 && !merge_msg.len)
printf(_("No merge message -- not updating HEAD\n"));
@@ -376,7 +399,7 @@ static void finish(const unsigned char *new_head, const char *msg)
const char *argv_gc_auto[] = { "gc", "--auto", NULL };
update_ref(reflog_message.buf, "HEAD",
new_head, head, 0,
- DIE_ON_ERR);
+ UPDATE_REFS_DIE_ON_ERR);
/*
* We ignore errors in 'gc --auto', since the
* user should see them.
@@ -387,18 +410,19 @@ static void finish(const unsigned char *new_head, const char *msg)
if (new_head && show_diffstat) {
struct diff_options opts;
diff_setup(&opts);
+ opts.stat_width = -1; /* use full terminal width */
+ opts.stat_graph_width = -1; /* respect statGraphWidth config */
opts.output_format |=
DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
opts.detect_rename = DIFF_DETECT_RENAME;
- if (diff_setup_done(&opts) < 0)
- die(_("diff_setup_done failed"));
+ diff_setup_done(&opts);
diff_tree_sha1(head, new_head, "", &opts);
diffcore_std(&opts);
diff_flush(&opts);
}
/* Run a post-merge hook */
- run_hook(NULL, "post-merge", squash ? "1" : "0", NULL);
+ run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
strbuf_release(&reflog_message);
}
@@ -406,8 +430,8 @@ static void finish(const unsigned char *new_head, const char *msg)
/* Get the name for the merge commit's message. */
static void merge_name(const char *remote, struct strbuf *msg)
{
- struct object *remote_head;
- unsigned char branch_head[20], buf_sha[20];
+ struct commit *remote_head;
+ unsigned char branch_head[20];
struct strbuf buf = STRBUF_INIT;
struct strbuf bname = STRBUF_INIT;
const char *ptr;
@@ -418,17 +442,22 @@ static void merge_name(const char *remote, struct strbuf *msg)
remote = bname.buf;
memset(branch_head, 0, sizeof(branch_head));
- remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ remote_head = get_merge_parent(remote);
if (!remote_head)
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/remotes/")) {
+ 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 (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;
@@ -466,10 +495,10 @@ static void merge_name(const char *remote, struct strbuf *msg)
strbuf_addstr(&truname, "refs/heads/");
strbuf_addstr(&truname, remote);
strbuf_setlen(&truname, truname.len - len);
- if (resolve_ref(truname.buf, buf_sha, 1, NULL)) {
+ if (ref_exists(truname.buf)) {
strbuf_addf(msg,
"%s\t\tbranch '%s'%s of .\n",
- sha1_to_hex(remote_head->sha1),
+ sha1_to_hex(remote_head->object.sha1),
truname.buf + 11,
(early ? " (early part)" : ""));
strbuf_release(&truname);
@@ -479,14 +508,16 @@ static void merge_name(const char *remote, struct strbuf *msg)
if (!strcmp(remote, "FETCH_HEAD") &&
!access(git_path("FETCH_HEAD"), R_OK)) {
+ const char *filename;
FILE *fp;
struct strbuf line = STRBUF_INIT;
char *ptr;
- fp = fopen(git_path("FETCH_HEAD"), "r");
+ filename = git_path("FETCH_HEAD");
+ fp = fopen(filename, "r");
if (!fp)
die_errno(_("could not open '%s' for reading"),
- git_path("FETCH_HEAD"));
+ filename);
strbuf_getline(&line, fp, '\n');
fclose(fp);
ptr = strstr(line.buf, "\tnot-for-merge\t");
@@ -496,8 +527,21 @@ static void merge_name(const char *remote, struct strbuf *msg)
strbuf_release(&line);
goto cleanup;
}
+
+ if (remote_head->util) {
+ struct merge_remote_desc *desc;
+ desc = merge_remote_util(remote_head);
+ if (desc && desc->obj && desc->obj->type == OBJ_TAG) {
+ strbuf_addf(msg, "%s\t\t%s '%s'\n",
+ sha1_to_hex(desc->obj->sha1),
+ typename(desc->obj->type),
+ remote);
+ goto cleanup;
+ }
+ }
+
strbuf_addf(msg, "%s\t\tcommit '%s'\n",
- sha1_to_hex(remote_head->sha1), remote);
+ sha1_to_hex(remote_head->object.sha1), remote);
cleanup:
strbuf_release(&buf);
strbuf_release(&bname);
@@ -514,7 +558,7 @@ static void parse_branch_merge_options(char *bmo)
if (argc < 0)
die(_("Bad branch.%s.mergeoptions string: %s"), branch,
split_cmdline_strerror(argc));
- argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ REALLOC_ARRAY(argv, argc + 2);
memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
argc++;
argv[0] = "branch.*.mergeoptions";
@@ -525,8 +569,10 @@ static void parse_branch_merge_options(char *bmo)
static int git_merge_config(const char *k, const char *v, void *cb)
{
- if (branch && !prefixcmp(k, "branch.") &&
- !prefixcmp(k + 7, branch) &&
+ int status;
+
+ if (branch && starts_with(k, "branch.") &&
+ starts_with(k + 7, branch) &&
!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
free(branch_mergeoptions);
branch_mergeoptions = xstrdup(v);
@@ -541,27 +587,28 @@ static int git_merge_config(const char *k, const char *v, void *cb)
return git_config_string(&pull_octopus, k, v);
else if (!strcmp(k, "merge.renormalize"))
option_renormalize = git_config_bool(k, v);
- else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
- int is_bool;
- shortlog_len = git_config_bool_or_int(k, v, &is_bool);
- if (!is_bool && shortlog_len < 0)
- return error(_("%s: negative length %s"), k, v);
- if (is_bool && shortlog_len)
- shortlog_len = DEFAULT_MERGE_LOG_LEN;
- return 0;
- } else if (!strcmp(k, "merge.ff")) {
+ 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);
+ if (status)
+ return status;
+ status = git_gpg_config(k, v, NULL);
+ if (status)
+ return status;
return git_diff_ui_config(k, v, cb);
}
@@ -607,78 +654,22 @@ static void write_tree_trivial(unsigned char *sha1)
die(_("git write-tree failed to write a tree"));
}
-static const char *merge_argument(struct commit *commit)
-{
- if (commit)
- return sha1_to_hex(commit->object.sha1);
- else
- return EMPTY_TREE_SHA1_HEX;
-}
-
-int try_merge_command(const char *strategy, size_t xopts_nr,
- const char **xopts, struct commit_list *common,
- const char *head_arg, struct commit_list *remotes)
-{
- const char **args;
- int i = 0, x = 0, ret;
- struct commit_list *j;
- struct strbuf buf = STRBUF_INIT;
-
- args = xmalloc((4 + xopts_nr + commit_list_count(common) +
- commit_list_count(remotes)) * sizeof(char *));
- strbuf_addf(&buf, "merge-%s", strategy);
- args[i++] = buf.buf;
- for (x = 0; x < xopts_nr; x++) {
- char *s = xmalloc(strlen(xopts[x])+2+1);
- strcpy(s, "--");
- strcpy(s+2, xopts[x]);
- args[i++] = s;
- }
- for (j = common; j; j = j->next)
- args[i++] = xstrdup(merge_argument(j->item));
- args[i++] = "--";
- args[i++] = head_arg;
- for (j = remotes; j; j = j->next)
- args[i++] = xstrdup(merge_argument(j->item));
- args[i] = NULL;
- ret = run_command_v_opt(args, RUN_GIT_CMD);
- strbuf_release(&buf);
- i = 1;
- for (x = 0; x < xopts_nr; x++)
- free((void *)args[i++]);
- for (j = common; j; j = j->next)
- free((void *)args[i++]);
- i += 2;
- for (j = remotes; j; j = j->next)
- free((void *)args[i++]);
- free(args);
- discard_cache();
- if (read_cache() < 0)
- die(_("failed to read the cache"));
- resolve_undo_clear();
-
- return ret;
-}
-
static int try_merge_strategy(const char *strategy, struct commit_list *common,
- const char *head_arg)
+ struct commit_list *remoteheads,
+ struct commit *head, const char *head_arg)
{
- int index_fd;
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ static struct lock_file lock;
- index_fd = hold_locked_index(lock, 1);
+ hold_locked_index(&lock, 1);
refresh_cache(REFRESH_QUIET);
if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(lock)))
+ write_locked_index(&the_index, &lock, COMMIT_LOCK))
return error(_("Unable to write index."));
- rollback_lock_file(lock);
+ rollback_lock_file(&lock);
if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
int clean, x;
struct commit *result;
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
- int index_fd;
struct commit_list *reversed = NULL;
struct merge_options o;
struct commit_list *j;
@@ -701,19 +692,18 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
o.branch1 = head_arg;
- o.branch2 = remoteheads->item->util;
+ o.branch2 = merge_remote_util(remoteheads->item)->name;
for (j = common; j; j = j->next)
commit_list_insert(j->item, &reversed);
- index_fd = hold_locked_index(lock, 1);
- clean = merge_recursive(&o, lookup_commit(head),
+ hold_locked_index(&lock, 1);
+ clean = merge_recursive(&o, head,
remoteheads->item, reversed, &result);
if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(lock)))
+ write_locked_index(&the_index, &lock, COMMIT_LOCK))
die (_("unable to write %s"), get_index_file());
- rollback_lock_file(lock);
+ rollback_lock_file(&lock);
return clean ? 0 : 1;
} else {
return try_merge_command(strategy, xopts_nr, xopts,
@@ -740,54 +730,6 @@ static int count_unmerged_entries(void)
return ret;
}
-int checkout_fast_forward(const unsigned char *head, const unsigned char *remote)
-{
- struct tree *trees[MAX_UNPACK_TREES];
- struct unpack_trees_options opts;
- struct tree_desc t[MAX_UNPACK_TREES];
- int i, fd, nr_trees = 0;
- struct dir_struct dir;
- struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
-
- refresh_cache(REFRESH_QUIET);
-
- fd = hold_locked_index(lock_file, 1);
-
- memset(&trees, 0, sizeof(trees));
- memset(&opts, 0, sizeof(opts));
- memset(&t, 0, sizeof(t));
- memset(&dir, 0, sizeof(dir));
- dir.flags |= DIR_SHOW_IGNORED;
- dir.exclude_per_dir = ".gitignore";
- opts.dir = &dir;
-
- opts.head_idx = 1;
- opts.src_index = &the_index;
- opts.dst_index = &the_index;
- opts.update = 1;
- opts.verbose_update = 1;
- opts.merge = 1;
- opts.fn = twoway_merge;
- setup_unpack_trees_porcelain(&opts, "merge");
-
- trees[nr_trees] = parse_tree_indirect(head);
- if (!trees[nr_trees++])
- return -1;
- trees[nr_trees] = parse_tree_indirect(remote);
- if (!trees[nr_trees++])
- return -1;
- for (i = 0; i < nr_trees; i++) {
- parse_tree(trees[i]);
- init_tree_desc(t+i, trees[i]->buffer, trees[i]->size);
- }
- if (unpack_trees(nr_trees, t, &opts))
- return -1;
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
- die(_("unable to write new index file"));
- return 0;
-}
-
static void split_merge_strategies(const char *string, struct strategy **list,
int *nr, int *alloc)
{
@@ -832,103 +774,127 @@ static void add_strategies(const char *string, unsigned attr)
}
-static void write_merge_msg(void)
+static void write_merge_msg(struct strbuf *msg)
{
- int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+ const char *filename = git_path("MERGE_MSG");
+ int fd = open(filename, O_WRONLY | O_CREAT, 0666);
if (fd < 0)
die_errno(_("Could not open '%s' for writing"),
- git_path("MERGE_MSG"));
- if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
- die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
+ filename);
+ if (write_in_full(fd, msg->buf, msg->len) != msg->len)
+ die_errno(_("Could not write to '%s'"), filename);
close(fd);
}
-static void read_merge_msg(void)
+static void read_merge_msg(struct strbuf *msg)
{
- strbuf_reset(&merge_msg);
- if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
- die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
+ const char *filename = git_path("MERGE_MSG");
+ strbuf_reset(msg);
+ if (strbuf_read_file(msg, filename, 0) < 0)
+ die_errno(_("Could not read from '%s'"), filename);
}
-static void run_prepare_commit_msg(void)
+static void write_merge_state(struct commit_list *);
+static void abort_commit(struct commit_list *remoteheads, const char *err_msg)
{
- write_merge_msg();
- run_hook(get_index_file(), "prepare-commit-msg",
- git_path("MERGE_MSG"), "merge", NULL, NULL);
- read_merge_msg();
+ if (err_msg)
+ error("%s", err_msg);
+ fprintf(stderr,
+ _("Not committing merge; use 'git commit' to complete the merge.\n"));
+ write_merge_state(remoteheads);
+ exit(1);
+}
+
+static const char merge_editor_comment[] =
+N_("Please enter a commit message to explain why this merge is necessary,\n"
+ "especially if it merges an updated upstream into a topic branch.\n"
+ "\n"
+ "Lines starting with '%c' will be ignored, and an empty message aborts\n"
+ "the commit.\n");
+
+static void prepare_to_commit(struct commit_list *remoteheads)
+{
+ struct strbuf msg = STRBUF_INIT;
+ strbuf_addbuf(&msg, &merge_msg);
+ strbuf_addch(&msg, '\n');
+ if (0 < option_edit)
+ strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char);
+ write_merge_msg(&msg);
+ if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg",
+ git_path("MERGE_MSG"), "merge", NULL))
+ abort_commit(remoteheads, NULL);
+ if (0 < option_edit) {
+ if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
+ abort_commit(remoteheads, NULL);
+ }
+ read_merge_msg(&msg);
+ stripspace(&msg, 0 < option_edit);
+ if (!msg.len)
+ abort_commit(remoteheads, _("Empty commit message."));
+ strbuf_release(&merge_msg);
+ strbuf_addbuf(&merge_msg, &msg);
+ strbuf_release(&msg);
}
-static int merge_trivial(void)
+static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
{
unsigned char result_tree[20], result_commit[20];
- struct commit_list *parent = xmalloc(sizeof(*parent));
+ struct commit_list *parents, **pptr = &parents;
write_tree_trivial(result_tree);
printf(_("Wonderful.\n"));
- parent->item = lookup_commit(head);
- parent->next = xmalloc(sizeof(*parent->next));
- parent->next->item = remoteheads->item;
- parent->next->next = NULL;
- run_prepare_commit_msg();
- commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
- finish(result_commit, "In-index merge");
+ pptr = commit_list_append(head, pptr);
+ pptr = commit_list_append(remoteheads->item, pptr);
+ prepare_to_commit(remoteheads);
+ if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
+ result_commit, NULL, sign_commit))
+ die(_("failed to write commit object"));
+ finish(head, remoteheads, result_commit, "In-index merge");
drop_save();
return 0;
}
-static int finish_automerge(struct commit_list *common,
+static int finish_automerge(struct commit *head,
+ int head_subsumed,
+ struct commit_list *common,
+ struct commit_list *remoteheads,
unsigned char *result_tree,
const char *wt_strategy)
{
- struct commit_list *parents = NULL, *j;
+ struct commit_list *parents = NULL;
struct strbuf buf = STRBUF_INIT;
unsigned char result_commit[20];
free_commit_list(common);
- if (allow_fast_forward) {
- parents = remoteheads;
- commit_list_insert(lookup_commit(head), &parents);
- parents = reduce_heads(parents);
- } else {
- struct commit_list **pptr = &parents;
-
- pptr = &commit_list_insert(lookup_commit(head),
- pptr)->next;
- for (j = remoteheads; j; j = j->next)
- pptr = &commit_list_insert(j->item, pptr)->next;
- }
- free_commit_list(remoteheads);
+ parents = remoteheads;
+ if (!head_subsumed || fast_forward == FF_NO)
+ commit_list_insert(head, &parents);
strbuf_addch(&merge_msg, '\n');
- run_prepare_commit_msg();
- commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+ prepare_to_commit(remoteheads);
+ if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
+ result_commit, NULL, sign_commit))
+ die(_("failed to write commit object"));
strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
- finish(result_commit, buf.buf);
+ finish(head, remoteheads, result_commit, buf.buf);
strbuf_release(&buf);
drop_save();
return 0;
}
-static int suggest_conflicts(int renormalizing)
+static int suggest_conflicts(void)
{
+ const char *filename;
FILE *fp;
- int pos;
+ struct strbuf msgbuf = STRBUF_INIT;
- fp = fopen(git_path("MERGE_MSG"), "a");
+ filename = git_path("MERGE_MSG");
+ fp = fopen(filename, "a");
if (!fp)
- die_errno(_("Could not open '%s' for writing"),
- git_path("MERGE_MSG"));
- fprintf(fp, "\nConflicts:\n");
- for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
-
- if (ce_stage(ce)) {
- fprintf(fp, "\t%s\n", ce->name);
- while (pos + 1 < active_nr &&
- !strcmp(ce->name,
- active_cache[pos + 1]->name))
- pos++;
- }
- }
+ die_errno(_("Could not open '%s' for writing"), filename);
+
+ append_conflicts_hint(&msgbuf);
+ fputs(msgbuf.buf, fp);
+ strbuf_release(&msgbuf);
fclose(fp);
rerere(allow_rerere_auto);
printf(_("Automatic merge failed; "
@@ -936,7 +902,8 @@ static int suggest_conflicts(int renormalizing)
return 1;
}
-static struct commit *is_old_style_invocation(int argc, const char **argv)
+static struct commit *is_old_style_invocation(int argc, const char **argv,
+ const unsigned char *head)
{
struct commit *second_token = NULL;
if (argc > 2) {
@@ -977,7 +944,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)
@@ -996,7 +963,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;
}
@@ -1005,16 +972,120 @@ static int setup_with_upstream(const char ***argv)
return i;
}
+static void write_merge_state(struct commit_list *remoteheads)
+{
+ const char *filename;
+ int fd;
+ struct commit_list *j;
+ struct strbuf buf = STRBUF_INIT;
+
+ for (j = remoteheads; j; j = j->next) {
+ unsigned const char *sha1;
+ struct commit *c = j->item;
+ if (c->util && merge_remote_util(c)->obj) {
+ sha1 = merge_remote_util(c)->obj->sha1;
+ } else {
+ sha1 = c->object.sha1;
+ }
+ strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+ }
+ filename = git_path("MERGE_HEAD");
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die_errno(_("Could not write to '%s'"), filename);
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ write_merge_msg(&merge_msg);
+
+ filename = git_path("MERGE_MODE");
+ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ strbuf_reset(&buf);
+ 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);
+ close(fd);
+}
+
+static int default_edit_option(void)
+{
+ static const char name[] = "GIT_MERGE_AUTOEDIT";
+ const char *e = getenv(name);
+ struct stat st_stdin, st_stdout;
+
+ if (have_message)
+ /* an explicit -m msg without --[no-]edit */
+ return 0;
+
+ if (e) {
+ int v = git_config_maybe_bool(name, e);
+ if (v < 0)
+ die("Bad value '%s' in environment '%s'", e, name);
+ return v;
+ }
+
+ /* Use editor if stdin and stdout are the same and is a tty */
+ return (!fstat(0, &st_stdin) &&
+ !fstat(1, &st_stdout) &&
+ isatty(0) && isatty(1) &&
+ st_stdin.st_dev == st_stdout.st_dev &&
+ st_stdin.st_ino == st_stdout.st_ino &&
+ st_stdin.st_mode == st_stdout.st_mode);
+}
+
+static struct commit_list *collect_parents(struct commit *head_commit,
+ int *head_subsumed,
+ int argc, const char **argv)
+{
+ int i;
+ struct commit_list *remoteheads = NULL, *parents, *next;
+ struct commit_list **remotes = &remoteheads;
+
+ if (head_commit)
+ remotes = &commit_list_insert(head_commit, remotes)->next;
+ for (i = 0; i < argc; i++) {
+ struct commit *commit = get_merge_parent(argv[i]);
+ if (!commit)
+ help_unknown_ref(argv[i], "merge",
+ "not something we can merge");
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+ *remotes = NULL;
+
+ parents = reduce_heads(remoteheads);
+
+ *head_subsumed = 1; /* we will flip this to 0 when we find it */
+ for (remoteheads = NULL, remotes = &remoteheads;
+ parents;
+ parents = next) {
+ struct commit *commit = parents->item;
+ next = parents->next;
+ if (commit == head_commit)
+ *head_subsumed = 0;
+ else
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+ return remoteheads;
+}
+
int cmd_merge(int argc, const char **argv, const char *prefix)
{
unsigned char result_tree[20];
+ unsigned char stash[20];
+ unsigned char head_sha1[20];
+ struct commit *head_commit;
struct strbuf buf = STRBUF_INIT;
const char *head_arg;
- int flag, head_invalid = 0, i;
+ int flag, i, ret = 0, head_subsumed;
int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
struct commit_list *common = NULL;
const char *best_strategy = NULL, *wt_strategy = NULL;
- struct commit_list **remotes = &remoteheads;
+ struct commit_list *remoteheads, *p;
+ void *branch_to_free;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_merge_usage, builtin_merge_options);
@@ -1023,11 +1094,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
*/
- branch = resolve_ref("HEAD", head, 0, &flag);
- if (branch && !prefixcmp(branch, "refs/heads/"))
+ branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag);
+ if (branch && starts_with(branch, "refs/heads/"))
branch += 11;
- if (is_null_sha1(head))
- head_invalid = 1;
+ if (!branch || is_null_sha1(head_sha1))
+ head_commit = NULL;
+ else
+ head_commit = lookup_commit_or_die(head_sha1, "HEAD");
git_config(git_merge_config, NULL);
@@ -1035,6 +1108,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
parse_branch_merge_options(branch_mergeoptions);
argc = parse_options(argc, argv, prefix, builtin_merge_options,
builtin_merge_usage, 0);
+ if (shortlog_len < 0)
+ shortlog_len = (merge_log_config > 0) ? merge_log_config : 0;
if (verbosity < 0 && show_progress == -1)
show_progress = 0;
@@ -1047,7 +1122,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die(_("There is no merge to abort (MERGE_HEAD missing)."));
/* Invoke 'git reset --merge' */
- return cmd_reset(nargc, nargv, prefix);
+ ret = cmd_reset(nargc, nargv, prefix);
+ goto done;
}
if (read_cache_unmerged())
@@ -1060,14 +1136,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
*/
if (advice_resolve_conflict)
die(_("You have not concluded your merge (MERGE_HEAD exists).\n"
- "Please, commit your changes before you can merge."));
+ "Please, commit your changes before you merge."));
else
die(_("You have not concluded your merge (MERGE_HEAD exists)."));
}
if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
if (advice_resolve_conflict)
die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
- "Please, commit your changes before you can merge."));
+ "Please, commit your changes before you merge."));
else
die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."));
}
@@ -1077,18 +1153,18 @@ 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 && default_to_upstream)
- argc = setup_with_upstream(&argv);
- else if (argc == 1 && !strcmp(argv[0], "-"))
+ if (!argc) {
+ if (default_to_upstream)
+ argc = setup_with_upstream(&argv);
+ else
+ die(_("No commit specified and merge.defaultToUpstream not set."));
+ } else if (argc == 1 && !strcmp(argv[0], "-"))
argv[0] = "@{-1}";
}
if (!argc)
@@ -1099,18 +1175,20 @@ 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.
*/
- if (!have_message && is_old_style_invocation(argc, argv)) {
+ if (!have_message && head_commit &&
+ is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
strbuf_addstr(&merge_msg, argv[0]);
head_arg = argv[1];
argv += 2;
argc -= 2;
- } else if (head_invalid) {
- struct object *remote_head;
+ remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+ } else if (!head_commit) {
+ struct commit *remote_head;
/*
* If the merged head is a valid one there is no reason
* to forbid "git merge" into a branch yet to be born.
@@ -1121,16 +1199,17 @@ 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"));
- remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+ remote_head = remoteheads->item;
if (!remote_head)
die(_("%s - not something we can merge"), argv[0]);
- read_empty(remote_head->sha1, 0);
- update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
- DIE_ON_ERR);
- return 0;
+ read_empty(remote_head->object.sha1, 0);
+ update_ref("initial pull", "HEAD", remote_head->object.sha1,
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+ goto done;
} else {
struct strbuf merge_names = STRBUF_INIT;
@@ -1138,52 +1217,87 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
head_arg = "HEAD";
/*
- * All the rest are the commits being merged;
- * prepare the standard merge summary message to
- * be appended to the given message. If remote
- * is invalid we will die later in the common
- * codepath so we discard the error in this
- * loop.
+ * All the rest are the commits being merged; prepare
+ * the standard merge summary message to be appended
+ * to the given message.
*/
- for (i = 0; i < argc; i++)
- merge_name(argv[i], &merge_names);
+ remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
+ for (p = remoteheads; p; p = p->next)
+ merge_name(merge_remote_util(p->item)->name, &merge_names);
if (!have_message || shortlog_len) {
- fmt_merge_msg(&merge_names, &merge_msg, !have_message,
- shortlog_len);
+ struct fmt_merge_msg_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.add_title = !have_message;
+ opts.shortlog_len = shortlog_len;
+ opts.credit_people = (0 < option_edit);
+
+ fmt_merge_msg(&merge_names, &merge_msg, &opts);
if (merge_msg.len)
strbuf_setlen(&merge_msg, merge_msg.len - 1);
}
}
- if (head_invalid || !argc)
+ if (!head_commit || !argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
+ if (verify_signatures) {
+ for (p = remoteheads; p; p = p->next) {
+ struct commit *commit = p->item;
+ char hex[41];
+ struct signature_check signature_check;
+ memset(&signature_check, 0, sizeof(signature_check));
+
+ check_commit_signature(commit, &signature_check);
+
+ strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+ switch (signature_check.result) {
+ case 'G':
+ break;
+ case 'U':
+ die(_("Commit %s has an untrusted GPG signature, "
+ "allegedly by %s."), hex, signature_check.signer);
+ case 'B':
+ die(_("Commit %s has a bad GPG signature "
+ "allegedly by %s."), hex, signature_check.signer);
+ default: /* 'N' */
+ die(_("Commit %s does not have a GPG signature."), hex);
+ }
+ if (verbosity >= 0 && signature_check.result == 'G')
+ printf(_("Commit %s has a good GPG signature by %s\n"),
+ hex, signature_check.signer);
+
+ signature_check_clear(&signature_check);
+ }
+ }
+
strbuf_addstr(&buf, "merge");
- for (i = 0; i < argc; i++)
- strbuf_addf(&buf, " %s", argv[i]);
+ for (p = remoteheads; p; p = p->next)
+ strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name);
setenv("GIT_REFLOG_ACTION", buf.buf, 0);
strbuf_reset(&buf);
- for (i = 0; i < argc; i++) {
- struct object *o;
- struct commit *commit;
-
- o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
- if (!o)
- die(_("%s - not something we can merge"), argv[i]);
- commit = lookup_commit(o->sha1);
- commit->util = (void *)argv[i];
- remotes = &commit_list_insert(commit, remotes)->next;
-
- strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1));
- setenv(buf.buf, argv[i], 1);
+ for (p = remoteheads; p; p = p->next) {
+ struct commit *commit = p->item;
+ strbuf_addf(&buf, "GITHEAD_%s",
+ sha1_to_hex(commit->object.sha1));
+ setenv(buf.buf, merge_remote_util(commit)->name, 1);
strbuf_reset(&buf);
+ if (fast_forward != FF_ONLY &&
+ merge_remote_util(commit) &&
+ merge_remote_util(commit)->obj &&
+ merge_remote_util(commit)->obj->type == OBJ_TAG)
+ fast_forward = FF_NO;
}
+ if (option_edit < 0)
+ option_edit = default_edit_option();
+
if (!use_strategies) {
- if (!remoteheads->next)
+ if (!remoteheads)
+ ; /* already up-to-date */
+ else if (!remoteheads->next)
add_strategies(pull_twohead, DEFAULT_TWOHEAD);
else
add_strategies(pull_octopus, DEFAULT_OCTOPUS);
@@ -1191,43 +1305,45 @@ 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;
}
- if (!remoteheads->next)
- common = get_merge_bases(lookup_commit(head),
- remoteheads->item, 1);
+ if (!remoteheads)
+ ; /* already up-to-date */
+ else if (!remoteheads->next)
+ common = get_merge_bases(head_commit, remoteheads->item);
else {
struct commit_list *list = remoteheads;
- commit_list_insert(lookup_commit(head), &list);
+ commit_list_insert(head_commit, &list);
common = get_octopus_merge_bases(list);
free(list);
}
- update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
- DIE_ON_ERR);
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
- if (!common)
+ if (remoteheads && !common)
; /* No common ancestors found. We need a real merge. */
- else if (!remoteheads->next && !common->next &&
- common->item == remoteheads->item) {
+ else if (!remoteheads ||
+ (!remoteheads->next && !common->next &&
+ common->item == remoteheads->item)) {
/*
* If head can reach all the merge then we are up to date.
* but first the most common case of merging one remote.
*/
finish_up_to_date("Already up-to-date.");
- return 0;
- } else if (allow_fast_forward && !remoteheads->next &&
+ goto done;
+ } else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
- !hashcmp(common->item->object.sha1, head)) {
+ !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
/* Again the most common case of merging one remote. */
struct strbuf msg = STRBUF_INIT;
- struct object *o;
+ struct commit *commit;
char hex[41];
- strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+ strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
if (verbosity >= 0)
printf(_("Updating %s..%s\n"),
@@ -1238,17 +1354,22 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (have_message)
strbuf_addstr(&msg,
" (no commit created; -m option ignored)");
- o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
- 0, NULL, OBJ_COMMIT);
- if (!o)
- return 1;
+ commit = remoteheads->item;
+ if (!commit) {
+ ret = 1;
+ goto done;
+ }
- if (checkout_fast_forward(head, remoteheads->item->object.sha1))
- return 1;
+ if (checkout_fast_forward(head_commit->object.sha1,
+ commit->object.sha1,
+ overwrite_ignore)) {
+ ret = 1;
+ goto done;
+ }
- finish(o->sha1, msg.buf);
+ finish(head_commit, remoteheads, commit->object.sha1, msg.buf);
drop_save();
- return 0;
+ goto done;
} else if (!remoteheads->next && common->next)
;
/*
@@ -1261,13 +1382,16 @@ 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_ERROR_ON_NO_NAME);
+ git_committer_info(IDENT_STRICT);
printf(_("Trying really trivial in-index merge...\n"));
if (!read_tree_trivial(common->item->object.sha1,
- head, remoteheads->item->object.sha1))
- return merge_trivial();
+ head_commit->object.sha1,
+ remoteheads->item->object.sha1)) {
+ ret = merge_trivial(head_commit, remoteheads);
+ goto done;
+ }
printf(_("Nope.\n"));
}
} else {
@@ -1286,8 +1410,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* merge_bases again, otherwise "git merge HEAD^
* HEAD^^" would be missed.
*/
- common_one = get_merge_bases(lookup_commit(head),
- j->item, 1);
+ common_one = get_merge_bases(head_commit, j->item);
if (hashcmp(common_one->item->object.sha1,
j->item->object.sha1)) {
up_to_date = 0;
@@ -1296,15 +1419,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
if (up_to_date) {
finish_up_to_date("Already up-to-date. Yeeah!");
- return 0;
+ goto done;
}
}
- if (fast_forward_only)
+ if (fast_forward == FF_ONLY)
die(_("Not possible to fast-forward, aborting."));
/* We are going to make a new commit. */
- git_committer_info(IDENT_ERROR_ON_NO_NAME);
+ git_committer_info(IDENT_STRICT);
/*
* At this point, we need a real merge. No matter what strategy
@@ -1314,21 +1437,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* sync with the head commit. The strategies are responsible
* to ensure this.
*/
- if (use_strategies_nr != 1) {
- /*
- * Stash away the local changes so that we can try more
- * than one.
- */
- save_state();
- } else {
- memcpy(stash, null_sha1, 20);
- }
+ if (use_strategies_nr == 1 ||
+ /*
+ * Stash away the local changes so that we can try more than one.
+ */
+ save_state(stash))
+ hashcpy(stash, null_sha1);
for (i = 0; i < use_strategies_nr; i++) {
int ret;
if (i) {
printf(_("Rewinding the tree to pristine...\n"));
- restore_state();
+ restore_state(head_commit->object.sha1, stash);
}
if (use_strategies_nr != 1)
printf(_("Trying merge strategy %s...\n"),
@@ -1340,7 +1460,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
wt_strategy = use_strategies[i]->name;
ret = try_merge_strategy(use_strategies[i]->name,
- common, head_arg);
+ common, remoteheads,
+ head_commit, head_arg);
if (!option_commit && !ret) {
merge_was_ok = 1;
/*
@@ -1381,66 +1502,50 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* If we have a resulting tree, that means the strategy module
* auto resolved the merge cleanly.
*/
- if (automerge_was_ok)
- return finish_automerge(common, result_tree, wt_strategy);
+ if (automerge_was_ok) {
+ ret = finish_automerge(head_commit, head_subsumed,
+ common, remoteheads,
+ result_tree, wt_strategy);
+ goto done;
+ }
/*
* Pick the result from the best strategy and have the user fix
* it up.
*/
if (!best_strategy) {
- restore_state();
+ restore_state(head_commit->object.sha1, stash);
if (use_strategies_nr > 1)
fprintf(stderr,
_("No merge strategy handled the merge.\n"));
else
fprintf(stderr, _("Merge with strategy %s failed.\n"),
use_strategies[0]->name);
- return 2;
+ ret = 2;
+ goto done;
} else if (best_strategy == wt_strategy)
; /* We already have its result in the working tree. */
else {
printf(_("Rewinding the tree to pristine...\n"));
- restore_state();
+ restore_state(head_commit->object.sha1, stash);
printf(_("Using the %s to prepare resolving by hand.\n"),
best_strategy);
- try_merge_strategy(best_strategy, common, head_arg);
+ try_merge_strategy(best_strategy, common, remoteheads,
+ head_commit, head_arg);
}
if (squash)
- finish(NULL, NULL);
- else {
- int fd;
- struct commit_list *j;
-
- for (j = remoteheads; j; j = j->next)
- strbuf_addf(&buf, "%s\n",
- sha1_to_hex(j->item->object.sha1));
- fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno(_("Could not open '%s' for writing"),
- git_path("MERGE_HEAD"));
- if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
- close(fd);
- strbuf_addch(&merge_msg, '\n');
- write_merge_msg();
- fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
- if (fd < 0)
- die_errno(_("Could not open '%s' for writing"),
- git_path("MERGE_MODE"));
- strbuf_reset(&buf);
- if (!allow_fast_forward)
- strbuf_addf(&buf, "no-ff");
- if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
- close(fd);
- }
+ finish(head_commit, remoteheads, NULL, NULL);
+ else
+ write_merge_state(remoteheads);
- if (merge_was_ok) {
+ if (merge_was_ok)
fprintf(stderr, _("Automatic merge went well; "
"stopped before committing as requested\n"));
- return 0;
- } else
- return suggest_conflicts(option_renormalize);
+ else
+ ret = suggest_conflicts();
+
+done:
+ free(branch_to_free);
+ return ret;
}
diff --git a/builtin/mktree.c b/builtin/mktree.c
index 098395f..a964d6b 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -23,10 +23,7 @@ static void append_to_tree(unsigned mode, unsigned char *sha1, char *path)
if (strchr(path, '/'))
die("path %s contains slash", path);
- if (alloc <= used) {
- alloc = alloc_nr(used);
- entries = xrealloc(entries, sizeof(*entries) * alloc);
- }
+ ALLOC_GROW(entries, used + 1, alloc);
ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1);
ent->mode = mode;
ent->len = len;
@@ -60,10 +57,11 @@ static void write_tree(unsigned char *sha1)
}
write_sha1_file(buf.buf, buf.len, tree_type, sha1);
+ strbuf_release(&buf);
}
static const char *mktree_usage[] = {
- "git mktree [-z] [--missing] [--batch]",
+ N_("git mktree [-z] [--missing] [--batch]"),
NULL
};
@@ -149,9 +147,9 @@ int cmd_mktree(int ac, const char **av, const char *prefix)
int got_eof = 0;
const struct option option[] = {
- OPT_SET_INT('z', NULL, &line_termination, "input is NUL terminated", '\0'),
- OPT_SET_INT( 0 , "missing", &allow_missing, "allow missing objects", 1),
- OPT_SET_INT( 0 , "batch", &is_batch_mode, "allow creation of more than one tree", 1),
+ OPT_SET_INT('z', NULL, &line_termination, N_("input is NUL terminated"), '\0'),
+ OPT_SET_INT( 0 , "missing", &allow_missing, N_("allow missing objects"), 1),
+ OPT_SET_INT( 0 , "batch", &is_batch_mode, N_("allow creation of more than one tree"), 1),
OPT_END()
};
diff --git a/builtin/mv.c b/builtin/mv.c
index 40f33ca..563d05b 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -3,20 +3,25 @@
*
* Copyright (C) 2006 Johannes Schindelin
*/
-#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "dir.h"
#include "cache-tree.h"
#include "string-list.h"
#include "parse-options.h"
+#include "submodule.h"
static const char * const builtin_mv_usage[] = {
- "git mv [options] <source>... <destination>",
+ 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,16 @@ 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);
- result[i] = base_name ? strdup(basename(it)) : it;
+ if (flags & DUP_BASENAME) {
+ result[i] = xstrdup(basename(it));
+ free(it);
+ } else
+ result[i] = it;
}
}
return get_pathspec(prefix, result);
@@ -49,22 +59,65 @@ static const char *add_slash(const char *path)
}
static struct lock_file lock_file;
+#define SUBMODULE_WITH_GITDIR ((const char *)1)
+
+static void prepare_move_submodule(const char *src, int first,
+ const char **submodule_gitfile)
+{
+ struct strbuf submodule_dotgit = STRBUF_INIT;
+ if (!S_ISGITLINK(active_cache[first]->ce_mode))
+ die(_("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 = read_gitfile(submodule_dotgit.buf);
+ if (*submodule_gitfile)
+ *submodule_gitfile = xstrdup(*submodule_gitfile);
+ else
+ *submodule_gitfile = SUBMODULE_WITH_GITDIR;
+ strbuf_release(&submodule_dotgit);
+}
+
+static int index_range_of_same_dir(const char *src, int length,
+ int *first_p, int *last_p)
+{
+ const char *src_w_slash = add_slash(src);
+ int first, last, len_w_slash = length + 1;
+
+ first = cache_name_pos(src_w_slash, len_w_slash);
+ if (first >= 0)
+ die(_("%.*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;
+ }
+ if (src_w_slash != src)
+ free((char *)src_w_slash);
+ *first_p = first;
+ *last_p = last;
+ return last - first;
+}
int cmd_mv(int argc, const char **argv, const char *prefix)
{
- int i, newfd;
+ int i, gitmodules_modified = 0;
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
- OPT__DRY_RUN(&show_only, "dry run"),
- OPT__FORCE(&force, "force move/rename even if target exists"),
- OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
+ 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_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,
@@ -72,24 +125,30 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (--argc < 1)
usage_with_options(builtin_mv_usage, builtin_mv_options);
- newfd = hold_locked_index(&lock_file, 1);
+ hold_locked_index(&lock_file, 1);
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)
- usage_with_options(builtin_mv_usage, builtin_mv_options);
+ die(_("destination '%s' is not a directory"), dest_path[0]);
destination = dest_path;
}
@@ -112,59 +171,41 @@ 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;
+ int first = cache_name_pos(src, length), last;
- 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)
+ prepare_move_submodule(src, first,
+ submodule_gitfile + i);
+ else if (index_range_of_same_dir(src, length,
+ &first, &last) < 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));
- }
+ else { /* last - first >= 1 */
+ int j, dst_len, n;
+
+ modes[i] = WORKING_DIRECTORY;
+ n = argc + last - first;
+ REALLOC_ARRAY(source, n);
+ REALLOC_ARRAY(destination, n);
+ REALLOC_ARRAY(modes, n);
+ REALLOC_ARRAY(submodule_gitfile, n);
dst = add_slash(dst);
dst_len = strlen(dst);
for (j = 0; j < last - first; j++) {
- const char *path =
- active_cache[first + j]->name;
+ const char *path = active_cache[first + j]->name;
source[argc + j] = path;
destination[argc + j] =
- prefix_path(dst, dst_len,
- path + length + 1);
+ prefix_path(dst, dst_len, path + length + 1);
modes[argc + j] = INDEX;
+ submodule_gitfile[argc + j] = NULL;
}
argc += last - first;
}
} else if (cache_name_pos(src, length) < 0)
bad = _("not under version control");
- else if (lstat(dst, &st) == 0) {
+ else if (lstat(dst, &st) == 0 &&
+ (!ignore_case || strcasecmp(src, dst))) {
bad = _("destination exists");
if (force) {
/*
@@ -172,29 +213,35 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
* check both source and destination
*/
if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
- warning(_("%s; will overwrite!"), bad);
+ if (verbose)
+ warning(_("overwriting '%s'"), dst);
bad = NULL;
} else
bad = _("Cannot overwrite");
}
} 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);
- if (bad) {
- if (ignore_errors) {
- if (--argc > 0) {
- memmove(source + i, source + i + 1,
- (argc - i) * sizeof(char *));
- memmove(destination + i,
- destination + i + 1,
- (argc - i) * sizeof(char *));
- i--;
- }
- } else
- die (_("%s, source=%s, destination=%s"),
- bad, src, dst);
+ if (!bad)
+ continue;
+ if (!ignore_errors)
+ die(_("%s, source=%s, destination=%s"),
+ bad, src, dst);
+ if (--argc > 0) {
+ int n = argc - i;
+ memmove(source + i, source + i + 1,
+ n * sizeof(char *));
+ memmove(destination + i, destination + i + 1,
+ n * sizeof(char *));
+ memmove(modes + i, modes + i + 1,
+ n * sizeof(enum update_mode));
+ memmove(submodule_gitfile + i, submodule_gitfile + i + 1,
+ n * sizeof(char *));
+ i--;
}
}
@@ -204,9 +251,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;
@@ -217,11 +271,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
rename_cache_entry_at(pos, dst);
}
- if (active_cache_changed) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
- die(_("Unable to write new index file"));
- }
+ if (gitmodules_modified)
+ stage_updated_gitmodules();
+
+ if (active_cache_changed &&
+ write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+ die(_("Unable to write new index file"));
return 0;
}
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 31f5c1c..3c8f319 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,17 +27,13 @@ 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;
if (deref) {
- char *new_name = xmalloc(strlen(tip_name)+3);
- strcpy(new_name, tip_name);
- strcat(new_name, "^0");
- tip_name = new_name;
+ tip_name = xstrfmt("%s^0", tip_name);
if (generation)
die("generation: %d, but deref?", generation);
@@ -82,23 +79,88 @@ copy_data:
}
}
+static int subpath_matches(const char *path, const char *filter)
+{
+ const char *subpath = path;
+
+ while (subpath) {
+ if (!wildmatch(filter, subpath, 0, NULL))
+ 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 +172,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 +212,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)
@@ -172,7 +252,9 @@ static void show_name(const struct object *obj,
}
static char const * const name_rev_usage[] = {
- "git name-rev [options] ( --all | --stdin | <commit>... )",
+ N_("git name-rev [options] <commit>..."),
+ N_("git name-rev [options] --all"),
+ N_("git name-rev [options] --stdin"),
NULL
};
@@ -221,25 +303,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, "print only names (no SHA-1)"),
- OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"),
- OPT_STRING(0, "refs", &data.ref_filter, "pattern",
- "only use refs matching <pattern>"),
+ 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, "list all commits reachable from all refs"),
- OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"),
- OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"),
- OPT_BOOLEAN(0, "always", &always,
- "show abbreviated commit object as fallback"),
+ 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);
}
@@ -248,7 +336,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)) {
@@ -257,17 +345,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)
@@ -289,7 +394,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
max = get_max_object_index();
for (i = 0; i < max; i++) {
struct object *obj = get_indexed_object(i);
- if (!obj)
+ if (!obj || obj->type != OBJ_COMMIT)
continue;
show_name(obj, NULL,
always, allow_undefined, data.name_only);
diff --git a/builtin/notes.c b/builtin/notes.c
index f8e437d..a9f37d0 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,88 +18,96 @@
#include "parse-options.h"
#include "string-list.h"
#include "notes-merge.h"
+#include "notes-utils.h"
static const char * const git_notes_usage[] = {
- "git notes [--ref <notes_ref>] [list [<object>]]",
- "git notes [--ref <notes_ref>] add [-f] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
- "git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>",
- "git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
- "git notes [--ref <notes_ref>] edit [<object>]",
- "git notes [--ref <notes_ref>] show [<object>]",
- "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
- "git notes merge --commit [-v | -q]",
- "git notes merge --abort [-v | -q]",
- "git notes [--ref <notes_ref>] remove [<object>...]",
- "git notes [--ref <notes_ref>] prune [-n | -v]",
- "git notes [--ref <notes_ref>] get-ref",
+ N_("git notes [--ref <notes_ref>] [list [<object>]]"),
+ N_("git notes [--ref <notes_ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+ N_("git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>"),
+ N_("git notes [--ref <notes_ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
+ N_("git notes [--ref <notes_ref>] edit [--allow-empty] [<object>]"),
+ N_("git notes [--ref <notes_ref>] show [<object>]"),
+ N_("git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>"),
+ N_("git notes merge --commit [-v | -q]"),
+ N_("git notes merge --abort [-v | -q]"),
+ N_("git notes [--ref <notes_ref>] remove [<object>...]"),
+ N_("git notes [--ref <notes_ref>] prune [-n | -v]"),
+ N_("git notes [--ref <notes_ref>] get-ref"),
NULL
};
static const char * const git_notes_list_usage[] = {
- "git notes [list [<object>]]",
+ N_("git notes [list [<object>]]"),
NULL
};
static const char * const git_notes_add_usage[] = {
- "git notes add [<options>] [<object>]",
+ N_("git notes add [<options>] [<object>]"),
NULL
};
static const char * const git_notes_copy_usage[] = {
- "git notes copy [<options>] <from-object> <to-object>",
- "git notes copy --stdin [<from-object> <to-object>]...",
+ N_("git notes copy [<options>] <from-object> <to-object>"),
+ N_("git notes copy --stdin [<from-object> <to-object>]..."),
NULL
};
static const char * const git_notes_append_usage[] = {
- "git notes append [<options>] [<object>]",
+ N_("git notes append [<options>] [<object>]"),
NULL
};
static const char * const git_notes_edit_usage[] = {
- "git notes edit [<object>]",
+ N_("git notes edit [<object>]"),
NULL
};
static const char * const git_notes_show_usage[] = {
- "git notes show [<object>]",
+ N_("git notes show [<object>]"),
NULL
};
static const char * const git_notes_merge_usage[] = {
- "git notes merge [<options>] <notes_ref>",
- "git notes merge --commit [<options>]",
- "git notes merge --abort [<options>]",
+ N_("git notes merge [<options>] <notes_ref>"),
+ N_("git notes merge --commit [<options>]"),
+ N_("git notes merge --abort [<options>]"),
NULL
};
static const char * const git_notes_remove_usage[] = {
- "git notes remove [<object>]",
+ N_("git notes remove [<object>]"),
NULL
};
static const char * const git_notes_prune_usage[] = {
- "git notes prune [<options>]",
+ N_("git notes prune [<options>]"),
NULL
};
static const char * const git_notes_get_ref_usage[] = {
- "git notes get-ref",
+ N_("git notes get-ref"),
NULL
};
static const char note_template[] =
- "\n"
- "#\n"
- "# Write/edit the notes for the following object:\n"
- "#\n";
+ "\nWrite/edit the notes for the following object:\n";
-struct msg_arg {
+struct note_data {
int given;
int use_editor;
+ char *edit_path;
struct strbuf buf;
};
+static void free_note_data(struct note_data *d)
+{
+ if (d->edit_path) {
+ unlink_or_warn(d->edit_path);
+ free(d->edit_path);
+ }
+ strbuf_release(&d->buf);
+}
+
static int list_each_note(const unsigned char *object_sha1,
const unsigned char *note_sha1, char *note_path,
void *cb_data)
@@ -108,7 +116,7 @@ static int list_each_note(const unsigned char *object_sha1,
return 0;
}
-static void write_note_data(int fd, const unsigned char *sha1)
+static void copy_obj_to_fd(int fd, const unsigned char *sha1)
{
unsigned long size;
enum object_type type;
@@ -124,12 +132,11 @@ static void write_commented_object(int fd, const unsigned char *object)
{
const char *show_args[5] =
{"show", "--stat", "--no-notes", sha1_to_hex(object), NULL};
- struct child_process show;
+ struct child_process show = CHILD_PROCESS_INIT;
struct strbuf buf = STRBUF_INIT;
- FILE *show_out;
+ struct strbuf cbuf = STRBUF_INIT;
/* Invoke "git show --stat --no-notes $object" */
- memset(&show, 0, sizeof(show));
show.argv = show_args;
show.no_stdin = 1;
show.out = -1;
@@ -139,288 +146,139 @@ static void write_commented_object(int fd, const unsigned char *object)
die(_("unable to start 'show' for object '%s'"),
sha1_to_hex(object));
- /* Open the output as FILE* so strbuf_getline() can be used. */
- show_out = xfdopen(show.out, "r");
- if (show_out == NULL)
- die_errno(_("can't fdopen 'show' output fd"));
+ if (strbuf_read(&buf, show.out, 0) < 0)
+ die_errno(_("could not read 'show' output"));
+ strbuf_add_commented_lines(&cbuf, buf.buf, buf.len);
+ write_or_die(fd, cbuf.buf, cbuf.len);
- /* Prepend "# " to each output line and write result to 'fd' */
- while (strbuf_getline(&buf, show_out, '\n') != EOF) {
- write_or_die(fd, "# ", 2);
- write_or_die(fd, buf.buf, buf.len);
- write_or_die(fd, "\n", 1);
- }
+ strbuf_release(&cbuf);
strbuf_release(&buf);
- if (fclose(show_out))
- die_errno(_("failed to close pipe to 'show' for object '%s'"),
- sha1_to_hex(object));
+
if (finish_command(&show))
die(_("failed to finish 'show' for object '%s'"),
sha1_to_hex(object));
}
-static void create_note(const unsigned char *object, struct msg_arg *msg,
- int append_only, const unsigned char *prev,
- unsigned char *result)
+static void prepare_note_data(const unsigned char *object, struct note_data *d,
+ const unsigned char *old_note)
{
- char *path = NULL;
-
- if (msg->use_editor || !msg->given) {
+ if (d->use_editor || !d->given) {
int fd;
+ struct strbuf buf = STRBUF_INIT;
/* write the template message before editing: */
- path = git_pathdup("NOTES_EDITMSG");
- fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+ d->edit_path = git_pathdup("NOTES_EDITMSG");
+ fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd < 0)
- die_errno(_("could not create file '%s'"), path);
+ die_errno(_("could not create file '%s'"), d->edit_path);
+
+ if (d->given)
+ write_or_die(fd, d->buf.buf, d->buf.len);
+ else if (old_note)
+ copy_obj_to_fd(fd, old_note);
- if (msg->given)
- write_or_die(fd, msg->buf.buf, msg->buf.len);
- else if (prev && !append_only)
- write_note_data(fd, prev);
- write_or_die(fd, note_template, strlen(note_template));
+ strbuf_addch(&buf, '\n');
+ strbuf_add_commented_lines(&buf, note_template, strlen(note_template));
+ strbuf_addch(&buf, '\n');
+ write_or_die(fd, buf.buf, buf.len);
write_commented_object(fd, object);
close(fd);
- strbuf_reset(&(msg->buf));
-
- if (launch_editor(path, &(msg->buf), NULL)) {
- die(_("Please supply the note contents using either -m" \
- " or -F option"));
- }
- stripspace(&(msg->buf), 1);
- }
-
- if (prev && append_only) {
- /* Append buf to previous note contents */
- unsigned long size;
- enum object_type type;
- char *prev_buf = read_sha1_file(prev, &type, &size);
-
- strbuf_grow(&(msg->buf), size + 1);
- if (msg->buf.len && prev_buf && size)
- strbuf_insert(&(msg->buf), 0, "\n", 1);
- if (prev_buf && size)
- strbuf_insert(&(msg->buf), 0, prev_buf, size);
- free(prev_buf);
- }
+ strbuf_release(&buf);
+ strbuf_reset(&d->buf);
- if (!msg->buf.len) {
- fprintf(stderr, _("Removing note for object %s\n"),
- sha1_to_hex(object));
- hashclr(result);
- } else {
- if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) {
- error(_("unable to write note object"));
- if (path)
- error(_("The note contents has been left in %s"),
- path);
- exit(128);
+ if (launch_editor(d->edit_path, &d->buf, NULL)) {
+ die(_("Please supply the note contents using either -m or -F option"));
}
+ stripspace(&d->buf, 1);
}
+}
- if (path) {
- unlink_or_warn(path);
- free(path);
+static void write_note_data(struct note_data *d, unsigned char *sha1)
+{
+ if (write_sha1_file(d->buf.buf, d->buf.len, blob_type, sha1)) {
+ error(_("unable to write note object"));
+ if (d->edit_path)
+ error(_("The note contents have been left in %s"),
+ d->edit_path);
+ exit(128);
}
}
static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
{
- struct msg_arg *msg = opt->value;
+ struct note_data *d = opt->value;
- strbuf_grow(&(msg->buf), strlen(arg) + 2);
- if (msg->buf.len)
- strbuf_addch(&(msg->buf), '\n');
- strbuf_addstr(&(msg->buf), arg);
- stripspace(&(msg->buf), 0);
+ strbuf_grow(&d->buf, strlen(arg) + 2);
+ if (d->buf.len)
+ strbuf_addch(&d->buf, '\n');
+ strbuf_addstr(&d->buf, arg);
+ stripspace(&d->buf, 0);
- msg->given = 1;
+ d->given = 1;
return 0;
}
static int parse_file_arg(const struct option *opt, const char *arg, int unset)
{
- struct msg_arg *msg = opt->value;
+ struct note_data *d = opt->value;
- if (msg->buf.len)
- strbuf_addch(&(msg->buf), '\n');
+ if (d->buf.len)
+ strbuf_addch(&d->buf, '\n');
if (!strcmp(arg, "-")) {
- if (strbuf_read(&(msg->buf), 0, 1024) < 0)
+ if (strbuf_read(&d->buf, 0, 1024) < 0)
die_errno(_("cannot read '%s'"), arg);
- } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0)
+ } else if (strbuf_read_file(&d->buf, arg, 1024) < 0)
die_errno(_("could not open or read '%s'"), arg);
- stripspace(&(msg->buf), 0);
+ stripspace(&d->buf, 0);
- msg->given = 1;
+ d->given = 1;
return 0;
}
static int parse_reuse_arg(const struct option *opt, const char *arg, int unset)
{
- struct msg_arg *msg = opt->value;
+ struct note_data *d = opt->value;
char *buf;
unsigned char object[20];
enum object_type type;
unsigned long len;
- if (msg->buf.len)
- strbuf_addch(&(msg->buf), '\n');
+ if (d->buf.len)
+ strbuf_addch(&d->buf, '\n');
if (get_sha1(arg, object))
die(_("Failed to resolve '%s' as a valid ref."), arg);
- if (!(buf = read_sha1_file(object, &type, &len)) || !len) {
+ if (!(buf = read_sha1_file(object, &type, &len))) {
+ free(buf);
+ die(_("Failed to read object '%s'."), arg);
+ }
+ if (type != OBJ_BLOB) {
free(buf);
- die(_("Failed to read object '%s'."), arg);;
+ die(_("Cannot read note data from non-blob object '%s'."), arg);
}
- strbuf_add(&(msg->buf), buf, len);
+ strbuf_add(&d->buf, buf, len);
free(buf);
- msg->given = 1;
+ d->given = 1;
return 0;
}
static int parse_reedit_arg(const struct option *opt, const char *arg, int unset)
{
- struct msg_arg *msg = opt->value;
- msg->use_editor = 1;
+ struct note_data *d = opt->value;
+ d->use_editor = 1;
return parse_reuse_arg(opt, arg, unset);
}
-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, "notes: "); /* commit message starts at index 7 */
- 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.buf + 7, commit_sha1);
- update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
-
- strbuf_release(&buf);
-}
-
-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);
@@ -462,10 +320,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;
}
@@ -476,7 +334,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;
@@ -523,27 +381,28 @@ static int append_edit(int argc, const char **argv, const char *prefix);
static int add(int argc, const char **argv, const char *prefix)
{
- int retval = 0, force = 0;
+ int force = 0, allow_empty = 0;
const char *object_ref;
struct notes_tree *t;
unsigned char object[20], new_note[20];
- char logmsg[100];
const unsigned char *note;
- struct msg_arg msg = { 0, 0, STRBUF_INIT };
+ struct note_data d = { 0, 0, NULL, STRBUF_INIT };
struct option options[] = {
- { OPTION_CALLBACK, 'm', "message", &msg, "msg",
- "note contents as a string", PARSE_OPT_NONEG,
+ { OPTION_CALLBACK, 'm', "message", &d, N_("message"),
+ N_("note contents as a string"), PARSE_OPT_NONEG,
parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &msg, "file",
- "note contents in a file", PARSE_OPT_NONEG,
+ { OPTION_CALLBACK, 'F', "file", &d, N_("file"),
+ N_("note contents in a file"), PARSE_OPT_NONEG,
parse_file_arg},
- { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object",
- "reuse and edit specified note object", PARSE_OPT_NONEG,
+ { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"),
+ N_("reuse and edit specified note object"), PARSE_OPT_NONEG,
parse_reedit_arg},
- { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
- "reuse specified note object", PARSE_OPT_NONEG,
+ { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"),
+ N_("reuse specified note object"), PARSE_OPT_NONEG,
parse_reuse_arg},
- OPT__FORCE(&force, "replace existing notes"),
+ OPT_BOOL(0, "allow-empty", &allow_empty,
+ N_("allow storing empty note")),
+ OPT__FORCE(&force, N_("replace existing notes")),
OPT_END()
};
@@ -565,41 +424,44 @@ static int add(int argc, const char **argv, const char *prefix)
if (note) {
if (!force) {
- if (!msg.given) {
- /*
- * Redirect to "edit" subcommand.
- *
- * We only end up here if none of -m/-F/-c/-C
- * or -f are given. The original args are
- * therefore still in argv[0-1].
- */
- argv[0] = "edit";
- free_notes(t);
- return append_edit(argc, argv, prefix);
+ free_notes(t);
+ if (d.given) {
+ free_note_data(&d);
+ return error(_("Cannot add notes. "
+ "Found existing notes for object %s. "
+ "Use '-f' to overwrite existing notes"),
+ sha1_to_hex(object));
}
- retval = error(_("Cannot add notes. Found existing notes "
- "for object %s. Use '-f' to overwrite "
- "existing notes"), sha1_to_hex(object));
- goto out;
+ /*
+ * Redirect to "edit" subcommand.
+ *
+ * We only end up here if none of -m/-F/-c/-C or -f are
+ * given. The original args are therefore still in
+ * argv[0-1].
+ */
+ argv[0] = "edit";
+ return append_edit(argc, argv, prefix);
}
fprintf(stderr, _("Overwriting existing notes for object %s\n"),
sha1_to_hex(object));
}
- create_note(object, &msg, 0, note, new_note);
-
- if (is_null_sha1(new_note))
+ prepare_note_data(object, &d, note);
+ if (d.buf.len || allow_empty) {
+ write_note_data(&d, new_note);
+ if (add_note(t, object, new_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
+ commit_notes(t, "Notes added by 'git notes add'");
+ } else {
+ fprintf(stderr, _("Removing note for object %s\n"),
+ sha1_to_hex(object));
remove_note(t, object);
- else if (add_note(t, object, new_note, combine_notes_overwrite))
- die("BUG: combine_notes_overwrite failed");
+ commit_notes(t, "Notes removed by 'git notes add'");
+ }
- snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
- is_null_sha1(new_note) ? "removed" : "added", "add");
- commit_notes(t, logmsg);
-out:
+ free_note_data(&d);
free_notes(t);
- strbuf_release(&(msg.buf));
- return retval;
+ return 0;
}
static int copy(int argc, const char **argv, const char *prefix)
@@ -611,11 +473,11 @@ static int copy(int argc, const char **argv, const char *prefix)
struct notes_tree *t;
const char *rewrite_cmd = NULL;
struct option options[] = {
- OPT__FORCE(&force, "replace existing notes"),
- OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"),
- OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command",
- "load rewriting config for <command> (implies "
- "--stdin)"),
+ OPT__FORCE(&force, N_("replace existing notes")),
+ 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)")),
OPT_END()
};
@@ -680,26 +542,29 @@ out:
static int append_edit(int argc, const char **argv, const char *prefix)
{
+ int allow_empty = 0;
const char *object_ref;
struct notes_tree *t;
unsigned char object[20], new_note[20];
const unsigned char *note;
char logmsg[100];
const char * const *usage;
- struct msg_arg msg = { 0, 0, STRBUF_INIT };
+ struct note_data d = { 0, 0, NULL, STRBUF_INIT };
struct option options[] = {
- { OPTION_CALLBACK, 'm', "message", &msg, "msg",
- "note contents as a string", PARSE_OPT_NONEG,
+ { OPTION_CALLBACK, 'm', "message", &d, N_("message"),
+ N_("note contents as a string"), PARSE_OPT_NONEG,
parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &msg, "file",
- "note contents in a file", PARSE_OPT_NONEG,
+ { OPTION_CALLBACK, 'F', "file", &d, N_("file"),
+ N_("note contents in a file"), PARSE_OPT_NONEG,
parse_file_arg},
- { OPTION_CALLBACK, 'c', "reedit-message", &msg, "object",
- "reuse and edit specified note object", PARSE_OPT_NONEG,
+ { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"),
+ N_("reuse and edit specified note object"), PARSE_OPT_NONEG,
parse_reedit_arg},
- { OPTION_CALLBACK, 'C', "reuse-message", &msg, "object",
- "reuse specified note object", PARSE_OPT_NONEG,
+ { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"),
+ N_("reuse specified note object"), PARSE_OPT_NONEG,
parse_reuse_arg},
+ OPT_BOOL(0, "allow-empty", &allow_empty,
+ N_("allow storing empty note")),
OPT_END()
};
int edit = !strcmp(argv[0], "edit");
@@ -713,7 +578,7 @@ static int append_edit(int argc, const char **argv, const char *prefix)
usage_with_options(usage, options);
}
- if (msg.given && edit)
+ if (d.given && edit)
fprintf(stderr, _("The -m/-F/-c/-C options have been deprecated "
"for the 'edit' subcommand.\n"
"Please use 'git notes add -f -m/-F/-c/-C' instead.\n"));
@@ -726,18 +591,39 @@ static int append_edit(int argc, const char **argv, const char *prefix)
t = init_notes_check(argv[0]);
note = get_note(t, object);
- create_note(object, &msg, !edit, note, new_note);
+ prepare_note_data(object, &d, edit ? note : NULL);
- if (is_null_sha1(new_note))
- remove_note(t, object);
- else if (add_note(t, object, new_note, combine_notes_overwrite))
- die("BUG: combine_notes_overwrite failed");
+ if (note && !edit) {
+ /* Append buf to previous note contents */
+ unsigned long size;
+ enum object_type type;
+ char *prev_buf = read_sha1_file(note, &type, &size);
- snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
- is_null_sha1(new_note) ? "removed" : "added", argv[0]);
+ strbuf_grow(&d.buf, size + 1);
+ if (d.buf.len && prev_buf && size)
+ strbuf_insert(&d.buf, 0, "\n", 1);
+ if (prev_buf && size)
+ strbuf_insert(&d.buf, 0, prev_buf, size);
+ free(prev_buf);
+ }
+
+ if (d.buf.len || allow_empty) {
+ write_note_data(&d, new_note);
+ if (add_note(t, object, new_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
+ snprintf(logmsg, sizeof(logmsg), "Notes added by 'git notes %s'",
+ argv[0]);
+ } else {
+ fprintf(stderr, _("Removing note for object %s\n"),
+ sha1_to_hex(object));
+ remove_note(t, object);
+ snprintf(logmsg, sizeof(logmsg), "Notes removed by 'git notes %s'",
+ argv[0]);
+ }
commit_notes(t, logmsg);
+
+ free_note_data(&d);
free_notes(t);
- strbuf_release(&(msg.buf));
return 0;
}
@@ -804,6 +690,8 @@ static int merge_commit(struct notes_merge_options *o)
struct notes_tree *t;
struct commit *partial;
struct pretty_print_context pretty_ctx;
+ void *local_ref_to_free;
+ int ret;
/*
* Read partial merge result from .git/NOTES_MERGE_PARTIAL,
@@ -825,7 +713,8 @@ static int merge_commit(struct notes_merge_options *o)
t = xcalloc(1, sizeof(struct notes_tree));
init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
- o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL);
+ o->local_ref = local_ref_to_free =
+ resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL);
if (!o->local_ref)
die("Failed to resolve NOTES_MERGE_REF");
@@ -839,11 +728,13 @@ static int merge_commit(struct notes_merge_options *o)
strbuf_insert(&msg, 0, "notes: ", 7);
update_ref(msg.buf, o->local_ref, sha1,
is_null_sha1(parent_sha1) ? NULL : parent_sha1,
- 0, DIE_ON_ERR);
+ 0, UPDATE_REFS_DIE_ON_ERR);
free_notes(t);
strbuf_release(&msg);
- return merge_abort(o);
+ ret = merge_abort(o);
+ free(local_ref_to_free);
+ return ret;
}
static int merge(int argc, const char **argv, const char *prefix)
@@ -856,20 +747,20 @@ static int merge(int argc, const char **argv, const char *prefix)
int verbosity = 0, result;
const char *strategy = NULL;
struct option options[] = {
- OPT_GROUP("General options"),
+ OPT_GROUP(N_("General options")),
OPT__VERBOSITY(&verbosity),
- OPT_GROUP("Merge options"),
- OPT_STRING('s', "strategy", &strategy, "strategy",
- "resolve notes conflicts using the given strategy "
- "(manual/ours/theirs/union/cat_sort_uniq)"),
- OPT_GROUP("Committing unmerged notes"),
- { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
- "finalize notes merge by committing unmerged notes",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG },
- OPT_GROUP("Aborting notes merge resolution"),
- { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL,
- "abort notes merge",
- PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ OPT_GROUP(N_("Merge options")),
+ OPT_STRING('s', "strategy", &strategy, N_("strategy"),
+ N_("resolve notes conflicts using the given strategy "
+ "(manual/ours/theirs/union/cat_sort_uniq)")),
+ OPT_GROUP(N_("Committing unmerged notes")),
+ { OPTION_SET_INT, 0, "commit", &do_commit, NULL,
+ N_("finalize notes merge by committing unmerged notes"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
+ OPT_GROUP(N_("Aborting notes merge resolution")),
+ { OPTION_SET_INT, 0, "abort", &do_abort, NULL,
+ N_("abort notes merge"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
OPT_END()
};
@@ -932,11 +823,11 @@ static int merge(int argc, const char **argv, const char *prefix)
if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
/* Update default notes ref with new commit */
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
- 0, DIE_ON_ERR);
+ 0, UPDATE_REFS_DIE_ON_ERR);
else { /* Merge has unresolved conflicts */
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
- 0, DIE_ON_ERR);
+ 0, UPDATE_REFS_DIE_ON_ERR);
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
die("Failed to store link to current notes ref (%s)",
@@ -975,10 +866,10 @@ static int remove_cmd(int argc, const char **argv, const char *prefix)
int from_stdin = 0;
struct option options[] = {
OPT_BIT(0, "ignore-missing", &flag,
- "attempt to remove non-existent note is not an error",
+ N_("attempt to remove non-existent note is not an error"),
IGNORE_MISSING),
- OPT_BOOLEAN(0, "stdin", &from_stdin,
- "read object names from the standard input"),
+ OPT_BOOL(0, "stdin", &from_stdin,
+ N_("read object names from the standard input")),
OPT_END()
};
struct notes_tree *t;
@@ -1059,8 +950,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
int result;
const char *override_notes_ref = NULL;
struct option options[] = {
- OPT_STRING(0, "ref", &override_notes_ref, "notes_ref",
- "use notes from <notes_ref>"),
+ OPT_STRING(0, "ref", &override_notes_ref, N_("notes-ref"),
+ N_("use notes from <notes_ref>")),
OPT_END()
};
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index a9c67c1..c3a7516 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -14,60 +14,36 @@
#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"
-
-static const char pack_usage[] =
- "git pack-objects [ -q | --progress | --all-progress ]\n"
- " [--all-progress-implied]\n"
- " [--max-pack-size=<n>] [--local] [--incremental]\n"
- " [--window=<n>] [--window-memory=<n>] [--depth=<n>]\n"
- " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n"
- " [--threads=<n>] [--non-empty] [--revs [--unpacked | --all]]\n"
- " [--reflog] [--stdout | base-name] [--include-tag]\n"
- " [--keep-unreachable | --unpack-unreachable]\n"
- " [< ref-list | < object-list]";
-
-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 */
+#include "pack-bitmap.h"
+#include "reachable.h"
+#include "sha1-array.h"
+#include "argv-array.h"
+
+static const char *pack_usage[] = {
+ N_("git pack-objects --stdout [options...] [< ref-list | < object-list]"),
+ N_("git pack-objects [options...] base-name [< ref-list | < object-list]"),
+ NULL
};
/*
- * 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;
static int keep_unreachable, unpack_unreachable, include_tag;
+static unsigned long unpack_unreachable_expiration;
static int local;
static int incremental;
static int ignore_packed_keep;
@@ -76,7 +52,7 @@ static struct pack_idx_option pack_idx_opts;
static const char *base_name;
static int progress = 1;
static int window = 10;
-static unsigned long pack_size_limit, pack_size_limit_cfg;
+static unsigned long pack_size_limit;
static int depth = 50;
static int delta_search_threads;
static int pack_to_stdout;
@@ -85,6 +61,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;
@@ -92,20 +76,27 @@ 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;
+ REALLOC_ARRAY(indexed_commits, indexed_commits_alloc);
+ }
+
+ indexed_commits[indexed_commits_nr++] = commit;
+}
static void *get_delta(struct object_entry *entry)
{
@@ -134,7 +125,6 @@ static unsigned long do_compress(void **pptr, unsigned long size)
void *in, *out;
unsigned long maxsize;
- memset(&stream, 0, sizeof(stream));
git_deflate_init(&stream, pack_compression_level);
maxsize = git_deflate_bound(&stream, size);
@@ -154,6 +144,45 @@ static unsigned long do_compress(void **pptr, unsigned long size)
return stream.total_out;
}
+static unsigned long write_large_blob_data(struct git_istream *st, struct sha1file *f,
+ const unsigned char *sha1)
+{
+ git_zstream stream;
+ unsigned char ibuf[1024 * 16];
+ unsigned char obuf[1024 * 16];
+ unsigned long olen = 0;
+
+ git_deflate_init(&stream, pack_compression_level);
+
+ for (;;) {
+ ssize_t readlen;
+ int zret = Z_OK;
+ readlen = read_istream(st, ibuf, sizeof(ibuf));
+ if (readlen == -1)
+ die(_("unable to read %s"), sha1_to_hex(sha1));
+
+ stream.next_in = ibuf;
+ stream.avail_in = readlen;
+ while ((stream.avail_in || readlen == 0) &&
+ (zret == Z_OK || zret == Z_BUF_ERROR)) {
+ stream.next_out = obuf;
+ stream.avail_out = sizeof(obuf);
+ zret = git_deflate(&stream, readlen ? 0 : Z_FINISH);
+ sha1write(f, obuf, stream.next_out - obuf);
+ olen += stream.next_out - obuf;
+ }
+ if (stream.avail_in)
+ die(_("deflate error (%d)"), zret);
+ if (readlen == 0) {
+ if (zret != Z_STREAM_END)
+ die(_("deflate error (%d)"), zret);
+ break;
+ }
+ }
+ git_deflate_end(&stream);
+ return olen;
+}
+
/*
* we are going to reuse the existing object data as is. make
* sure it is not corrupt.
@@ -204,22 +233,198 @@ static void copy_pack_data(struct sha1file *f,
}
/* Return 0 if we will bust the pack-size limit */
-static unsigned long write_object(struct sha1file *f,
- struct object_entry *entry,
- off_t write_offset)
+static unsigned long write_no_reuse_object(struct sha1file *f, struct object_entry *entry,
+ unsigned long limit, int usable_delta)
{
- unsigned long size, limit, datalen;
- void *buf;
+ unsigned long size, datalen;
unsigned char header[10], dheader[10];
unsigned hdrlen;
enum object_type type;
+ void *buf;
+ struct git_istream *st = NULL;
+
+ if (!usable_delta) {
+ if (entry->type == OBJ_BLOB &&
+ entry->size > big_file_threshold &&
+ (st = open_istream(entry->idx.sha1, &type, &size, NULL)) != NULL)
+ buf = NULL;
+ else {
+ buf = read_sha1_file(entry->idx.sha1, &type, &size);
+ if (!buf)
+ die(_("unable to read %s"), sha1_to_hex(entry->idx.sha1));
+ }
+ /*
+ * make sure no cached delta data remains from a
+ * previous attempt before a pack split occurred.
+ */
+ free(entry->delta_data);
+ entry->delta_data = NULL;
+ entry->z_delta_size = 0;
+ } else if (entry->delta_data) {
+ size = entry->delta_size;
+ buf = entry->delta_data;
+ entry->delta_data = NULL;
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ } else {
+ buf = get_delta(entry);
+ size = entry->delta_size;
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ }
+
+ if (st) /* large blob case, just assume we don't compress well */
+ datalen = size;
+ else if (entry->z_delta_size)
+ datalen = entry->z_delta_size;
+ else
+ datalen = do_compress(&buf, size);
+
+ /*
+ * The object header is a byte of 'type' followed by zero or
+ * more bytes of length.
+ */
+ hdrlen = encode_in_pack_object_header(type, size, header);
+
+ if (type == OBJ_OFS_DELTA) {
+ /*
+ * Deltas with relative base contain an additional
+ * encoding of the relative offset for the delta
+ * base from this object's position in the pack.
+ */
+ off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+ unsigned pos = sizeof(dheader) - 1;
+ dheader[pos] = ofs & 127;
+ while (ofs >>= 7)
+ dheader[--pos] = 128 | (--ofs & 127);
+ if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+ if (st)
+ close_istream(st);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ } else if (type == OBJ_REF_DELTA) {
+ /*
+ * Deltas with a base reference contain
+ * an additional 20 bytes for the base sha1.
+ */
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ if (st)
+ close_istream(st);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
+ hdrlen += 20;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit) {
+ if (st)
+ close_istream(st);
+ free(buf);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ }
+ if (st) {
+ datalen = write_large_blob_data(st, f, entry->idx.sha1);
+ close_istream(st);
+ } else {
+ sha1write(f, buf, datalen);
+ free(buf);
+ }
+
+ return hdrlen + datalen;
+}
+
+/* Return 0 if we will bust the pack-size limit */
+static unsigned long write_reuse_object(struct sha1file *f, struct object_entry *entry,
+ unsigned long limit, int usable_delta)
+{
+ struct packed_git *p = entry->in_pack;
+ struct pack_window *w_curs = NULL;
+ struct revindex_entry *revidx;
+ off_t offset;
+ enum object_type type = entry->type;
+ unsigned long datalen;
+ unsigned char header[10], dheader[10];
+ unsigned hdrlen;
+
+ if (entry->delta)
+ type = (allow_ofs_delta && entry->delta->idx.offset) ?
+ OBJ_OFS_DELTA : OBJ_REF_DELTA;
+ hdrlen = encode_in_pack_object_header(type, entry->size, header);
+
+ offset = entry->in_pack_offset;
+ revidx = find_pack_revindex(p, offset);
+ datalen = revidx[1].offset - offset;
+ if (!pack_to_stdout && p->index_version > 1 &&
+ check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
+ error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
+ unuse_pack(&w_curs);
+ return write_no_reuse_object(f, entry, limit, usable_delta);
+ }
+
+ offset += entry->in_pack_header_size;
+ datalen -= entry->in_pack_header_size;
+
+ if (!pack_to_stdout && p->index_version == 1 &&
+ check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
+ error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
+ unuse_pack(&w_curs);
+ return write_no_reuse_object(f, entry, limit, usable_delta);
+ }
+
+ if (type == OBJ_OFS_DELTA) {
+ off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+ unsigned pos = sizeof(dheader) - 1;
+ dheader[pos] = ofs & 127;
+ while (ofs >>= 7)
+ dheader[--pos] = 128 | (--ofs & 127);
+ if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, dheader + pos, sizeof(dheader) - pos);
+ hdrlen += sizeof(dheader) - pos;
+ reused_delta++;
+ } else if (type == OBJ_REF_DELTA) {
+ if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ sha1write(f, entry->delta->idx.sha1, 20);
+ hdrlen += 20;
+ reused_delta++;
+ } else {
+ if (limit && hdrlen + datalen + 20 >= limit) {
+ unuse_pack(&w_curs);
+ return 0;
+ }
+ sha1write(f, header, hdrlen);
+ }
+ copy_pack_data(f, p, &w_curs, offset, datalen);
+ unuse_pack(&w_curs);
+ reused++;
+ return hdrlen + datalen;
+}
+
+/* Return 0 if we will bust the pack-size limit */
+static unsigned long write_object(struct sha1file *f,
+ struct object_entry *entry,
+ off_t write_offset)
+{
+ unsigned long limit, len;
int usable_delta, to_reuse;
if (!pack_to_stdout)
crc32_begin(f);
- type = entry->type;
-
/* apply size limit if limited packsize and not first object */
if (!pack_size_limit || !nr_written)
limit = 0;
@@ -247,11 +452,11 @@ static unsigned long write_object(struct sha1file *f,
to_reuse = 0; /* explicit */
else if (!entry->in_pack)
to_reuse = 0; /* can't reuse what we don't have */
- else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
+ else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA)
/* check_object() decided it for us ... */
to_reuse = usable_delta;
/* ... but pack split may override that */
- else if (type != entry->in_pack_type)
+ else if (entry->type != entry->in_pack_type)
to_reuse = 0; /* pack has delta which is unusable */
else if (entry->delta)
to_reuse = 0; /* we want to pack afresh */
@@ -260,174 +465,71 @@ static unsigned long write_object(struct sha1file *f,
* and we do not need to deltify it.
*/
- if (!to_reuse) {
- no_reuse:
- if (!usable_delta) {
- buf = read_sha1_file(entry->idx.sha1, &type, &size);
- if (!buf)
- die("unable to read %s", sha1_to_hex(entry->idx.sha1));
- /*
- * make sure no cached delta data remains from a
- * previous attempt before a pack split occurred.
- */
- free(entry->delta_data);
- entry->delta_data = NULL;
- entry->z_delta_size = 0;
- } else if (entry->delta_data) {
- size = entry->delta_size;
- buf = entry->delta_data;
- entry->delta_data = NULL;
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- } else {
- buf = get_delta(entry);
- size = entry->delta_size;
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- }
-
- if (entry->z_delta_size)
- datalen = entry->z_delta_size;
- else
- datalen = do_compress(&buf, size);
-
- /*
- * The object header is a byte of 'type' followed by zero or
- * more bytes of length.
- */
- hdrlen = encode_in_pack_object_header(type, size, header);
-
- if (type == OBJ_OFS_DELTA) {
- /*
- * Deltas with relative base contain an additional
- * encoding of the relative offset for the delta
- * base from this object's position in the pack.
- */
- off_t ofs = entry->idx.offset - entry->delta->idx.offset;
- unsigned pos = sizeof(dheader) - 1;
- dheader[pos] = ofs & 127;
- while (ofs >>= 7)
- dheader[--pos] = 128 | (--ofs & 127);
- if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
- free(buf);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, dheader + pos, sizeof(dheader) - pos);
- hdrlen += sizeof(dheader) - pos;
- } else if (type == OBJ_REF_DELTA) {
- /*
- * Deltas with a base reference contain
- * an additional 20 bytes for the base sha1.
- */
- if (limit && hdrlen + 20 + datalen + 20 >= limit) {
- free(buf);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, entry->delta->idx.sha1, 20);
- hdrlen += 20;
- } else {
- if (limit && hdrlen + datalen + 20 >= limit) {
- free(buf);
- return 0;
- }
- sha1write(f, header, hdrlen);
- }
- sha1write(f, buf, datalen);
- free(buf);
- }
- else {
- struct packed_git *p = entry->in_pack;
- struct pack_window *w_curs = NULL;
- struct revindex_entry *revidx;
- off_t offset;
-
- if (entry->delta)
- type = (allow_ofs_delta && entry->delta->idx.offset) ?
- OBJ_OFS_DELTA : OBJ_REF_DELTA;
- hdrlen = encode_in_pack_object_header(type, entry->size, header);
-
- offset = entry->in_pack_offset;
- revidx = find_pack_revindex(p, offset);
- datalen = revidx[1].offset - offset;
- if (!pack_to_stdout && p->index_version > 1 &&
- check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) {
- error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
- unuse_pack(&w_curs);
- goto no_reuse;
- }
-
- offset += entry->in_pack_header_size;
- datalen -= entry->in_pack_header_size;
- if (!pack_to_stdout && p->index_version == 1 &&
- check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) {
- error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
- unuse_pack(&w_curs);
- goto no_reuse;
- }
+ if (!to_reuse)
+ len = write_no_reuse_object(f, entry, limit, usable_delta);
+ else
+ len = write_reuse_object(f, entry, limit, usable_delta);
+ if (!len)
+ return 0;
- if (type == OBJ_OFS_DELTA) {
- off_t ofs = entry->idx.offset - entry->delta->idx.offset;
- unsigned pos = sizeof(dheader) - 1;
- dheader[pos] = ofs & 127;
- while (ofs >>= 7)
- dheader[--pos] = 128 | (--ofs & 127);
- if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, dheader + pos, sizeof(dheader) - pos);
- hdrlen += sizeof(dheader) - pos;
- reused_delta++;
- } else if (type == OBJ_REF_DELTA) {
- if (limit && hdrlen + 20 + datalen + 20 >= limit) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- sha1write(f, entry->delta->idx.sha1, 20);
- hdrlen += 20;
- reused_delta++;
- } else {
- if (limit && hdrlen + datalen + 20 >= limit) {
- unuse_pack(&w_curs);
- return 0;
- }
- sha1write(f, header, hdrlen);
- }
- copy_pack_data(f, p, &w_curs, offset, datalen);
- unuse_pack(&w_curs);
- reused++;
- }
if (usable_delta)
written_delta++;
written++;
if (!pack_to_stdout)
entry->idx.crc32 = crc32_end(f);
- return hdrlen + datalen;
+ return len;
}
-static int write_one(struct sha1file *f,
- struct object_entry *e,
- off_t *offset)
+enum write_one_status {
+ WRITE_ONE_SKIP = -1, /* already written */
+ WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */
+ WRITE_ONE_WRITTEN = 1, /* normal */
+ WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */
+};
+
+static enum write_one_status write_one(struct sha1file *f,
+ struct object_entry *e,
+ off_t *offset)
{
unsigned long size;
+ int recursing;
- /* offset is non zero if object is written already. */
- if (e->idx.offset || e->preferred_base)
- return -1;
+ /*
+ * we set offset to 1 (which is an impossible value) to mark
+ * the fact that this object is involved in "write its base
+ * first before writing a deltified object" recursion.
+ */
+ recursing = (e->idx.offset == 1);
+ if (recursing) {
+ warning("recursive delta detected for object %s",
+ sha1_to_hex(e->idx.sha1));
+ return WRITE_ONE_RECURSIVE;
+ } else if (e->idx.offset || e->preferred_base) {
+ /* offset is non zero if object is written already. */
+ return WRITE_ONE_SKIP;
+ }
/* if we are deltified, write out base object first. */
- if (e->delta && !write_one(f, e->delta, offset))
- return 0;
+ if (e->delta) {
+ e->idx.offset = 1; /* now recurse */
+ switch (write_one(f, e->delta, offset)) {
+ case WRITE_ONE_RECURSIVE:
+ /* we cannot depend on this one */
+ e->delta = NULL;
+ break;
+ default:
+ break;
+ case WRITE_ONE_BREAK:
+ e->idx.offset = recursing;
+ return WRITE_ONE_BREAK;
+ }
+ }
e->idx.offset = *offset;
size = write_object(f, e, *offset);
if (!size) {
- e->idx.offset = 0;
- return 0;
+ e->idx.offset = recursing;
+ return WRITE_ONE_BREAK;
}
written_list[nr_written++] = &e->idx;
@@ -435,27 +537,27 @@ static int write_one(struct sha1file *f,
if (signed_add_overflows(*offset, size))
die("pack too large for current definition of off_t");
*offset += size;
- return 1;
+ return WRITE_ONE_WRITTEN;
}
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;
}
return 0;
}
-static void add_to_write_order(struct object_entry **wo,
- int *endp,
+static inline void add_to_write_order(struct object_entry **wo,
+ unsigned int *endp,
struct object_entry *e)
{
if (e->filled)
@@ -465,36 +567,67 @@ static void add_to_write_order(struct object_entry **wo,
}
static void add_descendants_to_write_order(struct object_entry **wo,
- int *endp,
+ unsigned int *endp,
struct object_entry *e)
{
- struct object_entry *child;
-
- for (child = e->delta_child; child; child = child->delta_sibling)
- add_to_write_order(wo, endp, child);
- for (child = e->delta_child; child; child = child->delta_sibling)
- add_descendants_to_write_order(wo, endp, child);
+ int add_to_order = 1;
+ while (e) {
+ if (add_to_order) {
+ struct object_entry *s;
+ /* add this node... */
+ add_to_write_order(wo, endp, e);
+ /* all its siblings... */
+ for (s = e->delta_sibling; s; s = s->delta_sibling) {
+ add_to_write_order(wo, endp, s);
+ }
+ }
+ /* drop down a level to add left subtree nodes if possible */
+ if (e->delta_child) {
+ add_to_order = 1;
+ e = e->delta_child;
+ } else {
+ add_to_order = 0;
+ /* our sibling might have some children, it is next */
+ if (e->delta_sibling) {
+ e = e->delta_sibling;
+ continue;
+ }
+ /* go back to our parent node */
+ e = e->delta;
+ while (e && !e->delta_sibling) {
+ /* we're on the right side of a subtree, keep
+ * going up until we can go right again */
+ e = e->delta;
+ }
+ if (!e) {
+ /* done- we hit our original root node */
+ return;
+ }
+ /* pass it off to sibling at this level */
+ e = e->delta_sibling;
+ }
+ };
}
static void add_family_to_write_order(struct object_entry **wo,
- int *endp,
+ unsigned int *endp,
struct object_entry *e)
{
struct object_entry *root;
for (root = e; root->delta; root = root->delta)
; /* nothing */
- add_to_write_order(wo, endp, root);
add_descendants_to_write_order(wo, endp, root);
}
static struct object_entry **compute_write_order(void)
{
- int i, wo_end;
+ 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;
@@ -506,8 +639,8 @@ static struct object_entry **compute_write_order(void)
* Make sure delta_sibling is sorted in the original
* recency order.
*/
- for (i = nr_objects - 1; 0 <= i; i--) {
- struct object_entry *e = &objects[i];
+ for (i = to_pack.nr_objects; i > 0;) {
+ struct object_entry *e = &objects[--i];
if (!e->delta)
continue;
/* Mark me as the first child */
@@ -521,19 +654,20 @@ static struct object_entry **compute_write_order(void)
for_each_tag_ref(mark_tagged, NULL);
/*
- * Give the commits in the original recency order until
+ * 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]);
}
+ last_untagged = i;
/*
* 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]);
}
@@ -541,7 +675,7 @@ static struct object_entry **compute_write_order(void)
/*
* And then all remaining commits and tags.
*/
- for (i = 0; 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;
@@ -551,7 +685,7 @@ static struct object_entry **compute_write_order(void)
/*
* And then all the trees.
*/
- for (i = 0; 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]);
@@ -560,51 +694,107 @@ static struct object_entry **compute_write_order(void)
/*
* Finally all the rest in really tight order
*/
- for (i = 0; i < nr_objects; i++)
- add_family_to_write_order(wo, &wo_end, &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 != 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, total;
+ 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;
+
+ total = 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;
+
+ /*
+ * We don't know the actual number of objects written,
+ * only how many bytes written, how many bytes total, and
+ * how many objects total. So we can fake it by pretending all
+ * objects we are writing are the same size. This gives us a
+ * smooth progress meter, and at the end it matches the true
+ * answer.
+ */
+ written = reuse_packfile_objects *
+ (((double)(total - to_write)) / total);
+ display_progress(progress_state, written);
+ }
+
+ close(fd);
+ written = reuse_packfile_objects;
+ display_progress(progress_state, written);
+ return reuse_packfile_offset - sizeof(struct pack_header);
+}
+
static void write_pack_file(void)
{
uint32_t i = 0, j;
struct sha1file *f;
off_t offset;
- struct pack_header hdr;
uint32_t nr_remaining = nr_result;
time_t last_mtime = 0;
struct object_entry **write_order;
if (progress > pack_to_stdout)
- progress_state = start_progress("Writing objects", nr_result);
- written_list = xmalloc(nr_objects * sizeof(*written_list));
+ progress_state = start_progress(_("Writing objects"), nr_result);
+ written_list = xmalloc(to_pack.nr_objects * sizeof(*written_list));
write_order = compute_write_order();
do {
unsigned char sha1[20];
char *pack_tmp_name = NULL;
- if (pack_to_stdout) {
+ if (pack_to_stdout)
f = sha1fd_throughput(1, "<stdout>", progress_state);
- } else {
- char tmpname[PATH_MAX];
- int fd;
- fd = odb_mkstemp(tmpname, sizeof(tmpname),
- "pack/tmp_pack_XXXXXX");
- pack_tmp_name = xstrdup(tmpname);
- f = sha1fd(fd, pack_tmp_name);
+ else
+ f = create_tmp_packfile(&pack_tmp_name);
+
+ offset = write_pack_header(f, nr_remaining);
+
+ if (reuse_packfile) {
+ off_t packfile_size;
+ assert(pack_to_stdout);
+
+ packfile_size = write_reused_pack(f);
+ offset += packfile_size;
}
- hdr.hdr_signature = htonl(PACK_SIGNATURE);
- hdr.hdr_version = htonl(PACK_VERSION);
- hdr.hdr_entries = htonl(nr_remaining);
- sha1write(f, &hdr, sizeof(hdr));
- offset = sizeof(hdr);
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))
+ if (write_one(f, e, &offset) == WRITE_ONE_BREAK)
break;
display_progress(progress_state, written);
}
@@ -622,23 +812,12 @@ static void write_pack_file(void)
fixup_pack_header_footer(fd, sha1, pack_tmp_name,
nr_written, sha1, offset);
close(fd);
+ write_bitmap_index = 0;
}
if (!pack_to_stdout) {
struct stat st;
- const char *idx_tmp_name;
- char tmpname[PATH_MAX];
-
- idx_tmp_name = write_idx_file(NULL, written_list, nr_written,
- &pack_idx_opts, sha1);
-
- snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
- base_name, sha1_to_hex(sha1));
- free_pack_by_name(tmpname);
- if (adjust_shared_perm(pack_tmp_name))
- die_errno("unable to make temporary pack file readable");
- if (rename(pack_tmp_name, tmpname))
- die_errno("unable to rename temporary pack file");
+ struct strbuf tmpname = STRBUF_INIT;
/*
* Packs are runtime accessed in their mtime
@@ -647,28 +826,46 @@ static void write_pack_file(void)
* packs then we should modify the mtime of later ones
* to preserve this property.
*/
- if (stat(tmpname, &st) < 0) {
+ if (stat(pack_tmp_name, &st) < 0) {
warning("failed to stat %s: %s",
- tmpname, strerror(errno));
+ pack_tmp_name, strerror(errno));
} else if (!last_mtime) {
last_mtime = st.st_mtime;
} else {
struct utimbuf utb;
utb.actime = st.st_atime;
utb.modtime = --last_mtime;
- if (utime(tmpname, &utb) < 0)
+ if (utime(pack_tmp_name, &utb) < 0)
warning("failed utime() on %s: %s",
- tmpname, strerror(errno));
+ pack_tmp_name, strerror(errno));
+ }
+
+ strbuf_addf(&tmpname, "%s-", base_name);
+
+ if (write_bitmap_index) {
+ bitmap_writer_set_checksum(sha1);
+ bitmap_writer_build_type_index(written_list, nr_written);
}
- snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
- base_name, sha1_to_hex(sha1));
- if (adjust_shared_perm(idx_tmp_name))
- die_errno("unable to make temporary index file readable");
- if (rename(idx_tmp_name, tmpname))
- die_errno("unable to rename temporary index file");
+ finish_tmp_packfile(&tmpname, pack_tmp_name,
+ written_list, nr_written,
+ &pack_idx_opts, sha1);
- free((void *) idx_tmp_name);
+ if (write_bitmap_index) {
+ strbuf_addf(&tmpname, "%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.buf, write_bitmap_options);
+ write_bitmap_index = 0;
+ }
+
+ strbuf_release(&tmpname);
free(pack_tmp_name);
puts(sha1_to_hex(sha1));
}
@@ -678,7 +875,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);
@@ -688,73 +885,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;
@@ -777,38 +907,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) {
- found_offset = offset;
- found_pack = p;
+ if (!*found_pack) {
+ if (!is_pack_valid(p)) {
+ warning("packfile %s cannot be accessed", p->pack_name);
+ continue;
+ }
+ *found_offset = offset;
+ *found_pack = p;
}
if (exclude)
- break;
+ return 1;
if (incremental)
return 0;
if (local && !p->pack_local)
@@ -818,14 +979,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;
@@ -838,16 +1006,53 @@ 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 const char no_closure_warning[] = N_(
+"disabling bitmap writing, as some objects are not being packed"
+);
+
+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;
+
+ if (have_duplicate_entry(sha1, exclude, &index_pos))
+ return 0;
- display_progress(progress_state, nr_objects);
+ if (!want_object_in_pack(sha1, exclude, &found_pack, &found_offset)) {
+ /* The pack is missing an object, so it will not have closure */
+ if (write_bitmap_index) {
+ warning(_(no_closure_warning));
+ write_bitmap_index = 0;
+ }
+ 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, nr_result);
+ 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, nr_result);
return 1;
}
@@ -872,7 +1077,7 @@ static int pbase_tree_cache_ix_incr(int ix)
static struct pbase_tree {
struct pbase_tree *next;
/* This is a phony "cache" entry; we are not
- * going to evict it nor find it through _get()
+ * going to evict it or find it through _get()
* mechanism -- this is for the toplevel node that
* would almost always change with any commit.
*/
@@ -975,7 +1180,7 @@ static void add_pbase_object(struct tree_desc *tree,
while (tree_entry(tree,&entry)) {
if (S_ISGITLINK(entry.mode))
continue;
- cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
+ cmp = tree_entry_len(&entry) != cmplen ? 1 :
memcmp(name, entry.path, cmplen);
if (cmp > 0)
continue;
@@ -1029,12 +1234,9 @@ static int check_pbase_path(unsigned hash)
if (0 <= pos)
return 1;
pos = -pos - 1;
- if (done_pbase_paths_alloc <= done_pbase_paths_num) {
- done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc);
- done_pbase_paths = xrealloc(done_pbase_paths,
- done_pbase_paths_alloc *
- sizeof(unsigned));
- }
+ ALLOC_GROW(done_pbase_paths,
+ done_pbase_paths_num + 1,
+ done_pbase_paths_alloc);
done_pbase_paths_num++;
if (pos < done_pbase_paths_num)
memmove(done_pbase_paths + pos + 1,
@@ -1048,7 +1250,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;
@@ -1200,7 +1402,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
@@ -1274,15 +1476,15 @@ 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)
+ if (big_file_threshold < entry->size)
entry->no_try_delta = 1;
}
@@ -1385,11 +1587,16 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
return -1;
/*
- * We do not bother to try a delta that we discarded
- * on an earlier try, but only when reusing delta data.
+ * We do not bother to try a delta that we discarded on an
+ * earlier try, but only when reusing delta data. Note that
+ * src_entry that is marked as the preferred_base should always
+ * be considered, as even if we produce a suboptimal delta against
+ * it, we will still save the transfer cost, as we already know
+ * the other side has it and we won't send src_entry at all.
*/
if (reuse_delta && trg_entry->in_pack &&
trg_entry->in_pack == src_entry->in_pack &&
+ !src_entry->preferred_base &&
trg_entry->in_pack_type != OBJ_REF_DELTA &&
trg_entry->in_pack_type != OBJ_OFS_DELTA)
return 0;
@@ -1676,7 +1883,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();
}
@@ -1767,8 +1974,6 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
init_threaded_search();
- if (!delta_search_threads) /* --threads=0 means autodetect */
- delta_search_threads = online_cpus();
if (delta_search_threads <= 1) {
find_deltas(list, &list_size, window, depth, processed);
cleanup_threaded_search();
@@ -1898,10 +2103,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? */
- !is_null_sha1(peeled) && /* annotated tag? */
- locate_object_entry(peeled)) /* object packed? */
+ packlist_find(&to_pack, peeled, NULL)) /* object packed? */
add_object_entry(sha1, OBJ_TAG, NULL, 0);
return 0;
}
@@ -1924,14 +2128,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
@@ -1966,7 +2170,7 @@ static void prepare_pack(int window, int depth)
if (nr_deltas && n > 1) {
unsigned nr_done = 0;
if (progress)
- progress_state = start_progress("Compressing objects",
+ progress_state = start_progress(_("Compressing objects"),
nr_deltas);
qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
@@ -2009,6 +2213,16 @@ 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.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)
@@ -2027,10 +2241,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)
pack_idx_opts.version);
return 0;
}
- if (!strcmp(k, "pack.packsizelimit")) {
- pack_size_limit_cfg = git_config_ulong(k, v);
- return 0;
- }
return git_default_config(k, v, cb);
}
@@ -2071,9 +2281,14 @@ 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, const struct name_path *path, const char *last)
+static void show_object(struct object *obj,
+ const struct name_path *path, const char *last,
+ void *data)
{
char *name = path_name(path, last);
@@ -2192,6 +2407,27 @@ static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
return 0;
}
+/*
+ * Store a list of sha1s that are should not be discarded
+ * because they are either written too recently, or are
+ * reachable from another object that was.
+ *
+ * This is filled by get_object_list.
+ */
+static struct sha1_array recent_objects;
+
+static int loosened_object_can_be_discarded(const unsigned char *sha1,
+ unsigned long mtime)
+{
+ if (!unpack_unreachable_expiration)
+ return 0;
+ if (mtime > unpack_unreachable_expiration)
+ return 0;
+ if (sha1_array_lookup(&recent_objects, sha1) >= 0)
+ return 0;
+ return 1;
+}
+
static void loosen_unused_packed_objects(struct rev_info *revs)
{
struct packed_git *p;
@@ -2207,14 +2443,57 @@ 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) &&
- !has_sha1_pack_kept_or_nonlocal(sha1))
+ if (!packlist_find(&to_pack, sha1, NULL) &&
+ !has_sha1_pack_kept_or_nonlocal(sha1) &&
+ !loosened_object_can_be_discarded(sha1, p->mtime))
if (force_object_loose(sha1, p->mtime))
die("unable to force loose object");
}
}
}
+/*
+ * This tracks any options which a reader of the pack might
+ * not understand, and which would therefore prevent blind reuse
+ * of what we have on disk.
+ */
+static int pack_options_allow_reuse(void)
+{
+ return allow_ofs_delta;
+}
+
+static int get_object_list_from_bitmap(struct rev_info *revs)
+{
+ if (prepare_bitmap_walk(revs) < 0)
+ return -1;
+
+ if (pack_options_allow_reuse() &&
+ !reuse_partial_packfile_from_bitmap(
+ &reuse_packfile,
+ &reuse_packfile_objects,
+ &reuse_packfile_offset)) {
+ assert(reuse_packfile_objects);
+ nr_result += reuse_packfile_objects;
+ display_progress(progress_state, nr_result);
+ }
+
+ traverse_bitmap_commit_list(&add_object_entry_from_bitmap);
+ return 0;
+}
+
+static void record_recent_object(struct object *obj,
+ const struct name_path *path,
+ const char *last,
+ void *data)
+{
+ sha1_array_append(&recent_objects, obj->sha1);
+}
+
+static void record_recent_commit(struct commit *commit, void *data)
+{
+ sha1_array_append(&recent_objects, commit->object.sha1);
+}
+
static void get_object_list(int ac, const char **av)
{
struct rev_info revs;
@@ -2225,6 +2504,9 @@ static void get_object_list(int ac, const char **av)
save_commit_buffer = 0;
setup_revisions(ac, av, &revs, NULL);
+ /* make sure shallows are read */
+ is_repository_shallow();
+
while (fgets(line, sizeof(line), stdin) != NULL) {
int len = strlen(line);
if (len && line[len - 1] == '\n')
@@ -2234,42 +2516,182 @@ static void get_object_list(int ac, const char **av)
if (*line == '-') {
if (!strcmp(line, "--not")) {
flags ^= UNINTERESTING;
+ write_bitmap_index = 0;
+ continue;
+ }
+ if (starts_with(line, "--shallow ")) {
+ unsigned char sha1[20];
+ if (get_sha1_hex(line + 10, sha1))
+ die("not an SHA-1 '%s'", line + 10);
+ register_shallow(sha1);
+ use_bitmap_index = 0;
continue;
}
die("not a rev '%s'", line);
}
- if (handle_revision_arg(line, &revs, flags, 1))
+ if (handle_revision_arg(line, &revs, flags, REVARG_CANNOT_BE_FILENAME))
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 (unpack_unreachable_expiration) {
+ revs.ignore_missing_links = 1;
+ if (add_unseen_recent_objects_to_traversal(&revs,
+ unpack_unreachable_expiration))
+ die("unable to add recent objects");
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ traverse_commit_list(&revs, record_recent_commit,
+ record_recent_object, NULL);
+ }
+
if (keep_unreachable)
add_objects_in_unpacked_packs(&revs);
if (unpack_unreachable)
loosen_unused_packed_objects(&revs);
+
+ sha1_array_clear(&recent_objects);
}
+static int option_parse_index_version(const struct option *opt,
+ const char *arg, int unset)
+{
+ char *c;
+ const char *val = arg;
+ pack_idx_opts.version = strtoul(val, &c, 10);
+ if (pack_idx_opts.version > 2)
+ die(_("unsupported index version %s"), val);
+ if (*c == ',' && c[1])
+ pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
+ if (*c || pack_idx_opts.off32_limit & 0x80000000)
+ die(_("bad index version '%s'"), val);
+ return 0;
+}
+
+static int option_parse_unpack_unreachable(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset) {
+ unpack_unreachable = 0;
+ unpack_unreachable_expiration = 0;
+ }
+ else {
+ unpack_unreachable = 1;
+ if (arg)
+ unpack_unreachable_expiration = approxidate(arg);
+ }
+ return 0;
+}
+
+static int option_parse_ulong(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset)
+ die(_("option %s does not accept negative form"),
+ opt->long_name);
+
+ if (!git_parse_ulong(arg, opt->value))
+ die(_("unable to parse value '%s' for option %s"),
+ arg, opt->long_name);
+ return 0;
+}
+
+#define OPT_ULONG(s, l, v, h) \
+ { OPTION_CALLBACK, (s), (l), (v), "n", (h), \
+ PARSE_OPT_NONEG, option_parse_ulong }
+
int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{
int use_internal_rev_list = 0;
int thin = 0;
+ int shallow = 0;
int all_progress_implied = 0;
- uint32_t i;
- const char **rp_av;
- int rp_ac_alloc = 64;
- int rp_ac;
-
- read_replace_refs = 0;
-
- rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av));
-
- rp_av[0] = "pack-objects";
- rp_av[1] = "--objects"; /* --thin will make it --objects-edge */
- rp_ac = 2;
+ struct argv_array rp = ARGV_ARRAY_INIT;
+ int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0;
+ int rev_list_index = 0;
+ struct option pack_objects_options[] = {
+ OPT_SET_INT('q', "quiet", &progress,
+ N_("do not show progress meter"), 0),
+ OPT_SET_INT(0, "progress", &progress,
+ N_("show progress meter"), 1),
+ OPT_SET_INT(0, "all-progress", &progress,
+ N_("show progress meter during object writing phase"), 2),
+ OPT_BOOL(0, "all-progress-implied",
+ &all_progress_implied,
+ N_("similar to --all-progress when progress meter is shown")),
+ { OPTION_CALLBACK, 0, "index-version", NULL, N_("version[,offset]"),
+ N_("write the pack index file in the specified idx format version"),
+ 0, option_parse_index_version },
+ OPT_ULONG(0, "max-pack-size", &pack_size_limit,
+ N_("maximum size of each output pack file")),
+ OPT_BOOL(0, "local", &local,
+ N_("ignore borrowed objects from alternate object store")),
+ OPT_BOOL(0, "incremental", &incremental,
+ N_("ignore packed objects")),
+ OPT_INTEGER(0, "window", &window,
+ N_("limit pack window by objects")),
+ OPT_ULONG(0, "window-memory", &window_memory_limit,
+ N_("limit pack window by memory in addition to object limit")),
+ OPT_INTEGER(0, "depth", &depth,
+ N_("maximum length of delta chain allowed in the resulting pack")),
+ OPT_BOOL(0, "reuse-delta", &reuse_delta,
+ N_("reuse existing deltas")),
+ OPT_BOOL(0, "reuse-object", &reuse_object,
+ N_("reuse existing objects")),
+ OPT_BOOL(0, "delta-base-offset", &allow_ofs_delta,
+ N_("use OFS_DELTA objects")),
+ OPT_INTEGER(0, "threads", &delta_search_threads,
+ N_("use threads when searching for best delta matches")),
+ OPT_BOOL(0, "non-empty", &non_empty,
+ N_("do not create an empty pack output")),
+ OPT_BOOL(0, "revs", &use_internal_rev_list,
+ N_("read revision arguments from standard input")),
+ { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL,
+ N_("limit the objects to those that are not yet packed"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ { OPTION_SET_INT, 0, "all", &rev_list_all, NULL,
+ N_("include objects reachable from any reference"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL,
+ N_("include objects referred by reflog entries"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ { OPTION_SET_INT, 0, "indexed-objects", &rev_list_index, NULL,
+ N_("include objects referred to by the index"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 },
+ OPT_BOOL(0, "stdout", &pack_to_stdout,
+ N_("output pack to stdout")),
+ OPT_BOOL(0, "include-tag", &include_tag,
+ N_("include tag objects that refer to objects to be packed")),
+ OPT_BOOL(0, "keep-unreachable", &keep_unreachable,
+ N_("keep unreachable objects")),
+ { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"),
+ N_("unpack unreachable objects newer than <time>"),
+ PARSE_OPT_OPTARG, option_parse_unpack_unreachable },
+ OPT_BOOL(0, "thin", &thin,
+ N_("create thin packs")),
+ OPT_BOOL(0, "shallow", &shallow,
+ N_("create packs suitable for shallow fetches")),
+ OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep,
+ N_("ignore packs that have companion .keep file")),
+ OPT_INTEGER(0, "compression", &pack_compression_level,
+ 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(),
+ };
+
+ check_replace_refs = 0;
reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
@@ -2277,180 +2699,56 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
pack_compression_level = core_compression_level;
progress = isatty(2);
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
+ argc = parse_options(argc, argv, prefix, pack_objects_options,
+ pack_usage, 0);
- if (*arg != '-')
- break;
+ if (argc) {
+ base_name = argv[0];
+ argc--;
+ }
+ if (pack_to_stdout != !base_name || argc)
+ usage_with_options(pack_usage, pack_objects_options);
- if (!strcmp("--non-empty", arg)) {
- non_empty = 1;
- continue;
- }
- if (!strcmp("--local", arg)) {
- local = 1;
- continue;
- }
- if (!strcmp("--incremental", arg)) {
- incremental = 1;
- continue;
- }
- if (!strcmp("--honor-pack-keep", arg)) {
- ignore_packed_keep = 1;
- continue;
- }
- if (!prefixcmp(arg, "--compression=")) {
- char *end;
- int level = strtoul(arg+14, &end, 0);
- if (!arg[14] || *end)
- usage(pack_usage);
- if (level == -1)
- level = Z_DEFAULT_COMPRESSION;
- else if (level < 0 || level > Z_BEST_COMPRESSION)
- die("bad pack compression level %d", level);
- pack_compression_level = level;
- continue;
- }
- if (!prefixcmp(arg, "--max-pack-size=")) {
- pack_size_limit_cfg = 0;
- if (!git_parse_ulong(arg+16, &pack_size_limit))
- usage(pack_usage);
- continue;
- }
- if (!prefixcmp(arg, "--window=")) {
- char *end;
- window = strtoul(arg+9, &end, 0);
- if (!arg[9] || *end)
- usage(pack_usage);
- continue;
- }
- if (!prefixcmp(arg, "--window-memory=")) {
- if (!git_parse_ulong(arg+16, &window_memory_limit))
- usage(pack_usage);
- continue;
- }
- if (!prefixcmp(arg, "--threads=")) {
- char *end;
- delta_search_threads = strtoul(arg+10, &end, 0);
- if (!arg[10] || *end || delta_search_threads < 0)
- usage(pack_usage);
-#ifdef NO_PTHREADS
- if (delta_search_threads != 1)
- warning("no threads support, "
- "ignoring %s", arg);
-#endif
- continue;
- }
- if (!prefixcmp(arg, "--depth=")) {
- char *end;
- depth = strtoul(arg+8, &end, 0);
- if (!arg[8] || *end)
- usage(pack_usage);
- continue;
- }
- if (!strcmp("--progress", arg)) {
- progress = 1;
- continue;
- }
- if (!strcmp("--all-progress", arg)) {
- progress = 2;
- continue;
- }
- if (!strcmp("--all-progress-implied", arg)) {
- all_progress_implied = 1;
- continue;
- }
- if (!strcmp("-q", arg)) {
- progress = 0;
- continue;
- }
- if (!strcmp("--no-reuse-delta", arg)) {
- reuse_delta = 0;
- continue;
- }
- if (!strcmp("--no-reuse-object", arg)) {
- reuse_object = reuse_delta = 0;
- continue;
- }
- if (!strcmp("--delta-base-offset", arg)) {
- allow_ofs_delta = 1;
- continue;
- }
- if (!strcmp("--stdout", arg)) {
- pack_to_stdout = 1;
- continue;
- }
- if (!strcmp("--revs", arg)) {
- use_internal_rev_list = 1;
- continue;
- }
- if (!strcmp("--keep-unreachable", arg)) {
- keep_unreachable = 1;
- continue;
- }
- if (!strcmp("--unpack-unreachable", arg)) {
- unpack_unreachable = 1;
- continue;
- }
- if (!strcmp("--include-tag", arg)) {
- include_tag = 1;
- continue;
- }
- if (!strcmp("--unpacked", arg) ||
- !strcmp("--reflog", arg) ||
- !strcmp("--all", arg)) {
- use_internal_rev_list = 1;
- if (rp_ac >= rp_ac_alloc - 1) {
- rp_ac_alloc = alloc_nr(rp_ac_alloc);
- rp_av = xrealloc(rp_av,
- rp_ac_alloc * sizeof(*rp_av));
- }
- rp_av[rp_ac++] = arg;
- continue;
- }
- if (!strcmp("--thin", arg)) {
- use_internal_rev_list = 1;
- thin = 1;
- rp_av[1] = "--objects-edge";
- continue;
- }
- if (!prefixcmp(arg, "--index-version=")) {
- char *c;
- pack_idx_opts.version = strtoul(arg + 16, &c, 10);
- if (pack_idx_opts.version > 2)
- die("bad %s", arg);
- if (*c == ',')
- pack_idx_opts.off32_limit = strtoul(c+1, &c, 0);
- if (*c || pack_idx_opts.off32_limit & 0x80000000)
- die("bad %s", arg);
- continue;
- }
- if (!strcmp(arg, "--keep-true-parents")) {
- grafts_replace_parents = 0;
- continue;
- }
- usage(pack_usage);
- }
-
- /* Traditionally "pack-objects [options] base extra" failed;
- * we would however want to take refs parameter that would
- * have been given to upstream rev-list ourselves, which means
- * we somehow want to say what the base name is. So the
- * syntax would be:
- *
- * pack-objects [options] base <refs...>
- *
- * in other words, we would treat the first non-option as the
- * base_name and send everything else to the internal revision
- * walker.
- */
+ argv_array_push(&rp, "pack-objects");
+ if (thin) {
+ use_internal_rev_list = 1;
+ argv_array_push(&rp, shallow
+ ? "--objects-edge-aggressive"
+ : "--objects-edge");
+ } else
+ argv_array_push(&rp, "--objects");
- if (!pack_to_stdout)
- base_name = argv[i++];
+ if (rev_list_all) {
+ use_internal_rev_list = 1;
+ argv_array_push(&rp, "--all");
+ }
+ if (rev_list_reflog) {
+ use_internal_rev_list = 1;
+ argv_array_push(&rp, "--reflog");
+ }
+ if (rev_list_index) {
+ use_internal_rev_list = 1;
+ argv_array_push(&rp, "--indexed-objects");
+ }
+ if (rev_list_unpacked) {
+ use_internal_rev_list = 1;
+ argv_array_push(&rp, "--unpacked");
+ }
- if (pack_to_stdout != !base_name)
- usage(pack_usage);
+ if (!reuse_object)
+ reuse_delta = 0;
+ if (pack_compression_level == -1)
+ pack_compression_level = Z_DEFAULT_COMPRESSION;
+ else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION)
+ die("bad pack compression level %d", pack_compression_level);
+ if (!delta_search_threads) /* --threads=0 means autodetect */
+ delta_search_threads = online_cpus();
+
+#ifdef NO_PTHREADS
+ if (delta_search_threads != 1)
+ warning("no threads support, ignoring --threads");
+#endif
if (!pack_to_stdout && !pack_size_limit)
pack_size_limit = pack_size_limit_cfg;
if (pack_to_stdout && pack_size_limit)
@@ -2465,6 +2763,14 @@ 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 (!rev_list_all || !rev_list_reflog || !rev_list_index)
+ unpack_unreachable_expiration = 0;
+
+ 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;
@@ -2472,12 +2778,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
prepare_packed_git();
if (progress)
- progress_state = start_progress("Counting objects", 0);
+ progress_state = start_progress(_("Counting objects"), 0);
if (!use_internal_rev_list)
read_object_list_from_stdin();
else {
- rp_av[rp_ac] = NULL;
- get_object_list(rp_ac, rp_av);
+ get_object_list(rp.argc, rp.argv);
+ argv_array_clear(&rp);
}
cleanup_preferred_base();
if (include_tag && nr_result)
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c
index f5c6afc..649c3aa 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -301,14 +301,14 @@ static void pll_free(struct pll *l)
*/
static struct pll * get_permutations(struct pack_list *list, int n)
{
- struct pll *subset, *ret = NULL, *new_pll = NULL, *pll;
+ struct pll *subset, *ret = NULL, *new_pll = NULL;
if (list == NULL || pack_list_size(list) < n || n == 0)
return NULL;
if (n == 1) {
while (list) {
- new_pll = xmalloc(sizeof(pll));
+ new_pll = xmalloc(sizeof(*new_pll));
new_pll->pl = NULL;
pack_list_insert(&new_pll->pl, list);
new_pll->next = ret;
@@ -321,7 +321,7 @@ static struct pll * get_permutations(struct pack_list *list, int n)
while (list->next) {
subset = get_permutations(list->next, n - 1);
while (subset) {
- new_pll = xmalloc(sizeof(pll));
+ new_pll = xmalloc(sizeof(*new_pll));
new_pll->pl = subset->pl;
pack_list_insert(&new_pll->pl, list);
new_pll->next = ret;
diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c
index 39a9d89..b20b1ec 100644
--- a/builtin/pack-refs.c
+++ b/builtin/pack-refs.c
@@ -1,9 +1,9 @@
#include "builtin.h"
#include "parse-options.h"
-#include "pack-refs.h"
+#include "refs.h"
static char const * const pack_refs_usage[] = {
- "git pack-refs [options]",
+ N_("git pack-refs [options]"),
NULL
};
@@ -11,8 +11,8 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
{
unsigned int flags = PACK_REFS_PRUNE;
struct option opts[] = {
- OPT_BIT(0, "all", &flags, "pack everything", PACK_REFS_ALL),
- OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE),
+ OPT_BIT(0, "all", &flags, N_("pack everything"), PACK_REFS_ALL),
+ OPT_BIT(0, "prune", &flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE),
OPT_END(),
};
if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index f821eb3..77db873 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -1,17 +1,14 @@
#include "builtin.h"
-static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
+static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result)
{
- unsigned char result[20];
char name[50];
if (!patchlen)
return;
- git_SHA1_Final(result, c);
memcpy(name, sha1_to_hex(id), 41);
printf("%s %s\n", sha1_to_hex(result), name);
- git_SHA1_Init(c);
}
static int remove_space(char *line)
@@ -56,13 +53,34 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
return 1;
}
-static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
+static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx)
+{
+ unsigned char hash[20];
+ unsigned short carry = 0;
+ int i;
+
+ git_SHA1_Final(hash, ctx);
+ git_SHA1_Init(ctx);
+ /* 20-byte sum, with carry */
+ for (i = 0; i < 20; ++i) {
+ carry += result[i] + hash[i];
+ result[i] = carry;
+ carry >>= 8;
+ }
+}
+
+static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,
+ struct strbuf *line_buf, int stable)
{
- static char line[1000];
int patchlen = 0, found_next = 0;
int before = -1, after = -1;
+ git_SHA_CTX ctx;
- while (fgets(line, sizeof(line), stdin) != NULL) {
+ git_SHA1_Init(&ctx);
+ hashclr(result);
+
+ while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
+ char *line = line_buf->buf;
char *p = line;
int len;
@@ -107,6 +125,8 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
break;
/* Else we're parsing another header. */
+ if (stable)
+ flush_one_hunk(result, &ctx);
before = after = -1;
}
@@ -119,37 +139,63 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx)
/* Compute the sha without whitespace */
len = remove_space(line);
patchlen += len;
- git_SHA1_Update(ctx, line, len);
+ git_SHA1_Update(&ctx, line, len);
}
if (!found_next)
hashclr(next_sha1);
+ flush_one_hunk(result, &ctx);
+
return patchlen;
}
-static void generate_id_list(void)
+static void generate_id_list(int stable)
{
- unsigned char sha1[20], n[20];
- git_SHA_CTX ctx;
+ unsigned char sha1[20], n[20], result[20];
int patchlen;
+ struct strbuf line_buf = STRBUF_INIT;
- git_SHA1_Init(&ctx);
hashclr(sha1);
while (!feof(stdin)) {
- patchlen = get_one_patchid(n, &ctx);
- flush_current_id(patchlen, sha1, &ctx);
+ patchlen = get_one_patchid(n, result, &line_buf, stable);
+ flush_current_id(patchlen, sha1, result);
hashcpy(sha1, n);
}
+ strbuf_release(&line_buf);
}
-static const char patch_id_usage[] = "git patch-id < patch";
+static const char patch_id_usage[] = "git patch-id [--stable | --unstable] < patch";
+
+static int git_patch_id_config(const char *var, const char *value, void *cb)
+{
+ int *stable = cb;
+
+ if (!strcmp(var, "patchid.stable")) {
+ *stable = git_config_bool(var, value);
+ return 0;
+ }
+
+ return git_default_config(var, value, cb);
+}
int cmd_patch_id(int argc, const char **argv, const char *prefix)
{
- if (argc != 1)
+ int stable = -1;
+
+ git_config(git_patch_id_config, &stable);
+
+ /* If nothing is set, default to unstable. */
+ if (stable < 0)
+ stable = 0;
+
+ if (argc == 2 && !strcmp(argv[1], "--stable"))
+ stable = 1;
+ else if (argc == 2 && !strcmp(argv[1], "--unstable"))
+ stable = 0;
+ else if (argc != 1)
usage(patch_id_usage);
- generate_id_list();
+ generate_id_list(stable);
return 0;
}
diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c
index f9463de..f24a2c2 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -4,77 +4,58 @@
#include "parse-options.h"
static const char * const prune_packed_usage[] = {
- "git prune-packed [-n|--dry-run] [-q|--quiet]",
+ N_("git prune-packed [-n|--dry-run] [-q|--quiet]"),
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 int prune_subdir(int nr, const char *path, void *data)
+{
+ int *opts = data;
+ display_progress(progress, nr + 1);
+ if (!(*opts & PRUNE_PACKED_DRY_RUN))
+ rmdir(path);
+ return 0;
+}
+
+static int prune_object(const unsigned char *sha1, const char *path,
+ void *data)
{
- struct dirent *de;
- char hex[40];
+ int *opts = data;
+
+ if (!has_sha1_pack(sha1))
+ return 0;
- 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);
- 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);
- else
- unlink_or_warn(pathname);
- display_progress(progress, i + 1);
- }
- pathname[len] = 0;
- rmdir(pathname);
+ if (*opts & PRUNE_PACKED_DRY_RUN)
+ printf("rm -f %s\n", path);
+ else
+ unlink_or_warn(path);
+ return 0;
}
void prune_packed_objects(int opts)
{
- int i;
- static char pathname[PATH_MAX];
- const char *dir = get_object_directory();
- int len = strlen(dir);
-
- if (opts == VERBOSE)
- progress = start_progress_delay("Removing duplicate objects",
+ 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++] = '/';
- for (i = 0; i < 256; i++) {
- DIR *d;
+ for_each_loose_file_in_objdir(get_object_directory(),
+ prune_object, NULL, prune_subdir, &opts);
- display_progress(progress, i + 1);
- sprintf(pathname + len, "%02x/", i);
- d = opendir(pathname);
- if (!d)
- continue;
- prune_dir(i, d, pathname, len + 3, opts);
- closedir(d);
- }
+ /* Ensure we show 100% before finishing progress */
+ display_progress(progress, 256);
stop_progress(&progress);
}
int cmd_prune_packed(int argc, const char **argv, const char *prefix)
{
- int opts = isatty(2) ? VERBOSE : 0;
+ int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0;
const struct option prune_packed_options[] = {
- OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN),
- OPT_NEGBIT('q', "quiet", &opts, "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 e65690b..17094ad 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -5,36 +5,49 @@
#include "builtin.h"
#include "reachable.h"
#include "parse-options.h"
+#include "progress.h"
#include "dir.h"
static const char * const prune_usage[] = {
- "git prune [-n] [-v] [--expire <time>] [--] [<head>...]",
+ N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
NULL
};
static int show_only;
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);
if (st.st_mtime > expire)
return 0;
- printf("Removing stale temporary file %s\n", fullpath);
+ if (show_only || verbose)
+ printf("Removing stale temporary file %s\n", fullpath);
if (!show_only)
unlink_or_warn(fullpath);
return 0;
}
-static int prune_object(char *path, const char *filename, const unsigned char *sha1)
+static int prune_object(const unsigned char *sha1, const char *fullpath,
+ void *data)
{
- const char *fullpath = mkpath("%s/%s", path, filename);
struct stat st;
- if (lstat(fullpath, &st))
- return error("Could not stat '%s'", fullpath);
+
+ /*
+ * Do we know about this object?
+ * It must have been reachable
+ */
+ if (lookup_object(sha1))
+ return 0;
+
+ if (lstat(fullpath, &st)) {
+ /* report errors, but do not stop pruning */
+ error("Could not stat '%s'", fullpath);
+ return 0;
+ }
if (st.st_mtime > expire)
return 0;
if (show_only || verbose) {
@@ -47,56 +60,20 @@ 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_cruft(const char *basename, const char *path, void *data)
{
- DIR *dir = opendir(path);
- struct dirent *de;
-
- if (!dir)
- return 0;
-
- while ((de = readdir(dir)) != NULL) {
- char name[100];
- unsigned char sha1[20];
-
- if (is_dot_or_dotdot(de->d_name))
- continue;
- if (strlen(de->d_name) == 38) {
- sprintf(name, "%02x", i);
- memcpy(name+2, de->d_name, 39);
- if (get_sha1_hex(name, sha1) < 0)
- break;
-
- /*
- * Do we know about this object?
- * It must have been reachable
- */
- if (lookup_object(sha1))
- continue;
-
- prune_object(path, de->d_name, sha1);
- continue;
- }
- if (!prefixcmp(de->d_name, "tmp_obj_")) {
- prune_tmp_object(path, de->d_name);
- continue;
- }
- fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
- }
- if (!show_only)
- rmdir(path);
- closedir(dir);
+ if (starts_with(basename, "tmp_obj_"))
+ prune_tmp_file(path);
+ else
+ fprintf(stderr, "bad sha1 file: %s\n", path);
return 0;
}
-static void prune_object_dir(const char *path)
+static int prune_subdir(int nr, const char *path, void *data)
{
- int i;
- for (i = 0; i < 256; i++) {
- static char dir[4096];
- sprintf(dir, "%s/%02x", path, i);
- prune_dir(i, dir);
- }
+ if (!show_only)
+ rmdir(path);
+ return 0;
}
/*
@@ -116,26 +93,29 @@ 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);
}
int cmd_prune(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
+ struct progress *progress = NULL;
const struct option options[] = {
- OPT__DRY_RUN(&show_only, "do not remove, show only"),
- OPT__VERBOSE(&verbose, "report pruned objects"),
- OPT_DATE(0, "expire", &expire,
- "expire objects older than <time>"),
+ 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_EXPIRY_DATE(0, "expire", &expire,
+ N_("expire objects older than <time>")),
OPT_END()
};
char *s;
expire = ULONG_MAX;
save_commit_buffer = 0;
- read_replace_refs = 0;
+ check_replace_refs = 0;
+ ref_paranoia = 1;
init_revisions(&revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
@@ -144,21 +124,31 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
const char *name = *argv++;
if (!get_sha1(name, sha1)) {
- struct object *object = parse_object(sha1);
- if (!object)
- die("bad object: %s", name);
+ struct object *object = parse_object_or_die(sha1, name);
add_pending_object(&revs, object, "");
}
else
die("unrecognized argument: %s", name);
}
- mark_reachable_objects(&revs, 1);
- prune_object_dir(get_object_directory());
- prune_packed_objects(show_only);
+ if (show_progress == -1)
+ show_progress = isatty(2);
+ if (show_progress)
+ progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2);
+
+ mark_reachable_objects(&revs, 1, expire, progress);
+ stop_progress(&progress);
+ for_each_loose_file_in_objdir(get_object_directory(), prune_object,
+ prune_cruft, prune_subdir, NULL);
+
+ prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
remove_temporary_files(get_object_directory());
- s = xstrdup(mkpath("%s/pack", 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 35cce53..12f5e69 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -11,15 +11,17 @@
#include "submodule.h"
static const char * const push_usage[] = {
- "git push [<options>] [<repository> [<refspec>...]]",
+ N_("git push [<options>] [<repository> [<refspec>...]]"),
NULL,
};
-static int thin;
+static int thin = 1;
static int deleterefs;
static const char *receivepack;
static int verbosity;
-static int progress;
+static int progress = -1;
+
+static struct push_cas_option cas;
static const char **refspec;
static int refspec_nr;
@@ -32,51 +34,140 @@ 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 &&
+ starts_with(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);
}
}
-static void setup_push_upstream(struct remote *remote)
+static int push_url_of_remote(struct remote *remote, const char ***url_p)
+{
+ if (remote->pushurl_nr) {
+ *url_p = remote->pushurl;
+ return remote->pushurl_nr;
+ }
+ *url_p = remote->url;
+ return remote->url_nr;
+}
+
+static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) {
+ /*
+ * There's no point in using shorten_unambiguous_ref here,
+ * as the ambiguity would be on the remote side, not what
+ * we have locally. Plus, this is supposed to be the simple
+ * mode. If the user is doing something crazy like setting
+ * upstream to a non-branch, we should probably be showing
+ * them the big ugly fully qualified ref.
+ */
+ const char *advice_maybe = "";
+ const char *short_upstream = branch->merge[0]->src;
+
+ skip_prefix(short_upstream, "refs/heads/", &short_upstream);
+
+ /*
+ * Don't show advice for people who explicitly set
+ * push.default.
+ */
+ if (push_default == PUSH_DEFAULT_UNSPECIFIED)
+ advice_maybe = _("\n"
+ "To choose either option permanently, "
+ "see push.default in 'git help config'.");
+ die(_("The upstream branch of your current branch does not match\n"
+ "the name of your current branch. To push to the upstream branch\n"
+ "on the remote, use\n"
+ "\n"
+ " git push %s HEAD:%s\n"
+ "\n"
+ "To push to the branch of the same name on the remote, use\n"
+ "\n"
+ " git push %s %s\n"
+ "%s"),
+ remote->name, short_upstream,
+ remote->name, branch->name, advice_maybe);
+}
+
+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, int simple)
{
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);
- if (!branch->merge_nr || !branch->merge)
+ die(_(message_detached_head_die), remote->name);
+ if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n"
"\n"
@@ -87,24 +178,94 @@ static void setup_push_upstream(struct remote *remote)
if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name);
+ if (triangular)
+ die(_("You are pushing to remote '%s', which is not the upstream of\n"
+ "your current branch '%s', without telling me what to push\n"
+ "to update which remote branch."),
+ remote->name, branch->name);
+
+ if (simple) {
+ /* Additional safety */
+ if (strcmp(branch->refname, branch->merge[0]->src))
+ die_push_simple(branch, remote);
+ }
+
strbuf_addf(&refspec, "%s:%s", branch->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 has changed in\n"
+ "Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
+ "and maintain the traditional behavior, use:\n"
+ "\n"
+ " git config --global push.default matching\n"
+ "\n"
+ "To squelch this message and adopt the new behavior now, use:\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"
+ "Since Git 2.0, Git defaults 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)");
+
+static void warn_unspecified_push_default_configuration(void)
+{
+ static int warn_once;
+
+ if (warn_once++)
+ return;
+ 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_MATCHING:
add_refspec(":");
break;
+ case PUSH_DEFAULT_UNSPECIFIED:
+ warn_unspecified_push_default_configuration();
+ /* fallthru */
+
+ case PUSH_DEFAULT_SIMPLE:
+ if (triangular)
+ setup_push_current(remote, branch);
+ else
+ setup_push_upstream(remote, branch, triangular, 1);
+ break;
+
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote);
+ setup_push_upstream(remote, branch, triangular, 0);
break;
case PUSH_DEFAULT_CURRENT:
- add_refspec("HEAD");
+ setup_push_current(remote, branch);
break;
case PUSH_DEFAULT_NOTHING:
@@ -114,35 +275,108 @@ 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. 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_checkout_pull_push[] =
+ N_("Updates were rejected because a pushed branch tip is behind its remote\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 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[] =
+ N_("Updates were rejected because the tag already exists in the remote.");
+
+static const char message_advice_ref_needs_force[] =
+ N_("You cannot update a remote ref that points at a non-commit object,\n"
+ "or update a remote ref to make it point at a non-commit object,\n"
+ "without using the '--force' option.\n");
+
+static void advise_pull_before_push(void)
+{
+ if (!advice_push_non_ff_current || !advice_push_update_rejected)
+ return;
+ advise(_(message_advice_pull_before_push));
+}
+
+static void advise_checkout_pull_push(void)
+{
+ if (!advice_push_non_ff_matching || !advice_push_update_rejected)
+ return;
+ advise(_(message_advice_checkout_pull_push));
+}
+
+static void advise_ref_already_exists(void)
+{
+ if (!advice_push_already_exists || !advice_push_update_rejected)
+ return;
+ advise(_(message_advice_ref_already_exists));
+}
+
+static void advise_ref_fetch_first(void)
+{
+ if (!advice_push_fetch_first || !advice_push_update_rejected)
+ return;
+ advise(_(message_advice_ref_fetch_first));
+}
+
+static void advise_ref_needs_force(void)
+{
+ if (!advice_push_needs_force || !advice_push_update_rejected)
+ return;
+ advise(_(message_advice_ref_needs_force));
+}
+
static int push_with_options(struct transport *transport, int flags)
{
int err;
- int nonfastforward;
+ unsigned int reject_reasons;
transport_set_verbosity(transport, verbosity, progress);
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);
err = transport_push(transport, refspec_nr, refspec, flags,
- &nonfastforward);
+ &reject_reasons);
if (err != 0)
error(_("failed to push some refs to '%s'"), transport->url);
err |= transport_disconnect(transport);
-
if (!err)
return 0;
- if (nonfastforward && advice_push_nonfastforward) {
- fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n"
- "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"
- "'Note about fast-forwards' section of 'git push --help' for details.\n"));
+ if (reject_reasons & REJECT_NON_FF_HEAD) {
+ advise_pull_before_push();
+ } else if (reject_reasons & REJECT_NON_FF_OTHER) {
+ advise_checkout_pull_push();
+ } else if (reject_reasons & REJECT_ALREADY_EXISTS) {
+ advise_ref_already_exists();
+ } else if (reject_reasons & REJECT_FETCH_FIRST) {
+ advise_ref_fetch_first();
+ } else if (reject_reasons & REJECT_NEEDS_FORCE) {
+ advise_ref_needs_force();
}
return 1;
@@ -151,7 +385,7 @@ static int push_with_options(struct transport *transport, int flags)
static int do_push(const char *repo, int flags)
{
int i, errs;
- struct remote *remote = remote_get(repo);
+ struct remote *remote = pushremote_get(repo);
const char **url;
int url_nr;
@@ -196,13 +430,7 @@ static int do_push(const char *repo, int flags)
setup_default_push_refspecs(remote);
}
errs = 0;
- if (remote->pushurl_nr) {
- url = remote->pushurl;
- url_nr = remote->pushurl_nr;
- } else {
- url = remote->url;
- url_nr = remote->url_nr;
- }
+ url_nr = push_url_of_remote(remote, &url);
if (url_nr) {
for (i = 0; i < url_nr; i++) {
struct transport *transport =
@@ -224,17 +452,36 @@ static int option_parse_recurse_submodules(const struct option *opt,
const char *arg, int unset)
{
int *flags = opt->value;
+
+ if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |
+ TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))
+ die("%s can only be used once.", opt->long_name);
+
if (arg) {
if (!strcmp(arg, "check"))
*flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
+ else if (!strcmp(arg, "on-demand"))
+ *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
else
die("bad %s argument: %s", opt->long_name, arg);
} else
- die("option %s needs an argument (check)", opt->long_name);
+ die("option %s needs an argument (check|on-demand)",
+ opt->long_name);
return 0;
}
+static int git_push_config(const char *k, const char *v, void *cb)
+{
+ struct wt_status *s = cb;
+ int status;
+
+ status = git_gpg_config(k, v, NULL);
+ if (status)
+ return status;
+ return git_default_config(k, v, s);
+}
+
int cmd_push(int argc, const char **argv, const char *prefix)
{
int flags = 0;
@@ -243,29 +490,39 @@ int cmd_push(int argc, const char **argv, const char *prefix)
const char *repo = NULL; /* default repository */
struct option options[] = {
OPT__VERBOSITY(&verbosity),
- OPT_STRING( 0 , "repo", &repo, "repository", "repository"),
- OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL),
- OPT_BIT( 0 , "mirror", &flags, "mirror all refs",
+ OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")),
+ 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, "delete refs"),
- OPT_BOOLEAN( 0 , "tags", &tags, "push tags (can't be used with --all or --mirror)"),
- OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN),
- OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN),
- OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE),
- { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check",
- "controls recursive pushing of submodules",
+ 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, "check|on-demand",
+ N_("control recursive pushing of submodules"),
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
- OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"),
- OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"),
- OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
- OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
+ 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"),
TRANSPORT_PUSH_SET_UPSTREAM),
- OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+ OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
+ OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
+ TRANSPORT_PUSH_PRUNE),
+ OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
+ OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
+ TRANSPORT_PUSH_FOLLOW_TAGS),
+ OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
OPT_END()
};
packet_trace_identity("push");
- git_config(git_default_config, NULL);
+ git_config(git_push_config, NULL);
argc = parse_options(argc, argv, prefix, options, push_usage, 0);
if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
@@ -278,7 +535,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 df6c4c8..43b47f7 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -5,6 +5,7 @@
*/
#include "cache.h"
+#include "lockfile.h"
#include "object.h"
#include "tree.h"
#include "tree-walk.h"
@@ -33,7 +34,7 @@ static int list_tree(unsigned char *sha1)
}
static const char * const read_tree_usage[] = {
- "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])",
+ N_("git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])"),
NULL
};
@@ -66,7 +67,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 +81,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;
@@ -98,43 +100,43 @@ static struct lock_file lock_file;
int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
{
- int i, newfd, stage = 0;
+ int i, stage = 0;
unsigned char sha1[20];
struct tree_desc t[MAX_UNPACK_TREES];
struct unpack_trees_options opts;
int prefix_set = 0;
const struct option read_tree_options[] = {
- { OPTION_CALLBACK, 0, "index-output", NULL, "file",
- "write resulting index to <file>",
+ { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"),
+ N_("write resulting index to <file>"),
PARSE_OPT_NONEG, index_output_cb },
OPT_SET_INT(0, "empty", &read_empty,
- "only empty the index", 1),
- OPT__VERBOSE(&opts.verbose_update, "be verbose"),
- OPT_GROUP("Merging"),
+ N_("only empty the index"), 1),
+ OPT__VERBOSE(&opts.verbose_update, N_("be verbose")),
+ OPT_GROUP(N_("Merging")),
OPT_SET_INT('m', NULL, &opts.merge,
- "perform a merge in addition to a read", 1),
+ N_("perform a merge in addition to a read"), 1),
OPT_SET_INT(0, "trivial", &opts.trivial_merges_only,
- "3-way merge if no file level merging required", 1),
+ N_("3-way merge if no file level merging required"), 1),
OPT_SET_INT(0, "aggressive", &opts.aggressive,
- "3-way merge in presence of adds and removes", 1),
+ N_("3-way merge in presence of adds and removes"), 1),
OPT_SET_INT(0, "reset", &opts.reset,
- "same as -m, but discard unmerged entries", 1),
- { OPTION_STRING, 0, "prefix", &opts.prefix, "<subdirectory>/",
- "read the tree into the index under <subdirectory>/",
+ N_("same as -m, but discard unmerged entries"), 1),
+ { OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"),
+ N_("read the tree into the index under <subdirectory>/"),
PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP },
OPT_SET_INT('u', NULL, &opts.update,
- "update working tree with merge result", 1),
+ N_("update working tree with merge result"), 1),
{ OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
- "gitignore",
- "allow explicitly ignored files to be overwritten",
+ N_("gitignore"),
+ N_("allow explicitly ignored files to be overwritten"),
PARSE_OPT_NONEG, exclude_per_directory_cb },
OPT_SET_INT('i', NULL, &opts.index_only,
- "don't check the working tree after merging", 1),
- OPT__DRY_RUN(&opts.dry_run, "don't update the index or the work tree"),
+ N_("don't check the working tree after merging"), 1),
+ OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")),
OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
- "skip applying sparse checkout filter", 1),
+ N_("skip applying sparse checkout filter"), 1),
OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
- "debug unpack-trees", 1),
+ N_("debug unpack-trees"), 1),
OPT_END()
};
@@ -148,12 +150,21 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
argc = parse_options(argc, argv, unused_prefix, read_tree_options,
read_tree_usage, 0);
- newfd = hold_locked_index(&lock_file, 1);
+ hold_locked_index(&lock_file, 1);
prefix_set = opts.prefix ? 1 : 0;
if (1 < opts.merge + opts.reset + prefix_set)
die("Which one? -m, --reset, or --prefix?");
+ /*
+ * NEEDSWORK
+ *
+ * The old index should be read anyway even if we're going to
+ * destroy all index entries because we still need to preserve
+ * certain information such as index version or split-index
+ * mode.
+ */
+
if (opts.reset || opts.merge || opts.prefix) {
if (read_cache_unmerged() && (opts.prefix || opts.merge))
die("You need to resolve your current index first");
@@ -177,7 +188,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))
@@ -230,10 +241,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
* what came from the tree.
*/
if (nr_trees == 1 && !opts.prefix)
- prime_cache_tree(&active_cache_tree, trees[0]);
+ prime_cache_tree(&the_index, trees[0]);
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
+ if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die("unable to write new index file");
return 0;
}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index ae164da..8266c1f 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1,4 +1,5 @@
#include "builtin.h"
+#include "lockfile.h"
#include "pack.h"
#include "refs.h"
#include "pkt-line.h"
@@ -8,9 +9,16 @@
#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"
+#include "tag.h"
+#include "gpg-interface.h"
+#include "sigchain.h"
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
@@ -18,24 +26,47 @@ enum deny_action {
DENY_UNCONFIGURED,
DENY_IGNORE,
DENY_WARN,
- DENY_REFUSE
+ DENY_REFUSE,
+ DENY_UPDATE_INSTEAD
};
static int deny_deletes;
static int deny_non_fast_forwards;
static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
-static int receive_fsck_objects;
+static int receive_fsck_objects = -1;
+static int transfer_fsck_objects = -1;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int unpack_limit = 100;
static int report_status;
static int use_sideband;
+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 int stateless_rpc;
+static const char *service_dir;
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 struct strbuf push_cert = STRBUF_INIT;
+static unsigned char push_cert_sha1[20];
+static struct signature_check sigcheck;
+static const char *push_cert_nonce;
+static const char *cert_nonce_seed;
+
+static const char *NONCE_UNSOLICITED = "UNSOLICITED";
+static const char *NONCE_BAD = "BAD";
+static const char *NONCE_MISSING = "MISSING";
+static const char *NONCE_OK = "OK";
+static const char *NONCE_SLOP = "SLOP";
+static const char *nonce_status;
+static long nonce_stamp_slop;
+static unsigned long nonce_stamp_slop_limit;
static enum deny_action parse_deny_action(const char *var, const char *value)
{
@@ -46,6 +77,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)
return DENY_WARN;
if (!strcasecmp(value, "refuse"))
return DENY_REFUSE;
+ if (!strcasecmp(value, "updateinstead"))
+ return DENY_UPDATE_INSTEAD;
}
if (git_config_bool(var, value))
return DENY_REFUSE;
@@ -54,6 +87,11 @@ static enum deny_action parse_deny_action(const char *var, const char *value)
static int receive_pack_config(const char *var, const char *value, void *cb)
{
+ int status = parse_hide_refs_config(var, value, "receive");
+
+ if (status)
+ return status;
+
if (strcmp(var, "receive.denydeletes") == 0) {
deny_deletes = git_config_bool(var, value);
return 0;
@@ -79,6 +117,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "transfer.fsckobjects") == 0) {
+ transfer_fsck_objects = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "receive.denycurrentbranch")) {
deny_current_branch = parse_deny_action(var, value);
return 0;
@@ -104,23 +147,47 @@ 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;
+ }
+
+ if (strcmp(var, "receive.certnonceseed") == 0)
+ return git_config_string(&cert_nonce_seed, var, value);
+
+ if (strcmp(var, "receive.certnonceslop") == 0) {
+ nonce_stamp_slop_limit = git_config_ulong(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
-static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static void show_ref(const char *path, const unsigned char *sha1)
{
- if (sent_capabilities)
+ if (ref_is_hidden(path))
+ return;
+
+ if (sent_capabilities) {
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
- else
- packet_write(1, "%s %s%c%s%s\n",
- sha1_to_hex(sha1), path, 0,
- " report-status delete-refs side-band-64k",
- prefer_ofs_delta ? " ofs-delta" : "");
- sent_capabilities = 1;
- return 0;
+ } else {
+ struct strbuf cap = STRBUF_INIT;
+
+ strbuf_addstr(&cap,
+ "report-status delete-refs side-band-64k quiet");
+ if (prefer_ofs_delta)
+ strbuf_addstr(&cap, " ofs-delta");
+ if (push_cert_nonce)
+ strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
+ strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
+ packet_write(1, "%s %s%c%s\n",
+ sha1_to_hex(sha1), path, 0, cap.buf);
+ strbuf_release(&cap);
+ sent_capabilities = 1;
+ }
}
-static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
{
path = strip_namespace(path);
/*
@@ -133,29 +200,48 @@ static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, vo
*/
if (!path)
path = ".have";
- return show_ref(path, sha1, flag, cb_data);
+ show_ref(path, sha1);
+ return 0;
+}
+
+static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
+{
+ show_ref(".have", sha1);
+}
+
+static void collect_one_alternate_ref(const struct ref *ref, void *data)
+{
+ struct sha1_array *sa = data;
+ sha1_array_append(sa, ref->old_sha1);
}
static void write_head_info(void)
{
+ struct sha1_array sa = SHA1_ARRAY_INIT;
+ for_each_alternate_ref(collect_one_alternate_ref, &sa);
+ sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
+ sha1_array_clear(&sa);
for_each_ref(show_ref_cb, NULL);
if (!sent_capabilities)
- show_ref("capabilities^{}", null_sha1, 0, NULL);
+ show_ref("capabilities^{}", null_sha1);
+ advertise_shallow_grafts(1);
+
+ /* EOF */
+ packet_flush(1);
}
struct command {
struct command *next;
const char *error_string;
- unsigned int skip_update;
+ 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 */
};
-static const char pre_receive_hook[] = "hooks/pre-receive";
-static const char post_receive_hook[] = "hooks/post-receive";
-
static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
@@ -205,27 +291,241 @@ static int copy_to_sideband(int in, int out, void *arg)
return 0;
}
-static int run_receive_hook(struct command *commands, const char *hook_name)
+#define HMAC_BLOCK_SIZE 64
+
+static void hmac_sha1(unsigned char *out,
+ const char *key_in, size_t key_len,
+ const char *text, size_t text_len)
{
- static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
- struct command *cmd;
- struct child_process proc;
- struct async muxer;
- const char *argv[2];
- int have_input = 0, code;
+ unsigned char key[HMAC_BLOCK_SIZE];
+ unsigned char k_ipad[HMAC_BLOCK_SIZE];
+ unsigned char k_opad[HMAC_BLOCK_SIZE];
+ int i;
+ git_SHA_CTX ctx;
+
+ /* RFC 2104 2. (1) */
+ memset(key, '\0', HMAC_BLOCK_SIZE);
+ if (HMAC_BLOCK_SIZE < key_len) {
+ git_SHA1_Init(&ctx);
+ git_SHA1_Update(&ctx, key_in, key_len);
+ git_SHA1_Final(key, &ctx);
+ } else {
+ memcpy(key, key_in, key_len);
+ }
- for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
- if (!cmd->error_string)
- have_input = 1;
+ /* RFC 2104 2. (2) & (5) */
+ for (i = 0; i < sizeof(key); i++) {
+ k_ipad[i] = key[i] ^ 0x36;
+ k_opad[i] = key[i] ^ 0x5c;
+ }
+
+ /* RFC 2104 2. (3) & (4) */
+ git_SHA1_Init(&ctx);
+ git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad));
+ git_SHA1_Update(&ctx, text, text_len);
+ git_SHA1_Final(out, &ctx);
+
+ /* RFC 2104 2. (6) & (7) */
+ git_SHA1_Init(&ctx);
+ git_SHA1_Update(&ctx, k_opad, sizeof(k_opad));
+ git_SHA1_Update(&ctx, out, 20);
+ git_SHA1_Final(out, &ctx);
+}
+
+static char *prepare_push_cert_nonce(const char *path, unsigned long stamp)
+{
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char sha1[20];
+
+ strbuf_addf(&buf, "%s:%lu", path, stamp);
+ hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));;
+ strbuf_release(&buf);
+
+ /* RFC 2104 5. HMAC-SHA1-80 */
+ strbuf_addf(&buf, "%lu-%.*s", stamp, 20, sha1_to_hex(sha1));
+ return strbuf_detach(&buf, NULL);
+}
+
+/*
+ * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing
+ * after dropping "_commit" from its name and possibly moving it out
+ * of commit.c
+ */
+static char *find_header(const char *msg, size_t len, const char *key)
+{
+ int key_len = strlen(key);
+ const char *line = msg;
+
+ while (line && line < msg + len) {
+ const char *eol = strchrnul(line, '\n');
+
+ if ((msg + len <= eol) || line == eol)
+ return NULL;
+ if (line + key_len < eol &&
+ !memcmp(line, key, key_len) && line[key_len] == ' ') {
+ int offset = key_len + 1;
+ return xmemdupz(line + offset, (eol - line) - offset);
+ }
+ line = *eol ? eol + 1 : NULL;
+ }
+ return NULL;
+}
+
+static const char *check_nonce(const char *buf, size_t len)
+{
+ char *nonce = find_header(buf, len, "nonce");
+ unsigned long stamp, ostamp;
+ char *bohmac, *expect = NULL;
+ const char *retval = NONCE_BAD;
+
+ if (!nonce) {
+ retval = NONCE_MISSING;
+ goto leave;
+ } else if (!push_cert_nonce) {
+ retval = NONCE_UNSOLICITED;
+ goto leave;
+ } else if (!strcmp(push_cert_nonce, nonce)) {
+ retval = NONCE_OK;
+ goto leave;
+ }
+
+ if (!stateless_rpc) {
+ /* returned nonce MUST match what we gave out earlier */
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ /*
+ * In stateless mode, we may be receiving a nonce issued by
+ * another instance of the server that serving the same
+ * repository, and the timestamps may not match, but the
+ * nonce-seed and dir should match, so we can recompute and
+ * report the time slop.
+ *
+ * In addition, when a nonce issued by another instance has
+ * timestamp within receive.certnonceslop seconds, we pretend
+ * as if we issued that nonce when reporting to the hook.
+ */
+
+ /* nonce is concat(<seconds-since-epoch>, "-", <hmac>) */
+ if (*nonce <= '0' || '9' < *nonce) {
+ retval = NONCE_BAD;
+ goto leave;
+ }
+ stamp = strtoul(nonce, &bohmac, 10);
+ if (bohmac == nonce || bohmac[0] != '-') {
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ expect = prepare_push_cert_nonce(service_dir, stamp);
+ if (strcmp(expect, nonce)) {
+ /* Not what we would have signed earlier */
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ /*
+ * By how many seconds is this nonce stale? Negative value
+ * would mean it was issued by another server with its clock
+ * skewed in the future.
+ */
+ ostamp = strtoul(push_cert_nonce, NULL, 10);
+ nonce_stamp_slop = (long)ostamp - (long)stamp;
+
+ if (nonce_stamp_slop_limit &&
+ labs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
+ /*
+ * Pretend as if the received nonce (which passes the
+ * HMAC check, so it is not a forged by third-party)
+ * is what we issued.
+ */
+ free((void *)push_cert_nonce);
+ push_cert_nonce = xstrdup(nonce);
+ retval = NONCE_OK;
+ } else {
+ retval = NONCE_SLOP;
}
- if (!have_input || access(hook_name, X_OK) < 0)
+leave:
+ free(nonce);
+ free(expect);
+ return retval;
+}
+
+static void prepare_push_cert_sha1(struct child_process *proc)
+{
+ static int already_done;
+
+ if (!push_cert.len)
+ return;
+
+ if (!already_done) {
+ struct strbuf gpg_output = STRBUF_INIT;
+ struct strbuf gpg_status = STRBUF_INIT;
+ int bogs /* beginning_of_gpg_sig */;
+
+ already_done = 1;
+ if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1))
+ hashclr(push_cert_sha1);
+
+ memset(&sigcheck, '\0', sizeof(sigcheck));
+ sigcheck.result = 'N';
+
+ bogs = parse_signature(push_cert.buf, push_cert.len);
+ if (verify_signed_buffer(push_cert.buf, bogs,
+ push_cert.buf + bogs, push_cert.len - bogs,
+ &gpg_output, &gpg_status) < 0) {
+ ; /* error running gpg */
+ } else {
+ sigcheck.payload = push_cert.buf;
+ sigcheck.gpg_output = gpg_output.buf;
+ sigcheck.gpg_status = gpg_status.buf;
+ parse_gpg_output(&sigcheck);
+ }
+
+ strbuf_release(&gpg_output);
+ strbuf_release(&gpg_status);
+ nonce_status = check_nonce(push_cert.buf, bogs);
+ }
+ if (!is_null_sha1(push_cert_sha1)) {
+ argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s",
+ sha1_to_hex(push_cert_sha1));
+ argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s",
+ sigcheck.signer ? sigcheck.signer : "");
+ argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s",
+ sigcheck.key ? sigcheck.key : "");
+ argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c",
+ sigcheck.result);
+ if (push_cert_nonce) {
+ argv_array_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE=%s",
+ push_cert_nonce);
+ argv_array_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE_STATUS=%s",
+ nonce_status);
+ if (nonce_status == NONCE_SLOP)
+ argv_array_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE_SLOP=%ld",
+ nonce_stamp_slop);
+ }
+ }
+}
+
+typedef int (*feed_fn)(void *, const char **, size_t *);
+static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
+{
+ struct child_process proc = CHILD_PROCESS_INIT;
+ struct async muxer;
+ const char *argv[2];
+ int code;
+
+ argv[0] = find_hook(hook_name);
+ if (!argv[0])
return 0;
- argv[0] = hook_name;
argv[1] = NULL;
- memset(&proc, 0, sizeof(proc));
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
@@ -240,6 +540,8 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
proc.err = muxer.in;
}
+ prepare_push_cert_sha1(&proc);
+
code = start_command(&proc);
if (code) {
if (use_sideband)
@@ -247,39 +549,85 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
return code;
}
- for (cmd = commands; cmd; cmd = cmd->next) {
- if (!cmd->error_string) {
- size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
- sha1_to_hex(cmd->old_sha1),
- sha1_to_hex(cmd->new_sha1),
- cmd->ref_name);
- if (write_in_full(proc.in, buf, n) != n)
- break;
- }
+ sigchain_push(SIGPIPE, SIG_IGN);
+
+ while (1) {
+ const char *buf;
+ size_t n;
+ if (feed(feed_state, &buf, &n))
+ break;
+ if (write_in_full(proc.in, buf, n) != n)
+ break;
}
close(proc.in);
if (use_sideband)
finish_async(&muxer);
+
+ sigchain_pop(SIGPIPE);
+
return finish_command(&proc);
}
+struct receive_hook_feed_state {
+ struct command *cmd;
+ int skip_broken;
+ struct strbuf buf;
+};
+
+static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
+{
+ struct receive_hook_feed_state *state = state_;
+ struct command *cmd = state->cmd;
+
+ while (cmd &&
+ state->skip_broken && (cmd->error_string || cmd->did_not_exist))
+ cmd = cmd->next;
+ if (!cmd)
+ return -1; /* EOF */
+ strbuf_reset(&state->buf);
+ strbuf_addf(&state->buf, "%s %s %s\n",
+ sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),
+ cmd->ref_name);
+ state->cmd = cmd->next;
+ if (bufp) {
+ *bufp = state->buf.buf;
+ *sizep = state->buf.len;
+ }
+ return 0;
+}
+
+static int run_receive_hook(struct command *commands, const char *hook_name,
+ int skip_broken)
+{
+ struct receive_hook_feed_state state;
+ int status;
+
+ strbuf_init(&state.buf, 0);
+ state.cmd = commands;
+ state.skip_broken = skip_broken;
+ if (feed_receive_hook(&state, NULL, NULL))
+ return 0;
+ state.cmd = commands;
+ status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
+ strbuf_release(&state.buf);
+ return status;
+}
+
static int run_update_hook(struct command *cmd)
{
- static const char update_hook[] = "hooks/update";
const char *argv[5];
- struct child_process proc;
+ struct child_process proc = CHILD_PROCESS_INIT;
int code;
- if (access(update_hook, X_OK) < 0)
+ argv[0] = find_hook("update");
+ if (!argv[0])
return 0;
- argv[0] = update_hook;
argv[1] = cmd->ref_name;
argv[2] = sha1_to_hex(cmd->old_sha1);
argv[3] = sha1_to_hex(cmd->new_sha1);
argv[4] = NULL;
- memset(&proc, 0, sizeof(proc));
proc.no_stdin = 1;
proc.stdout_to_stderr = 1;
proc.err = use_sideband ? -1 : 0;
@@ -346,17 +694,133 @@ 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(&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_worktree(unsigned char *sha1)
+{
+ const char *update_refresh[] = {
+ "update-index", "-q", "--ignore-submodules", "--refresh", NULL
+ };
+ const char *diff_files[] = {
+ "diff-files", "--quiet", "--ignore-submodules", "--", NULL
+ };
+ const char *diff_index[] = {
+ "diff-index", "--quiet", "--cached", "--ignore-submodules",
+ "HEAD", "--", NULL
+ };
+ const char *read_tree[] = {
+ "read-tree", "-u", "-m", NULL, NULL
+ };
+ const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
+ struct argv_array env = ARGV_ARRAY_INIT;
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ if (is_bare_repository())
+ return "denyCurrentBranch = updateInstead needs a worktree";
+
+ argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+
+ child.argv = update_refresh;
+ child.env = env.argv;
+ child.dir = work_tree;
+ child.no_stdin = 1;
+ child.stdout_to_stderr = 1;
+ child.git_cmd = 1;
+ if (run_command(&child)) {
+ argv_array_clear(&env);
+ return "Up-to-date check failed";
+ }
+
+ /* run_command() does not clean up completely; reinitialize */
+ child_process_init(&child);
+ child.argv = diff_files;
+ child.env = env.argv;
+ child.dir = work_tree;
+ child.no_stdin = 1;
+ child.stdout_to_stderr = 1;
+ child.git_cmd = 1;
+ if (run_command(&child)) {
+ argv_array_clear(&env);
+ return "Working directory has unstaged changes";
+ }
+
+ child_process_init(&child);
+ child.argv = diff_index;
+ child.env = env.argv;
+ child.no_stdin = 1;
+ child.no_stdout = 1;
+ child.stdout_to_stderr = 0;
+ child.git_cmd = 1;
+ if (run_command(&child)) {
+ argv_array_clear(&env);
+ return "Working directory has staged changes";
+ }
+
+ read_tree[3] = sha1_to_hex(sha1);
+ child_process_init(&child);
+ child.argv = read_tree;
+ child.env = env.argv;
+ child.dir = work_tree;
+ child.no_stdin = 1;
+ child.no_stdout = 1;
+ child.stdout_to_stderr = 0;
+ child.git_cmd = 1;
+ if (run_command(&child)) {
+ argv_array_clear(&env);
+ return "Could not update working tree to new HEAD";
+ }
+
+ argv_array_clear(&env);
+ return NULL;
+}
+
+static const char *update(struct command *cmd, struct shallow_info *si)
{
const char *name = cmd->ref_name;
struct strbuf namespaced_name_buf = STRBUF_INIT;
- const char *namespaced_name;
+ const char *namespaced_name, *ret;
unsigned char *old_sha1 = cmd->old_sha1;
unsigned char *new_sha1 = cmd->new_sha1;
- struct ref_lock *lock;
/* only refs/... are allowed */
- if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
rp_error("refusing to create funny ref '%s' remotely", name);
return "funny refname";
}
@@ -377,6 +841,11 @@ static const char *update(struct command *cmd)
if (deny_current_branch == DENY_UNCONFIGURED)
refuse_unconfigured_deny();
return "branch is currently checked out";
+ case DENY_UPDATE_INSTEAD:
+ ret = update_worktree(new_sha1);
+ if (ret)
+ return ret;
+ break;
}
}
@@ -387,7 +856,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";
}
@@ -401,20 +870,22 @@ static const char *update(struct command *cmd)
break;
case DENY_REFUSE:
case DENY_UNCONFIGURED:
+ case DENY_UPDATE_INSTEAD:
if (deny_delete_current == DENY_UNCONFIGURED)
refuse_unconfigured_deny_delete_current();
rp_error("refusing to delete the current branch: %s", name);
return "deletion of the current branch prohibited";
+ default:
+ return "Invalid denyDeleteCurrent setting";
}
}
}
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;
- struct commit_list *bases, *ent;
old_object = parse_object(old_sha1);
new_object = parse_object(new_sha1);
@@ -427,12 +898,7 @@ static const char *update(struct command *cmd)
}
old_commit = (struct commit *)old_object;
new_commit = (struct commit *)new_object;
- bases = get_merge_bases(old_commit, new_commit, 1);
- for (ent = bases; ent; ent = ent->next)
- if (!hashcmp(old_sha1, ent->item->object.sha1))
- break;
- free_commit_list(bases);
- if (!ent) {
+ if (!in_merge_bases(old_commit, new_commit)) {
rp_error("denying non-fast-forward %s"
" (you should pull first)", name);
return "non-fast-forward";
@@ -445,8 +911,13 @@ static const char *update(struct command *cmd)
if (is_null_sha1(new_sha1)) {
if (!parse_object(old_sha1)) {
- rp_warning("Allowing deletion of corrupt ref.");
old_sha1 = NULL;
+ if (ref_exists(name)) {
+ rp_warning("Allowing deletion of corrupt ref.");
+ } else {
+ rp_warning("Deleting a non-existent ref.");
+ cmd->did_not_exist = 1;
+ }
}
if (delete_ref(namespaced_name, old_sha1, 0)) {
rp_error("failed to delete %s", name);
@@ -455,49 +926,60 @@ static const char *update(struct command *cmd)
return NULL; /* good */
}
else {
- lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
- if (!lock) {
- rp_error("failed to lock %s", name);
- return "failed to lock";
- }
- if (write_ref_sha1(lock, new_sha1, "push")) {
- return "failed to write"; /* error() already called */
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction;
+
+ if (shallow_update && si->shallow_ref[cmd->index] &&
+ update_shallow_ref(cmd, si))
+ return "shallow error";
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, namespaced_name,
+ new_sha1, old_sha1, 0, 1, "push",
+ &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ref_transaction_free(transaction);
+
+ rp_error("%s", err.buf);
+ strbuf_release(&err);
+ return "failed to update ref";
}
+
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
return NULL; /* good */
}
}
-static char update_post_hook[] = "hooks/post-update";
-
static void run_update_post_hook(struct command *commands)
{
struct command *cmd;
int argc;
const char **argv;
- struct child_process proc;
+ struct child_process proc = CHILD_PROCESS_INIT;
+ char *hook;
+ hook = find_hook("post-update");
for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
- if (cmd->error_string)
+ if (cmd->error_string || cmd->did_not_exist)
continue;
argc++;
}
- if (!argc || access(update_post_hook, X_OK) < 0)
+ if (!argc || !hook)
return;
+
argv = xmalloc(sizeof(*argv) * (2 + argc));
- argv[0] = update_post_hook;
+ argv[0] = hook;
for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
- char *p;
- if (cmd->error_string)
+ if (cmd->error_string || cmd->did_not_exist)
continue;
- p = xmalloc(strlen(cmd->ref_name) + 1);
- strcpy(p, cmd->ref_name);
- argv[argc] = p;
+ argv[argc] = xstrdup(cmd->ref_name);
argc++;
}
argv[argc] = NULL;
- memset(&proc, 0, sizeof(proc));
proc.no_stdin = 1;
proc.stdout_to_stderr = 1;
proc.err = use_sideband ? -1 : 0;
@@ -521,7 +1003,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
int flag;
strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
- dst_name = resolve_ref(buf.buf, sha1, 0, &flag);
+ dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag);
strbuf_release(&buf);
if (!(flag & REF_ISSYMREF))
@@ -571,79 +1053,250 @@ static void check_aliased_updates(struct command *commands)
string_list_append(&ref_list, cmd->ref_name);
item->util = (void *)cmd;
}
- sort_string_list(&ref_list);
+ string_list_sort(&ref_list);
- for (cmd = commands; cmd; cmd = cmd->next)
- check_aliased_update(cmd, &ref_list);
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->error_string)
+ check_aliased_update(cmd, &ref_list);
+ }
string_list_clear(&ref_list, 0);
}
-static void execute_commands(struct command *commands, const char *unpacker_error)
+static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
+{
+ struct command **cmd_list = cb_data;
+ struct command *cmd = *cmd_list;
+
+ if (!cmd || is_null_sha1(cmd->new_sha1))
+ return -1; /* end of list */
+ *cmd_list = NULL; /* this returns only one */
+ hashcpy(sha1, cmd->new_sha1);
+ return 0;
+}
+
+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;
+ cmd->error_string = "missing necessary objects";
+ }
+}
+
+struct iterate_data {
+ struct command *cmds;
+ struct shallow_info *si;
+};
+
+static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
+{
+ struct iterate_data *data = cb_data;
+ struct command **cmd_list = &data->cmds;
+ struct command *cmd = *cmd_list;
+
+ 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_list = NULL;
+ return -1; /* end of list */
+}
+
+static void reject_updates_to_hidden(struct command *commands)
+{
+ struct command *cmd;
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (cmd->error_string || !ref_is_hidden(cmd->ref_name))
+ continue;
+ if (is_null_sha1(cmd->new_sha1))
+ cmd->error_string = "deny deleting a hidden ref";
+ else
+ cmd->error_string = "deny updating a hidden ref";
+ }
+}
+
+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)
- cmd->error_string = "n/a (unpacker error)";
+ cmd->error_string = "unpacker error";
return;
}
- if (run_receive_hook(commands, pre_receive_hook)) {
- for (cmd = commands; cmd; cmd = cmd->next)
- cmd->error_string = "pre-receive hook declined";
+ 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);
+
+ if (run_receive_hook(commands, "pre-receive", 0)) {
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->error_string)
+ cmd->error_string = "pre-receive hook declined";
+ }
return;
}
check_aliased_updates(commands);
- head_name = resolve_ref("HEAD", sha1, 0, NULL);
+ free(head_name_to_free);
+ head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
- for (cmd = commands; cmd; cmd = cmd->next)
- if (!cmd->skip_update)
- cmd->error_string = update(cmd);
+ checked_connectivity = 1;
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (cmd->error_string)
+ continue;
+
+ if (cmd->skip_update)
+ continue;
+
+ 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 && !checked_connectivity)
+ error("BUG: run 'git fsck' for safety.\n"
+ "If there are errors, try to remove "
+ "the reported refs above");
}
-static struct command *read_head_info(void)
+static struct command **queue_command(struct command **tail,
+ const char *line,
+ int linelen)
+{
+ unsigned char old_sha1[20], new_sha1[20];
+ struct command *cmd;
+ const char *refname;
+ int reflen;
+
+ if (linelen < 83 ||
+ line[40] != ' ' ||
+ line[81] != ' ' ||
+ get_sha1_hex(line, old_sha1) ||
+ get_sha1_hex(line + 41, new_sha1))
+ die("protocol error: expected old/new/ref, got '%s'", line);
+
+ refname = line + 82;
+ reflen = linelen - 82;
+ cmd = xcalloc(1, sizeof(struct command) + reflen + 1);
+ hashcpy(cmd->old_sha1, old_sha1);
+ hashcpy(cmd->new_sha1, new_sha1);
+ memcpy(cmd->ref_name, refname, reflen);
+ cmd->ref_name[reflen] = '\0';
+ *tail = cmd;
+ return &cmd->next;
+}
+
+static void queue_commands_from_cert(struct command **tail,
+ struct strbuf *push_cert)
+{
+ const char *boc, *eoc;
+
+ if (*tail)
+ die("protocol error: got both push certificate and unsigned commands");
+
+ boc = strstr(push_cert->buf, "\n\n");
+ if (!boc)
+ die("malformed push certificate %.*s", 100, push_cert->buf);
+ else
+ boc += 2;
+ eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len);
+
+ while (boc < eoc) {
+ const char *eol = memchr(boc, '\n', eoc - boc);
+ tail = queue_command(tail, boc, eol ? eol - boc : eoc - eol);
+ boc = eol ? eol + 1 : eoc;
+ }
+}
+
+static struct command *read_head_info(struct sha1_array *shallow)
{
struct command *commands = NULL;
struct command **p = &commands;
for (;;) {
- static char line[1000];
- unsigned char old_sha1[20], new_sha1[20];
- struct command *cmd;
- char *refname;
- int len, reflen;
-
- len = packet_read_line(0, line, sizeof(line));
- if (!len)
+ char *line;
+ int len, linelen;
+
+ line = packet_read_line(0, &len);
+ if (!line)
break;
- if (line[len-1] == '\n')
- line[--len] = 0;
- if (len < 83 ||
- line[40] != ' ' ||
- line[81] != ' ' ||
- get_sha1_hex(line, old_sha1) ||
- get_sha1_hex(line + 41, new_sha1))
- die("protocol error: expected old/new/ref, got '%s'",
- line);
-
- refname = line + 82;
- reflen = strlen(refname);
- if (reflen + 82 < len) {
- if (strstr(refname + reflen + 1, "report-status"))
+
+ if (len == 48 && starts_with(line, "shallow ")) {
+ unsigned char sha1[20];
+ if (get_sha1_hex(line + 8, sha1))
+ die("protocol error: expected shallow sha, got '%s'",
+ line + 8);
+ sha1_array_append(shallow, sha1);
+ continue;
+ }
+
+ linelen = strlen(line);
+ if (linelen < len) {
+ const char *feature_list = line + linelen + 1;
+ if (parse_feature_request(feature_list, "report-status"))
report_status = 1;
- if (strstr(refname + reflen + 1, "side-band-64k"))
+ if (parse_feature_request(feature_list, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
+ if (parse_feature_request(feature_list, "quiet"))
+ quiet = 1;
+ }
+
+ if (!strcmp(line, "push-cert")) {
+ int true_flush = 0;
+ char certbuf[1024];
+
+ for (;;) {
+ len = packet_read(0, NULL, NULL,
+ certbuf, sizeof(certbuf), 0);
+ if (!len) {
+ true_flush = 1;
+ break;
+ }
+ if (!strcmp(certbuf, "push-cert-end\n"))
+ break; /* end of cert */
+ strbuf_addstr(&push_cert, certbuf);
+ }
+
+ if (true_flush)
+ break;
+ continue;
}
- cmd = xcalloc(1, sizeof(struct command) + len - 80);
- hashcpy(cmd->old_sha1, old_sha1);
- hashcpy(cmd->new_sha1, new_sha1);
- memcpy(cmd->ref_name, line + 82, len - 81);
- *p = cmd;
- p = &cmd->next;
+
+ p = queue_command(p, line, linelen);
}
+
+ if (push_cert.len)
+ queue_commands_from_cert(p, &push_cert);
+
return commands;
}
@@ -669,66 +1322,180 @@ static const char *parse_pack_header(struct pack_header *hdr)
static const char *pack_lockfile;
-static const char *unpack(void)
+static const char *unpack(int err_fd, struct shallow_info *si)
{
struct pack_header hdr;
const char *hdr_err;
+ int status;
char hdr_arg[38];
+ struct child_process child = CHILD_PROCESS_INIT;
+ int fsck_objects = (receive_fsck_objects >= 0
+ ? receive_fsck_objects
+ : transfer_fsck_objects >= 0
+ ? transfer_fsck_objects
+ : 0);
hdr_err = parse_pack_header(&hdr);
- if (hdr_err)
+ if (hdr_err) {
+ if (err_fd > 0)
+ close(err_fd);
return hdr_err;
+ }
snprintf(hdr_arg, sizeof(hdr_arg),
"--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_push(&child.args, "--shallow-file");
+ argv_array_push(&child.args, alt_shallow_file);
+ }
+
if (ntohl(hdr.hdr_entries) < unpack_limit) {
- int code, i = 0;
- const char *unpacker[4];
- unpacker[i++] = "unpack-objects";
- if (receive_fsck_objects)
- unpacker[i++] = "--strict";
- unpacker[i++] = hdr_arg;
- unpacker[i++] = NULL;
- code = run_command_v_opt(unpacker, RUN_GIT_CMD);
- if (!code)
- return NULL;
- return "unpack-objects abnormal exit";
+ argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
+ if (quiet)
+ argv_array_push(&child.args, "-q");
+ if (fsck_objects)
+ argv_array_push(&child.args, "--strict");
+ child.no_stdout = 1;
+ child.err = err_fd;
+ child.git_cmd = 1;
+ 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";
- if (receive_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.git_cmd = 1;
- status = start_command(&ip);
- if (status) {
+ argv_array_pushl(&child.args, "index-pack",
+ "--stdin", hdr_arg, keep_arg, NULL);
+ if (fsck_objects)
+ argv_array_push(&child.args, "--strict");
+ if (fix_thin)
+ argv_array_push(&child.args, "--fix-thin");
+ child.out = -1;
+ child.err = err_fd;
+ child.git_cmd = 1;
+ status = start_command(&child);
+ if (status)
return "index-pack fork failed";
+ pack_lockfile = index_pack_lockfile(child.out);
+ 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(struct shallow_info *si)
+{
+ struct async muxer;
+ const char *ret;
+
+ if (!use_sideband)
+ return unpack(0, si);
+
+ memset(&muxer, 0, sizeof(muxer));
+ muxer.proc = copy_to_sideband;
+ muxer.in = -1;
+ if (start_async(&muxer))
+ return NULL;
+
+ 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]++;
}
- pack_lockfile = index_pack_lockfile(ip.out);
- close(ip.out);
- status = finish_command(&ip);
- if (!status) {
- reprepare_packed_git();
- return NULL;
+
+ /*
+ * 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;
}
- return "index-pack abnormal exit";
}
+ free(ref_status);
}
static void report(struct command *commands, const char *unpack_status)
@@ -751,7 +1518,7 @@ static void report(struct command *commands, const char *unpack_status)
if (use_sideband)
send_sideband(1, 1, buf.buf, buf.len, use_sideband);
else
- safe_write(1, buf.buf, buf.len);
+ write_or_die(1, buf.buf, buf.len);
strbuf_release(&buf);
}
@@ -765,32 +1532,14 @@ static int delete_only(struct command *commands)
return 1;
}
-static void add_one_alternate_sha1(const unsigned char sha1[20], void *unused)
-{
- add_extra_ref(".have", sha1, 0);
-}
-
-static void collect_one_alternate_ref(const struct ref *ref, void *data)
-{
- struct sha1_array *sa = data;
- sha1_array_append(sa, ref->old_sha1);
-}
-
-static void add_alternate_refs(void)
-{
- struct sha1_array sa = SHA1_ARRAY_INIT;
- for_each_alternate_ref(collect_one_alternate_ref, &sa);
- sha1_array_for_each_unique(&sa, add_one_alternate_sha1, NULL);
- sha1_array_clear(&sa);
-}
-
int cmd_receive_pack(int argc, const char **argv, const char *prefix)
{
int advertise_refs = 0;
- int stateless_rpc = 0;
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");
@@ -799,6 +1548,11 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
const char *arg = *argv++;
if (*arg == '-') {
+ if (!strcmp(arg, "--quiet")) {
+ quiet = 1;
+ continue;
+ }
+
if (!strcmp(arg, "--advertise-refs")) {
advertise_refs = 1;
continue;
@@ -807,25 +1561,28 @@ 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);
}
- if (dir)
+ if (service_dir)
usage(receive_pack_usage);
- dir = xstrdup(arg);
+ service_dir = arg;
}
- if (!dir)
+ if (!service_dir)
usage(receive_pack_usage);
setup_path();
- 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");
+ if (!enter_repo(service_dir, 0))
+ die("'%s' does not appear to be a git repository", service_dir);
git_config(receive_pack_config, NULL);
+ if (cert_nonce_seed)
+ push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL));
if (0 <= transfer_unpack_limit)
unpack_limit = transfer_unpack_limit;
@@ -833,38 +1590,43 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
unpack_limit = receive_unpack_limit;
if (advertise_refs || !stateless_rpc) {
- add_alternate_refs();
write_head_info();
- clear_extra_refs();
-
- /* EOF */
- packet_flush(1);
}
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();
- 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)
report(commands, unpack_status);
- run_receive_hook(commands, post_receive_hook);
+ run_receive_hook(commands, "post-receive", 1);
run_update_post_hook(commands);
if (auto_gc) {
const char *argv_gc_auto[] = {
"gc", "--auto", "--quiet", NULL,
};
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ int opt = RUN_GIT_CMD | RUN_COMMAND_STDOUT_TO_STDERR;
+ run_command_v_opt(argv_gc_auto, opt);
}
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);
+ free((void *)push_cert_nonce);
return 0;
}
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 3a9c80f..2d85d26 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -1,5 +1,5 @@
-#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "commit.h"
#include "refs.h"
#include "dir.h"
@@ -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;
@@ -330,8 +329,10 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
printf("keep %s", message);
return 0;
prune:
- if (!cb->newlog || cb->cmd->verbose)
- printf("%sprune %s", cb->newlog ? "" : "would ", message);
+ if (!cb->newlog)
+ printf("would prune %s", message);
+ else if (cb->cmd->verbose)
+ printf("prune %s", message);
return 0;
}
@@ -364,11 +365,11 @@ 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);
- if (!file_exists(log_file))
+ if (!reflog_exists(ref))
goto finish;
if (!cmd->dry_run) {
newlog_path = git_pathdup("logs/%s.lock", ref);
@@ -412,7 +413,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
if (cb.unreachable_expire_kind == UE_HEAD) {
struct commit_list *elem;
for (elem = tips; elem; elem = elem->next)
- clear_commit_marks(tip_commit, REACHABLE);
+ clear_commit_marks(elem->item, REACHABLE);
free_commit_list(tips);
} else {
clear_commit_marks(tip_commit, REACHABLE);
@@ -430,7 +431,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
write_str_in_full(lock->lock_fd, "\n") != 1 ||
close_ref(lock) < 0)) {
status |= error("Couldn't write %s",
- lock->lk->filename);
+ lock->lk->filename.buf);
unlink(newlog_path);
} else if (rename(newlog_path, log_file)) {
status |= error("cannot rename %s to %s",
@@ -494,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;
}
@@ -508,26 +507,27 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l
static int reflog_expire_config(const char *var, const char *value, void *cb)
{
- const char *lastdot = strrchr(var, '.');
+ const char *pattern, *key;
+ int pattern_len;
unsigned long expire;
int slot;
struct reflog_expire_cfg *ent;
- if (!lastdot || prefixcmp(var, "gc."))
+ if (parse_config_key(var, "gc", &pattern, &pattern_len, &key) < 0)
return git_default_config(var, value, cb);
- if (!strcmp(lastdot, ".reflogexpire")) {
+ if (!strcmp(key, "reflogexpire")) {
slot = EXPIRE_TOTAL;
if (parse_expire_cfg_value(var, value, &expire))
return -1;
- } else if (!strcmp(lastdot, ".reflogexpireunreachable")) {
+ } else if (!strcmp(key, "reflogexpireunreachable")) {
slot = EXPIRE_UNREACH;
if (parse_expire_cfg_value(var, value, &expire))
return -1;
} else
return git_default_config(var, value, cb);
- if (lastdot == var + 2) {
+ if (!pattern) {
switch (slot) {
case EXPIRE_TOTAL:
default_reflog_expire = expire;
@@ -539,7 +539,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)
return 0;
}
- ent = find_cfg_ent(var + 3, lastdot - (var+3));
+ ent = find_cfg_ent(pattern, pattern_len);
if (!ent)
return -1;
switch (slot) {
@@ -561,7 +561,7 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c
return; /* both given explicitly -- nothing to tweak */
for (ent = reflog_expire_cfg; ent; ent = ent->next) {
- if (!fnmatch(ent->pattern, ref, 0)) {
+ if (!wildmatch(ent->pattern, ref, 0, NULL)) {
if (!(slot & EXPIRE_TOTAL))
cb->expire_total = ent->expire_total;
if (!(slot & EXPIRE_UNREACH))
@@ -610,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"))
@@ -647,7 +649,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
init_revisions(&cb.revs, prefix);
if (cb.verbose)
printf("Marking reachable objects...");
- mark_reachable_objects(&cb.revs, 0);
+ mark_reachable_objects(&cb.revs, 0, 0, NULL);
if (cb.verbose)
putchar('\n');
}
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
index 692c834..3b8c22c 100644
--- a/builtin/remote-ext.c
+++ b/builtin/remote-ext.c
@@ -30,16 +30,14 @@ static char *strip_escapes(const char *str, const char *service,
size_t rpos = 0;
int escape = 0;
char special = 0;
- size_t psoff = 0;
+ const char *service_noprefix = service;
struct strbuf ret = STRBUF_INIT;
- /* Calculate prefix length for \s and lengths for \s and \S */
- if (!strncmp(service, "git-", 4))
- psoff = 4;
+ skip_prefix(service_noprefix, "git-", &service_noprefix);
/* Pass the service to command. */
setenv("GIT_EXT_SERVICE", service, 1);
- setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1);
+ setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1);
/* Scan the length of argument. */
while (str[rpos] && (escape || str[rpos] != ' ')) {
@@ -85,7 +83,7 @@ static char *strip_escapes(const char *str, const char *service,
strbuf_addch(&ret, str[rpos]);
break;
case 's':
- strbuf_addstr(&ret, service + psoff);
+ strbuf_addstr(&ret, service_noprefix);
break;
case 'S':
strbuf_addstr(&ret, service);
@@ -179,9 +177,8 @@ static void send_git_request(int stdin_fd, const char *serv, const char *repo,
static int run_child(const char *arg, const char *service)
{
int r;
- struct child_process child;
+ struct child_process child = CHILD_PROCESS_INIT;
- memset(&child, 0, sizeof(child));
child.in = -1;
child.out = -1;
child.err = 0;
diff --git a/builtin/remote.c b/builtin/remote.c
index f2a9c26..b4ff468 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -6,68 +6,69 @@
#include "strbuf.h"
#include "run-command.h"
#include "refs.h"
+#include "argv-array.h"
static const char * const builtin_remote_usage[] = {
- "git remote [-v | --verbose]",
- "git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>",
- "git remote rename <old> <new>",
- "git remote rm <name>",
- "git remote set-head <name> (-a | -d | <branch>)",
- "git remote [-v | --verbose] show [-n] <name>",
- "git remote prune [-n | --dry-run] <name>",
- "git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]",
- "git remote set-branches <name> [--add] <branch>...",
- "git remote set-url <name> <newurl> [<oldurl>]",
- "git remote set-url --add <name> <newurl>",
- "git remote set-url --delete <name> <url>",
+ 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 | --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>)...]"),
+ N_("git remote set-branches [--add] <name> <branch>..."),
+ N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
+ N_("git remote set-url --add <name> <newurl>"),
+ N_("git remote set-url --delete <name> <url>"),
NULL
};
static const char * const builtin_remote_add_usage[] = {
- "git remote add [<options>] <name> <url>",
+ N_("git remote add [<options>] <name> <url>"),
NULL
};
static const char * const builtin_remote_rename_usage[] = {
- "git remote rename <old> <new>",
+ N_("git remote rename <old> <new>"),
NULL
};
static const char * const builtin_remote_rm_usage[] = {
- "git remote rm <name>",
+ N_("git remote remove <name>"),
NULL
};
static const char * const builtin_remote_sethead_usage[] = {
- "git remote set-head <name> (-a | -d | <branch>])",
+ N_("git remote set-head <name> (-a | --auto | -d | --delete | <branch>)"),
NULL
};
static const char * const builtin_remote_setbranches_usage[] = {
- "git remote set-branches <name> <branch>...",
- "git remote set-branches --add <name> <branch>...",
+ N_("git remote set-branches <name> <branch>..."),
+ N_("git remote set-branches --add <name> <branch>..."),
NULL
};
static const char * const builtin_remote_show_usage[] = {
- "git remote show [<options>] <name>",
+ N_("git remote show [<options>] <name>"),
NULL
};
static const char * const builtin_remote_prune_usage[] = {
- "git remote prune [<options>] <name>",
+ N_("git remote prune [<options>] <name>"),
NULL
};
static const char * const builtin_remote_update_usage[] = {
- "git remote update [<options>] [<group> | <remote>]...",
+ N_("git remote update [<options>] [<group> | <remote>]..."),
NULL
};
static const char * const builtin_remote_seturl_usage[] = {
- "git remote set-url [--push] <name> <newurl> [<oldurl>]",
- "git remote set-url --add <name> <newurl>",
- "git remote set-url --delete <name> <url>",
+ N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
+ N_("git remote set-url --add <name> <newurl>"),
+ N_("git remote set-url --delete <name> <url>"),
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 };
@@ -95,9 +85,9 @@ static int fetch_remote(const char *name)
argv[1] = "-v";
argv[2] = name;
}
- printf("Updating %s\n", name);
+ printf_ln(_("Updating %s"), name);
if (run_command_v_opt(argv, RUN_GIT_CMD))
- return error("Could not fetch %s", name);
+ return error(_("Could not fetch %s"), name);
return 0;
}
@@ -127,8 +117,8 @@ static int add_branch(const char *key, const char *branchname,
}
static const char mirror_advice[] =
-"--mirror is dangerous and deprecated; please\n"
-"\t use --mirror=fetch or --mirror=push instead";
+N_("--mirror is dangerous and deprecated; please\n"
+ "\t use --mirror=fetch or --mirror=push instead");
static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
{
@@ -136,7 +126,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
if (not)
*mirror = MIRROR_NONE;
else if (!arg) {
- warning("%s", mirror_advice);
+ warning("%s", _(mirror_advice));
*mirror = MIRROR_BOTH;
}
else if (!strcmp(arg, "fetch"))
@@ -144,7 +134,7 @@ static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
else if (!strcmp(arg, "push"))
*mirror = MIRROR_PUSH;
else
- return error("unknown mirror argument: %s", arg);
+ return error(_("unknown mirror argument: %s"), arg);
return 0;
}
@@ -160,17 +150,17 @@ static int add(int argc, const char **argv)
int i;
struct option options[] = {
- OPT_BOOLEAN('f', "fetch", &fetch, "fetch the remote branches"),
+ OPT_BOOL('f', "fetch", &fetch, N_("fetch the remote branches")),
OPT_SET_INT(0, "tags", &fetch_tags,
- "import all tags and associated objects when fetching",
+ N_("import all tags and associated objects when fetching"),
TAGS_SET),
OPT_SET_INT(0, NULL, &fetch_tags,
- "or do not fetch any tag at all (--no-tags)", TAGS_UNSET),
- OPT_STRING_LIST('t', "track", &track, "branch",
- "branch(es) to track"),
- OPT_STRING('m', "master", &master, "branch", "master branch"),
- { OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch",
- "set up remote as a mirror to push to or fetch from",
+ N_("or do not fetch any tag at all (--no-tags)"), TAGS_UNSET),
+ OPT_STRING_LIST('t', "track", &track, N_("branch"),
+ N_("branch(es) to track")),
+ OPT_STRING('m', "master", &master, N_("branch"), N_("master branch")),
+ { OPTION_CALLBACK, 0, "mirror", &mirror, N_("push|fetch"),
+ N_("set up remote as a mirror to push to or fetch from"),
PARSE_OPT_OPTARG, parse_mirror_opt },
OPT_END()
};
@@ -178,25 +168,27 @@ 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)
- die("specifying a master branch makes no sense with --mirror");
+ die(_("specifying a master branch makes no sense with --mirror"));
if (mirror && !(mirror & MIRROR_FETCH) && track.nr)
- die("specifying branches to track makes sense only with fetch mirrors");
+ die(_("specifying branches to track makes sense only with fetch mirrors"));
name = argv[0];
url = argv[1];
remote = remote_get(name);
- if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
+ if (remote && (remote->url_nr > 1 ||
+ (strcmp(name, remote->url[0]) &&
+ strcmp(url, remote->url[0])) ||
remote->fetch_refspec_nr))
- die("remote %s already exists.", name);
+ die(_("remote %s already exists."), name);
strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
if (!valid_fetch_refspec(buf2.buf))
- die("'%s' is not a valid remote name", name);
+ die(_("'%s' is not a valid remote name"), name);
strbuf_addf(&buf, "remote.%s.url", name);
if (git_config_set(buf.buf, url))
@@ -240,7 +232,7 @@ static int add(int argc, const char **argv)
strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
if (create_symref(buf.buf, buf2.buf, "remote add"))
- return error("Could not setup master '%s'", master);
+ return error(_("Could not setup master '%s'"), master);
}
strbuf_release(&buf);
@@ -260,31 +252,30 @@ static struct string_list branch_list;
static const char *abbrev_ref(const char *name, const char *prefix)
{
- const char *abbrev = skip_prefix(name, prefix);
- if (abbrev)
- return abbrev;
+ skip_prefix(name, prefix, &name);
return name;
}
#define abbrev_branch(name) abbrev_ref((name), "refs/heads/")
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;
struct branch_info *info;
enum { REMOTE, MERGE, REBASE } type;
+ size_t key_len;
key += 7;
- if (!postfixcmp(key, ".remote")) {
- name = xstrndup(key, strlen(key) - 7);
+ if (strip_suffix(key, ".remote", &key_len)) {
+ name = xmemdupz(key, key_len);
type = REMOTE;
- } else if (!postfixcmp(key, ".merge")) {
- name = xstrndup(key, strlen(key) - 6);
+ } else if (strip_suffix(key, ".merge", &key_len)) {
+ name = xmemdupz(key, key_len);
type = MERGE;
- } else if (!postfixcmp(key, ".rebase")) {
- name = xstrndup(key, strlen(key) - 7);
+ } else if (strip_suffix(key, ".rebase", &key_len)) {
+ name = xmemdupz(key, key_len);
type = REBASE;
} else
return 0;
@@ -292,11 +283,11 @@ static int config_read_branches(const char *key, const char *value, void *cb)
item = string_list_insert(&branch_list, name);
if (!item->util)
- item->util = xcalloc(sizeof(struct branch_info), 1);
+ item->util = xcalloc(1, sizeof(struct branch_info));
info = item->util;
if (type == REMOTE) {
if (info->remote_name)
- warning("more than one %s", orig_key);
+ warning(_("more than one %s"), orig_key);
info->remote_name = xstrdup(value);
} else if (type == MERGE) {
char *space = strchr(value, ' ');
@@ -309,8 +300,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;
}
@@ -336,20 +332,20 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
for (i = 0; i < states->remote->fetch_refspec_nr; i++)
if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
- die("Could not get fetch map for refspec %s",
+ die(_("Could not get fetch map for refspec %s"),
states->remote->fetch_refspec[i]);
states->new.strdup_strings = 1;
states->tracked.strdup_strings = 1;
states->stale.strdup_strings = 1;
for (ref = fetch_map; ref; ref = ref->next) {
- unsigned char sha1[20];
- if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
+ if (!ref->peer_ref || !ref_exists(ref->peer_ref->name))
string_list_append(&states->new, abbrev_branch(ref->name));
else
string_list_append(&states->tracked, abbrev_branch(ref->name));
}
- stale_refs = get_stale_heads(states->remote, fetch_map);
+ stale_refs = get_stale_heads(states->remote->fetch,
+ states->remote->fetch_refspec_nr, fetch_map);
for (ref = stale_refs; ref; ref = ref->next) {
struct string_list_item *item =
string_list_append(&states->stale, abbrev_branch(ref->name));
@@ -358,9 +354,9 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
free_refs(stale_refs);
free_refs(fetch_map);
- sort_string_list(&states->new);
- sort_string_list(&states->tracked);
- sort_string_list(&states->stale);
+ string_list_sort(&states->new);
+ string_list_sort(&states->tracked);
+ string_list_sort(&states->stale);
return 0;
}
@@ -389,8 +385,8 @@ static int get_push_ref_states(const struct ref *remote_refs,
local_refs = get_local_heads();
push_map = copy_ref_list(remote_refs);
- match_refs(local_refs, &push_map, remote->push_refspec_nr,
- remote->push_refspec, MATCH_REFS_NONE);
+ match_push_refs(local_refs, &push_map, remote->push_refspec_nr,
+ remote->push_refspec, MATCH_REFS_NONE);
states->push.strdup_strings = 1;
for (ref = push_map; ref; ref = ref->next) {
@@ -403,7 +399,7 @@ static int get_push_ref_states(const struct ref *remote_refs,
item = string_list_append(&states->push,
abbrev_branch(ref->peer_ref->name));
- item->util = xcalloc(sizeof(struct push_info), 1);
+ item->util = xcalloc(1, sizeof(struct push_info));
info = item->util;
info->forced = ref->force;
info->dest = xstrdup(abbrev_branch(ref->name));
@@ -437,21 +433,21 @@ static int get_push_ref_states_noquery(struct ref_states *states)
states->push.strdup_strings = 1;
if (!remote->push_refspec_nr) {
- item = string_list_append(&states->push, "(matching)");
- info = item->util = xcalloc(sizeof(struct push_info), 1);
+ item = string_list_append(&states->push, _("(matching)"));
+ info = item->util = xcalloc(1, sizeof(struct push_info));
info->status = PUSH_STATUS_NOTQUERIED;
info->dest = xstrdup(item->string);
}
for (i = 0; i < remote->push_refspec_nr; i++) {
struct refspec *spec = remote->push + i;
if (spec->matching)
- item = string_list_append(&states->push, "(matching)");
+ item = string_list_append(&states->push, _("(matching)"));
else if (strlen(spec->src))
item = string_list_append(&states->push, spec->src);
else
- item = string_list_append(&states->push, "(delete)");
+ item = string_list_append(&states->push, _("(delete)"));
- info = item->util = xcalloc(sizeof(struct push_info), 1);
+ info = item->util = xcalloc(1, sizeof(struct push_info));
info->forced = spec->force;
info->status = PUSH_STATUS_NOTQUERIED;
info->dest = xstrdup(spec->dst ? spec->dst : item->string);
@@ -534,9 +530,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 */
@@ -570,10 +566,11 @@ static int read_remote_branches(const char *refname,
unsigned char orig_sha1[20];
const char *symref;
- strbuf_addf(&buf, "refs/remotes/%s", rename->old);
- if (!prefixcmp(refname, buf.buf)) {
+ strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
+ if (starts_with(refname, buf.buf)) {
item = string_list_append(rename->remote_branches, xstrdup(refname));
- symref = resolve_ref(refname, orig_sha1, 1, &flag);
+ symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,
+ orig_sha1, &flag);
if (flag & REF_ISSYMREF)
item->util = xstrdup(symref);
else
@@ -592,19 +589,19 @@ static int migrate_file(struct remote *remote)
strbuf_addf(&buf, "remote.%s.url", remote->name);
for (i = 0; i < remote->url_nr; i++)
if (git_config_set_multivar(buf.buf, remote->url[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->url[i], buf.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.push", remote->name);
for (i = 0; i < remote->push_refspec_nr; i++)
if (git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->push_refspec[i], buf.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.fetch", remote->name);
for (i = 0; i < remote->fetch_refspec_nr; i++)
if (git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0))
- return error("Could not append '%s' to '%s'",
+ return error(_("Could not append '%s' to '%s'"),
remote->fetch_refspec[i], buf.buf);
if (remote->origin == REMOTE_REMOTES)
path = git_path("remotes/%s", remote->name);
@@ -621,10 +618,11 @@ static int mv(int argc, const char **argv)
OPT_END()
};
struct remote *oldremote, *newremote;
- struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT,
+ old_remote_context = STRBUF_INIT;
struct string_list remote_branches = STRING_LIST_INIT_NODUP;
struct rename_info rename;
- int i;
+ int i, refspec_updated = 0;
if (argc != 3)
usage_with_options(builtin_remote_rename_usage, options);
@@ -635,41 +633,51 @@ static int mv(int argc, const char **argv)
oldremote = remote_get(rename.old);
if (!oldremote)
- die("No such remote: %s", rename.old);
+ die(_("No such remote: %s"), rename.old);
if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG)
return migrate_file(oldremote);
newremote = remote_get(rename.new);
if (newremote && (newremote->url_nr > 1 || newremote->fetch_refspec_nr))
- die("remote %s already exists.", rename.new);
+ die(_("remote %s already exists."), rename.new);
strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
if (!valid_fetch_refspec(buf.buf))
- die("'%s' is not a valid remote name", rename.new);
+ die(_("'%s' is not a valid remote name"), rename.new);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s", rename.old);
strbuf_addf(&buf2, "remote.%s", rename.new);
if (git_config_rename_section(buf.buf, buf2.buf) < 1)
- return error("Could not rename config section '%s' to '%s'",
+ return error(_("Could not rename config section '%s' to '%s'"),
buf.buf, buf2.buf);
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.fetch", rename.new);
if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
- return error("Could not remove config section '%s'", buf.buf);
+ return error(_("Could not remove config section '%s'"), buf.buf);
+ strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
char *ptr;
strbuf_reset(&buf2);
strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
- ptr = strstr(buf2.buf, rename.old);
- if (ptr)
- strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
- rename.new, strlen(rename.new));
+ ptr = strstr(buf2.buf, old_remote_context.buf);
+ if (ptr) {
+ refspec_updated = 1;
+ strbuf_splice(&buf2,
+ ptr-buf2.buf + strlen(":refs/remotes/"),
+ strlen(rename.old), rename.new,
+ strlen(rename.new));
+ } else
+ warning(_("Not updating non-default fetch refspec\n"
+ "\t%s\n"
+ "\tPlease update the configuration manually if necessary."),
+ buf2.buf);
+
if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
- return error("Could not append '%s'", buf.buf);
+ return error(_("Could not append '%s'"), buf.buf);
}
read_branches();
@@ -680,11 +688,14 @@ static int mv(int argc, const char **argv)
strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.remote", item->string);
if (git_config_set(buf.buf, rename.new)) {
- return error("Could not set '%s'", buf.buf);
+ return error(_("Could not set '%s'"), buf.buf);
}
}
}
+ if (!refspec_updated)
+ return 0;
+
/*
* First remove symrefs, then rename the rest, finally create
* the new symrefs.
@@ -695,11 +706,11 @@ static int mv(int argc, const char **argv)
int flag = 0;
unsigned char sha1[20];
- resolve_ref(item->string, sha1, 1, &flag);
+ read_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag);
if (!(flag & REF_ISSYMREF))
continue;
if (delete_ref(item->string, NULL, REF_NODEREF))
- die("deleting '%s' failed", item->string);
+ die(_("deleting '%s' failed"), item->string);
}
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
@@ -714,7 +725,7 @@ static int mv(int argc, const char **argv)
strbuf_addf(&buf2, "remote: renamed %s to %s",
item->string, buf.buf);
if (rename_ref(item->string, buf.buf, buf2.buf))
- die("renaming '%s' failed", item->string);
+ die(_("renaming '%s' failed"), item->string);
}
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
@@ -733,22 +744,28 @@ static int mv(int argc, const char **argv)
strbuf_addf(&buf3, "remote: renamed %s to %s",
item->string, buf.buf);
if (create_symref(buf.buf, buf2.buf, buf3.buf))
- die("creating '%s' failed", buf.buf);
+ die(_("creating '%s' failed"), buf.buf);
}
return 0;
}
static int remove_branches(struct string_list *branches)
{
+ struct strbuf err = STRBUF_INIT;
int i, result = 0;
+
+ if (repack_without_refs(branches, &err))
+ result |= error("%s", err.buf);
+ strbuf_release(&err);
+
for (i = 0; i < branches->nr; i++) {
struct string_list_item *item = branches->items + i;
const char *refname = item->string;
- unsigned char *sha1 = item->util;
- if (delete_ref(refname, sha1, 0))
- result |= error("Could not remove branch %s", refname);
+ if (delete_ref(refname, NULL, 0))
+ result |= error(_("Could not remove branch %s"), refname);
}
+
return result;
}
@@ -775,15 +792,11 @@ static int rm(int argc, const char **argv)
remote = remote_get(argv[1]);
if (!remote)
- die("No such remote: %s", argv[1]);
+ die(_("No such remote: %s"), argv[1]);
known_remotes.to_delete = remote;
for_each_remote(add_known_remote, &known_remotes);
- strbuf_addf(&buf, "remote.%s", remote->name);
- if (git_config_rename_section(buf.buf, NULL) < 1)
- return error("Could not remove config section '%s'", buf.buf);
-
read_branches();
for (i = 0; i < branch_list.nr; i++) {
struct string_list_item *item = branch_list.items + i;
@@ -816,17 +829,24 @@ static int rm(int argc, const char **argv)
string_list_clear(&branches, 1);
if (skipped.nr) {
- fprintf(stderr, skipped.nr == 1 ?
- "Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
- "to delete it, use:\n" :
- "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
- "to delete them, use:\n");
+ fprintf_ln(stderr,
+ Q_("Note: A branch outside the refs/remotes/ hierarchy was not removed;\n"
+ "to delete it, use:",
+ "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n"
+ "to delete them, use:",
+ skipped.nr));
for (i = 0; i < skipped.nr; i++)
fprintf(stderr, " git branch -d %s\n",
skipped.items[i].string);
}
string_list_clear(&skipped, 0);
+ if (!result) {
+ strbuf_addf(&buf, "remote.%s", remote->name);
+ if (git_config_rename_section(buf.buf, NULL) < 1)
+ return error(_("Could not remove config section '%s'"), buf.buf);
+ }
+
return result;
}
@@ -872,7 +892,7 @@ static int get_remote_ref_states(const char *name,
states->remote = remote_get(name);
if (!states->remote)
- return error("No such remote: %s", name);
+ return error(_("No such remote: %s"), name);
read_branches();
@@ -891,7 +911,7 @@ static int get_remote_ref_states(const char *name,
get_push_ref_states(remote_refs, states);
} else {
for_each_ref(append_ref_to_tracked_list, states);
- sort_string_list(&states->tracked);
+ string_list_sort(&states->tracked);
get_push_ref_states_noquery(states);
}
@@ -925,14 +945,14 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data)
const char *fmt = "%s";
const char *arg = "";
if (string_list_has_string(&states->new, name)) {
- fmt = " new (next fetch will store in remotes/%s)";
+ fmt = _(" new (next fetch will store in remotes/%s)");
arg = states->remote->name;
} else if (string_list_has_string(&states->tracked, name))
- arg = " tracked";
+ arg = _(" tracked");
else if (string_list_has_string(&states->stale, name))
- arg = " stale (use 'git remote prune' to remove)";
+ arg = _(" stale (use 'git remote prune' to remove)");
else
- arg = " ???";
+ arg = _(" ???");
printf(" %-*s", info->width, name);
printf(fmt, arg);
printf("\n");
@@ -973,21 +993,21 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
int i;
if (branch_info->rebase && branch_info->merge.nr > 1) {
- error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+ error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"),
item->string);
return 0;
}
printf(" %-*s ", show_info->width, item->string);
if (branch_info->rebase) {
- printf("rebases onto remote %s\n", merge->items[0].string);
+ printf_ln(_("rebases onto remote %s"), merge->items[0].string);
return 0;
} else if (show_info->any_rebase) {
- printf(" merges with remote %s\n", merge->items[0].string);
- also = " and with remote";
+ printf_ln(_(" merges with remote %s"), merge->items[0].string);
+ also = _(" and with remote");
} else {
- printf("merges with remote %s\n", merge->items[0].string);
- also = " and with remote";
+ printf_ln(_("merges with remote %s"), merge->items[0].string);
+ also = _(" and with remote");
}
for (i = 1; i < merge->nr; i++)
printf(" %-*s %s %s\n", show_info->width, "", also,
@@ -1029,44 +1049,109 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data)
{
struct show_info *show_info = cb_data;
struct push_info *push_info = item->util;
- char *src = item->string, *status = NULL;
+ const char *src = item->string, *status = NULL;
switch (push_info->status) {
case PUSH_STATUS_CREATE:
- status = "create";
+ status = _("create");
break;
case PUSH_STATUS_DELETE:
- status = "delete";
- src = "(none)";
+ status = _("delete");
+ src = _("(none)");
break;
case PUSH_STATUS_UPTODATE:
- status = "up to date";
+ status = _("up to date");
break;
case PUSH_STATUS_FASTFORWARD:
- status = "fast-forwardable";
+ status = _("fast-forwardable");
break;
case PUSH_STATUS_OUTOFDATE:
- status = "local out of date";
+ status = _("local out of date");
break;
case PUSH_STATUS_NOTQUERIED:
break;
}
- if (status)
- printf(" %-*s %s to %-*s (%s)\n", show_info->width, src,
- push_info->forced ? "forces" : "pushes",
- show_info->width2, push_info->dest, status);
- else
- printf(" %-*s %s to %s\n", show_info->width, src,
- push_info->forced ? "forces" : "pushes",
- push_info->dest);
+ if (status) {
+ if (push_info->forced)
+ printf_ln(_(" %-*s forces to %-*s (%s)"), show_info->width, src,
+ show_info->width2, push_info->dest, status);
+ else
+ printf_ln(_(" %-*s pushes to %-*s (%s)"), show_info->width, src,
+ show_info->width2, push_info->dest, status);
+ } else {
+ if (push_info->forced)
+ printf_ln(_(" %-*s forces to %s"), show_info->width, src,
+ push_info->dest);
+ else
+ printf_ln(_(" %-*s pushes to %s"), show_info->width, src,
+ push_info->dest);
+ }
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;
+
+ string_list_sort(&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, "do not query remotes"),
+ OPT_BOOL('n', NULL, &no_query, N_("do not query remotes")),
OPT_END()
};
struct ref_states states;
@@ -1093,9 +1178,9 @@ static int show(int argc, const char **argv)
get_remote_ref_states(*argv, &states, query_flag);
- printf("* remote %s\n", *argv);
- printf(" Fetch URL: %s\n", states.remote->url_nr > 0 ?
- states.remote->url[0] : "(no URL)");
+ printf_ln(_("* remote %s"), *argv);
+ printf_ln(_(" Fetch URL: %s"), states.remote->url_nr > 0 ?
+ states.remote->url[0] : _("(no URL)"));
if (states.remote->pushurl_nr) {
url = states.remote->pushurl;
url_nr = states.remote->pushurl_nr;
@@ -1104,18 +1189,18 @@ static int show(int argc, const char **argv)
url_nr = states.remote->url_nr;
}
for (i = 0; i < url_nr; i++)
- printf(" Push URL: %s\n", url[i]);
+ printf_ln(_(" Push URL: %s"), url[i]);
if (!i)
- printf(" Push URL: %s\n", "(no URL)");
+ printf_ln(_(" Push URL: %s"), "(no URL)");
if (no_query)
- printf(" HEAD branch: (not queried)\n");
+ printf_ln(_(" HEAD branch: %s"), "(not queried)");
else if (!states.heads.nr)
- printf(" HEAD branch: (unknown)\n");
+ printf_ln(_(" HEAD branch: %s"), "(unknown)");
else if (states.heads.nr == 1)
- printf(" HEAD branch: %s\n", states.heads.items[0].string);
+ printf_ln(_(" HEAD branch: %s"), states.heads.items[0].string);
else {
- printf(" HEAD branch (remote HEAD is ambiguous,"
- " may be one of the following):\n");
+ printf(_(" HEAD branch (remote HEAD is ambiguous,"
+ " may be one of the following):\n"));
for (i = 0; i < states.heads.nr; i++)
printf(" %s\n", states.heads.items[i].string);
}
@@ -1126,9 +1211,10 @@ static int show(int argc, const char **argv)
for_each_string_list(&states.tracked, add_remote_to_show_info, &info);
for_each_string_list(&states.stale, add_remote_to_show_info, &info);
if (info.list->nr)
- printf(" Remote branch%s:%s\n",
- info.list->nr > 1 ? "es" : "",
- no_query ? " (status not queried)" : "");
+ printf_ln(Q_(" Remote branch:%s",
+ " Remote branches:%s",
+ info.list->nr),
+ no_query ? _(" (status not queried)") : "");
for_each_string_list(info.list, show_remote_info_item, &info);
string_list_clear(info.list, 0);
@@ -1137,23 +1223,25 @@ static int show(int argc, const char **argv)
info.any_rebase = 0;
for_each_string_list(&branch_list, add_local_to_show_info, &info);
if (info.list->nr)
- printf(" Local branch%s configured for 'git pull':\n",
- info.list->nr > 1 ? "es" : "");
+ printf_ln(Q_(" Local branch configured for 'git pull':",
+ " Local branches configured for 'git pull':",
+ info.list->nr));
for_each_string_list(info.list, show_local_info_item, &info);
string_list_clear(info.list, 0);
/* git push info */
if (states.remote->mirror)
- printf(" Local refs will be mirrored by 'git push'\n");
+ printf_ln(_(" Local refs will be mirrored by 'git push'"));
info.width = info.width2 = 0;
for_each_string_list(&states.push, add_push_to_show_info, &info);
qsort(info.list->items, info.list->nr,
sizeof(*info.list->items), cmp_string_with_push);
if (info.list->nr)
- printf(" Local ref%s configured for 'git push'%s:\n",
- info.list->nr > 1 ? "s" : "",
- no_query ? " (status not queried)" : "");
+ printf_ln(Q_(" Local ref configured for 'git push'%s:",
+ " Local refs configured for 'git push'%s:",
+ info.list->nr),
+ no_query ? _(" (status not queried)") : "");
for_each_string_list(info.list, show_push_info_item, &info);
string_list_clear(info.list, 0);
@@ -1170,10 +1258,10 @@ static int set_head(int argc, const char **argv)
char *head_name = NULL;
struct option options[] = {
- OPT_BOOLEAN('a', "auto", &opt_a,
- "set refs/remotes/<name>/HEAD according to remote"),
- OPT_BOOLEAN('d', "delete", &opt_d,
- "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,
@@ -1188,10 +1276,10 @@ static int set_head(int argc, const char **argv)
memset(&states, 0, sizeof(states));
get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
if (!states.heads.nr)
- result |= error("Cannot determine remote HEAD");
+ result |= error(_("Cannot determine remote HEAD"));
else if (states.heads.nr > 1) {
- result |= error("Multiple remote HEAD branches. "
- "Please choose one explicitly with:");
+ result |= error(_("Multiple remote HEAD branches. "
+ "Please choose one explicitly with:"));
for (i = 0; i < states.heads.nr; i++)
fprintf(stderr, " git remote set-head %s %s\n",
argv[0], states.heads.items[i].string);
@@ -1200,18 +1288,17 @@ static int set_head(int argc, const char **argv)
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
if (delete_ref(buf.buf, NULL, REF_NODEREF))
- result |= error("Could not delete %s", buf.buf);
+ result |= error(_("Could not delete %s"), buf.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
if (head_name) {
- unsigned char sha1[20];
strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!resolve_ref(buf2.buf, sha1, 1, NULL))
- result |= error("Not a valid ref: %s", buf2.buf);
+ if (!ref_exists(buf2.buf))
+ result |= error(_("Not a valid ref: %s"), buf2.buf);
else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
- result |= error("Could not setup %s", buf.buf);
+ result |= error(_("Could not setup %s"), buf.buf);
if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
free(head_name);
@@ -1222,60 +1309,82 @@ 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, "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;
+ int result = 0;
struct ref_states states;
+ struct string_list refs_to_prune = STRING_LIST_INIT_NODUP;
+ struct string_list_item *item;
const char *dangling_msg = dry_run
- ? " %s will become dangling!\n"
- : " %s has become dangling!\n";
+ ? _(" %s will become dangling!")
+ : _(" %s has become dangling!");
memset(&states, 0, sizeof(states));
get_remote_ref_states(remote, &states, GET_REF_STATES);
- if (states.stale.nr) {
- printf("Pruning %s\n", remote);
- printf("URL: %s\n",
- states.remote->url_nr
- ? states.remote->url[0]
- : "(no URL)");
+ if (!states.stale.nr) {
+ free_remote_ref_states(&states);
+ return 0;
+ }
+
+ printf_ln(_("Pruning %s"), remote);
+ printf_ln(_("URL: %s"),
+ states.remote->url_nr
+ ? states.remote->url[0]
+ : _("(no URL)"));
+
+ for_each_string_list_item(item, &states.stale)
+ string_list_append(&refs_to_prune, item->util);
+ string_list_sort(&refs_to_prune);
+
+ if (!dry_run) {
+ struct strbuf err = STRBUF_INIT;
+ if (repack_without_refs(&refs_to_prune, &err))
+ result |= error("%s", err.buf);
+ strbuf_release(&err);
}
- for (i = 0; i < states.stale.nr; i++) {
- const char *refname = states.stale.items[i].util;
+ for_each_string_list_item(item, &states.stale) {
+ const char *refname = item->util;
if (!dry_run)
result |= delete_ref(refname, NULL, 0);
- printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
- abbrev_ref(refname, "refs/remotes/"));
- warn_dangling_symref(stdout, dangling_msg, refname);
+ if (dry_run)
+ printf_ln(_(" * [would prune] %s"),
+ abbrev_ref(refname, "refs/remotes/"));
+ else
+ printf_ln(_(" * [pruned] %s"),
+ abbrev_ref(refname, "refs/remotes/"));
}
+ warn_dangling_symrefs(stdout, dangling_msg, &refs_to_prune);
+
+ string_list_clear(&refs_to_prune, 0);
free_remote_ref_states(&states);
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) {
@@ -1287,42 +1396,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,
- "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)
@@ -1356,7 +1465,7 @@ static int set_remote_branches(const char *remotename, const char **branches,
strbuf_addf(&key, "remote.%s.fetch", remotename);
if (!remote_is_configured(remotename))
- die("No such remote '%s'", remotename);
+ die(_("No such remote '%s'"), remotename);
remote = remote_get(remotename);
if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
@@ -1376,15 +1485,15 @@ static int set_branches(int argc, const char **argv)
{
int add_mode = 0;
struct option options[] = {
- OPT_BOOLEAN('\0', "add", &add_mode, "add branch"),
+ OPT_BOOL('\0', "add", &add_mode, N_("add branch")),
OPT_END()
};
argc = parse_options(argc, argv, NULL, options,
builtin_remote_setbranches_usage, 0);
if (argc == 0) {
- error("no remote specified");
- usage_with_options(builtin_remote_seturl_usage, options);
+ error(_("no remote specified"));
+ usage_with_options(builtin_remote_setbranches_usage, options);
}
argv[argc] = NULL;
@@ -1404,19 +1513,19 @@ 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,
- "manipulate push URLs"),
- OPT_BOOLEAN('\0', "add", &add_mode,
- "add URL"),
- OPT_BOOLEAN('\0', "delete", &delete_mode,
- "delete URLs"),
+ 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()
};
- argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage,
PARSE_OPT_KEEP_ARGV0);
if (add_mode && delete_mode)
- die("--add --delete doesn't make sense");
+ die(_("--add --delete doesn't make sense"));
if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3))
usage_with_options(builtin_remote_seturl_usage, options);
@@ -1430,7 +1539,7 @@ static int set_url(int argc, const char **argv)
oldurl = newurl;
if (!remote_is_configured(remotename))
- die("No such remote '%s'", remotename);
+ die(_("No such remote '%s'"), remotename);
remote = remote_get(remotename);
if (push_mode) {
@@ -1456,7 +1565,7 @@ static int set_url(int argc, const char **argv)
/* Old URL specified. Demand that one matches. */
if (regcomp(&old_regex, oldurl, REG_EXTENDED))
- die("Invalid old URL pattern: %s", oldurl);
+ die(_("Invalid old URL pattern: %s"), oldurl);
for (i = 0; i < urlset_nr; i++)
if (!regexec(&old_regex, urlset[i], 0, NULL, 0))
@@ -1464,9 +1573,9 @@ static int set_url(int argc, const char **argv)
else
negative_matches++;
if (!delete_mode && !matches)
- die("No such URL found: %s", oldurl);
+ die(_("No such URL found: %s"), oldurl);
if (delete_mode && !negative_matches && !push_mode)
- die("Will not delete all non-push URLs");
+ die(_("Will not delete all non-push URLs"));
regfree(&old_regex);
@@ -1477,68 +1586,10 @@ 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[] = {
- OPT__VERBOSE(&verbose, "be verbose; must be placed before a subcommand"),
+ OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")),
OPT_END()
};
int result;
@@ -1552,7 +1603,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
result = add(argc, argv);
else if (!strcmp(argv[0], "rename"))
result = mv(argc, argv);
- else if (!strcmp(argv[0], "rm"))
+ else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove"))
result = rm(argc, argv);
else if (!strcmp(argv[0], "set-head"))
result = set_head(argc, argv);
@@ -1567,7 +1618,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
else if (!strcmp(argv[0], "update"))
result = update(argc, argv);
else {
- error("Unknown subcommand: %s", argv[0]);
+ error(_("Unknown subcommand: %s"), argv[0]);
usage_with_options(builtin_remote_usage, options);
}
diff --git a/builtin/repack.c b/builtin/repack.c
new file mode 100644
index 0000000..2fe1b30
--- /dev/null
+++ b/builtin/repack.c
@@ -0,0 +1,407 @@
+#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 int pack_kept_objects = -1;
+static int write_bitmaps;
+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;
+ }
+ if (!strcmp(var, "repack.packkeptobjects")) {
+ pack_kept_objects = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "repack.writebitmaps") ||
+ !strcmp(var, "pack.writebitmaps")) {
+ write_bitmaps = 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;
+
+ if (!(dir = opendir(packdir)))
+ return;
+
+ while ((e = readdir(dir)) != NULL) {
+ size_t len;
+ if (!strip_suffix(e->d_name, ".pack", &len))
+ continue;
+
+ 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 = CHILD_PROCESS_INIT;
+ struct string_list_item *item;
+ struct string_list names = STRING_LIST_INIT_DUP;
+ struct string_list rollback = STRING_LIST_INIT_NODUP;
+ struct string_list existing_packs = STRING_LIST_INIT_DUP;
+ struct 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;
+
+ 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_bitmaps,
+ 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_BOOL(0, "pack-kept-objects", &pack_kept_objects,
+ N_("repack objects in packs marked with .keep")),
+ OPT_END()
+ };
+
+ git_config(repack_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, builtin_repack_options,
+ git_repack_usage, 0);
+
+ if (pack_kept_objects < 0)
+ pack_kept_objects = write_bitmaps;
+
+ 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");
+ if (!pack_kept_objects)
+ 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");
+ argv_array_push(&cmd.args, "--indexed-objects");
+ 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_bitmaps)
+ argv_array_push(&cmd.args, "--write-bitmap-index");
+
+ 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);
+ argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
+ } else if (pack_everything & LOOSEN_UNREACHABLE) {
+ argv_array_push(&cmd.args,
+ "--unpack-unreachable");
+ } else {
+ argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
+ }
+ }
+ } 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);
+
+ 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;
+
+ 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) {
+ int opts = 0;
+ string_list_sort(&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);
+ }
+ if (!quiet && isatty(2))
+ opts |= PRUNE_PACKED_VERBOSE;
+ prune_packed_objects(opts);
+ }
+
+ if (!no_update_server_info)
+ update_server_info(0);
+ 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 fe3a647..85d39b5 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.
*/
@@ -12,31 +12,77 @@
#include "builtin.h"
#include "refs.h"
#include "parse-options.h"
+#include "run-command.h"
+#include "tag.h"
static const char * const git_replace_usage[] = {
- "git replace [-f] <object> <replacement>",
- "git replace -d <object>...",
- "git replace -l [<pattern>]",
+ N_("git replace [-f] <object> <replacement>"),
+ N_("git replace [-f] --edit <object>"),
+ N_("git replace [-f] --graft <commit> [<parent>...]"),
+ N_("git replace -d <object>..."),
+ 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 (!wildmatch(data->pattern, refname, 0, NULL)) {
+ 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;
}
@@ -46,24 +92,27 @@ typedef int (*each_replace_name_fn)(const char *name, const char *ref,
static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
{
- const char **p;
+ const char **p, *full_hex;
char ref[PATH_MAX];
int had_error = 0;
unsigned char sha1[20];
for (p = argv; *p; p++) {
- if (snprintf(ref, sizeof(ref), "refs/replace/%s", *p)
- >= sizeof(ref)) {
- error("replace ref name too long: %.*s...", 50, *p);
+ if (get_sha1(*p, sha1)) {
+ error("Failed to resolve '%s' as a valid ref.", *p);
had_error = 1;
continue;
}
- if (!resolve_ref(ref, sha1, 1, NULL)) {
- error("replace ref '%s' not found.", *p);
+ full_hex = sha1_to_hex(sha1);
+ snprintf(ref, sizeof(ref), "refs/replace/%s", full_hex);
+ /* read_ref() may reuse the buffer */
+ full_hex = ref + strlen("refs/replace/");
+ if (read_ref(ref, sha1)) {
+ error("replace ref '%s' not found.", full_hex);
had_error = 1;
continue;
}
- if (fn(*p, ref, sha1))
+ if (fn(full_hex, ref, sha1))
had_error = 1;
}
return had_error;
@@ -78,82 +127,372 @@ static int delete_replace_ref(const char *name, const char *ref,
return 0;
}
-static int replace_object(const char *object_ref, const char *replace_ref,
- int force)
+static void check_ref_valid(unsigned char object[20],
+ unsigned char prev[20],
+ char *ref,
+ int ref_size,
+ int force)
{
- unsigned char object[20], prev[20], repl[20];
- char ref[PATH_MAX];
- struct ref_lock *lock;
-
- if (get_sha1(object_ref, object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
- if (get_sha1(replace_ref, repl))
- die("Failed to resolve '%s' as a valid ref.", replace_ref);
-
- if (snprintf(ref, sizeof(ref),
+ if (snprintf(ref, ref_size,
"refs/replace/%s",
- sha1_to_hex(object)) > sizeof(ref) - 1)
+ sha1_to_hex(object)) > ref_size - 1)
die("replace ref name too long: %.*s...", 50, ref);
- if (check_ref_format(ref))
+ if (check_refname_format(ref, 0))
die("'%s' is not a valid ref name.", ref);
- if (!resolve_ref(ref, prev, 1, NULL))
+ if (read_ref(ref, prev))
hashclr(prev);
else if (!force)
die("replace ref '%s' already exists", ref);
+}
+
+static int replace_object_sha1(const char *object_ref,
+ unsigned char object[20],
+ const char *replace_ref,
+ unsigned char repl[20],
+ int force)
+{
+ unsigned char prev[20];
+ enum object_type obj_type, repl_type;
+ char ref[PATH_MAX];
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+
+ 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));
- lock = lock_any_ref_for_update(ref, prev, 0);
- if (!lock)
- die("%s: cannot lock the ref", ref);
- if (write_ref_sha1(lock, repl, NULL) < 0)
- die("%s: cannot update the ref", ref);
+ check_ref_valid(object, prev, ref, sizeof(ref), force);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref, repl, prev,
+ 0, 1, NULL, &err) ||
+ ref_transaction_commit(transaction, &err))
+ die("%s", err.buf);
+
+ ref_transaction_free(transaction);
return 0;
}
+static int replace_object(const char *object_ref, const char *replace_ref, int force)
+{
+ unsigned char object[20], repl[20];
+
+ if (get_sha1(object_ref, object))
+ die("Failed to resolve '%s' as a valid ref.", object_ref);
+ if (get_sha1(replace_ref, repl))
+ die("Failed to resolve '%s' as a valid ref.", replace_ref);
+
+ return replace_object_sha1(object_ref, object, replace_ref, repl, force);
+}
+
+/*
+ * Write the contents of the object named by "sha1" to the file "filename".
+ * If "raw" is true, then the object's raw contents are printed according to
+ * "type". Otherwise, we pretty-print the contents for human editing.
+ */
+static void export_object(const unsigned char *sha1, enum object_type type,
+ int raw, const char *filename)
+{
+ struct child_process cmd = CHILD_PROCESS_INIT;
+ int fd;
+
+ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ die_errno("unable to open %s for writing", filename);
+
+ argv_array_push(&cmd.args, "--no-replace-objects");
+ argv_array_push(&cmd.args, "cat-file");
+ if (raw)
+ argv_array_push(&cmd.args, typename(type));
+ else
+ argv_array_push(&cmd.args, "-p");
+ argv_array_push(&cmd.args, sha1_to_hex(sha1));
+ cmd.git_cmd = 1;
+ cmd.out = fd;
+
+ if (run_command(&cmd))
+ die("cat-file reported failure");
+}
+
+/*
+ * Read a previously-exported (and possibly edited) object back from "filename",
+ * interpreting it as "type", and writing the result to the object database.
+ * The sha1 of the written object is returned via sha1.
+ */
+static void import_object(unsigned char *sha1, enum object_type type,
+ int raw, const char *filename)
+{
+ int fd;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ die_errno("unable to open %s for reading", filename);
+
+ if (!raw && type == OBJ_TREE) {
+ const char *argv[] = { "mktree", NULL };
+ struct child_process cmd = CHILD_PROCESS_INIT;
+ struct strbuf result = STRBUF_INIT;
+
+ cmd.argv = argv;
+ cmd.git_cmd = 1;
+ cmd.in = fd;
+ cmd.out = -1;
+
+ if (start_command(&cmd))
+ die("unable to spawn mktree");
+
+ if (strbuf_read(&result, cmd.out, 41) < 0)
+ die_errno("unable to read from mktree");
+ close(cmd.out);
+
+ if (finish_command(&cmd))
+ die("mktree reported failure");
+ if (get_sha1_hex(result.buf, sha1) < 0)
+ die("mktree did not return an object name");
+
+ strbuf_release(&result);
+ } else {
+ struct stat st;
+ int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
+
+ if (fstat(fd, &st) < 0)
+ die_errno("unable to fstat %s", filename);
+ if (index_fd(sha1, fd, &st, type, NULL, flags) < 0)
+ die("unable to write object to database");
+ /* index_fd close()s fd for us */
+ }
+
+ /*
+ * No need to close(fd) here; both run-command and index-fd
+ * will have done it for us.
+ */
+}
+
+static int edit_and_replace(const char *object_ref, int force, int raw)
+{
+ char *tmpfile = git_pathdup("REPLACE_EDITOBJ");
+ enum object_type type;
+ unsigned char old[20], new[20], prev[20];
+ char ref[PATH_MAX];
+
+ if (get_sha1(object_ref, old) < 0)
+ die("Not a valid object name: '%s'", object_ref);
+
+ type = sha1_object_info(old, NULL);
+ if (type < 0)
+ die("unable to get object type for %s", sha1_to_hex(old));
+
+ check_ref_valid(old, prev, ref, sizeof(ref), force);
+
+ export_object(old, type, raw, tmpfile);
+ if (launch_editor(tmpfile, NULL, NULL) < 0)
+ die("editing object file failed");
+ import_object(new, type, raw, tmpfile);
+
+ free(tmpfile);
+
+ if (!hashcmp(old, new))
+ return error("new object is the same as the old one: '%s'", sha1_to_hex(old));
+
+ return replace_object_sha1(object_ref, old, "replacement", new, force);
+}
+
+static void replace_parents(struct strbuf *buf, int argc, const char **argv)
+{
+ struct strbuf new_parents = STRBUF_INIT;
+ const char *parent_start, *parent_end;
+ int i;
+
+ /* find existing parents */
+ parent_start = buf->buf;
+ parent_start += 46; /* "tree " + "hex sha1" + "\n" */
+ parent_end = parent_start;
+
+ while (starts_with(parent_end, "parent "))
+ parent_end += 48; /* "parent " + "hex sha1" + "\n" */
+
+ /* prepare new parents */
+ for (i = 0; i < argc; i++) {
+ unsigned char sha1[20];
+ if (get_sha1(argv[i], sha1) < 0)
+ die(_("Not a valid object name: '%s'"), argv[i]);
+ lookup_commit_or_die(sha1, argv[i]);
+ strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1));
+ }
+
+ /* replace existing parents with new ones */
+ strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start,
+ new_parents.buf, new_parents.len);
+
+ strbuf_release(&new_parents);
+}
+
+struct check_mergetag_data {
+ int argc;
+ const char **argv;
+};
+
+static void check_one_mergetag(struct commit *commit,
+ struct commit_extra_header *extra,
+ void *data)
+{
+ struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data;
+ const char *ref = mergetag_data->argv[0];
+ unsigned char tag_sha1[20];
+ struct tag *tag;
+ int i;
+
+ hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1);
+ tag = lookup_tag(tag_sha1);
+ if (!tag)
+ die(_("bad mergetag in commit '%s'"), ref);
+ if (parse_tag_buffer(tag, extra->value, extra->len))
+ die(_("malformed mergetag in commit '%s'"), ref);
+
+ /* iterate over new parents */
+ for (i = 1; i < mergetag_data->argc; i++) {
+ unsigned char sha1[20];
+ if (get_sha1(mergetag_data->argv[i], sha1) < 0)
+ die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]);
+ if (!hashcmp(tag->tagged->sha1, sha1))
+ return; /* found */
+ }
+
+ die(_("original commit '%s' contains mergetag '%s' that is discarded; "
+ "use --edit instead of --graft"), ref, sha1_to_hex(tag_sha1));
+}
+
+static void check_mergetags(struct commit *commit, int argc, const char **argv)
+{
+ struct check_mergetag_data mergetag_data;
+
+ mergetag_data.argc = argc;
+ mergetag_data.argv = argv;
+ for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
+}
+
+static int create_graft(int argc, const char **argv, int force)
+{
+ unsigned char old[20], new[20];
+ const char *old_ref = argv[0];
+ struct commit *commit;
+ struct strbuf buf = STRBUF_INIT;
+ const char *buffer;
+ unsigned long size;
+
+ if (get_sha1(old_ref, old) < 0)
+ die(_("Not a valid object name: '%s'"), old_ref);
+ commit = lookup_commit_or_die(old, old_ref);
+
+ buffer = get_commit_buffer(commit, &size);
+ strbuf_add(&buf, buffer, size);
+ unuse_commit_buffer(commit, buffer);
+
+ replace_parents(&buf, argc - 1, &argv[1]);
+
+ if (remove_signature(&buf)) {
+ warning(_("the original commit '%s' has a gpg signature."), old_ref);
+ warning(_("the signature will be removed in the replacement commit!"));
+ }
+
+ check_mergetags(commit, argc, argv);
+
+ if (write_sha1_file(buf.buf, buf.len, commit_type, new))
+ die(_("could not write replacement commit for: '%s'"), old_ref);
+
+ strbuf_release(&buf);
+
+ if (!hashcmp(old, new))
+ return error("new commit is the same as the old one: '%s'", sha1_to_hex(old));
+
+ return replace_object_sha1(old_ref, old, "replacement", new, force);
+}
+
int cmd_replace(int argc, const char **argv, const char *prefix)
{
- int list = 0, delete = 0, force = 0;
+ int force = 0;
+ int raw = 0;
+ const char *format = NULL;
+ enum {
+ MODE_UNSPECIFIED = 0,
+ MODE_LIST,
+ MODE_DELETE,
+ MODE_EDIT,
+ MODE_GRAFT,
+ MODE_REPLACE
+ } cmdmode = MODE_UNSPECIFIED;
struct option options[] = {
- OPT_BOOLEAN('l', NULL, &list, "list replace refs"),
- OPT_BOOLEAN('d', NULL, &delete, "delete replace refs"),
- OPT_BOOLEAN('f', NULL, &force, "replace the ref if it exists"),
+ OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
+ OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
+ OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
+ OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
+ OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")),
+ OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")),
+ OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
OPT_END()
};
+ check_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",
+ if (!cmdmode)
+ cmdmode = argc ? MODE_REPLACE : MODE_LIST;
+
+ if (format && cmdmode != MODE_LIST)
+ usage_msg_opt("--format cannot be used when not listing",
+ git_replace_usage, options);
+
+ if (force &&
+ cmdmode != MODE_REPLACE &&
+ cmdmode != MODE_EDIT &&
+ cmdmode != MODE_GRAFT)
+ usage_msg_opt("-f only makes sense when writing a replacement",
git_replace_usage, options);
- if (force && (list || delete))
- usage_msg_opt("-f cannot be used with -d or -l",
+ if (raw && cmdmode != MODE_EDIT)
+ usage_msg_opt("--raw only makes sense with --edit",
git_replace_usage, options);
- /* Delete refs */
- if (delete) {
+ switch (cmdmode) {
+ case MODE_DELETE:
if (argc < 1)
usage_msg_opt("-d needs at least one argument",
git_replace_usage, options);
return for_each_replace_name(argv, delete_replace_ref);
- }
- /* Replace object */
- if (!list && argc) {
+ case MODE_REPLACE:
if (argc != 2)
usage_msg_opt("bad number of arguments",
git_replace_usage, options);
return replace_object(argv[0], argv[1], force);
- }
- /* List refs, even if "list" is not set */
- if (argc > 1)
- usage_msg_opt("only one pattern can be given with -l",
- git_replace_usage, options);
- if (force)
- usage_msg_opt("-f needs some arguments",
- git_replace_usage, options);
+ case MODE_EDIT:
+ if (argc != 1)
+ usage_msg_opt("-e needs exactly one argument",
+ git_replace_usage, options);
+ return edit_and_replace(argv[0], force, raw);
+
+ case MODE_GRAFT:
+ if (argc < 1)
+ usage_msg_opt("-g needs at least one argument",
+ git_replace_usage, options);
+ return create_graft(argc, argv, force);
- return list_replace_refs(argv[0]);
+ case MODE_LIST:
+ if (argc > 1)
+ usage_msg_opt("only one pattern can be given with -l",
+ git_replace_usage, options);
+ return list_replace_refs(argv[0], format);
+
+ default:
+ die("BUG: invalid cmdmode %d", (int)cmdmode);
+ }
}
diff --git a/builtin/rerere.c b/builtin/rerere.c
index 08213c7..98eb8c5 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -6,9 +6,10 @@
#include "rerere.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
+#include "pathspec.h"
static const char * const rerere_usage[] = {
- "git rerere [clear | forget path... | status | remaining | diff | gc]",
+ N_("git rerere [clear | forget path... | status | remaining | diff | gc]"),
NULL,
};
@@ -53,12 +54,14 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
- "register clean resolutions in index", 1),
+ N_("register clean resolutions in index"), 1),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+ git_config(git_xmerge_config, NULL);
+
if (autoupdate == 1)
flags = RERERE_AUTOUPDATE;
if (autoupdate == 0)
@@ -68,11 +71,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 811e8e2..4c08ddc 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -8,6 +8,7 @@
* Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
*/
#include "builtin.h"
+#include "lockfile.h"
#include "tag.h"
#include "object.h"
#include "commit.h"
@@ -22,9 +23,9 @@
#include "cache-tree.h"
static const char * const git_reset_usage[] = {
- "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]",
- "git reset [-q] <commit> [--] <paths>...",
- "git reset --patch [<commit>] [--] [<paths>...]",
+ N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
+ N_("git reset [-q] <tree-ish> [--] <paths>..."),
+ N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
NULL
};
@@ -38,13 +39,12 @@ static inline int is_merge(void)
return !access(git_path("MERGE_HEAD"), F_OK);
}
-static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet)
+static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
{
int nr = 1;
- int newfd;
struct tree_desc desc[2];
+ struct tree *tree;
struct unpack_trees_options opts;
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
memset(&opts, 0, sizeof(opts));
opts.head_idx = 1;
@@ -66,8 +66,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
opts.reset = 1;
}
- newfd = hold_locked_index(lock, 1);
-
read_cache_unmerged();
if (reset_type == KEEP) {
@@ -84,9 +82,11 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
return error(_("Failed to find tree of %s."), sha1_to_hex(sha1));
if (unpack_trees(nr, desc, &opts))
return -1;
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock))
- return error(_("Could not write new index file."));
+
+ if (reset_type == MIXED || reset_type == HARD) {
+ tree = parse_tree_indirect(sha1);
+ prime_cache_tree(&the_index, tree);
+ }
return 0;
}
@@ -94,10 +94,12 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet
static void print_new_head_line(struct commit *commit)
{
const char *hex, *body;
+ const 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;
@@ -108,92 +110,57 @@ static void print_new_head_line(struct commit *commit)
}
else
printf("\n");
-}
-
-static int update_index_refresh(int fd, struct lock_file *index_lock, int flags)
-{
- int result;
-
- if (!index_lock) {
- index_lock = xcalloc(1, sizeof(struct lock_file));
- fd = hold_locked_index(index_lock, 1);
- }
-
- if (read_cache() < 0)
- return error(_("Could not read index"));
-
- result = refresh_index(&the_index, (flags), NULL, NULL,
- _("Unstaged changes after reset:")) ? 1 : 0;
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(index_lock))
- return error ("Could not refresh index");
- return result;
+ unuse_commit_buffer(commit, msg);
}
static void update_index_from_diff(struct diff_queue_struct *q,
struct diff_options *opt, void *data)
{
int i;
- int *discard_flag = data;
-
- /* do_diff_cache() mangled the index */
- discard_cache();
- *discard_flag = 1;
- read_cache();
+ 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
- remove_file_from_cache(one->path);
- }
-}
+ int is_missing = !(one->mode && !is_null_sha1(one->sha1));
+ struct cache_entry *ce;
-static int interactive_reset(const char *revision, const char **argv,
- const char *prefix)
-{
- const char **pathspec = NULL;
-
- if (*argv)
- pathspec = get_pathspec(prefix, argv);
+ if (is_missing && !intent_to_add) {
+ remove_file_from_cache(one->path);
+ continue;
+ }
- return run_add_interactive(revision, "--patch=reset", pathspec);
+ 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 *prefix, const char **argv,
- unsigned char *tree_sha1, int refresh_flags)
+static int read_from_tree(const struct pathspec *pathspec,
+ unsigned char *tree_sha1,
+ int intent_to_add)
{
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
- int index_fd, index_was_discarded = 0;
struct diff_options opt;
memset(&opt, 0, sizeof(opt));
- diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
+ copy_pathspec(&opt.pathspec, pathspec);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.format_callback = update_index_from_diff;
- opt.format_callback_data = &index_was_discarded;
+ opt.format_callback_data = &intent_to_add;
- index_fd = hold_locked_index(lock, 1);
- index_was_discarded = 0;
- 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);
- if (!index_was_discarded)
- /* The index is still clobbered from do_diff_cache() */
- discard_cache();
- return update_index_refresh(index_fd, lock, refresh_flags);
+ return 0;
}
static void set_reflog_message(struct strbuf *sb, const char *action,
@@ -212,158 +179,211 @@ 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]));
}
-int cmd_reset(int argc, const char **argv, const char *prefix)
+static void parse_args(struct pathspec *pathspec,
+ const char **argv, const char *prefix,
+ int patch_mode,
+ const char **rev_ret)
{
- int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0;
- int patch_mode = 0;
const char *rev = "HEAD";
- unsigned char sha1[20], *orig = NULL, sha1_orig[20],
- *old_orig = NULL, sha1_old_orig[20];
- struct commit *commit;
- struct strbuf msg = STRBUF_INIT;
- const struct option options[] = {
- OPT__QUIET(&quiet, "be quiet, only report errors"),
- OPT_SET_INT(0, "mixed", &reset_type,
- "reset HEAD and index", MIXED),
- OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT),
- OPT_SET_INT(0, "hard", &reset_type,
- "reset HEAD, index and working tree", HARD),
- OPT_SET_INT(0, "merge", &reset_type,
- "reset HEAD, index and working tree", MERGE),
- OPT_SET_INT(0, "keep", &reset_type,
- "reset HEAD but keep local changes", KEEP),
- OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
- OPT_END()
- };
-
- git_config(git_default_config, NULL);
-
- argc = parse_options(argc, argv, prefix, options, git_reset_usage,
- PARSE_OPT_KEEP_DASHDASH);
-
+ unsigned char unused[20];
/*
* Possible arguments are:
*
- * git reset [-opts] <rev> <paths>...
- * git reset [-opts] <rev> -- <paths>...
- * git reset [-opts] -- <paths>...
+ * git reset [-opts] [<rev>]
+ * git reset [-opts] <tree> [<paths>...]
+ * git reset [-opts] <tree> -- [<paths>...]
+ * git reset [-opts] -- [<paths>...]
* git reset [-opts] <paths>...
*
- * At this point, argv[i] points immediately after [-opts].
+ * At this point, argv points immediately after [-opts].
*/
- if (i < argc) {
- if (!strcmp(argv[i], "--")) {
- i++; /* reset to HEAD, possibly with paths */
- } else if (i + 1 < argc && !strcmp(argv[i+1], "--")) {
- rev = argv[i];
- i += 2;
+ if (argv[0]) {
+ if (!strcmp(argv[0], "--")) {
+ argv++; /* reset to HEAD, possibly with paths */
+ } else if (argv[1] && !strcmp(argv[1], "--")) {
+ rev = argv[0];
+ argv += 2;
}
/*
- * Otherwise, argv[i] could be either <rev> or <paths> and
- * has to be unambiguous.
+ * Otherwise, argv[0] could be either <rev> or <paths> and
+ * has to be unambiguous. If there is a single argument, it
+ * can not be a tree
*/
- else if (!get_sha1(argv[i], sha1)) {
+ else if ((!argv[1] && !get_sha1_committish(argv[0], unused)) ||
+ (argv[1] && !get_sha1_treeish(argv[0], unused))) {
/*
- * Ok, argv[i] looks like a rev; it should not
+ * Ok, argv[0] looks like a commit/tree; it should not
* be a filename.
*/
- verify_non_filename(prefix, argv[i]);
- rev = argv[i++];
+ verify_non_filename(prefix, argv[0]);
+ rev = *argv++;
} else {
/* Otherwise we treat this as a filename */
- verify_filename(prefix, argv[i]);
+ verify_filename(prefix, argv[0], 1);
}
}
+ *rev_ret = rev;
- if (get_sha1(rev, sha1))
- die(_("Failed to resolve '%s' as a valid ref."), rev);
+ if (read_cache() < 0)
+ die(_("index file corrupt"));
- commit = lookup_commit_reference(sha1);
- if (!commit)
- die(_("Could not parse object '%s'."), rev);
- hashcpy(sha1, commit->object.sha1);
+ parse_pathspec(pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP |
+ (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0),
+ prefix, argv);
+}
+
+static int reset_refs(const char *rev, const unsigned char *sha1)
+{
+ int update_ref_status;
+ struct strbuf msg = STRBUF_INIT;
+ unsigned char *orig = NULL, sha1_orig[20],
+ *old_orig = NULL, sha1_old_orig[20];
+
+ if (!get_sha1("ORIG_HEAD", sha1_old_orig))
+ old_orig = sha1_old_orig;
+ if (!get_sha1("HEAD", sha1_orig)) {
+ orig = sha1_orig;
+ set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
+ update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
+ UPDATE_REFS_MSG_ON_ERR);
+ } else if (old_orig)
+ delete_ref("ORIG_HEAD", old_orig, 0);
+ set_reflog_message(&msg, "updating HEAD", rev);
+ update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0,
+ UPDATE_REFS_MSG_ON_ERR);
+ strbuf_release(&msg);
+ return update_ref_status;
+}
+
+int cmd_reset(int argc, const char **argv, const char *prefix)
+{
+ int reset_type = NONE, update_ref_status = 0, quiet = 0;
+ int patch_mode = 0, unborn;
+ const char *rev;
+ unsigned char sha1[20];
+ 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,
+ N_("reset HEAD and index"), MIXED),
+ OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT),
+ OPT_SET_INT(0, "hard", &reset_type,
+ N_("reset HEAD, index and working tree"), HARD),
+ OPT_SET_INT(0, "merge", &reset_type,
+ N_("reset HEAD, index and working tree"), MERGE),
+ OPT_SET_INT(0, "keep", &reset_type,
+ N_("reset HEAD but keep local changes"), KEEP),
+ 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()
+ };
+
+ git_config(git_default_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, options, git_reset_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+ 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.nr) {
+ struct commit *commit;
+ if (get_sha1_committish(rev, sha1))
+ die(_("Failed to resolve '%s' as a valid revision."), rev);
+ commit = lookup_commit_reference(sha1);
+ if (!commit)
+ die(_("Could not parse object '%s'."), rev);
+ hashcpy(sha1, commit->object.sha1);
+ } else {
+ struct tree *tree;
+ if (get_sha1_treeish(rev, sha1))
+ die(_("Failed to resolve '%s' as a valid tree."), rev);
+ tree = parse_tree_indirect(sha1);
+ if (!tree)
+ die(_("Could not parse object '%s'."), rev);
+ hashcpy(sha1, tree->object.sha1);
+ }
if (patch_mode) {
if (reset_type != NONE)
die(_("--patch is incompatible with --{hard,mixed,soft}"));
- return interactive_reset(rev, argv + i, prefix);
+ 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 (i < argc) {
+ if (pathspec.nr) {
if (reset_type == MIXED)
warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
else if (reset_type != NONE)
die(_("Cannot do %s reset with paths."),
_(reset_type_names[reset_type]));
- return read_from_tree(prefix, argv + i, sha1,
- quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
}
if (reset_type == NONE)
reset_type = MIXED; /* by default */
- if (reset_type != SOFT && reset_type != MIXED)
+ if (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))
setup_work_tree();
if (reset_type == MIXED && is_bare_repository())
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. */
- if (reset_type == SOFT)
+ if (reset_type == SOFT || reset_type == KEEP)
die_if_unmerged_cache(reset_type);
- else {
- int err;
- if (reset_type == KEEP)
- die_if_unmerged_cache(reset_type);
- err = reset_index_file(sha1, reset_type, quiet);
- if (reset_type == KEEP)
- err = err || reset_index_file(sha1, MIXED, quiet);
- if (err)
- die(_("Could not reset index file to revision '%s'."), rev);
- }
- /* Any resets update HEAD to the head being switched to,
- * saving the previous head in ORIG_HEAD before. */
- if (!get_sha1("ORIG_HEAD", sha1_old_orig))
- old_orig = sha1_old_orig;
- if (!get_sha1("HEAD", sha1_orig)) {
- orig = sha1_orig;
- set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
- update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
- }
- else if (old_orig)
- delete_ref("ORIG_HEAD", old_orig, 0);
- set_reflog_message(&msg, "updating HEAD", rev);
- update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+ if (reset_type != SOFT) {
+ struct lock_file *lock = xcalloc(1, sizeof(*lock));
+ hold_locked_index(lock, 1);
+ if (reset_type == MIXED) {
+ int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
+ if (read_from_tree(&pathspec, sha1, intent_to_add))
+ return 1;
+ if (get_git_work_tree())
+ 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)
+ err = reset_index(sha1, MIXED, quiet);
+ if (err)
+ die(_("Could not reset index file to revision '%s'."), rev);
+ }
- switch (reset_type) {
- case HARD:
- if (!update_ref_status && !quiet)
- print_new_head_line(commit);
- break;
- case SOFT: /* Nothing else to do. */
- break;
- case MIXED: /* Report what has not been updated. */
- update_index_refresh(0, NULL,
- quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN);
- break;
+ if (write_locked_index(&the_index, lock, COMMIT_LOCK))
+ die(_("Could not write new index file."));
}
- remove_branch_state();
+ 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 = reset_refs(rev, sha1);
- strbuf_release(&msg);
+ if (reset_type == HARD && !update_ref_status && !quiet)
+ print_new_head_line(lookup_commit_reference(sha1));
+ }
+ if (!pathspec.nr)
+ remove_branch_state();
return update_ref_status;
}
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 56727e8..ff84a82 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"
@@ -52,6 +54,11 @@ static void show_commit(struct commit *commit, void *data)
struct rev_list_info *info = data;
struct rev_info *revs = info->revs;
+ if (info->flags & REV_LIST_QUIET) {
+ finish_commit(commit, data);
+ return;
+ }
+
graph_show_commit(revs->graph);
if (revs->count) {
@@ -99,12 +106,14 @@ static void show_commit(struct commit *commit, void *data)
else
putchar('\n');
- if (revs->verbose_header && commit->buffer) {
+ if (revs->verbose_header && get_cached_commit_buffer(commit, NULL)) {
struct strbuf buf = STRBUF_INIT;
struct pretty_print_context ctx = {0};
ctx.abbrev = revs->abbrev;
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) {
@@ -164,33 +173,29 @@ static void finish_commit(struct commit *commit, void *data)
free_commit_list(commit->parents);
commit->parents = NULL;
}
- free(commit->buffer);
- commit->buffer = NULL;
+ free_commit_buffer(commit);
}
-static void finish_object(struct object *obj, const struct name_path *path, const char *name)
+static void finish_object(struct object *obj,
+ const struct name_path *path, const char *name,
+ void *cb_data)
{
+ struct rev_list_info *info = cb_data;
if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
die("missing blob object '%s'", sha1_to_hex(obj->sha1));
+ if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
+ parse_object(obj->sha1);
}
-static void show_object(struct object *obj, const struct name_path *path, const char *component)
+static void show_object(struct object *obj,
+ const struct name_path *path, const char *component,
+ void *cb_data)
{
- char *name = path_name(path, component);
- /* An object with name "foo\n0000000..." can be used to
- * confuse downstream "git pack-objects" very badly.
- */
- const char *ep = strchr(name, '\n');
-
- finish_object(obj, path, name);
- if (ep) {
- printf("%s %.*s\n", sha1_to_hex(obj->sha1),
- (int) (ep - name),
- name);
- }
- else
- printf("%s %s\n", sha1_to_hex(obj->sha1), name);
- free(name);
+ struct rev_list_info *info = cb_data;
+ finish_object(obj, path, component, cb_data);
+ if (info->flags & REV_LIST_QUIET)
+ return;
+ show_object_with_name(stdout, obj, path, component);
}
static void show_edge(struct commit *commit)
@@ -198,62 +203,6 @@ static void show_edge(struct commit *commit)
printf("-%s\n", sha1_to_hex(commit->object.sha1));
}
-static inline int log2i(int n)
-{
- int log2 = 0;
-
- for (; n > 1; n >>= 1)
- log2++;
-
- return log2;
-}
-
-static inline int exp2i(int n)
-{
- return 1 << n;
-}
-
-/*
- * Estimate the number of bisect steps left (after the current step)
- *
- * For any x between 0 included and 2^n excluded, the probability for
- * n - 1 steps left looks like:
- *
- * P(2^n + x) == (2^n - x) / (2^n + x)
- *
- * and P(2^n + x) < 0.5 means 2^n < 3x
- */
-int estimate_bisect_steps(int all)
-{
- int n, x, e;
-
- if (all < 3)
- return 0;
-
- n = log2i(all);
- e = exp2i(n);
- x = all - e;
-
- return (e < 3 * x) ? n : n - 1;
-}
-
-void print_commit_list(struct commit_list *list,
- const char *format_cur,
- const char *format_last)
-{
- for ( ; list; list = list->next) {
- const char *format = list->next ? format_cur : format_last;
- printf(format, sha1_to_hex(list->item->object.sha1));
- }
-}
-
-static void show_tried_revs(struct commit_list *tried)
-{
- printf("bisect_tried='");
- print_commit_list(tried, "%s|", "%s");
- printf("'\n");
-}
-
static void print_var_str(const char *var, const char *val)
{
printf("%s='%s'\n", var, val);
@@ -266,12 +215,12 @@ static void print_var_int(const char *var, int val)
static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
{
- int cnt, flags = info->bisect_show_flags;
+ int cnt, flags = info->flags;
char hex[41] = "";
struct commit_list *tried;
struct rev_info *revs = info->revs;
- if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
+ if (!revs->commits)
return 1;
revs->commits = filter_skipped(revs->commits, &tried,
@@ -299,9 +248,6 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
printf("------\n");
}
- if (flags & BISECT_SHOW_TRIED)
- show_tried_revs(tried);
-
print_var_str("bisect_rev", hex);
print_var_int("bisect_nr", cnt - 1);
print_var_int("bisect_good", all - reaches - 1);
@@ -312,6 +258,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;
@@ -320,7 +278,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 quiet = 0;
+ int use_bitmap_index = 0;
git_config(git_default_config, NULL);
init_revisions(&revs, prefix);
@@ -333,7 +291,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (revs.bisect)
bisect_list = 1;
- quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
+ if (DIFF_OPT_TST(&revs.diffopt, QUICK))
+ info.flags |= REV_LIST_QUIET;
for (i = 1 ; i < argc; i++) {
const char *arg = argv[i];
@@ -352,7 +311,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--bisect-all")) {
bisect_list = 1;
bisect_find_all = 1;
- info.bisect_show_flags = BISECT_SHOW_ALL;
+ info.flags |= BISECT_SHOW_ALL;
revs.show_decorations = 1;
continue;
}
@@ -361,6 +320,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);
}
@@ -377,7 +344,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);
@@ -388,10 +355,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;
@@ -403,10 +386,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
return show_bisect_vars(&info, reaches, all);
}
- traverse_commit_list(&revs,
- quiet ? finish_commit : show_commit,
- quiet ? finish_object : show_object,
- &info);
+ traverse_commit_list(&revs, show_commit, show_object, &info);
if (revs.count) {
if (revs.left_right && revs.cherry_mark)
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 4c19f84..95328b8 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -9,6 +9,9 @@
#include "quote.h"
#include "builtin.h"
#include "parse-options.h"
+#include "diff.h"
+#include "revision.h"
+#include "split-index.h"
#define DO_REVS 1
#define DO_NOREV 2
@@ -30,6 +33,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.
@@ -145,6 +151,7 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
error("refname '%s' is ambiguous", name);
break;
}
+ free(full);
} else {
show_with_type(type, name);
}
@@ -185,6 +192,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;
}
@@ -195,6 +204,12 @@ static int anti_reference(const char *refname, const unsigned char *sha1, int fl
return 0;
}
+static int show_abbrev(const unsigned char *sha1, void *cb_data)
+{
+ show_rev(NORMAL, sha1, NULL);
+ return 0;
+}
+
static void show_datestring(const char *flag, const char *datestr)
{
static char buffer[100];
@@ -206,11 +221,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;
@@ -224,6 +245,7 @@ static int try_difference(const char *arg)
const char *next;
const char *this;
int symmetric;
+ static const char head_by_default[] = "HEAD";
if (!(dotdot = strstr(arg, "..")))
return 0;
@@ -235,10 +257,21 @@ static int try_difference(const char *arg)
next += symmetric;
if (!*next)
- next = "HEAD";
+ next = head_by_default;
if (dotdot == arg)
- this = "HEAD";
- if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
+ this = head_by_default;
+
+ if (this == head_by_default && next == head_by_default &&
+ !symmetric) {
+ /*
+ * Just ".."? That is not a range but the
+ * pathspec for the parent directory.
+ */
+ *dotdot = '.';
+ return 0;
+ }
+
+ if (!get_sha1_committish(this, sha1) && !get_sha1_committish(next, end)) {
show_rev(NORMAL, end, next);
show_rev(symmetric ? NORMAL : REVERSED, sha1, this);
if (symmetric) {
@@ -246,7 +279,7 @@ static int try_difference(const char *arg)
struct commit *a, *b;
a = lookup_commit_reference(sha1);
b = lookup_commit_reference(end);
- exclude = get_merge_bases(a, b, 1);
+ exclude = get_merge_bases(a, b);
while (exclude) {
struct commit_list *n = exclude->next;
show_rev(REVERSED,
@@ -255,6 +288,7 @@ static int try_difference(const char *arg)
exclude = n;
}
}
+ *dotdot = '.';
return 1;
}
*dotdot = '.';
@@ -278,8 +312,10 @@ static int try_parent_shorthands(const char *arg)
return 0;
*dotdot = 0;
- if (get_sha1(arg, sha1))
+ if (get_sha1_committish(arg, sha1)) {
+ *dotdot = '^';
return 0;
+ }
if (!parents_only)
show_rev(NORMAL, sha1, arg);
@@ -288,6 +324,7 @@ static int try_parent_shorthands(const char *arg)
show_rev(parents_only ? NORMAL : REVERSED,
parents->item->object.sha1, arg);
+ *dotdot = '^';
return 1;
}
@@ -296,12 +333,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;
@@ -318,15 +358,17 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
{
static int keep_dashdash = 0, stop_at_non_option = 0;
static char const * const parseopt_usage[] = {
- "git rev-parse --parseopt [options] -- [<args>...]",
+ N_("git rev-parse --parseopt [options] -- [<args>...]"),
NULL
};
static struct option parseopt_opts[] = {
- OPT_BOOLEAN(0, "keep-dashdash", &keep_dashdash,
- "keep the `--` passed as an arg"),
- OPT_BOOLEAN(0, "stop-at-non-option", &stop_at_non_option,
- "stop parsing after the "
- "first non-option argument"),
+ OPT_BOOL(0, "keep-dashdash", &keep_dashdash,
+ N_("keep the `--` passed as an arg")),
+ 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(),
};
@@ -355,9 +397,10 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
usage[unb++] = strbuf_detach(&sb, NULL);
}
- /* parse: (<short>|<short>,<long>|<long>)[=?]? SP+ <help> */
+ /* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */
while (strbuf_getline(&sb, stdin, '\n') != EOF) {
const char *s;
+ const char *end;
struct option *o;
if (!sb.len)
@@ -379,6 +422,16 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
o->value = &parsed;
o->flags = PARSE_OPT_NOARG;
o->callback = &parseopt_dump;
+
+ /* Possible argument name hint */
+ end = s;
+ while (s > sb.buf && strchr("*=?!", s[-1]) == NULL)
+ --s;
+ if (s != sb.buf && s != end)
+ o->argh = xmemdupz(s, end - s);
+ if (s == sb.buf)
+ s = end;
+
while (s > sb.buf && strchr("*=?!", s[-1])) {
switch (*--s) {
case '=':
@@ -443,17 +496,21 @@ static void die_no_single_rev(int quiet)
}
static const char builtin_rev_parse_usage[] =
-"git rev-parse --parseopt [options] -- [<args>...]\n"
-" or: git rev-parse --sq-quote [<arg>...]\n"
-" or: git rev-parse [options] [<arg>...]\n"
-"\n"
-"Run \"git rev-parse --parseopt -h\" for more information on the first usage.";
+N_("git rev-parse --parseopt [options] -- [<args>...]\n"
+ " or: git rev-parse --sq-quote [<arg>...]\n"
+ " or: git rev-parse [options] [<arg>...]\n"
+ "\n"
+ "Run \"git rev-parse --parseopt -h\" for more information on the first usage.");
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];
+ unsigned int flags = 0;
const char *name = NULL;
+ struct object_context unused;
if (argc > 1 && !strcmp("--parseopt", argv[1]))
return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -461,24 +518,24 @@ 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 > 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)
- verify_filename(prefix, arg);
+ if (show_file(arg, output_prefix) && as_is < 2)
+ verify_filename(prefix, arg, 0);
continue;
}
if (!strcmp(arg,"-n")) {
@@ -490,7 +547,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;
@@ -501,12 +558,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")) {
@@ -532,10 +598,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
}
if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) {
quiet = 1;
+ flags |= GET_SHA1_QUIETLY;
continue;
}
if (!strcmp(arg, "--short") ||
- !prefixcmp(arg, "--short=")) {
+ starts_with(arg, "--short=")) {
filter &= ~(DO_FLAGS|DO_NOREV);
verify = 1;
abbrev = DEFAULT_ABBREV;
@@ -563,7 +630,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;
@@ -581,40 +648,61 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
for_each_ref(show_reference, NULL);
continue;
}
+ if (starts_with(arg, "--disambiguate=")) {
+ for_each_abbrev(arg + 15, show_abbrev, NULL);
+ continue;
+ }
if (!strcmp(arg, "--bisect")) {
for_each_ref_in("refs/bisect/bad", show_reference, NULL);
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")) {
@@ -626,6 +714,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--show-prefix")) {
if (prefix)
puts(prefix);
+ else
+ putchar('\n');
continue;
}
if (!strcmp(arg, "--show-cdup")) {
@@ -649,7 +739,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
}
if (!strcmp(arg, "--git-dir")) {
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
- static char cwd[PATH_MAX];
+ char *cwd;
int len;
if (gitdir) {
puts(gitdir);
@@ -659,10 +749,20 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
puts(".git");
continue;
}
- if (!getcwd(cwd, PATH_MAX))
- die_errno("unable to get current working directory");
+ cwd = xgetcwd();
len = strlen(cwd);
printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
+ free(cwd);
+ 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")) {
@@ -680,19 +780,28 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
: "false");
continue;
}
- if (!prefixcmp(arg, "--since=")) {
+ if (!strcmp(arg, "--shared-index-path")) {
+ if (read_cache() < 0)
+ die(_("Could not read the index"));
+ if (the_index.split_index) {
+ const unsigned char *sha1 = the_index.split_index->base_sha1;
+ puts(git_path("sharedindex.%s", sha1_to_hex(sha1)));
+ }
+ continue;
+ }
+ 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;
}
@@ -712,7 +821,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
name++;
type = REVERSED;
}
- if (!get_sha1(name, sha1)) {
+ if (!get_sha1_with_context(name, flags, sha1, &unused)) {
if (verify)
revs_count++;
else
@@ -721,10 +830,12 @@ 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);
+ verify_filename(prefix, arg, 1);
}
if (verify) {
if (revs_count == 1) {
diff --git a/builtin/revert.c b/builtin/revert.c
index 3117776..f9ed5bd 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -1,18 +1,11 @@
#include "cache.h"
#include "builtin.h"
-#include "object.h"
-#include "commit.h"
-#include "tag.h"
-#include "run-command.h"
-#include "exec_cmd.h"
-#include "utf8.h"
#include "parse-options.h"
-#include "cache-tree.h"
#include "diff.h"
#include "revision.h"
#include "rerere.h"
-#include "merge-recursive.h"
-#include "refs.h"
+#include "dir.h"
+#include "sequencer.h"
/*
* This implements the builtins revert and cherry-pick.
@@ -26,575 +19,196 @@
*/
static const char * const revert_usage[] = {
- "git revert [options] <commit-ish>",
+ N_("git revert [options] <commit-ish>..."),
+ N_("git revert <subcommand>"),
NULL
};
static const char * const cherry_pick_usage[] = {
- "git cherry-pick [options] <commit-ish>",
+ N_("git cherry-pick [options] <commit-ish>..."),
+ N_("git cherry-pick <subcommand>"),
NULL
};
-static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
-static enum { REVERT, CHERRY_PICK } action;
-static struct commit *commit;
-static int commit_argc;
-static const char **commit_argv;
-static int allow_rerere_auto;
-
-static const char *me;
-
-/* Merge strategy. */
-static const char *strategy;
-static const char **xopts;
-static size_t xopts_nr, xopts_alloc;
-
-#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-
-static char *get_encoding(const char *message);
+static const char *action_name(const struct replay_opts *opts)
+{
+ return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
+}
-static const char * const *revert_or_cherry_pick_usage(void)
+static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
{
- return action == REVERT ? revert_usage : cherry_pick_usage;
+ return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage;
}
static int option_parse_x(const struct option *opt,
const char *arg, int unset)
{
+ struct replay_opts **opts_ptr = opt->value;
+ struct replay_opts *opts = *opts_ptr;
+
if (unset)
return 0;
- ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
- xopts[xopts_nr++] = xstrdup(arg);
+ ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+ opts->xopts[opts->xopts_nr++] = xstrdup(arg);
return 0;
}
-static void parse_args(int argc, const char **argv)
+LAST_ARG_MUST_BE_NULL
+static void verify_opt_compatible(const char *me, const char *base_opt, ...)
+{
+ const char *this_opt;
+ va_list ap;
+
+ va_start(ap, base_opt);
+ while ((this_opt = va_arg(ap, const char *))) {
+ if (va_arg(ap, int))
+ break;
+ }
+ va_end(ap);
+
+ if (this_opt)
+ die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
+}
+
+static void parse_args(int argc, const char **argv, struct replay_opts *opts)
{
- const char * const * usage_str = revert_or_cherry_pick_usage();
- int noop;
+ const char * const * usage_str = revert_or_cherry_pick_usage(opts);
+ const char *me = action_name(opts);
+ int cmd = 0;
struct option options[] = {
- OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
- OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
- { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
- OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
- OPT_INTEGER('m', "mainline", &mainline, "parent number"),
- OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
- OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
- OPT_CALLBACK('X', "strategy-option", &xopts, "option",
- "option for merge strategy", option_parse_x),
+ 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_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(),
OPT_END(),
OPT_END(),
OPT_END(),
};
- if (action == CHERRY_PICK) {
+ if (opts->action == REPLAY_PICK) {
struct option cp_extra[] = {
- OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
- OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
+ 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))
die(_("program error"));
}
- commit_argc = parse_options(argc, argv, NULL, options, usage_str,
- PARSE_OPT_KEEP_ARGV0 |
- PARSE_OPT_KEEP_UNKNOWN);
- if (commit_argc < 2)
- usage_with_options(usage_str, options);
-
- commit_argv = argv;
-}
-
-struct commit_message {
- char *parent_label;
- const char *label;
- const char *subject;
- char *reencoded_message;
- const char *message;
-};
-
-static int get_message(const char *raw_message, struct commit_message *out)
-{
- const char *encoding;
- const char *abbrev, *subject;
- int abbrev_len, subject_len;
- char *q;
-
- if (!raw_message)
- return -1;
- encoding = get_encoding(raw_message);
- if (!encoding)
- encoding = "UTF-8";
- if (!git_commit_encoding)
- git_commit_encoding = "UTF-8";
-
- out->reencoded_message = NULL;
- out->message = raw_message;
- if (strcmp(encoding, git_commit_encoding))
- out->reencoded_message = reencode_string(raw_message,
- git_commit_encoding, encoding);
- if (out->reencoded_message)
- out->message = out->reencoded_message;
-
- abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
- abbrev_len = strlen(abbrev);
-
- subject_len = find_commit_subject(out->message, &subject);
-
- out->parent_label = xmalloc(strlen("parent of ") + abbrev_len +
- strlen("... ") + subject_len + 1);
- q = out->parent_label;
- q = mempcpy(q, "parent of ", strlen("parent of "));
- out->label = q;
- q = mempcpy(q, abbrev, abbrev_len);
- q = mempcpy(q, "... ", strlen("... "));
- out->subject = q;
- q = mempcpy(q, subject, subject_len);
- *q = '\0';
- return 0;
-}
-
-static void free_message(struct commit_message *msg)
-{
- free(msg->parent_label);
- free(msg->reencoded_message);
-}
-
-static char *get_encoding(const char *message)
-{
- const char *p = message, *eol;
-
- if (!p)
- die (_("Could not read commit message of %s"),
- sha1_to_hex(commit->object.sha1));
- while (*p && *p != '\n') {
- for (eol = p + 1; *eol && *eol != '\n'; eol++)
- ; /* do nothing */
- if (!prefixcmp(p, "encoding ")) {
- char *result = xmalloc(eol - 8 - p);
- strlcpy(result, p + 9, eol - 8 - p);
- return result;
+ argc = parse_options(argc, argv, NULL, options, usage_str,
+ PARSE_OPT_KEEP_ARGV0 |
+ PARSE_OPT_KEEP_UNKNOWN);
+
+ /* implies allow_empty */
+ if (opts->keep_redundant_commits)
+ opts->allow_empty = 1;
+
+ /* Set the subcommand */
+ if (cmd == 'q')
+ opts->subcommand = REPLAY_REMOVE_STATE;
+ else if (cmd == 'c')
+ opts->subcommand = REPLAY_CONTINUE;
+ else if (cmd == 'a')
+ opts->subcommand = REPLAY_ROLLBACK;
+ else
+ opts->subcommand = REPLAY_NONE;
+
+ /* Check for incompatible command line arguments */
+ if (opts->subcommand != REPLAY_NONE) {
+ char *this_operation;
+ if (opts->subcommand == REPLAY_REMOVE_STATE)
+ this_operation = "--quit";
+ else if (opts->subcommand == REPLAY_CONTINUE)
+ this_operation = "--continue";
+ else {
+ assert(opts->subcommand == REPLAY_ROLLBACK);
+ this_operation = "--abort";
}
- p = eol;
- if (*p == '\n')
- p++;
- }
- return NULL;
-}
-static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
-{
- const char *p = message;
- while (*p && (*p != '\n' || p[1] != '\n'))
- p++;
-
- if (!*p)
- strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1));
-
- p += 2;
- strbuf_addstr(msgbuf, p);
-}
-
-static void write_cherry_pick_head(void)
-{
- int fd;
- struct strbuf buf = STRBUF_INIT;
-
- strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
-
- fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno(_("Could not open '%s' for writing"),
- git_path("CHERRY_PICK_HEAD"));
- if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
- die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD"));
- strbuf_release(&buf);
-}
-
-static void advise(const char *advice, ...)
-{
- va_list params;
-
- va_start(params, advice);
- vreportf("hint: ", advice, params);
- va_end(params);
-}
-
-static void print_advice(void)
-{
- char *msg = getenv("GIT_CHERRY_PICK_HELP");
-
- if (msg) {
- fprintf(stderr, "%s\n", msg);
- /*
- * A conflict has occured but the porcelain
- * (typically rebase --interactive) wants to take care
- * of the commit itself so remove CHERRY_PICK_HEAD
- */
- unlink(git_path("CHERRY_PICK_HEAD"));
- return;
+ verify_opt_compatible(me, this_operation,
+ "--no-commit", opts->no_commit,
+ "--signoff", opts->signoff,
+ "--mainline", opts->mainline,
+ "--strategy", opts->strategy ? 1 : 0,
+ "--strategy-option", opts->xopts ? 1 : 0,
+ "-x", opts->record_origin,
+ "--ff", opts->allow_ff,
+ NULL);
}
- advise("after resolving the conflicts, mark the corrected paths");
- advise("with 'git add <paths>' or 'git rm <paths>'");
- advise("and commit the result with 'git commit'");
-}
+ if (opts->allow_ff)
+ verify_opt_compatible(me, "--ff",
+ "--signoff", opts->signoff,
+ "--no-commit", opts->no_commit,
+ "-x", opts->record_origin,
+ "--edit", opts->edit,
+ NULL);
-static void write_message(struct strbuf *msgbuf, const char *filename)
-{
- static struct lock_file msg_file;
-
- int msg_fd = hold_lock_file_for_update(&msg_file, filename,
- LOCK_DIE_ON_ERROR);
- if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
- die_errno(_("Could not write to %s."), filename);
- strbuf_release(msgbuf);
- if (commit_lock_file(&msg_file) < 0)
- die(_("Error wrapping up %s"), filename);
-}
-
-static struct tree *empty_tree(void)
-{
- return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
-}
-
-static NORETURN void die_dirty_index(const char *me)
-{
- if (read_cache_unmerged()) {
- die_resolve_conflict(me);
+ if (opts->subcommand != REPLAY_NONE) {
+ opts->revs = NULL;
} else {
- if (advice_commit_before_merge) {
- if (action == REVERT)
- die(_("Your local changes would be overwritten by revert.\n"
- "Please, commit your changes or stash them to proceed."));
- else
- die(_("Your local changes would be overwritten by cherry-pick.\n"
- "Please, commit your changes or stash them to proceed."));
- } else {
- if (action == REVERT)
- die(_("Your local changes would be overwritten by revert.\n"));
- else
- die(_("Your local changes would be overwritten by cherry-pick.\n"));
- }
+ struct setup_revision_opt s_r_opt;
+ opts->revs = xmalloc(sizeof(*opts->revs));
+ init_revisions(opts->revs, NULL);
+ 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);
}
-}
-
-static int fast_forward_to(const unsigned char *to, const unsigned char *from)
-{
- struct ref_lock *ref_lock;
-
- read_cache();
- if (checkout_fast_forward(from, to))
- exit(1); /* the callee should have complained already */
- ref_lock = lock_any_ref_for_update("HEAD", from, 0);
- return write_ref_sha1(ref_lock, to, "cherry-pick");
-}
-
-static int do_recursive_merge(struct commit *base, struct commit *next,
- const char *base_label, const char *next_label,
- unsigned char *head, struct strbuf *msgbuf)
-{
- struct merge_options o;
- struct tree *result, *next_tree, *base_tree, *head_tree;
- int clean, index_fd;
- const char **xopt;
- static struct lock_file index_lock;
-
- index_fd = hold_locked_index(&index_lock, 1);
-
- read_cache();
-
- init_merge_options(&o);
- o.ancestor = base ? base_label : "(empty tree)";
- o.branch1 = "HEAD";
- o.branch2 = next ? next_label : "(empty tree)";
-
- head_tree = parse_tree_indirect(head);
- next_tree = next ? next->tree : empty_tree();
- base_tree = base ? base->tree : empty_tree();
-
- for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
- parse_merge_opt(&o, *xopt);
-
- clean = merge_trees(&o,
- head_tree,
- next_tree, base_tree, &result);
- if (active_cache_changed &&
- (write_cache(index_fd, active_cache, active_nr) ||
- commit_locked_index(&index_lock)))
- /* TRANSLATORS: %s will be "revert" or "cherry-pick" */
- die(_("%s: Unable to write new index file"), me);
- rollback_lock_file(&index_lock);
-
- if (!clean) {
- int i;
- strbuf_addstr(msgbuf, "\nConflicts:\n\n");
- for (i = 0; i < active_nr;) {
- struct cache_entry *ce = active_cache[i++];
- if (ce_stage(ce)) {
- strbuf_addch(msgbuf, '\t');
- strbuf_addstr(msgbuf, ce->name);
- strbuf_addch(msgbuf, '\n');
- while (i < active_nr && !strcmp(ce->name,
- active_cache[i]->name))
- i++;
- }
- }
- }
-
- return !clean;
-}
-
-/*
- * If we are cherry-pick, and if the merge did not result in
- * hand-editing, we will hit this commit and inherit the original
- * author date and name.
- * If we are revert, or if our cherry-pick results in a hand merge,
- * we had better say that the current user is responsible for that.
- */
-static int run_git_commit(const char *defmsg)
-{
- /* 6 is max possible length of our args array including NULL */
- const char *args[6];
- int i = 0;
-
- args[i++] = "commit";
- args[i++] = "-n";
- if (signoff)
- args[i++] = "-s";
- if (!edit) {
- args[i++] = "-F";
- args[i++] = defmsg;
- }
- args[i] = NULL;
-
- return run_command_v_opt(args, RUN_GIT_CMD);
+ if (argc > 1)
+ usage_with_options(usage_str, options);
}
-static int do_pick_commit(void)
+int cmd_revert(int argc, const char **argv, const char *prefix)
{
- unsigned char head[20];
- struct commit *base, *next, *parent;
- const char *base_label, *next_label;
- struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
- char *defmsg = NULL;
- struct strbuf msgbuf = STRBUF_INIT;
+ struct replay_opts opts;
int res;
- if (no_commit) {
- /*
- * We do not intend to commit immediately. We just want to
- * merge the differences in, so let's compute the tree
- * that represents the "current" state for merge-recursive
- * to work on.
- */
- if (write_cache_as_tree(head, 0, NULL))
- die (_("Your index file is unmerged."));
- } else {
- if (get_sha1("HEAD", head))
- die (_("You do not have a valid HEAD"));
- if (index_differs_from("HEAD", 0))
- die_dirty_index(me);
- }
- discard_cache();
-
- if (!commit->parents) {
- parent = NULL;
- }
- else if (commit->parents->next) {
- /* Reverting or cherry-picking a merge commit */
- int cnt;
- struct commit_list *p;
-
- if (!mainline)
- die(_("Commit %s is a merge but no -m option was given."),
- sha1_to_hex(commit->object.sha1));
-
- for (cnt = 1, p = commit->parents;
- cnt != mainline && p;
- cnt++)
- p = p->next;
- if (cnt != mainline || !p)
- die(_("Commit %s does not have parent %d"),
- sha1_to_hex(commit->object.sha1), mainline);
- parent = p->item;
- } else if (0 < mainline)
- die(_("Mainline was specified but commit %s is not a merge."),
- sha1_to_hex(commit->object.sha1));
- else
- parent = commit->parents->item;
-
- if (allow_ff && parent && !hashcmp(parent->object.sha1, head))
- return fast_forward_to(commit->object.sha1, head);
-
- if (parent && parse_commit(parent) < 0)
- /* TRANSLATORS: The first %s will be "revert" or
- "cherry-pick", the second %s a SHA1 */
- die(_("%s: cannot parse parent commit %s"),
- me, sha1_to_hex(parent->object.sha1));
-
- if (get_message(commit->buffer, &msg) != 0)
- die(_("Cannot get commit message for %s"),
- sha1_to_hex(commit->object.sha1));
-
- /*
- * "commit" is an existing commit. We would want to apply
- * the difference it introduces since its first parent "prev"
- * on top of the current HEAD if we are cherry-pick. Or the
- * reverse of it if we are revert.
- */
-
- defmsg = git_pathdup("MERGE_MSG");
-
- if (action == REVERT) {
- base = commit;
- base_label = msg.label;
- next = parent;
- next_label = msg.parent_label;
- strbuf_addstr(&msgbuf, "Revert \"");
- strbuf_addstr(&msgbuf, msg.subject);
- strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit ");
- strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
-
- if (commit->parents && commit->parents->next) {
- strbuf_addstr(&msgbuf, ", reversing\nchanges made to ");
- strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1));
- }
- strbuf_addstr(&msgbuf, ".\n");
- } else {
- base = parent;
- base_label = msg.parent_label;
- next = commit;
- next_label = msg.label;
- add_message_to_msg(&msgbuf, msg.message);
- if (no_replay) {
- strbuf_addstr(&msgbuf, "(cherry picked from commit ");
- strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
- strbuf_addstr(&msgbuf, ")\n");
- }
- if (!no_commit)
- write_cherry_pick_head();
- }
-
- if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
- res = do_recursive_merge(base, next, base_label, next_label,
- head, &msgbuf);
- write_message(&msgbuf, defmsg);
- } else {
- struct commit_list *common = NULL;
- struct commit_list *remotes = NULL;
-
- write_message(&msgbuf, defmsg);
-
- commit_list_insert(base, &common);
- commit_list_insert(next, &remotes);
- res = try_merge_command(strategy, xopts_nr, xopts, common,
- sha1_to_hex(head), remotes);
- free_commit_list(common);
- free_commit_list(remotes);
- }
-
- if (res) {
- error(action == REVERT
- ? _("could not revert %s... %s")
- : _("could not apply %s... %s"),
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
- msg.subject);
- print_advice();
- rerere(allow_rerere_auto);
- } else {
- if (!no_commit)
- res = run_git_commit(defmsg);
- }
-
- free_message(&msg);
- free(defmsg);
-
+ memset(&opts, 0, sizeof(opts));
+ if (isatty(0))
+ opts.edit = 1;
+ opts.action = REPLAY_REVERT;
+ git_config(git_default_config, NULL);
+ parse_args(argc, argv, &opts);
+ res = sequencer_pick_revisions(&opts);
+ if (res < 0)
+ die(_("revert failed"));
return res;
}
-static void prepare_revs(struct rev_info *revs)
-{
- int argc;
-
- init_revisions(revs, NULL);
- revs->no_walk = 1;
- if (action != REVERT)
- revs->reverse = 1;
-
- argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
- if (argc > 1)
- usage(*revert_or_cherry_pick_usage());
-
- if (prepare_revision_walk(revs))
- die(_("revision walk setup failed"));
-
- if (!revs->commits)
- die(_("empty commit set passed"));
-}
-
-static void read_and_refresh_cache(const char *me)
-{
- static struct lock_file index_lock;
- int index_fd = hold_locked_index(&index_lock, 0);
- if (read_index_preload(&the_index, NULL) < 0)
- die(_("git %s: failed to read the index"), me);
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
- if (the_index.cache_changed) {
- if (write_index(&the_index, index_fd) ||
- commit_locked_index(&index_lock))
- die(_("git %s: failed to refresh the index"), me);
- }
- rollback_lock_file(&index_lock);
-}
-
-static int revert_or_cherry_pick(int argc, const char **argv)
+int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
{
- struct rev_info revs;
+ struct replay_opts opts;
+ int res;
+ memset(&opts, 0, sizeof(opts));
+ opts.action = REPLAY_PICK;
git_config(git_default_config, NULL);
- me = action == REVERT ? "revert" : "cherry-pick";
- setenv(GIT_REFLOG_ACTION, me, 0);
- parse_args(argc, argv);
-
- if (allow_ff) {
- if (signoff)
- die(_("cherry-pick --ff cannot be used with --signoff"));
- if (no_commit)
- die(_("cherry-pick --ff cannot be used with --no-commit"));
- if (no_replay)
- die(_("cherry-pick --ff cannot be used with -x"));
- if (edit)
- die(_("cherry-pick --ff cannot be used with --edit"));
- }
-
- read_and_refresh_cache(me);
-
- prepare_revs(&revs);
-
- while ((commit = get_revision(&revs))) {
- int res = do_pick_commit();
- if (res)
- return res;
- }
-
- return 0;
-}
-
-int cmd_revert(int argc, const char **argv, const char *prefix)
-{
- if (isatty(0))
- edit = 1;
- action = REVERT;
- return revert_or_cherry_pick(argc, argv);
-}
-
-int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
-{
- action = CHERRY_PICK;
- return revert_or_cherry_pick(argc, argv);
+ parse_args(argc, argv, &opts);
+ res = sequencer_pick_revisions(&opts);
+ if (res < 0)
+ die(_("cherry-pick failed"));
+ return res;
}
diff --git a/builtin/rm.c b/builtin/rm.c
index 90c8a50..d8a9c86 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -3,23 +3,111 @@
*
* Copyright (C) Linus Torvalds 2006
*/
-#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "dir.h"
#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[] = {
- "git rm [options] [--] <file>...",
+ N_("git rm [options] [--] <file>..."),
NULL
};
static struct {
int nr, alloc;
- const char **name;
+ struct {
+ const char *name;
+ char is_submodule;
+ } *entry;
} list;
+static int get_ours_cache_pos(const char *path, int pos)
+{
+ int i = -pos - 1;
+
+ while ((i < active_nr) && !strcmp(active_cache[i]->name, path)) {
+ if (ce_stage(active_cache[i]) == 2)
+ return i;
+ i++;
+ }
+ 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 their 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;
+ const struct cache_entry *ce;
+ struct stat st;
+
+ pos = cache_name_pos(name, strlen(name));
+ if (pos < 0) {
+ pos = get_ours_cache_pos(name, pos);
+ if (pos < 0)
+ continue;
+ }
+ ce = active_cache[pos];
+
+ if (!S_ISGITLINK(ce->ce_mode) ||
+ (lstat(ce->name, &st) < 0) ||
+ is_empty_dir(name))
+ continue;
+
+ if (!submodule_uses_gitfile(name))
+ string_list_append(&files, name);
+ }
+
+ error_removing_concrete_submodules(&files, &errs);
+
+ return errs;
+}
+
static int check_local_mod(unsigned char *head, int index_only)
{
/*
@@ -31,25 +119,40 @@ 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 char *name = list.name[i];
+ const struct cache_entry *ce;
+ const char *name = list.entry[i].name;
unsigned char sha1[20];
unsigned mode;
int local_changes = 0;
int staged_changes = 0;
pos = cache_name_pos(name, strlen(name));
- if (pos < 0)
- continue; /* removing unmerged entry */
+ if (pos < 0) {
+ /*
+ * Skip unmerged entries except for populated submodules
+ * that could lose history when removed.
+ */
+ pos = get_ours_cache_pos(name, pos);
+ if (pos < 0)
+ continue;
+
+ if (!S_ISGITLINK(active_cache[pos]->ce_mode) ||
+ is_empty_dir(name))
+ continue;
+ }
ce = active_cache[pos];
if (lstat(ce->name, &st) < 0) {
- if (errno != ENOENT)
+ if (errno != ENOENT && errno != ENOTDIR)
warning("'%s': %s", ce->name, strerror(errno));
/* It already vanished from the working tree */
continue;
@@ -58,9 +161,10 @@ static int check_local_mod(unsigned char *head, int index_only)
/* if a file was removed and it is now a
* directory, that is the same as ENOENT as
* far as git is concerned; we do not track
- * directories.
+ * directories unless they are submodules.
*/
- continue;
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
}
/*
@@ -80,8 +184,11 @@ static int check_local_mod(unsigned char *head, int index_only)
/*
* Is the index different from the file in the work tree?
+ * If it's a submodule, is its work tree modified?
*/
- if (ce_match_stat(ce, &st, 0))
+ if (ce_match_stat(ce, &st, 0) ||
+ (S_ISGITLINK(ce->ce_mode) &&
+ !ok_to_remove_submodule(ce->name)))
local_changes = 1;
/*
@@ -106,21 +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);
- if (local_changes)
- errs = error(_("'%s' has local modifications\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))
+ 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;
}
@@ -130,22 +266,23 @@ static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
static int ignore_unmatch = 0;
static struct option builtin_rm_options[] = {
- OPT__DRY_RUN(&show_only, "dry run"),
- OPT__QUIET(&quiet, "do not list removed files"),
- OPT_BOOLEAN( 0 , "cached", &index_only, "only remove from the index"),
- OPT__FORCE(&force, "override the up-to-date check"),
- OPT_BOOLEAN('r', NULL, &recursive, "allow recursive removal"),
- OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
- "exit with a zero status even if nothing matched"),
+ OPT__DRY_RUN(&show_only, N_("dry run")),
+ OPT__QUIET(&quiet, N_("do not list removed files")),
+ OPT_BOOL( 0 , "cached", &index_only, N_("only remove from the index")),
+ OPT__FORCE(&force, N_("override the up-to-date check")),
+ 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(),
};
int cmd_rm(int argc, const char **argv, const char *prefix)
{
- int i, newfd;
- const char **pathspec;
+ int i;
+ struct pathspec pathspec;
char *seen;
+ gitmodules_config();
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_rm_options,
@@ -156,35 +293,40 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (!index_only)
setup_work_tree();
- newfd = hold_locked_index(&lock_file, 1);
+ hold_locked_index(&lock_file, 1);
if (read_cache() < 0)
die(_("index file corrupt"));
- 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.name, list.nr + 1, list.alloc);
- list.name[list.nr++] = ce->name;
+ ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
+ 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 {
@@ -192,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);
}
@@ -215,6 +357,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
hashclr(sha1);
if (check_local_mod(sha1, index_only))
exit(1);
+ } else if (!index_only) {
+ if (check_submodules_use_gitfiles())
+ exit(1);
}
/*
@@ -222,7 +367,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
* the index unless all of them succeed.
*/
for (i = 0; i < list.nr; i++) {
- const char *path = list.name[i];
+ const char *path = list.entry[i].name;
if (!quiet)
printf("rm '%s'\n", path);
@@ -242,9 +387,34 @@ 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.name[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 {
+ struct strbuf buf = STRBUF_INIT;
+ 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. */
+ }
+ }
if (!remove_path(path)) {
removed = 1;
continue;
@@ -252,11 +422,12 @@ 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) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
+ if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die(_("Unable to write new index file"));
}
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index c1f6ddd..b564a77 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -5,9 +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"
@@ -15,164 +18,6 @@ static const char send_pack_usage[] =
static struct send_pack_args args;
-static int feed_object(const unsigned char *sha1, int fd, int negative)
-{
- char buf[42];
-
- if (negative && !has_sha1_file(sha1))
- return 1;
-
- memcpy(buf + negative, sha1_to_hex(sha1), 40);
- if (negative)
- buf[0] = '^';
- buf[40 + negative] = '\n';
- return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs");
-}
-
-/*
- * Make a pack stream and spit it out into file descriptor fd
- */
-static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
-{
- /*
- * The child becomes pack-objects --revs; we feed
- * the revision parameters to it via its stdin and
- * let its stdout go back to the other end.
- */
- const char *argv[] = {
- "pack-objects",
- "--all-progress-implied",
- "--revs",
- "--stdout",
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- };
- struct child_process po;
- int i;
-
- i = 4;
- if (args->use_thin_pack)
- argv[i++] = "--thin";
- if (args->use_ofs_delta)
- argv[i++] = "--delta-base-offset";
- if (args->quiet)
- argv[i++] = "-q";
- if (args->progress)
- argv[i++] = "--progress";
- memset(&po, 0, sizeof(po));
- po.argv = argv;
- po.in = -1;
- po.out = args->stateless_rpc ? -1 : fd;
- po.git_cmd = 1;
- if (start_command(&po))
- die_errno("git pack-objects failed");
-
- /*
- * We feed the pack-objects we just spawned with revision
- * parameters by writing to the pipe.
- */
- for (i = 0; i < extra->nr; i++)
- if (!feed_object(extra->array[i], po.in, 1))
- break;
-
- while (refs) {
- if (!is_null_sha1(refs->old_sha1) &&
- !feed_object(refs->old_sha1, po.in, 1))
- break;
- if (!is_null_sha1(refs->new_sha1) &&
- !feed_object(refs->new_sha1, po.in, 0))
- break;
- refs = refs->next;
- }
-
- close(po.in);
-
- if (args->stateless_rpc) {
- char *buf = xmalloc(LARGE_PACKET_MAX);
- while (1) {
- ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
- if (n <= 0)
- break;
- send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
- }
- free(buf);
- close(po.out);
- po.out = -1;
- }
-
- if (finish_command(&po))
- return -1;
- return 0;
-}
-
-static int receive_status(int in, struct ref *refs)
-{
- struct ref *hint;
- char line[1000];
- int ret = 0;
- int len = packet_read_line(in, line, sizeof(line));
- if (len < 10 || memcmp(line, "unpack ", 7))
- return error("did not receive remote status");
- if (memcmp(line, "unpack ok\n", 10)) {
- char *p = line + strlen(line) - 1;
- if (*p == '\n')
- *p = '\0';
- error("unpack failed: %s", line + 7);
- ret = -1;
- }
- hint = NULL;
- while (1) {
- char *refname;
- char *msg;
- len = packet_read_line(in, line, sizeof(line));
- if (!len)
- break;
- if (len < 3 ||
- (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
- fprintf(stderr, "protocol error: %s\n", line);
- ret = -1;
- break;
- }
-
- line[strlen(line)-1] = '\0';
- refname = line + 3;
- msg = strchr(refname, ' ');
- if (msg)
- *msg++ = '\0';
-
- /* first try searching at our hint, falling back to all refs */
- if (hint)
- hint = find_ref_by_name(hint, refname);
- if (!hint)
- hint = find_ref_by_name(refs, refname);
- if (!hint) {
- warning("remote reported status on unknown ref: %s",
- refname);
- continue;
- }
- if (hint->status != REF_STATUS_EXPECTING_REPORT) {
- warning("remote reported status on unexpected ref: %s",
- refname);
- continue;
- }
-
- if (line[0] == 'o' && line[1] == 'k')
- hint->status = REF_STATUS_OK;
- else {
- hint->status = REF_STATUS_REMOTE_REJECT;
- ret = -1;
- }
- if (msg)
- hint->remote_status = xstrdup(msg);
- /* start our next search from the next ref */
- hint = hint->next;
- }
- return ret;
-}
-
static void print_helper_status(struct ref *ref)
{
struct strbuf buf = STRBUF_INIT;
@@ -201,6 +46,26 @@ static void print_helper_status(struct ref *ref)
msg = "non-fast forward";
break;
+ case REF_STATUS_REJECT_FETCH_FIRST:
+ res = "error";
+ msg = "fetch first";
+ break;
+
+ case REF_STATUS_REJECT_NEEDS_FORCE:
+ res = "error";
+ 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";
+ break;
+
case REF_STATUS_REJECT_NODELETE:
case REF_STATUS_REMOTE_REJECT:
res = "error";
@@ -221,173 +86,11 @@ static void print_helper_status(struct ref *ref)
}
strbuf_addch(&buf, '\n');
- safe_write(1, buf.buf, buf.len);
+ write_or_die(1, buf.buf, buf.len);
}
strbuf_release(&buf);
}
-static int sideband_demux(int in, int out, void *data)
-{
- int *fd = data, ret;
-#ifdef NO_PTHREADS
- close(fd[1]);
-#endif
- ret = recv_sideband("send-pack", fd[0], out);
- close(out);
- return ret;
-}
-
-int send_pack(struct send_pack_args *args,
- int fd[], struct child_process *conn,
- struct ref *remote_refs,
- struct extra_have_objects *extra_have)
-{
- int in = fd[0];
- int out = fd[1];
- struct strbuf req_buf = STRBUF_INIT;
- struct ref *ref;
- int new_refs;
- int allow_deleting_refs = 0;
- int status_report = 0;
- int use_sideband = 0;
- unsigned cmds_sent = 0;
- int ret;
- struct async demux;
-
- /* Does the other end support the reporting? */
- if (server_supports("report-status"))
- status_report = 1;
- if (server_supports("delete-refs"))
- allow_deleting_refs = 1;
- if (server_supports("ofs-delta"))
- args->use_ofs_delta = 1;
- if (server_supports("side-band-64k"))
- use_sideband = 1;
-
- if (!remote_refs) {
- fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
- "Perhaps you should specify a branch such as 'master'.\n");
- return 0;
- }
-
- /*
- * Finally, tell the other end!
- */
- new_refs = 0;
- for (ref = remote_refs; ref; ref = ref->next) {
- if (!ref->peer_ref && !args->send_mirror)
- continue;
-
- /* Check for statuses set by set_ref_status_for_push() */
- switch (ref->status) {
- case REF_STATUS_REJECT_NONFASTFORWARD:
- case REF_STATUS_UPTODATE:
- continue;
- default:
- ; /* do nothing */
- }
-
- if (ref->deletion && !allow_deleting_refs) {
- ref->status = REF_STATUS_REJECT_NODELETE;
- continue;
- }
-
- if (!ref->deletion)
- new_refs++;
-
- if (args->dry_run) {
- ref->status = REF_STATUS_OK;
- } else {
- char *old_hex = sha1_to_hex(ref->old_sha1);
- char *new_hex = sha1_to_hex(ref->new_sha1);
-
- if (!cmds_sent && (status_report || use_sideband)) {
- packet_buf_write(&req_buf, "%s %s %s%c%s%s",
- old_hex, new_hex, ref->name, 0,
- status_report ? " report-status" : "",
- use_sideband ? " side-band-64k" : "");
- }
- else
- packet_buf_write(&req_buf, "%s %s %s",
- old_hex, new_hex, ref->name);
- ref->status = status_report ?
- REF_STATUS_EXPECTING_REPORT :
- REF_STATUS_OK;
- cmds_sent++;
- }
- }
-
- if (args->stateless_rpc) {
- if (!args->dry_run && cmds_sent) {
- packet_buf_flush(&req_buf);
- send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
- }
- } else {
- safe_write(out, req_buf.buf, req_buf.len);
- packet_flush(out);
- }
- strbuf_release(&req_buf);
-
- if (use_sideband && cmds_sent) {
- memset(&demux, 0, sizeof(demux));
- demux.proc = sideband_demux;
- demux.data = fd;
- demux.out = -1;
- if (start_async(&demux))
- die("receive-pack: unable to fork off sideband demultiplexer");
- in = demux.out;
- }
-
- if (new_refs && cmds_sent) {
- if (pack_objects(out, remote_refs, extra_have, args) < 0) {
- for (ref = remote_refs; ref; ref = ref->next)
- ref->status = REF_STATUS_NONE;
- if (args->stateless_rpc)
- close(out);
- if (git_connection_is_socket(conn))
- shutdown(fd[0], SHUT_WR);
- if (use_sideband)
- finish_async(&demux);
- return -1;
- }
- }
- if (args->stateless_rpc && cmds_sent)
- packet_flush(out);
-
- if (status_report && cmds_sent)
- ret = receive_status(in, remote_refs);
- else
- ret = 0;
- if (args->stateless_rpc)
- packet_flush(out);
-
- if (use_sideband && cmds_sent) {
- if (finish_async(&demux)) {
- error("error in sideband demultiplexer");
- ret = -1;
- }
- close(demux.out);
- }
-
- if (ret < 0)
- return ret;
-
- if (args->porcelain)
- return 0;
-
- for (ref = remote_refs; ref; ref = ref->next) {
- switch (ref->status) {
- case REF_STATUS_NONE:
- case REF_STATUS_UPTODATE:
- case REF_STATUS_OK:
- break;
- default:
- return -1;
- }
- }
- return 0;
-}
-
int cmd_send_pack(int argc, const char **argv, const char *prefix)
{
int i, nr_refspecs = 0;
@@ -397,29 +100,33 @@ 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;
int send_all = 0;
const char *receivepack = "git-receive-pack";
int flags;
- int nonfastforward = 0;
+ unsigned int reject_reasons;
+ int progress = -1;
+ int from_stdin = 0;
+ 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;
}
@@ -439,10 +146,26 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.force_update = 1;
continue;
}
+ if (!strcmp(arg, "--quiet")) {
+ args.quiet = 1;
+ continue;
+ }
if (!strcmp(arg, "--verbose")) {
args.verbose = 1;
continue;
}
+ if (!strcmp(arg, "--signed")) {
+ args.push_cert = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--progress")) {
+ progress = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--no-progress")) {
+ progress = 0;
+ continue;
+ }
if (!strcmp(arg, "--thin")) {
args.use_thin_pack = 1;
continue;
@@ -451,10 +174,30 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.stateless_rpc = 1;
continue;
}
+ if (!strcmp(arg, "--stdin")) {
+ from_stdin = 1;
+ continue;
+ }
if (!strcmp(arg, "--helper-status")) {
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) {
@@ -467,6 +210,28 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
}
if (!dest)
usage(send_pack_usage);
+
+ if (from_stdin) {
+ struct argv_array all_refspecs = ARGV_ARRAY_INIT;
+
+ for (i = 0; i < nr_refspecs; i++)
+ argv_array_push(&all_refspecs, refspecs[i]);
+
+ if (args.stateless_rpc) {
+ const char *buf;
+ while ((buf = packet_read_line(0, NULL)))
+ argv_array_push(&all_refspecs, buf);
+ } else {
+ struct strbuf line = STRBUF_INIT;
+ while (strbuf_getline(&line, stdin, '\n') != EOF)
+ argv_array_push(&all_refspecs, line.buf);
+ strbuf_release(&line);
+ }
+
+ refspecs = all_refspecs.argv;
+ nr_refspecs = all_refspecs.argc;
+ }
+
/*
* --all and --mirror are incompatible; neither makes sense
* with any refspecs.
@@ -483,6 +248,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
}
}
+ if (progress == -1)
+ progress = !args.quiet && isatty(2);
+ args.progress = progress;
+
if (args.stateless_rpc) {
conn = NULL;
fd[0] = 0;
@@ -492,10 +261,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], &remote_refs, 0, NULL, 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);
@@ -509,9 +276,12 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
flags |= MATCH_REFS_MIRROR;
/* match them up */
- if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
+ 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);
@@ -526,7 +296,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
ret |= finish_connect(conn);
if (!helper_status)
- transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward);
+ transport_print_push_status(dest, remote_refs, args.verbose, 0, &reject_reasons);
if (!args.dry_run && remote) {
struct ref *ref;
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 37f3193..4b7e536 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -10,9 +10,7 @@
#include "parse-options.h"
static char const * const shortlog_usage[] = {
- "git shortlog [-n] [-s] [-e] [-w] [rev-opts] [--] [<commit-id>... ]",
- "",
- "[rev-opts] are documented in git-rev-list(1)",
+ N_("git shortlog [<options>] [<revision range>] [[--] [<path>...]]"),
NULL
};
@@ -36,52 +34,28 @@ static void insert_one_record(struct shortlog *log,
const char *dot3 = log->common_repo_prefix;
char *buffer, *p;
struct string_list_item *item;
- char namebuf[1024];
- char emailbuf[1024];
- size_t len;
+ const char *mailbuf, *namebuf;
+ size_t namelen, maillen;
const char *eol;
- const char *boemail, *eoemail;
struct strbuf subject = STRBUF_INIT;
+ struct strbuf namemailbuf = STRBUF_INIT;
+ struct ident_split ident;
- boemail = strchr(author, '<');
- if (!boemail)
- return;
- eoemail = strchr(boemail, '>');
- if (!eoemail)
+ if (split_ident_line(&ident, author, strlen(author)))
return;
- /* copy author name to namebuf, to support matching on both name and email */
- memcpy(namebuf, author, boemail - author);
- len = boemail - author;
- while (len > 0 && isspace(namebuf[len-1]))
- len--;
- namebuf[len] = 0;
-
- /* copy email name to emailbuf, to allow email replacement as well */
- memcpy(emailbuf, boemail+1, eoemail - boemail);
- emailbuf[eoemail - boemail - 1] = 0;
-
- if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
- while (author < boemail && isspace(*author))
- author++;
- for (len = 0;
- len < sizeof(namebuf) - 1 && author + len < boemail;
- len++)
- namebuf[len] = author[len];
- while (0 < len && isspace(namebuf[len-1]))
- len--;
- namebuf[len] = '\0';
- }
- else
- len = strlen(namebuf);
+ namebuf = ident.name_begin;
+ mailbuf = ident.mail_begin;
+ namelen = ident.name_end - ident.name_begin;
+ maillen = ident.mail_end - ident.mail_begin;
- if (log->email) {
- size_t room = sizeof(namebuf) - len - 1;
- int maillen = strlen(emailbuf);
- snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
- }
+ map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
+ strbuf_add(&namemailbuf, namebuf, namelen);
- item = string_list_insert(&log->list, namebuf);
+ if (log->email)
+ strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf);
+
+ item = string_list_insert(&log->list, namemailbuf.buf);
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct string_list));
@@ -91,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;
@@ -121,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')
@@ -149,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;
@@ -163,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) {
@@ -249,14 +226,14 @@ 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,
- "sort output according to the number of commits per author"),
- OPT_BOOLEAN('s', "summary", &log.summary,
- "Suppress commit descriptions, only provides commit count"),
- OPT_BOOLEAN('e', "email", &log.email,
- "Show the email address of each author"),
- { OPTION_CALLBACK, 'w', NULL, &log, "w[,i1[,i2]]",
- "Linewrap output", PARSE_OPT_OPTARG, &parse_wrap_args },
+ 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(),
};
@@ -306,9 +283,8 @@ parse_done:
static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s,
const struct shortlog *log)
{
- int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
- if (col != log->wrap)
- strbuf_addch(sb, '\n');
+ strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap);
+ strbuf_addch(sb, '\n');
}
void shortlog_output(struct shortlog *log)
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 4b480d7..365228a 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -6,8 +6,11 @@
#include "parse-options.h"
static const char* show_branch_usage[] = {
- "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=<when>] | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]",
- "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]",
+ N_("git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order]\n"
+ " [--current] [--color[=<when>] | --no-color] [--sparse]\n"
+ " [--more=<n> | --list | --independent | --merge-base]\n"
+ " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"),
+ N_("git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"),
NULL
};
@@ -162,29 +165,28 @@ static void name_commits(struct commit_list *list,
nth = 0;
while (parents) {
struct commit *p = parents->item;
- char newname[1000], *en;
+ struct strbuf newname = STRBUF_INIT;
parents = parents->next;
nth++;
if (p->util)
continue;
- en = newname;
switch (n->generation) {
case 0:
- en += sprintf(en, "%s", n->head_name);
+ strbuf_addstr(&newname, n->head_name);
break;
case 1:
- en += sprintf(en, "%s^", n->head_name);
+ strbuf_addf(&newname, "%s^", n->head_name);
break;
default:
- en += sprintf(en, "%s~%d",
- n->head_name, n->generation);
+ strbuf_addf(&newname, "%s~%d",
+ n->head_name, n->generation);
break;
}
if (nth == 1)
- en += sprintf(en, "^");
+ strbuf_addch(&newname, '^');
else
- en += sprintf(en, "^%d", nth);
- name_commit(p, xstrdup(newname), 0);
+ strbuf_addf(&newname, "^%d", nth);
+ name_commit(p, strbuf_detach(&newname, NULL), 0);
i++;
name_first_parent_chain(p);
}
@@ -228,8 +230,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;
@@ -286,7 +287,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) {
@@ -397,7 +398,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.
@@ -411,7 +412,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.
@@ -423,7 +424,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);
}
@@ -452,11 +453,11 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i
slash--;
if (!*tail)
return 0;
- if (fnmatch(match_ref_pattern, tail, 0))
+ if (wildmatch(match_ref_pattern, tail, 0, NULL))
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);
}
@@ -481,11 +482,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);
}
@@ -565,7 +566,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
default_arg[default_num++] = "show-branch";
} else if (default_alloc <= default_num + 1) {
default_alloc = default_alloc * 3 / 2 + 20;
- default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
+ REALLOC_ARRAY(default_arg, default_alloc);
}
default_arg[default_num++] = xstrdup(value);
default_arg[default_num] = NULL;
@@ -631,7 +632,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;
@@ -647,37 +648,39 @@ 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,
- "show remote-tracking and local branches"),
- OPT_BOOLEAN('r', "remotes", &all_remotes,
- "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,
- "color '*!+-' corresponding to the branch"),
- { OPTION_INTEGER, 0, "more", &extra, "n",
- "show <n> more commits after the common ancestor",
+ 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, "synonym to more=-1", -1),
- OPT_BOOLEAN(0, "no-name", &no_name, "suppress naming strings"),
- OPT_BOOLEAN(0, "current", &with_current_branch,
- "include the current branch"),
- OPT_BOOLEAN(0, "sha1-name", &sha1_name,
- "name commits with their object names"),
- OPT_BOOLEAN(0, "merge-base", &merge_base,
- "show possible merge bases"),
- OPT_BOOLEAN(0, "independent", &independent,
- "show refs unreachable from any other ref"),
- OPT_BOOLEAN(0, "topo-order", &lifo,
- "show commits in topological order"),
- OPT_BOOLEAN(0, "topics", &topics,
- "show only commits not on the first branch"),
+ OPT_SET_INT(0, "list", &extra, N_("synonym to more=-1"), -1),
+ 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_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,
- "show merges reachable from only one tip", 0),
- OPT_SET_INT(0, "date-order", &lifo,
- "show commits where no parent comes before its "
- "children", 0),
- { OPTION_CALLBACK, 'g', "reflog", &reflog_base, "<n>[,<base>]",
- "show <n> most recent ref-log entries starting at "
- "base",
+ N_("show merges reachable from only one tip"), 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"),
PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
parse_reflog_param },
OPT_END()
@@ -723,13 +726,14 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
char nth_desc[256];
char *ref;
int base = 0;
+ unsigned int flags = 0;
if (ac == 0) {
static const char *fake_av[2];
- const char *refname;
- refname = resolve_ref("HEAD", sha1, 1, NULL);
- fake_av[0] = xstrdup(refname);
+ fake_av[0] = resolve_refdup("HEAD",
+ RESOLVE_REF_READING,
+ sha1, NULL);
fake_av[1] = NULL;
av = fake_av;
ac = 1;
@@ -751,18 +755,18 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
/* Ah, that is a date spec... */
unsigned long at;
at = approxidate(reflog_base);
- read_ref_at(ref, at, -1, sha1, NULL,
+ read_ref_at(ref, flags, at, -1, sha1, NULL,
NULL, NULL, &base);
}
}
for (i = 0; i < reflog; i++) {
- char *logmsg, *m;
+ char *logmsg;
const char *msg;
unsigned long timestamp;
int tz;
- if (read_ref_at(ref, 0, base+i, sha1, &logmsg,
+ if (read_ref_at(ref, flags, 0, base+i, sha1, &logmsg,
&timestamp, &tz, NULL)) {
reflog = i;
break;
@@ -772,15 +776,14 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
msg = "(none)";
else
msg++;
- m = xmalloc(strlen(msg) + 200);
- sprintf(m, "(%s) %s",
- show_date(timestamp, tz, 1),
- msg);
- reflog_msg[i] = m;
+ reflog_msg[i] = xstrfmt("(%s) %s",
+ show_date(timestamp, tz, 1),
+ msg);
free(logmsg);
sprintf(nth_desc, "%s@{%d}", *av, base+i);
append_ref(nth_desc, sha1, 1);
}
+ free(ref);
}
else if (all_heads + all_remotes)
snarf_refs(all_heads, all_remotes);
@@ -791,7 +794,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
}
}
- head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+ head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+ head_sha1, NULL);
if (head_p) {
head_len = strlen(head_p);
memcpy(head, head_p, head_len + 1);
@@ -814,7 +818,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);
}
}
@@ -903,7 +907,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 45f0340..5ba1f30 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -7,8 +7,8 @@
#include "parse-options.h"
static const char * const show_ref_usage[] = {
- "git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] ",
- "git show-ref --exclude-existing[=pattern] < ref-list",
+ N_("git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] "),
+ N_("git show-ref --exclude-existing[=pattern] < ref-list"),
NULL
};
@@ -28,15 +28,17 @@ static void show_one(const char *refname, const unsigned char *sha1)
static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
{
- struct object *obj;
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;
}
@@ -79,25 +81,9 @@ match:
if (!deref_tags)
return 0;
- if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) {
- if (!is_null_sha1(peeled)) {
- hex = find_unique_abbrev(peeled, abbrev);
- printf("%s %s^{}\n", hex, refname);
- }
- }
- else {
- obj = parse_object(sha1);
- if (!obj)
- die("git show-ref: bad ref %s (%s)", refname,
- sha1_to_hex(sha1));
- if (obj->type == OBJ_TAG) {
- obj = deref_tag(obj, refname, 0);
- if (!obj)
- die("git show-ref: bad tag at ref %s (%s)", refname,
- sha1_to_hex(sha1));
- hex = find_unique_abbrev(obj->sha1, abbrev);
- printf("%s %s^{}\n", hex, refname);
- }
+ if (!peel_ref(refname, peeled)) {
+ hex = find_unique_abbrev(peeled, abbrev);
+ printf("%s %s^{}\n", hex, refname);
}
return 0;
}
@@ -120,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;
@@ -145,7 +131,7 @@ static int exclude_existing(const char *match)
if (strncmp(ref, match, matchlen))
continue;
}
- if (check_ref_format(ref)) {
+ if (check_refname_format(ref, 0)) {
warning("ref '%s' ignored", ref);
continue;
}
@@ -179,26 +165,26 @@ 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, "only show tags (can be combined with heads)"),
- OPT_BOOLEAN(0, "heads", &heads_only, "only show heads (can be combined with tags)"),
- OPT_BOOLEAN(0, "verify", &verify, "stricter reference checking, "
- "requires exact ref path"),
- { OPTION_BOOLEAN, 'h', NULL, &show_head, NULL,
- "show the HEAD reference",
- PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- OPT_BOOLEAN(0, "head", &show_head, "show the HEAD reference"),
- OPT_BOOLEAN('d', "dereference", &deref_tags,
- "dereference tags into object IDs"),
- { OPTION_CALLBACK, 's', "hash", &abbrev, "n",
- "only show SHA1 hash using <n> digits",
+ 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")),
+ 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"),
PARSE_OPT_OPTARG, &hash_callback },
OPT__ABBREV(&abbrev),
OPT__QUIET(&quiet,
- "do not print results to stdout (useful with --verify)"),
+ N_("do not print results to stdout (useful with --verify)")),
{ OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
- "pattern", "show refs from stdin that aren't in local repository",
+ N_("pattern"), N_("show refs from stdin that aren't in local repository"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
- { OPTION_CALLBACK, 0, "help-all", NULL, NULL, "show usage",
+ { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"),
PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
OPT_END()
};
@@ -224,8 +210,8 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
while (*pattern) {
unsigned char sha1[20];
- if (!prefixcmp(*pattern, "refs/") &&
- resolve_ref(*pattern, sha1, 1, NULL)) {
+ 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 4d3b93f..1259ed7 100644
--- a/builtin/stripspace.c
+++ b/builtin/stripspace.c
@@ -22,8 +22,6 @@ static size_t cleanup(char *line, size_t len)
* Remove empty lines from the beginning and end
* and also trailing spaces from every line.
*
- * Note that the buffer will not be NUL-terminated.
- *
* Turn multiple consecutive empty lines between paragraphs
* into just one empty line.
*
@@ -32,7 +30,8 @@ static size_t cleanup(char *line, size_t len)
*
* If last line does not have a newline at the end, one is added.
*
- * Enable skip_comments to skip every line starting with "#".
+ * Enable skip_comments to skip every line starting with comment
+ * character.
*/
void stripspace(struct strbuf *sb, int skip_comments)
{
@@ -47,7 +46,7 @@ void stripspace(struct strbuf *sb, int skip_comments)
eol = memchr(sb->buf + i, '\n', sb->len - i);
len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
- if (skip_comments && len && sb->buf[i] == '#') {
+ if (skip_comments && len && sb->buf[i] == comment_line_char) {
newlen = 0;
continue;
}
@@ -68,21 +67,53 @@ void stripspace(struct strbuf *sb, int skip_comments)
strbuf_setlen(sb, j);
}
+static void comment_lines(struct strbuf *buf)
+{
+ char *msg;
+ size_t len;
+
+ msg = strbuf_detach(buf, &len);
+ strbuf_add_commented_lines(buf, msg, len);
+ free(msg);
+}
+
+static const char *usage_msg = "\n"
+" git stripspace [-s | --strip-comments] < input\n"
+" git stripspace [-c | --comment-lines] < input";
+
int cmd_stripspace(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
int strip_comments = 0;
+ enum { INVAL = 0, STRIP_SPACE = 1, COMMENT_LINES = 2 } mode = STRIP_SPACE;
+
+ if (argc == 2) {
+ if (!strcmp(argv[1], "-s") ||
+ !strcmp(argv[1], "--strip-comments")) {
+ strip_comments = 1;
+ } else if (!strcmp(argv[1], "-c") ||
+ !strcmp(argv[1], "--comment-lines")) {
+ mode = COMMENT_LINES;
+ } else {
+ mode = INVAL;
+ }
+ } else if (argc > 1) {
+ mode = INVAL;
+ }
+
+ if (mode == INVAL)
+ usage(usage_msg);
- if (argc == 2 && (!strcmp(argv[1], "-s") ||
- !strcmp(argv[1], "--strip-comments")))
- strip_comments = 1;
- else if (argc > 1)
- usage("git stripspace [-s | --strip-comments] < <stream>");
+ if (strip_comments || mode == COMMENT_LINES)
+ git_config(git_default_config, NULL);
if (strbuf_read(&buf, 0, 1024) < 0)
die_errno("could not read the input");
- stripspace(&buf, strip_comments);
+ if (mode == STRIP_SPACE)
+ stripspace(&buf, strip_comments);
+ else
+ comment_lines(&buf);
write_or_die(1, buf.buf, buf.len);
strbuf_release(&buf);
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index dea849c..29fb3f1 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -4,55 +4,73 @@
#include "parse-options.h"
static const char * const git_symbolic_ref_usage[] = {
- "git symbolic-ref [options] name [ref]",
+ N_("git symbolic-ref [options] name [ref]"),
+ N_("git symbolic-ref -d [-q] name"),
NULL
};
-static void check_symref(const char *HEAD, int quiet)
+static int check_symref(const char *HEAD, int quiet, int shorten, int print)
{
unsigned char sha1[20];
int flag;
- const char *refs_heads_master = resolve_ref(HEAD, sha1, 0, &flag);
+ const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag);
- if (!refs_heads_master)
+ if (!refname)
die("No such ref: %s", HEAD);
else if (!(flag & REF_ISSYMREF)) {
if (!quiet)
die("ref %s is not a symbolic ref", HEAD);
else
- exit(1);
+ return 1;
}
- puts(refs_heads_master);
+ if (print) {
+ if (shorten)
+ refname = shorten_unambiguous_ref(refname, 0);
+ puts(refname);
+ }
+ return 0;
}
int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
{
- int quiet = 0;
+ int quiet = 0, delete = 0, shorten = 0, ret = 0;
const char *msg = NULL;
struct option options[] = {
OPT__QUIET(&quiet,
- "suppress error message for non-symbolic (detached) refs"),
- OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
+ N_("suppress error message for non-symbolic (detached) refs")),
+ OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")),
+ OPT_BOOL(0, "short", &shorten, N_("shorten ref output")),
+ OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_END(),
};
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) {
+ if (argc != 1)
+ usage_with_options(git_symbolic_ref_usage, options);
+ ret = check_symref(argv[0], 1, 0, 0);
+ if (ret)
+ die("Cannot delete %s, not a symbolic ref", argv[0]);
+ return delete_ref(argv[0], NULL, REF_NODEREF);
+ }
+
switch (argc) {
case 1:
- check_symref(argv[0], quiet);
+ ret = check_symref(argv[0], quiet, shorten, 1);
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;
default:
usage_with_options(git_symbolic_ref_usage, options);
}
- return 0;
+ return ret;
}
diff --git a/builtin/tag.c b/builtin/tag.c
index 667515e..d315827 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -14,34 +14,66 @@
#include "parse-options.h"
#include "diff.h"
#include "revision.h"
+#include "gpg-interface.h"
+#include "sha1-array.h"
+#include "column.h"
static const char * const git_tag_usage[] = {
- "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
- "git tag -d <tagname>...",
- "git tag -l [-n[<num>]] [<pattern>...]",
- "git tag -v <tagname>...",
+ N_("git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]"),
+ N_("git tag -d <tagname>..."),
+ N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] "
+ "\n\t\t[<pattern>...]"),
+ N_("git tag -v <tagname>..."),
NULL
};
-static char signingkey[1000];
+#define STRCMP_SORT 0 /* must be zero */
+#define VERCMP_SORT 1
+#define SORT_MASK 0x7fff
+#define REVERSE_SORT 0x8000
+
+static int tag_sort;
struct tag_filter {
const char **patterns;
int lines;
+ int sort;
+ struct string_list tags;
struct commit_list *with_commit;
};
+static struct sha1_array points_at;
+static unsigned int colopts;
+
static int match_pattern(const char **patterns, const char *ref)
{
/* no pattern means match everything */
if (!*patterns)
return 1;
for (; *patterns; patterns++)
- if (!fnmatch(*patterns, ref, 0))
+ if (!wildmatch(*patterns, ref, 0, NULL))
return 1;
return 0;
}
+static const unsigned char *match_points_at(const char *refname,
+ const unsigned char *sha1)
+{
+ const unsigned char *tagged_sha1 = NULL;
+ struct object *obj;
+
+ if (sha1_array_lookup(&points_at, sha1) >= 0)
+ return sha1;
+ obj = parse_object(sha1);
+ if (!obj)
+ die(_("malformed object at '%s'"), refname);
+ if (obj->type == OBJ_TAG)
+ tagged_sha1 = ((struct tag *)obj)->tagged->sha1;
+ if (tagged_sha1 && sha1_array_lookup(&points_at, tagged_sha1) >= 0)
+ return tagged_sha1;
+ return NULL;
+}
+
static int in_commit_list(const struct commit_list *want, struct commit *c)
{
for (; want; want = want->next)
@@ -50,11 +82,19 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
return 0;
}
-static int contains_recurse(struct commit *candidate,
+enum contains_result {
+ CONTAINS_UNKNOWN = -1,
+ CONTAINS_NO = 0,
+ CONTAINS_YES = 1
+};
+
+/*
+ * Test whether the candidate or one of its parents is contained in the list.
+ * Do not recurse to find out, though, but return -1 if inconclusive.
+ */
+static enum contains_result contains_test(struct commit *candidate,
const struct commit_list *want)
{
- struct commit_list *p;
-
/* was it previously marked as containing a want commit? */
if (candidate->object.flags & TMP_MARK)
return 1;
@@ -62,26 +102,117 @@ static int contains_recurse(struct commit *candidate,
if (candidate->object.flags & UNINTERESTING)
return 0;
/* or are we it? */
- if (in_commit_list(want, candidate))
+ if (in_commit_list(want, candidate)) {
+ candidate->object.flags |= TMP_MARK;
return 1;
+ }
if (parse_commit(candidate) < 0)
return 0;
- /* Otherwise recurse and mark ourselves for future traversals. */
- for (p = candidate->parents; p; p = p->next) {
- if (contains_recurse(p->item, want)) {
- candidate->object.flags |= TMP_MARK;
- return 1;
+ return -1;
+}
+
+/*
+ * Mimicking the real stack, this stack lives on the heap, avoiding stack
+ * overflows.
+ *
+ * At each recursion step, the stack items points to the commits whose
+ * ancestors are to be inspected.
+ */
+struct stack {
+ int nr, alloc;
+ struct stack_entry {
+ struct commit *commit;
+ struct commit_list *parents;
+ } *stack;
+};
+
+static void push_to_stack(struct commit *candidate, struct stack *stack)
+{
+ int index = stack->nr++;
+ ALLOC_GROW(stack->stack, stack->nr, stack->alloc);
+ stack->stack[index].commit = candidate;
+ stack->stack[index].parents = candidate->parents;
+}
+
+static enum contains_result contains(struct commit *candidate,
+ const struct commit_list *want)
+{
+ struct stack stack = { 0, 0, NULL };
+ int result = contains_test(candidate, want);
+
+ if (result != CONTAINS_UNKNOWN)
+ return result;
+
+ push_to_stack(candidate, &stack);
+ while (stack.nr) {
+ struct stack_entry *entry = &stack.stack[stack.nr - 1];
+ struct commit *commit = entry->commit;
+ struct commit_list *parents = entry->parents;
+
+ if (!parents) {
+ commit->object.flags |= UNINTERESTING;
+ stack.nr--;
+ }
+ /*
+ * If we just popped the stack, parents->item has been marked,
+ * therefore contains_test will return a meaningful 0 or 1.
+ */
+ else switch (contains_test(parents->item, want)) {
+ case CONTAINS_YES:
+ commit->object.flags |= TMP_MARK;
+ stack.nr--;
+ break;
+ case CONTAINS_NO:
+ entry->parents = parents->next;
+ break;
+ case CONTAINS_UNKNOWN:
+ push_to_stack(parents->item, &stack);
+ break;
}
}
- candidate->object.flags |= UNINTERESTING;
- return 0;
+ free(stack.stack);
+ return contains_test(candidate, want);
}
-static int contains(struct commit *candidate, const struct commit_list *want)
+static void show_tag_lines(const unsigned char *sha1, int lines)
{
- return contains_recurse(candidate, want);
+ int i;
+ unsigned long size;
+ enum object_type type;
+ char *buf, *sp, *eol;
+ size_t len;
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ die_errno("unable to read object %s", sha1_to_hex(sha1));
+ if (type != OBJ_COMMIT && type != OBJ_TAG)
+ goto free_return;
+ if (!size)
+ die("an empty %s object %s?",
+ typename(type), sha1_to_hex(sha1));
+
+ /* skip header */
+ sp = strstr(buf, "\n\n");
+ if (!sp)
+ goto free_return;
+
+ /* only take up to "lines" lines, and strip the signature from a tag */
+ if (type == OBJ_TAG)
+ size = parse_signature(buf, size);
+ for (i = 0, sp += 2; i < lines && sp < buf + size; i++) {
+ if (i)
+ printf("\n ");
+ eol = memchr(sp, '\n', size - (sp - buf));
+ len = eol ? eol - sp : size - (sp - buf);
+ fwrite(sp, len, 1, stdout);
+ if (!eol)
+ break;
+ sp = eol + 1;
+ }
+free_return:
+ free(buf);
}
static int show_reference(const char *refname, const unsigned char *sha1,
@@ -90,12 +221,6 @@ static int show_reference(const char *refname, const unsigned char *sha1,
struct tag_filter *filter = cb_data;
if (match_pattern(filter->patterns, refname)) {
- int i;
- unsigned long size;
- enum object_type type;
- char *buf, *sp, *eol;
- size_t len;
-
if (filter->with_commit) {
struct commit *commit;
@@ -106,54 +231,57 @@ static int show_reference(const char *refname, const unsigned char *sha1,
return 0;
}
- if (!filter->lines) {
- printf("%s\n", refname);
+ if (points_at.nr && !match_points_at(refname, sha1))
return 0;
- }
- printf("%-15s ", refname);
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf || !size)
- return 0;
-
- /* skip header */
- sp = strstr(buf, "\n\n");
- if (!sp) {
- free(buf);
+ if (!filter->lines) {
+ if (filter->sort)
+ string_list_append(&filter->tags, refname);
+ else
+ printf("%s\n", refname);
return 0;
}
- /* only take up to "lines" lines, and strip the signature */
- size = parse_signature(buf, size);
- for (i = 0, sp += 2;
- i < filter->lines && sp < buf + size;
- i++) {
- if (i)
- printf("\n ");
- eol = memchr(sp, '\n', size - (sp - buf));
- len = eol ? eol - sp : size - (sp - buf);
- fwrite(sp, len, 1, stdout);
- if (!eol)
- break;
- sp = eol + 1;
- }
+ printf("%-15s ", refname);
+ show_tag_lines(sha1, filter->lines);
putchar('\n');
- free(buf);
}
return 0;
}
+static int sort_by_version(const void *a_, const void *b_)
+{
+ const struct string_list_item *a = a_;
+ const struct string_list_item *b = b_;
+ return versioncmp(a->string, b->string);
+}
+
static int list_tags(const char **patterns, int lines,
- struct commit_list *with_commit)
+ struct commit_list *with_commit, int sort)
{
struct tag_filter filter;
filter.patterns = patterns;
filter.lines = lines;
+ filter.sort = sort;
filter.with_commit = with_commit;
+ memset(&filter.tags, 0, sizeof(filter.tags));
+ filter.tags.strdup_strings = 1;
for_each_tag_ref(show_reference, (void *) &filter);
-
+ if (sort) {
+ int i;
+ if ((sort & SORT_MASK) == VERCMP_SORT)
+ qsort(filter.tags.items, filter.tags.nr,
+ sizeof(struct string_list_item), sort_by_version);
+ if (sort & REVERSE_SORT)
+ for (i = filter.tags.nr - 1; i >= 0; i--)
+ printf("%s\n", filter.tags.items[i].string);
+ else
+ for (i = 0; i < filter.tags.nr; i++)
+ printf("%s\n", filter.tags.items[i].string);
+ string_list_clear(&filter.tags, 0);
+ }
return 0;
}
@@ -174,7 +302,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
had_error = 1;
continue;
}
- if (!resolve_ref(ref, sha1, 1, NULL)) {
+ if (read_ref(ref, sha1)) {
error(_("tag '%s' not found."), *p);
had_error = 1;
continue;
@@ -208,83 +336,67 @@ static int verify_tag(const char *name, const char *ref,
static int do_sign(struct strbuf *buffer)
{
- struct child_process gpg;
- const char *args[4];
- char *bracket;
- int len;
- int i, j;
-
- if (!*signingkey) {
- if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
- sizeof(signingkey)) > sizeof(signingkey) - 1)
- return error(_("committer info too long."));
- bracket = strchr(signingkey, '>');
- if (bracket)
- bracket[1] = '\0';
- }
-
- /* When the username signingkey is bad, program could be terminated
- * because gpg exits without reading and then write gets SIGPIPE. */
- signal(SIGPIPE, SIG_IGN);
-
- memset(&gpg, 0, sizeof(gpg));
- gpg.argv = args;
- gpg.in = -1;
- gpg.out = -1;
- args[0] = "gpg";
- args[1] = "-bsau";
- args[2] = signingkey;
- args[3] = NULL;
-
- if (start_command(&gpg))
- return error(_("could not run gpg."));
-
- if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
- close(gpg.in);
- close(gpg.out);
- finish_command(&gpg);
- return error(_("gpg did not accept the tag data"));
- }
- close(gpg.in);
- len = strbuf_read(buffer, gpg.out, 1024);
- close(gpg.out);
-
- if (finish_command(&gpg) || !len || len < 0)
- return error(_("gpg failed to sign the tag"));
-
- /* Strip CR from the line endings, in case we are on Windows. */
- for (i = j = 0; i < buffer->len; i++)
- if (buffer->buf[i] != '\r') {
- if (i != j)
- buffer->buf[j] = buffer->buf[i];
- j++;
- }
- strbuf_setlen(buffer, j);
-
- return 0;
+ return sign_buffer(buffer, buffer, get_signing_key());
}
static const char tag_template[] =
- N_("\n"
- "#\n"
- "# Write a tag message\n"
- "#\n");
+ N_("\nWrite a message for tag:\n %s\n"
+ "Lines starting with '%c' will be ignored.\n");
-static void set_signingkey(const char *value)
+static const char tag_template_nocleanup[] =
+ N_("\nWrite a message for tag:\n %s\n"
+ "Lines starting with '%c' will be kept; you may remove them"
+ " yourself if you want to.\n");
+
+/*
+ * Parse a sort string, and return 0 if parsed successfully. Will return
+ * non-zero when the sort string does not parse into a known type. If var is
+ * given, the error message becomes a warning and includes information about
+ * the configuration value.
+ */
+static int parse_sort_string(const char *var, const char *arg, int *sort)
{
- if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
- die(_("signing key value too long (%.10s...)"), value);
+ int type = 0, flags = 0;
+
+ if (skip_prefix(arg, "-", &arg))
+ flags |= REVERSE_SORT;
+
+ if (skip_prefix(arg, "version:", &arg) || skip_prefix(arg, "v:", &arg))
+ type = VERCMP_SORT;
+ else
+ type = STRCMP_SORT;
+
+ if (strcmp(arg, "refname")) {
+ if (!var)
+ return error(_("unsupported sort specification '%s'"), arg);
+ else {
+ warning(_("unsupported sort specification '%s' in variable '%s'"),
+ var, arg);
+ return -1;
+ }
+ }
+
+ *sort = (type | flags);
+
+ return 0;
}
static int git_tag_config(const char *var, const char *value, void *cb)
{
- if (!strcmp(var, "user.signingkey")) {
+ int status;
+
+ if (!strcmp(var, "tag.sort")) {
if (!value)
return config_error_nonbool(var);
- set_signingkey(value);
+ parse_sort_string(var, value, &tag_sort);
return 0;
}
+ status = git_gpg_config(var, value, cb);
+ if (status)
+ return status;
+ if (starts_with(var, "column."))
+ return git_column_config(var, value, "tag", &colopts);
return git_default_config(var, value, cb);
}
@@ -319,8 +431,18 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result)
return 0;
}
+struct create_tag_options {
+ unsigned int message_given:1;
+ unsigned int sign;
+ enum {
+ CLEANUP_NONE,
+ CLEANUP_SPACE,
+ CLEANUP_ALL
+ } cleanup_mode;
+};
+
static void create_tag(const unsigned char *object, const char *tag,
- struct strbuf *buf, int message, int sign,
+ struct strbuf *buf, struct create_tag_options *opt,
unsigned char *prev, unsigned char *result)
{
enum object_type type;
@@ -340,12 +462,12 @@ static void create_tag(const unsigned char *object, const char *tag,
sha1_to_hex(object),
typename(type),
tag,
- git_committer_info(IDENT_ERROR_ON_NO_NAME));
+ git_committer_info(IDENT_STRICT));
if (header_len > sizeof(header_buf) - 1)
die(_("tag header too big."));
- if (!message) {
+ if (!opt->message_given) {
int fd;
/* write the template message before editing: */
@@ -354,10 +476,18 @@ static void create_tag(const unsigned char *object, const char *tag,
if (fd < 0)
die_errno(_("could not create file '%s'"), path);
- if (!is_null_sha1(prev))
+ if (!is_null_sha1(prev)) {
write_tag_body(fd, prev);
- else
- write_or_die(fd, _(tag_template), strlen(_(tag_template)));
+ } else {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addch(&buf, '\n');
+ if (opt->cleanup_mode == CLEANUP_ALL)
+ strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char);
+ else
+ strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char);
+ write_or_die(fd, buf.buf, buf.len);
+ strbuf_release(&buf);
+ }
close(fd);
if (launch_editor(path, buf, NULL)) {
@@ -367,14 +497,15 @@ static void create_tag(const unsigned char *object, const char *tag,
}
}
- stripspace(buf, 1);
+ if (opt->cleanup_mode != CLEANUP_NONE)
+ stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
- if (!message && !buf->len)
+ if (!opt->message_given && !buf->len)
die(_("no tag message?"));
strbuf_insert(buf, 0, header_buf, header_len);
- if (build_tag_object(buf, sign, result) < 0) {
+ if (build_tag_object(buf, opt->sign, result) < 0) {
if (path)
fprintf(stderr, _("The tag message has been left in %s\n"),
path);
@@ -407,12 +538,36 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
{
if (name[0] == '-')
- return CHECK_REF_FORMAT_ERROR;
+ return -1;
strbuf_reset(sb);
strbuf_addf(sb, "refs/tags/%s", name);
- return check_ref_format(sb->buf);
+ return check_refname_format(sb->buf, 0);
+}
+
+static int parse_opt_points_at(const struct option *opt __attribute__((unused)),
+ const char *arg, int unset)
+{
+ unsigned char sha1[20];
+
+ if (unset) {
+ sha1_array_clear(&points_at);
+ return 0;
+ }
+ if (!arg)
+ return error(_("switch 'points-at' requires an object"));
+ if (get_sha1(arg, sha1))
+ return error(_("malformed object name '%s'"), arg);
+ sha1_array_append(&points_at, sha1);
+ return 0;
+}
+
+static int parse_opt_sort(const struct option *opt, const char *arg, int unset)
+{
+ int *sort = opt->value;
+
+ return parse_sort_string(NULL, arg, sort);
}
int cmd_tag(int argc, const char **argv, const char *prefix)
@@ -421,71 +576,109 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
struct strbuf ref = STRBUF_INIT;
unsigned char object[20], prev[20];
const char *object_ref, *tag;
- struct ref_lock *lock;
-
- int annotate = 0, sign = 0, force = 0, lines = -1,
- list = 0, delete = 0, verify = 0;
+ struct create_tag_options opt;
+ char *cleanup_arg = NULL;
+ 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 ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
struct option options[] = {
- OPT_BOOLEAN('l', NULL, &list, "list tag names"),
- { OPTION_INTEGER, 'n', NULL, &lines, "n",
- "print <n> lines of each tag message",
+ 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', NULL, &delete, "delete tags"),
- OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
-
- OPT_GROUP("Tag creation options"),
- OPT_BOOLEAN('a', NULL, &annotate,
- "annotated tag, needs a message"),
- OPT_CALLBACK('m', NULL, &msg, "message",
- "tag message", parse_msg_arg),
- OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
- OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
- OPT_STRING('u', NULL, &keyid, "key-id",
- "use another key to sign the tag"),
- OPT__FORCE(&force, "replace the tag if exists"),
-
- OPT_GROUP("Tag listing options"),
+ 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_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_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"),
+ N_("use another key to sign the tag")),
+ OPT__FORCE(&force, N_("replace the tag if exists")),
+
+ OPT_GROUP(N_("Tag listing options")),
+ OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
{
- OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
- "print only tags that contain the commit",
+ OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"),
+ PARSE_OPT_NONEG, parse_opt_sort
+ },
+ {
+ OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"),
+ N_("print only tags that contain the commit"),
PARSE_OPT_LASTARG_DEFAULT,
parse_opt_with_commit, (intptr_t)"HEAD",
},
+ {
+ OPTION_CALLBACK, 0, "with", &with_commit, N_("commit"),
+ N_("print only tags that contain the commit"),
+ PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
+ parse_opt_with_commit, (intptr_t)"HEAD",
+ },
+ {
+ OPTION_CALLBACK, 0, "points-at", NULL, N_("object"),
+ N_("print only tags of the object"), 0, parse_opt_points_at
+ },
OPT_END()
};
git_config(git_tag_config, NULL);
+ memset(&opt, 0, sizeof(opt));
+
argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
if (keyid) {
- sign = 1;
- set_signingkey(keyid);
+ opt.sign = 1;
+ set_signing_key(keyid);
}
- if (sign)
+ 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);
- if (list)
- return list_tags(argv, lines == -1 ? 0 : lines,
- with_commit);
+ finalize_colopts(&colopts, -1);
+ if (cmdmode == 'l' && lines != -1) {
+ if (explicitly_enable_column(colopts))
+ die(_("--column and -n are incompatible"));
+ colopts = 0;
+ }
+ if (cmdmode == 'l') {
+ int ret;
+ if (column_active(colopts)) {
+ struct column_options copts;
+ memset(&copts, 0, sizeof(copts));
+ copts.padding = 2;
+ run_column_filter(colopts, &copts);
+ }
+ if (lines != -1 && tag_sort)
+ die(_("--sort and -n are incompatible"));
+ ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit, tag_sort);
+ if (column_active(colopts))
+ stop_column_filter();
+ return ret;
+ }
if (lines != -1)
die(_("-n option is only allowed with -l."));
if (with_commit)
die(_("--contains option is only allowed with -l."));
- if (delete)
+ if (points_at.nr)
+ die(_("--points-at option is only allowed with -l."));
+ 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) {
@@ -518,23 +711,36 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (strbuf_check_tag_ref(&ref, tag))
die(_("'%s' is not a valid tag name."), tag);
- if (!resolve_ref(ref.buf, prev, 1, NULL))
+ if (read_ref(ref.buf, prev))
hashclr(prev);
else if (!force)
die(_("tag '%s' already exists"), tag);
+ opt.message_given = msg.given || msgfile;
+
+ if (!cleanup_arg || !strcmp(cleanup_arg, "strip"))
+ opt.cleanup_mode = CLEANUP_ALL;
+ else if (!strcmp(cleanup_arg, "verbatim"))
+ opt.cleanup_mode = CLEANUP_NONE;
+ else if (!strcmp(cleanup_arg, "whitespace"))
+ opt.cleanup_mode = CLEANUP_SPACE;
+ else
+ die(_("Invalid cleanup mode %s"), cleanup_arg);
+
if (annotate)
- create_tag(object, tag, &buf, msg.given || msgfile,
- sign, prev, object);
-
- lock = lock_any_ref_for_update(ref.buf, prev, 0);
- if (!lock)
- die(_("%s: cannot lock the ref"), ref.buf);
- if (write_ref_sha1(lock, object, NULL) < 0)
- die(_("%s: cannot update the ref"), ref.buf);
- if (force && hashcmp(prev, object))
+ create_tag(object, tag, &buf, &opt, prev, object);
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref.buf, object, prev,
+ 0, 1, NULL, &err) ||
+ ref_transaction_commit(transaction, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
+ if (force && !is_null_sha1(prev) && hashcmp(prev, object))
printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
+ strbuf_release(&err);
strbuf_release(&buf);
strbuf_release(&ref);
return 0;
diff --git a/builtin/tar-tree.c b/builtin/tar-tree.c
deleted file mode 100644
index 3f1e701..0000000
--- 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 14e04e6..ac66672 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -91,7 +91,7 @@ static void use(int bytes)
static void *get_data(unsigned long size)
{
git_zstream stream;
- void *buf = xmalloc(size);
+ void *buf = xmallocz(size);
memset(&stream, 0, sizeof(stream));
@@ -107,7 +107,7 @@ static void *get_data(unsigned long size)
if (stream.total_out == size && ret == Z_STREAM_END)
break;
if (ret != Z_OK) {
- error("inflate returned %d\n", ret);
+ error("inflate returned %d", ret);
free(buf);
buf = NULL;
if (!recover)
@@ -164,10 +164,10 @@ static unsigned nr_objects;
* Called only from check_object() after it verified this object
* is Ok.
*/
-static void write_cached_object(struct object *obj)
+static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf)
{
unsigned char sha1[20];
- struct obj_buffer *obj_buf = lookup_object_buffer(obj);
+
if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0)
die("failed to write object %s", sha1_to_hex(obj->sha1));
obj->flags |= FLAG_WRITTEN;
@@ -180,6 +180,8 @@ static void write_cached_object(struct object *obj)
*/
static int check_object(struct object *obj, int type, void *data)
{
+ struct obj_buffer *obj_buf;
+
if (!obj)
return 1;
@@ -198,11 +200,15 @@ static int check_object(struct object *obj, int type, void *data)
return 0;
}
- if (fsck_object(obj, 1, fsck_error_function))
+ obj_buf = lookup_object_buffer(obj);
+ if (!obj_buf)
+ die("Whoops! Cannot find object '%s'", sha1_to_hex(obj->sha1));
+ if (fsck_object(obj, obj_buf->buffer, obj_buf->size, 1,
+ fsck_error_function))
die("Error in object");
if (fsck_walk(obj, check_object, NULL))
die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
- write_cached_object(obj);
+ write_cached_object(obj, obj_buf);
return 0;
}
@@ -480,7 +486,7 @@ static void unpack_all(void)
use(sizeof(struct pack_header));
if (!quiet)
- progress = start_progress("Unpacking objects", nr_objects);
+ progress = start_progress(_("Unpacking objects"), nr_objects);
obj_list = xcalloc(nr_objects, sizeof(*obj_list));
for (i = 0; i < nr_objects; i++) {
unpack_one(i);
@@ -497,7 +503,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
int i;
unsigned char sha1[20];
- read_replace_refs = 0;
+ check_replace_refs = 0;
git_config(git_default_config, NULL);
@@ -523,7 +529,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 a6a23fa..b0e3dc9 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -4,6 +4,7 @@
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
+#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
#include "tree-walk.h"
@@ -11,6 +12,9 @@
#include "refs.h"
#include "resolve-undo.h"
#include "parse-options.h"
+#include "pathspec.h"
+#include "dir.h"
+#include "split-index.h"
/*
* Default to not allowing changes to the list of files. The
@@ -53,8 +57,9 @@ static int mark_ce_flags(const char *path, int flag, int mark)
active_cache[pos]->ce_flags |= flag;
else
active_cache[pos]->ce_flags &= ~flag;
- cache_tree_invalidate_path(active_cache_tree, path);
- active_cache_changed = 1;
+ active_cache[pos]->ce_flags |= CE_UPDATE_IN_BASE;
+ cache_tree_invalidate_path(&the_index, path);
+ active_cache_changed |= CE_ENTRY_CHANGED;
return 0;
}
return -1;
@@ -83,7 +88,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;
@@ -95,7 +100,8 @@ static int add_one_path(struct cache_entry *old, const char *path, int len, stru
size = cache_entry_size(len);
ce = xcalloc(1, size);
memcpy(ce->name, path, len);
- ce->ce_flags = len;
+ ce->ce_flags = create_ce_flags(0);
+ ce->ce_namelen = len;
fill_stat_cache_info(ce, st);
ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
@@ -141,7 +147,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! */
@@ -157,7 +163,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;
@@ -182,7 +188,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))
@@ -211,12 +217,6 @@ static int process_path(const char *path)
if (S_ISDIR(st.st_mode))
return process_directory(path, len, &st);
- /*
- * Process a regular file
- */
- if (ce && S_ISGITLINK(ce->ce_mode))
- return error("%s is already a gitlink, not replacing", path);
-
return add_one_path(ce, path, len, &st);
}
@@ -235,7 +235,8 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
hashcpy(ce->sha1, sha1);
memcpy(ce->name, path, len);
- ce->ce_flags = create_ce_flags(len, stage);
+ ce->ce_flags = create_ce_flags(stage);
+ ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
if (assume_unchanged)
ce->ce_flags |= CE_VALID;
@@ -269,44 +270,41 @@ static void chmod_path(int flip, const char *path)
default:
goto fail;
}
- cache_tree_invalidate_path(active_cache_tree, path);
- active_cache_changed = 1;
+ cache_tree_invalidate_path(&the_index, path);
+ ce->ce_flags |= CE_UPDATE_IN_BASE;
+ active_cache_changed |= CE_ENTRY_CHANGED;
report("chmod %cx '%s'", flip, path);
return;
fail:
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)
@@ -402,7 +400,7 @@ static void read_index_info(int line_termination)
}
static const char * const update_index_usage[] = {
- "git update-index [options] [--] [<file>...]",
+ N_("git update-index [options] [--] [<file>...]"),
NULL
};
@@ -433,7 +431,8 @@ static struct cache_entry *read_one_ent(const char *which,
hashcpy(ce->sha1, sha1);
memcpy(ce->name, path, namelen);
- ce->ce_flags = create_ce_flags(namelen, stage);
+ ce->ce_flags = create_ce_flags(stage);
+ ce->ce_namelen = namelen;
ce->ce_mode = create_ce_mode(mode);
return ce;
}
@@ -451,7 +450,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))
@@ -465,7 +464,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,
@@ -549,10 +548,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
@@ -561,11 +561,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,
@@ -580,7 +581,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;
}
@@ -596,6 +599,7 @@ struct refresh_params {
static int refresh(struct refresh_params *o, unsigned int flag)
{
setup_work_tree();
+ read_cache_preload(NULL);
*o->has_errors |= refresh_cache(o->flags | flag);
return 0;
}
@@ -629,14 +633,45 @@ static int resolve_undo_clear_callback(const struct option *opt,
return 0;
}
+static int parse_new_style_cacheinfo(const char *arg,
+ unsigned int *mode,
+ unsigned char sha1[],
+ const char **path)
+{
+ unsigned long ul;
+ char *endp;
+
+ if (!arg)
+ return -1;
+
+ errno = 0;
+ ul = strtoul(arg, &endp, 8);
+ if (errno || endp == arg || *endp != ',' || (unsigned int) ul != ul)
+ return -1; /* not a new-style cacheinfo */
+ *mode = ul;
+ endp++;
+ if (get_sha1_hex(endp, sha1) || endp[40] != ',')
+ return -1;
+ *path = endp + 41;
+ return 0;
+}
+
static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
const struct option *opt, int unset)
{
unsigned char sha1[20];
unsigned int mode;
+ const char *path;
+ if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, sha1, &path)) {
+ if (add_cacheinfo(mode, sha1, path, 0))
+ die("git update-index: --cacheinfo cannot add %s", path);
+ ctx->argv++;
+ ctx->argc--;
+ return 0;
+ }
if (ctx->argc <= 3)
- return error("option 'cacheinfo' expects three arguments");
+ return error("option 'cacheinfo' expects <mode>,<sha1>,<path>");
if (strtoul_ui(*++ctx->argv, 8, &mode) ||
get_sha1_hex(*++ctx->argv, sha1) ||
add_cacheinfo(mode, sha1, *++ctx->argv, 0))
@@ -708,94 +743,100 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
int newfd, entries, has_errors = 0, line_termination = '\n';
int read_from_stdin = 0;
int prefix_length = prefix ? strlen(prefix) : 0;
+ int preferred_index_format = 0;
char set_executable_bit = 0;
struct refresh_params refresh_args = {0, &has_errors};
int lock_error = 0;
+ int split_index = -1;
struct lock_file *lock_file;
struct parse_opt_ctx_t ctx;
int parseopt_state = PARSE_OPT_UNKNOWN;
struct option options[] = {
OPT_BIT('q', NULL, &refresh_args.flags,
- "continue refresh even when index needs update",
+ N_("continue refresh even when index needs update"),
REFRESH_QUIET),
OPT_BIT(0, "ignore-submodules", &refresh_args.flags,
- "refresh: ignore submodules",
+ N_("refresh: ignore submodules"),
REFRESH_IGNORE_SUBMODULES),
OPT_SET_INT(0, "add", &allow_add,
- "do not ignore new files", 1),
+ N_("do not ignore new files"), 1),
OPT_SET_INT(0, "replace", &allow_replace,
- "let files replace directories and vice-versa", 1),
+ N_("let files replace directories and vice-versa"), 1),
OPT_SET_INT(0, "remove", &allow_remove,
- "notice files missing from worktree", 1),
+ N_("notice files missing from worktree"), 1),
OPT_BIT(0, "unmerged", &refresh_args.flags,
- "refresh even if index contains unmerged entries",
+ N_("refresh even if index contains unmerged entries"),
REFRESH_UNMERGED),
{OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL,
- "refresh stat information",
+ N_("refresh stat information"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
refresh_callback},
{OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL,
- "like --refresh, but ignore assume-unchanged setting",
+ N_("like --refresh, but ignore assume-unchanged setting"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
really_refresh_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL,
- "<mode> <object> <path>",
- "add the specified entry to the index",
- PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
+ N_("<mode>,<object>,<path>"),
+ N_("add the specified entry to the index"),
+ PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
(parse_opt_cb *) cacheinfo_callback},
- {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+/-)x",
- "override the executable bit of the listed files",
+ {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, N_("(+/-)x"),
+ N_("override the executable bit of the listed files"),
PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
chmod_callback},
{OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL,
- "mark files as \"not changing\"",
+ N_("mark files as \"not changing\""),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
{OPTION_SET_INT, 0, "no-assume-unchanged", &mark_valid_only, NULL,
- "clear assumed-unchanged bit",
+ N_("clear assumed-unchanged bit"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
{OPTION_SET_INT, 0, "skip-worktree", &mark_skip_worktree_only, NULL,
- "mark files as \"index-only\"",
+ N_("mark files as \"index-only\""),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
{OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL,
- "clear skip-worktree bit",
+ N_("clear skip-worktree bit"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
OPT_SET_INT(0, "info-only", &info_only,
- "add to index only; do not add content to object database", 1),
+ N_("add to index only; do not add content to object database"), 1),
OPT_SET_INT(0, "force-remove", &force_remove,
- "remove named paths even if present in worktree", 1),
+ N_("remove named paths even if present in worktree"), 1),
OPT_SET_INT('z', NULL, &line_termination,
- "with --stdin: input lines are terminated by null bytes", '\0'),
+ N_("with --stdin: input lines are terminated by null bytes"), '\0'),
{OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
- "read list of paths to be updated from standard input",
+ N_("read list of paths to be updated from standard input"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) stdin_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &line_termination, NULL,
- "add entries from standard input to the index",
+ N_("add entries from standard input to the index"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) stdin_cacheinfo_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL,
- "repopulate stages #2 and #3 for the listed paths",
+ N_("repopulate stages #2 and #3 for the listed paths"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) unresolve_callback},
{OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL,
- "only update entries that differ from HEAD",
+ N_("only update entries that differ from HEAD"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) reupdate_callback},
OPT_BIT(0, "ignore-missing", &refresh_args.flags,
- "ignore files missing from worktree",
+ N_("ignore files missing from worktree"),
REFRESH_IGNORE_MISSING),
OPT_SET_INT(0, "verbose", &verbose,
- "report actions to standard output", 1),
+ N_("report actions to standard output"), 1),
{OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL,
- "(for porcelains) forget saved unresolved conflicts",
+ N_("(for porcelains) forget saved unresolved conflicts"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
resolve_undo_clear_callback},
+ OPT_INTEGER(0, "index-version", &preferred_index_format,
+ N_("write index in this format")),
+ OPT_BOOL(0, "split-index", &split_index,
+ N_("enable or disable split index")),
OPT_END()
};
if (argc == 2 && !strcmp(argv[1], "-h"))
- usage(update_index_usage[0]);
+ usage_with_options(update_index_usage, options);
git_config(git_default_config, NULL);
@@ -833,11 +874,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;
@@ -851,6 +891,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
}
}
argc = parse_options_end(&ctx);
+ if (preferred_index_format) {
+ if (preferred_index_format < INDEX_FORMAT_LB ||
+ INDEX_FORMAT_UB < preferred_index_format)
+ die("index-version %d not in range: %d..%d",
+ preferred_index_format,
+ INDEX_FORMAT_LB, INDEX_FORMAT_UB);
+
+ if (the_index.version != preferred_index_format)
+ active_cache_changed |= SOMETHING_CHANGED;
+ the_index.version = preferred_index_format;
+ }
if (read_from_stdin) {
struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
@@ -865,24 +916,36 @@ 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);
}
+ if (split_index > 0) {
+ init_split_index(&the_index);
+ the_index.cache_changed |= SPLIT_INDEX_ORDERED;
+ } else if (!split_index && the_index.split_index) {
+ /*
+ * can't discard_split_index(&the_index); because that
+ * will destroy split_index->base->cache[], which may
+ * be shared with the_index.cache[]. So yeah we're
+ * leaking a bit here.
+ */
+ the_index.split_index = NULL;
+ the_index.cache_changed |= SOMETHING_CHANGED;
+ }
+
if (active_cache_changed) {
if (newfd < 0) {
if (refresh_args.flags & REFRESH_QUIET)
exit(128);
- unable_to_lock_index_die(get_index_file(), lock_error);
+ unable_to_lock_die(get_index_file(), lock_error);
}
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock_file))
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
die("Unable to write new index file");
}
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 835c62a..1993529 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -2,23 +2,365 @@
#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[] = {
- "git update-ref [options] -d <refname> [<oldval>]",
- "git update-ref [options] <refname> <newval> [<oldval>]",
+ 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 char line_termination = '\n';
+static int update_flags;
+static const char *msg;
+
+/*
+ * Parse one whitespace- or NUL-terminated, possibly C-quoted argument
+ * and append the result to arg. Return a pointer to the terminator.
+ * Die if there is an error in how the argument is C-quoted. This
+ * function is only used if not -z.
+ */
+static const char *parse_arg(const char *next, struct strbuf *arg)
+{
+ if (*next == '"') {
+ const char *orig = next;
+
+ if (unquote_c_style(arg, next, &next))
+ die("badly quoted argument: %s", orig);
+ if (*next && !isspace(*next))
+ die("unexpected character after quoted argument: %s", orig);
+ } else {
+ while (*next && !isspace(*next))
+ strbuf_addch(arg, *next++);
+ }
+
+ return next;
+}
+
+/*
+ * Parse the reference name immediately after "command SP". If not
+ * -z, then handle C-quoting. Return a pointer to a newly allocated
+ * string containing the name of the reference, or NULL if there was
+ * an error. Update *next to point at the character that terminates
+ * the argument. Die if C-quoting is malformed or the reference name
+ * is invalid.
+ */
+static char *parse_refname(struct strbuf *input, const char **next)
+{
+ struct strbuf ref = STRBUF_INIT;
+
+ if (line_termination) {
+ /* Without -z, use the next argument */
+ *next = parse_arg(*next, &ref);
+ } else {
+ /* With -z, use everything up to the next NUL */
+ strbuf_addstr(&ref, *next);
+ *next += ref.len;
+ }
+
+ if (!ref.len) {
+ strbuf_release(&ref);
+ return NULL;
+ }
+
+ if (check_refname_format(ref.buf, REFNAME_ALLOW_ONELEVEL))
+ die("invalid ref format: %s", ref.buf);
+
+ return strbuf_detach(&ref, NULL);
+}
+
+/*
+ * The value being parsed is <oldvalue> (as opposed to <newvalue>; the
+ * difference affects which error messages are generated):
+ */
+#define PARSE_SHA1_OLD 0x01
+
+/*
+ * For backwards compatibility, accept an empty string for update's
+ * <newvalue> in binary mode to be equivalent to specifying zeros.
+ */
+#define PARSE_SHA1_ALLOW_EMPTY 0x02
+
+/*
+ * Parse an argument separator followed by the next argument, if any.
+ * If there is an argument, convert it to a SHA-1, write it to sha1,
+ * set *next to point at the character terminating the argument, and
+ * return 0. If there is no argument at all (not even the empty
+ * string), return 1 and leave *next unchanged. If the value is
+ * provided but cannot be converted to a SHA-1, die. flags can
+ * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.
+ */
+static int parse_next_sha1(struct strbuf *input, const char **next,
+ unsigned char *sha1,
+ const char *command, const char *refname,
+ int flags)
+{
+ struct strbuf arg = STRBUF_INIT;
+ int ret = 0;
+
+ if (*next == input->buf + input->len)
+ goto eof;
+
+ if (line_termination) {
+ /* Without -z, consume SP and use next argument */
+ if (!**next || **next == line_termination)
+ return 1;
+ if (**next != ' ')
+ die("%s %s: expected SP but got: %s",
+ command, refname, *next);
+ (*next)++;
+ *next = parse_arg(*next, &arg);
+ if (arg.len) {
+ if (get_sha1(arg.buf, sha1))
+ goto invalid;
+ } else {
+ /* Without -z, an empty value means all zeros: */
+ hashclr(sha1);
+ }
+ } else {
+ /* With -z, read the next NUL-terminated line */
+ if (**next)
+ die("%s %s: expected NUL but got: %s",
+ command, refname, *next);
+ (*next)++;
+ if (*next == input->buf + input->len)
+ goto eof;
+ strbuf_addstr(&arg, *next);
+ *next += arg.len;
+
+ if (arg.len) {
+ if (get_sha1(arg.buf, sha1))
+ goto invalid;
+ } else if (flags & PARSE_SHA1_ALLOW_EMPTY) {
+ /* With -z, treat an empty value as all zeros: */
+ warning("%s %s: missing <newvalue>, treating as zero",
+ command, refname);
+ hashclr(sha1);
+ } else {
+ /*
+ * With -z, an empty non-required value means
+ * unspecified:
+ */
+ ret = 1;
+ }
+ }
+
+ strbuf_release(&arg);
+
+ return ret;
+
+ invalid:
+ die(flags & PARSE_SHA1_OLD ?
+ "%s %s: invalid <oldvalue>: %s" :
+ "%s %s: invalid <newvalue>: %s",
+ command, refname, arg.buf);
+
+ eof:
+ die(flags & PARSE_SHA1_OLD ?
+ "%s %s: unexpected end of input when reading <oldvalue>" :
+ "%s %s: unexpected end of input when reading <newvalue>",
+ command, refname);
+}
+
+
+/*
+ * The following five parse_cmd_*() functions parse the corresponding
+ * command. In each case, next points at the character following the
+ * command name and the following space. They each return a pointer
+ * to the character terminating the command, and die with an
+ * explanatory message if there are any parsing problems. All of
+ * these functions handle either text or binary format input,
+ * depending on how line_termination is set.
+ */
+
+static const char *parse_cmd_update(struct ref_transaction *transaction,
+ struct strbuf *input, const char *next)
+{
+ struct strbuf err = STRBUF_INIT;
+ char *refname;
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+ int have_old;
+
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("update: missing <ref>");
+
+ if (parse_next_sha1(input, &next, new_sha1, "update", refname,
+ PARSE_SHA1_ALLOW_EMPTY))
+ die("update %s: missing <newvalue>", refname);
+
+ have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname,
+ PARSE_SHA1_OLD);
+
+ if (*next != line_termination)
+ die("update %s: extra input: %s", refname, next);
+
+ if (ref_transaction_update(transaction, refname, new_sha1, old_sha1,
+ update_flags, have_old, msg, &err))
+ die("%s", err.buf);
+
+ update_flags = 0;
+ free(refname);
+ strbuf_release(&err);
+
+ return next;
+}
+
+static const char *parse_cmd_create(struct ref_transaction *transaction,
+ struct strbuf *input, const char *next)
+{
+ struct strbuf err = STRBUF_INIT;
+ char *refname;
+ unsigned char new_sha1[20];
+
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("create: missing <ref>");
+
+ if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0))
+ die("create %s: missing <newvalue>", refname);
+
+ if (is_null_sha1(new_sha1))
+ die("create %s: zero <newvalue>", refname);
+
+ if (*next != line_termination)
+ die("create %s: extra input: %s", refname, next);
+
+ if (ref_transaction_create(transaction, refname, new_sha1,
+ update_flags, msg, &err))
+ die("%s", err.buf);
+
+ update_flags = 0;
+ free(refname);
+ strbuf_release(&err);
+
+ return next;
+}
+
+static const char *parse_cmd_delete(struct ref_transaction *transaction,
+ struct strbuf *input, const char *next)
+{
+ struct strbuf err = STRBUF_INIT;
+ char *refname;
+ unsigned char old_sha1[20];
+ int have_old;
+
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("delete: missing <ref>");
+
+ if (parse_next_sha1(input, &next, old_sha1, "delete", refname,
+ PARSE_SHA1_OLD)) {
+ have_old = 0;
+ } else {
+ if (is_null_sha1(old_sha1))
+ die("delete %s: zero <oldvalue>", refname);
+ have_old = 1;
+ }
+
+ if (*next != line_termination)
+ die("delete %s: extra input: %s", refname, next);
+
+ if (ref_transaction_delete(transaction, refname, old_sha1,
+ update_flags, have_old, msg, &err))
+ die("%s", err.buf);
+
+ update_flags = 0;
+ free(refname);
+ strbuf_release(&err);
+
+ return next;
+}
+
+static const char *parse_cmd_verify(struct ref_transaction *transaction,
+ struct strbuf *input, const char *next)
+{
+ struct strbuf err = STRBUF_INIT;
+ char *refname;
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("verify: missing <ref>");
+
+ if (parse_next_sha1(input, &next, old_sha1, "verify", refname,
+ PARSE_SHA1_OLD))
+ hashclr(old_sha1);
+
+ hashcpy(new_sha1, old_sha1);
+
+ if (*next != line_termination)
+ die("verify %s: extra input: %s", refname, next);
+
+ if (ref_transaction_update(transaction, refname, new_sha1, old_sha1,
+ update_flags, 1, msg, &err))
+ die("%s", err.buf);
+
+ update_flags = 0;
+ free(refname);
+ strbuf_release(&err);
+
+ return next;
+}
+
+static const char *parse_cmd_option(struct strbuf *input, const char *next)
+{
+ if (!strncmp(next, "no-deref", 8) && next[8] == line_termination)
+ update_flags |= REF_NODEREF;
+ else
+ die("option unknown: %s", next);
+ return next + 8;
+}
+
+static void update_refs_stdin(struct ref_transaction *transaction)
+{
+ struct strbuf input = STRBUF_INIT;
+ const char *next;
+
+ if (strbuf_read(&input, 0, 1000) < 0)
+ die_errno("could not read from stdin");
+ next = input.buf;
+ /* Read each line dispatch its command */
+ while (next < input.buf + input.len) {
+ if (*next == line_termination)
+ die("empty command in input");
+ else if (isspace(*next))
+ die("whitespace before command: %s", next);
+ else if (starts_with(next, "update "))
+ next = parse_cmd_update(transaction, &input, next + 7);
+ else if (starts_with(next, "create "))
+ next = parse_cmd_create(transaction, &input, next + 7);
+ else if (starts_with(next, "delete "))
+ next = parse_cmd_delete(transaction, &input, next + 7);
+ else if (starts_with(next, "verify "))
+ next = parse_cmd_verify(transaction, &input, next + 7);
+ else if (starts_with(next, "option "))
+ next = parse_cmd_option(&input, next + 7);
+ else
+ die("unknown command: %s", next);
+
+ next++;
+ }
+
+ strbuf_release(&input);
+}
+
int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
- const char *refname, *oldval, *msg = NULL;
+ const char *refname, *oldval;
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, "reason", "reason of the update"),
- OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"),
- OPT_BOOLEAN( 0 , "no-deref", &no_deref,
- "update <refname> not the one it points to"),
+ OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
+ 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 +370,28 @@ 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) {
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction)
+ die("%s", err.buf);
+ if (delete || no_deref || argc > 0)
+ usage_with_options(git_update_ref_usage, options);
+ if (end_null)
+ line_termination = '\0';
+ update_refs_stdin(transaction);
+ if (ref_transaction_commit(transaction, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ return 0;
+ }
+
+ 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);
@@ -54,5 +418,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
else
return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
- flags, DIE_ON_ERR);
+ flags, UPDATE_REFS_DIE_ON_ERR);
}
diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c
index b90dce6..6c8cc3e 100644
--- a/builtin/update-server-info.c
+++ b/builtin/update-server-info.c
@@ -3,7 +3,7 @@
#include "parse-options.h"
static const char * const update_server_info_usage[] = {
- "git update-server-info [--force]",
+ N_("git update-server-info [--force]"),
NULL
};
@@ -11,10 +11,11 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix)
{
int force = 0;
struct option options[] = {
- OPT__FORCE(&force, "update the info files from scratch"),
+ OPT__FORCE(&force, N_("update the info files from scratch")),
OPT_END()
};
+ git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options,
update_server_info_usage, 0);
if (argc > 0)
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index 2d0b383..32ab94c 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -6,6 +6,8 @@
#include "archive.h"
#include "pkt-line.h"
#include "sideband.h"
+#include "run-command.h"
+#include "argv-array.h"
static const char upload_archive_usage[] =
"git upload-archive <repo>";
@@ -13,58 +15,35 @@ static const char upload_archive_usage[] =
static const char deadchild[] =
"git upload-archive: archiver died with error";
-static const char lostchild[] =
-"git upload-archive: archiver process was lost";
-
#define MAX_ARGS (64)
-static int run_upload_archive(int argc, const char **argv, const char *prefix)
+int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
{
- const char *sent_argv[MAX_ARGS];
+ struct argv_array sent_argv = ARGV_ARRAY_INIT;
const char *arg_cmd = "argument ";
- char *p, buf[4096];
- int sent_argc;
- int len;
if (argc != 2)
usage(upload_archive_usage);
- if (strlen(argv[1]) + 1 > sizeof(buf))
- die("insanely long repository name");
-
- strcpy(buf, argv[1]); /* enter-repo smudges its argument */
-
- if (!enter_repo(buf, 0))
- die("'%s' does not appear to be a git repository", buf);
+ if (!enter_repo(argv[1], 0))
+ die("'%s' does not appear to be a git repository", argv[1]);
/* put received options in sent_argv[] */
- sent_argc = 1;
- sent_argv[0] = "git-upload-archive";
- for (p = buf;;) {
- /* This will die if not enough free space in buf */
- len = packet_read_line(0, p, (buf + sizeof buf) - p);
- if (len == 0)
+ argv_array_push(&sent_argv, "git-upload-archive");
+ for (;;) {
+ char *buf = packet_read_line(0, NULL);
+ if (!buf)
break; /* got a flush */
- if (sent_argc > MAX_ARGS - 2)
- die("Too many options (>%d)", MAX_ARGS - 2);
+ if (sent_argv.argc > MAX_ARGS)
+ die("Too many options (>%d)", MAX_ARGS - 1);
- if (p[len-1] == '\n') {
- p[--len] = 0;
- }
- if (len < strlen(arg_cmd) ||
- strncmp(arg_cmd, p, strlen(arg_cmd)))
+ if (!starts_with(buf, arg_cmd))
die("'argument' token or flush expected");
-
- len -= strlen(arg_cmd);
- memmove(p, p + strlen(arg_cmd), len);
- sent_argv[sent_argc++] = p;
- p += len;
- *p++ = 0;
+ argv_array_push(&sent_argv, buf + strlen(arg_cmd));
}
- sent_argv[sent_argc] = NULL;
/* parse all options sent by the client */
- return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1);
+ return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1);
}
__attribute__((format (printf, 1, 2)))
@@ -96,8 +75,8 @@ static ssize_t process_input(int child_fd, int band)
int cmd_upload_archive(int argc, const char **argv, const char *prefix)
{
- pid_t writer;
- int fd1[2], fd2[2];
+ struct child_process writer = { argv };
+
/*
* Set up sideband subprocess.
*
@@ -105,39 +84,24 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
* multiplexed out to our fd#1. If the child dies, we tell the other
* end over channel #3.
*/
- if (pipe(fd1) < 0 || pipe(fd2) < 0) {
+ argv[0] = "upload-archive--writer";
+ writer.out = writer.err = -1;
+ writer.git_cmd = 1;
+ if (start_command(&writer)) {
int err = errno;
- packet_write(1, "NACK pipe failed on the remote side\n");
+ packet_write(1, "NACK unable to spawn subprocess\n");
die("upload-archive: %s", strerror(err));
}
- writer = fork();
- if (writer < 0) {
- int err = errno;
- packet_write(1, "NACK fork failed on the remote side\n");
- die("upload-archive: %s", strerror(err));
- }
- if (!writer) {
- /* child - connect fd#1 and fd#2 to the pipe */
- dup2(fd1[1], 1);
- dup2(fd2[1], 2);
- close(fd1[1]); close(fd2[1]);
- close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
-
- exit(run_upload_archive(argc, argv, prefix));
- }
- /* parent - read from child, multiplex and send out to fd#1 */
- close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
packet_write(1, "ACK\n");
packet_flush(1);
while (1) {
struct pollfd pfd[2];
- int status;
- pfd[0].fd = fd1[0];
+ pfd[0].fd = writer.out;
pfd[0].events = POLLIN;
- pfd[1].fd = fd2[0];
+ pfd[1].fd = writer.err;
pfd[1].events = POLLIN;
if (poll(pfd, 2, -1) < 0) {
if (errno != EINTR) {
@@ -156,9 +120,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
if (process_input(pfd[0].fd, 1))
continue;
- if (waitpid(writer, &status, 0) < 0)
- error_clnt("%s", lostchild);
- else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+ if (finish_command(&writer))
error_clnt("%s", deadchild);
packet_flush(1);
break;
diff --git a/builtin/var.c b/builtin/var.c
index 99d068a..aedbb53 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -11,7 +11,7 @@ static const char *editor(int flag)
{
const char *pgm = git_editor();
- if (!pgm && flag & IDENT_ERROR_ON_NO_NAME)
+ if (!pgm && flag & IDENT_STRICT)
die("Terminal is dumb, but EDITOR unset");
return pgm;
@@ -55,7 +55,7 @@ static const char *read_var(const char *var)
val = NULL;
for (ptr = git_vars; ptr->read; ptr++) {
if (strcmp(var, ptr->name) == 0) {
- val = ptr->read(IDENT_ERROR_ON_NO_NAME);
+ val = ptr->read(IDENT_STRICT);
break;
}
}
diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c
new file mode 100644
index 0000000..b0f85042
--- /dev/null
+++ b/builtin/verify-commit.c
@@ -0,0 +1,93 @@
+/*
+ * Builtin "git commit-commit"
+ *
+ * Copyright (c) 2014 Michael J Gruber <git@drmicha.warpmail.net>
+ *
+ * Based on git-verify-tag
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "commit.h"
+#include "run-command.h"
+#include <signal.h>
+#include "parse-options.h"
+#include "gpg-interface.h"
+
+static const char * const verify_commit_usage[] = {
+ N_("git verify-commit [-v|--verbose] <commit>..."),
+ NULL
+};
+
+static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose)
+{
+ struct signature_check signature_check;
+
+ memset(&signature_check, 0, sizeof(signature_check));
+
+ check_commit_signature(lookup_commit(sha1), &signature_check);
+
+ if (verbose && signature_check.payload)
+ fputs(signature_check.payload, stdout);
+
+ if (signature_check.gpg_output)
+ fputs(signature_check.gpg_output, stderr);
+
+ signature_check_clear(&signature_check);
+ return signature_check.result != 'G';
+}
+
+static int verify_commit(const char *name, int verbose)
+{
+ enum object_type type;
+ unsigned char sha1[20];
+ char *buf;
+ unsigned long size;
+ int ret;
+
+ if (get_sha1(name, sha1))
+ return error("commit '%s' not found.", name);
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf)
+ return error("%s: unable to read file.", name);
+ if (type != OBJ_COMMIT)
+ return error("%s: cannot verify a non-commit object of type %s.",
+ name, typename(type));
+
+ ret = run_gpg_verify(sha1, buf, size, verbose);
+
+ free(buf);
+ return ret;
+}
+
+static int git_verify_commit_config(const char *var, const char *value, void *cb)
+{
+ int status = git_gpg_config(var, value, cb);
+ if (status)
+ return status;
+ return git_default_config(var, value, cb);
+}
+
+int cmd_verify_commit(int argc, const char **argv, const char *prefix)
+{
+ int i = 1, verbose = 0, had_error = 0;
+ const struct option verify_commit_options[] = {
+ OPT__VERBOSE(&verbose, N_("print commit contents")),
+ OPT_END()
+ };
+
+ git_config(git_verify_commit_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, verify_commit_options,
+ verify_commit_usage, PARSE_OPT_KEEP_ARGV0);
+ if (argc <= i)
+ usage_with_options(verify_commit_usage, verify_commit_options);
+
+ /* sometimes the program was terminated because this signal
+ * was received in the process of writing the gpg input: */
+ signal(SIGPIPE, SIG_IGN);
+ while (i < argc)
+ if (verify_commit(argv[i++], verbose))
+ had_error = 1;
+ return had_error;
+}
diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c
index e841b4a..7747537 100644
--- a/builtin/verify-pack.c
+++ b/builtin/verify-pack.c
@@ -8,7 +8,7 @@
static int verify_one_pack(const char *path, unsigned int flags)
{
- struct child_process index_pack;
+ struct child_process index_pack = CHILD_PROCESS_INIT;
const char *argv[] = {"index-pack", NULL, NULL, NULL };
struct strbuf arg = STRBUF_INIT;
int verbose = flags & VERIFY_PACK_VERBOSE;
@@ -27,13 +27,11 @@ static int verify_one_pack(const char *path, unsigned int flags)
* normalize these forms to "foo.pack" for "index-pack --verify".
*/
strbuf_addstr(&arg, path);
- if (has_extension(arg.buf, ".idx"))
- strbuf_splice(&arg, arg.len - 3, 3, "pack", 4);
- else if (!has_extension(arg.buf, ".pack"))
- strbuf_add(&arg, ".pack", 5);
+ if (strbuf_strip_suffix(&arg, ".idx") ||
+ !ends_with(arg.buf, ".pack"))
+ strbuf_addstr(&arg, ".pack");
argv[2] = arg.buf;
- memset(&index_pack, 0, sizeof(index_pack));
index_pack.argv = argv;
index_pack.git_cmd = 1;
@@ -53,7 +51,7 @@ static int verify_one_pack(const char *path, unsigned int flags)
}
static const char * const verify_pack_usage[] = {
- "git verify-pack [-v|--verbose] [-s|--stat-only] <pack>...",
+ N_("git verify-pack [-v|--verbose] [-s|--stat-only] <pack>..."),
NULL
};
@@ -63,9 +61,9 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
unsigned int flags = 0;
int i;
const struct option verify_pack_options[] = {
- OPT_BIT('v', "verbose", &flags, "verbose",
+ OPT_BIT('v', "verbose", &flags, N_("verbose"),
VERIFY_PACK_VERBOSE),
- OPT_BIT('s', "stat-only", &flags, "show statistics only",
+ OPT_BIT('s', "stat-only", &flags, N_("show statistics only"),
VERIFY_PACK_STAT_ONLY),
OPT_END()
};
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 3134766..9cdf332 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -11,50 +11,25 @@
#include "run-command.h"
#include <signal.h>
#include "parse-options.h"
+#include "gpg-interface.h"
static const char * const verify_tag_usage[] = {
- "git verify-tag [-v|--verbose] <tag>...",
+ N_("git verify-tag [-v|--verbose] <tag>..."),
NULL
};
static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
{
- struct child_process gpg;
- const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
- char path[PATH_MAX];
- size_t len;
- int fd, ret;
-
- fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
- if (fd < 0)
- return error("could not create temporary file '%s': %s",
- path, strerror(errno));
- if (write_in_full(fd, buf, size) < 0)
- return error("failed writing temporary file '%s': %s",
- path, strerror(errno));
- close(fd);
-
- /* find the length without signature */
+ int len;
+
len = parse_signature(buf, size);
if (verbose)
write_in_full(1, buf, len);
- memset(&gpg, 0, sizeof(gpg));
- gpg.argv = args_gpg;
- gpg.in = -1;
- args_gpg[2] = path;
- if (start_command(&gpg)) {
- unlink(path);
- return error("could not run gpg.");
- }
-
- write_in_full(gpg.in, buf, len);
- close(gpg.in);
- ret = finish_command(&gpg);
+ if (size == len)
+ return error("no signature found");
- unlink_or_warn(path);
-
- return ret;
+ return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL);
}
static int verify_tag(const char *name, int verbose)
@@ -83,15 +58,23 @@ static int verify_tag(const char *name, int verbose)
return ret;
}
+static int git_verify_tag_config(const char *var, const char *value, void *cb)
+{
+ int status = git_gpg_config(var, value, cb);
+ if (status)
+ return status;
+ return git_default_config(var, value, cb);
+}
+
int cmd_verify_tag(int argc, const char **argv, const char *prefix)
{
int i = 1, verbose = 0, had_error = 0;
const struct option verify_tag_options[] = {
- OPT__VERBOSE(&verbose, "print tag contents"),
+ OPT__VERBOSE(&verbose, N_("print tag contents")),
OPT_END()
};
- git_config(git_default_config, NULL);
+ git_config(git_verify_tag_config, NULL);
argc = parse_options(argc, argv, prefix, verify_tag_options,
verify_tag_usage, PARSE_OPT_KEEP_ARGV0);
diff --git a/builtin/write-tree.c b/builtin/write-tree.c
index b223af4..084c0df 100644
--- a/builtin/write-tree.c
+++ b/builtin/write-tree.c
@@ -10,7 +10,7 @@
#include "parse-options.h"
static const char * const write_tree_usage[] = {
- "git write-tree [--missing-ok] [--prefix=<prefix>/]",
+ N_("git write-tree [--missing-ok] [--prefix=<prefix>/]"),
NULL
};
@@ -21,13 +21,13 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
unsigned char sha1[20];
const char *me = "git-write-tree";
struct option write_tree_options[] = {
- OPT_BIT(0, "missing-ok", &flags, "allow missing objects",
+ OPT_BIT(0, "missing-ok", &flags, N_("allow missing objects"),
WRITE_TREE_MISSING_OK),
- { OPTION_STRING, 0, "prefix", &prefix, "<prefix>/",
- "write tree object for a subdirectory <prefix>" ,
+ { OPTION_STRING, 0, "prefix", &prefix, N_("<prefix>/"),
+ N_("write tree object for a subdirectory <prefix>") ,
PARSE_OPT_LITERAL_ARGHELP },
{ OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL,
- "only useful for debugging",
+ N_("only useful for debugging"),
PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL,
WRITE_TREE_IGNORE_CACHE_TREE },
OPT_END()