summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/add.c238
-rw-r--r--builtin/am.c2423
-rw-r--r--builtin/annotate.c12
-rw-r--r--builtin/apply.c4508
-rw-r--r--builtin/archive.c10
-rw-r--r--builtin/blame.c1423
-rw-r--r--builtin/branch.c785
-rw-r--r--builtin/bundle.c8
-rw-r--r--builtin/cat-file.c411
-rw-r--r--builtin/check-attr.c95
-rw-r--r--builtin/check-ignore.c24
-rw-r--r--builtin/check-mailmap.c4
-rw-r--r--builtin/check-ref-format.c4
-rw-r--r--builtin/checkout-index.c127
-rw-r--r--builtin/checkout.c289
-rw-r--r--builtin/clean.c96
-rw-r--r--builtin/clone.c502
-rw-r--r--builtin/column.c4
-rw-r--r--builtin/commit-tree.c34
-rw-r--r--builtin/commit.c718
-rw-r--r--builtin/config.c374
-rw-r--r--builtin/count-objects.c141
-rw-r--r--builtin/describe.c111
-rw-r--r--builtin/diff-files.c3
-rw-r--r--builtin/diff-index.c3
-rw-r--r--builtin/diff-tree.c32
-rw-r--r--builtin/diff.c53
-rw-r--r--builtin/difftool.c692
-rw-r--r--builtin/fast-export.c399
-rw-r--r--builtin/fetch-pack.c70
-rw-r--r--builtin/fetch.c527
-rw-r--r--builtin/fmt-merge-msg.c69
-rw-r--r--builtin/for-each-ref.c1113
-rw-r--r--builtin/fsck.c535
-rw-r--r--builtin/gc.c279
-rw-r--r--builtin/get-tar-commit-id.c7
-rw-r--r--builtin/grep.c583
-rw-r--r--builtin/hash-object.c125
-rw-r--r--builtin/help.c99
-rw-r--r--builtin/index-pack.c527
-rw-r--r--builtin/init-db.c425
-rw-r--r--builtin/interpret-trailers.c49
-rw-r--r--builtin/log.c534
-rw-r--r--builtin/ls-files.c287
-rw-r--r--builtin/ls-remote.c96
-rw-r--r--builtin/ls-tree.c41
-rw-r--r--builtin/mailinfo.c1036
-rw-r--r--builtin/mailsplit.c83
-rw-r--r--builtin/merge-base.c21
-rw-r--r--builtin/merge-file.c23
-rw-r--r--builtin/merge-index.c8
-rw-r--r--builtin/merge-recursive.c41
-rw-r--r--builtin/merge-tree.c47
-rw-r--r--builtin/merge.c603
-rw-r--r--builtin/mktag.c2
-rw-r--r--builtin/mktree.c28
-rw-r--r--builtin/mv.c259
-rw-r--r--builtin/name-rev.c131
-rw-r--r--builtin/notes.c459
-rw-r--r--builtin/pack-objects.c725
-rw-r--r--builtin/pack-redundant.c4
-rw-r--r--builtin/pack-refs.c2
-rw-r--r--builtin/patch-id.c109
-rw-r--r--builtin/prune-packed.c72
-rw-r--r--builtin/prune.c99
-rw-r--r--builtin/pull.c880
-rw-r--r--builtin/push.c207
-rw-r--r--builtin/read-tree.c61
-rw-r--r--builtin/rebase--helper.c40
-rw-r--r--builtin/receive-pack.c1247
-rw-r--r--builtin/reflog.c341
-rw-r--r--builtin/remote-ext.c75
-rw-r--r--builtin/remote.c327
-rw-r--r--builtin/repack.c154
-rw-r--r--builtin/replace.c406
-rw-r--r--builtin/rerere.c34
-rw-r--r--builtin/reset.c107
-rw-r--r--builtin/rev-list.c140
-rw-r--r--builtin/rev-parse.c256
-rw-r--r--builtin/revert.c65
-rw-r--r--builtin/rm.c121
-rw-r--r--builtin/send-pack.c202
-rw-r--r--builtin/shortlog.c218
-rw-r--r--builtin/show-branch.c233
-rw-r--r--builtin/show-ref.c79
-rw-r--r--builtin/stripspace.c126
-rw-r--r--builtin/submodule--helper.c1170
-rw-r--r--builtin/symbolic-ref.c12
-rw-r--r--builtin/tag.c428
-rw-r--r--builtin/unpack-file.c2
-rw-r--r--builtin/unpack-objects.c53
-rw-r--r--builtin/update-index.c405
-rw-r--r--builtin/update-ref.c489
-rw-r--r--builtin/upload-archive.c18
-rw-r--r--builtin/verify-commit.c94
-rw-r--r--builtin/verify-pack.c12
-rw-r--r--builtin/verify-tag.c75
-rw-r--r--builtin/worktree.c550
98 files changed, 17457 insertions, 13811 deletions
diff --git a/builtin/add.c b/builtin/add.c
index 672adc01ff..9f53f020d0 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -5,6 +5,7 @@
*/
#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "dir.h"
#include "pathspec.h"
#include "exec_cmd.h"
@@ -15,9 +16,10 @@
#include "diffcore.h"
#include "revision.h"
#include "bulk-checkin.h"
+#include "argv-array.h"
static const char * const builtin_add_usage[] = {
- N_("git add [options] [--] <pathspec>..."),
+ N_("git add [<options>] [--] <pathspec>..."),
NULL
};
static int patch_interactive, add_interactive, edit_interactive;
@@ -26,53 +28,21 @@ static int take_worktree_changes;
struct update_callback_data {
int flags;
int add_errors;
- const char *implicit_dot;
- size_t implicit_dot_len;
-
- /* only needed for 2.0 transition preparation */
- int warn_add_would_remove;
};
-static const char *option_with_implicit_dot;
-static const char *short_option_with_implicit_dot;
-
-static void warn_pathless_add(void)
+static void chmod_pathspec(struct pathspec *pathspec, int force_mode)
{
- static int shown;
- assert(option_with_implicit_dot && short_option_with_implicit_dot);
+ int i;
- if (shown)
- return;
- shown = 1;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
- /*
- * To be consistent with "git add -p" and most Git
- * commands, we should default to being tree-wide, but
- * this is not the original behavior and can't be
- * changed until users trained themselves not to type
- * "git add -u" or "git add -A". For now, we warn and
- * keep the old behavior. Later, the behavior can be changed
- * to tree-wide, keeping the warning for a while, and
- * eventually we can drop the warning.
- */
- warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n"
- "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n"
- "To add content for the whole tree, run:\n"
- "\n"
- " git add %s :/\n"
- " (or git add %s :/)\n"
- "\n"
- "To restrict the command to the current directory, run:\n"
- "\n"
- " git add %s .\n"
- " (or git add %s .)\n"
- "\n"
- "With the current Git version, the command is restricted to "
- "the current directory.\n"
- ""),
- option_with_implicit_dot, short_option_with_implicit_dot,
- option_with_implicit_dot, short_option_with_implicit_dot,
- option_with_implicit_dot, short_option_with_implicit_dot);
+ if (pathspec && !ce_path_match(ce, pathspec, NULL))
+ continue;
+
+ if (chmod_cache_entry(ce, force_mode) < 0)
+ fprintf(stderr, "cannot chmod '%s'", ce->name);
+ }
}
static int fix_unmerged_status(struct diff_filepair *p,
@@ -96,65 +66,27 @@ static int fix_unmerged_status(struct diff_filepair *p,
return DIFF_STATUS_MODIFIED;
}
-static const char *add_would_remove_warning = N_(
- "You ran 'git add' with neither '-A (--all)' or '--ignore-removal',\n"
-"whose behaviour will change in Git 2.0 with respect to paths you removed.\n"
-"Paths like '%s' that are\n"
-"removed from your working tree are ignored with this version of Git.\n"
-"\n"
-"* 'git add --ignore-removal <pathspec>', which is the current default,\n"
-" ignores paths you removed from your working tree.\n"
-"\n"
-"* 'git add --all <pathspec>' will let you also record the removals.\n"
-"\n"
-"Run 'git status' to check the paths you removed from your working tree.\n");
-
-static void warn_add_would_remove(const char *path)
-{
- warning(_(add_would_remove_warning), path);
-}
-
static void update_callback(struct diff_queue_struct *q,
struct diff_options *opt, void *cbdata)
{
int i;
struct update_callback_data *data = cbdata;
- const char *implicit_dot = data->implicit_dot;
- size_t implicit_dot_len = data->implicit_dot_len;
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
const char *path = p->one->path;
- /*
- * Check if "git add -A" or "git add -u" was run from a
- * subdirectory with a modified file outside that directory,
- * and warn if so.
- *
- * "git add -u" will behave like "git add -u :/" instead of
- * "git add -u ." in the future. This warning prepares for
- * that change.
- */
- if (implicit_dot &&
- strncmp_icase(path, implicit_dot, implicit_dot_len)) {
- warn_pathless_add();
- continue;
- }
switch (fix_unmerged_status(p, data)) {
default:
die(_("unexpected diff status %c"), p->status);
case DIFF_STATUS_MODIFIED:
case DIFF_STATUS_TYPE_CHANGED:
- if (add_file_to_index(&the_index, path, data->flags)) {
+ if (add_file_to_index(&the_index, path, data->flags)) {
if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
die(_("updating files failed"));
data->add_errors++;
}
break;
case DIFF_STATUS_DELETED:
- if (data->warn_add_would_remove) {
- warn_add_would_remove(path);
- data->warn_add_would_remove = 0;
- }
if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
break;
if (!(data->flags & ADD_CACHE_PRETEND))
@@ -166,37 +98,28 @@ static void update_callback(struct diff_queue_struct *q,
}
}
-static void update_files_in_cache(const char *prefix,
- const struct pathspec *pathspec,
- struct update_callback_data *data)
+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);
if (pathspec)
copy_pathspec(&rev.prune_data, pathspec);
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
- rev.diffopt.format_callback_data = data;
+ 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);
-}
-
-int add_files_to_cache(const char *prefix,
- const struct pathspec *pathspec, int flags)
-{
- struct update_callback_data data;
-
- memset(&data, 0, sizeof(data));
- data.flags = flags;
- update_files_in_cache(prefix, pathspec, &data);
return !!data.add_errors;
}
-#define WARN_IMPLICIT_DOT (1u << 0)
-static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
- int prefix, unsigned flag)
+static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix)
{
char *seen;
int i;
@@ -210,16 +133,6 @@ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
struct dir_entry *entry = *src++;
if (dir_path_match(entry, pathspec, prefix, seen))
*dst++ = entry;
- else if (flag & WARN_IMPLICIT_DOT)
- /*
- * "git add -A" was run from a subdirectory with a
- * new file outside that directory.
- *
- * "git add -A" will behave like "git add -A :/"
- * instead of "git add -A ." in the future.
- * Warn about the coming behavior change.
- */
- warn_pathless_add();
}
dir->nr = dst - dir->entries;
add_pathspec_matches_against_index(pathspec, seen);
@@ -245,23 +158,21 @@ static void refresh(int verbose, const struct pathspec *pathspec)
int run_add_interactive(const char *revision, const char *patch_mode,
const struct pathspec *pathspec)
{
- int status, ac, i;
- const char **args;
+ int status, i;
+ struct argv_array argv = ARGV_ARRAY_INIT;
- args = xcalloc(sizeof(const char *), (pathspec->nr + 6));
- 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++] = "--";
+ argv_array_push(&argv, revision);
+ argv_array_push(&argv, "--");
for (i = 0; i < pathspec->nr; i++)
/* pass original pathspec, to be re-parsed */
- args[ac++] = pathspec->items[i].original;
+ argv_array_push(&argv, pathspec->items[i].original);
- status = run_command_v_opt(args, RUN_GIT_CMD);
- free(args);
+ status = run_command_v_opt(argv.argv, RUN_GIT_CMD);
+ argv_array_clear(&argv);
return status;
}
@@ -285,7 +196,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
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;
@@ -312,14 +223,14 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
if (run_diff_files(&rev, 0))
die(_("Could not write patch"));
- launch_editor(file, NULL, NULL);
+ if (launch_editor(file, NULL, NULL))
+ die(_("editing patch failed"));
if (stat(file, &st))
die_errno(_("Could not stat '%s'"), file);
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))
@@ -338,10 +249,12 @@ N_("The following paths are ignored by one of your .gitignore files:\n");
static int verbose, show_only, ignored_too, refresh_only;
static int ignore_add_errors, intent_to_add, ignore_missing;
-#define ADDREMOVE_DEFAULT 0 /* Change to 1 in Git 2.0 */
+#define ADDREMOVE_DEFAULT 1
static int addremove = ADDREMOVE_DEFAULT;
static int addremove_explicit = -1; /* unspecified */
+static char *chmod_arg;
+
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 */
@@ -367,6 +280,7 @@ static struct option builtin_add_options[] = {
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_STRING( 0 , "chmod", &chmod_arg, N_("(+/-)x"), N_("override the executable bit of the listed files")),
OPT_END(),
};
@@ -389,11 +303,11 @@ 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++)
- if (add_file_to_cache(dir->entries[i]->name, flags)) {
+ if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
if (!ignore_add_errors)
die(_("adding files failed"));
exit_status = 1;
@@ -404,15 +318,12 @@ 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;
struct pathspec pathspec;
struct dir_struct dir;
int flags;
int add_new_files;
int require_pathspec;
char *seen = NULL;
- int implicit_dot = 0;
- struct update_callback_data update_data;
git_config(add_config, NULL);
@@ -436,50 +347,28 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (addremove && take_worktree_changes)
die(_("-A and -u are mutually incompatible"));
- /*
- * Warn when "git add pathspec..." was given without "-u" or "-A"
- * and pathspec... covers a removed path.
- */
- memset(&update_data, 0, sizeof(update_data));
- if (!take_worktree_changes && addremove_explicit < 0)
- update_data.warn_add_would_remove = 1;
-
if (!take_worktree_changes && addremove_explicit < 0 && argc)
- /*
- * Turn "git add pathspec..." to "git add -A pathspec..."
- * in Git 2.0 but not yet
- */
- ; /* addremove = 1; */
+ /* 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) {
- option_with_implicit_dot = "--all";
- short_option_with_implicit_dot = "-A";
- }
- if (take_worktree_changes) {
- option_with_implicit_dot = "--update";
- short_option_with_implicit_dot = "-u";
- }
- if (option_with_implicit_dot && !argc) {
- static const char *here[2] = { ".", NULL };
- argc = 1;
- argv = here;
- implicit_dot = 1;
- }
+
+ if (chmod_arg && ((chmod_arg[0] != '-' && chmod_arg[0] != '+') ||
+ chmod_arg[1] != 'x' || chmod_arg[2]))
+ die(_("--chmod param '%s' must be either -x or +x"), chmod_arg);
add_new_files = !take_worktree_changes && !refresh_only;
- require_pathspec = !take_worktree_changes;
+ require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
- newfd = hold_locked_index(&lock_file, 1);
+ hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
(show_only ? ADD_CACHE_PRETEND : 0) |
(intent_to_add ? ADD_CACHE_INTENT : 0) |
(ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
(!(addremove || take_worktree_changes)
- ? ADD_CACHE_IGNORE_REMOVAL : 0)) |
- (implicit_dot ? ADD_CACHE_IMPLICIT_DOT : 0);
+ ? ADD_CACHE_IGNORE_REMOVAL : 0));
if (require_pathspec && argc == 0) {
fprintf(stderr, _("Nothing specified, nothing added.\n"));
@@ -502,7 +391,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (add_new_files) {
int baselen;
- struct pathspec empty_pathspec;
/* Set up the default git porcelain excludes */
memset(&dir, 0, sizeof(dir));
@@ -511,20 +399,16 @@ 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, implicit_dot ? &empty_pathspec : &pathspec);
+ baselen = fill_directory(&dir, &pathspec);
if (pathspec.nr)
- seen = prune_directory(&dir, &pathspec, baselen,
- implicit_dot ? WARN_IMPLICIT_DOT : 0);
+ seen = prune_directory(&dir, &pathspec, baselen);
}
if (refresh_only) {
refresh(verbose, &pathspec);
goto finish;
}
- if (implicit_dot && prefix)
- refresh_cache(REFRESH_QUIET);
if (pathspec.nr) {
int i;
@@ -564,30 +448,18 @@ int cmd_add(int argc, const char **argv, const char *prefix)
plug_bulk_checkin();
- if ((flags & ADD_CACHE_IMPLICIT_DOT) && prefix) {
- /*
- * Check for modified files throughout the worktree so
- * update_callback has a chance to warn about changes
- * outside the cwd.
- */
- update_data.implicit_dot = prefix;
- update_data.implicit_dot_len = strlen(prefix);
- free_pathspec(&pathspec);
- memset(&pathspec, 0, sizeof(pathspec));
- }
- update_data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT;
- update_files_in_cache(prefix, &pathspec, &update_data);
+ exit_status |= add_files_to_cache(prefix, &pathspec, flags);
- exit_status |= !!update_data.add_errors;
if (add_new_files)
exit_status |= add_files(&dir, flags);
+ if (chmod_arg && pathspec.nr)
+ chmod_pathspec(&pathspec, chmod_arg[0]);
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/am.c b/builtin/am.c
new file mode 100644
index 0000000000..f7a7a971fb
--- /dev/null
+++ b/builtin/am.c
@@ -0,0 +1,2423 @@
+/*
+ * Builtin "git am"
+ *
+ * Based on git-am.sh by Junio C Hamano.
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+#include "dir.h"
+#include "run-command.h"
+#include "quote.h"
+#include "tempfile.h"
+#include "lockfile.h"
+#include "cache-tree.h"
+#include "refs.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "unpack-trees.h"
+#include "branch.h"
+#include "sequencer.h"
+#include "revision.h"
+#include "merge-recursive.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "notes-utils.h"
+#include "rerere.h"
+#include "prompt.h"
+#include "mailinfo.h"
+#include "apply.h"
+#include "string-list.h"
+
+/**
+ * Returns 1 if the file is empty or does not exist, 0 otherwise.
+ */
+static int is_empty_file(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) < 0) {
+ if (errno == ENOENT)
+ return 1;
+ die_errno(_("could not stat %s"), filename);
+ }
+
+ return !st.st_size;
+}
+
+/**
+ * Returns the length of the first line of msg.
+ */
+static int linelen(const char *msg)
+{
+ return strchrnul(msg, '\n') - msg;
+}
+
+/**
+ * Returns true if `str` consists of only whitespace, false otherwise.
+ */
+static int str_isspace(const char *str)
+{
+ for (; *str; str++)
+ if (!isspace(*str))
+ return 0;
+
+ return 1;
+}
+
+enum patch_format {
+ PATCH_FORMAT_UNKNOWN = 0,
+ PATCH_FORMAT_MBOX,
+ PATCH_FORMAT_STGIT,
+ PATCH_FORMAT_STGIT_SERIES,
+ PATCH_FORMAT_HG,
+ PATCH_FORMAT_MBOXRD
+};
+
+enum keep_type {
+ KEEP_FALSE = 0,
+ KEEP_TRUE, /* pass -k flag to git-mailinfo */
+ KEEP_NON_PATCH /* pass -b flag to git-mailinfo */
+};
+
+enum scissors_type {
+ SCISSORS_UNSET = -1,
+ SCISSORS_FALSE = 0, /* pass --no-scissors to git-mailinfo */
+ SCISSORS_TRUE /* pass --scissors to git-mailinfo */
+};
+
+enum signoff_type {
+ SIGNOFF_FALSE = 0,
+ SIGNOFF_TRUE = 1,
+ SIGNOFF_EXPLICIT /* --signoff was set on the command-line */
+};
+
+struct am_state {
+ /* state directory path */
+ char *dir;
+
+ /* current and last patch numbers, 1-indexed */
+ int cur;
+ int last;
+
+ /* commit metadata and message */
+ char *author_name;
+ char *author_email;
+ char *author_date;
+ char *msg;
+ size_t msg_len;
+
+ /* when --rebasing, records the original commit the patch came from */
+ struct object_id orig_commit;
+
+ /* number of digits in patch filename */
+ int prec;
+
+ /* various operating modes and command line options */
+ int interactive;
+ int threeway;
+ int quiet;
+ int signoff; /* enum signoff_type */
+ int utf8;
+ int keep; /* enum keep_type */
+ int message_id;
+ int scissors; /* enum scissors_type */
+ struct argv_array git_apply_opts;
+ const char *resolvemsg;
+ int committer_date_is_author_date;
+ int ignore_date;
+ int allow_rerere_autoupdate;
+ const char *sign_commit;
+ int rebasing;
+};
+
+/**
+ * Initializes am_state with the default values. The state directory is set to
+ * dir.
+ */
+static void am_state_init(struct am_state *state, const char *dir)
+{
+ int gpgsign;
+
+ memset(state, 0, sizeof(*state));
+
+ assert(dir);
+ state->dir = xstrdup(dir);
+
+ state->prec = 4;
+
+ git_config_get_bool("am.threeway", &state->threeway);
+
+ state->utf8 = 1;
+
+ git_config_get_bool("am.messageid", &state->message_id);
+
+ state->scissors = SCISSORS_UNSET;
+
+ argv_array_init(&state->git_apply_opts);
+
+ if (!git_config_get_bool("commit.gpgsign", &gpgsign))
+ state->sign_commit = gpgsign ? "" : NULL;
+}
+
+/**
+ * Releases memory allocated by an am_state.
+ */
+static void am_state_release(struct am_state *state)
+{
+ free(state->dir);
+ free(state->author_name);
+ free(state->author_email);
+ free(state->author_date);
+ free(state->msg);
+ argv_array_clear(&state->git_apply_opts);
+}
+
+/**
+ * Returns path relative to the am_state directory.
+ */
+static inline const char *am_path(const struct am_state *state, const char *path)
+{
+ return mkpath("%s/%s", state->dir, path);
+}
+
+/**
+ * For convenience to call write_file()
+ */
+static void write_state_text(const struct am_state *state,
+ const char *name, const char *string)
+{
+ write_file(am_path(state, name), "%s", string);
+}
+
+static void write_state_count(const struct am_state *state,
+ const char *name, int value)
+{
+ write_file(am_path(state, name), "%d", value);
+}
+
+static void write_state_bool(const struct am_state *state,
+ const char *name, int value)
+{
+ write_state_text(state, name, value ? "t" : "f");
+}
+
+/**
+ * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline
+ * at the end.
+ */
+static void say(const struct am_state *state, FILE *fp, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (!state->quiet) {
+ vfprintf(fp, fmt, ap);
+ putc('\n', fp);
+ }
+ va_end(ap);
+}
+
+/**
+ * Returns 1 if there is an am session in progress, 0 otherwise.
+ */
+static int am_in_progress(const struct am_state *state)
+{
+ struct stat st;
+
+ if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode))
+ return 0;
+ if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
+ return 0;
+ if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
+ return 0;
+ return 1;
+}
+
+/**
+ * Reads the contents of `file` in the `state` directory into `sb`. Returns the
+ * number of bytes read on success, -1 if the file does not exist. If `trim` is
+ * set, trailing whitespace will be removed.
+ */
+static int read_state_file(struct strbuf *sb, const struct am_state *state,
+ const char *file, int trim)
+{
+ strbuf_reset(sb);
+
+ if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
+ if (trim)
+ strbuf_trim(sb);
+
+ return sb->len;
+ }
+
+ if (errno == ENOENT)
+ return -1;
+
+ die_errno(_("could not read '%s'"), am_path(state, file));
+}
+
+/**
+ * Take a series of KEY='VALUE' lines where VALUE part is
+ * sq-quoted, and append <KEY, VALUE> at the end of the string list
+ */
+static int parse_key_value_squoted(char *buf, struct string_list *list)
+{
+ while (*buf) {
+ struct string_list_item *item;
+ char *np;
+ char *cp = strchr(buf, '=');
+ if (!cp)
+ return -1;
+ np = strchrnul(cp, '\n');
+ *cp++ = '\0';
+ item = string_list_append(list, buf);
+
+ buf = np + (*np == '\n');
+ *np = '\0';
+ cp = sq_dequote(cp);
+ if (!cp)
+ return -1;
+ item->util = xstrdup(cp);
+ }
+ return 0;
+}
+
+/**
+ * Reads and parses the state directory's "author-script" file, and sets
+ * state->author_name, state->author_email and state->author_date accordingly.
+ * Returns 0 on success, -1 if the file could not be parsed.
+ *
+ * The author script is of the format:
+ *
+ * GIT_AUTHOR_NAME='$author_name'
+ * GIT_AUTHOR_EMAIL='$author_email'
+ * GIT_AUTHOR_DATE='$author_date'
+ *
+ * where $author_name, $author_email and $author_date are quoted. We are strict
+ * with our parsing, as the file was meant to be eval'd in the old git-am.sh
+ * script, and thus if the file differs from what this function expects, it is
+ * better to bail out than to do something that the user does not expect.
+ */
+static int read_author_script(struct am_state *state)
+{
+ const char *filename = am_path(state, "author-script");
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list kv = STRING_LIST_INIT_DUP;
+ int retval = -1; /* assume failure */
+ int fd;
+
+ assert(!state->author_name);
+ assert(!state->author_email);
+ assert(!state->author_date);
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ return 0;
+ die_errno(_("could not open '%s' for reading"), filename);
+ }
+ strbuf_read(&buf, fd, 0);
+ close(fd);
+ if (parse_key_value_squoted(buf.buf, &kv))
+ goto finish;
+
+ if (kv.nr != 3 ||
+ strcmp(kv.items[0].string, "GIT_AUTHOR_NAME") ||
+ strcmp(kv.items[1].string, "GIT_AUTHOR_EMAIL") ||
+ strcmp(kv.items[2].string, "GIT_AUTHOR_DATE"))
+ goto finish;
+ state->author_name = kv.items[0].util;
+ state->author_email = kv.items[1].util;
+ state->author_date = kv.items[2].util;
+ retval = 0;
+finish:
+ string_list_clear(&kv, !!retval);
+ strbuf_release(&buf);
+ return retval;
+}
+
+/**
+ * Saves state->author_name, state->author_email and state->author_date in the
+ * state directory's "author-script" file.
+ */
+static void write_author_script(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_NAME=");
+ sq_quote_buf(&sb, state->author_name);
+ strbuf_addch(&sb, '\n');
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_EMAIL=");
+ sq_quote_buf(&sb, state->author_email);
+ strbuf_addch(&sb, '\n');
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_DATE=");
+ sq_quote_buf(&sb, state->author_date);
+ strbuf_addch(&sb, '\n');
+
+ write_state_text(state, "author-script", sb.buf);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Reads the commit message from the state directory's "final-commit" file,
+ * setting state->msg to its contents and state->msg_len to the length of its
+ * contents in bytes.
+ *
+ * Returns 0 on success, -1 if the file does not exist.
+ */
+static int read_commit_msg(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ assert(!state->msg);
+
+ if (read_state_file(&sb, state, "final-commit", 0) < 0) {
+ strbuf_release(&sb);
+ return -1;
+ }
+
+ state->msg = strbuf_detach(&sb, &state->msg_len);
+ return 0;
+}
+
+/**
+ * Saves state->msg in the state directory's "final-commit" file.
+ */
+static void write_commit_msg(const struct am_state *state)
+{
+ const char *filename = am_path(state, "final-commit");
+ write_file_buf(filename, state->msg, state->msg_len);
+}
+
+/**
+ * Loads state from disk.
+ */
+static void am_load(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ if (read_state_file(&sb, state, "next", 1) < 0)
+ die("BUG: state file 'next' does not exist");
+ state->cur = strtol(sb.buf, NULL, 10);
+
+ if (read_state_file(&sb, state, "last", 1) < 0)
+ die("BUG: state file 'last' does not exist");
+ state->last = strtol(sb.buf, NULL, 10);
+
+ if (read_author_script(state) < 0)
+ die(_("could not parse author script"));
+
+ read_commit_msg(state);
+
+ if (read_state_file(&sb, state, "original-commit", 1) < 0)
+ oidclr(&state->orig_commit);
+ else if (get_oid_hex(sb.buf, &state->orig_commit) < 0)
+ die(_("could not parse %s"), am_path(state, "original-commit"));
+
+ read_state_file(&sb, state, "threeway", 1);
+ state->threeway = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "quiet", 1);
+ state->quiet = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "sign", 1);
+ state->signoff = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "utf8", 1);
+ state->utf8 = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "keep", 1);
+ if (!strcmp(sb.buf, "t"))
+ state->keep = KEEP_TRUE;
+ else if (!strcmp(sb.buf, "b"))
+ state->keep = KEEP_NON_PATCH;
+ else
+ state->keep = KEEP_FALSE;
+
+ read_state_file(&sb, state, "messageid", 1);
+ state->message_id = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "scissors", 1);
+ if (!strcmp(sb.buf, "t"))
+ state->scissors = SCISSORS_TRUE;
+ else if (!strcmp(sb.buf, "f"))
+ state->scissors = SCISSORS_FALSE;
+ else
+ state->scissors = SCISSORS_UNSET;
+
+ read_state_file(&sb, state, "apply-opt", 1);
+ argv_array_clear(&state->git_apply_opts);
+ if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)
+ die(_("could not parse %s"), am_path(state, "apply-opt"));
+
+ state->rebasing = !!file_exists(am_path(state, "rebasing"));
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Removes the am_state directory, forcefully terminating the current am
+ * session.
+ */
+static void am_destroy(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, state->dir);
+ remove_dir_recursively(&sb, 0);
+ strbuf_release(&sb);
+}
+
+/**
+ * Runs applypatch-msg hook. Returns its exit code.
+ */
+static int run_applypatch_msg_hook(struct am_state *state)
+{
+ int ret;
+
+ assert(state->msg);
+ ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+
+ if (!ret) {
+ free(state->msg);
+ state->msg = NULL;
+ if (read_commit_msg(state) < 0)
+ die(_("'%s' was deleted by the applypatch-msg hook"),
+ am_path(state, "final-commit"));
+ }
+
+ return ret;
+}
+
+/**
+ * Runs post-rewrite hook. Returns it exit code.
+ */
+static int run_post_rewrite_hook(const struct am_state *state)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ const char *hook = find_hook("post-rewrite");
+ int ret;
+
+ if (!hook)
+ return 0;
+
+ argv_array_push(&cp.args, hook);
+ argv_array_push(&cp.args, "rebase");
+
+ cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
+ cp.stdout_to_stderr = 1;
+
+ ret = run_command(&cp);
+
+ close(cp.in);
+ return ret;
+}
+
+/**
+ * Reads the state directory's "rewritten" file, and copies notes from the old
+ * commits listed in the file to their rewritten commits.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int copy_notes_for_rebase(const struct am_state *state)
+{
+ struct notes_rewrite_cfg *c;
+ struct strbuf sb = STRBUF_INIT;
+ const char *invalid_line = _("Malformed input line: '%s'.");
+ const char *msg = "Notes added by 'git rebase'";
+ FILE *fp;
+ int ret = 0;
+
+ assert(state->rebasing);
+
+ c = init_copy_notes_for_rewrite("rebase");
+ if (!c)
+ return 0;
+
+ fp = xfopen(am_path(state, "rewritten"), "r");
+
+ while (!strbuf_getline_lf(&sb, fp)) {
+ struct object_id from_obj, to_obj;
+
+ if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (get_oid_hex(sb.buf, &from_obj)) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (sb.buf[GIT_SHA1_HEXSZ] != ' ') {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (get_oid_hex(sb.buf + GIT_SHA1_HEXSZ + 1, &to_obj)) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (copy_note_for_rewrite(c, from_obj.hash, to_obj.hash))
+ ret = error(_("Failed to copy notes from '%s' to '%s'"),
+ oid_to_hex(&from_obj), oid_to_hex(&to_obj));
+ }
+
+finish:
+ finish_copy_notes_for_rewrite(c, msg);
+ fclose(fp);
+ strbuf_release(&sb);
+ return ret;
+}
+
+/**
+ * Determines if the file looks like a piece of RFC2822 mail by grabbing all
+ * non-indented lines and checking if they look like they begin with valid
+ * header field names.
+ *
+ * Returns 1 if the file looks like a piece of mail, 0 otherwise.
+ */
+static int is_mail(FILE *fp)
+{
+ const char *header_regex = "^[!-9;-~]+:";
+ struct strbuf sb = STRBUF_INIT;
+ regex_t regex;
+ int ret = 1;
+
+ if (fseek(fp, 0L, SEEK_SET))
+ die_errno(_("fseek failed"));
+
+ if (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))
+ die("invalid pattern: %s", header_regex);
+
+ while (!strbuf_getline(&sb, fp)) {
+ if (!sb.len)
+ break; /* End of header */
+
+ /* Ignore indented folded lines */
+ if (*sb.buf == '\t' || *sb.buf == ' ')
+ continue;
+
+ /* It's a header if it matches header_regex */
+ if (regexec(&regex, sb.buf, 0, NULL, 0)) {
+ ret = 0;
+ goto done;
+ }
+ }
+
+done:
+ regfree(&regex);
+ strbuf_release(&sb);
+ return ret;
+}
+
+/**
+ * Attempts to detect the patch_format of the patches contained in `paths`,
+ * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if
+ * detection fails.
+ */
+static int detect_patch_format(const char **paths)
+{
+ enum patch_format ret = PATCH_FORMAT_UNKNOWN;
+ struct strbuf l1 = STRBUF_INIT;
+ struct strbuf l2 = STRBUF_INIT;
+ struct strbuf l3 = STRBUF_INIT;
+ FILE *fp;
+
+ /*
+ * We default to mbox format if input is from stdin and for directories
+ */
+ if (!*paths || !strcmp(*paths, "-") || is_directory(*paths))
+ return PATCH_FORMAT_MBOX;
+
+ /*
+ * Otherwise, check the first few lines of the first patch, starting
+ * from the first non-blank line, to try to detect its format.
+ */
+
+ fp = xfopen(*paths, "r");
+
+ while (!strbuf_getline(&l1, fp)) {
+ if (l1.len)
+ break;
+ }
+
+ if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) {
+ ret = PATCH_FORMAT_MBOX;
+ goto done;
+ }
+
+ if (starts_with(l1.buf, "# This series applies on GIT commit")) {
+ ret = PATCH_FORMAT_STGIT_SERIES;
+ goto done;
+ }
+
+ if (!strcmp(l1.buf, "# HG changeset patch")) {
+ ret = PATCH_FORMAT_HG;
+ goto done;
+ }
+
+ strbuf_reset(&l2);
+ strbuf_getline(&l2, fp);
+ strbuf_reset(&l3);
+ strbuf_getline(&l3, fp);
+
+ /*
+ * If the second line is empty and the third is a From, Author or Date
+ * entry, this is likely an StGit patch.
+ */
+ if (l1.len && !l2.len &&
+ (starts_with(l3.buf, "From:") ||
+ starts_with(l3.buf, "Author:") ||
+ starts_with(l3.buf, "Date:"))) {
+ ret = PATCH_FORMAT_STGIT;
+ goto done;
+ }
+
+ if (l1.len && is_mail(fp)) {
+ ret = PATCH_FORMAT_MBOX;
+ goto done;
+ }
+
+done:
+ fclose(fp);
+ strbuf_release(&l1);
+ return ret;
+}
+
+/**
+ * Splits out individual email patches from `paths`, where each path is either
+ * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
+ */
+static int split_mail_mbox(struct am_state *state, const char **paths,
+ int keep_cr, int mboxrd)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf last = STRBUF_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "mailsplit");
+ argv_array_pushf(&cp.args, "-d%d", state->prec);
+ argv_array_pushf(&cp.args, "-o%s", state->dir);
+ argv_array_push(&cp.args, "-b");
+ if (keep_cr)
+ argv_array_push(&cp.args, "--keep-cr");
+ if (mboxrd)
+ argv_array_push(&cp.args, "--mboxrd");
+ argv_array_push(&cp.args, "--");
+ argv_array_pushv(&cp.args, paths);
+
+ if (capture_command(&cp, &last, 8))
+ return -1;
+
+ state->cur = 1;
+ state->last = strtol(last.buf, NULL, 10);
+
+ return 0;
+}
+
+/**
+ * Callback signature for split_mail_conv(). The foreign patch should be
+ * read from `in`, and the converted patch (in RFC2822 mail format) should be
+ * written to `out`. Return 0 on success, or -1 on failure.
+ */
+typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);
+
+/**
+ * Calls `fn` for each file in `paths` to convert the foreign patch to the
+ * RFC2822 mail format suitable for parsing with git-mailinfo.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
+ const char **paths, int keep_cr)
+{
+ static const char *stdin_only[] = {"-", NULL};
+ int i;
+
+ if (!*paths)
+ paths = stdin_only;
+
+ for (i = 0; *paths; paths++, i++) {
+ FILE *in, *out;
+ const char *mail;
+ int ret;
+
+ if (!strcmp(*paths, "-"))
+ in = stdin;
+ else
+ in = fopen(*paths, "r");
+
+ if (!in)
+ return error_errno(_("could not open '%s' for reading"),
+ *paths);
+
+ mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
+
+ out = fopen(mail, "w");
+ if (!out)
+ return error_errno(_("could not open '%s' for writing"),
+ mail);
+
+ ret = fn(out, in, keep_cr);
+
+ fclose(out);
+ fclose(in);
+
+ if (ret)
+ return error(_("could not parse patch '%s'"), *paths);
+ }
+
+ state->cur = 1;
+ state->last = i;
+ return 0;
+}
+
+/**
+ * A split_mail_conv() callback that converts an StGit patch to an RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int subject_printed = 0;
+
+ while (!strbuf_getline_lf(&sb, in)) {
+ const char *str;
+
+ if (str_isspace(sb.buf))
+ continue;
+ else if (skip_prefix(sb.buf, "Author:", &str))
+ fprintf(out, "From:%s\n", str);
+ else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date"))
+ fprintf(out, "%s\n", sb.buf);
+ else if (!subject_printed) {
+ fprintf(out, "Subject: %s\n", sb.buf);
+ subject_printed = 1;
+ } else {
+ fprintf(out, "\n%s\n", sb.buf);
+ break;
+ }
+ }
+
+ strbuf_reset(&sb);
+ while (strbuf_fread(&sb, 8192, in) > 0) {
+ fwrite(sb.buf, 1, sb.len, out);
+ strbuf_reset(&sb);
+ }
+
+ strbuf_release(&sb);
+ return 0;
+}
+
+/**
+ * This function only supports a single StGit series file in `paths`.
+ *
+ * Given an StGit series file, converts the StGit patches in the series into
+ * RFC2822 messages suitable for parsing with git-mailinfo, and queues them in
+ * the state directory.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_stgit_series(struct am_state *state, const char **paths,
+ int keep_cr)
+{
+ const char *series_dir;
+ char *series_dir_buf;
+ FILE *fp;
+ struct argv_array patches = ARGV_ARRAY_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
+
+ if (!paths[0] || paths[1])
+ return error(_("Only one StGIT patch series can be applied at once"));
+
+ series_dir_buf = xstrdup(*paths);
+ series_dir = dirname(series_dir_buf);
+
+ fp = fopen(*paths, "r");
+ if (!fp)
+ return error_errno(_("could not open '%s' for reading"), *paths);
+
+ while (!strbuf_getline_lf(&sb, fp)) {
+ if (*sb.buf == '#')
+ continue; /* skip comment lines */
+
+ argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf));
+ }
+
+ fclose(fp);
+ strbuf_release(&sb);
+ free(series_dir_buf);
+
+ ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr);
+
+ argv_array_clear(&patches);
+ return ret;
+}
+
+/**
+ * A split_patches_conv() callback that converts a mercurial patch to a RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ while (!strbuf_getline_lf(&sb, in)) {
+ const char *str;
+
+ if (skip_prefix(sb.buf, "# User ", &str))
+ fprintf(out, "From: %s\n", str);
+ else if (skip_prefix(sb.buf, "# Date ", &str)) {
+ unsigned long timestamp;
+ long tz, tz2;
+ char *end;
+
+ errno = 0;
+ timestamp = strtoul(str, &end, 10);
+ if (errno)
+ return error(_("invalid timestamp"));
+
+ if (!skip_prefix(end, " ", &str))
+ return error(_("invalid Date line"));
+
+ errno = 0;
+ tz = strtol(str, &end, 10);
+ if (errno)
+ return error(_("invalid timezone offset"));
+
+ if (*end)
+ return error(_("invalid Date line"));
+
+ /*
+ * mercurial's timezone is in seconds west of UTC,
+ * however git's timezone is in hours + minutes east of
+ * UTC. Convert it.
+ */
+ tz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60;
+ if (tz > 0)
+ tz2 = -tz2;
+
+ fprintf(out, "Date: %s\n", show_date(timestamp, tz2, DATE_MODE(RFC2822)));
+ } else if (starts_with(sb.buf, "# ")) {
+ continue;
+ } else {
+ fprintf(out, "\n%s\n", sb.buf);
+ break;
+ }
+ }
+
+ strbuf_reset(&sb);
+ while (strbuf_fread(&sb, 8192, in) > 0) {
+ fwrite(sb.buf, 1, sb.len, out);
+ strbuf_reset(&sb);
+ }
+
+ strbuf_release(&sb);
+ return 0;
+}
+
+/**
+ * Splits a list of files/directories into individual email patches. Each path
+ * in `paths` must be a file/directory that is formatted according to
+ * `patch_format`.
+ *
+ * Once split out, the individual email patches will be stored in the state
+ * directory, with each patch's filename being its index, padded to state->prec
+ * digits.
+ *
+ * state->cur will be set to the index of the first mail, and state->last will
+ * be set to the index of the last mail.
+ *
+ * Set keep_cr to 0 to convert all lines ending with \r\n to end with \n, 1
+ * to disable this behavior, -1 to use the default configured setting.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail(struct am_state *state, enum patch_format patch_format,
+ const char **paths, int keep_cr)
+{
+ if (keep_cr < 0) {
+ keep_cr = 0;
+ git_config_get_bool("am.keepcr", &keep_cr);
+ }
+
+ switch (patch_format) {
+ case PATCH_FORMAT_MBOX:
+ return split_mail_mbox(state, paths, keep_cr, 0);
+ case PATCH_FORMAT_STGIT:
+ return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
+ case PATCH_FORMAT_STGIT_SERIES:
+ return split_mail_stgit_series(state, paths, keep_cr);
+ case PATCH_FORMAT_HG:
+ return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr);
+ case PATCH_FORMAT_MBOXRD:
+ return split_mail_mbox(state, paths, keep_cr, 1);
+ default:
+ die("BUG: invalid patch_format");
+ }
+ return -1;
+}
+
+/**
+ * Setup a new am session for applying patches
+ */
+static void am_setup(struct am_state *state, enum patch_format patch_format,
+ const char **paths, int keep_cr)
+{
+ struct object_id curr_head;
+ const char *str;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (!patch_format)
+ patch_format = detect_patch_format(paths);
+
+ if (!patch_format) {
+ fprintf_ln(stderr, _("Patch format detection failed."));
+ exit(128);
+ }
+
+ if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
+ die_errno(_("failed to create directory '%s'"), state->dir);
+
+ if (split_mail(state, patch_format, paths, keep_cr) < 0) {
+ am_destroy(state);
+ die(_("Failed to split patches."));
+ }
+
+ if (state->rebasing)
+ state->threeway = 1;
+
+ write_state_bool(state, "threeway", state->threeway);
+ write_state_bool(state, "quiet", state->quiet);
+ write_state_bool(state, "sign", state->signoff);
+ write_state_bool(state, "utf8", state->utf8);
+
+ switch (state->keep) {
+ case KEEP_FALSE:
+ str = "f";
+ break;
+ case KEEP_TRUE:
+ str = "t";
+ break;
+ case KEEP_NON_PATCH:
+ str = "b";
+ break;
+ default:
+ die("BUG: invalid value for state->keep");
+ }
+
+ write_state_text(state, "keep", str);
+ write_state_bool(state, "messageid", state->message_id);
+
+ switch (state->scissors) {
+ case SCISSORS_UNSET:
+ str = "";
+ break;
+ case SCISSORS_FALSE:
+ str = "f";
+ break;
+ case SCISSORS_TRUE:
+ str = "t";
+ break;
+ default:
+ die("BUG: invalid value for state->scissors");
+ }
+ write_state_text(state, "scissors", str);
+
+ sq_quote_argv(&sb, state->git_apply_opts.argv, 0);
+ write_state_text(state, "apply-opt", sb.buf);
+
+ if (state->rebasing)
+ write_state_text(state, "rebasing", "");
+ else
+ write_state_text(state, "applying", "");
+
+ if (!get_oid("HEAD", &curr_head)) {
+ write_state_text(state, "abort-safety", oid_to_hex(&curr_head));
+ if (!state->rebasing)
+ update_ref_oid("am", "ORIG_HEAD", &curr_head, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
+ } else {
+ write_state_text(state, "abort-safety", "");
+ if (!state->rebasing)
+ delete_ref(NULL, "ORIG_HEAD", NULL, 0);
+ }
+
+ /*
+ * NOTE: Since the "next" and "last" files determine if an am_state
+ * session is in progress, they should be written last.
+ */
+
+ write_state_count(state, "next", state->cur);
+ write_state_count(state, "last", state->last);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Increments the patch pointer, and cleans am_state for the application of the
+ * next patch.
+ */
+static void am_next(struct am_state *state)
+{
+ struct object_id head;
+
+ free(state->author_name);
+ state->author_name = NULL;
+
+ free(state->author_email);
+ state->author_email = NULL;
+
+ free(state->author_date);
+ state->author_date = NULL;
+
+ free(state->msg);
+ state->msg = NULL;
+ state->msg_len = 0;
+
+ unlink(am_path(state, "author-script"));
+ unlink(am_path(state, "final-commit"));
+
+ oidclr(&state->orig_commit);
+ unlink(am_path(state, "original-commit"));
+
+ if (!get_oid("HEAD", &head))
+ write_state_text(state, "abort-safety", oid_to_hex(&head));
+ else
+ write_state_text(state, "abort-safety", "");
+
+ state->cur++;
+ write_state_count(state, "next", state->cur);
+}
+
+/**
+ * Returns the filename of the current patch email.
+ */
+static const char *msgnum(const struct am_state *state)
+{
+ static struct strbuf sb = STRBUF_INIT;
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%0*d", state->prec, state->cur);
+
+ return sb.buf;
+}
+
+/**
+ * Refresh and write index.
+ */
+static void refresh_and_write_cache(void)
+{
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+ refresh_cache(REFRESH_QUIET);
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write index file"));
+}
+
+/**
+ * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn
+ * branch, returns 1 if there are entries in the index, 0 otherwise. If an
+ * strbuf is provided, the space-separated list of files that differ will be
+ * appended to it.
+ */
+static int index_has_changes(struct strbuf *sb)
+{
+ struct object_id head;
+ int i;
+
+ if (!get_sha1_tree("HEAD", head.hash)) {
+ struct diff_options opt;
+
+ diff_setup(&opt);
+ DIFF_OPT_SET(&opt, EXIT_WITH_STATUS);
+ if (!sb)
+ DIFF_OPT_SET(&opt, QUICK);
+ do_diff_cache(head.hash, &opt);
+ diffcore_std(&opt);
+ for (i = 0; sb && i < diff_queued_diff.nr; i++) {
+ if (i)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);
+ }
+ diff_flush(&opt);
+ return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0;
+ } else {
+ for (i = 0; sb && i < active_nr; i++) {
+ if (i)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, active_cache[i]->name);
+ }
+ return !!active_nr;
+ }
+}
+
+/**
+ * Dies with a user-friendly message on how to proceed after resolving the
+ * problem. This message can be overridden with state->resolvemsg.
+ */
+static void NORETURN die_user_resolve(const struct am_state *state)
+{
+ if (state->resolvemsg) {
+ printf_ln("%s", state->resolvemsg);
+ } else {
+ const char *cmdline = state->interactive ? "git am -i" : "git am";
+
+ printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
+ printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
+ printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
+ }
+
+ exit(128);
+}
+
+static void am_signoff(struct strbuf *sb)
+{
+ char *cp;
+ struct strbuf mine = STRBUF_INIT;
+
+ /* Does it end with our own sign-off? */
+ strbuf_addf(&mine, "\n%s%s\n",
+ sign_off_header,
+ fmt_name(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL")));
+ if (mine.len < sb->len &&
+ !strcmp(mine.buf, sb->buf + sb->len - mine.len))
+ goto exit; /* no need to duplicate */
+
+ /* Does it have any Signed-off-by: in the text */
+ for (cp = sb->buf;
+ cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL;
+ cp = strchr(cp, '\n')) {
+ if (sb->buf == cp || cp[-1] == '\n')
+ break;
+ }
+
+ strbuf_addstr(sb, mine.buf + !!cp);
+exit:
+ strbuf_release(&mine);
+}
+
+/**
+ * Appends signoff to the "msg" field of the am_state.
+ */
+static void am_append_signoff(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
+ am_signoff(&sb);
+ state->msg = strbuf_detach(&sb, &state->msg_len);
+}
+
+/**
+ * Parses `mail` using git-mailinfo, extracting its patch and authorship info.
+ * state->msg will be set to the patch message. state->author_name,
+ * state->author_email and state->author_date will be set to the patch author's
+ * name, email and date respectively. The patch body will be written to the
+ * state directory's "patch" file.
+ *
+ * Returns 1 if the patch should be skipped, 0 otherwise.
+ */
+static int parse_mail(struct am_state *state, const char *mail)
+{
+ FILE *fp;
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf msg = STRBUF_INIT;
+ struct strbuf author_name = STRBUF_INIT;
+ struct strbuf author_date = STRBUF_INIT;
+ struct strbuf author_email = STRBUF_INIT;
+ int ret = 0;
+ struct mailinfo mi;
+
+ setup_mailinfo(&mi);
+
+ if (state->utf8)
+ mi.metainfo_charset = get_commit_output_encoding();
+ else
+ mi.metainfo_charset = NULL;
+
+ switch (state->keep) {
+ case KEEP_FALSE:
+ break;
+ case KEEP_TRUE:
+ mi.keep_subject = 1;
+ break;
+ case KEEP_NON_PATCH:
+ mi.keep_non_patch_brackets_in_subject = 1;
+ break;
+ default:
+ die("BUG: invalid value for state->keep");
+ }
+
+ if (state->message_id)
+ mi.add_message_id = 1;
+
+ switch (state->scissors) {
+ case SCISSORS_UNSET:
+ break;
+ case SCISSORS_FALSE:
+ mi.use_scissors = 0;
+ break;
+ case SCISSORS_TRUE:
+ mi.use_scissors = 1;
+ break;
+ default:
+ die("BUG: invalid value for state->scissors");
+ }
+
+ mi.input = fopen(mail, "r");
+ if (!mi.input)
+ die("could not open input");
+ mi.output = fopen(am_path(state, "info"), "w");
+ if (!mi.output)
+ die("could not open output 'info'");
+ if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch")))
+ die("could not parse patch");
+
+ fclose(mi.input);
+ fclose(mi.output);
+
+ /* Extract message and author information */
+ fp = xfopen(am_path(state, "info"), "r");
+ while (!strbuf_getline_lf(&sb, fp)) {
+ const char *x;
+
+ if (skip_prefix(sb.buf, "Subject: ", &x)) {
+ if (msg.len)
+ strbuf_addch(&msg, '\n');
+ strbuf_addstr(&msg, x);
+ } else if (skip_prefix(sb.buf, "Author: ", &x))
+ strbuf_addstr(&author_name, x);
+ else if (skip_prefix(sb.buf, "Email: ", &x))
+ strbuf_addstr(&author_email, x);
+ else if (skip_prefix(sb.buf, "Date: ", &x))
+ strbuf_addstr(&author_date, x);
+ }
+ fclose(fp);
+
+ /* Skip pine's internal folder data */
+ if (!strcmp(author_name.buf, "Mail System Internal Data")) {
+ ret = 1;
+ goto finish;
+ }
+
+ if (is_empty_file(am_path(state, "patch"))) {
+ printf_ln(_("Patch is empty. Was it split wrong?"));
+ die_user_resolve(state);
+ }
+
+ strbuf_addstr(&msg, "\n\n");
+ strbuf_addbuf(&msg, &mi.log_message);
+ strbuf_stripspace(&msg, 0);
+
+ if (state->signoff)
+ am_signoff(&msg);
+
+ assert(!state->author_name);
+ state->author_name = strbuf_detach(&author_name, NULL);
+
+ assert(!state->author_email);
+ state->author_email = strbuf_detach(&author_email, NULL);
+
+ assert(!state->author_date);
+ state->author_date = strbuf_detach(&author_date, NULL);
+
+ assert(!state->msg);
+ state->msg = strbuf_detach(&msg, &state->msg_len);
+
+finish:
+ strbuf_release(&msg);
+ strbuf_release(&author_date);
+ strbuf_release(&author_email);
+ strbuf_release(&author_name);
+ strbuf_release(&sb);
+ clear_mailinfo(&mi);
+ return ret;
+}
+
+/**
+ * Sets commit_id to the commit hash where the mail was generated from.
+ * Returns 0 on success, -1 on failure.
+ */
+static int get_mail_commit_oid(struct object_id *commit_id, const char *mail)
+{
+ struct strbuf sb = STRBUF_INIT;
+ FILE *fp = xfopen(mail, "r");
+ const char *x;
+
+ if (strbuf_getline_lf(&sb, fp))
+ return -1;
+
+ if (!skip_prefix(sb.buf, "From ", &x))
+ return -1;
+
+ if (get_oid_hex(x, commit_id) < 0)
+ return -1;
+
+ strbuf_release(&sb);
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * Sets state->msg, state->author_name, state->author_email, state->author_date
+ * to the commit's respective info.
+ */
+static void get_commit_info(struct am_state *state, struct commit *commit)
+{
+ const char *buffer, *ident_line, *author_date, *msg;
+ size_t ident_len;
+ struct ident_split ident_split;
+ struct strbuf sb = STRBUF_INIT;
+
+ buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());
+
+ ident_line = find_commit_header(buffer, "author", &ident_len);
+
+ if (split_ident_line(&ident_split, ident_line, ident_len) < 0) {
+ strbuf_add(&sb, ident_line, ident_len);
+ die(_("invalid ident line: %s"), sb.buf);
+ }
+
+ assert(!state->author_name);
+ if (ident_split.name_begin) {
+ strbuf_add(&sb, ident_split.name_begin,
+ ident_split.name_end - ident_split.name_begin);
+ state->author_name = strbuf_detach(&sb, NULL);
+ } else
+ state->author_name = xstrdup("");
+
+ assert(!state->author_email);
+ if (ident_split.mail_begin) {
+ strbuf_add(&sb, ident_split.mail_begin,
+ ident_split.mail_end - ident_split.mail_begin);
+ state->author_email = strbuf_detach(&sb, NULL);
+ } else
+ state->author_email = xstrdup("");
+
+ author_date = show_ident_date(&ident_split, DATE_MODE(NORMAL));
+ strbuf_addstr(&sb, author_date);
+ assert(!state->author_date);
+ state->author_date = strbuf_detach(&sb, NULL);
+
+ assert(!state->msg);
+ msg = strstr(buffer, "\n\n");
+ if (!msg)
+ die(_("unable to parse commit %s"), oid_to_hex(&commit->object.oid));
+ state->msg = xstrdup(msg + 2);
+ state->msg_len = strlen(state->msg);
+}
+
+/**
+ * Writes `commit` as a patch to the state directory's "patch" file.
+ */
+static void write_commit_patch(const struct am_state *state, struct commit *commit)
+{
+ struct rev_info rev_info;
+ FILE *fp;
+
+ fp = xfopen(am_path(state, "patch"), "w");
+ init_revisions(&rev_info, NULL);
+ rev_info.diff = 1;
+ rev_info.abbrev = 0;
+ rev_info.disable_stdin = 1;
+ rev_info.show_root_diff = 1;
+ rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev_info.no_commit_id = 1;
+ DIFF_OPT_SET(&rev_info.diffopt, BINARY);
+ DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX);
+ rev_info.diffopt.use_color = 0;
+ rev_info.diffopt.file = fp;
+ rev_info.diffopt.close_file = 1;
+ add_pending_object(&rev_info, &commit->object, "");
+ diff_setup_done(&rev_info.diffopt);
+ log_tree_commit(&rev_info, commit);
+}
+
+/**
+ * Writes the diff of the index against HEAD as a patch to the state
+ * directory's "patch" file.
+ */
+static void write_index_patch(const struct am_state *state)
+{
+ struct tree *tree;
+ struct object_id head;
+ struct rev_info rev_info;
+ FILE *fp;
+
+ if (!get_sha1_tree("HEAD", head.hash))
+ tree = lookup_tree(head.hash);
+ else
+ tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
+
+ fp = xfopen(am_path(state, "patch"), "w");
+ init_revisions(&rev_info, NULL);
+ rev_info.diff = 1;
+ rev_info.disable_stdin = 1;
+ rev_info.no_commit_id = 1;
+ rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev_info.diffopt.use_color = 0;
+ rev_info.diffopt.file = fp;
+ rev_info.diffopt.close_file = 1;
+ add_pending_object(&rev_info, &tree->object, "");
+ diff_setup_done(&rev_info.diffopt);
+ run_diff_index(&rev_info, 1);
+}
+
+/**
+ * Like parse_mail(), but parses the mail by looking up its commit ID
+ * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
+ * of patches.
+ *
+ * state->orig_commit will be set to the original commit ID.
+ *
+ * Will always return 0 as the patch should never be skipped.
+ */
+static int parse_mail_rebase(struct am_state *state, const char *mail)
+{
+ struct commit *commit;
+ struct object_id commit_oid;
+
+ if (get_mail_commit_oid(&commit_oid, mail) < 0)
+ die(_("could not parse %s"), mail);
+
+ commit = lookup_commit_or_die(commit_oid.hash, mail);
+
+ get_commit_info(state, commit);
+
+ write_commit_patch(state, commit);
+
+ oidcpy(&state->orig_commit, &commit_oid);
+ write_state_text(state, "original-commit", oid_to_hex(&commit_oid));
+
+ return 0;
+}
+
+/**
+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
+ * `index_file` is not NULL, the patch will be applied to that index.
+ */
+static int run_apply(const struct am_state *state, const char *index_file)
+{
+ struct argv_array apply_paths = ARGV_ARRAY_INIT;
+ struct argv_array apply_opts = ARGV_ARRAY_INIT;
+ struct apply_state apply_state;
+ int res, opts_left;
+ static struct lock_file lock_file;
+ int force_apply = 0;
+ int options = 0;
+
+ if (init_apply_state(&apply_state, NULL, &lock_file))
+ die("BUG: init_apply_state() failed");
+
+ argv_array_push(&apply_opts, "apply");
+ argv_array_pushv(&apply_opts, state->git_apply_opts.argv);
+
+ opts_left = apply_parse_options(apply_opts.argc, apply_opts.argv,
+ &apply_state, &force_apply, &options,
+ NULL);
+
+ if (opts_left != 0)
+ die("unknown option passed through to git apply");
+
+ if (index_file) {
+ apply_state.index_file = index_file;
+ apply_state.cached = 1;
+ } else
+ apply_state.check_index = 1;
+
+ /*
+ * If we are allowed to fall back on 3-way merge, don't give false
+ * errors during the initial attempt.
+ */
+ if (state->threeway && !index_file)
+ apply_state.apply_verbosity = verbosity_silent;
+
+ if (check_apply_state(&apply_state, force_apply))
+ die("BUG: check_apply_state() failed");
+
+ argv_array_push(&apply_paths, am_path(state, "patch"));
+
+ res = apply_all_patches(&apply_state, apply_paths.argc, apply_paths.argv, options);
+
+ argv_array_clear(&apply_paths);
+ argv_array_clear(&apply_opts);
+ clear_apply_state(&apply_state);
+
+ if (res)
+ return res;
+
+ if (index_file) {
+ /* Reload index as apply_all_patches() will have modified it. */
+ discard_cache();
+ read_cache_from(index_file);
+ }
+
+ return 0;
+}
+
+/**
+ * Builds an index that contains just the blobs needed for a 3way merge.
+ */
+static int build_fake_ancestor(const struct am_state *state, const char *index_file)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "apply");
+ argv_array_pushv(&cp.args, state->git_apply_opts.argv);
+ argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
+ argv_array_push(&cp.args, am_path(state, "patch"));
+
+ if (run_command(&cp))
+ return -1;
+
+ return 0;
+}
+
+/**
+ * Attempt a threeway merge, using index_path as the temporary index.
+ */
+static int fall_back_threeway(const struct am_state *state, const char *index_path)
+{
+ struct object_id orig_tree, their_tree, our_tree;
+ const struct object_id *bases[1] = { &orig_tree };
+ struct merge_options o;
+ struct commit *result;
+ char *their_tree_name;
+
+ if (get_oid("HEAD", &our_tree) < 0)
+ hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
+
+ if (build_fake_ancestor(state, index_path))
+ return error("could not build fake ancestor");
+
+ discard_cache();
+ read_cache_from(index_path);
+
+ if (write_index_as_tree(orig_tree.hash, &the_index, index_path, 0, NULL))
+ return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
+
+ say(state, stdout, _("Using index info to reconstruct a base tree..."));
+
+ if (!state->quiet) {
+ /*
+ * List paths that needed 3-way fallback, so that the user can
+ * review them with extra care to spot mismerges.
+ */
+ struct rev_info rev_info;
+ const char *diff_filter_str = "--diff-filter=AM";
+
+ init_revisions(&rev_info, NULL);
+ rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
+ diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
+ add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0);
+ diff_setup_done(&rev_info.diffopt);
+ run_diff_index(&rev_info, 1);
+ }
+
+ if (run_apply(state, index_path))
+ return error(_("Did you hand edit your patch?\n"
+ "It does not apply to blobs recorded in its index."));
+
+ if (write_index_as_tree(their_tree.hash, &the_index, index_path, 0, NULL))
+ return error("could not write tree");
+
+ say(state, stdout, _("Falling back to patching base and 3-way merge..."));
+
+ discard_cache();
+ read_cache();
+
+ /*
+ * This is not so wrong. Depending on which base we picked, orig_tree
+ * may be wildly different from ours, but their_tree has the same set of
+ * wildly different changes in parts the patch did not touch, so
+ * recursive ends up canceling them, saying that we reverted all those
+ * changes.
+ */
+
+ init_merge_options(&o);
+
+ o.branch1 = "HEAD";
+ their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+ o.branch2 = their_tree_name;
+
+ if (state->quiet)
+ o.verbosity = 0;
+
+ if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
+ rerere(state->allow_rerere_autoupdate);
+ free(their_tree_name);
+ return error(_("Failed to merge in the changes."));
+ }
+
+ free(their_tree_name);
+ return 0;
+}
+
+/**
+ * Commits the current index with state->msg as the commit message and
+ * state->author_name, state->author_email and state->author_date as the author
+ * information.
+ */
+static void do_commit(const struct am_state *state)
+{
+ struct object_id tree, parent, commit;
+ const struct object_id *old_oid;
+ struct commit_list *parents = NULL;
+ const char *reflog_msg, *author;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (run_hook_le(NULL, "pre-applypatch", NULL))
+ exit(1);
+
+ if (write_cache_as_tree(tree.hash, 0, NULL))
+ die(_("git write-tree failed to write a tree"));
+
+ if (!get_sha1_commit("HEAD", parent.hash)) {
+ old_oid = &parent;
+ commit_list_insert(lookup_commit(parent.hash), &parents);
+ } else {
+ old_oid = NULL;
+ say(state, stderr, _("applying to an empty history"));
+ }
+
+ author = fmt_ident(state->author_name, state->author_email,
+ state->ignore_date ? NULL : state->author_date,
+ IDENT_STRICT);
+
+ if (state->committer_date_is_author_date)
+ setenv("GIT_COMMITTER_DATE",
+ state->ignore_date ? "" : state->author_date, 1);
+
+ if (commit_tree(state->msg, state->msg_len, tree.hash, parents, commit.hash,
+ author, state->sign_commit))
+ die(_("failed to write commit object"));
+
+ reflog_msg = getenv("GIT_REFLOG_ACTION");
+ if (!reflog_msg)
+ reflog_msg = "am";
+
+ strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg),
+ state->msg);
+
+ update_ref_oid(sb.buf, "HEAD", &commit, old_oid, 0,
+ UPDATE_REFS_DIE_ON_ERR);
+
+ if (state->rebasing) {
+ FILE *fp = xfopen(am_path(state, "rewritten"), "a");
+
+ assert(!is_null_oid(&state->orig_commit));
+ fprintf(fp, "%s ", oid_to_hex(&state->orig_commit));
+ fprintf(fp, "%s\n", oid_to_hex(&commit));
+ fclose(fp);
+ }
+
+ run_hook_le(NULL, "post-applypatch", NULL);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Validates the am_state for resuming -- the "msg" and authorship fields must
+ * be filled up.
+ */
+static void validate_resume_state(const struct am_state *state)
+{
+ if (!state->msg)
+ die(_("cannot resume: %s does not exist."),
+ am_path(state, "final-commit"));
+
+ if (!state->author_name || !state->author_email || !state->author_date)
+ die(_("cannot resume: %s does not exist."),
+ am_path(state, "author-script"));
+}
+
+/**
+ * Interactively prompt the user on whether the current patch should be
+ * applied.
+ *
+ * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to
+ * skip it.
+ */
+static int do_interactive(struct am_state *state)
+{
+ assert(state->msg);
+
+ if (!isatty(0))
+ die(_("cannot be interactive without stdin connected to a terminal."));
+
+ for (;;) {
+ const char *reply;
+
+ puts(_("Commit Body is:"));
+ puts("--------------------------");
+ printf("%s", state->msg);
+ puts("--------------------------");
+
+ /*
+ * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+ * in your translation. The program will only accept English
+ * input at this point.
+ */
+ reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO);
+
+ if (!reply) {
+ continue;
+ } else if (*reply == 'y' || *reply == 'Y') {
+ return 0;
+ } else if (*reply == 'a' || *reply == 'A') {
+ state->interactive = 0;
+ return 0;
+ } else if (*reply == 'n' || *reply == 'N') {
+ return 1;
+ } else if (*reply == 'e' || *reply == 'E') {
+ struct strbuf msg = STRBUF_INIT;
+
+ if (!launch_editor(am_path(state, "final-commit"), &msg, NULL)) {
+ free(state->msg);
+ state->msg = strbuf_detach(&msg, &state->msg_len);
+ }
+ strbuf_release(&msg);
+ } else if (*reply == 'v' || *reply == 'V') {
+ const char *pager = git_pager(1);
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ if (!pager)
+ pager = "cat";
+ prepare_pager_args(&cp, pager);
+ argv_array_push(&cp.args, am_path(state, "patch"));
+ run_command(&cp);
+ }
+ }
+}
+
+/**
+ * Applies all queued mail.
+ *
+ * If `resume` is true, we are "resuming". The "msg" and authorship fields, as
+ * well as the state directory's "patch" file is used as-is for applying the
+ * patch and committing it.
+ */
+static void am_run(struct am_state *state, int resume)
+{
+ const char *argv_gc_auto[] = {"gc", "--auto", NULL};
+ struct strbuf sb = STRBUF_INIT;
+
+ unlink(am_path(state, "dirtyindex"));
+
+ refresh_and_write_cache();
+
+ if (index_has_changes(&sb)) {
+ write_state_bool(state, "dirtyindex", 1);
+ die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf);
+ }
+
+ strbuf_release(&sb);
+
+ while (state->cur <= state->last) {
+ const char *mail = am_path(state, msgnum(state));
+ int apply_status;
+
+ reset_ident_date();
+
+ if (!file_exists(mail))
+ goto next;
+
+ if (resume) {
+ validate_resume_state(state);
+ } else {
+ int skip;
+
+ if (state->rebasing)
+ skip = parse_mail_rebase(state, mail);
+ else
+ skip = parse_mail(state, mail);
+
+ if (skip)
+ goto next; /* mail should be skipped */
+
+ write_author_script(state);
+ write_commit_msg(state);
+ }
+
+ if (state->interactive && do_interactive(state))
+ goto next;
+
+ if (run_applypatch_msg_hook(state))
+ exit(1);
+
+ say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
+
+ apply_status = run_apply(state, NULL);
+
+ if (apply_status && state->threeway) {
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, am_path(state, "patch-merge-index"));
+ apply_status = fall_back_threeway(state, sb.buf);
+ strbuf_release(&sb);
+
+ /*
+ * Applying the patch to an earlier tree and merging
+ * the result may have produced the same tree as ours.
+ */
+ if (!apply_status && !index_has_changes(NULL)) {
+ say(state, stdout, _("No changes -- Patch already applied."));
+ goto next;
+ }
+ }
+
+ if (apply_status) {
+ int advice_amworkdir = 1;
+
+ printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
+ linelen(state->msg), state->msg);
+
+ git_config_get_bool("advice.amworkdir", &advice_amworkdir);
+
+ if (advice_amworkdir)
+ printf_ln(_("The copy of the patch that failed is found in: %s"),
+ am_path(state, "patch"));
+
+ die_user_resolve(state);
+ }
+
+ do_commit(state);
+
+next:
+ am_next(state);
+
+ if (resume)
+ am_load(state);
+ resume = 0;
+ }
+
+ if (!is_empty_file(am_path(state, "rewritten"))) {
+ assert(state->rebasing);
+ copy_notes_for_rebase(state);
+ run_post_rewrite_hook(state);
+ }
+
+ /*
+ * In rebasing mode, it's up to the caller to take care of
+ * housekeeping.
+ */
+ if (!state->rebasing) {
+ am_destroy(state);
+ close_all_packs();
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+}
+
+/**
+ * Resume the current am session after patch application failure. The user did
+ * all the hard work, and we do not have to do any patch application. Just
+ * trust and commit what the user has in the index and working tree.
+ */
+static void am_resolve(struct am_state *state)
+{
+ validate_resume_state(state);
+
+ say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
+
+ if (!index_has_changes(NULL)) {
+ printf_ln(_("No changes - did you forget to use 'git add'?\n"
+ "If there is nothing left to stage, chances are that something else\n"
+ "already introduced the same changes; you might want to skip this patch."));
+ die_user_resolve(state);
+ }
+
+ if (unmerged_cache()) {
+ printf_ln(_("You still have unmerged paths in your index.\n"
+ "Did you forget to use 'git add'?"));
+ die_user_resolve(state);
+ }
+
+ if (state->interactive) {
+ write_index_patch(state);
+ if (do_interactive(state))
+ goto next;
+ }
+
+ rerere(0);
+
+ do_commit(state);
+
+next:
+ am_next(state);
+ am_load(state);
+ am_run(state, 0);
+}
+
+/**
+ * Performs a checkout fast-forward from `head` to `remote`. If `reset` is
+ * true, any unmerged entries will be discarded. Returns 0 on success, -1 on
+ * failure.
+ */
+static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
+{
+ struct lock_file *lock_file;
+ struct unpack_trees_options opts;
+ struct tree_desc t[2];
+
+ if (parse_tree(head) || parse_tree(remote))
+ return -1;
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+ hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+
+ refresh_cache(REFRESH_QUIET);
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.merge = 1;
+ opts.reset = reset;
+ opts.fn = twoway_merge;
+ init_tree_desc(&t[0], head->buffer, head->size);
+ init_tree_desc(&t[1], remote->buffer, remote->size);
+
+ if (unpack_trees(2, t, &opts)) {
+ rollback_lock_file(lock_file);
+ return -1;
+ }
+
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write new index file"));
+
+ return 0;
+}
+
+/**
+ * Merges a tree into the index. The index's stat info will take precedence
+ * over the merged tree's. Returns 0 on success, -1 on failure.
+ */
+static int merge_tree(struct tree *tree)
+{
+ struct lock_file *lock_file;
+ struct unpack_trees_options opts;
+ struct tree_desc t[1];
+
+ if (parse_tree(tree))
+ return -1;
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+ hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.merge = 1;
+ opts.fn = oneway_merge;
+ init_tree_desc(&t[0], tree->buffer, tree->size);
+
+ if (unpack_trees(1, t, &opts)) {
+ rollback_lock_file(lock_file);
+ return -1;
+ }
+
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write new index file"));
+
+ return 0;
+}
+
+/**
+ * Clean the index without touching entries that are not modified between
+ * `head` and `remote`.
+ */
+static int clean_index(const struct object_id *head, const struct object_id *remote)
+{
+ struct tree *head_tree, *remote_tree, *index_tree;
+ struct object_id index;
+
+ head_tree = parse_tree_indirect(head->hash);
+ if (!head_tree)
+ return error(_("Could not parse object '%s'."), oid_to_hex(head));
+
+ remote_tree = parse_tree_indirect(remote->hash);
+ if (!remote_tree)
+ return error(_("Could not parse object '%s'."), oid_to_hex(remote));
+
+ read_cache_unmerged();
+
+ if (fast_forward_to(head_tree, head_tree, 1))
+ return -1;
+
+ if (write_cache_as_tree(index.hash, 0, NULL))
+ return -1;
+
+ index_tree = parse_tree_indirect(index.hash);
+ if (!index_tree)
+ return error(_("Could not parse object '%s'."), oid_to_hex(&index));
+
+ if (fast_forward_to(index_tree, remote_tree, 0))
+ return -1;
+
+ if (merge_tree(remote_tree))
+ return -1;
+
+ remove_branch_state();
+
+ return 0;
+}
+
+/**
+ * Resets rerere's merge resolution metadata.
+ */
+static void am_rerere_clear(void)
+{
+ struct string_list merge_rr = STRING_LIST_INIT_DUP;
+ rerere_clear(&merge_rr);
+ string_list_clear(&merge_rr, 1);
+}
+
+/**
+ * Resume the current am session by skipping the current patch.
+ */
+static void am_skip(struct am_state *state)
+{
+ struct object_id head;
+
+ am_rerere_clear();
+
+ if (get_oid("HEAD", &head))
+ hashcpy(head.hash, EMPTY_TREE_SHA1_BIN);
+
+ if (clean_index(&head, &head))
+ die(_("failed to clean index"));
+
+ am_next(state);
+ am_load(state);
+ am_run(state, 0);
+}
+
+/**
+ * Returns true if it is safe to reset HEAD to the ORIG_HEAD, false otherwise.
+ *
+ * It is not safe to reset HEAD when:
+ * 1. git-am previously failed because the index was dirty.
+ * 2. HEAD has moved since git-am previously failed.
+ */
+static int safe_to_abort(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+ struct object_id abort_safety, head;
+
+ if (file_exists(am_path(state, "dirtyindex")))
+ return 0;
+
+ if (read_state_file(&sb, state, "abort-safety", 1) > 0) {
+ if (get_oid_hex(sb.buf, &abort_safety))
+ die(_("could not parse %s"), am_path(state, "abort-safety"));
+ } else
+ oidclr(&abort_safety);
+
+ if (get_oid("HEAD", &head))
+ oidclr(&head);
+
+ if (!oidcmp(&head, &abort_safety))
+ return 1;
+
+ warning(_("You seem to have moved HEAD since the last 'am' failure.\n"
+ "Not rewinding to ORIG_HEAD"));
+
+ return 0;
+}
+
+/**
+ * Aborts the current am session if it is safe to do so.
+ */
+static void am_abort(struct am_state *state)
+{
+ struct object_id curr_head, orig_head;
+ int has_curr_head, has_orig_head;
+ char *curr_branch;
+
+ if (!safe_to_abort(state)) {
+ am_destroy(state);
+ return;
+ }
+
+ am_rerere_clear();
+
+ curr_branch = resolve_refdup("HEAD", 0, curr_head.hash, NULL);
+ has_curr_head = !is_null_oid(&curr_head);
+ if (!has_curr_head)
+ hashcpy(curr_head.hash, EMPTY_TREE_SHA1_BIN);
+
+ has_orig_head = !get_oid("ORIG_HEAD", &orig_head);
+ if (!has_orig_head)
+ hashcpy(orig_head.hash, EMPTY_TREE_SHA1_BIN);
+
+ clean_index(&curr_head, &orig_head);
+
+ if (has_orig_head)
+ update_ref_oid("am --abort", "HEAD", &orig_head,
+ has_curr_head ? &curr_head : NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
+ else if (curr_branch)
+ delete_ref(NULL, curr_branch, NULL, REF_NODEREF);
+
+ free(curr_branch);
+ am_destroy(state);
+}
+
+/**
+ * parse_options() callback that validates and sets opt->value to the
+ * PATCH_FORMAT_* enum value corresponding to `arg`.
+ */
+static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)
+{
+ int *opt_value = opt->value;
+
+ if (!strcmp(arg, "mbox"))
+ *opt_value = PATCH_FORMAT_MBOX;
+ else if (!strcmp(arg, "stgit"))
+ *opt_value = PATCH_FORMAT_STGIT;
+ else if (!strcmp(arg, "stgit-series"))
+ *opt_value = PATCH_FORMAT_STGIT_SERIES;
+ else if (!strcmp(arg, "hg"))
+ *opt_value = PATCH_FORMAT_HG;
+ else if (!strcmp(arg, "mboxrd"))
+ *opt_value = PATCH_FORMAT_MBOXRD;
+ else
+ return error(_("Invalid value for --patch-format: %s"), arg);
+ return 0;
+}
+
+enum resume_mode {
+ RESUME_FALSE = 0,
+ RESUME_APPLY,
+ RESUME_RESOLVED,
+ RESUME_SKIP,
+ RESUME_ABORT
+};
+
+static int git_am_config(const char *k, const char *v, void *cb)
+{
+ int status;
+
+ status = git_gpg_config(k, v, NULL);
+ if (status)
+ return status;
+
+ return git_default_config(k, v, NULL);
+}
+
+int cmd_am(int argc, const char **argv, const char *prefix)
+{
+ struct am_state state;
+ int binary = -1;
+ int keep_cr = -1;
+ int patch_format = PATCH_FORMAT_UNKNOWN;
+ enum resume_mode resume = RESUME_FALSE;
+ int in_progress;
+
+ const char * const usage[] = {
+ N_("git am [<options>] [(<mbox> | <Maildir>)...]"),
+ N_("git am [<options>] (--continue | --skip | --abort)"),
+ NULL
+ };
+
+ struct option options[] = {
+ OPT_BOOL('i', "interactive", &state.interactive,
+ N_("run interactively")),
+ OPT_HIDDEN_BOOL('b', "binary", &binary,
+ N_("historical option -- no-op")),
+ OPT_BOOL('3', "3way", &state.threeway,
+ N_("allow fall back on 3way merging if needed")),
+ OPT__QUIET(&state.quiet, N_("be quiet")),
+ OPT_SET_INT('s', "signoff", &state.signoff,
+ N_("add a Signed-off-by line to the commit message"),
+ SIGNOFF_EXPLICIT),
+ OPT_BOOL('u', "utf8", &state.utf8,
+ N_("recode into utf8 (default)")),
+ OPT_SET_INT('k', "keep", &state.keep,
+ N_("pass -k flag to git-mailinfo"), KEEP_TRUE),
+ OPT_SET_INT(0, "keep-non-patch", &state.keep,
+ N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
+ OPT_BOOL('m', "message-id", &state.message_id,
+ N_("pass -m flag to git-mailinfo")),
+ { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL,
+ N_("pass --keep-cr flag to git-mailsplit for mbox format"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
+ { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL,
+ N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+ OPT_BOOL('c', "scissors", &state.scissors,
+ N_("strip everything before a scissors line")),
+ OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
+ N_("format the patch(es) are in"),
+ parse_opt_patchformat),
+ OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
+ N_("override error message when patch failure occurs")),
+ OPT_CMDMODE(0, "continue", &resume,
+ N_("continue applying patches after resolving a conflict"),
+ RESUME_RESOLVED),
+ OPT_CMDMODE('r', "resolved", &resume,
+ N_("synonyms for --continue"),
+ RESUME_RESOLVED),
+ OPT_CMDMODE(0, "skip", &resume,
+ N_("skip the current patch"),
+ RESUME_SKIP),
+ OPT_CMDMODE(0, "abort", &resume,
+ N_("restore the original branch and abort the patching operation."),
+ RESUME_ABORT),
+ OPT_BOOL(0, "committer-date-is-author-date",
+ &state.committer_date_is_author_date,
+ N_("lie about committer date")),
+ OPT_BOOL(0, "ignore-date", &state.ignore_date,
+ N_("use current timestamp for author date")),
+ OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate),
+ { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
+ N_("GPG-sign commits"),
+ PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
+ N_("(internal use for git-rebase)")),
+ OPT_END()
+ };
+
+ git_config(git_am_config, NULL);
+
+ am_state_init(&state, git_path("rebase-apply"));
+
+ in_progress = am_in_progress(&state);
+ if (in_progress)
+ am_load(&state);
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (binary >= 0)
+ fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
+ "it will be removed. Please do not use it anymore."));
+
+ /* Ensure a valid committer ident can be constructed */
+ git_committer_info(IDENT_STRICT);
+
+ if (read_index_preload(&the_index, NULL) < 0)
+ die(_("failed to read the index"));
+
+ if (in_progress) {
+ /*
+ * Catch user error to feed us patches when there is a session
+ * in progress:
+ *
+ * 1. mbox path(s) are provided on the command-line.
+ * 2. stdin is not a tty: the user is trying to feed us a patch
+ * from standard input. This is somewhat unreliable -- stdin
+ * could be /dev/null for example and the caller did not
+ * intend to feed us a patch but wanted to continue
+ * unattended.
+ */
+ if (argc || (resume == RESUME_FALSE && !isatty(0)))
+ die(_("previous rebase directory %s still exists but mbox given."),
+ state.dir);
+
+ if (resume == RESUME_FALSE)
+ resume = RESUME_APPLY;
+
+ if (state.signoff == SIGNOFF_EXPLICIT)
+ am_append_signoff(&state);
+ } else {
+ struct argv_array paths = ARGV_ARRAY_INIT;
+ int i;
+
+ /*
+ * Handle stray state directory in the independent-run case. In
+ * the --rebasing case, it is up to the caller to take care of
+ * stray directories.
+ */
+ if (file_exists(state.dir) && !state.rebasing) {
+ if (resume == RESUME_ABORT) {
+ am_destroy(&state);
+ am_state_release(&state);
+ return 0;
+ }
+
+ die(_("Stray %s directory found.\n"
+ "Use \"git am --abort\" to remove it."),
+ state.dir);
+ }
+
+ if (resume)
+ die(_("Resolve operation not in progress, we are not resuming."));
+
+ for (i = 0; i < argc; i++) {
+ if (is_absolute_path(argv[i]) || !prefix)
+ argv_array_push(&paths, argv[i]);
+ else
+ argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
+ }
+
+ am_setup(&state, patch_format, paths.argv, keep_cr);
+
+ argv_array_clear(&paths);
+ }
+
+ switch (resume) {
+ case RESUME_FALSE:
+ am_run(&state, 0);
+ break;
+ case RESUME_APPLY:
+ am_run(&state, 1);
+ break;
+ case RESUME_RESOLVED:
+ am_resolve(&state);
+ break;
+ case RESUME_SKIP:
+ am_skip(&state);
+ break;
+ case RESUME_ABORT:
+ am_abort(&state);
+ break;
+ default:
+ die("BUG: invalid resume value");
+ }
+
+ am_state_release(&state);
+
+ return 0;
+}
diff --git a/builtin/annotate.c b/builtin/annotate.c
index fc43eed36b..da413ae0d1 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 a7e72d57ab..81b9a61c37 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -1,4510 +1,36 @@
-/*
- * apply.c
- *
- * Copyright (C) Linus Torvalds, 2005
- *
- * This applies patches on top of some (arbitrary) version of the SCM.
- *
- */
#include "cache.h"
-#include "cache-tree.h"
-#include "quote.h"
-#include "blob.h"
-#include "delta.h"
#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"
+#include "lockfile.h"
+#include "apply.h"
-/*
- * --check turns on checking that the working tree matches the
- * files that are being modified, but doesn't apply the patch
- * --stat does just a diffstat, and doesn't actually apply
- * --numstat does numeric diffstat, and doesn't actually apply
- * --index-info shows the old and new index info for paths if available.
- * --index updates the cache as well.
- * --cached updates only the cache without ever touching the working tree.
- */
-static const char *prefix;
-static int prefix_length = -1;
-static int newfd = -1;
-
-static int unidiff_zero;
-static int p_value = 1;
-static int p_value_known;
-static int check_index;
-static int update_index;
-static int cached;
-static int diffstat;
-static int numstat;
-static int summary;
-static int check;
-static int apply = 1;
-static int apply_in_reverse;
-static int apply_with_reject;
-static int apply_verbosely;
-static int allow_overlap;
-static int no_add;
-static int threeway;
-static const char *fake_ancestor;
-static int line_termination = '\n';
-static unsigned int p_context = UINT_MAX;
static const char * const apply_usage[] = {
- N_("git apply [options] [<patch>...]"),
+ N_("git apply [<options>] [<patch>...]"),
NULL
};
-static enum ws_error_action {
- nowarn_ws_error,
- warn_on_ws_error,
- die_on_ws_error,
- correct_ws_error
-} ws_error_action = warn_on_ws_error;
-static int whitespace_error;
-static int squelch_whitespace_errors = 5;
-static int applied_after_fixing_ws;
-
-static enum ws_ignore {
- ignore_ws_none,
- ignore_ws_change
-} ws_ignore_action = ignore_ws_none;
-
-
-static const char *patch_input_file;
-static const char *root;
-static int root_len;
-static int read_stdin = 1;
-static int options;
-
-static void parse_whitespace_option(const char *option)
-{
- if (!option) {
- ws_error_action = warn_on_ws_error;
- return;
- }
- if (!strcmp(option, "warn")) {
- ws_error_action = warn_on_ws_error;
- return;
- }
- if (!strcmp(option, "nowarn")) {
- ws_error_action = nowarn_ws_error;
- return;
- }
- if (!strcmp(option, "error")) {
- ws_error_action = die_on_ws_error;
- return;
- }
- if (!strcmp(option, "error-all")) {
- ws_error_action = die_on_ws_error;
- squelch_whitespace_errors = 0;
- return;
- }
- if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
- ws_error_action = correct_ws_error;
- return;
- }
- die(_("unrecognized whitespace option '%s'"), option);
-}
-
-static void parse_ignorewhitespace_option(const char *option)
-{
- if (!option || !strcmp(option, "no") ||
- !strcmp(option, "false") || !strcmp(option, "never") ||
- !strcmp(option, "none")) {
- ws_ignore_action = ignore_ws_none;
- return;
- }
- if (!strcmp(option, "change")) {
- ws_ignore_action = ignore_ws_change;
- return;
- }
- die(_("unrecognized whitespace ignore option '%s'"), option);
-}
-
-static void set_default_whitespace_mode(const char *whitespace_option)
-{
- if (!whitespace_option && !apply_default_whitespace)
- ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error);
-}
-
-/*
- * For "diff-stat" like behaviour, we keep track of the biggest change
- * we've seen, and the longest filename. That allows us to do simple
- * scaling.
- */
-static int max_change, max_len;
-
-/*
- * Various "current state", notably line numbers and what
- * file (and how) we're patching right now.. The "is_xxxx"
- * things are flags, where -1 means "don't know yet".
- */
-static int linenr = 1;
-
-/*
- * This represents one "hunk" from a patch, starting with
- * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The
- * patch text is pointed at by patch, and its byte length
- * is stored in size. leading and trailing are the number
- * of context lines.
- */
-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 linenr;
- struct fragment *next;
-};
-
-/*
- * When dealing with a binary patch, we reuse "leading" field
- * to store the type of the binary hunk, either deflated "delta"
- * or deflated "literal".
- */
-#define binary_patch_method leading
-#define BINARY_DELTA_DEFLATED 1
-#define BINARY_LITERAL_DEFLATED 2
-
-/*
- * This represents a "patch" to a file, both metainfo changes
- * such as creation/deletion, filemode and content changes represented
- * as a series of fragments.
- */
-struct patch {
- char *new_name, *old_name, *def_name;
- unsigned int old_mode, new_mode;
- int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
- int rejected;
- unsigned ws_rule;
- int lines_added, lines_deleted;
- int score;
- unsigned int is_toplevel_relative:1;
- unsigned int inaccurate_eof:1;
- unsigned int is_binary:1;
- 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
- * one), and its contents hashes to 'hash'.
- */
-struct line {
- size_t len;
- unsigned hash : 24;
- unsigned flag : 8;
-#define LINE_COMMON 1
-#define LINE_PATCHED 2
-};
-
-/*
- * This represents a "file", which is an array of "lines".
- */
-struct image {
- char *buf;
- size_t len;
- size_t nr;
- size_t alloc;
- struct line *line_allocated;
- struct line *line;
-};
-
-/*
- * Records filenames that have been touched, in order to handle
- * the case where more than one patches touch the same file.
- */
-
-static struct string_list fn_table;
-
-static uint32_t hash_line(const char *cp, size_t len)
-{
- size_t i;
- uint32_t h;
- for (i = 0, h = 0; i < len; i++) {
- if (!isspace(cp[i])) {
- h = h * 3 + (cp[i] & 0xff);
- }
- }
- return h;
-}
-
-/*
- * Compare lines s1 of length n1 and s2 of length n2, ignoring
- * whitespace difference. Returns 1 if they match, 0 otherwise
- */
-static int fuzzy_matchlines(const char *s1, size_t n1,
- const char *s2, size_t n2)
-{
- const char *last1 = s1 + n1 - 1;
- const char *last2 = s2 + n2 - 1;
- int result = 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++;
- /* early return if both lines are empty */
- if ((s1 > last1) && (s2 > last2))
- return 1;
- while (!result) {
- result = *s1++ - *s2++;
- /*
- * Skip whitespace inside. We check for whitespace on
- * both buffers because we don't want "a b" to match
- * "ab"
- */
- if (isspace(*s1) && isspace(*s2)) {
- while (isspace(*s1) && s1 <= last1)
- s1++;
- while (isspace(*s2) && s2 <= last2)
- s2++;
- }
- /*
- * If we reached the end on one side only,
- * lines don't match
- */
- if (
- ((s2 > last2) && (s1 <= last1)) ||
- ((s1 > last1) && (s2 <= last2)))
- return 0;
- if ((s1 > last1) && (s2 > last2))
- break;
- }
-
- return !result;
-}
-
-static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
-{
- ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
- img->line_allocated[img->nr].len = len;
- img->line_allocated[img->nr].hash = hash_line(bol, len);
- img->line_allocated[img->nr].flag = flag;
- 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)
-{
- const char *cp, *ep;
-
- memset(image, 0, sizeof(*image));
- image->buf = buf;
- image->len = len;
-
- if (!prepare_linetable)
- return;
-
- ep = image->buf + image->len;
- cp = image->buf;
- while (cp < ep) {
- const char *next;
- for (next = cp; next < ep && *next != '\n'; next++)
- ;
- if (next < ep)
- next++;
- add_line_info(image, cp, next - cp, 0);
- cp = next;
- }
- image->line = image->line_allocated;
-}
-
-static void clear_image(struct image *image)
-{
- free(image->buf);
- free(image->line_allocated);
- memset(image, 0, sizeof(*image));
-}
-
-/* fmt must contain _one_ %s and no other substitution */
-static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
-{
- 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, &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, &sb, NULL, 0);
- }
- fprintf(output, fmt, sb.buf);
- fputc('\n', output);
- strbuf_release(&sb);
-}
-
-#define SLOP (16)
-
-static void read_patch_file(struct strbuf *sb, int fd)
-{
- if (strbuf_read(sb, fd, 0) < 0)
- die_errno("git apply: failed to read");
-
- /*
- * Make sure that we have some slop in the buffer
- * so that we can do speculative "memcmp" etc, and
- * see to it that it is NUL-filled.
- */
- strbuf_grow(sb, SLOP);
- memset(sb->buf + sb->len, 0, SLOP);
-}
-
-static unsigned long linelen(const char *buffer, unsigned long size)
-{
- unsigned long len = 0;
- while (size--) {
- len++;
- if (*buffer++ == '\n')
- break;
- }
- return len;
-}
-
-static int is_dev_null(const char *str)
-{
- return !memcmp("/dev/null", str, 9) && isspace(str[9]);
-}
-
-#define TERM_SPACE 1
-#define TERM_TAB 2
-
-static int name_terminate(const char *name, int namelen, int c, int terminate)
-{
- if (c == ' ' && !(terminate & TERM_SPACE))
- return 0;
- if (c == '\t' && !(terminate & TERM_TAB))
- return 0;
-
- return 1;
-}
-
-/* remove double slashes to make --index work with such filenames */
-static char *squash_slash(char *name)
-{
- int i = 0, j = 0;
-
- if (!name)
- return NULL;
-
- while (name[i]) {
- if ((name[j++] = name[i++]) == '/')
- while (name[i] == '/')
- i++;
- }
- name[j] = '\0';
- return name;
-}
-
-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.info/?l=git&m=112927316408690&w=2
- */
- if (unquote_c_style(&name, line, NULL)) {
- strbuf_release(&name);
- return NULL;
- }
-
- for (cp = name.buf; p_value; p_value--) {
- cp = strchr(cp, '/');
- if (!cp) {
- strbuf_release(&name);
- return NULL;
- }
- cp++;
- }
-
- strbuf_remove(&name, 0, cp - name.buf);
- if (root)
- strbuf_insert(&name, 0, root, root_len);
- return squash_slash(strbuf_detach(&name, NULL));
-}
-
-static size_t sane_tz_len(const char *line, size_t len)
-{
- const char *tz, *p;
-
- if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ')
- return 0;
- tz = line + len - strlen(" +0500");
-
- if (tz[1] != '+' && tz[1] != '-')
- return 0;
-
- for (p = tz + 2; p != line + len; p++)
- if (!isdigit(*p))
- return 0;
-
- return line + len - tz;
-}
-
-static size_t tz_with_colon_len(const char *line, size_t len)
-{
- const char *tz, *p;
-
- if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':')
- return 0;
- tz = line + len - strlen(" +08:00");
-
- if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-'))
- return 0;
- p = tz + 2;
- if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
- !isdigit(*p++) || !isdigit(*p++))
- return 0;
-
- return line + len - tz;
-}
-
-static size_t date_len(const char *line, size_t len)
-{
- const char *date, *p;
-
- if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-')
- return 0;
- p = date = line + len - strlen("72-02-05");
-
- if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
- !isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
- !isdigit(*p++) || !isdigit(*p++)) /* Not a date. */
- return 0;
-
- if (date - line >= strlen("19") &&
- isdigit(date[-1]) && isdigit(date[-2])) /* 4-digit year */
- date -= strlen("19");
-
- return line + len - date;
-}
-
-static size_t short_time_len(const char *line, size_t len)
-{
- const char *time, *p;
-
- if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':')
- return 0;
- p = time = line + len - strlen(" 07:01:32");
-
- /* Permit 1-digit hours? */
- if (*p++ != ' ' ||
- !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
- !isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
- !isdigit(*p++) || !isdigit(*p++)) /* Not a time. */
- return 0;
-
- return line + len - time;
-}
-
-static size_t fractional_time_len(const char *line, size_t len)
-{
- const char *p;
- size_t n;
-
- /* Expected format: 19:41:17.620000023 */
- if (!len || !isdigit(line[len - 1]))
- return 0;
- p = line + len - 1;
-
- /* Fractional seconds. */
- while (p > line && isdigit(*p))
- p--;
- if (*p != '.')
- return 0;
-
- /* Hours, minutes, and whole seconds. */
- n = short_time_len(line, p - line);
- if (!n)
- return 0;
-
- return line + len - p + n;
-}
-
-static size_t trailing_spaces_len(const char *line, size_t len)
-{
- const char *p;
-
- /* Expected format: ' ' x (1 or more) */
- if (!len || line[len - 1] != ' ')
- return 0;
-
- p = line + len;
- while (p != line) {
- p--;
- if (*p != ' ')
- return line + len - (p + 1);
- }
-
- /* All spaces! */
- return len;
-}
-
-static size_t diff_timestamp_len(const char *line, size_t len)
-{
- const char *end = line + len;
- size_t n;
-
- /*
- * Posix: 2010-07-05 19:41:17
- * GNU: 2010-07-05 19:41:17.620000023 -0500
- */
-
- if (!isdigit(end[-1]))
- return 0;
-
- n = sane_tz_len(line, end - line);
- if (!n)
- n = tz_with_colon_len(line, end - line);
- end -= n;
-
- n = short_time_len(line, end - line);
- if (!n)
- n = fractional_time_len(line, end - line);
- end -= n;
-
- n = date_len(line, end - line);
- if (!n) /* No date. Too bad. */
- return 0;
- end -= n;
-
- if (end == line) /* No space before date. */
- return 0;
- if (end[-1] == '\t') { /* Success! */
- end--;
- return line + len - end;
- }
- if (end[-1] != ' ') /* No space before date. */
- return 0;
-
- /* Whitespace damage. */
- end -= trailing_spaces_len(line, end - line);
- return line + len - end;
-}
-
-static char *null_strdup(const char *s)
-{
- return s ? xstrdup(s) : NULL;
-}
-
-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;
-
- if (p_value == 0)
- start = line;
- while (line != end) {
- char c = *line;
-
- if (!end && isspace(c)) {
- if (c == '\n')
- break;
- if (name_terminate(start, line-start, c, terminate))
- break;
- }
- line++;
- if (c == '/' && !--p_value)
- start = line;
- }
- if (!start)
- return squash_slash(null_strdup(def));
- len = line - start;
- if (!len)
- return squash_slash(null_strdup(def));
-
- /*
- * Generally we prefer the shorter name, especially
- * if the other one is just a variation of that with
- * something else tacked on to the end (ie "file.orig"
- * or "file~").
- */
- if (def) {
- int deflen = strlen(def);
- if (deflen < len && !strncmp(start, def, deflen))
- return squash_slash(xstrdup(def));
- }
-
- if (root) {
- char *ret = xmalloc(root_len + len + 1);
- strcpy(ret, root);
- memcpy(ret + root_len, start, len);
- ret[root_len + len] = '\0';
- return squash_slash(ret);
- }
-
- return squash_slash(xmemdupz(start, len));
-}
-
-static char *find_name(const char *line, char *def, int p_value, int terminate)
-{
- if (*line == '"') {
- char *name = find_name_gnu(line, def, p_value);
- if (name)
- return name;
- }
-
- return find_name_common(line, def, p_value, NULL, terminate);
-}
-
-static char *find_name_traditional(const char *line, char *def, int p_value)
-{
- size_t len;
- size_t date_len;
-
- if (*line == '"') {
- char *name = find_name_gnu(line, def, p_value);
- if (name)
- return name;
- }
-
- len = strchrnul(line, '\n') - line;
- date_len = diff_timestamp_len(line, len);
- if (!date_len)
- return find_name_common(line, def, p_value, NULL, TERM_TAB);
- len -= date_len;
-
- return find_name_common(line, def, p_value, line + len, 0);
-}
-
-static int count_slashes(const char *cp)
-{
- int cnt = 0;
- char ch;
-
- while ((ch = *cp++))
- if (ch == '/')
- cnt++;
- return cnt;
-}
-
-/*
- * Given the string after "--- " or "+++ ", guess the appropriate
- * p_value for the given patch.
- */
-static int guess_p_value(const char *nameline)
-{
- char *name, *cp;
- int val = -1;
-
- if (is_dev_null(nameline))
- return -1;
- name = find_name_traditional(nameline, NULL, 0);
- if (!name)
- return -1;
- cp = strchr(name, '/');
- if (!cp)
- val = 0;
- else if (prefix) {
- /*
- * Does it begin with "a/$our-prefix" and such? Then this is
- * very likely to apply to our directory.
- */
- if (!strncmp(name, prefix, prefix_length))
- val = count_slashes(prefix);
- else {
- cp++;
- if (!strncmp(cp, prefix, prefix_length))
- val = count_slashes(prefix) + 1;
- }
- }
- free(name);
- return val;
-}
-
-/*
- * Does the ---/+++ line has the POSIX timestamp after the last HT?
- * GNU diff puts epoch there to signal a creation/deletion event. Is
- * this such a timestamp?
- */
-static int has_epoch_timestamp(const char *nameline)
-{
- /*
- * We are only interested in epoch timestamp; any non-zero
- * fraction cannot be one, hence "(\.0+)?" in the regexp below.
- * For the same reason, the date must be either 1969-12-31 or
- * 1970-01-01, and the seconds part must be "00".
- */
- const char stamp_regexp[] =
- "^(1969-12-31|1970-01-01)"
- " "
- "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
- " "
- "([-+][0-2][0-9]:?[0-5][0-9])\n";
- const char *timestamp = NULL, *cp, *colon;
- static regex_t *stamp;
- regmatch_t m[10];
- int zoneoffset;
- int hourminute;
- int status;
-
- for (cp = nameline; *cp != '\n'; cp++) {
- if (*cp == '\t')
- timestamp = cp + 1;
- }
- if (!timestamp)
- return 0;
- if (!stamp) {
- stamp = xmalloc(sizeof(*stamp));
- if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
- warning(_("Cannot prepare timestamp regexp %s"),
- stamp_regexp);
- return 0;
- }
- }
-
- status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
- if (status) {
- if (status != REG_NOMATCH)
- warning(_("regexec returned %d for input: %s"),
- status, timestamp);
- return 0;
- }
-
- zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10);
- if (*colon == ':')
- zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10);
- else
- zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
- if (timestamp[m[3].rm_so] == '-')
- zoneoffset = -zoneoffset;
-
- /*
- * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
- * (west of GMT) or 1970-01-01 (east of GMT)
- */
- if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) ||
- (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10)))
- return 0;
-
- hourminute = (strtol(timestamp + 11, NULL, 10) * 60 +
- strtol(timestamp + 14, NULL, 10) -
- zoneoffset);
-
- return ((zoneoffset < 0 && hourminute == 1440) ||
- (0 <= zoneoffset && !hourminute));
-}
-
-/*
- * Get the name etc info from the ---/+++ lines of a traditional patch header
- *
- * FIXME! The end-of-filename heuristics are kind of screwy. For existing
- * files, we can happily check the index for a match, but for creating a
- * new file we should try to match whatever "patch" does. I have no idea.
- */
-static void parse_traditional_patch(const char *first, const char *second, struct patch *patch)
-{
- char *name;
-
- first += 4; /* skip "--- " */
- second += 4; /* skip "+++ " */
- if (!p_value_known) {
- int p, q;
- p = guess_p_value(first);
- q = guess_p_value(second);
- if (p < 0) p = q;
- if (0 <= p && p == q) {
- p_value = p;
- p_value_known = 1;
- }
- }
- if (is_dev_null(first)) {
- patch->is_new = 1;
- patch->is_delete = 0;
- name = find_name_traditional(second, NULL, p_value);
- patch->new_name = name;
- } else if (is_dev_null(second)) {
- patch->is_new = 0;
- patch->is_delete = 1;
- name = find_name_traditional(first, NULL, p_value);
- patch->old_name = name;
- } else {
- 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;
- patch->new_name = name;
- } else if (has_epoch_timestamp(second)) {
- patch->is_new = 0;
- patch->is_delete = 1;
- patch->old_name = name;
- } else {
- patch->old_name = name;
- patch->new_name = null_strdup(name);
- }
- }
- if (!name)
- die(_("unable to find filename in patch at line %d"), linenr);
-}
-
-static int gitdiff_hdrend(const char *line, struct patch *patch)
-{
- return -1;
-}
-
-/*
- * We're anal about diff header consistency, to make
- * sure that we don't end up having strange ambiguous
- * patches floating around.
- *
- * As a result, gitdiff_{old|new}name() will check
- * their names against any previous information, just
- * to make sure..
- */
-#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);
-
- if (orig_name) {
- int len;
- const char *name;
- char *another;
- name = orig_name;
- len = strlen(name);
- if (isnull)
- 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((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);
- return NULL;
- }
-}
-
-static int gitdiff_oldname(const char *line, struct patch *patch)
-{
- 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)
-{
- 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;
-}
-
-static int gitdiff_oldmode(const char *line, struct patch *patch)
-{
- patch->old_mode = strtoul(line, NULL, 8);
- return 0;
-}
-
-static int gitdiff_newmode(const char *line, struct patch *patch)
-{
- patch->new_mode = strtoul(line, NULL, 8);
- return 0;
-}
-
-static int gitdiff_delete(const char *line, struct patch *patch)
-{
- patch->is_delete = 1;
- free(patch->old_name);
- patch->old_name = null_strdup(patch->def_name);
- return gitdiff_oldmode(line, patch);
-}
-
-static int gitdiff_newfile(const char *line, struct patch *patch)
-{
- patch->is_new = 1;
- free(patch->new_name);
- patch->new_name = null_strdup(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;
-}
-
-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;
-}
-
-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;
-}
-
-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)
-{
- 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)
-{
- unsigned long val = strtoul(line, NULL, 10);
- if (val <= 100)
- patch->score = val;
- return 0;
-}
-
-static int gitdiff_index(const char *line, struct patch *patch)
-{
- /*
- * index line is N hexadecimal, "..", N hexadecimal,
- * and optional space with octal mode.
- */
- const char *ptr, *eol;
- int len;
-
- ptr = strchr(line, '.');
- if (!ptr || ptr[1] != '.' || 40 < ptr - line)
- return 0;
- len = ptr - line;
- memcpy(patch->old_sha1_prefix, line, len);
- patch->old_sha1_prefix[len] = 0;
-
- line = ptr + 2;
- ptr = strchr(line, ' ');
- eol = strchr(line, '\n');
-
- if (!ptr || eol < ptr)
- ptr = eol;
- len = ptr - line;
-
- if (40 < len)
- return 0;
- memcpy(patch->new_sha1_prefix, line, len);
- patch->new_sha1_prefix[len] = 0;
- if (*ptr == ' ')
- patch->old_mode = strtoul(ptr+1, NULL, 8);
- return 0;
-}
-
-/*
- * This is normal for a diff that doesn't change anything: we'll fall through
- * into the next diff. Tell the parser to break out.
- */
-static int gitdiff_unrecognized(const char *line, struct patch *patch)
-{
- return -1;
-}
-
-/*
- * 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;
- 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 (i == 0) ? NULL : &line[i + 1];
- }
- return NULL;
-}
-
-/*
- * This is to extract the same name that appears on "diff --git"
- * line. We do not find and return anything if it is a rename
- * patch, and it is OK because we will find the name elsewhere.
- * We need to reliably find name only when it is mode-change only,
- * 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(const char *line, int llen)
-{
- const char *name;
- const char *second = NULL;
- size_t len, line_len;
-
- line += strlen("diff --git ");
- llen -= strlen("diff --git ");
-
- if (*line == '"') {
- const char *cp;
- struct strbuf first = STRBUF_INIT;
- struct strbuf sp = STRBUF_INIT;
-
- if (unquote_c_style(&first, line, &second))
- goto free_and_fail1;
-
- /* 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 - first.buf);
-
- /*
- * second points at one past closing dq of name.
- * find the second name.
- */
- while ((second < line + llen) && isspace(*second))
- second++;
-
- if (line + llen <= second)
- goto free_and_fail1;
- if (*second == '"') {
- if (unquote_c_style(&sp, second, NULL))
- goto free_and_fail1;
- cp = skip_tree_prefix(sp.buf, sp.len);
- if (!cp)
- goto free_and_fail1;
- /* They must match, otherwise ignore */
- if (strcmp(cp, first.buf))
- goto free_and_fail1;
- strbuf_release(&sp);
- return strbuf_detach(&first, NULL);
- }
-
- /* unquoted second */
- cp = skip_tree_prefix(second, line + llen - second);
- if (!cp)
- goto free_and_fail1;
- if (line + llen - cp != first.len ||
- memcmp(first.buf, cp, first.len))
- goto free_and_fail1;
- return strbuf_detach(&first, NULL);
-
- free_and_fail1:
- strbuf_release(&first);
- strbuf_release(&sp);
- return NULL;
- }
-
- /* unquoted first name */
- name = skip_tree_prefix(line, llen);
- if (!name)
- return NULL;
-
- /*
- * since the first name is unquoted, a dq if exists must be
- * the beginning of the second name.
- */
- for (second = name; second < line + llen; second++) {
- if (*second == '"') {
- struct strbuf sp = STRBUF_INIT;
- const char *np;
-
- if (unquote_c_style(&sp, second, NULL))
- goto free_and_fail2;
-
- np = skip_tree_prefix(sp.buf, sp.len);
- if (!np)
- goto free_and_fail2;
-
- len = sp.buf + sp.len - np;
- if (len < second - name &&
- !strncmp(np, name, len) &&
- isspace(name[len])) {
- /* Good */
- strbuf_remove(&sp, 0, np - sp.buf);
- return strbuf_detach(&sp, NULL);
- }
-
- free_and_fail2:
- strbuf_release(&sp);
- return NULL;
- }
- }
-
- /*
- * Accept a name only if it shows up twice, exactly the same
- * form.
- */
- second = strchr(name, '\n');
- if (!second)
- return NULL;
- line_len = second - name;
- for (len = 0 ; ; len++) {
- switch (name[len]) {
- default:
- continue;
- case '\n':
- return NULL;
- case '\t': case ' ':
- /*
- * 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;
- /*
- * 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(const char *line, int len, unsigned int size, struct patch *patch)
-{
- unsigned long offset;
-
- /* A git diff has explicit new/delete information, so we don't guess */
- patch->is_new = 0;
- patch->is_delete = 0;
-
- /*
- * Some things may not have the old name in the
- * rest of the headers anywhere (pure mode changes,
- * or removing or adding empty files), so we get
- * the default name from the header.
- */
- 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);
- free(patch->def_name);
- patch->def_name = s;
- }
-
- line += len;
- size -= len;
- linenr++;
- for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
- static const struct opentry {
- const char *str;
- int (*fn)(const char *, struct patch *);
- } optable[] = {
- { "@@ -", gitdiff_hdrend },
- { "--- ", gitdiff_oldname },
- { "+++ ", gitdiff_newname },
- { "old mode ", gitdiff_oldmode },
- { "new mode ", gitdiff_newmode },
- { "deleted file mode ", gitdiff_delete },
- { "new file mode ", gitdiff_newfile },
- { "copy from ", gitdiff_copysrc },
- { "copy to ", gitdiff_copydst },
- { "rename old ", gitdiff_renamesrc },
- { "rename new ", gitdiff_renamedst },
- { "rename from ", gitdiff_renamesrc },
- { "rename to ", gitdiff_renamedst },
- { "similarity index ", gitdiff_similarity },
- { "dissimilarity index ", gitdiff_dissimilarity },
- { "index ", gitdiff_index },
- { "", gitdiff_unrecognized },
- };
- int i;
-
- len = linelen(line, size);
- if (!len || line[len-1] != '\n')
- break;
- for (i = 0; i < ARRAY_SIZE(optable); i++) {
- const struct opentry *p = optable + i;
- int oplen = strlen(p->str);
- if (len < oplen || memcmp(p->str, line, oplen))
- continue;
- if (p->fn(line + oplen, patch) < 0)
- return offset;
- break;
- }
- }
-
- return offset;
-}
-
-static int parse_num(const char *line, unsigned long *p)
-{
- char *ptr;
-
- if (!isdigit(*line))
- return 0;
- *p = strtoul(line, &ptr, 10);
- return ptr - line;
-}
-
-static int parse_range(const char *line, int len, int offset, const char *expect,
- unsigned long *p1, unsigned long *p2)
-{
- int digits, ex;
-
- if (offset < 0 || offset >= len)
- return -1;
- line += offset;
- len -= offset;
-
- digits = parse_num(line, p1);
- if (!digits)
- return -1;
-
- offset += digits;
- line += digits;
- len -= digits;
-
- *p2 = 1;
- if (*line == ',') {
- digits = parse_num(line+1, p2);
- if (!digits)
- return -1;
-
- offset += digits+1;
- line += digits+1;
- len -= digits+1;
- }
-
- ex = strlen(expect);
- if (ex > len)
- return -1;
- if (memcmp(line, expect, ex))
- return -1;
-
- return offset + ex;
-}
-
-static void recount_diff(const char *line, int size, struct fragment *fragment)
-{
- int oldlines = 0, newlines = 0, ret = 0;
-
- if (size < 1) {
- warning("recount: ignore empty hunk");
- return;
- }
-
- for (;;) {
- int len = linelen(line, size);
- size -= len;
- line += len;
-
- if (size < 1)
- break;
-
- switch (*line) {
- case ' ': case '\n':
- newlines++;
- /* fall through */
- case '-':
- oldlines++;
- continue;
- case '+':
- newlines++;
- continue;
- case '\\':
- continue;
- case '@':
- ret = size < 3 || !starts_with(line, "@@ ");
- break;
- case 'd':
- ret = size < 5 || !starts_with(line, "diff ");
- break;
- default:
- ret = -1;
- break;
- }
- if (ret) {
- warning(_("recount: unexpected line: %.*s"),
- (int)linelen(line, size), line);
- return;
- }
- break;
- }
- fragment->oldlines = oldlines;
- fragment->newlines = newlines;
-}
-
-/*
- * Parse a unified diff fragment header of the
- * form "@@ -a,b +c,d @@"
- */
-static int parse_fragment_header(const char *line, int len, struct fragment *fragment)
-{
- int offset;
-
- if (!len || line[len-1] != '\n')
- return -1;
-
- /* Figure out the number of lines in a fragment */
- offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines);
- offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines);
-
- return offset;
-}
-
-static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch)
-{
- unsigned long offset, len;
-
- patch->is_toplevel_relative = 0;
- patch->is_rename = patch->is_copy = 0;
- patch->is_new = patch->is_delete = -1;
- patch->old_mode = patch->new_mode = 0;
- patch->old_name = patch->new_name = NULL;
- for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
- unsigned long nextlen;
-
- len = linelen(line, size);
- if (!len)
- break;
-
- /* Testing this early allows us to take a few shortcuts.. */
- if (len < 6)
- continue;
-
- /*
- * Make sure we don't find any unconnected patch fragments.
- * That's a sign that we didn't find a header, and that a
- * patch has become corrupted/broken up.
- */
- if (!memcmp("@@ -", line, 4)) {
- struct fragment dummy;
- if (parse_fragment_header(line, len, &dummy) < 0)
- continue;
- die(_("patch fragment without header at line %d: %.*s"),
- linenr, (int)len-1, line);
- }
-
- if (size < len + 6)
- break;
-
- /*
- * Git patch? It might not have a real patch, just a rename
- * or mode change, so we handle that specially
- */
- if (!memcmp("diff --git ", line, 11)) {
- int git_hdr_len = parse_git_header(line, len, size, patch);
- if (git_hdr_len <= len)
- continue;
- if (!patch->old_name && !patch->new_name) {
- if (!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;
- }
-
- /* --- followed by +++ ? */
- if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4))
- continue;
-
- /*
- * We only accept unified patches, so we want it to
- * at least have "@@ -a,b +c,d @@\n", which is 14 chars
- * minimum ("@@ -0,0 +1 @@\n" is the shortest).
- */
- nextlen = linelen(line + len, size - len);
- if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
- continue;
-
- /* Ok, we'll consider it a patch */
- parse_traditional_patch(line, line+len, patch);
- *hdrsize = len + nextlen;
- linenr += 2;
- return offset;
- }
- return -1;
-}
-
-static void record_ws_error(unsigned result, const char *line, int len, int linenr)
-{
- char *err;
-
- if (!result)
- return;
-
- whitespace_error++;
- if (squelch_whitespace_errors &&
- squelch_whitespace_errors < whitespace_error)
- return;
-
- err = whitespace_error_string(result);
- fprintf(stderr, "%s:%d: %s.\n%.*s\n",
- patch_input_file, linenr, err, len, line);
- free(err);
-}
-
-static void check_whitespace(const char *line, int len, unsigned ws_rule)
-{
- unsigned result = ws_check(line + 1, len - 1, ws_rule);
-
- record_ws_error(result, line + 1, len - 2, linenr);
-}
-
-/*
- * Parse a unified diff. Note that this really needs to parse each
- * fragment separately, since the only way to know the difference
- * 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(const char *line, unsigned long size,
- struct patch *patch, struct fragment *fragment)
-{
- int added, deleted;
- int len = linelen(line, size), offset;
- unsigned long oldlines, newlines;
- unsigned long leading, trailing;
-
- offset = parse_fragment_header(line, len, fragment);
- if (offset < 0)
- return -1;
- if (offset > 0 && patch->recount)
- recount_diff(line + offset, size - offset, fragment);
- oldlines = fragment->oldlines;
- newlines = fragment->newlines;
- leading = 0;
- trailing = 0;
-
- /* Parse the thing.. */
- line += len;
- size -= len;
- linenr++;
- added = deleted = 0;
- for (offset = len;
- 0 < size;
- offset += len, size -= len, line += len, linenr++) {
- if (!oldlines && !newlines)
- break;
- len = linelen(line, size);
- if (!len || line[len-1] != '\n')
- return -1;
- switch (*line) {
- default:
- return -1;
- case '\n': /* newer GNU diff, an empty context line */
- case ' ':
- oldlines--;
- newlines--;
- if (!deleted && !added)
- leading++;
- trailing++;
- break;
- case '-':
- if (apply_in_reverse &&
- ws_error_action != nowarn_ws_error)
- check_whitespace(line, len, patch->ws_rule);
- deleted++;
- oldlines--;
- trailing = 0;
- break;
- case '+':
- if (!apply_in_reverse &&
- ws_error_action != nowarn_ws_error)
- check_whitespace(line, len, patch->ws_rule);
- added++;
- newlines--;
- trailing = 0;
- break;
-
- /*
- * We allow "\ No newline at end of file". Depending
- * on locale settings when the patch was produced we
- * don't know what this line looks like. The only
- * thing we do know is that it begins with "\ ".
- * Checking for 12 is just for sanity check -- any
- * l10n of "\ No newline..." is at least that long.
- */
- case '\\':
- if (len < 12 || memcmp(line, "\\ ", 2))
- return -1;
- break;
- }
- }
- if (oldlines || newlines)
- return -1;
- fragment->leading = leading;
- fragment->trailing = trailing;
-
- /*
- * If a fragment ends with an incomplete line, we failed to include
- * it in the above loop because we hit oldlines == newlines == 0
- * before seeing it.
- */
- if (12 < size && !memcmp(line, "\\ ", 2))
- offset += linelen(line, size);
-
- patch->lines_added += added;
- patch->lines_deleted += deleted;
-
- if (0 < patch->is_new && oldlines)
- return error(_("new file depends on old contents"));
- if (0 < patch->is_delete && newlines)
- return error(_("deleted file still has contents"));
- return offset;
-}
-
-/*
- * 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;
- struct fragment **fragp = &patch->fragments;
-
- while (size > 4 && !memcmp(line, "@@ -", 4)) {
- struct fragment *fragment;
- int len;
-
- fragment = xcalloc(1, sizeof(*fragment));
- fragment->linenr = linenr;
- len = parse_fragment(line, size, patch, fragment);
- if (len <= 0)
- die(_("corrupt patch at line %d"), linenr);
- fragment->patch = line;
- fragment->size = len;
- oldlines += fragment->oldlines;
- newlines += fragment->newlines;
- context += fragment->leading + fragment->trailing;
-
- *fragp = fragment;
- fragp = &fragment->next;
-
- offset += len;
- line += len;
- size -= len;
- }
-
- /*
- * If something was removed (i.e. we have old-lines) it cannot
- * be creation, and if something was added it cannot be
- * deletion. However, the reverse is not true; --unified=0
- * patches that only add are not necessarily creation even
- * though they do not have any old lines, and ones that only
- * delete are not necessarily deletion.
- *
- * Unfortunately, a real creation/deletion patch do _not_ have
- * any context line by definition, so we cannot safely tell it
- * apart with --unified=0 insanity. At least if the patch has
- * more than one hunk it is not creation or deletion.
- */
- if (patch->is_new < 0 &&
- (oldlines || (patch->fragments && patch->fragments->next)))
- patch->is_new = 0;
- if (patch->is_delete < 0 &&
- (newlines || (patch->fragments && patch->fragments->next)))
- patch->is_delete = 0;
-
- if (0 < patch->is_new && oldlines)
- 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);
- if (!patch->is_delete && !newlines && context)
- fprintf_ln(stderr,
- _("** warning: "
- "file %s becomes empty but is not deleted"),
- patch->new_name);
-
- return offset;
-}
-
-static inline int metadata_changes(struct patch *patch)
-{
- return patch->is_rename > 0 ||
- patch->is_copy > 0 ||
- patch->is_new > 0 ||
- patch->is_delete ||
- (patch->old_mode && patch->new_mode &&
- patch->old_mode != patch->new_mode);
-}
-
-static char *inflate_it(const void *data, unsigned long size,
- unsigned long inflated_size)
-{
- git_zstream stream;
- void *out;
- int st;
-
- memset(&stream, 0, sizeof(stream));
-
- stream.next_in = (unsigned char *)data;
- stream.avail_in = size;
- stream.next_out = out = xmalloc(inflated_size);
- stream.avail_out = inflated_size;
- git_inflate_init(&stream);
- st = git_inflate(&stream, Z_FINISH);
- git_inflate_end(&stream);
- if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
- free(out);
- return NULL;
- }
- 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,
- int *used_p)
-{
- /*
- * Expect a line that begins with binary patch method ("literal"
- * or "delta"), followed by the length of data before deflating.
- * a sequence of 'length-byte' followed by base-85 encoded data
- * should follow, terminated by a newline.
- *
- * Each 5-byte sequence of base-85 encodes up to 4 bytes,
- * and we would limit the patch line to 66 characters,
- * so one line can fit up to 13 groups that would decode
- * to 52 bytes max. The length byte 'A'-'Z' corresponds
- * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes.
- */
- int llen, used;
- unsigned long size = *sz_p;
- char *buffer = *buf_p;
- int patch_method;
- unsigned long origlen;
- char *data = NULL;
- int hunk_size = 0;
- struct fragment *frag;
-
- llen = linelen(buffer, size);
- used = llen;
-
- *status_p = 0;
-
- if (starts_with(buffer, "delta ")) {
- patch_method = BINARY_DELTA_DEFLATED;
- origlen = strtoul(buffer + 6, NULL, 10);
- }
- else if (starts_with(buffer, "literal ")) {
- patch_method = BINARY_LITERAL_DEFLATED;
- origlen = strtoul(buffer + 8, NULL, 10);
- }
- else
- return NULL;
-
- linenr++;
- buffer += llen;
- while (1) {
- int byte_length, max_byte_length, newsize;
- llen = linelen(buffer, size);
- used += llen;
- linenr++;
- if (llen == 1) {
- /* consume the blank line */
- buffer++;
- size--;
- break;
- }
- /*
- * Minimum line is "A00000\n" which is 7-byte long,
- * and the line length must be multiple of 5 plus 2.
- */
- if ((llen < 7) || (llen-2) % 5)
- goto corrupt;
- max_byte_length = (llen - 2) / 5 * 4;
- byte_length = *buffer;
- if ('A' <= byte_length && byte_length <= 'Z')
- byte_length = byte_length - 'A' + 1;
- else if ('a' <= byte_length && byte_length <= 'z')
- byte_length = byte_length - 'a' + 27;
- else
- goto corrupt;
- /* if the input length was not multiple of 4, we would
- * have filler at the end but the filler should never
- * exceed 3 bytes
- */
- if (max_byte_length < byte_length ||
- byte_length <= max_byte_length - 4)
- goto corrupt;
- newsize = hunk_size + byte_length;
- data = xrealloc(data, newsize);
- if (decode_85(data + hunk_size, buffer + 1, byte_length))
- goto corrupt;
- hunk_size = newsize;
- buffer += llen;
- size -= llen;
- }
-
- frag = xcalloc(1, sizeof(*frag));
- frag->patch = inflate_it(data, hunk_size, origlen);
- frag->free_patch = 1;
- if (!frag->patch)
- goto corrupt;
- free(data);
- frag->size = origlen;
- *buf_p = buffer;
- *sz_p = size;
- *used_p = used;
- frag->binary_patch_method = patch_method;
- return frag;
-
- corrupt:
- free(data);
- *status_p = -1;
- error(_("corrupt binary patch at line %d: %.*s"),
- linenr-1, llen-1, buffer);
- return NULL;
-}
-
-static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
-{
- /*
- * We have read "GIT binary patch\n"; what follows is a line
- * that says the patch method (currently, either "literal" or
- * "delta") and the length of data before deflating; a
- * sequence of 'length-byte' followed by base-85 encoded data
- * follows.
- *
- * When a binary patch is reversible, there is another binary
- * hunk in the same format, starting with patch method (either
- * "literal" or "delta") with the length of data, and a sequence
- * of length-byte + base-85 encoded data, terminated with another
- * empty line. This data, when applied to the postimage, produces
- * the preimage.
- */
- struct fragment *forward;
- struct fragment *reverse;
- int status;
- int used, used_1;
-
- 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);
- if (status)
- /* otherwise we already gave an error message */
- return status;
-
- reverse = parse_binary_hunk(&buffer, &size, &status, &used_1);
- if (reverse)
- used += used_1;
- else if (status) {
- /*
- * Not having reverse hunk is not an error, but having
- * a corrupt reverse hunk is.
- */
- free((void*) forward->patch);
- free(forward);
- return status;
- }
- forward->next = reverse;
- patch->fragments = forward;
- patch->is_binary = 1;
- return used;
-}
-
-/*
- * 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;
- int offset = find_header(buffer, size, &hdrsize, patch);
-
- if (offset < 0)
- return offset;
-
- 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 git_binary[] = "GIT binary patch\n";
- int hd = hdrsize + offset;
- unsigned long llen = linelen(buffer + hd, size - hd);
-
- if (llen == sizeof(git_binary) - 1 &&
- !memcmp(git_binary, buffer + hd, llen)) {
- int used;
- linenr++;
- used = parse_binary(buffer + hd + llen,
- size - hd - llen, patch);
- if (used)
- patchsize = used + llen;
- else
- 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 &&
- !memcmp(binhdr[i], buffer + hd, len)) {
- linenr++;
- patch->is_binary = 1;
- patchsize = llen;
- break;
- }
- }
- }
-
- /* Empty patch cannot be applied if it is a text patch
- * without metadata change. A binary patch appears
- * empty to us here.
- */
- if ((apply || check) &&
- (!patch->is_binary && !metadata_changes(patch)))
- die(_("patch with only garbage at line %d"), linenr);
- }
-
- return offset + hdrsize + patchsize;
-}
-
-#define swap(a,b) myswap((a),(b),sizeof(a))
-
-#define myswap(a, b, size) do { \
- unsigned char mytmp[size]; \
- memcpy(mytmp, &a, size); \
- memcpy(&a, &b, size); \
- memcpy(&b, mytmp, size); \
-} while (0)
-
-static void reverse_patches(struct patch *p)
-{
- for (; p; p = p->next) {
- struct fragment *frag = p->fragments;
-
- swap(p->new_name, p->old_name);
- swap(p->new_mode, p->old_mode);
- swap(p->is_new, p->is_delete);
- swap(p->lines_added, p->lines_deleted);
- swap(p->old_sha1_prefix, p->new_sha1_prefix);
-
- for (; frag; frag = frag->next) {
- swap(frag->newpos, frag->oldpos);
- swap(frag->newlines, frag->oldlines);
- }
- }
-}
-
-static const char pluses[] =
-"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]=
-"----------------------------------------------------------------------";
-
-static void show_stats(struct patch *patch)
-{
- struct strbuf qname = STRBUF_INIT;
- char *cp = patch->new_name ? patch->new_name : patch->old_name;
- int max, add, del;
-
- quote_c_style(cp, &qname, NULL, 0);
-
- /*
- * "scale" the filename
- */
- max = max_len;
- if (max > 50)
- max = 50;
-
- if (qname.len > max) {
- cp = strchr(qname.buf + qname.len + 3 - max, '/');
- if (!cp)
- cp = qname.buf + qname.len + 3 - max;
- strbuf_splice(&qname, 0, cp - qname.buf, "...", 3);
- }
-
- if (patch->is_binary) {
- printf(" %-*s | Bin\n", max, qname.buf);
- strbuf_release(&qname);
- return;
- }
-
- printf(" %-*s |", max, qname.buf);
- strbuf_release(&qname);
-
- /*
- * scale the add/delete
- */
- max = max + max_change > 70 ? 70 - max : max_change;
- add = patch->lines_added;
- del = patch->lines_deleted;
-
- if (max_change > 0) {
- int total = ((add + del) * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
- del = total - add;
- }
- printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted,
- add, pluses, del, minuses);
-}
-
-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 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);
- convert_to_git(path, buf->buf, buf->len, buf, 0);
- return 0;
- default:
- return -1;
- }
-}
-
-/*
- * Update the preimage, and the common lines in postimage,
- * from buffer buf of length len. If postlen is 0 the postimage
- * is updated in place, otherwise it's updated on a new buffer
- * of length postlen
- */
-
-static void update_pre_post_images(struct image *preimage,
- struct image *postimage,
- char *buf,
- size_t len, size_t postlen)
-{
- int i, ctx, reduced;
- char *new, *old, *fixed;
- struct image fixed_preimage;
-
- /*
- * Update the preimage with whitespace fixes. Note that we
- * are not losing preimage->buf -- apply_one_fragment() will
- * free "oldlines".
- */
- prepare_image(&fixed_preimage, buf, len, 1);
- 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 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.
- */
- old = postimage->buf;
- if (postlen)
- new = postimage->buf = xmalloc(postlen);
- else
- new = old;
- fixed = preimage->buf;
-
- 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 */
- memmove(new, old, len);
- old += len;
- new += len;
- continue;
- }
-
- /* a common context -- skip it in the original postimage */
- old += len;
-
- /* and find the corresponding one in the fixed preimage */
- while (ctx < preimage->nr &&
- !(preimage->line[ctx].flag & LINE_COMMON)) {
- fixed += preimage->line[ctx].len;
- ctx++;
- }
-
- /*
- * 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;
- memcpy(new, fixed, len);
- new += len;
- fixed += len;
- postimage->line[i].len = len;
- ctx++;
- }
-
- /* Fix the length of the whole thing */
- postimage->len = new - postimage->buf;
- postimage->nr -= reduced;
-}
-
-static int match_fragment(struct image *img,
- struct image *preimage,
- struct image *postimage,
- unsigned long try,
- int try_lno,
- unsigned ws_rule,
- int match_beginning, int match_end)
-{
- int i;
- char *fixed_buf, *buf, *orig, *target;
- struct strbuf fixed;
- size_t fixed_len, postlen;
- int preimage_limit;
-
- if (preimage->nr + try_lno <= img->nr) {
- /*
- * The hunk falls within the boundaries of img.
- */
- preimage_limit = preimage->nr;
- if (match_end && (preimage->nr + try_lno != img->nr))
- return 0;
- } else if (ws_error_action == correct_ws_error &&
- (ws_rule & WS_BLANK_AT_EOF)) {
- /*
- * This hunk extends beyond the end of img, and we are
- * removing blank lines at the end of the file. This
- * many lines from the beginning of the preimage must
- * match with img, and the remainder of the preimage
- * must be blank.
- */
- preimage_limit = img->nr - try_lno;
- } else {
- /*
- * The hunk extends beyond the end of the img and
- * we are not removing blanks at the end, so we
- * should reject the hunk at this position.
- */
- return 0;
- }
-
- if (match_beginning && try_lno)
- return 0;
-
- /* Quick hash check */
- for (i = 0; i < preimage_limit; i++)
- if ((img->line[try_lno + i].flag & LINE_PATCHED) ||
- (preimage->line[i].hash != img->line[try_lno + i].hash))
- return 0;
-
- if (preimage_limit == preimage->nr) {
- /*
- * Do we have an exact match? If we were told to match
- * at the end, size must be exactly at try+fragsize,
- * otherwise try+fragsize must be still within the preimage,
- * and either case, the old piece should match the preimage
- * exactly.
- */
- if ((match_end
- ? (try + preimage->len == img->len)
- : (try + preimage->len <= img->len)) &&
- !memcmp(img->buf + try, preimage->buf, preimage->len))
- return 1;
- } else {
- /*
- * The preimage extends beyond the end of img, so
- * there cannot be an exact match.
- *
- * There must be one non-blank context line that match
- * a line before the end of img.
- */
- char *buf_end;
-
- buf = preimage->buf;
- buf_end = buf;
- for (i = 0; i < preimage_limit; i++)
- buf_end += preimage->line[i].len;
-
- for ( ; buf < buf_end; buf++)
- if (!isspace(*buf))
- break;
- if (buf == buf_end)
- return 0;
- }
-
- /*
- * No exact match. If we are ignoring whitespace, run a line-by-line
- * fuzzy matching. We collect all the line length information because
- * we need it to adjust whitespace if we match.
- */
- if (ws_ignore_action == ignore_ws_change) {
- size_t imgoff = 0;
- size_t preoff = 0;
- size_t postlen = postimage->len;
- size_t extra_chars;
- char *preimage_eof;
- char *preimage_end;
- for (i = 0; i < preimage_limit; i++) {
- size_t prelen = preimage->line[i].len;
- size_t imglen = img->line[try_lno+i].len;
-
- if (!fuzzy_matchlines(img->buf + try + imgoff, imglen,
- preimage->buf + preoff, prelen))
- return 0;
- if (preimage->line[i].flag & LINE_COMMON)
- postlen += imglen - prelen;
- imgoff += imglen;
- preoff += prelen;
- }
-
- /*
- * Ok, the preimage matches with whitespace fuzz.
- *
- * imgoff now holds the true length of the target that
- * matches the preimage before the end of the file.
- *
- * Count the number of characters in the preimage that fall
- * beyond the end of the file and make sure that all of them
- * are whitespace characters. (This can only happen if
- * we are removing blank lines at the end of the file.)
- */
- buf = preimage_eof = preimage->buf + preoff;
- for ( ; i < preimage->nr; i++)
- preoff += preimage->line[i].len;
- preimage_end = preimage->buf + preoff;
- for ( ; buf < preimage_end; buf++)
- if (!isspace(*buf))
- return 0;
-
- /*
- * Update the preimage and the common postimage context
- * lines to use the same whitespace as the target.
- * If whitespace is missing in the target (i.e.
- * if the preimage extends beyond the end of the file),
- * use the whitespace from the preimage.
- */
- extra_chars = preimage_end - preimage_eof;
- strbuf_init(&fixed, imgoff + extra_chars);
- strbuf_add(&fixed, img->buf + try, imgoff);
- strbuf_add(&fixed, preimage_eof, extra_chars);
- fixed_buf = strbuf_detach(&fixed, &fixed_len);
- update_pre_post_images(preimage, postimage,
- fixed_buf, fixed_len, postlen);
- return 1;
- }
-
- if (ws_error_action != correct_ws_error)
- return 0;
-
- /*
- * The hunk does not apply byte-by-byte, but the hash says
- * it might with whitespace fuzz. We haven't been asked to
- * ignore whitespace, we were asked to correct whitespace
- * errors, so let's try matching after whitespace correction.
- *
- * 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.
- */
- strbuf_init(&fixed, preimage->len + 1);
- orig = preimage->buf;
- target = img->buf + try;
- postlen = 0;
- for (i = 0; i < preimage_limit; i++) {
- size_t oldlen = preimage->line[i].len;
- size_t tgtlen = img->line[try_lno + i].len;
- size_t fixstart = fixed.len;
- struct strbuf tgtfix;
- int match;
-
- /* Try fixing the line in the preimage */
- ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL);
-
- /* Try fixing the line in the target */
- strbuf_init(&tgtfix, tgtlen);
- ws_fix_copy(&tgtfix, target, tgtlen, ws_rule, NULL);
-
- /*
- * If they match, either the preimage was based on
- * a version before our tree fixed whitespace breakage,
- * or we are lacking a whitespace-fix patch the tree
- * the preimage was based on already had (i.e. target
- * has whitespace breakage, the preimage doesn't).
- * In either case, we are fixing the whitespace breakages
- * so we might as well take the fix together with their
- * real change.
- */
- match = (tgtfix.len == fixed.len - fixstart &&
- !memcmp(tgtfix.buf, fixed.buf + fixstart,
- fixed.len - fixstart));
- postlen += tgtfix.len;
-
- strbuf_release(&tgtfix);
- if (!match)
- goto unmatch_exit;
-
- orig += oldlen;
- target += tgtlen;
- }
-
-
- /*
- * Now handle the lines in the preimage that falls beyond the
- * end of the file (if any). They will only match if they are
- * empty or only contain whitespace (if WS_BLANK_AT_EOL is
- * false).
- */
- for ( ; i < preimage->nr; i++) {
- size_t fixstart = fixed.len; /* start of the fixed preimage */
- size_t oldlen = preimage->line[i].len;
- int j;
-
- /* Try fixing the line in the preimage */
- ws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL);
-
- for (j = fixstart; j < fixed.len; j++)
- if (!isspace(fixed.buf[j]))
- goto unmatch_exit;
-
- orig += oldlen;
- }
-
- /*
- * Yes, the preimage is based on an older version that still
- * has whitespace breakages unfixed, and fixing them makes the
- * 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, postlen);
- return 1;
-
- unmatch_exit:
- strbuf_release(&fixed);
- return 0;
-}
-
-static int find_pos(struct image *img,
- struct image *preimage,
- struct image *postimage,
- int line,
- unsigned ws_rule,
- int match_beginning, int match_end)
-{
- int i;
- unsigned long backwards, forwards, try;
- int backwards_lno, forwards_lno, try_lno;
-
- /*
- * If match_beginning or match_end is specified, there is no
- * point starting from a wrong line that will never match and
- * wander around and wait for a match at the specified end.
- */
- if (match_beginning)
- line = 0;
- else if (match_end)
- line = img->nr - preimage->nr;
-
- /*
- * Because the comparison is unsigned, the following test
- * will also take care of a negative line number that can
- * result when match_end and preimage is larger than the target.
- */
- if ((size_t) line > img->nr)
- line = img->nr;
-
- try = 0;
- for (i = 0; i < line; i++)
- try += img->line[i].len;
-
- /*
- * There's probably some smart way to do this, but I'll leave
- * that to the smart and beautiful people. I'm simple and stupid.
- */
- backwards = try;
- backwards_lno = line;
- forwards = try;
- forwards_lno = line;
- try_lno = line;
-
- for (i = 0; ; i++) {
- if (match_fragment(img, preimage, postimage,
- try, try_lno, ws_rule,
- match_beginning, match_end))
- return try_lno;
-
- again:
- if (backwards_lno == 0 && forwards_lno == img->nr)
- break;
-
- if (i & 1) {
- if (backwards_lno == 0) {
- i++;
- goto again;
- }
- backwards_lno--;
- backwards -= img->line[backwards_lno].len;
- try = backwards;
- try_lno = backwards_lno;
- } else {
- if (forwards_lno == img->nr) {
- i++;
- goto again;
- }
- forwards += img->line[forwards_lno].len;
- forwards_lno++;
- try = forwards;
- try_lno = forwards_lno;
- }
-
- }
- return -1;
-}
-
-static void remove_first_line(struct image *img)
-{
- img->buf += img->line[0].len;
- img->len -= img->line[0].len;
- img->line++;
- img->nr--;
-}
-
-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,
- struct image *postimage)
-{
- /*
- * remove the copy of preimage at offset in img
- * and replace it with postimage
- */
- int i, nr;
- size_t remove_count, insert_count, applied_at = 0;
- char *result;
- int preimage_limit;
-
- /*
- * If we are removing blank lines at the end of img,
- * the preimage may extend beyond the end.
- * If that is the case, we must be careful only to
- * remove the part of the preimage that falls within
- * the boundaries of img. Initialize preimage_limit
- * to the number of lines in the preimage that falls
- * within the boundaries.
- */
- preimage_limit = preimage->nr;
- if (preimage_limit > img->nr - applied_pos)
- preimage_limit = img->nr - applied_pos;
-
- for (i = 0; i < applied_pos; i++)
- applied_at += img->line[i].len;
-
- remove_count = 0;
- for (i = 0; i < preimage_limit; i++)
- remove_count += img->line[applied_pos + i].len;
- insert_count = postimage->len;
-
- /* Adjust the contents */
- result = xmalloc(img->len + insert_count - remove_count + 1);
- memcpy(result, img->buf, applied_at);
- memcpy(result + applied_at, postimage->buf, postimage->len);
- memcpy(result + applied_at + postimage->len,
- img->buf + (applied_at + remove_count),
- img->len - (applied_at + remove_count));
- free(img->buf);
- img->buf = result;
- img->len += insert_count - remove_count;
- result[img->len] = '\0';
-
- /* Adjust the line table */
- nr = img->nr + postimage->nr - preimage_limit;
- if (preimage_limit < postimage->nr) {
- /*
- * 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));
- img->line_allocated = img->line;
- }
- if (preimage_limit != postimage->nr)
- memmove(img->line + applied_pos + postimage->nr,
- img->line + applied_pos + preimage_limit,
- (img->nr - (applied_pos + preimage_limit)) *
- sizeof(*img->line));
- memcpy(img->line + applied_pos,
- postimage->line,
- postimage->nr * sizeof(*img->line));
- if (!allow_overlap)
- for (i = 0; i < postimage->nr; i++)
- img->line[applied_pos + i].flag |= LINE_PATCHED;
- 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)
-{
- int match_beginning, match_end;
- const char *patch = frag->patch;
- int size = frag->size;
- 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;
- struct image postimage;
-
- memset(&preimage, 0, sizeof(preimage));
- memset(&postimage, 0, sizeof(postimage));
- oldlines = xmalloc(size);
- strbuf_init(&newlines, size);
-
- old = oldlines;
- while (size > 0) {
- char first;
- int len = linelen(patch, size);
- int plen;
- int added_blank_line = 0;
- int is_blank_context = 0;
- size_t start;
-
- if (!len)
- break;
-
- /*
- * "plen" is how much of the line we should use for
- * the actual patch data. Normally we just remove the
- * first character on the line, but if the line is
- * followed by "\ No newline", then we also remove the
- * last one (which is the newline, of course).
- */
- plen = len - 1;
- if (len < size && patch[len] == '\\')
- plen--;
- first = *patch;
- if (apply_in_reverse) {
- if (first == '-')
- first = '+';
- else if (first == '+')
- first = '-';
- }
-
- switch (first) {
- case '\n':
- /* Newer GNU diff, empty context line */
- if (plen < 0)
- /* ... followed by '\No newline'; nothing */
- break;
- *old++ = '\n';
- strbuf_addch(&newlines, '\n');
- add_line_info(&preimage, "\n", 1, LINE_COMMON);
- add_line_info(&postimage, "\n", 1, LINE_COMMON);
- is_blank_context = 1;
- break;
- case ' ':
- if (plen && (ws_rule & WS_BLANK_AT_EOF) &&
- ws_blank_line(patch + 1, plen, ws_rule))
- is_blank_context = 1;
- case '-':
- memcpy(old, patch + 1, plen);
- add_line_info(&preimage, old, plen,
- (first == ' ' ? LINE_COMMON : 0));
- old += plen;
- if (first == '-')
- break;
- /* Fall-through for ' ' */
- case '+':
- /* --no-add does not add new lines */
- if (first == '+' && no_add)
- break;
-
- start = newlines.len;
- if (first != '+' ||
- !whitespace_error ||
- ws_error_action != correct_ws_error) {
- strbuf_add(&newlines, patch + 1, plen);
- }
- else {
- ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
- }
- add_line_info(&postimage, newlines.buf + start, newlines.len - start,
- (first == '+' ? 0 : LINE_COMMON));
- if (first == '+' &&
- (ws_rule & WS_BLANK_AT_EOF) &&
- ws_blank_line(patch + 1, plen, ws_rule))
- added_blank_line = 1;
- break;
- case '@': case '\\':
- /* Ignore it, we already handled it */
- break;
- default:
- if (apply_verbosely)
- error(_("invalid start of line: '%c'"), first);
- return -1;
- }
- 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' &&
- newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') {
- old--;
- strbuf_setlen(&newlines, newlines.len - 1);
- }
-
- leading = frag->leading;
- trailing = frag->trailing;
-
- /*
- * A hunk to change lines at the beginning would begin with
- * @@ -1,L +N,M @@
- * but we need to be careful. -U0 that inserts before the second
- * line also has this pattern.
- *
- * And a hunk to add to an empty file would begin with
- * @@ -0,0 +N,M @@
- *
- * In other words, a hunk that is (frag->oldpos <= 1) with or
- * without leading context must match at the beginning.
- */
- match_beginning = (!frag->oldpos ||
- (frag->oldpos == 1 && !unidiff_zero));
-
- /*
- * A hunk without trailing lines must match at the end.
- * However, we simply cannot tell if a hunk must match end
- * from the lack of trailing lines if the patch was generated
- * with unidiff without any context.
- */
- match_end = !unidiff_zero && !trailing;
-
- pos = frag->newpos ? (frag->newpos - 1) : 0;
- preimage.buf = oldlines;
- preimage.len = old - oldlines;
- postimage.buf = newlines.buf;
- postimage.len = newlines.len;
- preimage.line = preimage.line_allocated;
- postimage.line = postimage.line_allocated;
-
- for (;;) {
-
- applied_pos = find_pos(img, &preimage, &postimage, pos,
- ws_rule, match_beginning, match_end);
-
- if (applied_pos >= 0)
- break;
-
- /* Am I at my context limits? */
- if ((leading <= p_context) && (trailing <= p_context))
- break;
- if (match_beginning || match_end) {
- match_beginning = match_end = 0;
- continue;
- }
-
- /*
- * Reduce the number of context lines; reduce both
- * leading and trailing if they are equal otherwise
- * just reduce the larger context.
- */
- if (leading >= trailing) {
- remove_first_line(&preimage);
- remove_first_line(&postimage);
- pos--;
- leading--;
- }
- if (trailing > leading) {
- remove_last_line(&preimage);
- remove_last_line(&postimage);
- trailing--;
- }
- }
-
- if (applied_pos >= 0) {
- if (new_blank_lines_at_end &&
- 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,
- found_new_blank_lines_at_end);
- if (ws_error_action == correct_ws_error) {
- while (new_blank_lines_at_end--)
- remove_last_line(&postimage);
- }
- /*
- * We would want to prevent write_out_results()
- * from taking place in apply_patch() that follows
- * the callchain led us here, which is:
- * apply_patch->check_patch_list->check_patch->
- * apply_data->apply_fragments->apply_one_fragment
- */
- if (ws_error_action == die_on_ws_error)
- apply = 0;
- }
-
- if (apply_verbosely && applied_pos != pos) {
- int offset = applied_pos - pos;
- if (apply_in_reverse)
- offset = 0 - 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);
- }
-
- /*
- * Warn if it was necessary to reduce the number
- * of context lines.
- */
- if ((leading != frag->leading) ||
- (trailing != frag->trailing))
- 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"),
- (int)(old - oldlines), oldlines);
- }
-
- free(oldlines);
- strbuf_release(&newlines);
- free(preimage.line_allocated);
- free(postimage.line_allocated);
-
- return (applied_pos < 0);
-}
-
-static int apply_binary_fragment(struct image *img, struct patch *patch)
-{
- struct fragment *fragment = patch->fragments;
- unsigned long len;
- void *dst;
-
- if (!fragment)
- return error(_("missing binary patch data for '%s'"),
- patch->new_name ?
- patch->new_name :
- patch->old_name);
-
- /* Binary patch is irreversible without the optional second hunk */
- if (apply_in_reverse) {
- if (!fragment->next)
- return error("cannot reverse-apply a binary patch "
- "without the reverse hunk to '%s'",
- patch->new_name
- ? patch->new_name : patch->old_name);
- fragment = fragment->next;
- }
- switch (fragment->binary_patch_method) {
- case BINARY_DELTA_DEFLATED:
- dst = patch_delta(img->buf, img->len, fragment->patch,
- fragment->size, &len);
- if (!dst)
- return -1;
- clear_image(img);
- img->buf = dst;
- img->len = len;
- return 0;
- 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';
- 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;
- unsigned char sha1[20];
-
- /*
- * For safety, we require patch index line to contain
- * full 40-byte textual SHA1 for old and new, at least for now.
- */
- if (strlen(patch->old_sha1_prefix) != 40 ||
- strlen(patch->new_sha1_prefix) != 40 ||
- get_sha1_hex(patch->old_sha1_prefix, sha1) ||
- get_sha1_hex(patch->new_sha1_prefix, sha1))
- return error("cannot apply binary patch to '%s' "
- "without full index line", name);
-
- if (patch->old_name) {
- /*
- * See if the old one matches what the patch
- * applies to.
- */
- hash_sha1_file(img->buf, img->len, blob_type, sha1);
- if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
- return error("the patch applies to '%s' (%s), "
- "which does not match the "
- "current contents.",
- name, sha1_to_hex(sha1));
- }
- else {
- /* Otherwise, the old one must be empty. */
- if (img->len)
- return error("the patch applies to an empty "
- "'%s' but it is not empty", name);
- }
-
- get_sha1_hex(patch->new_sha1_prefix, sha1);
- if (is_null_sha1(sha1)) {
- clear_image(img);
- return 0; /* deletion patch */
- }
-
- if (has_sha1_file(sha1)) {
- /* We already have the postimage */
- enum object_type type;
- unsigned long size;
- char *result;
-
- result = read_sha1_file(sha1, &type, &size);
- if (!result)
- return error("the necessary postimage %s for "
- "'%s' cannot be read",
- patch->new_sha1_prefix, name);
- clear_image(img);
- img->buf = result;
- img->len = size;
- } else {
- /*
- * We have verified buf matches the preimage;
- * apply the patch data to it, which is stored
- * in the patch->fragments->{patch,size}.
- */
- if (apply_binary_fragment(img, patch))
- 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)"),
- name, patch->new_sha1_prefix, sha1_to_hex(sha1));
- }
-
- return 0;
-}
-
-static int apply_fragments(struct image *img, struct patch *patch)
-{
- struct fragment *frag = patch->fragments;
- const char *name = patch->old_name ? patch->old_name : patch->new_name;
- unsigned ws_rule = patch->ws_rule;
- unsigned inaccurate_eof = patch->inaccurate_eof;
- int nth = 0;
-
- if (patch->is_binary)
- return apply_binary(img, patch);
-
- while (frag) {
- nth++;
- if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) {
- error(_("patch failed: %s:%ld"), name, frag->oldpos);
- if (!apply_with_reject)
- return -1;
- frag->rejected = 1;
- }
- frag = frag->next;
- }
- return 0;
-}
-
-static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode)
-{
- if (S_ISGITLINK(mode)) {
- strbuf_grow(buf, 100);
- 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(sha1, &type, &sz);
- if (!result)
- return -1;
- /* XXX read_sha1_file NUL-terminates */
- strbuf_attach(buf, result, sz, sz + 1);
- }
- 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;
-
- if (name == NULL)
- return NULL;
-
- item = string_list_lookup(&fn_table, name);
- if (item != NULL)
- return (struct patch *)item->util;
-
- return NULL;
-}
-
-/*
- * 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, 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_WAS_DELETED ((struct patch *) -1)
-
-static int to_be_deleted(struct patch *patch)
-{
- return patch == PATH_TO_BE_DELETED;
-}
-
-static int was_deleted(struct patch *patch)
-{
- return patch == PATH_WAS_DELETED;
-}
-
-static void add_to_fn_table(struct patch *patch)
-{
- struct string_list_item *item;
-
- /*
- * Always add new_name unless patch is a deletion
- * This should cover the cases for normal diffs,
- * file creations and copies
- */
- if (patch->new_name != NULL) {
- item = string_list_insert(&fn_table, patch->new_name);
- item->util = patch;
- }
-
- /*
- * store a failure on rename/deletion cases because
- * later chunks shouldn't patch old names
- */
- if ((patch->new_name == NULL) || (patch->is_rename)) {
- item = string_list_insert(&fn_table, patch->old_name);
- item->util = PATH_WAS_DELETED;
- }
-}
-
-static void prepare_fn_table(struct patch *patch)
-{
- /*
- * store information about incoming file deletion
- */
- while (patch) {
- if ((patch->new_name == NULL) || (patch->is_rename)) {
- struct string_list_item *item;
- item = string_list_insert(&fn_table, patch->old_name);
- item->util = PATH_TO_BE_DELETED;
- }
- patch = patch->next;
- }
-}
-
-static int checkout_target(struct cache_entry *ce, struct stat *st)
-{
- struct checkout costate;
-
- memset(&costate, 0, sizeof(costate));
- costate.base_dir = "";
- costate.refresh_cache = 1;
- 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) {
- 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 (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;
- size_t len;
- char *img;
- struct patch *previous;
- int status;
-
- 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);
- return 0;
-}
-
-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;
-
- 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(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 try_threeway(struct image *image, struct patch *patch,
- struct stat *st, const struct cache_entry *ce)
-{
- 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;
- }
-
- 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);
- }
- return 0;
-}
-
-static int apply_data(struct patch *patch, struct stat *st, const struct cache_entry *ce)
-{
- 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;
- }
- 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 *previous = NULL;
- int stat_ret = 0, status;
- unsigned st_mode = 0;
-
- if (!old_name)
- return 0;
-
- assert(patch->is_new <= 0);
- previous = previous_patch(patch, &status);
-
- 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));
- }
-
- 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);
- }
- *ce = active_cache[pos];
- if (stat_ret < 0) {
- if (checkout_target(*ce, st))
- return -1;
- }
- if (!cached && verify_index_match(*ce, st))
- 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));
- }
-
- if (!cached && !previous)
- st_mode = ce_mode_from_stat(*ce, st->st_mode);
-
- if (patch->is_new < 0)
- patch->is_new = 0;
- if (!patch->old_mode)
- patch->old_mode = st_mode;
- if ((st_mode ^ patch->old_mode) & S_IFMT)
- return error(_("%s: wrong type"), old_name);
- if (st_mode != patch->old_mode)
- 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;
- return 0;
-
- 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;
-}
-
-/*
- * 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;
- const char *old_name = patch->old_name;
- const char *new_name = patch->new_name;
- const char *name = old_name ? old_name : new_name;
- struct cache_entry *ce = NULL;
- struct patch *tpatch;
- int ok_if_exists;
- int status;
-
- patch->rejected = 1; /* we will drop this after we succeed */
-
- status = check_preimage(patch, &ce, &st);
- if (status)
- 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)))
- ok_if_exists = 1;
- else
- ok_if_exists = 0;
-
- if (new_name &&
- ((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;
- else
- patch->new_mode = patch->old_mode;
- }
- }
-
- if (new_name && old_name) {
- 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) {
- 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 (apply_data(patch, &st, ce) < 0)
- return error(_("%s: patch does not apply"), name);
- patch->rejected = 0;
- return 0;
-}
-
-static int check_patch_list(struct patch *patch)
-{
- int err = 0;
-
- prepare_fn_table(patch);
- while (patch) {
- if (apply_verbosely)
- say_patch_name(stderr,
- _("Checking patch %s..."), patch);
- err |= check_patch(patch);
- patch = patch->next;
- }
- return err;
-}
-
-/* This function tries to read the sha1 from the current index */
-static int get_current_sha1(const char *path, unsigned char *sha1)
-{
- int pos;
-
- if (read_cache() < 0)
- return -1;
- pos = cache_name_pos(path, strlen(path));
- if (pos < 0)
- return -1;
- hashcpy(sha1, active_cache[pos]->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;
-
- /* 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) {
- unsigned char sha1[20];
- struct cache_entry *ce;
- const char *name;
-
- name = patch->old_name ? patch->old_name : patch->new_name;
- if (0 < patch->is_new)
- continue;
-
- 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 submoule %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);
- 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))
- die ("Could not write temporary index to %s", filename);
-
- discard_index(&result);
-}
-
-static void stat_patch_list(struct patch *patch)
-{
- int files, adds, dels;
-
- for (files = adds = dels = 0 ; patch ; patch = patch->next) {
- files++;
- adds += patch->lines_added;
- dels += patch->lines_deleted;
- show_stats(patch);
- }
-
- print_stat_summary(stdout, files, adds, dels);
-}
-
-static void numstat_patch_list(struct patch *patch)
-{
- for ( ; patch; patch = patch->next) {
- const char *name;
- name = patch->new_name ? patch->new_name : patch->old_name;
- if (patch->is_binary)
- printf("-\t-\t");
- else
- printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
- write_name_quoted(name, stdout, line_termination);
- }
-}
-
-static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
-{
- if (mode)
- printf(" %s mode %06o %s\n", newdelete, mode, name);
- else
- printf(" %s %s\n", newdelete, name);
-}
-
-static void show_mode_change(struct patch *p, int show_name)
-{
- if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
- if (show_name)
- printf(" mode change %06o => %06o %s\n",
- p->old_mode, p->new_mode, p->new_name);
- else
- printf(" mode change %06o => %06o\n",
- p->old_mode, p->new_mode);
- }
-}
-
-static void show_rename_copy(struct patch *p)
-{
- const char *renamecopy = p->is_rename ? "rename" : "copy";
- const char *old, *new;
-
- /* Find common prefix */
- old = p->old_name;
- new = p->new_name;
- while (1) {
- const char *slash_old, *slash_new;
- slash_old = strchr(old, '/');
- slash_new = strchr(new, '/');
- if (!slash_old ||
- !slash_new ||
- slash_old - old != slash_new - new ||
- memcmp(old, new, slash_new - new))
- break;
- old = slash_old + 1;
- new = slash_new + 1;
- }
- /* p->old_name thru old is the common prefix, and old and new
- * through the end of names are renames
- */
- if (old != p->old_name)
- printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
- (int)(old - p->old_name), p->old_name,
- old, new, p->score);
- else
- printf(" %s %s => %s (%d%%)\n", renamecopy,
- p->old_name, p->new_name, p->score);
- show_mode_change(p, 0);
-}
-
-static void summary_patch_list(struct patch *patch)
-{
- struct patch *p;
-
- for (p = patch; p; p = p->next) {
- if (p->is_new)
- show_file_mode_name("create", p->new_mode, p->new_name);
- else if (p->is_delete)
- show_file_mode_name("delete", p->old_mode, p->old_name);
- else {
- if (p->is_rename || p->is_copy)
- show_rename_copy(p);
- else {
- if (p->score) {
- printf(" rewrite %s (%d%%)\n",
- p->new_name, p->score);
- show_mode_change(p, 0);
- }
- else
- show_mode_change(p, 1);
- }
- }
- }
-}
-
-static void patch_stats(struct patch *patch)
-{
- int lines = patch->lines_added + patch->lines_deleted;
-
- if (lines > max_change)
- max_change = lines;
- if (patch->old_name) {
- int len = quote_c_style(patch->old_name, NULL, NULL, 0);
- if (!len)
- len = strlen(patch->old_name);
- if (len > max_len)
- max_len = len;
- }
- if (patch->new_name) {
- int len = quote_c_style(patch->new_name, NULL, NULL, 0);
- if (!len)
- len = strlen(patch->new_name);
- if (len > max_len)
- max_len = len;
- }
-}
-
-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);
- }
- if (!cached) {
- if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
- remove_path(patch->old_name);
- }
- }
-}
-
-static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
-{
- struct stat st;
- struct cache_entry *ce;
- int namelen = strlen(path);
- unsigned ce_size = cache_entry_size(namelen);
-
- if (!update_index)
- return;
-
- ce = xcalloc(1, ce_size);
- memcpy(ce->name, path, namelen);
- ce->ce_mode = create_ce_mode(mode);
- ce->ce_flags = create_ce_flags(0);
- ce->ce_namelen = namelen;
- if (S_ISGITLINK(mode)) {
- const char *s = buf;
-
- if (get_sha1_hex(s + strlen("Subproject commit "), 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'"),
- 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);
- }
- if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
- 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)
-{
- int fd;
- struct strbuf nbuf = STRBUF_INIT;
-
- if (S_ISGITLINK(mode)) {
- struct stat st;
- if (!lstat(path, &st) && S_ISDIR(st.st_mode))
- return 0;
- return mkdir(path, 0777);
- }
-
- if (has_symlinks && S_ISLNK(mode))
- /* Although buf:size is counted string, it also is NUL
- * terminated.
- */
- return symlink(buf, path);
-
- fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
- if (fd < 0)
- return -1;
-
- if (convert_to_working_tree(path, buf, size, &nbuf)) {
- size = nbuf.len;
- buf = nbuf.buf;
- }
- write_or_die(fd, buf, size);
- strbuf_release(&nbuf);
-
- if (close(fd) < 0)
- die_errno(_("closing file '%s'"), path);
- return 0;
-}
-
-/*
- * We optimistically assume that the directories exist,
- * which is true 99% of the time anyway. If they don't,
- * we create them and try again.
- */
-static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size)
-{
- if (cached)
- return;
- if (!try_create_file(path, mode, buf, size))
- return;
-
- if (errno == ENOENT) {
- if (safe_create_leading_directories(path))
- return;
- if (!try_create_file(path, mode, buf, size))
- return;
- }
-
- if (errno == EEXIST || errno == EACCES) {
- /* We may be trying to create a file where a directory
- * used to be.
- */
- struct stat st;
- if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path)))
- errno = EEXIST;
- }
-
- if (errno == EEXIST) {
- unsigned int nr = getpid();
-
- for (;;) {
- char newpath[PATH_MAX];
- mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr);
- if (!try_create_file(newpath, mode, buf, size)) {
- if (!rename(newpath, path))
- return;
- unlink_or_warn(newpath);
- break;
- }
- if (errno != EEXIST)
- break;
- ++nr;
- }
- }
- 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)
-{
- char *path = patch->new_name;
- unsigned mode = patch->new_mode;
- unsigned long size = patch->resultsize;
- char *buf = patch->result;
-
- if (!mode)
- mode = S_IFREG | 0644;
- create_one_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 */
-static void write_out_one_result(struct patch *patch, int phase)
-{
- if (patch->is_delete > 0) {
- if (phase == 0)
- remove_file(patch, 1);
- return;
- }
- if (patch->is_new > 0 || patch->is_copy) {
- if (phase == 1)
- create_file(patch);
- return;
- }
- /*
- * Rename or modification boils down to the same
- * thing: remove the old, write the new
- */
- if (phase == 0)
- remove_file(patch, patch->is_rename);
- if (phase == 1)
- create_file(patch);
-}
-
-static int write_out_one_reject(struct patch *patch)
-{
- FILE *rej;
- 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)
- continue;
- cnt++;
- }
-
- if (!cnt) {
- if (apply_verbosely)
- say_patch_name(stderr,
- _("Applied patch %s cleanly."), patch);
- return 0;
- }
-
- /* This should not happen, because a removal patch that leaves
- * contents are marked "rejected" at the patch level.
- */
- if (!patch->new_name)
- die(_("internal error"));
-
- /* Say this even without --verbose */
- 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"),
- cnt - 1, patch->new_name);
- }
- memcpy(namebuf, patch->new_name, cnt);
- memcpy(namebuf + cnt, ".rej", 5);
-
- rej = fopen(namebuf, "w");
- if (!rej)
- 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
- * headers. While at it, maybe please "kompare" that wants
- * the trailing TAB and some garbage at the end of line ;-).
- */
- fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n",
- patch->new_name, patch->new_name);
- for (cnt = 1, frag = patch->fragments;
- frag;
- cnt++, frag = frag->next) {
- if (!frag->rejected) {
- fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt);
- continue;
- }
- fprintf_ln(stderr, _("Rejected hunk #%d."), cnt);
- fprintf(rej, "%.*s", frag->size, frag->patch);
- if (frag->patch[frag->size-1] != '\n')
- fputc('\n', rej);
- }
- fclose(rej);
- return -1;
-}
-
-static int write_out_results(struct patch *list)
-{
- int phase;
- int errs = 0;
- struct patch *l;
- struct string_list cpath = STRING_LIST_INIT_DUP;
-
- for (phase = 0; phase < 2; phase++) {
- l = list;
- while (l) {
- if (l->rejected)
- errs = 1;
- else {
- write_out_one_result(l, phase);
- 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;
- }
- }
-
- if (cpath.nr) {
- struct string_list_item *item;
-
- sort_string_list(&cpath);
- for_each_string_list_item(item, &cpath)
- fprintf(stderr, "U %s\n", item->string);
- string_list_clear(&cpath, 0);
-
- rerere(0);
- }
-
- 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;
-
- /* 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 (!fnmatch(it->string, pathname, 0))
- 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;
-}
-
-
-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_patches(struct patch *p)
+int cmd_apply(int argc, const char **argv, const char *prefix)
{
- if (!prefix || p->is_toplevel_relative)
- return;
- for ( ; p; p = p->next) {
- prefix_one(&p->new_name);
- prefix_one(&p->old_name);
- }
-}
-
-#define INACCURATE_EOF (1<<0)
-#define RECOUNT (1<<1)
-
-static int apply_patch(int fd, const char *filename, int options)
-{
- size_t offset;
- struct strbuf buf = STRBUF_INIT; /* owns the patch text */
- struct patch *list = NULL, **listp = &list;
- int skipped_patch = 0;
-
- patch_input_file = filename;
- read_patch_file(&buf, fd);
- offset = 0;
- while (offset < buf.len) {
- struct patch *patch;
- int nr;
-
- patch = xcalloc(1, sizeof(*patch));
- patch->inaccurate_eof = !!(options & INACCURATE_EOF);
- patch->recount = !!(options & RECOUNT);
- nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
- if (nr < 0)
- 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 {
- 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;
-
- update_index = check_index && apply;
- if (update_index && newfd < 0)
- newfd = hold_locked_index(&lock_file, 1);
-
- if (check_index) {
- if (read_cache() < 0)
- die(_("unable to read index file"));
- }
-
- if ((check || apply) &&
- check_patch_list(list) < 0 &&
- !apply_with_reject)
- 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);
-
- if (diffstat)
- stat_patch_list(list);
-
- if (numstat)
- numstat_patch_list(list);
-
- 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)
-{
- 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);
-}
-
-static int option_parse_exclude(const struct option *opt,
- const char *arg, int unset)
-{
- add_name_limit(arg, 1);
- return 0;
-}
-
-static int option_parse_include(const struct option *opt,
- const char *arg, int unset)
-{
- add_name_limit(arg, 0);
- has_include = 1;
- return 0;
-}
-
-static int option_parse_p(const struct option *opt,
- const char *arg, int unset)
-{
- p_value = atoi(arg);
- p_value_known = 1;
- return 0;
-}
-
-static int option_parse_z(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset)
- line_termination = '\n';
- else
- line_termination = 0;
- return 0;
-}
-
-static int option_parse_space_change(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset)
- ws_ignore_action = ignore_ws_none;
- else
- ws_ignore_action = ignore_ws_change;
- return 0;
-}
-
-static int option_parse_whitespace(const struct option *opt,
- const char *arg, int unset)
-{
- const char **whitespace_option = opt->value;
-
- *whitespace_option = arg;
- parse_whitespace_option(arg);
- return 0;
-}
-
-static int option_parse_directory(const struct option *opt,
- const char *arg, int unset)
-{
- root_len = strlen(arg);
- if (root_len && arg[root_len - 1] != '/') {
- char *new_root;
- root = new_root = xmalloc(root_len + 2);
- strcpy(new_root, arg);
- strcpy(new_root + root_len++, "/");
- } else
- root = arg;
- return 0;
-}
-
-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 force_apply = 0;
+ int options = 0;
+ int ret;
+ struct apply_state state;
- const char *whitespace_option = NULL;
-
- struct option builtin_apply_options[] = {
- { 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, N_("path"),
- N_("apply changes matching the given path"),
- 0, option_parse_include },
- { OPTION_CALLBACK, 'p', NULL, NULL, N_("num"),
- N_("remove <num> leading slashes from traditional diff paths"),
- 0, option_parse_p },
- 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, "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,
- N_("build a temporary index based on embedded index information")),
- { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- N_("paths are separated with NUL character"),
- PARSE_OPT_NOARG, option_parse_z },
- OPT_INTEGER('C', NULL, &p_context,
- 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,
- N_("ignore changes in whitespace when finding context"),
- PARSE_OPT_NOARG, option_parse_space_change },
- { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
- N_("ignore changes in whitespace when finding context"),
- PARSE_OPT_NOARG, option_parse_space_change },
- 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,
- N_("tolerate incorrectly detected missing new-line at the end of file"),
- INACCURATE_EOF),
- OPT_BIT(0, "recount", &options,
- N_("do not trust the line counts in the hunk headers"),
- RECOUNT),
- { 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);
- if (apply_default_whitespace)
- parse_whitespace_option(apply_default_whitespace);
- if (apply_default_ignorewhitespace)
- parse_ignorewhitespace_option(apply_default_ignorewhitespace);
-
- argc = parse_options(argc, argv, prefix, builtin_apply_options,
- apply_usage, 0);
+ if (init_apply_state(&state, prefix, &lock_file))
+ exit(128);
- 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"));
- if (cached) {
- if (is_not_gitdir)
- die(_("--cached outside a repository"));
- check_index = 1;
- }
- for (i = 0; i < argc; i++) {
- const char *arg = argv[i];
- int fd;
+ argc = apply_parse_options(argc, argv,
+ &state, &force_apply, &options,
+ apply_usage);
- if (!strcmp(arg, "-")) {
- errs |= apply_patch(0, "<stdin>", options);
- read_stdin = 0;
- continue;
- } else if (0 < prefix_length)
- arg = prefix_filename(prefix, prefix_length, arg);
+ if (check_apply_state(&state, force_apply))
+ exit(128);
- fd = open(arg, O_RDONLY);
- if (fd < 0)
- die_errno(_("can't open patch '%s'"), arg);
- read_stdin = 0;
- set_default_whitespace_mode(whitespace_option);
- errs |= apply_patch(fd, arg, options);
- close(fd);
- }
- set_default_whitespace_mode(whitespace_option);
- if (read_stdin)
- errs |= apply_patch(0, "<stdin>", options);
- if (whitespace_error) {
- if (squelch_whitespace_errors &&
- squelch_whitespace_errors < whitespace_error) {
- int squelched =
- whitespace_error - squelch_whitespace_errors;
- warning(Q_("squelched %d whitespace error",
- "squelched %d whitespace errors",
- squelched),
- squelched);
- }
- if (ws_error_action == die_on_ws_error)
- 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(Q_("%d line adds whitespace errors.",
- "%d lines add whitespace errors.",
- whitespace_error),
- whitespace_error);
- }
+ ret = apply_all_patches(&state, argc, argv, options);
- if (update_index) {
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(&lock_file))
- die(_("Unable to write new index file"));
- }
+ clear_apply_state(&state);
- return !!errs;
+ return ret;
}
diff --git a/builtin/archive.c b/builtin/archive.c
index a1e3b940c2..f863465a0f 100644
--- a/builtin/archive.c
+++ b/builtin/archive.c
@@ -47,10 +47,10 @@ static int run_remote_archiver(int argc, const char **argv,
if (name_hint) {
const char *format = archive_format_from_filename(name_hint);
if (format)
- packet_write(fd[1], "argument --format=%s\n", format);
+ packet_write_fmt(fd[1], "argument --format=%s\n", format);
}
for (i = 1; i < argc; i++)
- packet_write(fd[1], "argument %s\n", argv[i]);
+ packet_write_fmt(fd[1], "argument %s\n", argv[i]);
packet_flush(fd[1]);
buf = packet_read_line(fd[0], NULL);
@@ -85,8 +85,8 @@ 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, N_("file"),
- N_("write the archive to this file")),
+ OPT_FILENAME('o', "output", &output,
+ 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"),
@@ -105,5 +105,5 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
- return write_archive(argc, argv, prefix, 1, output, 0);
+ return write_archive(argc, argv, prefix, output, 0);
}
diff --git a/builtin/blame.c b/builtin/blame.c
index e5b5d71bad..cffc626540 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -1,10 +1,12 @@
/*
* Blame
*
- * Copyright (c) 2006, Junio C Hamano
+ * Copyright (c) 2006, 2014 by its authors
+ * See COPYING for licensing conditions
*/
#include "cache.h"
+#include "refs.h"
#include "builtin.h"
#include "blob.h"
#include "commit.h"
@@ -18,18 +20,22 @@
#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"
+#include "dir.h"
+#include "progress.h"
-static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
+static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
static const char *blame_opt_usage[] = {
blame_usage,
"",
- N_("[rev-opts] are documented in git-rev-list(1)"),
+ N_("<rev-opts> are documented in git-rev-list(1)"),
NULL
};
@@ -45,11 +51,12 @@ static int incremental;
static int xdl_opts;
static int abbrev = -1;
static int no_whole_file_rename;
+static int show_progress;
-static enum date_mode blame_date_mode = DATE_ISO8601;
+static struct date_mode blame_date_mode = { DATE_ISO8601 };
static size_t blame_date_width;
-static struct string_list mailmap;
+static struct string_list mailmap = STRING_LIST_INIT_NODUP;
#ifndef DEBUG
#define DEBUG 0
@@ -74,7 +81,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)
@@ -83,15 +90,51 @@ 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];
+ struct object_id blob_oid;
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,
+struct progress_info {
+ struct progress *progress;
+ int blamed_lines;
+};
+
+static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b,
xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
{
xpparam_t xpp = {0};
@@ -99,7 +142,6 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen,
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);
@@ -112,8 +154,8 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen,
*/
int textconv_object(const char *path,
unsigned mode,
- const unsigned char *sha1,
- int sha1_valid,
+ const struct object_id *oid,
+ int oid_valid,
char **buf,
unsigned long *buf_size)
{
@@ -121,7 +163,7 @@ int textconv_object(const char *path,
struct userdiff_driver *textconv;
df = alloc_filespec(path);
- fill_filespec(df, sha1, sha1_valid, mode);
+ fill_filespec(df, oid->hash, oid_valid, mode);
textconv = get_textconv(df);
if (!textconv) {
free_filespec(df);
@@ -146,15 +188,16 @@ 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, 1, &file->ptr, &file_size))
+ textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size))
;
else
- file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
+ file->ptr = read_sha1_file(o->blob_oid.hash, &type,
+ &file_size);
file->size = file_size;
if (!file->ptr)
die("Cannot read blob %s for path %s",
- sha1_to_hex(o->blob_sha1),
+ oid_to_hex(&o->blob_oid),
o->path);
o->file = *file;
}
@@ -176,10 +219,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");
}
}
@@ -193,8 +248,12 @@ 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 *next;
@@ -210,15 +269,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.
*/
@@ -231,11 +281,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;
@@ -268,7 +419,6 @@ static void coalesce(struct scoreboard *sb)
for (ent = sb->ent; ent && (next = ent->next); ent = next) {
if (ent->suspect == next->suspect &&
- ent->guilty == next->guilty &&
ent->s_lno + ent->num_lines == next->s_lno) {
ent->num_lines += next->num_lines;
ent->next = next->next;
@@ -284,6 +434,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
@@ -292,26 +466,34 @@ static void coalesce(struct scoreboard *sb)
static struct origin *make_origin(struct commit *commit, const char *path)
{
struct origin *o;
- o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+ FLEX_ALLOC_STR(o, path, path);
o->commit = commit;
o->refcnt = 1;
- strcpy(o->path, path);
+ o->next = commit->util;
+ commit->util = o;
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);
}
@@ -327,17 +509,17 @@ static struct origin *get_origin(struct scoreboard *sb,
*/
static int fill_blob_sha1_and_mode(struct origin *origin)
{
- if (!is_null_sha1(origin->blob_sha1))
+ if (!is_null_oid(&origin->blob_oid))
return 0;
- if (get_tree_entry(origin->commit->object.sha1,
+ if (get_tree_entry(origin->commit->object.oid.hash,
origin->path,
- origin->blob_sha1, &origin->mode))
+ origin->blob_oid.hash, &origin->mode))
goto error_out;
- if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
+ if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB)
goto error_out;
return 0;
error_out:
- hashclr(origin->blob_sha1);
+ oidclr(&origin->blob_oid);
origin->mode = S_IFINVALID;
return -1;
}
@@ -350,41 +532,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
@@ -402,18 +562,18 @@ static struct origin *find_origin(struct scoreboard *sb,
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);
+ if (is_null_oid(&origin->commit->object.oid))
+ do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
else
- diff_tree_sha1(parent->tree->object.sha1,
- origin->commit->tree->object.sha1,
+ diff_tree_sha1(parent->tree->object.oid.hash,
+ origin->commit->tree->object.oid.hash,
"", &diff_opts);
diffcore_std(&diff_opts);
if (!diff_queued_diff.nr) {
/* The path is the same as parent */
porigin = get_origin(sb, parent, origin->path);
- hashcpy(porigin->blob_sha1, origin->blob_sha1);
+ oidcpy(&porigin->blob_oid, &origin->blob_oid);
porigin->mode = origin->mode;
} else {
/*
@@ -439,7 +599,7 @@ static struct origin *find_origin(struct scoreboard *sb,
p->status);
case 'M':
porigin = get_origin(sb, parent, origin->path);
- hashcpy(porigin->blob_sha1, p->one->sha1);
+ oidcpy(&porigin->blob_oid, &p->one->oid);
porigin->mode = p->one->mode;
break;
case 'A':
@@ -449,20 +609,7 @@ static struct origin *find_origin(struct scoreboard *sb,
}
}
diff_flush(&diff_opts);
- free_pathspec(&diff_opts.pathspec);
- 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;
- }
+ clear_pathspec(&diff_opts.pathspec);
return porigin;
}
@@ -485,11 +632,11 @@ static struct origin *find_rename(struct scoreboard *sb,
diff_opts.single_follow = origin->path;
diff_setup_done(&diff_opts);
- if (is_null_sha1(origin->commit->object.sha1))
- do_diff_cache(parent->tree->object.sha1, &diff_opts);
+ if (is_null_oid(&origin->commit->object.oid))
+ do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
else
- diff_tree_sha1(parent->tree->object.sha1,
- origin->commit->tree->object.sha1,
+ diff_tree_sha1(parent->tree->object.oid.hash,
+ origin->commit->tree->object.oid.hash,
"", &diff_opts);
diffcore_std(&diff_opts);
@@ -498,57 +645,42 @@ static struct origin *find_rename(struct scoreboard *sb,
if ((p->status == 'R' || p->status == 'C') &&
!strcmp(p->two->path, origin->path)) {
porigin = get_origin(sb, parent, p->one->path);
- hashcpy(porigin->blob_sha1, p->one->sha1);
+ oidcpy(&porigin->blob_oid, &p->one->oid);
porigin->mode = p->one->mode;
break;
}
}
diff_flush(&diff_opts);
- free_pathspec(&diff_opts.pathspec);
+ clear_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 */
-
- if (prev) {
- e->next = prev->next;
- prev->next = e;
- }
- else {
- e->next = sb->ent;
- sb->ent = 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 *n;
-
- n = dst->next;
origin_incref(src->suspect);
origin_decref(dst->suspect);
memcpy(dst, src, sizeof(*src));
- dst->next = n;
- dst->score = 0;
+ dst->next = **queue;
+ **queue = dst;
+ *queue = &dst->next;
}
static const char *nth_line(struct scoreboard *sb, long lno)
@@ -620,10 +752,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)
{
@@ -631,61 +764,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);
}
}
@@ -702,74 +813,146 @@ 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 || 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 || 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;
};
+/* 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, start_b, d->target, d->parent);
- d->plno = start_a + count_a;
- d->tlno = start_b + count_b;
+ 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;
}
@@ -778,29 +961,35 @@ static int blame_chunk_cb(long start_a, long count_a,
* 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;
+ struct blame_entry *newdest = NULL;
- 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 */
+ 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++;
- diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
- /* 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);
+ if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d))
+ die("unable to generate diff (%s -> %s)",
+ oid_to_hex(&parent->commit->object.oid),
+ oid_to_hex(&target->commit->object.oid));
+ /* 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;
}
/*
@@ -940,48 +1129,87 @@ static void find_copy_in_blob(struct scoreboard *sb,
* file_p partially may match that image.
*/
memset(split, 0, sizeof(struct blame_entry [3]));
- diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
+ if (diff_hunks(file_p, &file_o, handle_split_cb, &d))
+ die("unable to generate diff (%s)",
+ oid_to_hex(&parent->commit->object.oid));
/* 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 || 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 {
@@ -993,62 +1221,46 @@ 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 &&
- 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 &&
- 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;
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);
@@ -1068,19 +1280,19 @@ static int find_copy_in_parent(struct scoreboard *sb,
&& (!porigin || strcmp(target->path, porigin->path))))
DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
- if (is_null_sha1(target->commit->object.sha1))
- do_diff_cache(parent->tree->object.sha1, &diff_opts);
+ if (is_null_oid(&target->commit->object.oid))
+ do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
else
- diff_tree_sha1(parent->tree->object.sha1,
- target->commit->tree->object.sha1,
+ diff_tree_sha1(parent->tree->object.oid.hash,
+ target->commit->tree->object.oid.hash,
"", &diff_opts);
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];
@@ -1097,7 +1309,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
continue;
norigin = get_origin(sb, parent, p->one->path);
- hashcpy(norigin->blob_sha1, p->one->sha1);
+ oidcpy(&norigin->blob_oid, &p->one->oid);
norigin->mode = p->one->mode;
fill_origin_blob(&sb->revs->diffopt, norigin, &file_p);
if (!file_p.ptr)
@@ -1117,27 +1329,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);
- free_pathspec(&diff_opts.pathspec);
- return retval;
+ clear_pathspec(&diff_opts.pathspec);
}
/*
@@ -1147,20 +1353,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 (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);
}
/*
@@ -1170,18 +1377,43 @@ static void pass_whole_blame(struct scoreboard *sb,
*/
static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
{
- if (!reverse)
+ if (!reverse) {
+ if (revs->first_parent_only &&
+ commit->parents &&
+ commit->parents->next) {
+ free_commit_list(commit->parents->next);
+ commit->parents->next = NULL;
+ }
return commit->parents;
+ }
return lookup_decoration(&revs->children, &commit->object);
}
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
@@ -1194,6 +1426,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)
@@ -1225,15 +1459,14 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
porigin = find(sb, p, origin);
if (!porigin)
continue;
- if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
+ if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) {
pass_whole_blame(sb, origin, porigin);
origin_decref(porigin);
goto finish;
}
for (j = same = 0; j < i; j++)
if (sg_origin[j] &&
- !hashcmp(sg_origin[j]->blob_sha1,
- porigin->blob_sha1)) {
+ !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) {
same = 1;
break;
}
@@ -1255,38 +1488,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]);
@@ -1405,7 +1671,7 @@ static void get_commit_info(struct commit *commit,
{
int len;
const char *subject, *encoding;
- char *message;
+ const char *message;
commit_info_init(ret);
@@ -1416,7 +1682,7 @@ static void get_commit_info(struct commit *commit,
&ret->author_time, &ret->author_tz);
if (!detailed) {
- logmsg_free(message, commit);
+ unuse_commit_buffer(commit, message);
return;
}
@@ -1428,19 +1694,29 @@ static void get_commit_info(struct commit *commit,
if (len)
strbuf_add(&ret->summary, subject, len);
else
- strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1));
+ strbuf_addf(&ret->summary, "(%s)", oid_to_hex(&commit->object.oid));
- logmsg_free(message, commit);
+ unuse_commit_buffer(commit, message);
}
/*
+ * Write out any suspect information which depends on the path. This must be
+ * handled separately from emit_one_suspect_detail(), because a given commit
+ * may have changes in multiple paths. So this needs to appear each time
+ * we mention a new group.
+ *
* To allow LF and other nonportable characters in pathnames,
* they are c-style quoted as needed.
*/
-static void write_filename_info(const char *path)
+static void write_filename_info(struct origin *suspect)
{
+ if (suspect->previous) {
+ struct origin *prev = suspect->previous;
+ printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
+ write_name_quoted(prev->path, stdout, '\n');
+ }
printf("filename ");
- write_name_quoted(path, stdout, '\n');
+ write_name_quoted(suspect->path, stdout, '\n');
}
/*
@@ -1469,11 +1745,6 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
printf("summary %s\n", ci.summary.buf);
if (suspect->commit->object.flags & UNINTERESTING)
printf("boundary\n");
- if (suspect->previous) {
- struct origin *prev = suspect->previous;
- printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
- write_name_quoted(prev->path, stdout, '\n');
- }
commit_info_destroy(&ci);
@@ -1481,53 +1752,60 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
}
/*
- * 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)
+static void found_guilty_entry(struct blame_entry *ent,
+ struct progress_info *pi)
{
- if (ent->guilty)
- return;
- ent->guilty = 1;
if (incremental) {
struct origin *suspect = ent->suspect;
printf("%s %d %d %d\n",
- sha1_to_hex(suspect->commit->object.sha1),
+ oid_to_hex(&suspect->commit->object.oid),
ent->s_lno + 1, ent->lno + 1, ent->num_lines);
emit_one_suspect_detail(suspect, 0);
- write_filename_info(suspect->path);
+ write_filename_info(suspect);
maybe_flush_or_die(stdout, "stdout");
}
+ pi->blamed_lines += ent->num_lines;
+ display_progress(pi->progress, pi->blamed_lines);
}
/*
- * 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);
+ struct progress_info pi = { NULL, 0 };
+
+ if (show_progress)
+ pi.progress = start_progress_delay(_("Blaming lines"),
+ sb->num_lines, 50, 1);
- 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;
parse_commit(commit);
if (reverse ||
(!(commit->object.flags & UNINTERESTING) &&
@@ -1543,35 +1821,57 @@ 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 (ent->suspect == suspect)
- found_guilty_entry(ent);
+ ent = suspect->suspects;
+ if (ent) {
+ suspect->guilty = 1;
+ for (;;) {
+ struct blame_entry *next = ent->next;
+ found_guilty_entry(ent, &pi);
+ if (next) {
+ ent = next;
+ continue;
+ }
+ ent->next = sb->ent;
+ sb->ent = suspect->suspects;
+ suspect->suspects = NULL;
+ break;
+ }
+ }
origin_decref(suspect);
if (DEBUG) /* sanity */
sanity_check_refcnt(sb);
}
+
+ stop_progress(&pi.progress);
}
static const char *format_time(unsigned long time, const char *tz_str,
int show_raw_time)
{
- static char time_buf[128];
+ static struct strbuf time_buf = STRBUF_INIT;
+ strbuf_reset(&time_buf);
if (show_raw_time) {
- snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
+ strbuf_addf(&time_buf, "%lu %s", time, tz_str);
}
else {
const char *time_str;
- int time_len;
+ 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);
+ time_str = show_date(time, tz, &blame_date_mode);
+ 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
@@ -1589,7 +1889,7 @@ static void emit_porcelain_details(struct origin *suspect, int repeat)
{
if (emit_one_suspect_detail(suspect, repeat) ||
(suspect->commit->object.flags & MORE_THAN_ONE_PATH))
- write_filename_info(suspect->path);
+ write_filename_info(suspect);
}
static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
@@ -1599,12 +1899,11 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
int cnt;
const char *cp;
struct origin *suspect = ent->suspect;
- char hex[41];
+ char hex[GIT_SHA1_HEXSZ + 1];
- strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
- printf("%s%c%d %d %d\n",
+ oid_to_hex_r(hex, &suspect->commit->object.oid);
+ printf("%s %d %d %d\n",
hex,
- ent->guilty ? ' ' : '*', /* purely for debugging */
ent->s_lno + 1,
ent->lno + 1,
ent->num_lines);
@@ -1638,16 +1937,16 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
const char *cp;
struct origin *suspect = ent->suspect;
struct commit_info ci;
- char hex[41];
+ char hex[GIT_SHA1_HEXSZ + 1];
int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
get_commit_info(suspect->commit, &ci, 1);
- strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+ oid_to_hex_r(hex, &suspect->commit->object.oid);
cp = nth_line(sb, ent->lno);
for (cnt = 0; cnt < ent->num_lines; cnt++) {
char ch;
- int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev;
+ int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;
if (suspect->commit->object.flags & UNINTERESTING) {
if (blank_boundary)
@@ -1717,17 +2016,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;
+ }
}
}
}
@@ -1741,6 +2039,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.
@@ -1752,39 +2056,20 @@ static int prepare_lines(struct scoreboard *sb)
const char *end = buf + len;
const char *p;
int *lineno;
- int num = 0, incomplete = 0;
+ int num = 0;
- for (p = buf;;) {
- p = memchr(p, '\n', end - p);
- if (p) {
- p++;
- num++;
- continue;
- }
- break;
- }
-
- if (len && end[-1] != '\n')
- incomplete++; /* incomplete line at the end */
+ for (p = buf; p < end; p = get_next_line(p, end))
+ num++;
- sb->lineno = xmalloc(sizeof(*sb->lineno) * (num + incomplete + 1));
+ ALLOC_ARRAY(sb->lineno, num + 1);
lineno = sb->lineno;
- *lineno++ = 0;
- for (p = buf;;) {
- p = memchr(p, '\n', end - p);
- if (p) {
- p++;
- *lineno++ = p - buf;
- continue;
- }
- break;
- }
+ for (p = buf; p < end; p = get_next_line(p, end))
+ *lineno++ = p - buf;
- if (incomplete)
- *lineno++ = len;
+ *lineno = len;
- sb->num_lines = num + incomplete;
+ sb->num_lines = num;
return sb->num_lines;
}
@@ -1812,7 +2097,7 @@ static int read_ancestry(const char *graft_file)
static int update_auto_abbrev(int auto_abbrev, struct origin *suspect)
{
- const char *uniq = find_unique_abbrev(suspect->commit->object.sha1,
+ const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash,
auto_abbrev);
int len = strlen(uniq);
if (auto_abbrev < len)
@@ -1831,11 +2116,10 @@ static void find_alignment(struct scoreboard *sb, int *option)
unsigned largest_score = 0;
struct blame_entry *e;
int compute_auto_abbrev = (abbrev < 0);
- int auto_abbrev = default_abbrev;
+ 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)
@@ -1846,6 +2130,7 @@ static void find_alignment(struct scoreboard *sb, int *option)
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)
@@ -1854,6 +2139,7 @@ static void find_alignment(struct scoreboard *sb, int *option)
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)
@@ -1863,8 +2149,6 @@ static void find_alignment(struct scoreboard *sb, int *option)
longest_dst_lines = num;
if (largest_score < ent_score(sb, e))
largest_score = ent_score(sb, e);
-
- commit_info_destroy(&ci);
}
max_orig_digits = decimal_width(longest_src_lines);
max_digits = decimal_width(longest_dst_lines);
@@ -1889,7 +2173,7 @@ static void sanity_check_refcnt(struct scoreboard *sb)
if (ent->suspect->refcnt <= 0) {
fprintf(stderr, "%s in %s has negative refcnt %d\n",
ent->suspect->path,
- sha1_to_hex(ent->suspect->commit->object.sha1),
+ oid_to_hex(&ent->suspect->commit->object.oid),
ent->suspect->refcnt);
baa = 1;
}
@@ -1902,16 +2186,6 @@ static void sanity_check_refcnt(struct scoreboard *sb)
}
}
-/*
- * Used for the command line parsing; check if the path exists
- * in the working tree.
- */
-static int has_string_in_work_tree(const char *path)
-{
- struct stat st;
- return !lstat(path, &st);
-}
-
static unsigned parse_score(const char *arg)
{
char *end;
@@ -1936,13 +2210,23 @@ static int git_blame_config(const char *var, const char *value, void *cb)
blank_boundary = git_config_bool(var, value);
return 0;
}
+ if (!strcmp(var, "blame.showemail")) {
+ int *output_option = cb;
+ if (git_config_bool(var, value))
+ *output_option |= OUTPUT_SHOW_EMAIL;
+ else
+ *output_option &= ~OUTPUT_SHOW_EMAIL;
+ return 0;
+ }
if (!strcmp(var, "blame.date")) {
if (!value)
return config_error_nonbool(var);
- blame_date_mode = parse_date_format(value);
+ parse_date_format(value, &blame_date_mode);
return 0;
}
+ if (git_diff_heuristic_config(var, value, cb) < 0)
+ return -1;
if (userdiff_config(var, value) < 0)
return -1;
@@ -1952,53 +2236,73 @@ static int git_blame_config(const char *var, const char *value, void *cb)
static void verify_working_tree_path(struct commit *work_tree, const char *path)
{
struct commit_list *parents;
+ int pos;
for (parents = work_tree->parents; parents; parents = parents->next) {
- const unsigned char *commit_sha1 = parents->item->object.sha1;
- unsigned char blob_sha1[20];
+ const struct object_id *commit_oid = &parents->item->object.oid;
+ struct object_id blob_oid;
unsigned mode;
- if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) &&
- sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+ if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) &&
+ sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB)
return;
}
- die("no such path '%s' in HEAD", path);
+
+ pos = cache_name_pos(path, strlen(path));
+ if (pos >= 0)
+ ; /* path is in the index */
+ else if (-1 - pos < active_nr &&
+ !strcmp(active_cache[-1 - pos]->name, path))
+ ; /* path is in the index, unmerged */
+ else
+ die("no such path '%s' in HEAD", path);
}
-static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1)
+static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid)
{
struct commit *parent;
- parent = lookup_commit_reference(sha1);
+ parent = lookup_commit_reference(oid->hash);
if (!parent)
- die("no such commit %s", sha1_to_hex(sha1));
+ die("no such commit %s", oid_to_hex(oid));
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);
+ merge_head = open(git_path_merge_head(), O_RDONLY);
if (merge_head < 0) {
if (errno == ENOENT)
return;
- die("cannot open '%s' for reading", merge_head_file);
+ die("cannot open '%s' for reading", git_path_merge_head());
}
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);
+ struct object_id oid;
+ if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid))
+ die("unknown line in '%s': %s", git_path_merge_head(), line.buf);
+ tail = append_parent(tail, &oid);
}
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.
*/
@@ -2009,7 +2313,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 object_id head_oid;
struct strbuf buf = STRBUF_INIT;
const char *ident;
time_t now;
@@ -2018,17 +2322,17 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
unsigned mode;
struct strbuf msg = STRBUF_INIT;
+ read_cache();
time(&now);
- commit = xcalloc(1, sizeof(*commit));
+ 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", head_sha1, 1, NULL))
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
die("no such ref: HEAD");
- parent_tail = append_parent(parent_tail, head_sha1);
+ parent_tail = append_parent(parent_tail, &head_oid);
append_merge_parents(parent_tail);
verify_working_tree_path(commit, path);
@@ -2038,7 +2342,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
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));
+ oid_to_hex(&parent->item->object.oid));
strbuf_addf(&msg,
"author %s\n"
"committer %s\n\n"
@@ -2046,7 +2350,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
ident, ident, path,
(!contents_from ? path :
(!strcmp(contents_from, "-") ? "standard input" : contents_from)));
- commit->buffer = strbuf_detach(&msg, NULL);
+ set_commit_buffer_from_strbuf(commit, &msg);
if (!contents_from || strcmp("-", contents_from)) {
struct stat st;
@@ -2069,7 +2373,7 @@ 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, 0, &buf_ptr, &buf_len))
+ textconv_object(read_from, mode, &null_oid, 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);
@@ -2091,8 +2395,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
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;
+ pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash);
/*
* Read the current index, replace the path entry with
@@ -2114,52 +2417,86 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
}
size = cache_entry_size(len);
ce = xcalloc(1, size);
- hashcpy(ce->sha1, origin->blob_sha1);
+ oidcpy(&ce->oid, &origin->blob_oid);
memcpy(ce->name, path, len);
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);
- /*
- * We are not going to write this out, so this does not matter
- * 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);
return commit;
}
-static const char *prepare_final(struct scoreboard *sb)
+static struct commit *find_single_final(struct rev_info *revs,
+ const char **name_p)
{
int i;
- const char *final_commit_name = NULL;
- struct rev_info *revs = sb->revs;
+ struct commit *found = NULL;
+ const char *name = NULL;
- /*
- * There must be one and only one positive commit in the
- * revs->pending array.
- */
for (i = 0; i < revs->pending.nr; i++) {
struct object *obj = revs->pending.objects[i].item;
if (obj->flags & UNINTERESTING)
continue;
- while (obj->type == OBJ_TAG)
- obj = deref_tag(obj, NULL, 0);
+ obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
- if (sb->final)
+ if (found)
die("More than one commit to dig from %s and %s?",
- revs->pending.objects[i].name,
- final_commit_name);
- sb->final = (struct commit *) obj;
- final_commit_name = revs->pending.objects[i].name;
+ revs->pending.objects[i].name, name);
+ found = (struct commit *)obj;
+ name = revs->pending.objects[i].name;
}
- return final_commit_name;
+ if (name_p)
+ *name_p = name;
+ return found;
+}
+
+static char *prepare_final(struct scoreboard *sb)
+{
+ const char *name;
+ sb->final = find_single_final(sb->revs, &name);
+ return xstrdup_or_null(name);
+}
+
+static const char *dwim_reverse_initial(struct scoreboard *sb)
+{
+ /*
+ * DWIM "git blame --reverse ONE -- PATH" as
+ * "git blame --reverse ONE..HEAD -- PATH" but only do so
+ * when it makes sense.
+ */
+ struct object *obj;
+ struct commit *head_commit;
+ unsigned char head_sha1[20];
+
+ if (sb->revs->pending.nr != 1)
+ return NULL;
+
+ /* Is that sole rev a committish? */
+ obj = sb->revs->pending.objects[0].item;
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ return NULL;
+
+ /* Do we have HEAD? */
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
+ return NULL;
+ head_commit = lookup_commit_reference_gently(head_sha1, 1);
+ if (!head_commit)
+ return NULL;
+
+ /* Turn "ONE" into "ONE..HEAD" then */
+ obj->flags |= UNINTERESTING;
+ add_pending_object(sb->revs, &head_commit->object, "HEAD");
+
+ sb->final = (struct commit *)obj;
+ return sb->revs->pending.objects[0].name;
}
-static const char *prepare_initial(struct scoreboard *sb)
+static char *prepare_initial(struct scoreboard *sb)
{
int i;
const char *final_commit_name = NULL;
@@ -2173,20 +2510,22 @@ static const char *prepare_initial(struct scoreboard *sb)
struct object *obj = revs->pending.objects[i].item;
if (!(obj->flags & UNINTERESTING))
continue;
- while (obj->type == OBJ_TAG)
- obj = deref_tag(obj, NULL, 0);
+ obj = deref_tag(obj, NULL, 0);
if (obj->type != OBJ_COMMIT)
die("Non commit %s?", revs->pending.objects[i].name);
if (sb->final)
- die("More than one commit to dig down to %s and %s?",
+ die("More than one commit to dig up from, %s and %s?",
revs->pending.objects[i].name,
final_commit_name);
sb->final = (struct commit *) obj;
final_commit_name = revs->pending.objects[i].name;
}
+
if (!final_commit_name)
- die("No commit to dig down to?");
- return final_commit_name;
+ final_commit_name = dwim_reverse_initial(sb);
+ if (!final_commit_name)
+ die("No commit to dig up from?");
+ return xstrdup(final_commit_name);
}
static int blame_copy_callback(const struct option *option, const char *arg, int unset)
@@ -2230,19 +2569,21 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
struct origin *o;
struct blame_entry *ent = NULL;
long dashdash_pos, lno;
- const char *final_commit_name = NULL;
+ char *final_commit_name = NULL;
enum object_type type;
-
- 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[] = {
+ struct commit *final_commit = NULL;
+
+ struct string_list range_list = STRING_LIST_INIT_NODUP;
+ int output_option = 0, opt = 0;
+ int show_stats = 0;
+ const char *revs_file = NULL;
+ const char *contents_from = NULL;
+ const struct option options[] = {
OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")),
+ OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")),
OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE),
OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
@@ -2254,6 +2595,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
+
+ /*
+ * The following two options are parsed by parse_revision_opt()
+ * and are only included here to get included in the "-h"
+ * output:
+ */
+ { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb },
+
OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),
OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
@@ -2270,7 +2619,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
unsigned int range_i;
long anchor;
- git_config(git_blame_config, NULL);
+ git_config(git_blame_config, &output_option);
init_revisions(&revs, NULL);
revs.date_mode = blame_date_mode;
DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
@@ -2278,6 +2627,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
save_commit_buffer = 0;
dashdash_pos = 0;
+ show_progress = -1;
parse_options_start(&ctx, argc, argv, prefix, options,
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
@@ -2299,43 +2649,68 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
}
parse_done:
no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES);
+ xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC;
DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES);
argc = parse_options_end(&ctx);
- if (0 < abbrev)
+ if (incremental || (output_option & OUTPUT_PORCELAIN)) {
+ if (show_progress > 0)
+ die(_("--progress can't be used with --incremental or porcelain formats"));
+ show_progress = 0;
+ } else if (show_progress < 0)
+ show_progress = isatty(2);
+
+ if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ)
/* one more abbrev length is needed for the boundary commit */
abbrev++;
+ else if (!abbrev)
+ abbrev = GIT_SHA1_HEXSZ;
if (revs_file && read_ancestry(revs_file))
die_errno("reading graft file '%s' failed", revs_file);
if (cmd_is_annotate) {
output_option |= OUTPUT_ANNOTATE_COMPAT;
- blame_date_mode = DATE_ISO8601;
+ blame_date_mode.type = DATE_ISO8601;
} else {
blame_date_mode = revs.date_mode;
}
/* The maximum width used to show the dates */
- switch (blame_date_mode) {
+ switch (blame_date_mode.type) {
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;
case DATE_RAW:
blame_date_width = sizeof("1161298804 -0700");
break;
+ case DATE_UNIX:
+ blame_date_width = sizeof("1161298804");
+ break;
case DATE_SHORT:
blame_date_width = sizeof("2006-10-19");
break;
case DATE_RELATIVE:
- /* "normal" is used as the fallback for "relative" */
- case DATE_LOCAL:
+ /* 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_NORMAL:
blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
break;
+ case DATE_STRFTIME:
+ blame_date_width = strlen(show_date(0, 0, &blame_date_mode)) + 1; /* add the null */
+ break;
}
blame_date_width -= 1; /* strip the null */
@@ -2387,14 +2762,14 @@ parse_done:
if (argc < 2)
usage_with_options(blame_opt_usage, options);
path = add_prefix(prefix, argv[argc - 1]);
- if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
+ if (argc == 3 && !file_exists(path)) { /* (2b) */
path = add_prefix(prefix, argv[1]);
argv[1] = argv[2];
}
argv[argc - 1] = "--";
setup_work_tree();
- if (!has_string_in_work_tree(path))
+ if (!file_exists(path))
die_errno("cannot stat path '%s'", path);
}
@@ -2403,12 +2778,18 @@ 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
+ die(_("--contents and --reverse do not blend well."));
+ else {
final_commit_name = prepare_initial(&sb);
+ sb.commits.compare = compare_commits_by_reverse_commit_date;
+ if (revs.first_parent_only)
+ revs.children.name = NULL;
+ }
if (!sb.final) {
/*
@@ -2422,7 +2803,13 @@ parse_done:
add_pending_object(&revs, &(sb.final->object), ":");
}
else if (contents_from)
- die("Cannot use --contents with final commit object name");
+ die(_("cannot use --contents with final commit object name"));
+
+ if (reverse && revs.first_parent_only) {
+ final_commit = find_single_final(sb.revs, NULL);
+ if (!final_commit)
+ die(_("--reverse and --first-parent together require specified latest commit"));
+ }
/*
* If we have bottom, this will mark the ancestors of the
@@ -2430,39 +2817,55 @@ parse_done:
* uninteresting.
*/
if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
+
+ if (reverse && revs.first_parent_only) {
+ struct commit *c = final_commit;
+
+ sb.revs->children.name = "children";
+ while (c->parents &&
+ oidcmp(&c->object.oid, &sb.final->object.oid)) {
+ struct commit_list *l = xcalloc(1, sizeof(*l));
+
+ l->item = c;
+ if (add_decoration(&sb.revs->children,
+ &c->parents->item->object, l))
+ die("BUG: not unique item in first-parent chain");
+ c = c->parents->item;
+ }
- if (is_null_sha1(sb.final->object.sha1)) {
- char *buf;
+ if (oidcmp(&c->object.oid, &sb.final->object.oid))
+ die(_("--reverse --first-parent together require range along first-parent chain"));
+ }
+
+ if (is_null_oid(&sb.final->object.oid)) {
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 {
o = get_origin(&sb, sb.final, path);
if (fill_blob_sha1_and_mode(o))
- die("no such path %s in %s", path, final_commit_name);
+ 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, 1, (char **) &sb.final_buf,
+ textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb.final_buf,
&sb.final_buf_size))
;
else
- sb.final_buf = read_sha1_file(o->blob_sha1, &type,
+ sb.final_buf = read_sha1_file(o->blob_oid.hash, &type,
&sb.final_buf_size);
if (!sb.final_buf)
- die("Cannot read blob %s for path %s",
- sha1_to_hex(o->blob_sha1),
+ die(_("cannot read blob %s for path %s"),
+ oid_to_hex(&o->blob_oid),
path);
}
num_read_blob++;
lno = prepare_lines(&sb);
if (lno && !range_list.nr)
- string_list_append(&range_list, xstrdup("1"));
+ string_list_append(&range_list, "1");
anchor = 1;
range_set_init(&ranges, range_list.nr);
@@ -2473,7 +2876,9 @@ parse_done:
&bottom, &top, sb.path))
usage(blame_usage);
if (lno < top || ((lno || bottom) && lno < bottom))
- die("file %s has only %lu lines", path, lno);
+ die(Q_("file %s has only %lu line",
+ "file %s has only %lu lines",
+ lno), path, lno);
if (bottom < 1)
bottom = 1;
if (top < 1)
@@ -2497,24 +2902,32 @@ parse_done:
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 = ent;
+ sb.ent = NULL;
sb.path = path;
read_mailmap(&mailmap, NULL);
+ assign_blame(&sb, opt);
+
if (!incremental)
setup_pager();
- 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 b4d771673e..94f7de7fa5 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -19,29 +19,30 @@
#include "column.h"
#include "utf8.h"
#include "wt-status.h"
+#include "ref-filter.h"
+#include "worktree.h"
static const char * const builtin_branch_usage[] = {
- 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>"),
+ N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
+ N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
+ N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
+ N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
+ N_("git branch [<options>] [-r | -a] [--points-at]"),
+ N_("git branch [<options>] [-r | -a] [--format]"),
NULL
};
-#define REF_LOCAL_BRANCH 0x01
-#define REF_REMOTE_BRANCH 0x02
-
static const char *head;
static unsigned char head_sha1[20];
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
- GIT_COLOR_NORMAL, /* PLAIN */
- GIT_COLOR_RED, /* REMOTE */
- GIT_COLOR_NORMAL, /* LOCAL */
- GIT_COLOR_GREEN, /* CURRENT */
- GIT_COLOR_BLUE, /* UPSTREAM */
+ GIT_COLOR_NORMAL, /* PLAIN */
+ GIT_COLOR_RED, /* REMOTE */
+ GIT_COLOR_NORMAL, /* LOCAL */
+ GIT_COLOR_GREEN, /* CURRENT */
+ GIT_COLOR_BLUE, /* UPSTREAM */
};
enum color_branch {
BRANCH_COLOR_RESET = 0,
@@ -52,49 +53,43 @@ enum color_branch {
BRANCH_COLOR_UPSTREAM = 5
};
-static enum merge_filter {
- NO_FILTER = 0,
- SHOW_NOT_MERGED,
- SHOW_MERGED
-} merge_filter;
-static unsigned char merge_filter_ref[20];
-
static struct string_list output = STRING_LIST_INIT_DUP;
static unsigned int colopts;
-static int parse_branch_color_slot(const char *var, int ofs)
+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(var+ofs, "upstream"))
+ 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 (starts_with(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);
}
@@ -120,16 +115,15 @@ static int branch_merged(int kind, const char *name,
void *reference_name_to_free = NULL;
int merged;
- if (kind == REF_LOCAL_BRANCH) {
+ if (kind == FILTER_REFS_BRANCHES) {
struct branch *branch = branch_get(name);
+ const char *upstream = branch_get_upstream(branch, NULL);
unsigned char sha1[20];
- if (branch &&
- branch->merge &&
- branch->merge[0] &&
- branch->merge[0]->dst &&
+ if (upstream &&
(reference_name = reference_name_to_free =
- resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+ resolve_refdup(upstream, RESOLVE_REF_READING,
+ sha1, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1);
}
if (!reference_rev)
@@ -160,7 +154,7 @@ static int branch_merged(int kind, const char *name,
}
static int check_branch_commit(const char *branchname, const char *refname,
- unsigned char *sha1, struct commit *head_rev,
+ const unsigned char *sha1, struct commit *head_rev,
int kinds, int force)
{
struct commit *rev = lookup_commit_reference(sha1);
@@ -199,14 +193,14 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
struct strbuf bname = STRBUF_INIT;
switch (kinds) {
- case REF_REMOTE_BRANCH:
+ case FILTER_REFS_REMOTES:
fmt = "refs/remotes/%s";
/* For subsequent UI messages */
remote_branch = 1;
force = 1;
break;
- case REF_LOCAL_BRANCH:
+ case FILTER_REFS_BRANCHES:
fmt = "refs/heads/%s";
break;
default:
@@ -219,55 +213,67 @@ 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;
+ char *target = NULL;
int flags = 0;
strbuf_branchname(&bname, argv[i]);
- if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
- error(_("Cannot delete the branch '%s' "
- "which you are currently on."), bname.buf);
- ret = 1;
- continue;
- }
-
free(name);
-
name = mkpathdup(fmt, bname.buf);
- target = resolve_ref_unsafe(name, sha1, 0, &flags);
- if (!target ||
- (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {
+
+ if (kinds == FILTER_REFS_BRANCHES) {
+ const struct worktree *wt =
+ find_shared_symref("HEAD", name);
+ if (wt) {
+ error(_("Cannot delete branch '%s' "
+ "checked out at '%s'"),
+ bname.buf, wt->path);
+ ret = 1;
+ continue;
+ }
+ }
+
+ target = resolve_refdup(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.")
+ ? _("remote-tracking branch '%s' not found.")
: _("branch '%s' not found."), bname.buf);
ret = 1;
continue;
}
- if (!(flags & REF_ISSYMREF) &&
+ if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
force)) {
ret = 1;
- continue;
+ goto next;
}
- if (delete_ref(name, sha1, REF_NODEREF)) {
+ if (delete_ref(NULL, name, is_null_sha1(sha1) ? NULL : sha1,
+ REF_NODEREF)) {
error(remote_branch
- ? _("Error deleting remote branch '%s'")
+ ? _("Error deleting remote-tracking branch '%s'")
: _("Error deleting branch '%s'"),
bname.buf);
ret = 1;
- continue;
+ goto next;
}
if (!quiet) {
printf(remote_branch
- ? _("Deleted remote branch %s (was %s).\n")
+ ? _("Deleted remote-tracking branch %s (was %s).\n")
: _("Deleted branch %s (was %s).\n"),
bname.buf,
- (flags & REF_ISSYMREF)
- ? target
+ (flags & REF_ISBROKEN) ? "broken"
+ : (flags & REF_ISSYMREF) ? target
: find_unique_abbrev(sha1, DEFAULT_ABBREV));
}
delete_branch_config(bname.buf);
+
+ next:
+ free(target);
}
free(name);
@@ -275,412 +281,158 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
return(ret);
}
-struct ref_item {
- char *name;
- char *dest;
- unsigned int kind, width;
- struct commit *commit;
-};
-
-struct ref_list {
- struct rev_info revs;
- int index, alloc, maxwidth, verbose, abbrev;
- struct ref_item *list;
- struct commit_list *with_commit;
- int kinds;
-};
-
-static char *resolve_symref(const char *src, const char *prefix)
-{
- unsigned char sha1[20];
- int flag;
- const char *dst, *cp;
-
- dst = resolve_ref_unsafe(src, sha1, 0, &flag);
- if (!(dst && (flag & REF_ISSYMREF)))
- return NULL;
- if (prefix && (cp = skip_prefix(dst, prefix)))
- dst = cp;
- 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)
+static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
{
- if (!*pattern)
- return 1; /* no pattern always matches */
- while (*pattern) {
- if (!fnmatch(*pattern, refname, 0))
- return 1;
- pattern++;
+ int i, max = 0;
+ for (i = 0; i < refs->nr; i++) {
+ struct ref_array_item *it = refs->items[i];
+ const char *desc = it->refname;
+ int w;
+
+ skip_prefix(it->refname, "refs/heads/", &desc);
+ skip_prefix(it->refname, "refs/remotes/", &desc);
+ if (it->kind == FILTER_REFS_DETACHED_HEAD) {
+ char *head_desc = get_head_description();
+ w = utf8_strwidth(head_desc);
+ free(head_desc);
+ } else
+ w = utf8_strwidth(desc);
+
+ if (it->kind == FILTER_REFS_REMOTES)
+ w += remote_bonus;
+ if (w > max)
+ max = w;
}
- return 0;
+ return max;
}
-static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+static const char *quote_literal_for_format(const char *s)
{
- struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
- struct ref_list *ref_list = cb->ref_list;
- struct ref_item *newitem;
- struct commit *commit;
- int kind, i;
- const char *prefix, *orig_refname = refname;
-
- static struct {
- int kind;
- const char *prefix;
- int pfxlen;
- } ref_kind[] = {
- { REF_LOCAL_BRANCH, "refs/heads/", 11 },
- { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
- };
-
- /* 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 (ARRAY_SIZE(ref_kind) <= i)
- return 0;
-
- /* Don't add types the caller doesn't want */
- if ((kind & ref_list->kinds) == 0)
- return 0;
-
- if (!match_patterns(cb->pattern, refname))
- return 0;
+ static struct strbuf buf = STRBUF_INIT;
- commit = NULL;
- if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
- commit = lookup_commit_reference_gently(sha1, 1);
- if (!commit) {
- cb->ret = error(_("branch '%s' does not point at a commit"), refname);
- return 0;
+ strbuf_reset(&buf);
+ while (*s) {
+ const char *ep = strchrnul(s, '%');
+ if (s < ep)
+ strbuf_add(&buf, s, ep - s);
+ if (*ep == '%') {
+ strbuf_addstr(&buf, "%%");
+ s = ep + 1;
+ } else {
+ s = ep;
}
-
- /* Filter with with_commit if specified */
- if (!is_descendant_of(commit, ref_list->with_commit))
- return 0;
-
- if (merge_filter != NO_FILTER)
- add_pending_object(&ref_list->revs,
- (struct object *)commit, refname);
- }
-
- ALLOC_GROW(ref_list->list, ref_list->index + 1, ref_list->alloc);
-
- /* Record the new item */
- newitem = &(ref_list->list[ref_list->index++]);
- newitem->name = xstrdup(refname);
- newitem->kind = kind;
- newitem->commit = commit;
- newitem->width = utf8_strwidth(refname);
- newitem->dest = resolve_symref(orig_refname, prefix);
- /* adjust for "remotes/" */
- if (newitem->kind == REF_REMOTE_BRANCH &&
- ref_list->kinds != REF_REMOTE_BRANCH)
- newitem->width += 8;
- if (newitem->width > ref_list->maxwidth)
- ref_list->maxwidth = newitem->width;
-
- return 0;
-}
-
-static void free_ref_list(struct ref_list *ref_list)
-{
- int i;
-
- for (i = 0; i < ref_list->index; i++) {
- free(ref_list->list[i].name);
- free(ref_list->list[i].dest);
}
- free(ref_list->list);
+ return buf.buf;
}
-static int ref_cmp(const void *r1, const void *r2)
+static char *build_format(struct ref_filter *filter, int maxwidth, const char *remote_prefix)
{
- struct ref_item *c1 = (struct ref_item *)(r1);
- struct ref_item *c2 = (struct ref_item *)(r2);
-
- if (c1->kind != c2->kind)
- return c1->kind - c2->kind;
- return strcmp(c1->name, c2->name);
-}
-
-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;
-
- switch (stat_tracking_info(branch, &ours, &theirs)) {
- case 0:
- /* no base */
- return;
- case -1:
- /* with "gone" base */
- upstream_is_gone = 1;
- break;
- default:
- /* with base */
- break;
- }
-
- if (show_upstream_ref) {
- 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));
+ struct strbuf fmt = STRBUF_INIT;
+ struct strbuf local = STRBUF_INIT;
+ struct strbuf remote = STRBUF_INIT;
+
+ strbuf_addf(&fmt, "%%(if)%%(HEAD)%%(then)* %s%%(else) %%(end)",
+ branch_get_color(BRANCH_COLOR_CURRENT));
+
+ if (filter->verbose) {
+ strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
+ strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET));
+ strbuf_addf(&local, " %%(objectname:short=7) ");
+
+ if (filter->verbose > 1)
+ strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)"
+ "%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)",
+ branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET));
else
- strbuf_addstr(&fancy, ref);
- }
-
- if (upstream_is_gone) {
- if (show_upstream_ref)
- strbuf_addf(stat, _("[%s: gone]"), fancy.buf);
- else
- added_decoration = 0;
- } else if (!ours && !theirs) {
- if (show_upstream_ref)
- strbuf_addf(stat, _("[%s]"), fancy.buf);
- else
- added_decoration = 0;
- } else if (!ours) {
- if (show_upstream_ref)
- strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs);
- else
- strbuf_addf(stat, _("[behind %d]"), theirs);
+ strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
- } else if (!theirs) {
- if (show_upstream_ref)
- strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
- else
- strbuf_addf(stat, _("[ahead %d]"), ours);
+ strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s%%(if)%%(symref)%%(then) -> %%(symref:short)"
+ "%%(else) %%(objectname:short=7) %%(contents:subject)%%(end)",
+ branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix),
+ branch_get_color(BRANCH_COLOR_RESET));
} 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_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
+ branch_get_color(BRANCH_COLOR_RESET));
+ strbuf_addf(&remote, "%s%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
+ branch_get_color(BRANCH_COLOR_REMOTE), quote_literal_for_format(remote_prefix),
+ branch_get_color(BRANCH_COLOR_RESET));
}
- strbuf_release(&fancy);
- if (added_decoration)
- strbuf_addch(stat, ' ');
- free(ref);
-}
-
-static int matches_merge_filter(struct commit *commit)
-{
- int is_merged;
- if (merge_filter == NO_FILTER)
- return 1;
+ strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf);
- is_merged = !!(commit->object.flags & UNINTERESTING);
- return (is_merged == (merge_filter == SHOW_MERGED));
+ strbuf_release(&local);
+ strbuf_release(&remote);
+ return strbuf_detach(&fmt, NULL);
}
-static void add_verbose_info(struct strbuf *out, struct ref_item *item,
- int verbose, int abbrev)
+static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
{
- struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
- const char *sub = _(" **** invalid ref ****");
- struct commit *commit = item->commit;
+ int i;
+ struct ref_array array;
+ int maxwidth = 0;
+ const char *remote_prefix = "";
+ struct strbuf out = STRBUF_INIT;
+ char *to_free = NULL;
- if (!parse_commit(commit)) {
- pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject);
- sub = subject.buf;
- }
+ /*
+ * If we are listing more than just remote branches,
+ * then remote branches will have a "remotes/" prefix.
+ * We need to account for this in the width.
+ */
+ if (filter->kind != FILTER_REFS_REMOTES)
+ remote_prefix = "remotes/";
- if (item->kind == REF_LOCAL_BRANCH)
- fill_tracking_info(&stat, item->name, verbose > 1);
+ memset(&array, 0, sizeof(array));
- strbuf_addf(out, " %s %s%s",
- find_unique_abbrev(item->commit->object.sha1, abbrev),
- stat.buf, sub);
- strbuf_release(&stat);
- strbuf_release(&subject);
-}
+ filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
-static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
- int abbrev, int current, char *prefix)
-{
- char c;
- int color;
- struct commit *commit = item->commit;
- struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
+ if (filter->verbose)
+ maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
- if (!matches_merge_filter(commit))
- return;
+ if (!format)
+ format = to_free = build_format(filter, maxwidth, remote_prefix);
+ verify_ref_format(format);
- switch (item->kind) {
- case REF_LOCAL_BRANCH:
- color = BRANCH_COLOR_LOCAL;
- break;
- case REF_REMOTE_BRANCH:
- color = BRANCH_COLOR_REMOTE;
- break;
- default:
- color = BRANCH_COLOR_PLAIN;
- break;
- }
+ ref_array_sort(sorting, &array);
- c = ' ';
- if (current) {
- c = '*';
- color = BRANCH_COLOR_CURRENT;
+ for (i = 0; i < array.nr; i++) {
+ format_ref_array_item(array.items[i], format, 0, &out);
+ if (column_active(colopts)) {
+ assert(!filter->verbose && "--column and --verbose are incompatible");
+ /* format to a string_list to let print_columns() do its job */
+ string_list_append(&output, out.buf);
+ } else {
+ fwrite(out.buf, 1, out.len, stdout);
+ putchar('\n');
+ }
+ strbuf_release(&out);
}
- strbuf_addf(&name, "%s%s", prefix, item->name);
- 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 + utf8_compensation, name.buf,
- branch_get_color(BRANCH_COLOR_RESET));
- } else
- strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
- name.buf, branch_get_color(BRANCH_COLOR_RESET));
-
- if (item->dest)
- strbuf_addf(&out, " -> %s", item->dest);
- else if (verbose)
- /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
- add_verbose_info(&out, item, verbose, abbrev);
- 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);
+ ref_array_clear(&array);
+ free(to_free);
}
-static int calc_maxwidth(struct ref_list *refs)
+static void reject_rebase_or_bisect_branch(const char *target)
{
- int i, w = 0;
- for (i = 0; i < refs->index; i++) {
- if (!matches_merge_filter(refs->list[i].commit))
- continue;
- if (refs->list[i].width > w)
- w = refs->list[i].width;
- }
- return w;
-}
+ struct worktree **worktrees = get_worktrees(0);
+ int i;
-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);
-}
+ for (i = 0; worktrees[i]; i++) {
+ struct worktree *wt = worktrees[i];
-static void show_detached(struct ref_list *ref_list)
-{
- struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
-
- if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
- struct ref_item item;
- 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.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);
- }
-}
+ if (!wt->is_detached)
+ continue;
-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;
- struct ref_list ref_list;
-
- memset(&ref_list, 0, sizeof(ref_list));
- ref_list.kinds = kinds;
- ref_list.verbose = verbose;
- ref_list.abbrev = abbrev;
- ref_list.with_commit = with_commit;
- 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 (verbose)
- ref_list.maxwidth = calc_maxwidth(&ref_list);
- }
+ if (is_worktree_being_rebased(wt, target))
+ die(_("Branch %s is being rebased at %s"),
+ target, wt->path);
- qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
-
- detached = (detached && (kinds & REF_LOCAL_BRANCH));
- if (detached && match_patterns(pattern, "HEAD"))
- show_detached(&ref_list);
-
- for (i = 0; i < ref_list.index; i++) {
- int current = !detached &&
- (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
- !strcmp(ref_list.list[i].name, head);
- char *prefix = (kinds != REF_REMOTE_BRANCH &&
- ref_list.list[i].kind == REF_REMOTE_BRANCH)
- ? "remotes/" : "";
- print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
- abbrev, current, prefix);
+ if (is_worktree_being_bisected(wt, target))
+ die(_("Branch %s is being bisected at %s"),
+ target, wt->path);
}
- free_ref_list(&ref_list);
-
- if (cb.ret)
- error(_("some refs could not be read"));
-
- return cb.ret;
+ free_worktrees(worktrees);
}
static void rename_branch(const char *oldname, const char *newname, int force)
@@ -712,20 +464,22 @@ static void rename_branch(const char *oldname, const char *newname, int force)
validate_new_branchname(newname, &newref, force, clobber_head_ok);
+ reject_rebase_or_bisect_branch(oldref.buf);
+
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
oldref.buf, newref.buf);
if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch rename failed"));
- strbuf_release(&logmsg);
if (recovery)
warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
- /* no need to pass logmsg here as HEAD didn't really move */
- if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
+ if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
+ strbuf_release(&logmsg);
+
strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
strbuf_release(&oldref);
strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
@@ -736,26 +490,10 @@ static void rename_branch(const char *oldname, const char *newname, int force)
strbuf_release(&newsection);
}
-static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
-{
- merge_filter = ((opt->long_name[0] == 'n')
- ? SHOW_NOT_MERGED
- : SHOW_MERGED);
- if (unset)
- merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */
- if (!arg)
- arg = "HEAD";
- if (get_sha1(arg, merge_filter_ref))
- die(_("malformed object name %s"), arg);
- 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;
@@ -763,73 +501,59 @@ static int edit_branch_description(const char *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",
+ _("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));
- }
+ write_file_buf(git_path(edit_description), buf.buf, buf.len);
strbuf_reset(&buf);
if (launch_editor(git_path(edit_description), &buf, NULL)) {
strbuf_release(&buf);
return -1;
}
- stripspace(&buf, 1);
+ strbuf_stripspace(&buf, 1);
strbuf_addf(&name, "branch.%s.description", branch_name);
- status = git_config_set(name.buf, buf.len ? buf.buf : NULL);
+ git_config_set(name.buf, buf.len ? buf.buf : NULL);
strbuf_release(&name);
strbuf_release(&buf);
- return status;
+ return 0;
}
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, rename = 0, force_create = 0, list = 0;
- int verbose = 0, abbrev = -1, detached = 0;
+ int delete = 0, rename = 0, force = 0, list = 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 ref_filter filter;
+ int icase = 0;
+ static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+ const char *format = NULL;
struct option options[] = {
OPT_GROUP(N_("Generic options")),
- OPT__VERBOSE(&verbose,
+ OPT__VERBOSE(&filter.verbose,
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, N_("change upstream info"),
BRANCH_TRACK_OVERRIDE),
- 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_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
+ OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")),
OPT__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, 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, 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_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"),
+ FILTER_REFS_REMOTES),
+ OPT_CONTAINS(&filter.with_commit, N_("print only branches that contain the commit")),
+ OPT_WITH(&filter.with_commit, N_("print only branches that contain the commit")),
+ OPT__ABBREV(&filter.abbrev),
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_SET_INT('a', "all", &filter.kind, N_("list both remote-tracking and local branches"),
+ FILTER_REFS_REMOTES | FILTER_REFS_BRANCHES),
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),
@@ -838,23 +562,27 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")),
OPT_BOOL(0, "edit-description", &edit_description,
N_("edit the description for the branch")),
- OPT__FORCE(&force_create, N_("force creation (when already exists)")),
- {
- OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
- N_("commit"), N_("print only not merged branches"),
- PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
- opt_parse_merge_filter, (intptr_t) "HEAD",
- },
+ OPT__FORCE(&force, N_("force creation, move/rename, deletion")),
+ OPT_MERGED(&filter, N_("print only branches that are merged")),
+ OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
+ OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
+ OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+ N_("field name to sort on"), &parse_opt_ref_sorting),
{
- OPTION_CALLBACK, 0, "merged", &merge_filter_ref,
- N_("commit"), N_("print only merged branches"),
- PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
- opt_parse_merge_filter, (intptr_t) "HEAD",
+ OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+ N_("print only branches of the object"), 0, parse_opt_object_name
},
- OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
+ OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
+ OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
+ setup_ref_filter_porcelain_msg();
+
+ memset(&filter, 0, sizeof(filter));
+ filter.kind = FILTER_REFS_BRANCHES;
+ filter.abbrev = -1;
+
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_branch_usage, options);
@@ -862,18 +590,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
track = git_branch_track;
- head = resolve_refdup("HEAD", head_sha1, 0, NULL);
+ head = resolve_refdup("HEAD", 0, head_sha1, NULL);
if (!head)
die(_("Failed to resolve HEAD as a valid ref."));
- if (!strcmp(head, "HEAD")) {
- detached = 1;
- } else {
- if (!starts_with(head, "refs/heads/"))
- die(_("HEAD not found below refs/heads!"));
- head += 11;
- }
- hashcpy(merge_filter_ref, head_sha1);
-
+ if (!strcmp(head, "HEAD"))
+ filter.detached = 1;
+ else if (!skip_prefix(head, "refs/heads/", &head))
+ die(_("HEAD not found below refs/heads!"));
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
@@ -881,39 +604,59 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
list = 1;
- if (with_commit || merge_filter != NO_FILTER)
+ if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr)
list = 1;
- if (!!delete + !!rename + !!force_create + !!new_upstream +
+ if (!!delete + !!rename + !!new_upstream +
list + unset_upstream > 1)
usage_with_options(builtin_branch_usage, options);
- if (abbrev == -1)
- abbrev = DEFAULT_ABBREV;
+ if (filter.abbrev == -1)
+ filter.abbrev = DEFAULT_ABBREV;
+ filter.ignore_case = icase;
+
finalize_colopts(&colopts, -1);
- if (verbose) {
+ if (filter.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);
+ return delete_branches(argc, argv, delete > 1, filter.kind, quiet);
} else if (list) {
- int ret = print_ref_list(kinds, detached, verbose, abbrev,
- with_commit, argv);
+ /* git branch --local also shows HEAD when it is detached */
+ if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached)
+ filter.kind |= FILTER_REFS_DETACHED_HEAD;
+ filter.name_patterns = argv;
+ /*
+ * If no sorting parameter is given then we default to sorting
+ * by 'refname'. This would give us an alphabetically sorted
+ * array with the 'HEAD' ref at the beginning followed by
+ * local branches 'refs/heads/...' and finally remote-tacking
+ * branches 'refs/remotes/...'.
+ */
+ if (!sorting)
+ sorting = ref_default_sorting();
+ sorting->ignore_case = icase;
+ print_ref_list(&filter, sorting, format);
print_columns(&output, colopts, NULL);
string_list_clear(&output, 0);
- return ret;
+ return 0;
}
else if (edit_description) {
const char *branch_name;
struct strbuf branch_ref = STRBUF_INIT;
if (!argc) {
- if (detached)
+ if (filter.detached)
die(_("Cannot give description to detached HEAD"));
branch_name = head;
} else if (argc == 1)
@@ -966,7 +709,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
* 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);
+ create_branch(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;
@@ -1001,7 +744,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (!branch)
die(_("no such branch '%s'"), argv[0]);
- if (kinds != REF_LOCAL_BRANCH)
+ if (filter.kind != FILTER_REFS_BRANCHES)
die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
if (track == BRANCH_TRACK_OVERRIDE)
@@ -1012,8 +755,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
strbuf_release(&buf);
branch_existed = ref_exists(branch->refname);
- create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
- force_create, reflog, 0, quiet, track);
+ create_branch(argv[0], (argc == 2) ? argv[1] : head,
+ force, reflog, 0, quiet, track);
/*
* We only show the instructions if the user gave us
@@ -1023,8 +766,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
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);
+ fprintf(stderr, " git branch -d %s\n", branch->name);
+ fprintf(stderr, " git branch --set-upstream-to %s\n", branch->name);
}
} else
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 92a8a6026a..4883a435a9 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -42,6 +42,10 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
if (!strcmp(cmd, "verify")) {
close(bundle_fd);
+ if (argc != 1) {
+ usage(builtin_bundle_usage);
+ return 1;
+ }
if (verify_bundle(&header, 1))
return 1;
fprintf(stderr, _("%s is okay\n"), bundle_file);
@@ -52,6 +56,10 @@ int cmd_bundle(int argc, const char **argv, const char *prefix)
return !!list_bundle_refs(&header, argc, argv);
}
if (!strcmp(cmd, "create")) {
+ if (argc < 2) {
+ usage(builtin_bundle_usage);
+ return 1;
+ }
if (!startup_info->have_repository)
die(_("Need a repository to create a bundle."));
return !!create_bundle(&header, bundle_file, argc, argv);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index d5a93e0e91..30383e9eb4 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -4,57 +4,114 @@
* 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"
#include "streaming.h"
+#include "tree-walk.h"
+#include "sha1-array.h"
-static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
+struct batch_options {
+ int enabled;
+ int follow_symlinks;
+ int print_contents;
+ int buffer_output;
+ int all_objects;
+ int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */
+ const char *format;
+};
+
+static const char *force_path;
+
+static int filter_object(const char *path, unsigned mode,
+ const struct object_id *oid,
+ char **buf, unsigned long *size)
{
- unsigned char sha1[20];
+ enum object_type type;
+
+ *buf = read_sha1_file(oid->hash, &type, size);
+ if (!*buf)
+ return error(_("cannot read object %s '%s'"),
+ oid_to_hex(oid), path);
+ if ((type == OBJ_BLOB) && S_ISREG(mode)) {
+ struct strbuf strbuf = STRBUF_INIT;
+ if (convert_to_working_tree(path, *buf, *size, &strbuf)) {
+ free(*buf);
+ *size = strbuf.len;
+ *buf = strbuf_detach(&strbuf, NULL);
+ }
+ }
+
+ return 0;
+}
+
+static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
+ int unknown_type)
+{
+ struct object_id oid;
enum object_type type;
char *buf;
unsigned long size;
struct object_context obj_context;
+ struct object_info oi = OBJECT_INFO_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ unsigned flags = LOOKUP_REPLACE_OBJECT;
+ const char *path = force_path;
- if (get_sha1_with_context(obj_name, 0, sha1, &obj_context))
+ if (unknown_type)
+ flags |= LOOKUP_UNKNOWN_OBJECT;
+
+ if (get_sha1_with_context(obj_name, 0, oid.hash, &obj_context))
die("Not a valid object name %s", obj_name);
+ if (!path)
+ path = obj_context.path;
+ if (obj_context.mode == S_IFINVALID)
+ obj_context.mode = 0100644;
+
buf = NULL;
switch (opt) {
case 't':
- type = sha1_object_info(sha1, NULL);
- if (type > 0) {
- printf("%s\n", typename(type));
+ oi.typename = &sb;
+ if (sha1_object_info_extended(oid.hash, &oi, flags) < 0)
+ die("git cat-file: could not get object info");
+ if (sb.len) {
+ printf("%s\n", sb.buf);
+ strbuf_release(&sb);
return 0;
}
break;
case 's':
- type = sha1_object_info(sha1, &size);
- if (type > 0) {
- printf("%lu\n", size);
- return 0;
- }
- break;
+ oi.sizep = &size;
+ if (sha1_object_info_extended(oid.hash, &oi, flags) < 0)
+ die("git cat-file: could not get object info");
+ printf("%lu\n", size);
+ return 0;
case 'e':
- return !has_sha1_file(sha1);
+ return !has_object_file(&oid);
+
+ case 'w':
+ if (!path[0])
+ die("git cat-file --filters %s: <object> must be "
+ "<sha1:path>", obj_name);
+
+ if (filter_object(path, obj_context.mode,
+ &oid, &buf, &size))
+ return -1;
+ break;
case 'c':
- if (!obj_context.path[0])
+ if (!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))
+ if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size))
break;
case 'p':
- type = sha1_object_info(sha1, NULL);
+ type = sha1_object_info(oid.hash, NULL);
if (type < 0)
die("Not a valid object name %s", obj_name);
@@ -67,8 +124,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
}
if (type == OBJ_BLOB)
- return stream_blob_to_fd(1, sha1, NULL, 0);
- buf = read_sha1_file(sha1, &type, &size);
+ return stream_blob_to_fd(1, &oid, NULL, 0);
+ buf = read_sha1_file(oid.hash, &type, &size);
if (!buf)
die("Cannot read object %s", obj_name);
@@ -77,20 +134,19 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
case 0:
if (type_from_string(exp_type) == OBJ_BLOB) {
- unsigned char blob_sha1[20];
- if (sha1_object_info(sha1, NULL) == OBJ_TAG) {
- enum object_type type;
- unsigned long size;
- char *buffer = read_sha1_file(sha1, &type, &size);
- if (memcmp(buffer, "object ", 7) ||
- get_sha1_hex(buffer + 7, blob_sha1))
- die("%s not a valid tag", sha1_to_hex(sha1));
+ struct object_id blob_oid;
+ if (sha1_object_info(oid.hash, NULL) == OBJ_TAG) {
+ char *buffer = read_sha1_file(oid.hash, &type, &size);
+ const char *target;
+ if (!skip_prefix(buffer, "object ", &target) ||
+ get_oid_hex(target, &blob_oid))
+ die("%s not a valid tag", oid_to_hex(&oid));
free(buffer);
} else
- hashcpy(blob_sha1, sha1);
+ oidcpy(&blob_oid, &oid);
- if (sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
- return stream_blob_to_fd(1, blob_sha1, NULL, 0);
+ if (sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB)
+ return stream_blob_to_fd(1, &blob_oid, NULL, 0);
/*
* we attempted to dereference a tag to a blob
* and failed; there may be new dereference
@@ -98,7 +154,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
* fall-back to the usual case.
*/
}
- buf = read_object_with_reference(sha1, exp_type, &size, NULL);
+ buf = read_object_with_reference(oid.hash, exp_type, &size, NULL);
break;
default:
@@ -113,12 +169,12 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
}
struct expand_data {
- unsigned char sha1[20];
+ struct object_id oid;
enum object_type type;
unsigned long size;
- unsigned long disk_size;
+ off_t disk_size;
const char *rest;
- unsigned char delta_base_sha1[20];
+ struct object_id delta_base_oid;
/*
* If mark_query is true, we do not expand anything, but rather
@@ -139,6 +195,13 @@ struct expand_data {
* elements above, so you can retrieve the response from there.
*/
struct object_info info;
+
+ /*
+ * This flag will be true if the requested batch format and options
+ * don't require us to call sha1_object_info, which can then be
+ * optimized out.
+ */
+ unsigned skip_object_info : 1;
};
static int is_atom(const char *atom, const char *s, int slen)
@@ -154,7 +217,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
if (is_atom("objectname", atom, len)) {
if (!data->mark_query)
- strbuf_addstr(sb, sha1_to_hex(data->sha1));
+ strbuf_addstr(sb, oid_to_hex(&data->oid));
} else if (is_atom("objecttype", atom, len)) {
if (data->mark_query)
data->info.typep = &data->type;
@@ -169,7 +232,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
if (data->mark_query)
data->info.disk_sizep = &data->disk_size;
else
- strbuf_addf(sb, "%lu", data->disk_size);
+ strbuf_addf(sb, "%"PRIuMAX, (uintmax_t)data->disk_size);
} else if (is_atom("rest", atom, len)) {
if (data->mark_query)
data->split_on_whitespace = 1;
@@ -177,9 +240,10 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len,
strbuf_addstr(sb, data->rest);
} else if (is_atom("deltabase", atom, len)) {
if (data->mark_query)
- data->info.delta_base_sha1 = data->delta_base_sha1;
+ data->info.delta_base_sha1 = data->delta_base_oid.hash;
else
- strbuf_addstr(sb, sha1_to_hex(data->delta_base_sha1));
+ strbuf_addstr(sb,
+ oid_to_hex(&data->delta_base_oid));
} else
die("unknown format element: %.*s", len, atom);
}
@@ -199,69 +263,166 @@ static size_t expand_format(struct strbuf *sb, const char *start, void *data)
return end - start + 1;
}
-static void print_object_or_die(int fd, struct expand_data *data)
+static void batch_write(struct batch_options *opt, const void *data, int len)
{
- const unsigned char *sha1 = data->sha1;
+ if (opt->buffer_output) {
+ if (fwrite(data, 1, len, stdout) != len)
+ die_errno("unable to write to stdout");
+ } else
+ write_or_die(1, data, len);
+}
+
+static void print_object_or_die(struct batch_options *opt, struct expand_data *data)
+{
+ const struct object_id *oid = &data->oid;
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));
+ if (opt->buffer_output)
+ fflush(stdout);
+ if (opt->cmdmode) {
+ char *contents;
+ unsigned long size;
+
+ if (!data->rest)
+ die("missing path for '%s'", oid_to_hex(oid));
+
+ if (opt->cmdmode == 'w') {
+ if (filter_object(data->rest, 0100644, oid,
+ &contents, &size))
+ die("could not convert '%s' %s",
+ oid_to_hex(oid), data->rest);
+ } else if (opt->cmdmode == 'c') {
+ enum object_type type;
+ if (!textconv_object(data->rest, 0100644, oid,
+ 1, &contents, &size))
+ contents = read_sha1_file(oid->hash, &type,
+ &size);
+ if (!contents)
+ die("could not convert '%s' %s",
+ oid_to_hex(oid), data->rest);
+ } else
+ die("BUG: invalid cmdmode: %c", opt->cmdmode);
+ batch_write(opt, contents, size);
+ free(contents);
+ } else if (stream_blob_to_fd(1, oid, NULL, 0) < 0)
+ die("unable to stream %s to stdout", oid_to_hex(oid));
}
else {
enum object_type type;
unsigned long size;
void *contents;
- contents = read_sha1_file(sha1, &type, &size);
+ contents = read_sha1_file(oid->hash, &type, &size);
if (!contents)
- die("object %s disappeared", sha1_to_hex(sha1));
+ die("object %s disappeared", oid_to_hex(oid));
if (type != data->type)
- die("object %s changed type!?", sha1_to_hex(sha1));
+ die("object %s changed type!?", oid_to_hex(oid));
if (data->info.sizep && size != data->size)
- die("object %s changed size!?", sha1_to_hex(sha1));
+ die("object %s changed size!?", oid_to_hex(oid));
- write_or_die(fd, contents, size);
+ batch_write(opt, 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)
+static void batch_object_write(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, data->sha1)) {
- printf("%s missing\n", obj_name);
- fflush(stdout);
- return 0;
- }
-
- if (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) {
- printf("%s missing\n", obj_name);
+ if (!data->skip_object_info &&
+ sha1_object_info_extended(data->oid.hash, &data->info, LOOKUP_REPLACE_OBJECT) < 0) {
+ printf("%s missing\n",
+ obj_name ? obj_name : oid_to_hex(&data->oid));
fflush(stdout);
- return 0;
+ return;
}
strbuf_expand(&buf, opt->format, expand_format, data);
strbuf_addch(&buf, '\n');
- write_or_die(1, buf.buf, buf.len);
+ batch_write(opt, buf.buf, buf.len);
strbuf_release(&buf);
if (opt->print_contents) {
- print_object_or_die(1, data);
- write_or_die(1, "\n", 1);
+ print_object_or_die(opt, data);
+ batch_write(opt, "\n", 1);
}
+}
+
+static void batch_one_object(const char *obj_name, struct batch_options *opt,
+ struct expand_data *data)
+{
+ struct object_context ctx;
+ int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;
+ enum follow_symlinks_result result;
+
+ result = get_sha1_with_context(obj_name, flags, data->oid.hash, &ctx);
+ if (result != FOUND) {
+ switch (result) {
+ case MISSING_OBJECT:
+ printf("%s missing\n", obj_name);
+ break;
+ case DANGLING_SYMLINK:
+ printf("dangling %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ case SYMLINK_LOOP:
+ printf("loop %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ case NOT_DIR:
+ printf("notdir %"PRIuMAX"\n%s\n",
+ (uintmax_t)strlen(obj_name), obj_name);
+ break;
+ default:
+ die("BUG: unknown get_sha1_with_context result %d\n",
+ result);
+ break;
+ }
+ fflush(stdout);
+ return;
+ }
+
+ if (ctx.mode == 0) {
+ printf("symlink %"PRIuMAX"\n%s\n",
+ (uintmax_t)ctx.symlink_path.len,
+ ctx.symlink_path.buf);
+ fflush(stdout);
+ return;
+ }
+
+ batch_object_write(obj_name, opt, data);
+}
+
+struct object_cb_data {
+ struct batch_options *opt;
+ struct expand_data *expand;
+};
+
+static int batch_object_cb(const unsigned char sha1[20], void *vdata)
+{
+ struct object_cb_data *data = vdata;
+ hashcpy(data->expand->oid.hash, sha1);
+ batch_object_write(NULL, data->opt, data->expand);
+ return 0;
+}
+
+static int batch_loose_object(const unsigned char *sha1,
+ const char *path,
+ void *data)
+{
+ sha1_array_append(data, sha1);
+ return 0;
+}
+
+static int batch_packed_object(const unsigned char *sha1,
+ struct packed_git *pack,
+ uint32_t pos,
+ void *data)
+{
+ sha1_array_append(data, sha1);
return 0;
}
@@ -269,6 +430,8 @@ 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)";
@@ -282,6 +445,14 @@ static int batch_objects(struct batch_options *opt)
data.mark_query = 1;
strbuf_expand(&buf, opt->format, expand_format, &data);
data.mark_query = 0;
+ if (opt->cmdmode)
+ data.split_on_whitespace = 1;
+
+ if (opt->all_objects) {
+ struct object_info empty = OBJECT_INFO_INIT;
+ if (!memcmp(&data.info, &empty, sizeof(empty)))
+ data.skip_object_info = 1;
+ }
/*
* If we are printing out the object, then always fill in the type,
@@ -290,6 +461,21 @@ static int batch_objects(struct batch_options *opt)
if (opt->print_contents)
data.info.typep = &data.type;
+ if (opt->all_objects) {
+ struct sha1_array sa = SHA1_ARRAY_INIT;
+ struct object_cb_data cb;
+
+ for_each_loose_object(batch_loose_object, &sa, 0);
+ for_each_packed_object(batch_packed_object, &sa, 0);
+
+ cb.opt = opt;
+ cb.expand = &data;
+ sha1_array_for_each_unique(&sa, batch_object_cb, &cb);
+
+ sha1_array_clear(&sa);
+ return 0;
+ }
+
/*
* 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
@@ -297,11 +483,10 @@ static int batch_objects(struct batch_options *opt)
* 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;
-
+ while (strbuf_getline(&buf, stdin) != EOF) {
if (data.split_on_whitespace) {
/*
* Split at first whitespace, tying off the beginning
@@ -316,17 +501,17 @@ static int batch_objects(struct batch_options *opt)
data.rest = p;
}
- error = batch_one_object(buf.buf, opt, &data);
- if (error)
- return error;
+ batch_one_object(buf.buf, opt, &data);
}
- return 0;
+ strbuf_release(&buf);
+ warn_on_object_refname_ambiguity = save_warning;
+ return retval;
}
static const char * const cat_file_usage[] = {
- N_("git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"),
- N_("git cat-file (--batch|--batch-check) < <list_of_objects>"),
+ N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"),
+ N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"),
NULL
};
@@ -344,9 +529,8 @@ static int batch_option_callback(const struct option *opt,
{
struct batch_options *bo = opt->value;
- if (unset) {
- memset(bo, 0, sizeof(*bo));
- return 0;
+ if (bo->enabled) {
+ return 1;
}
bo->enabled = 1;
@@ -361,34 +545,46 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
int opt = 0;
const char *exp_type = NULL, *obj_name = NULL;
struct batch_options batch = {0};
+ int unknown_type = 0;
const struct option options[] = {
OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
- OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'),
- OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'),
- OPT_SET_INT('e', NULL, &opt,
+ OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'),
+ OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
+ OPT_CMDMODE('e', NULL, &opt,
N_("exit with zero when there's no error"), 'e'),
- OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
- OPT_SET_INT(0, "textconv", &opt,
+ OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
+ OPT_CMDMODE(0, "textconv", &opt,
N_("for blob objects, run textconv on object's content"), 'c'),
+ OPT_CMDMODE(0, "filters", &opt,
+ N_("for blob objects, run filters on object's content"), 'w'),
+ OPT_STRING(0, "path", &force_path, N_("blob"),
+ N_("use a specific path for --textconv/--filters")),
+ OPT_BOOL(0, "allow-unknown-type", &unknown_type,
+ N_("allow -s and -t to work with broken/corrupt objects")),
+ OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")),
{ OPTION_CALLBACK, 0, "batch", &batch, "format",
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_BOOL(0, "follow-symlinks", &batch.follow_symlinks,
+ N_("follow in-tree symlinks (used with --batch or --batch-check)")),
+ OPT_BOOL(0, "batch-all-objects", &batch.all_objects,
+ N_("show all objects with --batch or --batch-check")),
OPT_END()
};
git_config(git_cat_file_config, NULL);
- if (argc != 3 && argc != 2)
- usage_with_options(cat_file_usage, options);
-
+ batch.buffer_output = -1;
argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0);
if (opt) {
- if (argc == 1)
+ if (batch.enabled && (opt == 'c' || opt == 'w'))
+ batch.cmdmode = opt;
+ else if (argc == 1)
obj_name = argv[0];
else
usage_with_options(cat_file_usage, options);
@@ -400,12 +596,35 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
} else
usage_with_options(cat_file_usage, options);
}
- if (batch.enabled && (opt || argc)) {
+ if (batch.enabled) {
+ if (batch.cmdmode != opt || argc)
+ usage_with_options(cat_file_usage, options);
+ if (batch.cmdmode && batch.all_objects)
+ die("--batch-all-objects cannot be combined with "
+ "--textconv nor with --filters");
+ }
+
+ if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) {
+ usage_with_options(cat_file_usage, options);
+ }
+
+ if (force_path && opt != 'c' && opt != 'w') {
+ error("--path=<path> needs --textconv or --filters");
+ usage_with_options(cat_file_usage, options);
+ }
+
+ if (force_path && batch.enabled) {
+ error("--path=<path> incompatible with --batch");
usage_with_options(cat_file_usage, options);
}
+ if (batch.buffer_output < 0)
+ batch.buffer_output = batch.all_objects;
+
if (batch.enabled)
return batch_objects(&batch);
- return cat_one_file(opt, exp_type, obj_name);
+ if (unknown_type && opt != 't' && opt != 's')
+ die("git cat-file --allow-unknown-type: use with -s or -t");
+ return cat_one_file(opt, exp_type, obj_name, unknown_type);
}
diff --git a/builtin/check-attr.c b/builtin/check-attr.c
index e9af7b2bfb..4d01ca0c8b 100644
--- a/builtin/check-attr.c
+++ b/builtin/check-attr.c
@@ -8,8 +8,8 @@ static int all_attrs;
static int cached_attrs;
static int stdin_paths;
static const char * const check_attr_usage[] = {
-N_("git check-attr [-a | --all | attr...] [--] pathname..."),
-N_("git check-attr --stdin [-z] [-a | --all | attr...] < <list-of-paths>"),
+N_("git check-attr [-a | --all | <attr>...] [--] <pathname>..."),
+N_("git check-attr --stdin [-z] [-a | --all | <attr>...]"),
NULL
};
@@ -24,12 +24,13 @@ static const struct option check_attr_options[] = {
OPT_END()
};
-static void output_attr(int cnt, struct git_attr_check *check,
- const char *file)
+static void output_attr(struct attr_check *check, const char *file)
{
int j;
+ int cnt = check->nr;
+
for (j = 0; j < cnt; j++) {
- const char *value = check[j].value;
+ const char *value = check->items[j].value;
if (ATTR_TRUE(value))
value = "set";
@@ -42,53 +43,56 @@ static void output_attr(int cnt, struct git_attr_check *check,
printf("%s%c" /* path */
"%s%c" /* attrname */
"%s%c" /* attrvalue */,
- file, 0, git_attr_name(check[j].attr), 0, value, 0);
+ file, 0,
+ git_attr_name(check->items[j].attr), 0, value, 0);
} else {
quote_c_style(file, NULL, stdout, 0);
- printf(": %s: %s\n", git_attr_name(check[j].attr), value);
+ printf(": %s: %s\n",
+ git_attr_name(check->items[j].attr), value);
}
-
}
}
-static void check_attr(const char *prefix, int cnt,
- struct git_attr_check *check, const char *file)
+static void check_attr(const char *prefix,
+ struct attr_check *check,
+ int collect_all,
+ const char *file)
{
char *full_path =
prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
- if (check != NULL) {
- if (git_check_attr(full_path, cnt, check))
- die("git_check_attr died");
- output_attr(cnt, check, file);
+
+ if (collect_all) {
+ git_all_attrs(full_path, check);
} else {
- if (git_all_attrs(full_path, &cnt, &check))
- die("git_all_attrs died");
- output_attr(cnt, check, file);
- free(check);
+ if (git_check_attr(full_path, check))
+ die("git_check_attr died");
}
+ output_attr(check, file);
+
free(full_path);
}
-static void check_attr_stdin_paths(const char *prefix, int cnt,
- struct git_attr_check *check)
+static void check_attr_stdin_paths(const char *prefix,
+ struct attr_check *check,
+ int collect_all)
{
- struct strbuf buf, nbuf;
- int line_termination = nul_term_line ? 0 : '\n';
-
- 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))
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf unquoted = STRBUF_INIT;
+ strbuf_getline_fn getline_fn;
+
+ getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
+ while (getline_fn(&buf, stdin) != EOF) {
+ if (!nul_term_line && buf.buf[0] == '"') {
+ strbuf_reset(&unquoted);
+ if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
+ strbuf_swap(&buf, &unquoted);
}
- check_attr(prefix, cnt, check, buf.buf);
+ check_attr(prefix, check, collect_all, buf.buf);
maybe_flush_or_die(stdout, "attribute to stdout");
}
strbuf_release(&buf);
- strbuf_release(&nbuf);
+ strbuf_release(&unquoted);
}
static NORETURN void error_with_usage(const char *msg)
@@ -99,9 +103,12 @@ static NORETURN void error_with_usage(const char *msg)
int cmd_check_attr(int argc, const char **argv, const char *prefix)
{
- struct git_attr_check *check;
+ struct 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,
@@ -156,28 +163,26 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
error_with_usage("No file specified");
}
- if (all_attrs) {
- check = NULL;
- } else {
- check = xcalloc(cnt, sizeof(*check));
+ check = attr_check_alloc();
+ if (!all_attrs) {
for (i = 0; i < cnt; i++) {
- const char *name;
- struct git_attr *a;
- name = argv[i];
- a = git_attr(name);
+ const struct git_attr *a = git_attr(argv[i]);
+
if (!a)
return error("%s: not a valid attribute name",
- name);
- check[i].attr = a;
+ argv[i]);
+ attr_check_append(check, a);
}
}
if (stdin_paths)
- check_attr_stdin_paths(prefix, cnt, check);
+ check_attr_stdin_paths(prefix, check, all_attrs);
else {
for (i = filei; i < argc; i++)
- check_attr(prefix, cnt, check, argv[i]);
+ check_attr(prefix, check, all_attrs, argv[i]);
maybe_flush_or_die(stdout, "attribute to stdout");
}
+
+ attr_check_free(check);
return 0;
}
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
index 594463a11b..1d73d3ca3d 100644
--- a/builtin/check-ignore.c
+++ b/builtin/check-ignore.c
@@ -7,8 +7,8 @@
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>",
+"git check-ignore [<options>] <pathname>...",
+"git check-ignore [<options>] --stdin",
NULL
};
@@ -115,19 +115,19 @@ static int check_ignore(struct dir_struct *dir,
static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
{
- struct strbuf buf, nbuf;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf unquoted = STRBUF_INIT;
char *pathspec[2] = { NULL, NULL };
- int line_termination = nul_term_line ? 0 : '\n';
+ strbuf_getline_fn getline_fn;
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))
+ getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
+ while (getline_fn(&buf, stdin) != EOF) {
+ if (!nul_term_line && buf.buf[0] == '"') {
+ strbuf_reset(&unquoted);
+ if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
+ strbuf_swap(&buf, &unquoted);
}
pathspec[0] = buf.buf;
num_ignored += check_ignore(dir, prefix,
@@ -135,7 +135,7 @@ static int check_ignore_stdin_paths(struct dir_struct *dir, const char *prefix)
maybe_flush_or_die(stdout, "check-ignore to stdout");
}
strbuf_release(&buf);
- strbuf_release(&nbuf);
+ strbuf_release(&unquoted);
return num_ignored;
}
diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c
index 8f4d809bd8..cf0f54f6b9 100644
--- a/builtin/check-mailmap.c
+++ b/builtin/check-mailmap.c
@@ -5,7 +5,7 @@
static int use_stdin;
static const char * const check_mailmap_usage[] = {
-N_("git check-mailmap [options] <contact>..."),
+N_("git check-mailmap [<options>] <contact>..."),
NULL
};
@@ -54,7 +54,7 @@ int cmd_check_mailmap(int argc, const char **argv, const char *prefix)
if (use_stdin) {
struct strbuf buf = STRBUF_INIT;
- while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ while (strbuf_getline_lf(&buf, stdin) != EOF) {
check_mailmap(&mailmap, buf.buf);
maybe_flush_or_die(stdout, "stdout");
}
diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c
index 28a7320271..eac499450f 100644
--- a/builtin/check-ref-format.c
+++ b/builtin/check-ref-format.c
@@ -8,7 +8,7 @@
#include "strbuf.h"
static const char builtin_check_ref_format_usage[] =
-"git check-ref-format [--normalize] [options] <refname>\n"
+"git check-ref-format [--normalize] [<options>] <refname>\n"
" or: git check-ref-format --branch <branchname-shorthand>";
/*
@@ -20,7 +20,7 @@ static const char builtin_check_ref_format_usage[] =
*/
static char *collapse_slashes(const char *refname)
{
- char *ret = xmalloc(strlen(refname) + 1);
+ char *ret = xmallocz(strlen(refname));
char ch;
char prev = '/';
char *cp = ret;
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index 61e75eb60c..07631d0c9c 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -5,20 +5,20 @@
*
*/
#include "builtin.h"
-#include "cache.h"
+#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
#include "parse-options.h"
#define CHECKOUT_ALL 4
-static int line_termination = '\n';
+static int nul_term_line;
static int checkout_stage; /* default to checkout stage0 */
static int to_tempfile;
static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
-static struct checkout state;
+static struct checkout state = CHECKOUT_INIT;
-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,15 @@ 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,
+ nul_term_line ? '\0' : '\n');
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 +72,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 +107,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 +115,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,41 +124,12 @@ static void checkout_all(const char *prefix, int prefix_length)
}
static const char * const builtin_checkout_index_usage[] = {
- N_("git checkout-index [options] [--] [<file>...]"),
+ N_("git checkout-index [<options>] [--] [<file>...]"),
NULL
};
static struct lock_file lock_file;
-static int option_parse_u(const struct option *opt,
- const char *arg, int unset)
-{
- int *newfd = opt->value;
-
- state.refresh_cache = 1;
- if (*newfd < 0)
- *newfd = hold_locked_index(&lock_file, 1);
- return 0;
-}
-
-static int option_parse_z(const struct option *opt,
- const char *arg, int unset)
-{
- if (unset)
- line_termination = '\n';
- else
- line_termination = 0;
- return 0;
-}
-
-static int option_parse_prefix(const struct option *opt,
- const char *arg, int unset)
-{
- state.base_dir = arg;
- state.base_dir_len = strlen(arg);
- return 0;
-}
-
static int option_parse_stage(const struct option *opt,
const char *arg, int unset)
{
@@ -169,7 +141,7 @@ static int option_parse_stage(const struct option *opt,
if ('1' <= ch && ch <= '3')
checkout_stage = arg[0] - '0';
else
- die("stage should be between 1 and 3 or all");
+ die(_("stage should be between 1 and 3 or all"));
}
return 0;
}
@@ -182,6 +154,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
int read_from_stdin = 0;
int prefix_length;
int force = 0, quiet = 0, not_new = 0;
+ int index_opt = 0;
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
@@ -190,22 +163,19 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
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,
- N_("update stat information in the index file"),
- PARSE_OPT_NOARG, option_parse_u },
- { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
- N_("paths are separated with NUL character"),
- PARSE_OPT_NOARG, option_parse_z },
+ OPT_BOOL('u', "index", &index_opt,
+ N_("update stat information in the index file")),
+ OPT_BOOL('z', NULL, &nul_term_line,
+ N_("paths are separated with NUL character")),
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,
+ OPT_STRING(0, "prefix", &state.base_dir, N_("string"),
+ N_("when creating files, prepend <string>")),
+ { OPTION_CALLBACK, 0, "stage", NULL, "1-3|all",
N_("copy out the files from named stage"),
- option_parse_stage),
+ PARSE_OPT_NONEG, option_parse_stage },
OPT_END()
};
@@ -213,7 +183,6 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_checkout_index_usage,
builtin_checkout_index_options);
git_config(git_default_config, NULL);
- state.base_dir = "";
prefix_length = prefix ? strlen(prefix) : 0;
if (read_cache() < 0) {
@@ -226,52 +195,55 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
state.quiet = quiet;
state.not_new = not_new;
- if (state.base_dir_len || to_tempfile) {
- /* when --prefix is specified we do not
- * want to update cache.
- */
- if (state.refresh_cache) {
- rollback_lock_file(&lock_file);
- newfd = -1;
- }
- state.refresh_cache = 0;
+ if (!state.base_dir)
+ state.base_dir = "";
+ state.base_dir_len = strlen(state.base_dir);
+
+ /*
+ * when --prefix is specified we do not want to update cache.
+ */
+ if (index_opt && !state.base_dir_len && !to_tempfile) {
+ state.refresh_cache = 1;
+ state.istate = &the_index;
+ newfd = hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
}
/* Check out named files first */
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
- const char *p;
+ char *p;
if (all)
die("git checkout-index: don't mix '--all' and explicit filenames");
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);
- if (p < arg || p > arg + strlen(arg))
- free((char *)p);
+ checkout_file(p, prefix);
+ free(p);
}
if (read_from_stdin) {
- struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf unquoted = STRBUF_INIT;
+ strbuf_getline_fn getline_fn;
if (all)
die("git checkout-index: don't mix '--all' and '--stdin'");
- while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- const char *p;
- if (line_termination && buf.buf[0] == '"') {
- strbuf_reset(&nbuf);
- if (unquote_c_style(&nbuf, buf.buf, NULL))
+ getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
+ while (getline_fn(&buf, stdin) != EOF) {
+ char *p;
+ if (!nul_term_line && buf.buf[0] == '"') {
+ strbuf_reset(&unquoted);
+ if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
+ strbuf_swap(&buf, &unquoted);
}
p = prefix_path(prefix, prefix_length, buf.buf);
- checkout_file(p, prefix_length);
- if (p < buf.buf || p > buf.buf + buf.len)
- free((char *)p);
+ checkout_file(p, prefix);
+ free(p);
}
- strbuf_release(&nbuf);
+ strbuf_release(&unquoted);
strbuf_release(&buf);
}
@@ -279,8 +251,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 ada51fa70f..f174f50303 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"
@@ -18,12 +18,12 @@
#include "xdiff-interface.h"
#include "ll-merge.h"
#include "resolve-undo.h"
+#include "submodule-config.h"
#include "submodule.h"
-#include "argv-array.h"
static const char * const checkout_usage[] = {
- N_("git checkout [options] <branch>"),
- N_("git checkout [options] [<branch>] -- <file>..."),
+ N_("git checkout [<options>] <branch>"),
+ N_("git checkout [<options>] [<branch>] -- <file>..."),
NULL,
};
@@ -36,6 +36,8 @@ struct checkout_opts {
int writeout_stage;
int overwrite_ignore;
int ignore_skipworktree;
+ int ignore_other_worktrees;
+ int show_progress;
const char *new_branch;
const char *new_branch_force;
@@ -53,32 +55,50 @@ struct checkout_opts {
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",
+ oid_to_hex(old ? &old->object.oid : &null_oid),
+ oid_to_hex(new ? &new->object.oid : &null_oid),
+ 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);
+ hashcpy(ce->oid.hash, sha1);
+ 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 &&
+ !oidcmp(&ce->oid, &old->oid)) {
+ 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;
}
@@ -134,8 +154,8 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos)
return 0;
}
-static int checkout_stage(int stage, struct cache_entry *ce, int pos,
- struct checkout *state)
+static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
+ const struct checkout *state)
{
while (pos < active_nr &&
!strcmp(active_cache[pos]->name, ce->name)) {
@@ -149,15 +169,15 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos,
return error(_("path '%s' does not have their version"), ce->name);
}
-static int checkout_merged(int pos, struct checkout *state)
+static int checkout_merged(int pos, const struct checkout *state)
{
struct cache_entry *ce = active_cache[pos];
const char *path = ce->name;
mmfile_t ancestor, ours, theirs;
int status;
- unsigned char sha1[20];
+ struct object_id oid;
mmbuffer_t result_buf;
- unsigned char threeway[3][20];
+ struct object_id threeway[3];
unsigned mode = 0;
memset(threeway, 0, sizeof(threeway));
@@ -166,18 +186,18 @@ static int checkout_merged(int pos, struct checkout *state)
stage = ce_stage(ce);
if (!stage || strcmp(path, ce->name))
break;
- hashcpy(threeway[stage - 1], ce->sha1);
+ oidcpy(&threeway[stage - 1], &ce->oid);
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]))
+ if (is_null_oid(&threeway[1]) || is_null_oid(&threeway[2]))
return error(_("path '%s' does not have necessary versions"), path);
- read_mmblob(&ancestor, threeway[0]);
- read_mmblob(&ours, threeway[1]);
- read_mmblob(&theirs, threeway[2]);
+ read_mmblob(&ancestor, &threeway[0]);
+ read_mmblob(&ours, &threeway[1]);
+ read_mmblob(&theirs, &threeway[2]);
/*
* NEEDSWORK: re-create conflicts from merges with
@@ -206,9 +226,9 @@ static int checkout_merged(int pos, struct checkout *state)
* object database even when it may contain conflicts).
*/
if (write_sha1_file(result_buf.ptr, result_buf.size,
- blob_type, sha1))
+ blob_type, oid.hash))
die(_("Unable to add merge result for '%s'"), path);
- ce = make_cache_entry(mode, sha1, path, 2, 0);
+ ce = make_cache_entry(mode, oid.hash, path, 2, 0);
if (!ce)
die(_("make_cache_entry failed for path '%s'"), path);
status = checkout_entry(ce, state, NULL);
@@ -219,13 +239,11 @@ static int checkout_paths(const struct checkout_opts *opts,
const char *revision)
{
int pos;
- struct checkout state;
+ struct checkout state = CHECKOUT_INIT;
static char *ps_matched;
- unsigned char rev[20];
- int flag;
+ struct object_id rev;
struct commit *head;
int errs = 0;
- int newfd;
struct lock_file *lock_file;
if (opts->track != BRANCH_TRACK_UNSPECIFIED)
@@ -256,14 +274,14 @@ static int checkout_paths(const struct checkout_opts *opts,
lock_file = xcalloc(1, sizeof(struct lock_file));
- newfd = hold_locked_index(lock_file, 1);
+ hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
if (read_cache_preload(&opts->pathspec) < 0)
- return error(_("corrupt index file"));
+ return error(_("index file corrupt"));
if (opts->source_tree)
read_tree_some(opts->source_tree, &opts->pathspec);
- ps_matched = xcalloc(1, opts->pathspec.nr);
+ ps_matched = xcalloc(opts->pathspec.nr, 1);
/*
* Make sure all pathspecs participated in locating the paths
@@ -334,9 +352,9 @@ static int checkout_paths(const struct checkout_opts *opts,
return 1;
/* Now we are committed to check them out */
- 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 (ce->ce_flags & CE_MATCHED) {
@@ -352,12 +370,11 @@ static int checkout_paths(const struct checkout_opts *opts,
}
}
- 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"));
- read_ref_full("HEAD", rev, 0, &flag);
- head = lookup_commit_reference_gently(rev, 1);
+ read_ref_full("HEAD", 0, rev.hash, NULL);
+ head = lookup_commit_reference_gently(rev.hash, 1);
errs |= post_checkout_hook(head, head, 0);
return errs;
@@ -382,7 +399,7 @@ static void describe_detached_head(const char *msg, struct commit *commit)
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);
+ find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
strbuf_release(&sb);
}
@@ -399,7 +416,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
opts.reset = 1;
opts.merge = 1;
opts.fn = oneway_merge;
- opts.verbose_update = !o->quiet && isatty(2);
+ opts.verbose_update = o->show_progress;
opts.src_index = &the_index;
opts.dst_index = &the_index;
parse_tree(tree);
@@ -424,6 +441,11 @@ struct branch_info {
const char *name; /* The short name used */
const char *path; /* The full name of a real branch */
struct commit *commit; /* The named commit */
+ /*
+ * if not null the branch is detached because it's already
+ * checked out in this checkout
+ */
+ char *checkout;
};
static void setup_branch_path(struct branch_info *branch)
@@ -444,10 +466,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
{
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, LOCK_DIE_ON_ERROR);
if (read_cache_preload(NULL) < 0)
- return error(_("corrupt index file"));
+ return error(_("index file corrupt"));
resolve_undo_clear();
if (opts->force) {
@@ -478,7 +500,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
topts.update = 1;
topts.merge = 1;
topts.gently = opts->merge && old->commit;
- topts.verbose_update = !opts->quiet && isatty(2);
+ topts.verbose_update = opts->show_progress;
topts.fn = twoway_merge;
if (opts->overwrite_ignore) {
topts.dir = xcalloc(1, sizeof(*topts.dir));
@@ -486,10 +508,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
setup_standard_excludes(topts.dir);
}
tree = parse_tree_indirect(old->commit ?
- old->commit->object.sha1 :
+ old->commit->object.oid.hash :
EMPTY_TREE_SHA1_BIN);
init_tree_desc(&trees[0], tree->buffer, tree->size);
- tree = parse_tree_indirect(new->commit->object.sha1);
+ tree = parse_tree_indirect(new->commit->object.oid.hash);
init_tree_desc(&trees[1], tree->buffer, tree->size);
ret = unpack_trees(2, trees, &topts);
@@ -544,17 +566,25 @@ static int merge_working_tree(const struct checkout_opts *opts,
o.ancestor = old->name;
o.branch1 = new->name;
o.branch2 = "local";
- merge_trees(&o, new->commit->tree, work,
+ ret = merge_trees(&o, new->commit->tree, work,
old->commit->tree, &result);
+ if (ret < 0)
+ exit(128);
ret = reset_tree(new->commit->tree, opts, 0,
writeout_error);
+ strbuf_release(&o.obuf);
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)
@@ -582,24 +612,28 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
const char *old_desc, *reflog_msg;
if (opts->new_branch) {
if (opts->new_orphan_branch) {
- if (opts->new_branch_log && !log_all_ref_updates) {
- int temp;
- char log_file[PATH_MAX];
- char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
-
- temp = log_all_ref_updates;
- log_all_ref_updates = 1;
- if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
- fprintf(stderr, _("Can not do reflog for '%s'\n"),
- opts->new_orphan_branch);
- log_all_ref_updates = temp;
+ char *refname;
+
+ refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch);
+ if (opts->new_branch_log &&
+ !should_autocreate_reflog(refname)) {
+ int ret;
+ struct strbuf err = STRBUF_INIT;
+
+ ret = safe_create_reflog(refname, 1, &err);
+ if (ret) {
+ fprintf(stderr, _("Can not do reflog for '%s': %s\n"),
+ opts->new_orphan_branch, err.buf);
+ strbuf_release(&err);
+ free(refname);
return;
}
- log_all_ref_updates = temp;
+ strbuf_release(&err);
}
+ free(refname);
}
else
- create_branch(old->name, opts->new_branch, new->name,
+ create_branch(opts->new_branch, new->name,
opts->new_branch_force ? 1 : 0,
opts->new_branch_log,
opts->new_branch_force ? 1 : 0,
@@ -611,7 +645,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
old_desc = old->name;
if (!old_desc && old->commit)
- old_desc = sha1_to_hex(old->commit->object.sha1);
+ old_desc = oid_to_hex(&old->commit->object.oid);
reflog_msg = getenv("GIT_REFLOG_ACTION");
if (!reflog_msg)
@@ -623,15 +657,17 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
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);
+ update_ref(msg.buf, "HEAD", new->commit->object.oid.hash, NULL,
+ REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
if (!opts->quiet) {
- if (old->path && advice_detached_head)
+ if (old->path &&
+ advice_detached_head && !opts->force_detach)
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 (create_symref("HEAD", new->path, msg.buf) < 0)
+ die(_("unable to update HEAD"));
if (!opts->quiet) {
if (old->path && !strcmp(new->path, old->path)) {
if (opts->new_branch_force)
@@ -651,12 +687,8 @@ static void update_refs_for_switch(const 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();
@@ -667,18 +699,17 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
}
static int add_pending_uninteresting_ref(const char *refname,
- const unsigned char *sha1,
+ const struct object_id *oid,
int flags, void *cb_data)
{
- add_pending_sha1(cb_data, refname, sha1, UNINTERESTING);
+ add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING);
return 0;
}
static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
{
strbuf_addstr(sb, " ");
- strbuf_addstr(sb,
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+ strbuf_add_unique_abbrev(sb, commit->object.oid.hash, DEFAULT_ABBREV);
strbuf_addch(sb, ' ');
if (!parse_commit(commit))
pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
@@ -725,11 +756,18 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
if (advice_detached_head)
fprintf(stderr,
- _(
+ Q_(
+ /* The singular version */
+ "If you want to keep it by creating a new branch, "
+ "this may be a good time\nto do so with:\n\n"
+ " git branch <new-branch-name> %s\n\n",
+ /* The plural version */
"If you want to keep them by creating a new branch, "
"this may be a good time\nto do so with:\n\n"
- " git branch new_branch_name %s\n\n"),
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+ " git branch <new-branch-name> %s\n\n",
+ /* Give ngettext() the count */
+ lost),
+ find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
}
/*
@@ -747,10 +785,10 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new)
setup_revisions(0, NULL, &revs, NULL);
object->flags &= ~UNINTERESTING;
- add_pending_object(&revs, object, sha1_to_hex(object->sha1));
+ add_pending_object(&revs, object, oid_to_hex(&object->oid));
for_each_ref(add_pending_uninteresting_ref, &revs);
- add_pending_sha1(&revs, "HEAD", new->object.sha1, UNINTERESTING);
+ add_pending_sha1(&revs, "HEAD", new->object.oid.hash, UNINTERESTING);
refs = revs.pending;
revs.leak_pending = 1;
@@ -772,16 +810,16 @@ static int switch_branches(const struct checkout_opts *opts,
int ret = 0;
struct branch_info old;
void *path_to_free;
- unsigned char rev[20];
+ struct object_id rev;
int flag, writeout_error = 0;
memset(&old, 0, sizeof(old));
- old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag);
- old.commit = lookup_commit_reference_gently(rev, 1);
+ old.path = path_to_free = resolve_refdup("HEAD", 0, rev.hash, &flag);
+ old.commit = lookup_commit_reference_gently(rev.hash, 1);
if (!(flag & REF_ISSYMREF))
old.path = NULL;
- if (old.path && starts_with(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";
@@ -824,7 +862,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb)
struct tracking_name_data {
/* const */ char *src_ref;
char *dst_ref;
- unsigned char *dst_sha1;
+ struct object_id *dst_oid;
int unique;
};
@@ -835,7 +873,7 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
memset(&query, 0, sizeof(struct refspec));
query.src = cb->src_ref;
if (remote_find_tracking(remote, &query) ||
- get_sha1(query.dst, cb->dst_sha1)) {
+ get_oid(query.dst, cb->dst_oid)) {
free(query.dst);
return 0;
}
@@ -848,13 +886,13 @@ static int check_tracking_name(struct remote *remote, void *cb_data)
return 0;
}
-static const char *unique_tracking_name(const char *name, unsigned char *sha1)
+static const char *unique_tracking_name(const char *name, struct object_id *oid)
{
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;
+ cb_data.dst_oid = oid;
for_each_remote(check_tracking_name, &cb_data);
if (cb_data.unique)
return cb_data.dst_ref;
@@ -865,12 +903,13 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
struct branch_info *new,
- struct tree **source_tree,
- unsigned char rev[20],
- const char **new_branch)
+ struct checkout_opts *opts,
+ struct object_id *rev)
{
+ struct tree **source_tree = &opts->source_tree;
+ const char **new_branch = &opts->new_branch;
int argcount = 0;
- unsigned char branch_rev[20];
+ struct object_id branch_rev;
const char *arg;
int dash_dash_pos;
int has_dash_dash = 0;
@@ -895,7 +934,7 @@ static int parse_branchname_arg(int argc, const char **argv,
* 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 nor -b was given, and
+ * 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.
@@ -936,7 +975,7 @@ static int parse_branchname_arg(int argc, const char **argv,
if (!strcmp(arg, "-"))
arg = "@{-1}";
- if (get_sha1_mb(arg, rev)) {
+ if (get_oid_mb(arg, rev)) {
/*
* Either case (3) or (4), with <something> not being
* a commit, or an attempt to use case (1) with an
@@ -947,7 +986,8 @@ static int parse_branchname_arg(int argc, const char **argv,
*/
int recover_with_dwim = dwim_new_local_branch_ok;
- if (check_filename(NULL, arg) && !has_dash_dash)
+ if (!has_dash_dash &&
+ (check_filename(opts->prefix, arg) || !no_wildcard(arg)))
recover_with_dwim = 0;
/*
* Accept "git checkout foo" and "git checkout foo --"
@@ -984,15 +1024,15 @@ static int parse_branchname_arg(int argc, const char **argv,
setup_branch_path(new);
if (!check_refname_format(new->path, 0) &&
- !read_ref(new->path, branch_rev))
- hashcpy(rev, branch_rev);
+ !read_ref(new->path, branch_rev.hash))
+ oidcpy(rev, &branch_rev);
else
new->path = NULL; /* not an existing branch */
- new->commit = lookup_commit_reference_gently(rev, 1);
+ new->commit = lookup_commit_reference_gently(rev->hash, 1);
if (!new->commit) {
/* not a commit */
- *source_tree = parse_tree_indirect(rev);
+ *source_tree = parse_tree_indirect(rev->hash);
} else {
parse_commit_or_die(new->commit);
*source_tree = new->commit->tree;
@@ -1000,7 +1040,7 @@ static int parse_branchname_arg(int argc, const char **argv,
if (!*source_tree) /* case (1): want a tree */
die(_("reference is not a tree: %s"), arg);
- if (!has_dash_dash) {/* case (3).(d) -> (1) */
+ if (!has_dash_dash) { /* case (3).(d) -> (1) */
/*
* Do not complain the most common case
* git checkout branch
@@ -1008,7 +1048,7 @@ static int parse_branchname_arg(int argc, const char **argv,
* it would be extremely annoying.
*/
if (argc)
- verify_non_filename(NULL, arg);
+ verify_non_filename(opts->prefix, arg);
} else {
argcount++;
argv++;
@@ -1068,12 +1108,23 @@ static int checkout_branch(struct checkout_opts *opts,
die(_("Cannot switch branch to a non-commit '%s'"),
new->name);
+ if (new->path && !opts->force_detach && !opts->new_branch &&
+ !opts->ignore_other_worktrees) {
+ struct object_id oid;
+ int flag;
+ char *head_ref = resolve_refdup("HEAD", 0, oid.hash, &flag);
+ if (head_ref &&
+ (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
+ die_if_checked_out(new->path, 1);
+ free(head_ref);
+ }
+
if (!new->commit && opts->new_branch) {
- unsigned char rev[20];
+ struct object_id rev;
int flag;
- if (!read_ref_full("HEAD", rev, 0, &flag) &&
- (flag & REF_ISSYMREF) && is_null_sha1(rev))
+ if (!read_ref_full("HEAD", 0, rev.hash, &flag) &&
+ (flag & REF_ISSYMREF) && is_null_oid(&rev))
return switch_unborn_to_new_branch(opts);
}
return switch_branches(opts, new);
@@ -1092,10 +1143,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
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_BOOL(0, "detach", &opts.force_detach, N_("detach HEAD at named commit")),
OPT_SET_INT('t', "track", &opts.track, N_("set upstream info for new branch"),
BRANCH_TRACK_EXPLICIT),
- OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new branch"), N_("new unparented branch")),
+ 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, N_("checkout their version for unmerged files"),
@@ -1109,7 +1160,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
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'")),
+ N_("second guess 'git checkout <no-such-branch>'")),
+ OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees,
+ N_("do not check if another worktree is holding the given ref")),
+ OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
OPT_END(),
};
@@ -1117,6 +1171,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
memset(&new, 0, sizeof(new));
opts.overwrite_ignore = 1;
opts.prefix = prefix;
+ opts.show_progress = -1;
gitmodules_config();
git_config(git_checkout_config, &opts);
@@ -1126,6 +1181,13 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
+ if (opts.show_progress < 0) {
+ if (opts.quiet)
+ opts.show_progress = 0;
+ else
+ opts.show_progress = isatty(2);
+ }
+
if (conflict_style) {
opts.merge = 1; /* implied */
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
@@ -1150,10 +1212,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
const char *argv0 = argv[0];
if (!argc || !strcmp(argv0, "--"))
die (_("--track needs a branch name"));
- if (starts_with(argv0, "refs/"))
- argv0 += 5;
- if (starts_with(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"));
@@ -1174,15 +1234,14 @@ 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];
+ struct object_id rev;
int dwim_ok =
!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, &opts.source_tree,
- rev, &opts.new_branch);
+ &new, &opts, &rev);
argv += n;
argc -= n;
}
diff --git a/builtin/clean.c b/builtin/clean.c
index 114d7bf879..d861f836a2 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -10,7 +10,6 @@
#include "cache.h"
#include "dir.h"
#include "parse-options.h"
-#include "refs.h"
#include "string-list.h"
#include "quote.h"
#include "column.h"
@@ -48,7 +47,7 @@ enum color_clean {
CLEAN_COLOR_PROMPT = 2,
CLEAN_COLOR_HEADER = 3,
CLEAN_COLOR_HELP = 4,
- CLEAN_COLOR_ERROR = 5,
+ CLEAN_COLOR_ERROR = 5
};
#define MENU_OPTS_SINGLETON 01
@@ -67,7 +66,7 @@ struct menu_item {
char hotkey;
const char *title;
int selected;
- int (*fn)();
+ int (*fn)(void);
};
enum menu_stuff_type {
@@ -100,6 +99,8 @@ static int parse_clean_color_slot(const char *var)
static int git_clean_config(const char *var, const char *value, void *cb)
{
+ const char *slot_name;
+
if (starts_with(var, "column."))
return git_column_config(var, value, "clean", &colopts);
@@ -109,15 +110,13 @@ static int git_clean_config(const char *var, const char *value, void *cb)
clean_use_color = git_config_colorbool(var, value);
return 0;
}
- if (starts_with(var, "color.interactive.")) {
- int slot = parse_clean_color_slot(var +
- strlen("color.interactive."));
+ if (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);
- color_parse(value, var, clean_colors[slot]);
- return 0;
+ return color_parse(value, clean_colors[slot]);
}
if (!strcmp(var, "clean.requireforce")) {
@@ -155,13 +154,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
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 ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) {
if (!quiet) {
quote_path_relative(path->buf, prefix, &quoted);
printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
@@ -177,15 +174,16 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
/* an empty dir could be removed even if it is unreadble */
res = dry_run ? 0 : rmdir(path->buf);
if (res) {
+ int saved_errno = errno;
quote_path_relative(path->buf, prefix, &quoted);
- warning(_(msg_warn_remove_failed), quoted.buf);
+ errno = saved_errno;
+ warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
}
return res;
}
- if (path->buf[original_len - 1] != '/')
- strbuf_addch(path, '/');
+ strbuf_complete(path, '/');
len = path->len;
while ((e = readdir(dir)) != NULL) {
@@ -212,8 +210,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
quote_path_relative(path->buf, prefix, &quoted);
string_list_append(&dels, quoted.buf);
} else {
+ int saved_errno = errno;
quote_path_relative(path->buf, prefix, &quoted);
- warning(_(msg_warn_remove_failed), quoted.buf);
+ errno = saved_errno;
+ warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = 1;
}
@@ -234,8 +234,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if (!res)
*dir_gone = 1;
else {
+ int saved_errno = errno;
quote_path_relative(path->buf, prefix, &quoted);
- warning(_(msg_warn_remove_failed), quoted.buf);
+ errno = saved_errno;
+ warning_errno(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = 1;
}
@@ -291,11 +293,11 @@ static void pretty_print_menus(struct string_list *menu_list)
static void prompt_help_cmd(int singleton)
{
clean_print_color(CLEAN_COLOR_HELP);
- printf_ln(singleton ?
+ printf(singleton ?
_("Prompt help:\n"
"1 - select a numbered item\n"
"foo - select item based on unique prefix\n"
- " - (empty) select nothing") :
+ " - (empty) select nothing\n") :
_("Prompt help:\n"
"1 - select a single item\n"
"3-5 - select a range of items\n"
@@ -303,7 +305,7 @@ static void prompt_help_cmd(int singleton)
"foo - select item based on unique prefix\n"
"-... - unselect specified items\n"
"* - choose all items\n"
- " - (empty) finish selecting"));
+ " - (empty) finish selecting\n"));
clean_print_color(CLEAN_COLOR_RESET);
}
@@ -314,14 +316,13 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
{
struct string_list menu_list = STRING_LIST_INIT_DUP;
struct strbuf menu = STRBUF_INIT;
- struct strbuf buf = STRBUF_INIT;
struct menu_item *menu_item;
struct string_list_item *string_list_item;
int i;
switch (stuff->type) {
default:
- die("Bad type of menu_staff when print menu");
+ 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++) {
@@ -363,7 +364,6 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
pretty_print_menus(&menu_list);
strbuf_release(&menu);
- strbuf_release(&buf);
string_list_clear(&menu_list, 0);
}
@@ -514,7 +514,7 @@ static int parse_choice(struct menu_stuff *menu_stuff,
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);
+ printf(_("Huh (%s)?\n"), (*ptr)->buf);
clean_print_color(CLEAN_COLOR_RESET);
continue;
}
@@ -549,7 +549,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
int eof = 0;
int i;
- chosen = xmalloc(sizeof(int) * stuff->nr);
+ ALLOC_ARRAY(chosen, stuff->nr);
/* set chosen as uninitialized */
for (i = 0; i < stuff->nr; i++)
chosen[i] = -1;
@@ -576,7 +576,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
clean_get_color(CLEAN_COLOR_RESET));
}
- if (strbuf_getline(&choice, stdin, '\n') != EOF) {
+ if (strbuf_getline_lf(&choice, stdin) != EOF) {
strbuf_trim(&choice);
} else {
eof = 1;
@@ -621,8 +621,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
nr += chosen[i];
}
- result = xmalloc(sizeof(int) * (nr + 1));
- memset(result, 0, sizeof(int) * (nr + 1));
+ result = xcalloc(st_add(nr, 1), sizeof(int));
for (i = 0; i < stuff->nr && j < nr; i++) {
if (chosen[i])
result[j++] = i;
@@ -659,7 +658,7 @@ static int filter_by_patterns_cmd(void)
clean_print_color(CLEAN_COLOR_PROMPT);
printf(_("Input ignore patterns>> "));
clean_print_color(CLEAN_COLOR_RESET);
- if (strbuf_getline(&confirm, stdin, '\n') != EOF)
+ if (strbuf_getline_lf(&confirm, stdin) != EOF)
strbuf_trim(&confirm);
else
putchar('\n');
@@ -755,8 +754,9 @@ static int ask_each_cmd(void)
/* 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) {
+ /* TRANSLATORS: Make sure to keep [y/N] as is */
+ printf(_("Remove %s [y/N]? "), qname);
+ if (strbuf_getline_lf(&confirm, stdin) != EOF) {
strbuf_trim(&confirm);
} else {
putchar('\n');
@@ -780,7 +780,7 @@ static int ask_each_cmd(void)
static int quit_cmd(void)
{
string_list_clear(&del_list, 0);
- printf_ln(_("Bye."));
+ printf(_("Bye.\n"));
return MENU_RETURN_NO_LOOP;
}
@@ -904,11 +904,11 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (!interactive && !dry_run && !force) {
if (config_set)
- die(_("clean.requireForce set to true and neither -i, -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 -i, -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)
@@ -941,23 +941,21 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (!cache_name_is_other(ent->name, ent->len))
continue;
+ if (pathspec.nr)
+ matches = dir_path_match(ent, &pathspec, 0, NULL);
+
+ if (pathspec.nr && !matches)
+ continue;
+
if (lstat(ent->name, &st))
die_errno("Cannot lstat '%s'", ent->name);
- if (pathspec.nr)
- matches = dir_path_match(ent, &pathspec, 0, NULL);
+ if (S_ISDIR(st.st_mode) && !remove_directories &&
+ matches != MATCHED_EXACTLY)
+ continue;
- if (S_ISDIR(st.st_mode)) {
- if (remove_directories || (matches == MATCHED_EXACTLY)) {
- rel = relative_path(ent->name, prefix, &buf);
- string_list_append(&del_list, rel);
- }
- } else {
- if (pathspec.nr && !matches)
- continue;
- rel = relative_path(ent->name, prefix, &buf);
- string_list_append(&del_list, rel);
- }
+ rel = relative_path(ent->name, prefix, &buf);
+ string_list_append(&del_list, rel);
}
if (interactive && del_list.nr > 0)
@@ -989,8 +987,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
} else {
res = dry_run ? 0 : unlink(abs_path.buf);
if (res) {
+ int saved_errno = errno;
qname = quote_path_relative(item->string, NULL, &buf);
- warning(_(msg_warn_remove_failed), qname);
+ errno = saved_errno;
+ warning_errno(_(msg_warn_remove_failed), qname);
errors++;
} else if (!quiet) {
qname = quote_path_relative(item->string, NULL, &buf);
diff --git a/builtin/clone.c b/builtin/clone.c
index 43e772ccdb..3f63edbbf9 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"
@@ -33,30 +34,28 @@
*
*/
static const char * const builtin_clone_usage[] = {
- N_("git clone [options] [--] <repo> [<dir>]"),
+ N_("git clone [<options>] [--] <repo> [<dir>]"),
NULL
};
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 int option_shallow_submodules;
+static int deepen;
+static char *option_template, *option_depth, *option_since;
static char *option_origin = NULL;
static char *option_branch = NULL;
+static struct string_list option_not = STRING_LIST_INIT_NODUP;
static const char *real_git_dir;
static char *option_upload_pack = "git-upload-pack";
static int option_verbosity;
static int option_progress = -1;
-static struct string_list option_config;
-static struct string_list option_reference;
-
-static int opt_parse_reference(const struct option *opt, const char *arg, int unset)
-{
- struct string_list *option_reference = opt->value;
- if (!arg)
- return -1;
- string_list_append(option_reference, arg);
- return 0;
-}
+static enum transport_family family;
+static struct string_list option_config = STRING_LIST_INIT_NODUP;
+static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
+static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
+static int option_dissociate;
+static int max_jobs = -1;
static struct option builtin_clone_options[] = {
OPT__VERBOSITY(&option_verbosity),
@@ -79,10 +78,16 @@ static struct option builtin_clone_options[] = {
N_("initialize submodules in the clone")),
OPT_BOOL(0, "recurse-submodules", &option_recursive,
N_("initialize submodules in the clone")),
+ OPT_INTEGER('j', "jobs", &max_jobs,
+ N_("number of submodules cloned in parallel")),
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_LIST(0, "reference", &option_required_reference, N_("repo"),
+ N_("reference repository")),
+ OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference,
+ N_("repo"), N_("reference repository")),
+ OPT_BOOL(0, "dissociate", &option_dissociate,
+ N_("use --reference only while cloning")),
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"),
@@ -91,107 +96,164 @@ static struct option builtin_clone_options[] = {
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_STRING(0, "shallow-since", &option_since, N_("time"),
+ N_("create a shallow clone since a specific time")),
+ OPT_STRING_LIST(0, "shallow-exclude", &option_not, N_("revision"),
+ N_("deepen history of shallow clone, excluding rev")),
OPT_BOOL(0, "single-branch", &option_single_branch,
N_("clone only one branch, HEAD or --branch")),
+ OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules,
+ N_("any cloned submodules will be shallow")),
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_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
+ TRANSPORT_FAMILY_IPV4),
+ OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
+ TRANSPORT_FAMILY_IPV6),
OPT_END()
};
-static const char *argv_submodule[] = {
- "submodule", "update", "--init", "--recursive", NULL
-};
-
-static char *get_repo_path(const char *repo, int *is_bundle)
+static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
{
static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
static char *bundle_suffix[] = { ".bundle", "" };
+ size_t baselen = path->len;
struct stat st;
int i;
for (i = 0; i < ARRAY_SIZE(suffix); i++) {
- const char *path;
- path = mkpath("%s%s", repo, suffix[i]);
- if (stat(path, &st))
+ strbuf_setlen(path, baselen);
+ strbuf_addstr(path, suffix[i]);
+ if (stat(path->buf, &st))
continue;
- if (S_ISDIR(st.st_mode) && is_git_directory(path)) {
+ if (S_ISDIR(st.st_mode) && is_git_directory(path->buf)) {
*is_bundle = 0;
- return xstrdup(absolute_path(path));
+ return path->buf;
} else if (S_ISREG(st.st_mode) && st.st_size > 8) {
/* Is it a "gitfile"? */
char signature[8];
- int len, fd = open(path, O_RDONLY);
+ const char *dst;
+ int len, fd = open(path->buf, O_RDONLY);
if (fd < 0)
continue;
len = read_in_full(fd, signature, 8);
close(fd);
if (len != 8 || strncmp(signature, "gitdir: ", 8))
continue;
- path = read_gitfile(path);
- if (path) {
+ dst = read_gitfile(path->buf);
+ if (dst) {
*is_bundle = 0;
- return xstrdup(absolute_path(path));
+ return dst;
}
}
}
for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
- const char *path;
- path = mkpath("%s%s", repo, bundle_suffix[i]);
- if (!stat(path, &st) && S_ISREG(st.st_mode)) {
+ strbuf_setlen(path, baselen);
+ strbuf_addstr(path, bundle_suffix[i]);
+ if (!stat(path->buf, &st) && S_ISREG(st.st_mode)) {
*is_bundle = 1;
- return xstrdup(absolute_path(path));
+ return path->buf;
}
}
return NULL;
}
+static char *get_repo_path(const char *repo, int *is_bundle)
+{
+ struct strbuf path = STRBUF_INIT;
+ const char *raw;
+ char *canon;
+
+ strbuf_addstr(&path, repo);
+ raw = get_repo_path_1(&path, is_bundle);
+ canon = raw ? absolute_pathdup(raw) : NULL;
+ strbuf_release(&path);
+ return canon;
+}
+
static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
{
- const char *end = repo + strlen(repo), *start;
+ const char *end = repo + strlen(repo), *start, *ptr;
+ size_t len;
char *dir;
/*
+ * Skip scheme.
+ */
+ start = strstr(repo, "://");
+ if (start == NULL)
+ start = repo;
+ else
+ start += 3;
+
+ /*
+ * Skip authentication data. The stripping does happen
+ * greedily, such that we strip up to the last '@' inside
+ * the host part.
+ */
+ for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) {
+ if (*ptr == '@')
+ start = ptr + 1;
+ }
+
+ /*
* Strip trailing spaces, slashes and /.git
*/
- while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
+ while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
end--;
- if (end - repo > 5 && is_dir_sep(end[-5]) &&
+ if (end - start > 5 && is_dir_sep(end[-5]) &&
!strncmp(end - 4, ".git", 4)) {
end -= 5;
- while (repo < end && is_dir_sep(end[-1]))
+ while (start < end && is_dir_sep(end[-1]))
end--;
}
/*
- * Find last component, but be prepared that repo could have
- * the form "remote.example.com:foo.git", i.e. no slash
- * in the directory part.
+ * Strip trailing port number if we've got only a
+ * hostname (that is, there is no dir separator but a
+ * colon). This check is required such that we do not
+ * strip URI's like '/foo/bar:2222.git', which should
+ * result in a dir '2222' being guessed due to backwards
+ * compatibility.
*/
- start = end;
- while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':')
- start--;
+ if (memchr(start, '/', end - start) == NULL
+ && memchr(start, ':', end - start) != NULL) {
+ ptr = end;
+ while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':')
+ ptr--;
+ if (start < ptr && ptr[-1] == ':')
+ end = ptr - 1;
+ }
+
+ /*
+ * Find last component. To remain backwards compatible we
+ * also regard colons as path separators, such that
+ * cloning a repository 'foo:bar.git' would result in a
+ * directory 'bar' being guessed.
+ */
+ ptr = end;
+ while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':')
+ ptr--;
+ start = ptr;
/*
* Strip .{bundle,git}.
*/
- if (is_bundle) {
- if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
- end -= 7;
- } else {
- if (end - start > 4 && !strncmp(end - 4, ".git", 4))
- end -= 4;
- }
+ len = end - start;
+ strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
- if (is_bare) {
- struct strbuf result = STRBUF_INIT;
- strbuf_addf(&result, "%.*s.git", (int)(end - start), start);
- dir = strbuf_detach(&result, NULL);
- } else
- dir = xstrndup(start, end - start);
+ if (!len || (len == 1 && *start == '/'))
+ die(_("No directory name could be guessed.\n"
+ "Please specify a directory on the command line"));
+
+ if (is_bare)
+ dir = xstrfmt("%.*s.git", (int)len, start);
+ else
+ dir = xstrndup(start, len);
/*
* Replace sequences of 'control' characters and whitespace
* with one ascii space, remove leading and trailing spaces.
@@ -229,45 +291,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;
-
- /* Beware: read_gitfile(), real_path() and mkpath() return static buffer */
- ref_git = xstrdup(real_path(item->string));
-
- 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 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);
+ struct strbuf err = STRBUF_INIT;
+ int *required = cb_data;
+ char *ref_git = compute_alternate_path(item->string, &err);
+
+ if (!ref_git) {
+ if (*required)
+ die("%s", err.buf);
+ else
+ fprintf(stderr,
+ _("info: Could not add alternate for '%s': %s\n"),
+ item->string, err.buf);
+ } else {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addf(&sb, "%s/objects", ref_git);
+ add_to_alternates_file(sb.buf);
+ strbuf_release(&sb);
+ }
+
+ strbuf_release(&err);
free(ref_git);
return 0;
}
static void setup_reference(void)
{
- for_each_string_list(&option_reference, add_one_reference, NULL);
+ int required = 1;
+ for_each_string_list(&option_required_reference,
+ add_one_reference, &required);
+ required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_reference, &required);
}
static void copy_alternates(struct strbuf *src, struct strbuf *dst,
@@ -288,17 +342,21 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst,
FILE *in = fopen(src->buf, "r");
struct strbuf line = STRBUF_INIT;
- while (strbuf_getline(&line, in, '\n') != EOF) {
- char *abs_path, abs_buf[PATH_MAX];
+ while (strbuf_getline(&line, in) != EOF) {
+ char *abs_path;
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
add_to_alternates_file(line.buf);
continue;
}
- abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
- normalize_path_copy(abs_buf, abs_path);
- add_to_alternates_file(abs_buf);
+ abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
+ if (!normalize_path_copy(abs_path, abs_path))
+ add_to_alternates_file(abs_path);
+ else
+ warning("skipping invalid relative alternate: %s/%s",
+ src_repo, line.buf);
+ free(abs_path);
}
strbuf_release(&line);
fclose(in);
@@ -377,8 +435,10 @@ static void clone_local(const char *src_repo, const char *dest_repo)
} else {
struct strbuf src = STRBUF_INIT;
struct strbuf dest = STRBUF_INIT;
- strbuf_addf(&src, "%s/objects", src_repo);
- strbuf_addf(&dest, "%s/objects", dest_repo);
+ get_common_dir(&src, src_repo);
+ get_common_dir(&dest, dest_repo);
+ strbuf_addstr(&src, "/objects");
+ strbuf_addstr(&dest, "/objects");
copy_or_link_directory(&src, &dest, src_repo, src.len);
strbuf_release(&src);
strbuf_release(&dest);
@@ -390,7 +450,6 @@ static void clone_local(const char *src_repo, const char *dest_repo)
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,
@@ -417,8 +476,6 @@ static void remove_junk(void)
break;
}
- if (getpid() != junk_pid)
- return;
if (junk_git_dir) {
strbuf_addstr(&sb, junk_git_dir);
remove_dir_recursively(&sb, 0);
@@ -498,16 +555,26 @@ static void write_remote_refs(const struct ref *local_refs)
{
const struct ref *r;
- lock_packed_refs(LOCK_DIE_ON_ERROR);
+ struct ref_transaction *t;
+ struct strbuf err = STRBUF_INIT;
+
+ t = ref_transaction_begin(&err);
+ if (!t)
+ die("%s", err.buf);
for (r = local_refs; r; r = r->next) {
if (!r->peer_ref)
continue;
- add_packed_ref(r->peer_ref->name, r->old_sha1);
+ if (ref_transaction_create(t, r->peer_ref->name, r->old_oid.hash,
+ 0, NULL, &err))
+ die("%s", err.buf);
}
- if (commit_packed_refs())
- die_errno("unable to overwrite old ref-pack file");
+ if (initial_ref_transaction_commit(t, &err))
+ die("%s", err.buf);
+
+ strbuf_release(&err);
+ ref_transaction_free(t);
}
static void write_followtags(const struct ref *refs, const char *msg)
@@ -518,10 +585,10 @@ static void write_followtags(const struct ref *refs, const char *msg)
continue;
if (ends_with(ref->name, "^{}"))
continue;
- if (!has_sha1_file(ref->old_sha1))
+ if (!has_object_file(&ref->old_oid))
continue;
- update_ref(msg, ref->name, ref->old_sha1,
- NULL, 0, DIE_ON_ERR);
+ update_ref(msg, ref->name, ref->old_oid.hash,
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
}
}
@@ -540,7 +607,7 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
if (!ref)
return -1;
- hashcpy(sha1, ref->old_sha1);
+ hashcpy(sha1, ref->old_oid.hash);
*rm = ref->next;
return 0;
}
@@ -556,13 +623,13 @@ static void update_remote_refs(const struct ref *refs,
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))
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
+
+ opt.transport = transport;
+ opt.progress = transport->progress;
+
+ if (check_connected(iterate_ref_map, &rm, &opt))
die(_("remote did not send all necessary objects"));
- if (transport->progress)
- fprintf(stderr, _("done.\n"));
}
if (refs) {
@@ -575,40 +642,44 @@ static void update_remote_refs(const struct ref *refs,
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);
+ if (create_symref(head_ref.buf,
+ remote_head_points_at->peer_ref->name,
+ msg) < 0)
+ die(_("unable to update %s"), head_ref.buf);
+ strbuf_release(&head_ref);
}
}
static void update_head(const struct ref *our, const struct ref *remote,
const char *msg)
{
- if (our && starts_with(our->name, "refs/heads/")) {
+ const char *head;
+ if (our && skip_prefix(our->name, "refs/heads/", &head)) {
/* Local default branch link */
- create_symref("HEAD", our->name, NULL);
+ if (create_symref("HEAD", our->name, NULL) < 0)
+ die(_("unable to update HEAD"));
if (!option_bare) {
- const char *head = skip_prefix(our->name, "refs/heads/");
- update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+ update_ref(msg, "HEAD", our->old_oid.hash, 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);
+ struct commit *c = lookup_commit_reference(our->old_oid.hash);
/* --branch specifies a non-branch (i.e. tags), detach HEAD */
- update_ref(msg, "HEAD", c->object.sha1,
- NULL, REF_NODEREF, DIE_ON_ERR);
+ update_ref(msg, "HEAD", c->object.oid.hash,
+ 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, DIE_ON_ERR);
+ update_ref(msg, "HEAD", remote->old_oid.hash,
+ NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
}
}
-static int checkout(void)
+static int checkout(int submodule_progress)
{
unsigned char sha1[20];
char *head;
@@ -616,12 +687,12 @@ static int checkout(void)
struct unpack_trees_options opts;
struct tree *tree;
struct tree_desc t;
- int err = 0, fd;
+ int err = 0;
if (option_no_checkout)
return 0;
- head = resolve_refdup("HEAD", sha1, 1, NULL);
+ head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL);
if (!head) {
warning(_("remote HEAD refers to nonexistent ref, "
"unable to checkout.\n"));
@@ -640,7 +711,7 @@ static int checkout(void)
setup_work_tree();
lock_file = xcalloc(1, sizeof(struct lock_file));
- fd = hold_locked_index(lock_file, 1);
+ hold_locked_index(lock_file, LOCK_DIE_ON_ERROR);
memset(&opts, 0, sizeof opts);
opts.update = 1;
@@ -656,22 +727,35 @@ static int checkout(void)
if (unpack_trees(1, &t, &opts) < 0)
die(_("unable to checkout working tree"));
- if (write_cache(fd, 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"));
- err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
- sha1_to_hex(sha1), "1", NULL);
+ err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
+ sha1_to_hex(sha1), "1", NULL);
+
+ if (!err && option_recursive) {
+ struct argv_array args = ARGV_ARRAY_INIT;
+ argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
+
+ if (option_shallow_submodules == 1)
+ argv_array_push(&args, "--depth=1");
- if (!err && option_recursive)
- err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
+ if (max_jobs != -1)
+ argv_array_pushf(&args, "--jobs=%d", max_jobs);
+
+ if (submodule_progress)
+ argv_array_push(&args, "--progress");
+
+ err = run_command_v_opt(args.argv, RUN_GIT_CMD);
+ argv_array_clear(&args);
+ }
return err;
}
static int write_one_config(const char *key, const char *value, void *data)
{
- return git_config_set_multivar(key, value ? value : "true", "^$", 0);
+ return git_config_set_multivar_gently(key, value ? value : "true", "^$", 0);
}
static void write_config(struct string_list *config)
@@ -681,13 +765,14 @@ static void write_config(struct string_list *config)
for (i = 0; i < config->nr; i++) {
if (git_config_parse_parameter(config->items[i].string,
write_one_config, NULL) < 0)
- die("unable to write parameters to config file");
+ die(_("unable to write parameters to config file"));
}
}
-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)
+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;
@@ -695,16 +780,19 @@ static void write_refspec_config(const char* src_ref_prefix,
if (option_mirror || !option_bare) {
if (option_single_branch && !option_mirror) {
if (option_branch) {
- if (strstr(our_head_points_at->name, "refs/tags/"))
+ 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,
- skip_prefix(remote_head_points_at->name, "refs/heads/"));
+ branch_top->buf, head);
}
/*
* otherwise, the next "git fetch" will
@@ -733,6 +821,20 @@ static void write_refspec_config(const char* src_ref_prefix,
strbuf_release(&value);
}
+static void dissociate_from_references(void)
+{
+ static const char* argv[] = { "repack", "-a", "-d", NULL };
+ char *alternates = git_pathdup("objects/info/alternates");
+
+ if (!access(alternates, F_OK)) {
+ if (run_command_v_opt(argv, RUN_GIT_CMD|RUN_COMMAND_NO_STDIN))
+ die(_("cannot repack to clean up"));
+ if (unlink(alternates) && errno != ENOENT)
+ die_errno(_("cannot unlink temporary alternates file"));
+ }
+ free(alternates);
+}
+
int cmd_clone(int argc, const char **argv, const char *prefix)
{
int is_bundle = 0, is_local;
@@ -751,12 +853,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
const char *src_ref_prefix = "refs/heads/";
struct remote *remote;
int err = 0, complete_refs_before_fetch = 1;
+ int submodule_progress;
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);
@@ -769,8 +870,10 @@ 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_depth || option_since || option_not.nr)
+ deepen = 1;
if (option_single_branch == -1)
- option_single_branch = option_depth ? 1 : 0;
+ option_single_branch = deepen ? 1 : 0;
if (option_mirror)
option_bare = 1;
@@ -791,23 +894,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
path = get_repo_path(repo_name, &is_bundle);
if (path)
- repo = xstrdup(absolute_path(repo_name));
+ repo = absolute_pathdup(repo_name);
else if (!strchr(repo_name, ':'))
die(_("repository '%s' does not exist"), repo_name);
else
repo = repo_name;
- is_local = option_local != 0 && path && !is_bundle;
- if (is_local) {
- if (option_depth)
- warning(_("--depth is ignored in local clones; use file:// instead."));
- if (!access(mkpath("%s/shallow", path), F_OK)) {
- if (option_local > 0)
- warning(_("source repository is shallow, ignoring --local"));
- is_local = 0;
- }
- }
- if (option_local > 0 && !is_local)
- warning(_("--local is ignored"));
/* no need to be strict, transport_set_option() will validate it again */
if (option_depth && atoi(option_depth) < 1)
@@ -841,36 +932,54 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
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, 0777))
- die_errno(_("could not create work tree dir '%s'."),
+ 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);
+ junk_git_dir = real_git_dir ? real_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) {
- git_dir = real_git_dir;
- junk_git_dir = real_git_dir;
- }
-
if (0 <= option_verbosity) {
if (option_bare)
fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir);
else
fprintf(stderr, _("Cloning into '%s'...\n"), dir);
}
- init_db(option_template, INIT_DB_QUIET);
+
+ if (option_recursive) {
+ if (option_required_reference.nr &&
+ option_optional_reference.nr)
+ die(_("clone --recursive is not compatible with "
+ "both --reference and --reference-if-able"));
+ else if (option_required_reference.nr) {
+ string_list_append(&option_config,
+ "submodule.alternateLocation=superproject");
+ string_list_append(&option_config,
+ "submodule.alternateErrorStrategy=die");
+ } else if (option_optional_reference.nr) {
+ string_list_append(&option_config,
+ "submodule.alternateLocation=superproject");
+ string_list_append(&option_config,
+ "submodule.alternateErrorStrategy=info");
+ }
+ }
+
+ init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET);
+
+ if (real_git_dir)
+ git_dir = real_git_dir;
+
write_config(&option_config);
git_config(git_default_config, NULL);
@@ -890,7 +999,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
git_config_set(key.buf, repo);
strbuf_reset(&key);
- if (option_reference.nr)
+ if (option_required_reference.nr || option_optional_reference.nr)
setup_reference();
fetch_pattern = value.buf;
@@ -900,6 +1009,26 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
remote = remote_get(option_origin);
transport = transport_get(remote, remote->url[0]);
+ transport_set_verbosity(transport, option_verbosity, option_progress);
+ transport->family = family;
+
+ path = get_repo_path(remote->url[0], &is_bundle);
+ is_local = option_local != 0 && path && !is_bundle;
+ if (is_local) {
+ if (option_depth)
+ warning(_("--depth is ignored in local clones; use file:// instead."));
+ if (option_since)
+ warning(_("--shallow-since is ignored in local clones; use file:// instead."));
+ if (option_not.nr)
+ warning(_("--shallow-exclude 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 || (!is_local && !transport->fetch))
@@ -910,16 +1039,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_depth)
transport_set_option(transport, TRANS_OPT_DEPTH,
option_depth);
+ if (option_since)
+ transport_set_option(transport, TRANS_OPT_DEEPEN_SINCE,
+ option_since);
+ if (option_not.nr)
+ transport_set_option(transport, TRANS_OPT_DEEPEN_NOT,
+ (const char *)&option_not);
if (option_single_branch)
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
- transport_set_verbosity(transport, option_verbosity, option_progress);
-
if (option_upload_pack)
transport_set_option(transport, TRANS_OPT_UPLOADPACK,
option_upload_pack);
- if (transport->smart_options && !option_depth)
+ if (transport->smart_options && !deepen)
transport->smart_options->check_self_contained_and_connected = 1;
refs = transport_get_remote_refs(transport);
@@ -937,7 +1070,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
* remote HEAD check.
*/
for (ref = refs; ref; ref = ref->next)
- if (is_null_sha1(ref->old_sha1)) {
+ if (is_null_oid(&ref->old_oid)) {
complete_refs_before_fetch = 0;
break;
}
@@ -989,16 +1122,31 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
update_head(our_head_points_at, remote_head, reflog_msg.buf);
+ /*
+ * We want to show progress for recursive submodule clones iff
+ * we did so for the main clone. But only the transport knows
+ * the final decision for this flag, so we need to rescue the value
+ * before we free the transport.
+ */
+ submodule_progress = transport->progress;
+
transport_unlock_pack(transport);
transport_disconnect(transport);
+ if (option_dissociate) {
+ close_all_packs();
+ dissociate_from_references();
+ }
+
junk_mode = JUNK_LEAVE_REPO;
- err = checkout();
+ err = checkout(submodule_progress);
strbuf_release(&reflog_msg);
strbuf_release(&branch_top);
strbuf_release(&key);
strbuf_release(&value);
junk_mode = JUNK_LEAVE_ALL;
+
+ free(refspec);
return err;
}
diff --git a/builtin/column.c b/builtin/column.c
index 75818520e1..33314b4d71 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -6,7 +6,7 @@
#include "column.h"
static const char * const builtin_column_usage[] = {
- N_("git column [options]"),
+ N_("git column [<options>]"),
NULL
};
static unsigned int colopts;
@@ -51,7 +51,7 @@ int cmd_column(int argc, const char **argv, const char *prefix)
die(_("--command must be the first argument"));
}
finalize_colopts(&colopts, -1);
- while (!strbuf_getline(&sb, stdin, '\n'))
+ while (!strbuf_getline(&sb, stdin))
string_list_append(&list, sb.buf);
print_columns(&list, colopts, &copts);
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index 987a4c3d73..605017261c 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -10,17 +10,17 @@
#include "utf8.h"
#include "gpg-interface.h"
-static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1> <changelog";
+static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1>";
static const char *sign_commit;
static void new_parent(struct commit *parent, struct commit_list **parents_p)
{
- unsigned char *sha1 = parent->object.sha1;
+ struct object_id *oid = &parent->object.oid;
struct commit_list *parents;
for (parents = *parents_p; parents; parents = parents->next) {
if (parents->item == parent) {
- error("duplicate parent %s ignored", sha1_to_hex(sha1));
+ error("duplicate parent %s ignored", oid_to_hex(oid));
return;
}
parents_p = &parents->next;
@@ -33,10 +33,6 @@ 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);
}
@@ -44,8 +40,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
{
int i, got_tree = 0;
struct commit_list *parents = NULL;
- unsigned char tree_sha1[20];
- unsigned char commit_sha1[20];
+ struct object_id tree_oid;
+ struct object_id commit_oid;
struct strbuf buffer = STRBUF_INIT;
git_config(commit_tree_config, NULL);
@@ -56,20 +52,18 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "-p")) {
- unsigned char sha1[20];
+ struct object_id oid;
if (argc <= ++i)
usage(commit_tree_usage);
- if (get_sha1_commit(argv[i], sha1))
+ if (get_sha1_commit(argv[i], oid.hash))
die("Not a valid object name %s", argv[i]);
- assert_sha1_type(sha1, OBJ_COMMIT);
- new_parent(lookup_commit(sha1), &parents);
+ assert_sha1_type(oid.hash, OBJ_COMMIT);
+ new_parent(lookup_commit(oid.hash), &parents);
continue;
}
- if (!memcmp(arg, "-S", 2)) {
- sign_commit = arg + 2;
+ if (skip_prefix(arg, "-S", &sign_commit))
continue;
- }
if (!strcmp(arg, "--no-gpg-sign")) {
sign_commit = NULL;
@@ -111,7 +105,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
continue;
}
- if (get_sha1_tree(arg, tree_sha1))
+ if (get_sha1_tree(arg, tree_oid.hash))
die("Not a valid object name %s", arg);
if (got_tree)
die("Cannot give more than one trees");
@@ -123,13 +117,13 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
die_errno("git commit-tree: failed to read");
}
- if (commit_tree(&buffer, tree_sha1, parents, commit_sha1,
- NULL, sign_commit)) {
+ if (commit_tree(buffer.buf, buffer.len, tree_oid.hash, parents,
+ commit_oid.hash, NULL, sign_commit)) {
strbuf_release(&buffer);
return 1;
}
- printf("%s\n", sha1_to_hex(commit_sha1));
+ printf("%s\n", oid_to_hex(&commit_oid));
strbuf_release(&buffer);
return 0;
}
diff --git a/builtin/commit.c b/builtin/commit.c
index 3783bcadcd..2de5f6cc64 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"
@@ -31,18 +32,32 @@
#include "sequencer.h"
#include "notes-utils.h"
#include "mailmap.h"
+#include "sigchain.h"
static const char * const builtin_commit_usage[] = {
- N_("git commit [options] [--] <pathspec>..."),
+ N_("git commit [<options>] [--] <pathspec>..."),
NULL
};
static const char * const builtin_status_usage[] = {
- N_("git status [options] [--] <pathspec>..."),
+ 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"
@@ -77,8 +92,9 @@ N_("If you wish to skip this commit, use:\n"
"Then \"git cherry-pick --continue\" will resume cherry-picking\n"
"the remaining commits.\n");
+static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
+
static const char *use_message_buffer;
-static const char commit_editmsg[] = "COMMIT_EDITMSG";
static struct lock_file index_lock; /* real index */
static struct lock_file false_lock; /* used only for partial commits */
static enum {
@@ -99,6 +115,7 @@ static char *fixup_message, *squash_message;
static int all, also, interactive, patch_interactive, only, amend, signoff;
static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int config_commit_verbose = -1; /* unspecified */
static int no_post_rewrite, allow_empty_message;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
static char *sign_commit;
@@ -113,6 +130,7 @@ static char *sign_commit;
static enum {
CLEANUP_SPACE,
CLEANUP_NONE,
+ CLEANUP_SCISSORS,
CLEANUP_ALL
} cleanup_mode;
static const char *cleanup_arg;
@@ -124,14 +142,24 @@ static int show_ignored_in_status, have_option_m;
static const char *only_include_assumed;
static struct strbuf message = STRBUF_INIT;
-static enum status_format {
- STATUS_FORMAT_NONE = 0,
- STATUS_FORMAT_LONG,
- STATUS_FORMAT_SHORT,
- STATUS_FORMAT_PORCELAIN,
+static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
- STATUS_FORMAT_UNSPECIFIED
-} status_format = STATUS_FORMAT_UNSPECIFIED;
+static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
+{
+ enum wt_status_format *value = (enum wt_status_format *)opt->value;
+ if (unset)
+ *value = STATUS_FORMAT_NONE;
+ else if (!arg)
+ *value = STATUS_FORMAT_PORCELAIN;
+ else if (!strcmp(arg, "v1") || !strcmp(arg, "1"))
+ *value = STATUS_FORMAT_PORCELAIN;
+ else if (!strcmp(arg, "v2") || !strcmp(arg, "2"))
+ *value = STATUS_FORMAT_PORCELAIN_V2;
+ else
+ die("unsupported porcelain version '%s'", arg);
+
+ return 0;
+}
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
{
@@ -151,11 +179,11 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
static void determine_whence(struct wt_status *s)
{
- if (file_exists(git_path("MERGE_HEAD")))
+ 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")))
+ if (file_exists(git_path_seq_dir()))
sequencer_in_use = 1;
}
else
@@ -170,6 +198,7 @@ static void status_init_config(struct wt_status *s, config_fn_t fn)
gitmodules_config();
git_config(fn, s);
determine_whence(s);
+ init_diff_ui_defaults();
s->hints = advice_status_hints; /* must come after git_config() */
}
@@ -214,7 +243,7 @@ static int commit_index_files(void)
static int list_paths(struct string_list *list, const char *with_tree,
const char *prefix, const struct pathspec *pattern)
{
- int i;
+ int i, ret;
char *m;
if (!pattern->nr)
@@ -241,7 +270,9 @@ static int list_paths(struct string_list *list, const char *with_tree,
item->util = item; /* better a valid pointer than a fake one */
}
- return report_path_error(m, pattern, prefix);
+ ret = report_path_error(m, pattern, prefix);
+ free(m);
+ return ret;
}
static void add_remove_files(struct string_list *list)
@@ -282,7 +313,7 @@ static void create_base_index(const struct commit *current_head)
opts.dst_index = &the_index;
opts.fn = oneway_merge;
- tree = parse_tree_indirect(current_head->object.sha1);
+ tree = parse_tree_indirect(current_head->object.oid.hash);
if (!tree)
die(_("failed to unpack HEAD tree object"));
parse_tree(tree);
@@ -301,13 +332,13 @@ 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;
struct pathspec pathspec;
int refresh_flags = REFRESH_QUIET;
+ const char *ret;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
@@ -320,16 +351,15 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
if (interactive) {
char *old_index_env = NULL;
- fd = hold_locked_index(&index_lock, 1);
+ hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
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, get_lock_file_path(&index_lock), 1);
if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
die(_("interactive add failed"));
@@ -340,10 +370,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(get_lock_file_path(&index_lock));
+ 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 get_lock_file_path(&index_lock);
}
/*
@@ -359,15 +396,14 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
* (B) on failure, rollback the real index.
*/
if (all || (also && pathspec.nr)) {
- fd = hold_locked_index(&index_lock, 1);
+ hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
refresh_cache_or_die(refresh_flags);
update_main_cache_tree(WRITE_TREE_SILENT);
- if (write_cache(fd, active_cache, active_nr) ||
- close_lock_file(&index_lock))
+ 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 get_lock_file_path(&index_lock);
}
/*
@@ -380,12 +416,14 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
* We still need to refresh the index here.
*/
if (!only && !pathspec.nr) {
- fd = hold_locked_index(&index_lock, 1);
+ hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
refresh_cache_or_die(refresh_flags);
- if (active_cache_changed) {
+ if (active_cache_changed
+ || !cache_tree_fully_valid(active_cache_tree))
update_main_cache_tree(WRITE_TREE_SILENT);
- if (write_cache(fd, active_cache, active_nr) ||
- commit_locked_index(&index_lock))
+ if (active_cache_changed) {
+ if (write_locked_index(&the_index, &index_lock,
+ COMMIT_LOCK))
die(_("unable to write new_index file"));
} else {
rollback_lock_file(&index_lock);
@@ -422,8 +460,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
die(_("cannot do a partial commit during a cherry-pick."));
}
- memset(&partial, 0, sizeof(partial));
- partial.strdup_strings = 1;
+ string_list_init(&partial, 1);
if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
exit(1);
@@ -431,30 +468,29 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
if (read_cache() < 0)
die(_("cannot read the index"));
- fd = hold_locked_index(&index_lock, 1);
+ hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
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);
-
- return false_lock.filename;
+ ret = get_lock_file_path(&false_lock);
+ read_cache_from(ret);
+ return ret;
}
static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
@@ -474,24 +510,13 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
s->fp = fp;
s->nowarn = nowarn;
s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+ if (!s->is_initial)
+ hashcpy(s->sha1_commit, sha1);
+ s->status_format = status_format;
+ s->ignore_submodule_arg = ignore_submodule_arg;
wt_status_collect(s);
-
- switch (status_format) {
- case STATUS_FORMAT_SHORT:
- wt_shortstatus_print(s);
- break;
- case STATUS_FORMAT_PORCELAIN:
- wt_porcelain_print(s);
- break;
- case STATUS_FORMAT_UNSPECIFIED:
- die("BUG: finalize_deferred_config() should have been called");
- break;
- case STATUS_FORMAT_NONE:
- case STATUS_FORMAT_LONG:
- wt_status_print(s);
- break;
- }
+ wt_status_print(s);
return s->commitable;
}
@@ -501,6 +526,12 @@ static int is_a_merge(const struct commit *current_head)
return !!(current_head->parents && current_head->parents->next);
}
+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;
@@ -511,18 +542,25 @@ static void export_one(const char *var, const char *s, const char *e, int hack)
strbuf_release(&buf);
}
-static int sane_ident_split(struct ident_split *person)
+static int parse_force_date(const char *in, struct strbuf *out)
{
- if (!person->name_begin || !person->name_end ||
- person->name_begin == person->name_end)
- return 0; /* no human readable name */
- if (!person->mail_begin || !person->mail_end ||
- person->mail_begin == person->mail_end)
- return 0; /* no usable mail */
- if (!person->date_begin || !person->date_end ||
- !person->tz_begin || !person->tz_end)
- return 0;
- return 1;
+ 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)
@@ -530,67 +568,93 @@ 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("<")));
- len = eol - (rb + strlen("> "));
- date = xmalloc(len + 2);
- *date = '@';
- memcpy(date + 1, rb + strlen("> "), len);
- date[len + 1] = '\0';
+ 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_STRICT));
- if (!split_ident_line(&author, author_ident->buf, author_ident->len) &&
- sane_ident_split(&author)) {
- 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, '@');
+ 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));
}
+
+ 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);
+}
+
+static int author_date_is_interesting(void)
+{
+ return author_message || force_date;
}
-static char *cut_ident_timestamp_part(char *string)
+static void adjust_comment_line_char(const struct strbuf *sb)
{
- char *ket = strrchr(string, '>');
- if (!ket || ket[1] != ' ')
- die(_("Malformed ident string: '%s'"), string);
- *++ket = '\0';
- return ket;
+ char candidates[] = "#;@!$%^&|:";
+ char *candidate;
+ const char *p;
+
+ comment_line_char = candidates[0];
+ if (!memchr(sb->buf, comment_line_char, sb->len))
+ return;
+
+ 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 = ' ';
+ }
+ }
+
+ 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,
@@ -610,7 +674,7 @@ static int prepare_to_commit(const char *index_file, const char *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) {
@@ -632,7 +696,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
}
}
- if (message.len) {
+ if (have_option_m) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
} else if (logfile && !strcmp(logfile, "-")) {
@@ -649,9 +713,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
} else if (use_message) {
char *buffer;
buffer = strstr(use_message_buffer, "\n\n");
- if (!use_editor && (!buffer || buffer[2] == '\0'))
- die(_("commit has empty message"));
- strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+ if (buffer)
+ strbuf_addstr(&sb, skip_blank_lines(buffer + 2));
hook_arg1 = "commit";
hook_arg2 = use_message;
} else if (fixup_message) {
@@ -664,12 +727,21 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
format_commit_message(commit, "fixup! %s\n\n",
&sb, &ctx);
hook_arg1 = "message";
- } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
- if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
+ } else if (!stat(git_path_merge_msg(), &statbuf)) {
+ /*
+ * prepend SQUASH_MSG here if it exists and a
+ * "merge --squash" was originally performed
+ */
+ if (!stat(git_path_squash_msg(), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
+ die_errno(_("could not read SQUASH_MSG"));
+ hook_arg1 = "squash";
+ } else
+ hook_arg1 = "merge";
+ if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0)
die_errno(_("could not read MERGE_MSG"));
- hook_arg1 = "merge";
- } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
- if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
+ } else if (!stat(git_path_squash_msg(), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
die_errno(_("could not read SQUASH_MSG"));
hook_arg1 = "squash";
} else if (template_file) {
@@ -700,9 +772,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
hook_arg2 = "";
}
- s->fp = fopen(git_path(commit_editmsg), "w");
+ s->fp = fopen_for_writing(git_path_commit_editmsg());
if (s->fp == NULL)
- die_errno(_("could not open '%s'"), git_path(commit_editmsg));
+ 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;
@@ -715,38 +787,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
s->hints = 0;
if (clean_message_contents)
- stripspace(&sb, 0);
-
- if (signoff) {
- /*
- * See if we have a Conflicts: block at the end. If yes, count
- * its size, so we can ignore it.
- */
- int ignore_footer = 0;
- int i, eol, previous = 0;
- const char *nl;
-
- for (i = 0; i < sb.len; i++) {
- nl = memchr(sb.buf + i, '\n', sb.len - i);
- if (nl)
- eol = nl - sb.buf;
- else
- eol = sb.len;
- if (starts_with(sb.buf + previous, "\nConflicts:\n")) {
- ignore_footer = sb.len - previous;
- break;
- }
- while (i < eol)
- i++;
- previous = eol;
- }
+ strbuf_stripspace(&sb, 0);
- append_signoff(&sb, ignore_footer, 0);
- }
+ if (signoff)
+ append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 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 if committer ident is explicitly given */
@@ -754,8 +804,11 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (use_editor && include_status) {
int ident_shown = 0;
int saved_color_setting;
- char *ai_tmp, *ci_tmp;
- if (whence != FROM_COMMIT)
+ 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,
whence == FROM_MERGE
? _("\n"
@@ -771,6 +824,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
git_path(whence == FROM_MERGE
? "MERGE_HEAD"
: "CHERRY_PICK_HEAD"));
+ }
fprintf(s->fp, "\n");
if (cleanup_mode == CLEANUP_ALL)
@@ -778,6 +832,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
_("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(s, GIT_COLOR_NORMAL,
_("Please enter the commit message for your changes."
@@ -789,32 +845,45 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
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"),
+ "Author: %.*s <%.*s>"),
ident_shown++ ? "" : "\n",
- author_ident->buf);
+ (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"
+ "Date: %s"),
+ ident_shown++ ? "" : "\n",
+ show_ident_date(&ai, DATE_MODE(NORMAL)));
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";
@@ -825,10 +894,29 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (amend)
parent = "HEAD^1";
- if (get_sha1(parent, sha1))
- commitable = !!active_nr;
- else
- commitable = index_differs_from(parent, 0);
+ if (get_sha1(parent, sha1)) {
+ int i, ita_nr = 0;
+
+ for (i = 0; i < active_nr; i++)
+ if (ce_intent_to_add(active_cache[i]))
+ ita_nr++;
+ commitable = active_nr - ita_nr > 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, 1);
+ }
}
strbuf_release(&committer_ident);
@@ -867,24 +955,24 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
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) {
- char index[PATH_MAX];
- const char *env[2] = { NULL };
- env[0] = index;
- snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
- if (launch_editor(git_path(commit_editmsg), NULL, env)) {
+ struct argv_array env = ARGV_ARRAY_INIT;
+
+ argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file);
+ if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) {
fprintf(stderr,
_("Please supply the message using either -m or -F option.\n"));
exit(1);
}
+ argv_array_clear(&env);
}
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;
}
@@ -935,7 +1023,7 @@ static int message_is_empty(struct strbuf *sb)
static int template_untouched(struct strbuf *sb)
{
struct strbuf tmpl = STRBUF_INIT;
- char *start;
+ const char *start;
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
@@ -943,9 +1031,8 @@ static int template_untouched(struct strbuf *sb)
if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
return 0;
- stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
- start = (char *)skip_prefix(sb->buf, tmpl.buf);
- if (!start)
+ strbuf_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);
@@ -970,17 +1057,18 @@ static const char *find_author_by_nickname(const char *name)
revs.mailmap = &mailmap;
read_mailmap(revs.mailmap, NULL);
- prepare_revision_walk(&revs);
+ 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;
+ ctx.date_mode.type = DATE_NORMAL;
strbuf_release(&buf);
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);
}
@@ -1015,7 +1103,7 @@ static const char *read_commit_message(const char *name)
* is not in effect here.
*/
static struct status_deferred_config {
- enum status_format status_format;
+ enum wt_status_format status_format;
int show_branch;
} status_deferred_config = {
STATUS_FORMAT_UNSPECIFIED,
@@ -1025,6 +1113,7 @@ static struct status_deferred_config {
static void finalize_deferred_config(struct wt_status *s)
{
int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN &&
+ status_format != STATUS_FORMAT_PORCELAIN_V2 &&
!s->null_termination);
if (s->null_termination) {
@@ -1068,8 +1157,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
use_editor = 0;
if (0 <= edit_flag)
use_editor = edit_flag;
- if (!use_editor)
- setenv("GIT_EDITOR", ":", 1);
/* Sanity check options */
if (amend && !current_head)
@@ -1092,9 +1179,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
f++;
if (f > 1)
die(_("Only one of -c/-C/-F/--fixup can be used."));
- if (message.len && f > 0)
+ if (have_option_m && f > 0)
die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
- if (f || message.len)
+ if (f || have_option_m)
template_file = NULL;
if (edit_message)
use_message = edit_message;
@@ -1119,12 +1206,10 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (also + only + all + interactive > 1)
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
- if (argc == 0 && (also || (only && !amend)))
+ if (argc == 0 && (also || (only && !amend && !allow_empty)))
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"))
@@ -1133,6 +1218,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);
@@ -1160,22 +1247,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;
}
@@ -1183,6 +1269,7 @@ 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);
@@ -1212,14 +1299,14 @@ static int git_status_config(const char *k, const char *v, void *cb)
s->display_comment_prefix = git_config_bool(k, v);
return 0;
}
- if (starts_with(k, "status.color.") || starts_with(k, "color.status.")) {
- int slot = parse_status_slot(k, 13);
+ if (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);
@@ -1252,9 +1339,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
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,
- N_("machine-readable output"),
- STATUS_FORMAT_PORCELAIN),
+ { OPTION_CALLBACK, 0, "porcelain", &status_format,
+ N_("version"), N_("machine-readable output"),
+ PARSE_OPT_OPTARG, opt_parse_porcelain },
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
@@ -1294,36 +1381,43 @@ int cmd_status(int argc, const char **argv, const char *prefix)
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
- if (0 <= fd)
- update_index_if_able(&the_index, &index_lock);
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+ if (!s.is_initial)
+ hashcpy(s.sha1_commit, sha1);
+
s.ignore_submodule_arg = ignore_submodule_arg;
+ s.status_format = status_format;
+ s.verbose = verbose;
+
wt_status_collect(&s);
+ if (0 <= fd)
+ update_index_if_able(&the_index, &index_lock);
+
if (s.relative_paths)
s.prefix = prefix;
- switch (status_format) {
- case STATUS_FORMAT_SHORT:
- wt_shortstatus_print(&s);
- break;
- case STATUS_FORMAT_PORCELAIN:
- wt_porcelain_print(&s);
- break;
- case STATUS_FORMAT_UNSPECIFIED:
- die("BUG: finalize_deferred_config() should have been called");
- break;
- case STATUS_FORMAT_NONE:
- case STATUS_FORMAT_LONG:
- s.verbose = verbose;
- s.ignore_submodule_arg = ignore_submodule_arg;
- wt_status_print(&s);
- break;
- }
+ wt_status_print(&s);
return 0;
}
+static const char *implicit_ident_advice(void)
+{
+ char *user_config = expand_user_path("~/.gitconfig");
+ char *xdg_config = xdg_config_home("config");
+ int config_exists = file_exists(user_config) || file_exists(xdg_config);
+
+ 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)
{
@@ -1350,12 +1444,19 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
strbuf_addstr(&format, "\n Author: ");
strbuf_addbuf_percentquote(&format, &author_ident);
}
+ 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);
@@ -1376,14 +1477,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
rev.diffopt.break_opt = 0;
diff_setup_done(&rev.diffopt);
- head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL);
- printf("[%s%s ",
- starts_with(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;
@@ -1411,6 +1510,11 @@ static int git_commit_config(const char *k, const char *v, void *cb)
sign_commit = git_config_bool(k, v) ? "" : NULL;
return 0;
}
+ if (!strcmp(k, "commit.verbose")) {
+ int is_bool;
+ config_commit_verbose = git_config_bool_or_int(k, v, &is_bool);
+ return 0;
+ }
status = git_gpg_config(k, v, NULL);
if (status)
@@ -1421,12 +1525,10 @@ static int git_commit_config(const char *k, const char *v, void *cb)
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;
+ struct strbuf sb = STRBUF_INIT;
argv[0] = find_hook("post-rewrite");
if (!argv[0])
@@ -1435,7 +1537,6 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
argv[1] = "amend";
argv[2] = NULL;
- memset(&proc, 0, sizeof(proc));
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
@@ -1443,13 +1544,37 @@ static int run_rewrite_hook(const unsigned char *oldsha1,
code = start_command(&proc);
if (code)
return code;
- n = snprintf(buf, sizeof(buf), "%s %s\n",
- sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
- write_in_full(proc.in, buf, n);
+ strbuf_addf(&sb, "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+ sigchain_push(SIGPIPE, SIG_IGN);
+ write_in_full(proc.in, sb.buf, sb.len);
close(proc.in);
+ strbuf_release(&sb);
+ sigchain_pop(SIGPIPE);
return finish_command(&proc);
}
+int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
+{
+ struct argv_array hook_env = ARGV_ARRAY_INIT;
+ va_list args;
+ int ret;
+
+ argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
+
+ /*
+ * Let the hook know that no editor will be launched.
+ */
+ if (!editor_is_used)
+ argv_array_push(&hook_env, "GIT_EDITOR=:");
+
+ va_start(args, name);
+ ret = run_hook_ve(hook_env.argv,name, args);
+ va_end(args);
+ argv_array_clear(&hook_env);
+
+ return ret;
+}
+
int cmd_commit(int argc, const char **argv, const char *prefix)
{
static struct wt_status s;
@@ -1472,7 +1597,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
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"),
+ { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
/* end commit message options */
@@ -1482,7 +1607,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
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('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")),
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),
@@ -1512,11 +1637,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
const char *index_file, *reflog_msg;
char *nl;
unsigned char sha1[20];
- struct ref_lock *ref_lock;
- struct commit_list *parents = NULL, **pptr = &parents;
+ struct commit_list *parents = NULL;
struct stat statbuf;
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);
@@ -1532,9 +1658,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (parse_commit(current_head))
die(_("could not parse HEAD commit"));
}
+ verbose = -1; /* unspecified */
argc = parse_and_validate_options(argc, argv, builtin_commit_options,
builtin_commit_usage,
prefix, current_head, &s);
+ if (verbose == -1)
+ verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose;
+
if (dry_run)
return dry_run_commit(argc, argv, prefix, current_head, &s);
index_file = prepare_index(argc, argv, prefix, current_head, 0);
@@ -1553,36 +1683,34 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (!reflog_msg)
reflog_msg = "commit (initial)";
} else if (amend) {
- struct commit_list *c;
-
if (!reflog_msg)
reflog_msg = "commit (amend)";
- for (c = current_head->parents; c; c = c->next)
- pptr = &commit_list_insert(c->item, pptr)->next;
+ parents = copy_commit_list(current_head->parents);
} else if (whence == FROM_MERGE) {
struct strbuf m = STRBUF_INIT;
FILE *fp;
int allow_fast_forward = 1;
+ struct commit_list **pptr = &parents;
if (!reflog_msg)
reflog_msg = "commit (merge)";
- pptr = &commit_list_insert(current_head, pptr)->next;
- fp = fopen(git_path("MERGE_HEAD"), "r");
+ pptr = commit_list_append(current_head, pptr);
+ fp = fopen(git_path_merge_head(), "r");
if (fp == NULL)
die_errno(_("could not open '%s' for reading"),
- git_path("MERGE_HEAD"));
- while (strbuf_getline(&m, fp, '\n') != EOF) {
+ git_path_merge_head());
+ while (strbuf_getline_lf(&m, fp) != EOF) {
struct commit *parent;
parent = get_merge_parent(m.buf);
if (!parent)
die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
- pptr = &commit_list_insert(parent, pptr)->next;
+ pptr = commit_list_append(parent, pptr);
}
fclose(fp);
strbuf_release(&m);
- if (!stat(git_path("MERGE_MODE"), &statbuf)) {
- if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
+ if (!stat(git_path_merge_mode(), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path_merge_mode(), 0) < 0)
die_errno(_("could not read MERGE_MODE"));
if (!strcmp(sb.buf, "no-ff"))
allow_fast_forward = 0;
@@ -1594,23 +1722,23 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
reflog_msg = (whence == FROM_CHERRY_PICK)
? "commit (cherry-pick)"
: "commit";
- pptr = &commit_list_insert(current_head, pptr)->next;
+ commit_list_insert(current_head, &parents);
}
/* Finally, get the commit message */
strbuf_reset(&sb);
- if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
+ if (strbuf_read_file(&sb, git_path_commit_editmsg(), 0) < 0) {
int saved_errno = errno;
rollback_index_files();
die(_("could not read commit message: %s"), strerror(saved_errno));
}
- /* Truncate the message just before the diff, if any. */
- if (verbose)
+ 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);
+ strbuf_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"));
@@ -1630,20 +1758,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
append_merge_tag_headers(parents, &tail);
}
- if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
- author_ident.buf, sign_commit, extra)) {
+ 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);
free_commit_extra_headers(extra);
- ref_lock = lock_any_ref_for_update("HEAD",
- !current_head
- ? NULL
- : current_head->object.sha1,
- 0, NULL);
-
nl = strchr(sb.buf, '\n');
if (nl)
strbuf_setlen(&sb, nl + 1 - sb.buf);
@@ -1652,41 +1774,45 @@ 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.oid.hash : null_sha1,
+ 0, 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"));
- unlink(git_path("SQUASH_MSG"));
+ 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());
+ unlink(git_path_squash_msg());
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);
+ copy_note_for_rewrite(cfg, current_head->object.oid.hash, sha1);
finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
}
- run_rewrite_hook(current_head->object.sha1, sha1);
+ run_rewrite_hook(current_head->object.oid.hash, 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 92ebf23f0a..05843a0f96 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -3,9 +3,10 @@
#include "color.h"
#include "parse-options.h"
#include "urlmatch.h"
+#include "quote.h"
static const char *const builtin_config_usage[] = {
- N_("git config [options]"),
+ N_("git config [<options>]"),
NULL
};
@@ -13,6 +14,7 @@ static char *key;
static regex_t *key_regexp;
static regex_t *regexp;
static int show_keys;
+static int omit_values;
static int use_key_regexp;
static int do_all;
static int do_not_match;
@@ -21,12 +23,11 @@ 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 const char *given_config_blob;
+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;
+static int show_origin;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
@@ -55,8 +56,8 @@ static struct option builtin_config_options[] = {
OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")),
- OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")),
- OPT_STRING(0, "blob", &given_config_blob, N_("blob-id"), N_("read config from given blob object")),
+ OPT_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),
@@ -70,8 +71,8 @@ static struct option builtin_config_options[] = {
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_STRING(0, "get-color", &get_color_slot, N_("slot"), N_("find the color configured: [default]")),
- OPT_STRING(0, "get-colorbool", &get_colorbool_slot, N_("slot"), N_("find the color setting: [stdout-is-tty]")),
+ 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),
@@ -79,7 +80,9 @@ static struct option builtin_config_options[] = {
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, "name-only", &omit_values, N_("show variable names only")),
OPT_BOOL(0, "includes", &respect_includes, N_("respect include directives on lookup")),
+ OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
OPT_END(),
};
@@ -90,9 +93,29 @@ static void check_argc(int argc, int min, int max) {
usage_with_options(builtin_config_usage, builtin_config_options);
}
+static void show_config_origin(struct strbuf *buf)
+{
+ const char term = end_null ? '\0' : '\t';
+
+ strbuf_addstr(buf, current_config_origin_type());
+ strbuf_addch(buf, ':');
+ if (end_null)
+ strbuf_addstr(buf, current_config_name());
+ else
+ quote_c_style(current_config_name(), buf, NULL, 0);
+ strbuf_addch(buf, term);
+}
+
static int show_all_config(const char *key_, const char *value_, void *cb)
{
- if (value_)
+ if (show_origin) {
+ struct strbuf buf = STRBUF_INIT;
+ show_config_origin(&buf);
+ /* Use fwrite as "buf" can contain \0's if "end_null" is set. */
+ fwrite(buf.buf, 1, buf.len, stdout);
+ strbuf_release(&buf);
+ }
+ if (!omit_values && value_)
printf("%s%c%s%c", key_, delim, value_, term);
else
printf("%s%c", key_, term);
@@ -107,48 +130,42 @@ struct strbuf_list {
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;
-
- strbuf_init(buf, 0);
-
- if (show_keys) {
+ if (show_origin)
+ show_config_origin(buf);
+ if (show_keys)
strbuf_addstr(buf, key_);
- must_print_delim = 1;
- }
- if (types == TYPE_INT)
- 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) {
- int is_bool, v;
- v = git_config_bool_or_int(key_, value_, &is_bool);
- if (is_bool)
- vptr = v ? "true" : "false";
- else
- sprintf(value, "%d", v);
- } else if (types == TYPE_PATH) {
- 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;
- }
+ if (!omit_values) {
+ if (show_keys)
+ strbuf_addch(buf, key_delim);
- if (must_print_delim)
- strbuf_addch(buf, key_delim);
- strbuf_addstr(buf, vptr);
+ if (types == TYPE_INT)
+ strbuf_addf(buf, "%"PRId64,
+ git_config_int64(key_, value_ ? value_ : ""));
+ else if (types == TYPE_BOOL)
+ strbuf_addstr(buf, git_config_bool(key_, value_) ?
+ "true" : "false");
+ else if (types == TYPE_BOOL_OR_INT) {
+ int is_bool, v;
+ v = git_config_bool_or_int(key_, value_, &is_bool);
+ if (is_bool)
+ strbuf_addstr(buf, v ? "true" : "false");
+ else
+ strbuf_addf(buf, "%d", v);
+ } else if (types == TYPE_PATH) {
+ const char *v;
+ if (git_config_pathname(&v, key_, value_) < 0)
+ return -1;
+ strbuf_addstr(buf, v);
+ free((char *)v);
+ } else if (value_) {
+ strbuf_addstr(buf, value_);
+ } else {
+ /* Just show the key name; back out delimiter */
+ if (show_keys)
+ strbuf_setlen(buf, buf->len - 1);
+ }
+ }
strbuf_addch(buf, term);
-
- if (must_free_vptr)
- free((char *)vptr);
return 0;
}
@@ -165,6 +182,7 @@ static int collect_config(const char *key_, const char *value_, void *cb)
return 0;
ALLOC_GROW(values->items, values->nr + 1, values->alloc);
+ strbuf_init(&values->items[values->nr], 0);
return format_config(&values->items[values->nr++], key_, value_);
}
@@ -194,7 +212,7 @@ static int get_value(const char *key_, const char *regex_)
key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(key_regexp, key, REG_EXTENDED)) {
- fprintf(stderr, "Invalid key pattern: %s\n", key_);
+ error("invalid key pattern: %s", key_);
free(key_regexp);
key_regexp = NULL;
ret = CONFIG_INVALID_PATTERN;
@@ -215,7 +233,7 @@ static int get_value(const char *key_, const char *regex_)
regexp = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(regexp, regex_, REG_EXTENDED)) {
- fprintf(stderr, "Invalid pattern: %s\n", regex_);
+ error("invalid pattern: %s", regex_);
free(regexp);
regexp = NULL;
ret = CONFIG_INVALID_PATTERN;
@@ -224,8 +242,7 @@ static int get_value(const char *key_, const char *regex_)
}
git_config_with_options(collect_config, &values,
- given_config_file, given_config_blob,
- respect_includes);
+ &given_config_source, respect_includes);
ret = !values.nr;
@@ -253,8 +270,6 @@ free_strings:
static char *normalize_value(const char *key, const char *value)
{
- char *normalized;
-
if (!value)
return NULL;
@@ -265,27 +280,21 @@ static char *normalize_value(const char *key, const char *value)
* "~/foobar/" in the config file, and to expand the ~
* when retrieving the value.
*/
- normalized = xstrdup(value);
- else {
- normalized = xmalloc(64);
- if (types == TYPE_INT) {
- int64_t v = git_config_int64(key, value);
- sprintf(normalized, "%"PRId64, v);
- }
- else if (types == TYPE_BOOL)
- sprintf(normalized, "%s",
- git_config_bool(key, value) ? "true" : "false");
- else if (types == TYPE_BOOL_OR_INT) {
- int is_bool, v;
- v = git_config_bool_or_int(key, value, &is_bool);
- if (!is_bool)
- sprintf(normalized, "%d", v);
- else
- sprintf(normalized, "%s", v ? "true" : "false");
- }
+ return xstrdup(value);
+ if (types == TYPE_INT)
+ return xstrfmt("%"PRId64, git_config_int64(key, value));
+ if (types == TYPE_BOOL)
+ return xstrdup(git_config_bool(key, value) ? "true" : "false");
+ if (types == TYPE_BOOL_OR_INT) {
+ int is_bool, v;
+ v = git_config_bool_or_int(key, value, &is_bool);
+ if (!is_bool)
+ return xstrfmt("%d", v);
+ else
+ return xstrdup(v ? "true" : "false");
}
- return normalized;
+ die("BUG: cannot normalize type %d", types);
}
static int get_color_found;
@@ -298,22 +307,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_with_options(git_get_color_config, NULL,
- given_config_file, given_config_blob,
- respect_includes);
+ &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);
}
@@ -333,14 +345,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;
get_color_ui_found = -1;
git_config_with_options(git_get_colorbool_config, NULL,
- given_config_file, given_config_blob,
- respect_includes);
+ &given_config_source, respect_includes);
if (get_colorbool_found < 0) {
if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -362,9 +374,15 @@ static int get_colorbool(int print)
return get_colorbool_found ? 0 : 1;
}
-static void check_blob_write(void)
+static void check_write(void)
{
- if (given_config_blob)
+ if (!given_config_source.file && !startup_info->have_repository)
+ die("not in a git directory");
+
+ 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");
}
@@ -396,21 +414,9 @@ static int urlmatch_collect_fn(const char *var, const char *value, void *cb)
return 0;
}
-static char *dup_downcase(const char *string)
-{
- char *result;
- size_t len, i;
-
- len = strlen(string);
- result = xmalloc(len + 1);
- for (i = 0; i < len; i++)
- result[i] = tolower(string[i]);
- result[i] = '\0';
- return result;
-}
-
static int get_urlmatch(const char *var, const char *url)
{
+ int ret;
char *section_tail;
struct string_list_item *item;
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
@@ -423,7 +429,7 @@ static int get_urlmatch(const char *var, const char *url)
if (!url_normalize(url, &config.url))
die("%s", config.url.err);
- config.section = dup_downcase(var);
+ config.section = xstrdup_tolower(var);
section_tail = strchr(config.section, '.');
if (section_tail) {
*section_tail = '\0';
@@ -435,18 +441,17 @@ static int get_urlmatch(const char *var, const char *url)
}
git_config_with_options(urlmatch_config_entry, &config,
- given_config_file, NULL, respect_includes);
+ &given_config_source, respect_includes);
+
+ ret = !values.nr;
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,
+ format_config(&buf, item->string,
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);
@@ -456,7 +461,21 @@ static int get_urlmatch(const char *var, const char *url)
free(config.url.url);
free((void *)config.section);
- return 0;
+ return ret;
+}
+
+static char *default_user_config(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addf(&buf,
+ _("# This is Git's per-user configuration file.\n"
+ "[user]\n"
+ "# Please adapt and uncomment the following lines:\n"
+ "# name = %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)
@@ -464,23 +483,27 @@ int cmd_config(int argc, const char **argv, const char *prefix)
int nongit = !startup_info->have_repository;
char *value;
- given_config_file = 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 + !!given_config_blob > 1) {
+ !!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 (use_global_config) {
- char *user_config = NULL;
- char *xdg_config = NULL;
+ if (given_config_source.file &&
+ !strcmp(given_config_source.file, "-")) {
+ given_config_source.file = NULL;
+ given_config_source.use_stdin = 1;
+ }
- home_config_paths(&user_config, &xdg_config, "config");
+ if (use_global_config) {
+ char *user_config = expand_user_path("~/.gitconfig");
+ char *xdg_config = xdg_config_home("config");
if (!user_config)
/*
@@ -493,24 +516,24 @@ int cmd_config(int argc, const char **argv, const char *prefix)
if (access_or_warn(user_config, R_OK, 0) &&
xdg_config && !access_or_warn(xdg_config, R_OK, 0))
- given_config_file = xdg_config;
+ given_config_source.file = xdg_config;
else
- given_config_file = user_config;
+ given_config_source.file = user_config;
}
else if (use_system_config)
- given_config_file = git_etc_gitconfig();
+ given_config_source.file = git_etc_gitconfig();
else if (use_local_config)
- given_config_file = git_pathdup("config");
- else if (given_config_file) {
- if (!is_absolute_path(given_config_file) && prefix)
- 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_file));
+ given_config_source.file));
}
if (respect_includes == -1)
- respect_includes = !given_config_file;
+ respect_includes = !given_config_source.file;
if (end_null) {
term = '\0';
@@ -523,12 +546,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);
}
@@ -545,62 +563,90 @@ int cmd_config(int argc, const char **argv, const char *prefix)
default:
usage_with_options(builtin_config_usage, builtin_config_options);
}
+ if (omit_values &&
+ !(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) {
+ error("--name-only is only applicable to --list or --get-regexp");
+ usage_with_options(builtin_config_usage, builtin_config_options);
+ }
+
+ if (show_origin && !(actions &
+ (ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) {
+ error("--show-origin is only applicable to --get, --get-all, "
+ "--get-regexp, and --list.");
+ usage_with_options(builtin_config_usage, builtin_config_options);
+ }
if (actions == ACTION_LIST) {
check_argc(argc, 0, 0);
if (git_config_with_options(show_all_config, NULL,
- given_config_file,
- given_config_blob,
+ &given_config_source,
respect_includes) < 0) {
- if (given_config_file)
+ if (given_config_source.file)
die_errno("unable to read config file '%s'",
- given_config_file);
+ 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 (!given_config_file && nongit)
+ if (!given_config_source.file && nongit)
die("not in a git directory");
- if (given_config_blob)
+ 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(given_config_file ?
- given_config_file : 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 >= 0) {
+ 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_blob_write();
+ check_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
- ret = git_config_set_in_file(given_config_file, argv[0], value);
+ ret = git_config_set_in_file_gently(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 --replace-all to change %s.", argv[0]);
+ error(_("cannot overwrite multiple values with a single value\n"
+ " Use a regexp, --add or --replace-all to change %s."), argv[0]);
return ret;
}
else if (actions == ACTION_SET_ALL) {
- check_blob_write();
+ check_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar_in_file(given_config_file,
- argv[0], value, argv[2], 0);
+ return git_config_set_multivar_in_file_gently(given_config_source.file,
+ argv[0], value, argv[2], 0);
}
else if (actions == ACTION_ADD) {
- check_blob_write();
+ check_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar_in_file(given_config_file,
- argv[0], value, "^$", 0);
+ return git_config_set_multivar_in_file_gently(given_config_source.file,
+ argv[0], value,
+ CONFIG_REGEX_NONE, 0);
}
else if (actions == ACTION_REPLACE_ALL) {
- check_blob_write();
+ check_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
- return git_config_set_multivar_in_file(given_config_file,
- argv[0], value, argv[2], 1);
+ return git_config_set_multivar_in_file_gently(given_config_source.file,
+ argv[0], value, argv[2], 1);
}
else if (actions == ACTION_GET) {
check_argc(argc, 1, 2);
@@ -623,26 +669,26 @@ int cmd_config(int argc, const char **argv, const char *prefix)
return get_urlmatch(argv[0], argv[1]);
}
else if (actions == ACTION_UNSET) {
- check_blob_write();
+ check_write();
check_argc(argc, 1, 2);
if (argc == 2)
- return git_config_set_multivar_in_file(given_config_file,
- argv[0], NULL, argv[1], 0);
+ return git_config_set_multivar_in_file_gently(given_config_source.file,
+ argv[0], NULL, argv[1], 0);
else
- return git_config_set_in_file(given_config_file,
- argv[0], NULL);
+ return git_config_set_in_file_gently(given_config_source.file,
+ argv[0], NULL);
}
else if (actions == ACTION_UNSET_ALL) {
- check_blob_write();
+ check_write();
check_argc(argc, 1, 2);
- return git_config_set_multivar_in_file(given_config_file,
- argv[0], NULL, argv[1], 1);
+ return git_config_set_multivar_in_file_gently(given_config_source.file,
+ argv[0], NULL, argv[1], 1);
}
else if (actions == ACTION_RENAME_SECTION) {
int ret;
- check_blob_write();
+ check_write();
check_argc(argc, 2, 2);
- ret = git_config_rename_section_in_file(given_config_file,
+ ret = git_config_rename_section_in_file(given_config_source.file,
argv[0], argv[1]);
if (ret < 0)
return ret;
@@ -651,9 +697,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_REMOVE_SECTION) {
int ret;
- check_blob_write();
+ check_write();
check_argc(argc, 1, 1);
- ret = git_config_rename_section_in_file(given_config_file,
+ ret = git_config_rename_section_in_file(given_config_source.file,
argv[0], NULL);
if (ret < 0)
return ret;
@@ -661,12 +707,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
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;
diff --git a/builtin/count-objects.c b/builtin/count-objects.c
index a7f70cb858..a04b4f2ef3 100644
--- a/builtin/count-objects.c
+++ b/builtin/count-objects.c
@@ -8,74 +8,78 @@
#include "dir.h"
#include "builtin.h"
#include "parse-options.h"
+#include "quote.h"
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)
+static const char *bits_to_msg(unsigned seen_bits)
+{
+ switch (seen_bits) {
+ case 0:
+ return "no corresponding .idx or .pack";
+ case PACKDIR_FILE_GARBAGE:
+ return "garbage found";
+ case PACKDIR_FILE_PACK:
+ return "no corresponding .idx";
+ case PACKDIR_FILE_IDX:
+ return "no corresponding .pack";
+ case PACKDIR_FILE_PACK|PACKDIR_FILE_IDX:
+ default:
+ return NULL;
+ }
+}
+
+static void real_report_garbage(unsigned seen_bits, const char *path)
{
struct stat st;
+ const char *desc = bits_to_msg(seen_bits);
+
+ if (!desc)
+ return;
+
if (!stat(path, &st))
size_garbage += st.st_size;
warning("%s: %s", desc, path);
garbage++;
}
-static void count_objects(DIR *d, char *path, int len, int verbose,
- unsigned long *loose,
- off_t *loose_size,
- unsigned long *packed_loose)
+static void loose_garbage(const char *path)
{
- struct dirent *ent;
- while ((ent = readdir(d)) != NULL) {
- char hex[41];
- unsigned char sha1[20];
- const char *cp;
- int bad = 0;
-
- 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) {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addf(&sb, "%.*s/%s",
- len + 2, path, ent->d_name);
- report_garbage("garbage found", sb.buf);
- strbuf_release(&sb);
- }
- 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)++;
+ if (verbose)
+ report_garbage(PACKDIR_FILE_GARBAGE, 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 int print_alternate(struct alternate_object_database *alt, void *data)
+{
+ printf("alternate: ");
+ quote_c_style(alt->path, NULL, stdout, 0);
+ putchar('\n');
+ return 0;
}
static char const * const count_objects_usage[] = {
@@ -85,12 +89,7 @@ static char const * const count_objects_usage[] = {
int cmd_count_objects(int argc, const char **argv, const char *prefix)
{
- int i, verbose = 0, human_readable = 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;
- off_t loose_size = 0;
+ int human_readable = 0;
struct option opts[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_BOOL('H', "human-readable", &human_readable,
@@ -98,25 +97,20 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
OPT_END(),
};
+ git_config(git_default_config, NULL);
+
argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0);
/* we do not take arguments other than flags for now */
if (argc)
usage_with_options(count_objects_usage, opts);
- if (verbose)
+ if (verbose) {
report_garbage = real_report_garbage;
- 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);
- closedir(d);
+ report_linked_checkout_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;
@@ -157,6 +151,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
printf("prune-packable: %lu\n", packed_loose);
printf("garbage: %lu\n", garbage);
printf("size-garbage: %s\n", garbage_buf.buf);
+ foreach_alt_odb(print_alternate, NULL);
strbuf_release(&loose_buf);
strbuf_release(&pack_buf);
strbuf_release(&garbage_buf);
diff --git a/builtin/describe.c b/builtin/describe.c
index dadd999c41..6769446e1f 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"
@@ -13,8 +14,8 @@
#define MAX_TAGS (FLAG_BITS - 1)
static const char * const describe_usage[] = {
- N_("git describe [options] <commit-ish>*"),
- N_("git describe [options] --dirty"),
+ N_("git describe [<options>] [<commit-ish>...]"),
+ N_("git describe [<options>] --dirty"),
NULL
};
@@ -27,7 +28,8 @@ static int abbrev = -1; /* unspecified */
static int max_candidates = 10;
static struct hashmap names;
static int have_util;
-static const char *pattern;
+static struct string_list patterns = STRING_LIST_INIT_NODUP;
+static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP;
static int always;
static const char *dirty;
@@ -56,18 +58,9 @@ static int commit_name_cmp(const struct commit_name *cn1,
return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled);
}
-static inline unsigned int hash_sha1(const unsigned char *sha1)
-{
- unsigned int hash;
- memcpy(&hash, sha1, sizeof(hash));
- return hash;
-}
-
static inline struct commit_name *find_commit_name(const unsigned char *peeled)
{
- struct commit_name key;
- hashmap_entry_init(&key, hash_sha1(peeled));
- return hashmap_get(&names, &key, peeled);
+ return hashmap_get_from_hash(&names, sha1hash(peeled), peeled);
}
static int replace_name(struct commit_name *e,
@@ -114,7 +107,7 @@ static void add_to_known_names(const char *path,
if (!e) {
e = xmalloc(sizeof(struct commit_name));
hashcpy(e->peeled, peeled);
- hashmap_entry_init(e, hash_sha1(peeled));
+ hashmap_entry_init(e, sha1hash(peeled));
hashmap_add(&names, e);
e->path = NULL;
}
@@ -127,25 +120,56 @@ static void add_to_known_names(const char *path,
}
}
-static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data)
{
int is_tag = starts_with(path, "refs/tags/");
- unsigned char peeled[20];
+ struct object_id peeled;
int is_annotated, prio;
/* Reject anything outside refs/tags/ unless --all */
if (!all && !is_tag)
return 0;
- /* Accept only tags that match the pattern, if given */
- if (pattern && (!is_tag || fnmatch(pattern, path + 10, 0)))
- return 0;
+ /*
+ * If we're given exclude patterns, first exclude any tag which match
+ * any of the exclude pattern.
+ */
+ if (exclude_patterns.nr) {
+ struct string_list_item *item;
+
+ if (!is_tag)
+ return 0;
+
+ for_each_string_list_item(item, &exclude_patterns) {
+ if (!wildmatch(item->string, path + 10, 0, NULL))
+ return 0;
+ }
+ }
+
+ /*
+ * If we're given patterns, accept only tags which match at least one
+ * pattern.
+ */
+ if (patterns.nr) {
+ struct string_list_item *item;
+
+ if (!is_tag)
+ return 0;
+
+ for_each_string_list_item(item, &patterns) {
+ if (!wildmatch(item->string, path + 10, 0, NULL))
+ break;
+
+ /* If we get here, no pattern matched. */
+ return 0;
+ }
+ }
/* Is it annotated? */
- if (!peel_ref(path, peeled)) {
- is_annotated = !!hashcmp(sha1, peeled);
+ if (!peel_ref(path, peeled.hash)) {
+ is_annotated = !!oidcmp(oid, &peeled);
} else {
- hashcpy(peeled, sha1);
+ oidcpy(&peeled, oid);
is_annotated = 0;
}
@@ -162,7 +186,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void
else
prio = 0;
- add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1);
+ add_to_known_names(all ? path + 5 : path + 10, peeled.hash, prio, oid->hash);
return 0;
}
@@ -260,14 +284,14 @@ static void describe(const char *arg, int last_one)
if (!cmit)
die(_("%s is not a valid '%s' object"), arg, commit_type);
- n = find_commit_name(cmit->object.sha1);
+ n = find_commit_name(cmit->object.oid.hash);
if (n && (tags || all || n->prio == 2)) {
/*
* Exact match to an existing ref.
*/
display_name(n);
if (longformat)
- show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1);
+ show_suffix(0, n->tag ? n->tag->tagged->oid.hash : sha1);
if (dirty)
printf("%s", dirty);
printf("\n");
@@ -275,7 +299,7 @@ static void describe(const char *arg, int last_one)
}
if (!max_candidates)
- die(_("no tag exactly matches '%s'"), sha1_to_hex(cmit->object.sha1));
+ die(_("no tag exactly matches '%s'"), oid_to_hex(&cmit->object.oid));
if (debug)
fprintf(stderr, _("searching to describe %s\n"), arg);
@@ -325,7 +349,7 @@ static void describe(const char *arg, int last_one)
if (annotated_cnt && !list) {
if (debug)
fprintf(stderr, _("finished search at %s\n"),
- sha1_to_hex(c->object.sha1));
+ oid_to_hex(&c->object.oid));
break;
}
while (parents) {
@@ -342,9 +366,9 @@ static void describe(const char *arg, int last_one)
}
if (!match_cnt) {
- const unsigned char *sha1 = cmit->object.sha1;
+ struct object_id *oid = &cmit->object.oid;
if (always) {
- printf("%s", find_unique_abbrev(sha1, abbrev));
+ printf("%s", find_unique_abbrev(oid->hash, abbrev));
if (dirty)
printf("%s", dirty);
printf("\n");
@@ -353,14 +377,14 @@ static void describe(const char *arg, int last_one)
if (unannotated_cnt)
die(_("No annotated tags can describe '%s'.\n"
"However, there were unannotated tags: try --tags."),
- sha1_to_hex(sha1));
+ oid_to_hex(oid));
else
die(_("No tags can describe '%s'.\n"
"Try --always, or create some tags."),
- sha1_to_hex(sha1));
+ oid_to_hex(oid));
}
- qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
+ QSORT(all_matches, match_cnt, compare_pt);
if (gave_up_on) {
commit_list_insert_by_date(gave_up_on, &list);
@@ -382,13 +406,13 @@ static void describe(const char *arg, int last_one)
_("more than %i tags found; listed %i most recent\n"
"gave up search at %s\n"),
max_candidates, max_candidates,
- sha1_to_hex(gave_up_on->object.sha1));
+ oid_to_hex(&gave_up_on->object.oid));
}
}
display_name(all_matches[0].name);
if (abbrev)
- show_suffix(all_matches[0].depth, cmit->object.sha1);
+ show_suffix(all_matches[0].depth, cmit->object.oid.hash);
if (dirty)
printf("%s", dirty);
printf("\n");
@@ -412,8 +436,10 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
N_("only output exact matches"), 0),
OPT_INTEGER(0, "candidates", &max_candidates,
N_("consider <n> most recent tags (default: 10)")),
- OPT_STRING(0, "match", &pattern, N_("pattern"),
+ OPT_STRING_LIST(0, "match", &patterns, N_("pattern"),
N_("only consider tags matching <pattern>")),
+ OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"),
+ N_("do not consider tags matching <pattern>")),
OPT_BOOL(0, "always", &always,
N_("show abbreviated commit object as fallback")),
{OPTION_STRING, 0, "dirty", &dirty, N_("mark"),
@@ -438,6 +464,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
die(_("--long is incompatible with --abbrev=0"));
if (contains) {
+ struct string_list_item *item;
struct argv_array args;
argv_array_init(&args);
@@ -448,13 +475,15 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
argv_array_push(&args, "--always");
if (!all) {
argv_array_push(&args, "--tags");
- if (pattern)
- argv_array_pushf(&args, "--refs=refs/tags/%s", pattern);
- }
- while (*argv) {
- argv_array_push(&args, *argv);
- argv++;
+ for_each_string_list_item(item, &patterns)
+ argv_array_pushf(&args, "--refs=refs/tags/%s", item->string);
+ for_each_string_list_item(item, &exclude_patterns)
+ argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string);
}
+ if (argc)
+ argv_array_pushv(&args, argv);
+ else
+ argv_array_push(&args, "HEAD");
return cmd_name_rev(args.argc, args.argv, prefix);
}
diff --git a/builtin/diff-files.c b/builtin/diff-files.c
index 9200069363..15c61fd8d1 100644
--- a/builtin/diff-files.c
+++ b/builtin/diff-files.c
@@ -11,7 +11,7 @@
#include "submodule.h"
static const char diff_files_usage[] =
-"git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
+"git diff-files [-q] [-0 | -1 | -2 | -3 | -c | --cc] [<common-diff-options>] [<path>...]"
COMMON_DIFF_OPTIONS_HELP;
int cmd_diff_files(int argc, const char **argv, const char *prefix)
@@ -24,6 +24,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
gitmodules_config();
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
rev.abbrev = 0;
+ precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, &rev, NULL);
while (1 < argc && argv[1][0] == '-') {
diff --git a/builtin/diff-index.c b/builtin/diff-index.c
index ce15b23042..1af373d002 100644
--- a/builtin/diff-index.c
+++ b/builtin/diff-index.c
@@ -7,7 +7,7 @@
static const char diff_cache_usage[] =
"git diff-index [-m] [--cached] "
-"[<common diff options>] <tree-ish> [<path>...]"
+"[<common-diff-options>] <tree-ish> [<path>...]"
COMMON_DIFF_OPTIONS_HELP;
int cmd_diff_index(int argc, const char **argv, const char *prefix)
@@ -21,6 +21,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
gitmodules_config();
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
rev.abbrev = 0;
+ precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, &rev, NULL);
for (i = 1; i < argc; i++) {
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c
index be6417d166..8ce00480cd 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)) {
@@ -53,9 +49,9 @@ static int stdin_diff_trees(struct tree *tree1, char *line, int len)
tree2 = lookup_tree(sha1);
if (!tree2 || parse_tree(tree2))
return -1;
- printf("%s %s\n", sha1_to_hex(tree1->object.sha1),
- sha1_to_hex(tree2->object.sha1));
- diff_tree_sha1(tree1->object.sha1, tree2->object.sha1,
+ printf("%s %s\n", oid_to_hex(&tree1->object.oid),
+ oid_to_hex(&tree2->object.oid));
+ diff_tree_sha1(tree1->object.oid.hash, tree2->object.oid.hash,
"", &log_tree_opt.diffopt);
log_tree_diff_flush(&log_tree_opt);
return 0;
@@ -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)
@@ -88,7 +82,7 @@ static int diff_tree_stdin(char *line)
static const char diff_tree_usage[] =
"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
-"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
+"[<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n"
" -r diff recursively\n"
" --root include the initial commit as diff against /dev/null\n"
COMMON_DIFF_OPTIONS_HELP;
@@ -120,6 +114,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
opt->disable_stdin = 1;
memset(&s_r_opt, 0, sizeof(s_r_opt));
s_r_opt.tweak = diff_tree_tweak_rev;
+
+ precompose_argv(argc, argv);
argc = setup_revisions(argc, argv, opt, &s_r_opt);
while (--argc > 0) {
@@ -145,18 +141,16 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
break;
case 1:
tree1 = opt->pending.objects[0].item;
- diff_tree_commit_sha1(tree1->sha1);
+ diff_tree_commit_sha1(tree1->oid.hash);
break;
case 2:
tree1 = opt->pending.objects[0].item;
tree2 = opt->pending.objects[1].item;
if (tree2->flags & UNINTERESTING) {
- struct object *tmp = tree2;
- tree2 = tree1;
- tree1 = tmp;
+ SWAP(tree2, tree1);
}
- diff_tree_sha1(tree1->sha1,
- tree2->sha1,
+ diff_tree_sha1(tree1->oid.hash,
+ tree2->oid.hash,
"", &opt->diffopt);
log_tree_diff_flush(opt);
break;
diff --git a/builtin/diff.c b/builtin/diff.c
index 0f247d2400..3d64b85337 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"
@@ -44,12 +45,9 @@ static void stuff_change(struct diff_options *opt,
return;
if (DIFF_OPT_TST(opt, REVERSE_DIFF)) {
- unsigned tmp;
- const unsigned char *tmp_u;
- const char *tmp_c;
- tmp = old_mode; old_mode = new_mode; new_mode = tmp;
- tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
- tmp_c = old_name; old_name = new_name; new_name = tmp_c;
+ SWAP(old_mode, new_mode);
+ SWAP(old_sha1, new_sha1);
+ SWAP(old_name, new_name);
}
if (opt->prefix &&
@@ -174,8 +172,8 @@ static int builtin_diff_tree(struct rev_info *revs,
*/
if (ent1->item->flags & UNINTERESTING)
swap = 1;
- sha1[swap] = ent0->item->sha1;
- sha1[1 - swap] = ent1->item->sha1;
+ sha1[swap] = ent0->item->oid.hash;
+ sha1[1 - swap] = ent1->item->oid.hash;
diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
log_tree_diff_flush(revs);
return 0;
@@ -195,8 +193,8 @@ 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;
for (i = 1; i < ents; i++)
- sha1_array_append(&parents, ent[i].item->sha1);
- diff_tree_combined(ent[0].item->sha1, &parents,
+ sha1_array_append(&parents, ent[i].item->oid.hash);
+ diff_tree_combined(ent[0].item->oid.hash, &parents,
revs->dense_combined_merges, revs);
sha1_array_clear(&parents);
return 0;
@@ -300,24 +298,27 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
break;
}
- if (!no_index)
- prefix = setup_git_directory_gently(&nongit);
+ 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) {
+ /*
+ * 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();
+ init_diff_ui_defaults();
git_config(git_diff_ui_config, NULL);
+ precompose_argv(argc, argv);
init_revisions(&rev, prefix);
@@ -340,7 +341,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
}
if (no_index)
/* If this is a no-index diff, just run it and exit there. */
- diff_no_index(&rev, argc, argv, prefix);
+ diff_no_index(&rev, argc, argv);
/* Otherwise, we are doing the usual "git" diff */
rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
@@ -394,7 +395,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
const char *name = entry->name;
int flags = (obj->flags & UNINTERESTING);
if (!obj->parsed)
- obj = parse_object(obj->sha1);
+ obj = parse_object(obj->oid.hash);
obj = deref_tag(obj, NULL, 0);
if (!obj)
die(_("invalid object '%s' given."), name);
@@ -407,7 +408,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
} else if (obj->type == OBJ_BLOB) {
if (2 <= blobs)
die(_("more than two blobs given: '%s'"), name);
- hashcpy(blob[blobs].sha1, obj->sha1);
+ hashcpy(blob[blobs].sha1, obj->oid.hash);
blob[blobs].name = name;
blob[blobs].mode = entry->mode;
blobs++;
diff --git a/builtin/difftool.c b/builtin/difftool.c
new file mode 100644
index 0000000000..d13350ce83
--- /dev/null
+++ b/builtin/difftool.c
@@ -0,0 +1,692 @@
+/*
+ * "git difftool" builtin command
+ *
+ * This is a wrapper around the GIT_EXTERNAL_DIFF-compatible
+ * git-difftool--helper script.
+ *
+ * This script exports GIT_EXTERNAL_DIFF and GIT_PAGER for use by git.
+ * The GIT_DIFF* variables are exported for use by git-difftool--helper.
+ *
+ * Any arguments that are unknown to this script are forwarded to 'git diff'.
+ *
+ * Copyright (C) 2016 Johannes Schindelin
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+#include "argv-array.h"
+#include "strbuf.h"
+#include "lockfile.h"
+#include "dir.h"
+
+static char *diff_gui_tool;
+static int trust_exit_code;
+
+static const char *const builtin_difftool_usage[] = {
+ N_("git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]"),
+ NULL
+};
+
+static int difftool_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "diff.guitool")) {
+ diff_gui_tool = xstrdup(value);
+ return 0;
+ }
+
+ if (!strcmp(var, "difftool.trustexitcode")) {
+ trust_exit_code = git_config_bool(var, value);
+ return 0;
+ }
+
+ return git_default_config(var, value, cb);
+}
+
+static int print_tool_help(void)
+{
+ const char *argv[] = { "mergetool", "--tool-help=diff", NULL };
+ return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int parse_index_info(char *p, int *mode1, int *mode2,
+ struct object_id *oid1, struct object_id *oid2,
+ char *status)
+{
+ if (*p != ':')
+ return error("expected ':', got '%c'", *p);
+ *mode1 = (int)strtol(p + 1, &p, 8);
+ if (*p != ' ')
+ return error("expected ' ', got '%c'", *p);
+ *mode2 = (int)strtol(p + 1, &p, 8);
+ if (*p != ' ')
+ return error("expected ' ', got '%c'", *p);
+ if (get_oid_hex(++p, oid1))
+ return error("expected object ID, got '%s'", p + 1);
+ p += GIT_SHA1_HEXSZ;
+ if (*p != ' ')
+ return error("expected ' ', got '%c'", *p);
+ if (get_oid_hex(++p, oid2))
+ return error("expected object ID, got '%s'", p + 1);
+ p += GIT_SHA1_HEXSZ;
+ if (*p != ' ')
+ return error("expected ' ', got '%c'", *p);
+ *status = *++p;
+ if (!*status)
+ return error("missing status");
+ if (p[1] && !isdigit(p[1]))
+ return error("unexpected trailer: '%s'", p + 1);
+ return 0;
+}
+
+/*
+ * Remove any trailing slash from $workdir
+ * before starting to avoid double slashes in symlink targets.
+ */
+static void add_path(struct strbuf *buf, size_t base_len, const char *path)
+{
+ strbuf_setlen(buf, base_len);
+ if (buf->len && buf->buf[buf->len - 1] != '/')
+ strbuf_addch(buf, '/');
+ strbuf_addstr(buf, path);
+}
+
+/*
+ * Determine whether we can simply reuse the file in the worktree.
+ */
+static int use_wt_file(const char *workdir, const char *name,
+ struct object_id *oid)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct stat st;
+ int use = 0;
+
+ strbuf_addstr(&buf, workdir);
+ add_path(&buf, buf.len, name);
+
+ if (!lstat(buf.buf, &st) && !S_ISLNK(st.st_mode)) {
+ struct object_id wt_oid;
+ int fd = open(buf.buf, O_RDONLY);
+
+ if (fd >= 0 &&
+ !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) {
+ if (is_null_oid(oid)) {
+ oidcpy(oid, &wt_oid);
+ use = 1;
+ } else if (!oidcmp(oid, &wt_oid))
+ use = 1;
+ }
+ }
+
+ strbuf_release(&buf);
+
+ return use;
+}
+
+struct working_tree_entry {
+ struct hashmap_entry entry;
+ char path[FLEX_ARRAY];
+};
+
+static int working_tree_entry_cmp(struct working_tree_entry *a,
+ struct working_tree_entry *b, void *keydata)
+{
+ return strcmp(a->path, b->path);
+}
+
+/*
+ * The `left` and `right` entries hold paths for the symlinks hashmap,
+ * and a SHA-1 surrounded by brief text for submodules.
+ */
+struct pair_entry {
+ struct hashmap_entry entry;
+ char left[PATH_MAX], right[PATH_MAX];
+ const char path[FLEX_ARRAY];
+};
+
+static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata)
+{
+ return strcmp(a->path, b->path);
+}
+
+static void add_left_or_right(struct hashmap *map, const char *path,
+ const char *content, int is_right)
+{
+ struct pair_entry *e, *existing;
+
+ FLEX_ALLOC_STR(e, path, path);
+ hashmap_entry_init(e, strhash(path));
+ existing = hashmap_get(map, e, NULL);
+ if (existing) {
+ free(e);
+ e = existing;
+ } else {
+ e->left[0] = e->right[0] = '\0';
+ hashmap_add(map, e);
+ }
+ strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
+}
+
+struct path_entry {
+ struct hashmap_entry entry;
+ char path[FLEX_ARRAY];
+};
+
+static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key)
+{
+ return strcmp(a->path, key ? key : b->path);
+}
+
+static void changed_files(struct hashmap *result, const char *index_path,
+ const char *workdir)
+{
+ struct child_process update_index = CHILD_PROCESS_INIT;
+ struct child_process diff_files = CHILD_PROCESS_INIT;
+ struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT;
+ const char *git_dir = absolute_path(get_git_dir()), *env[] = {
+ NULL, NULL
+ };
+ FILE *fp;
+
+ strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path);
+ env[0] = index_env.buf;
+
+ argv_array_pushl(&update_index.args,
+ "--git-dir", git_dir, "--work-tree", workdir,
+ "update-index", "--really-refresh", "-q",
+ "--unmerged", NULL);
+ update_index.no_stdin = 1;
+ update_index.no_stdout = 1;
+ update_index.no_stderr = 1;
+ update_index.git_cmd = 1;
+ update_index.use_shell = 0;
+ update_index.clean_on_exit = 1;
+ update_index.dir = workdir;
+ update_index.env = env;
+ /* Ignore any errors of update-index */
+ run_command(&update_index);
+
+ argv_array_pushl(&diff_files.args,
+ "--git-dir", git_dir, "--work-tree", workdir,
+ "diff-files", "--name-only", "-z", NULL);
+ diff_files.no_stdin = 1;
+ diff_files.git_cmd = 1;
+ diff_files.use_shell = 0;
+ diff_files.clean_on_exit = 1;
+ diff_files.out = -1;
+ diff_files.dir = workdir;
+ diff_files.env = env;
+ if (start_command(&diff_files))
+ die("could not obtain raw diff");
+ fp = xfdopen(diff_files.out, "r");
+ while (!strbuf_getline_nul(&buf, fp)) {
+ struct path_entry *entry;
+ FLEX_ALLOC_STR(entry, path, buf.buf);
+ hashmap_entry_init(entry, strhash(buf.buf));
+ hashmap_add(result, entry);
+ }
+ if (finish_command(&diff_files))
+ die("diff-files did not exit properly");
+ strbuf_release(&index_env);
+ strbuf_release(&buf);
+}
+
+static NORETURN void exit_cleanup(const char *tmpdir, int exit_code)
+{
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_addstr(&buf, tmpdir);
+ remove_dir_recursively(&buf, 0);
+ if (exit_code)
+ warning(_("failed: %d"), exit_code);
+ exit(exit_code);
+}
+
+static int ensure_leading_directories(char *path)
+{
+ switch (safe_create_leading_directories(path)) {
+ case SCLD_OK:
+ case SCLD_EXISTS:
+ return 0;
+ default:
+ return error(_("could not create leading directories "
+ "of '%s'"), path);
+ }
+}
+
+static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
+ int argc, const char **argv)
+{
+ char tmpdir[PATH_MAX];
+ struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT;
+ struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT;
+ struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT;
+ struct strbuf wtdir = STRBUF_INIT;
+ size_t ldir_len, rdir_len, wtdir_len;
+ struct cache_entry *ce = xcalloc(1, sizeof(ce) + PATH_MAX + 1);
+ const char *workdir, *tmp;
+ int ret = 0, i;
+ FILE *fp;
+ struct hashmap working_tree_dups, submodules, symlinks2;
+ struct hashmap_iter iter;
+ struct pair_entry *entry;
+ enum object_type type;
+ unsigned long size;
+ struct index_state wtindex;
+ struct checkout lstate, rstate;
+ int rc, flags = RUN_GIT_CMD, err = 0;
+ struct child_process child = CHILD_PROCESS_INIT;
+ const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL };
+ struct hashmap wt_modified, tmp_modified;
+ int indices_loaded = 0;
+
+ workdir = get_git_work_tree();
+
+ /* Setup temp directories */
+ tmp = getenv("TMPDIR");
+ xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp");
+ if (!mkdtemp(tmpdir))
+ return error("could not create '%s'", tmpdir);
+ strbuf_addf(&ldir, "%s/left/", tmpdir);
+ strbuf_addf(&rdir, "%s/right/", tmpdir);
+ strbuf_addstr(&wtdir, workdir);
+ if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1]))
+ strbuf_addch(&wtdir, '/');
+ mkdir(ldir.buf, 0700);
+ mkdir(rdir.buf, 0700);
+
+ memset(&wtindex, 0, sizeof(wtindex));
+
+ memset(&lstate, 0, sizeof(lstate));
+ lstate.base_dir = ldir.buf;
+ lstate.base_dir_len = ldir.len;
+ lstate.force = 1;
+ memset(&rstate, 0, sizeof(rstate));
+ rstate.base_dir = rdir.buf;
+ rstate.base_dir_len = rdir.len;
+ rstate.force = 1;
+
+ ldir_len = ldir.len;
+ rdir_len = rdir.len;
+ wtdir_len = wtdir.len;
+
+ hashmap_init(&working_tree_dups,
+ (hashmap_cmp_fn)working_tree_entry_cmp, 0);
+ hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0);
+ hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0);
+
+ child.no_stdin = 1;
+ child.git_cmd = 1;
+ child.use_shell = 0;
+ child.clean_on_exit = 1;
+ child.dir = prefix;
+ child.out = -1;
+ argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z",
+ NULL);
+ for (i = 0; i < argc; i++)
+ argv_array_push(&child.args, argv[i]);
+ if (start_command(&child))
+ die("could not obtain raw diff");
+ fp = xfdopen(child.out, "r");
+
+ /* Build index info for left and right sides of the diff */
+ i = 0;
+ while (!strbuf_getline_nul(&info, fp)) {
+ int lmode, rmode;
+ struct object_id loid, roid;
+ char status;
+ const char *src_path, *dst_path;
+ size_t src_path_len, dst_path_len;
+
+ if (starts_with(info.buf, "::"))
+ die(N_("combined diff formats('-c' and '--cc') are "
+ "not supported in\n"
+ "directory diff mode('-d' and '--dir-diff')."));
+
+ if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid,
+ &status))
+ break;
+ if (strbuf_getline_nul(&lpath, fp))
+ break;
+ src_path = lpath.buf;
+ src_path_len = lpath.len;
+
+ i++;
+ if (status != 'C' && status != 'R') {
+ dst_path = src_path;
+ dst_path_len = src_path_len;
+ } else {
+ if (strbuf_getline_nul(&rpath, fp))
+ break;
+ dst_path = rpath.buf;
+ dst_path_len = rpath.len;
+ }
+
+ if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) {
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "Subproject commit %s",
+ oid_to_hex(&loid));
+ add_left_or_right(&submodules, src_path, buf.buf, 0);
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "Subproject commit %s",
+ oid_to_hex(&roid));
+ if (!oidcmp(&loid, &roid))
+ strbuf_addstr(&buf, "-dirty");
+ add_left_or_right(&submodules, dst_path, buf.buf, 1);
+ continue;
+ }
+
+ if (S_ISLNK(lmode)) {
+ char *content = read_sha1_file(loid.hash, &type, &size);
+ add_left_or_right(&symlinks2, src_path, content, 0);
+ free(content);
+ }
+
+ if (S_ISLNK(rmode)) {
+ char *content = read_sha1_file(roid.hash, &type, &size);
+ add_left_or_right(&symlinks2, dst_path, content, 1);
+ free(content);
+ }
+
+ if (lmode && status != 'C') {
+ ce->ce_mode = lmode;
+ oidcpy(&ce->oid, &loid);
+ strcpy(ce->name, src_path);
+ ce->ce_namelen = src_path_len;
+ if (checkout_entry(ce, &lstate, NULL))
+ return error("could not write '%s'", src_path);
+ }
+
+ if (rmode) {
+ struct working_tree_entry *entry;
+
+ /* Avoid duplicate working_tree entries */
+ FLEX_ALLOC_STR(entry, path, dst_path);
+ hashmap_entry_init(entry, strhash(dst_path));
+ if (hashmap_get(&working_tree_dups, entry, NULL)) {
+ free(entry);
+ continue;
+ }
+ hashmap_add(&working_tree_dups, entry);
+
+ if (!use_wt_file(workdir, dst_path, &roid)) {
+ ce->ce_mode = rmode;
+ oidcpy(&ce->oid, &roid);
+ strcpy(ce->name, dst_path);
+ ce->ce_namelen = dst_path_len;
+ if (checkout_entry(ce, &rstate, NULL))
+ return error("could not write '%s'",
+ dst_path);
+ } else if (!is_null_oid(&roid)) {
+ /*
+ * Changes in the working tree need special
+ * treatment since they are not part of the
+ * index.
+ */
+ struct cache_entry *ce2 =
+ make_cache_entry(rmode, roid.hash,
+ dst_path, 0, 0);
+
+ add_index_entry(&wtindex, ce2,
+ ADD_CACHE_JUST_APPEND);
+
+ add_path(&rdir, rdir_len, dst_path);
+ if (ensure_leading_directories(rdir.buf))
+ return error("could not create "
+ "directory for '%s'",
+ dst_path);
+ add_path(&wtdir, wtdir_len, dst_path);
+ if (symlinks) {
+ if (symlink(wtdir.buf, rdir.buf)) {
+ ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf);
+ goto finish;
+ }
+ } else {
+ struct stat st;
+ if (stat(wtdir.buf, &st))
+ st.st_mode = 0644;
+ if (copy_file(rdir.buf, wtdir.buf,
+ st.st_mode)) {
+ ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf);
+ goto finish;
+ }
+ }
+ }
+ }
+ }
+
+ if (finish_command(&child)) {
+ ret = error("error occurred running diff --raw");
+ goto finish;
+ }
+
+ if (!i)
+ return 0;
+
+ /*
+ * Changes to submodules require special treatment.This loop writes a
+ * temporary file to both the left and right directories to show the
+ * change in the recorded SHA1 for the submodule.
+ */
+ hashmap_iter_init(&submodules, &iter);
+ while ((entry = hashmap_iter_next(&iter))) {
+ if (*entry->left) {
+ add_path(&ldir, ldir_len, entry->path);
+ ensure_leading_directories(ldir.buf);
+ write_file(ldir.buf, "%s", entry->left);
+ }
+ if (*entry->right) {
+ add_path(&rdir, rdir_len, entry->path);
+ ensure_leading_directories(rdir.buf);
+ write_file(rdir.buf, "%s", entry->right);
+ }
+ }
+
+ /*
+ * Symbolic links require special treatment.The standard "git diff"
+ * shows only the link itself, not the contents of the link target.
+ * This loop replicates that behavior.
+ */
+ hashmap_iter_init(&symlinks2, &iter);
+ while ((entry = hashmap_iter_next(&iter))) {
+ if (*entry->left) {
+ add_path(&ldir, ldir_len, entry->path);
+ ensure_leading_directories(ldir.buf);
+ write_file(ldir.buf, "%s", entry->left);
+ }
+ if (*entry->right) {
+ add_path(&rdir, rdir_len, entry->path);
+ ensure_leading_directories(rdir.buf);
+ write_file(rdir.buf, "%s", entry->right);
+ }
+ }
+
+ strbuf_release(&buf);
+
+ strbuf_setlen(&ldir, ldir_len);
+ helper_argv[1] = ldir.buf;
+ strbuf_setlen(&rdir, rdir_len);
+ helper_argv[2] = rdir.buf;
+
+ if (extcmd) {
+ helper_argv[0] = extcmd;
+ flags = 0;
+ } else
+ setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1);
+ rc = run_command_v_opt(helper_argv, flags);
+
+ /*
+ * If the diff includes working copy files and those
+ * files were modified during the diff, then the changes
+ * should be copied back to the working tree.
+ * Do not copy back files when symlinks are used and the
+ * external tool did not replace the original link with a file.
+ *
+ * These hashes are loaded lazily since they aren't needed
+ * in the common case of --symlinks and the difftool updating
+ * files through the symlink.
+ */
+ hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp,
+ wtindex.cache_nr);
+ hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp,
+ wtindex.cache_nr);
+
+ for (i = 0; i < wtindex.cache_nr; i++) {
+ struct hashmap_entry dummy;
+ const char *name = wtindex.cache[i]->name;
+ struct stat st;
+
+ add_path(&rdir, rdir_len, name);
+ if (lstat(rdir.buf, &st))
+ continue;
+
+ if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode))
+ continue;
+
+ if (!indices_loaded) {
+ static struct lock_file lock;
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s/wtindex", tmpdir);
+ if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+ write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
+ ret = error("could not write %s", buf.buf);
+ rollback_lock_file(&lock);
+ goto finish;
+ }
+ changed_files(&wt_modified, buf.buf, workdir);
+ strbuf_setlen(&rdir, rdir_len);
+ changed_files(&tmp_modified, buf.buf, rdir.buf);
+ add_path(&rdir, rdir_len, name);
+ indices_loaded = 1;
+ }
+
+ hashmap_entry_init(&dummy, strhash(name));
+ if (hashmap_get(&tmp_modified, &dummy, name)) {
+ add_path(&wtdir, wtdir_len, name);
+ if (hashmap_get(&wt_modified, &dummy, name)) {
+ warning(_("both files modified: '%s' and '%s'."),
+ wtdir.buf, rdir.buf);
+ warning(_("working tree file has been left."));
+ warning("%s", "");
+ err = 1;
+ } else if (unlink(wtdir.buf) ||
+ copy_file(wtdir.buf, rdir.buf, st.st_mode))
+ warning_errno(_("could not copy '%s' to '%s'"),
+ rdir.buf, wtdir.buf);
+ }
+ }
+
+ if (err) {
+ warning(_("temporary files exist in '%s'."), tmpdir);
+ warning(_("you may want to cleanup or recover these."));
+ exit(1);
+ } else
+ exit_cleanup(tmpdir, rc);
+
+finish:
+ free(ce);
+ strbuf_release(&ldir);
+ strbuf_release(&rdir);
+ strbuf_release(&wtdir);
+ strbuf_release(&buf);
+
+ return ret;
+}
+
+static int run_file_diff(int prompt, const char *prefix,
+ int argc, const char **argv)
+{
+ struct argv_array args = ARGV_ARRAY_INIT;
+ const char *env[] = {
+ "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL,
+ NULL
+ };
+ int ret = 0, i;
+
+ if (prompt > 0)
+ env[2] = "GIT_DIFFTOOL_PROMPT=true";
+ else if (!prompt)
+ env[2] = "GIT_DIFFTOOL_NO_PROMPT=true";
+
+
+ argv_array_push(&args, "diff");
+ for (i = 0; i < argc; i++)
+ argv_array_push(&args, argv[i]);
+ ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env);
+ exit(ret);
+}
+
+int cmd_difftool(int argc, const char **argv, const char *prefix)
+{
+ int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
+ tool_help = 0;
+ static char *difftool_cmd = NULL, *extcmd = NULL;
+ struct option builtin_difftool_options[] = {
+ OPT_BOOL('g', "gui", &use_gui_tool,
+ N_("use `diff.guitool` instead of `diff.tool`")),
+ OPT_BOOL('d', "dir-diff", &dir_diff,
+ N_("perform a full-directory diff")),
+ { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL,
+ N_("do not prompt before launching a diff tool"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+ { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL,
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,
+ NULL, 1 },
+ OPT_BOOL(0, "symlinks", &symlinks,
+ N_("use symlinks in dir-diff mode")),
+ OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"),
+ N_("use the specified diff tool")),
+ OPT_BOOL(0, "tool-help", &tool_help,
+ N_("print a list of diff tools that may be used with "
+ "`--tool`")),
+ OPT_BOOL(0, "trust-exit-code", &trust_exit_code,
+ N_("make 'git-difftool' exit when an invoked diff "
+ "tool returns a non - zero exit code")),
+ OPT_STRING('x', "extcmd", &extcmd, N_("<command>"),
+ N_("specify a custom command for viewing diffs")),
+ OPT_END()
+ };
+
+ git_config(difftool_config, NULL);
+ symlinks = has_symlinks;
+
+ argc = parse_options(argc, argv, prefix, builtin_difftool_options,
+ builtin_difftool_usage, PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_DASHDASH);
+
+ if (tool_help)
+ return print_tool_help();
+
+ /* NEEDSWORK: once we no longer spawn anything, remove this */
+ setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
+ setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
+
+ if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
+ setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
+ else if (difftool_cmd) {
+ if (*difftool_cmd)
+ setenv("GIT_DIFF_TOOL", difftool_cmd, 1);
+ else
+ die(_("no <tool> given for --tool=<tool>"));
+ }
+
+ if (extcmd) {
+ if (*extcmd)
+ setenv("GIT_DIFFTOOL_EXTCMD", extcmd, 1);
+ else
+ die(_("no <cmd> given for --extcmd=<cmd>"));
+ }
+
+ setenv("GIT_DIFFTOOL_TRUST_EXIT_CODE",
+ trust_exit_code ? "true" : "false", 1);
+
+ /*
+ * In directory diff mode, 'git-difftool--helper' is called once
+ * to compare the a / b directories. In file diff mode, 'git diff'
+ * will invoke a separate instance of 'git-difftool--helper' for
+ * each file that changed.
+ */
+ if (dir_diff)
+ return run_dir_diff(extcmd, symlinks, prefix, argc, argv);
+ return run_file_diff(prompt, prefix, argc, argv);
+}
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index b8d8a3aaf9..1e815b5577 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -5,6 +5,7 @@
*/
#include "builtin.h"
#include "cache.h"
+#include "refs.h"
#include "commit.h"
#include "object.h"
#include "tag.h"
@@ -17,6 +18,8 @@
#include "utf8.h"
#include "parse-options.h"
#include "quote.h"
+#include "remote.h"
+#include "blob.h"
static const char *fast_export_usage[] = {
N_("git fast-export [rev-list-opts]"),
@@ -31,6 +34,9 @@ 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)
@@ -78,6 +84,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)
{
@@ -116,6 +192,26 @@ static void show_progress(void)
printf("progress %d objects\n", counter);
}
+/*
+ * 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;
@@ -134,12 +230,19 @@ static void export_blob(const unsigned char *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 (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 (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));
@@ -187,7 +290,7 @@ 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)
@@ -198,6 +301,43 @@ static void print_path(const char *path)
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)
{
@@ -207,7 +347,7 @@ static void show_filemodify(struct diff_queue_struct *q,
* Handle files below a directory first, in case they are all deleted
* and the directory changes to a file or symlink.
*/
- qsort(q->queue, q->nr, sizeof(q->queue[0]), depth_first);
+ QSORT(q->queue, q->nr, depth_first);
for (i = 0; i < q->nr; i++) {
struct diff_filespec *ospec = q->queue[i]->one;
@@ -228,7 +368,7 @@ static void show_filemodify(struct diff_queue_struct *q,
print_path(spec->path);
putchar('\n');
- if (!hashcmp(ospec->sha1, spec->sha1) &&
+ if (!oidcmp(&ospec->oid, &spec->oid) &&
ospec->mode == spec->mode)
break;
/* fallthrough */
@@ -242,9 +382,11 @@ 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->oid.hash) :
+ spec->oid.hash));
else {
- struct object *object = lookup_object(spec->sha1);
+ struct object *object = lookup_object(spec->oid.hash);
printf("M %06o :%d ", spec->mode,
get_object_mark(object));
}
@@ -276,28 +418,139 @@ 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_or_die(commit);
- author = strstr(commit->buffer, "\nauthor ");
+ 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));
+ oid_to_hex(&commit->object.oid));
author++;
author_end = strchrnul(author, '\n');
committer = strstr(author_end, "\ncommitter ");
if (!committer)
die ("Could not find committer in commit %s",
- sha1_to_hex(commit->object.sha1));
+ oid_to_hex(&commit->object.oid));
committer++;
committer_end = strchrnul(committer, '\n');
message = strstr(committer_end, "\n\n");
@@ -309,25 +562,34 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
get_object_mark(&commit->parents->item->object) != 0 &&
!full_tree) {
parse_commit_or_die(commit->parents->item);
- diff_tree_sha1(commit->parents->item->tree->object.sha1,
- commit->tree->object.sha1, "", &rev->diffopt);
+ diff_tree_sha1(commit->parents->item->tree->object.oid.hash,
+ commit->tree->object.oid.hash, "", &rev->diffopt);
}
else
- diff_root_tree_sha1(commit->tree->object.sha1,
+ diff_root_tree_sha1(commit->tree->object.oid.hash,
"", &rev->diffopt);
/* 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))
- export_blob(diff_queued_diff.queue[i]->two->sha1);
+ export_blob(diff_queued_diff.queue[i]->two->oid.hash);
+
+ 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
@@ -335,6 +597,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);
@@ -357,6 +620,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;
@@ -390,13 +661,13 @@ static void handle_tag(const char *name, struct tag *tag)
}
if (tagged->type == OBJ_TREE) {
warning("Omitting tag %s,\nsince tags of trees (or tags of tags of trees, etc.) are not supported.",
- sha1_to_hex(tag->object.sha1));
+ oid_to_hex(&tag->object.oid));
return;
}
- buf = read_sha1_file(tag->object.sha1, &type, &size);
+ buf = read_sha1_file(tag->object.oid.hash, &type, &size);
if (!buf)
- die ("Could not read tag %s", sha1_to_hex(tag->object.sha1));
+ die ("Could not read tag %s", oid_to_hex(&tag->object.oid));
message = memmem(buf, size, "\n\n", 2);
if (message) {
message += 2;
@@ -413,6 +684,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 */
@@ -424,16 +706,16 @@ static void handle_tag(const char *name, struct tag *tag)
case ABORT:
die ("Encountered signed tag %s; use "
"--signed-tags=<mode> to handle it.",
- sha1_to_hex(tag->object.sha1));
+ oid_to_hex(&tag->object.oid));
case WARN:
warning ("Exporting signed tag %s",
- sha1_to_hex(tag->object.sha1));
+ oid_to_hex(&tag->object.oid));
/* fallthru */
case VERBATIM:
break;
case WARN_STRIP:
warning ("Stripping signature from tag %s",
- sha1_to_hex(tag->object.sha1));
+ oid_to_hex(&tag->object.oid));
/* fallthru */
case STRIP:
message_size = signature + 1 - message;
@@ -449,14 +731,14 @@ static void handle_tag(const char *name, struct tag *tag)
case ABORT:
die ("Tag %s tags unexported object; use "
"--tag-of-filtered-object=<mode> to handle it.",
- sha1_to_hex(tag->object.sha1));
+ oid_to_hex(&tag->object.oid));
case DROP:
/* Ignore this tag altogether */
return;
case REWRITE:
if (tagged->type != OBJ_COMMIT) {
die ("Tag %s tags unexported %s!",
- sha1_to_hex(tag->object.sha1),
+ oid_to_hex(&tag->object.oid),
typename(tagged->type));
}
p = (struct commit *)tagged;
@@ -469,7 +751,7 @@ static void handle_tag(const char *name, struct tag *tag)
break;
if (!p->parents)
die ("Can't find replacement commit for tag %s\n",
- sha1_to_hex(tag->object.sha1));
+ oid_to_hex(&tag->object.oid));
p = p->parents->item;
}
tagged_mark = get_object_mark(&p->object);
@@ -495,7 +777,7 @@ static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)
/* handle nested tags */
while (tag && tag->object.type == OBJ_TAG) {
- parse_object(tag->object.sha1);
+ parse_object(tag->object.oid.hash);
string_list_append(&extra_refs, full_name)->util = tag;
tag = (struct tag *)tag->tagged;
}
@@ -525,6 +807,15 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
continue;
+ if (refspecs) {
+ char *private;
+ private = apply_refspecs(refspecs, refspecs_nr, full_name);
+ if (private) {
+ free(full_name);
+ full_name = private;
+ }
+ }
+
commit = get_commit(e, full_name);
if (!commit) {
warning("%s: Unexpected object of type %s, skipping.",
@@ -537,7 +828,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
case OBJ_COMMIT:
break;
case OBJ_BLOB:
- export_blob(commit->object.sha1);
+ export_blob(commit->object.oid.hash);
continue;
default: /* OBJ_TAG (nested tags) is already handled */
warning("Tag points to object of unexpected type %s, skipping.",
@@ -569,6 +860,8 @@ static void handle_tags_and_duplicates(void)
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,
@@ -587,7 +880,7 @@ static void export_marks(char *file)
FILE *f;
int e = 0;
- f = fopen(file, "w");
+ f = fopen_for_writing(file);
if (!f)
die_errno("Unable to open marks file %s for writing.", file);
@@ -595,7 +888,7 @@ static void export_marks(char *file)
if (deco->base && deco->base->type == 1) {
mark = ptr_to_mark(deco->decoration);
if (fprintf(f, ":%"PRIu32" %s\n", mark,
- sha1_to_hex(deco->base->sha1)) < 0) {
+ oid_to_hex(&deco->base->oid)) < 0) {
e = 1;
break;
}
@@ -661,6 +954,19 @@ static void import_marks(char *input_file)
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;
@@ -668,6 +974,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
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,
N_("show progress after <n> objects")),
@@ -688,6 +995,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
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()
};
@@ -701,11 +1011,27 @@ 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;
+
+ ALLOC_ARRAY(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");
@@ -733,6 +1059,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
}
handle_tags_and_duplicates();
+ handle_deletes();
if (export_filename && lastimportid != last_idnum)
export_marks(export_filename);
@@ -740,5 +1067,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
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 1262b405f8..cfe9e447c2 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -6,35 +6,38 @@
#include "sha1-array.h"
static const char fetch_pack_usage[] =
-"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
+"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 add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc,
- const char *name, int namelen)
+static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,
+ const char *name)
{
- struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1);
- unsigned char sha1[20];
-
- if (namelen > 41 && name[40] == ' ' && !get_sha1_hex(name, sha1)) {
- hashcpy(ref->old_sha1, sha1);
- name += 41;
- namelen -= 41;
+ struct ref *ref;
+ struct object_id oid;
+
+ if (!get_oid_hex(name, &oid)) {
+ if (name[GIT_SHA1_HEXSZ] == ' ') {
+ /* <sha1> <ref>, find refname */
+ name += GIT_SHA1_HEXSZ + 1;
+ } else if (name[GIT_SHA1_HEXSZ] == '\0') {
+ ; /* <sha1>, leave sha1 as name */
+ } else {
+ /* <ref>, clear cruft from oid */
+ oidclr(&oid);
+ }
+ } else {
+ /* <ref>, clear cruft from get_oid_hex */
+ oidclr(&oid);
}
- memcpy(ref->name, name, namelen);
- ref->name[namelen] = '\0';
+ ref = alloc_ref(name);
+ oidcpy(&ref->old_oid, &oid);
(*nr)++;
ALLOC_GROW(*sought, *nr, *alloc);
(*sought)[*nr - 1] = ref;
}
-static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,
- const char *string)
-{
- add_sought_entry_mem(sought, nr, alloc, string, strlen(string));
-}
-
int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
{
int i, ret;
@@ -48,6 +51,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
struct child_process *conn;
struct fetch_pack_args args;
struct sha1_array shallow = SHA1_ARRAY_INIT;
+ struct string_list deepen_not = STRING_LIST_INIT_DUP;
packet_trace_identity("fetch-pack");
@@ -57,12 +61,12 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
for (i = 1; i < argc && *argv[i] == '-'; i++) {
const char *arg = argv[i];
- if (starts_with(arg, "--upload-pack=")) {
- args.uploadpack = arg + 14;
+ if (skip_prefix(arg, "--upload-pack=", &arg)) {
+ args.uploadpack = arg;
continue;
}
- if (starts_with(arg, "--exec=")) {
- args.uploadpack = arg + 7;
+ if (skip_prefix(arg, "--exec=", &arg)) {
+ args.uploadpack = arg;
continue;
}
if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
@@ -98,8 +102,20 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
args.verbose = 1;
continue;
}
- if (starts_with(arg, "--depth=")) {
- args.depth = strtol(arg + 8, NULL, 0);
+ if (skip_prefix(arg, "--depth=", &arg)) {
+ args.depth = strtol(arg, NULL, 0);
+ continue;
+ }
+ if (skip_prefix(arg, "--shallow-since=", &arg)) {
+ args.deepen_since = xstrdup(arg);
+ continue;
+ }
+ if (skip_prefix(arg, "--shallow-exclude=", &arg)) {
+ string_list_append(&deepen_not, arg);
+ continue;
+ }
+ if (!strcmp(arg, "--deepen-relative")) {
+ args.deepen_relative = 1;
continue;
}
if (!strcmp("--no-progress", arg)) {
@@ -129,6 +145,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
}
usage(fetch_pack_usage);
}
+ if (deepen_not.nr)
+ args.deepen_not = &deepen_not;
if (i < argc)
dest = argv[i++];
@@ -156,7 +174,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
else {
/* read from stdin one ref per line, until EOF */
struct strbuf line = STRBUF_INIT;
- while (strbuf_getline(&line, stdin, '\n') != EOF)
+ while (strbuf_getline_lf(&line, stdin) != EOF)
add_sought_entry(&sought, &nr_sought, &alloc_sought, line.buf);
strbuf_release(&line);
}
@@ -210,7 +228,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
while (ref) {
printf("%s %s\n",
- sha1_to_hex(ref->old_sha1), ref->name);
+ oid_to_hex(&ref->old_oid), ref->name);
ref = ref->next;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 55f457c04f..b5ad09d046 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -11,10 +11,11 @@
#include "run-command.h"
#include "parse-options.h"
#include "sigchain.h"
-#include "transport.h"
+#include "submodule-config.h"
#include "submodule.h"
#include "connected.h"
#include "argv-array.h"
+#include "utf8.h"
static const char * const builtin_fetch_usage[] = {
N_("git fetch [<options>] [<repository> [<refspec>...]]"),
@@ -34,17 +35,23 @@ 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 all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
-static int tags = TAGS_DEFAULT, unshallow, update_shallow;
+static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
+static int max_children = -1;
+static enum transport_family family;
static const char *depth;
+static const char *deepen_since;
static const char *upload_pack;
+static struct string_list deepen_not = STRING_LIST_INIT_NODUP;
static struct strbuf default_rla = STRBUF_INIT;
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)
@@ -66,6 +73,19 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
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;
}
@@ -84,6 +104,8 @@ static struct option builtin_fetch_options[] = {
N_("fetch all tags and associated objects"), TAGS_SET),
OPT_SET_INT('n', NULL, &tags,
N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
+ OPT_INTEGER('j', "jobs", &max_children,
+ N_("number of submodules fetched in parallel")),
OPT_BOOL('p', "prune", &prune,
N_("prune remote-tracking branches no longer on remote")),
{ OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"),
@@ -97,6 +119,12 @@ static struct option builtin_fetch_options[] = {
OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
OPT_STRING(0, "depth", &depth, N_("depth"),
N_("deepen history of shallow clone")),
+ OPT_STRING(0, "shallow-since", &deepen_since, N_("time"),
+ N_("deepen history of shallow repository based on time")),
+ OPT_STRING_LIST(0, "shallow-exclude", &deepen_not, N_("revision"),
+ N_("deepen history of shallow clone, excluding rev")),
+ OPT_INTEGER(0, "deepen", &deepen_relative,
+ 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 },
@@ -107,6 +135,12 @@ static struct option builtin_fetch_options[] = {
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_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
+ TRANSPORT_FAMILY_IPV4),
+ OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
+ TRANSPORT_FAMILY_IPV6),
OPT_END()
};
@@ -163,13 +197,15 @@ static void add_merge_config(struct ref **head,
}
}
-static int add_existing(const char *refname, const unsigned char *sha1,
+static int add_existing(const char *refname, const struct object_id *oid,
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);
+ struct object_id *old_oid = xmalloc(sizeof(*old_oid));
+
+ oidcpy(old_oid, oid);
+ item->util = old_oid;
return 0;
}
@@ -177,7 +213,7 @@ static int will_fetch(struct ref **head, const unsigned char *sha1)
{
struct ref *rm = *head;
while (rm) {
- if (!hashcmp(rm->old_sha1, sha1))
+ if (!hashcmp(rm->old_oid.hash, sha1))
return 1;
rm = rm->next;
}
@@ -205,9 +241,10 @@ static void find_non_local_tags(struct transport *transport,
* 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) &&
+ if (item &&
+ !has_object_file_with_flags(&ref->old_oid, HAS_SHA1_QUICK) &&
+ !will_fetch(head, ref->old_oid.hash) &&
+ !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) &&
!will_fetch(head, item->util))
item->util = NULL;
item = NULL;
@@ -220,7 +257,8 @@ static void find_non_local_tags(struct transport *transport,
* to check if it is a lightweight tag that we want to
* fetch.
*/
- if (item && !has_sha1_file(item->util) &&
+ if (item &&
+ !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) &&
!will_fetch(head, item->util))
item->util = NULL;
@@ -232,7 +270,7 @@ static void find_non_local_tags(struct transport *transport,
continue;
item = string_list_insert(&remote_refs, ref->name);
- item->util = (void *)ref->old_sha1;
+ item->util = (void *)&ref->old_oid;
}
string_list_clear(&existing_refs, 1);
@@ -240,7 +278,8 @@ static void find_non_local_tags(struct transport *transport,
* 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) &&
+ if (item &&
+ !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) &&
!will_fetch(head, item->util))
item->util = NULL;
@@ -254,7 +293,7 @@ static void find_non_local_tags(struct transport *transport,
{
struct ref *rm = alloc_ref(item->string);
rm->peer_ref = alloc_ref(item->string);
- hashcpy(rm->old_sha1, item->util);
+ oidcpy(&rm->old_oid, item->util);
**tail = rm;
*tail = &rm->next;
}
@@ -278,6 +317,9 @@ static struct ref *get_ref_map(struct transport *transport,
const struct ref *remote_refs = transport_get_remote_refs(transport);
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])
@@ -307,12 +349,18 @@ static struct ref *get_ref_map(struct transport *transport,
* by ref_remove_duplicates() in favor of one of these
* opportunistic entries with FETCH_HEAD_IGNORE.
*/
- for (i = 0; i < transport->remote->fetch_refspec_nr; i++)
- get_fetch_map(ref_map, &transport->remote->fetch[i],
- &oref_tail, 1);
+ if (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;
+ }
- if (tags == TAGS_SET)
- get_fetch_map(remote_refs, tag_refspec, &tail, 0);
+ for (i = 0; i < fetch_refspec_nr; i++)
+ get_fetch_map(ref_map, &fetch_refspec[i], &oref_tail, 1);
+ } else if (refmap_array) {
+ die("--refmap option is only meaningful with command-line refspec(s).");
} else {
/* Use the defaults */
struct remote *remote = transport->remote;
@@ -375,78 +423,219 @@ 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, NULL);
- 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_oid.hash,
+ check_old ? ref->old_oid.hash : NULL,
+ 0, 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 refcol_width = 10;
+static int compact_format;
+
+static void adjust_refcol_width(const struct ref *ref)
+{
+ int max, rlen, llen, len;
+
+ /* uptodate lines are only shown on high verbosity level */
+ if (!verbosity && !oidcmp(&ref->peer_ref->old_oid, &ref->old_oid))
+ return;
+
+ max = term_columns();
+ rlen = utf8_strwidth(prettify_refname(ref->name));
+
+ llen = utf8_strwidth(prettify_refname(ref->peer_ref->name));
+
+ /*
+ * rough estimation to see if the output line is too long and
+ * should not be counted (we can't do precise calculation
+ * anyway because we don't know if the error explanation part
+ * will be printed in update_local_ref)
+ */
+ if (compact_format) {
+ llen = 0;
+ max = max * 2 / 3;
+ }
+ len = 21 /* flag and summary */ + rlen + 4 /* -> */ + llen;
+ if (len >= max)
+ return;
+
+ /*
+ * Not precise calculation for compact mode because '*' can
+ * appear on the left hand side of '->' and shrink the column
+ * back.
+ */
+ if (refcol_width < rlen)
+ refcol_width = rlen;
+}
+
+static void prepare_format_display(struct ref *ref_map)
+{
+ struct ref *rm;
+ const char *format = "full";
+
+ git_config_get_string_const("fetch.output", &format);
+ if (!strcasecmp(format, "full"))
+ compact_format = 0;
+ else if (!strcasecmp(format, "compact"))
+ compact_format = 1;
+ else
+ die(_("configuration fetch.output contains invalid value %s"),
+ format);
+
+ for (rm = ref_map; rm; rm = rm->next) {
+ if (rm->status == REF_STATUS_REJECT_SHALLOW ||
+ !rm->peer_ref ||
+ !strcmp(rm->name, "HEAD"))
+ continue;
+
+ adjust_refcol_width(rm);
+ }
+}
+
+static void print_remote_to_local(struct strbuf *display,
+ const char *remote, const char *local)
+{
+ strbuf_addf(display, "%-*s -> %s", refcol_width, remote, local);
+}
+
+static int find_and_replace(struct strbuf *haystack,
+ const char *needle,
+ const char *placeholder)
+{
+ const char *p = strstr(haystack->buf, needle);
+ int plen, nlen;
+
+ if (!p)
+ return 0;
+
+ if (p > haystack->buf && p[-1] != '/')
+ return 0;
+
+ plen = strlen(p);
+ nlen = strlen(needle);
+ if (plen > nlen && p[nlen] != '/')
+ return 0;
+
+ strbuf_splice(haystack, p - haystack->buf, nlen,
+ placeholder, strlen(placeholder));
+ return 1;
+}
+
+static void print_compact(struct strbuf *display,
+ const char *remote, const char *local)
+{
+ struct strbuf r = STRBUF_INIT;
+ struct strbuf l = STRBUF_INIT;
+
+ if (!strcmp(remote, local)) {
+ strbuf_addf(display, "%-*s -> *", refcol_width, remote);
+ return;
+ }
+
+ strbuf_addstr(&r, remote);
+ strbuf_addstr(&l, local);
+
+ if (!find_and_replace(&r, local, "*"))
+ find_and_replace(&l, remote, "*");
+ print_remote_to_local(display, r.buf, l.buf);
+
+ strbuf_release(&r);
+ strbuf_release(&l);
+}
+
+static void format_display(struct strbuf *display, char code,
+ const char *summary, const char *error,
+ const char *remote, const char *local,
+ int summary_width)
+{
+ int width = (summary_width + strlen(summary) - gettext_width(summary));
+
+ strbuf_addf(display, "%c %-*s ", code, width, summary);
+ if (!compact_format)
+ print_remote_to_local(display, remote, local);
+ else
+ print_compact(display, remote, local);
+ if (error)
+ strbuf_addf(display, " (%s)", error);
+}
static int update_local_ref(struct ref *ref,
const char *remote,
const struct ref *remote_ref,
- struct strbuf *display)
+ struct strbuf *display,
+ int summary_width)
{
struct commit *current = NULL, *updated;
enum object_type type;
struct branch *current_branch = branch_get(NULL);
const char *pretty_ref = prettify_refname(ref->name);
- type = sha1_object_info(ref->new_sha1, NULL);
+ type = sha1_object_info(ref->new_oid.hash, NULL);
if (type < 0)
- die(_("object %s not found"), sha1_to_hex(ref->new_sha1));
+ die(_("object %s not found"), oid_to_hex(&ref->new_oid));
- if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
+ if (!oidcmp(&ref->old_oid, &ref->new_oid)) {
if (verbosity > 0)
- strbuf_addf(display, "= %-*s %-*s -> %s",
- TRANSPORT_SUMMARY(_("[up to date]")),
- REFCOL_WIDTH, remote, pretty_ref);
+ format_display(display, '=', _("[up to date]"), NULL,
+ remote, pretty_ref, summary_width);
return 0;
}
if (current_branch &&
!strcmp(ref->name, current_branch->name) &&
!(update_head_ok || is_bare_repository()) &&
- !is_null_sha1(ref->old_sha1)) {
+ !is_null_oid(&ref->old_oid)) {
/*
* If this is the head, and it's not okay to update
* the head, and the old value of the head isn't empty...
*/
- strbuf_addf(display,
- _("! %-*s %-*s -> %s (can't fetch in current branch)"),
- TRANSPORT_SUMMARY(_("[rejected]")),
- REFCOL_WIDTH, remote, pretty_ref);
+ format_display(display, '!', _("[rejected]"),
+ _("can't fetch in current branch"),
+ remote, pretty_ref, summary_width);
return 1;
}
- if (!is_null_sha1(ref->old_sha1) &&
+ if (!is_null_oid(&ref->old_oid) &&
starts_with(ref->name, "refs/tags/")) {
int r;
r = s_update_ref("updating tag", ref, 0);
- strbuf_addf(display, "%c %-*s %-*s -> %s%s",
- r ? '!' : '-',
- TRANSPORT_SUMMARY(_("[tag update]")),
- REFCOL_WIDTH, remote, pretty_ref,
- r ? _(" (unable to update local ref)") : "");
+ format_display(display, r ? '!' : 't', _("[tag update]"),
+ r ? _("unable to update local ref") : NULL,
+ remote, pretty_ref, summary_width);
return r;
}
- current = lookup_commit_reference_gently(ref->old_sha1, 1);
- updated = lookup_commit_reference_gently(ref->new_sha1, 1);
+ current = lookup_commit_reference_gently(ref->old_oid.hash, 1);
+ updated = lookup_commit_reference_gently(ref->new_oid.hash, 1);
if (!current || !updated) {
const char *msg;
const char *what;
@@ -470,53 +659,47 @@ static int update_local_ref(struct ref *ref,
if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
(recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(ref->new_sha1);
+ check_for_new_submodule_commits(ref->new_oid.hash);
r = s_update_ref(msg, ref, 0);
- strbuf_addf(display, "%c %-*s %-*s -> %s%s",
- r ? '!' : '*',
- TRANSPORT_SUMMARY(what),
- REFCOL_WIDTH, remote, pretty_ref,
- r ? _(" (unable to update local ref)") : "");
+ format_display(display, r ? '!' : '*', what,
+ r ? _("unable to update local ref") : NULL,
+ remote, pretty_ref, summary_width);
return r;
}
if (in_merge_bases(current, updated)) {
- char quickref[83];
+ struct strbuf quickref = STRBUF_INIT;
int r;
- strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
- strcat(quickref, "..");
- strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ strbuf_add_unique_abbrev(&quickref, current->object.oid.hash, DEFAULT_ABBREV);
+ strbuf_addstr(&quickref, "..");
+ strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash, DEFAULT_ABBREV);
if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
(recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(ref->new_sha1);
+ check_for_new_submodule_commits(ref->new_oid.hash);
r = s_update_ref("fast-forward", ref, 1);
- strbuf_addf(display, "%c %-*s %-*s -> %s%s",
- r ? '!' : ' ',
- TRANSPORT_SUMMARY_WIDTH, quickref,
- REFCOL_WIDTH, remote, pretty_ref,
- r ? _(" (unable to update local ref)") : "");
+ format_display(display, r ? '!' : ' ', quickref.buf,
+ r ? _("unable to update local ref") : NULL,
+ remote, pretty_ref, summary_width);
+ strbuf_release(&quickref);
return r;
} else if (force || ref->force) {
- char quickref[84];
+ struct strbuf quickref = STRBUF_INIT;
int r;
- strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
- strcat(quickref, "...");
- strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+ strbuf_add_unique_abbrev(&quickref, current->object.oid.hash, DEFAULT_ABBREV);
+ strbuf_addstr(&quickref, "...");
+ strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash, DEFAULT_ABBREV);
if ((recurse_submodules != RECURSE_SUBMODULES_OFF) &&
(recurse_submodules != RECURSE_SUBMODULES_ON))
- check_for_new_submodule_commits(ref->new_sha1);
+ check_for_new_submodule_commits(ref->new_oid.hash);
r = s_update_ref("forced-update", ref, 1);
- 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"));
+ format_display(display, r ? '!' : '+', quickref.buf,
+ r ? _("unable to update local ref") : _("forced update"),
+ remote, pretty_ref, summary_width);
+ strbuf_release(&quickref);
return r;
} else {
- strbuf_addf(display, "! %-*s %-*s -> %s %s",
- TRANSPORT_SUMMARY(_("[rejected]")),
- REFCOL_WIDTH, remote, pretty_ref,
- _("(non-fast-forward)"));
+ format_display(display, '!', _("[rejected]"), _("non-fast-forward"),
+ remote, pretty_ref, summary_width);
return 1;
}
}
@@ -531,7 +714,7 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
if (!ref)
return -1; /* end of the list */
*rm = ref->next;
- hashcpy(sha1, ref->old_sha1);
+ hashcpy(sha1, ref->old_oid.hash);
return 0;
}
@@ -544,12 +727,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
struct strbuf note = STRBUF_INIT;
const char *what, *kind;
struct ref *rm;
- char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD");
+ char *url;
+ const char *filename = dry_run ? "/dev/null" : git_path_fetch_head();
int want_status;
+ int summary_width = transport_summary_width(ref_map);
fp = fopen(filename, "a");
if (!fp)
- return error(_("cannot open %s: %s\n"), filename, strerror(errno));
+ return error_errno(_("cannot open %s"), filename);
if (raw_url)
url = transport_anonymize_url(raw_url);
@@ -557,11 +742,13 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
url = xstrdup("foreign");
rm = ref_map;
- if (check_everything_connected(iterate_ref_map, 0, &rm)) {
+ if (check_connected(iterate_ref_map, &rm, NULL)) {
rc = error(_("%s did not send all necessary objects\n"), url);
goto abort;
}
+ prepare_format_display(ref_map);
+
/*
* 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
@@ -581,7 +768,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
continue;
}
- commit = lookup_commit_reference_gently(rm->old_sha1, 1);
+ commit = lookup_commit_reference_gently(rm->old_oid.hash, 1);
if (!commit)
rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
@@ -589,10 +776,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
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 = alloc_ref(rm->peer_ref->name);
+ oidcpy(&ref->old_oid, &rm->peer_ref->old_oid);
+ oidcpy(&ref->new_oid, &rm->old_oid);
ref->force = rm->peer_ref->force;
}
@@ -637,7 +823,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
/* fall-through */
case FETCH_HEAD_MERGE:
fprintf(fp, "%s\t%s\t%s",
- sha1_to_hex(rm->old_sha1),
+ oid_to_hex(&rm->old_oid),
merge_status_marker,
note.buf);
for (i = 0; i < url_len; ++i)
@@ -654,14 +840,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
strbuf_reset(&note);
if (ref) {
- rc |= update_local_ref(ref, what, rm, &note);
+ rc |= update_local_ref(ref, what, rm, &note,
+ summary_width);
free(ref);
} else
- strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
- TRANSPORT_SUMMARY_WIDTH,
- *kind ? kind : "branch",
- REFCOL_WIDTH,
- *what ? what : "HEAD");
+ format_display(&note, '*',
+ *kind ? kind : "branch", NULL,
+ *what ? what : "HEAD",
+ "FETCH_HEAD", summary_width);
if (note.len) {
if (verbosity >= 0 && !shown_url) {
fprintf(stderr, _("From %.*s\n"),
@@ -694,6 +880,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
static int quickfetch(struct ref *ref_map)
{
struct ref *rm = ref_map;
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
/*
* If we are deepening a shallow clone we already have these
@@ -702,9 +889,10 @@ static int quickfetch(struct ref *ref_map)
* really need to perform. Claiming failure now will ensure
* we perform the network exchange to deepen our history.
*/
- if (depth)
+ if (deepen)
return -1;
- return check_everything_connected(iterate_ref_map, 1, &rm);
+ opt.quiet = 1;
+ return check_connected(iterate_ref_map, &rm, &opt);
}
static int fetch_refs(struct transport *transport, struct ref *ref_map)
@@ -726,6 +914,7 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
int url_len, i, result = 0;
struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
char *url;
+ int summary_width = transport_summary_width(stale_refs);
const char *dangling_msg = dry_run
? _(" (%s will become dangling)")
: _(" (%s has become dangling)");
@@ -743,20 +932,32 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
if (4 < i && !strncmp(".git", url + i - 3, 4))
url_len = i - 3;
- for (ref = stale_refs; ref; ref = ref->next) {
- if (!dry_run)
- result |= delete_ref(ref->name, NULL, 0);
- if (verbosity >= 0 && !shown_url) {
- fprintf(stderr, _("From %.*s\n"), url_len, url);
- shown_url = 1;
- }
- if (verbosity >= 0) {
- fprintf(stderr, " x %-*s %-*s -> %s\n",
- TRANSPORT_SUMMARY(_("[deleted]")),
- REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name));
+ if (!dry_run) {
+ struct string_list refnames = STRING_LIST_INIT_NODUP;
+
+ for (ref = stale_refs; ref; ref = ref->next)
+ string_list_append(&refnames, ref->name);
+
+ result = delete_refs(&refnames, 0);
+ string_list_clear(&refnames, 0);
+ }
+
+ if (verbosity >= 0) {
+ for (ref = stale_refs; ref; ref = ref->next) {
+ struct strbuf sb = STRBUF_INIT;
+ if (!shown_url) {
+ fprintf(stderr, _("From %.*s\n"), url_len, url);
+ shown_url = 1;
+ }
+ format_display(&sb, '-', _("[deleted]"), NULL,
+ _("(none)"), prettify_refname(ref->name),
+ summary_width);
+ fprintf(stderr, " %s\n",sb.buf);
+ strbuf_release(&sb);
warn_dangling_symref(stderr, dangling_msg, ref->name);
}
}
+
free(url);
free_refs(stale_refs);
return result;
@@ -778,11 +979,11 @@ static void check_not_current_branch(struct ref *ref_map)
static int truncate_fetch_head(void)
{
- char *filename = git_path("FETCH_HEAD");
- FILE *fp = fopen(filename, "w");
+ const char *filename = git_path_fetch_head();
+ FILE *fp = fopen_for_writing(filename);
if (!fp)
- return error(_("cannot open %s: %s\n"), filename, strerror(errno));
+ return error_errno(_("cannot open %s"), filename);
fclose(fp);
return 0;
}
@@ -798,17 +999,25 @@ static void set_option(struct transport *transport, const char *name, const char
name, transport->url);
}
-static struct transport *prepare_transport(struct remote *remote)
+static struct transport *prepare_transport(struct remote *remote, int deepen)
{
struct transport *transport;
transport = transport_get(remote, NULL);
transport_set_verbosity(transport, verbosity, progress);
+ transport->family = family;
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 (deepen && deepen_since)
+ set_option(transport, TRANS_OPT_DEEPEN_SINCE, deepen_since);
+ if (deepen && deepen_not.nr)
+ set_option(transport, TRANS_OPT_DEEPEN_NOT,
+ (const char *)&deepen_not);
+ if (deepen_relative)
+ set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes");
if (update_shallow)
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
return transport;
@@ -816,13 +1025,25 @@ static struct transport *prepare_transport(struct remote *remote)
static void backfill_tags(struct transport *transport, struct ref *ref_map)
{
- if (transport->cannot_reuse) {
- gsecondary = prepare_transport(transport->remote);
+ int cannot_reuse;
+
+ /*
+ * Once we have set TRANS_OPT_DEEPEN_SINCE, we can't unset it
+ * when remote helper is used (setting it to an empty string
+ * is not unsetting). We could extend the remote helper
+ * protocol for that, but for now, just force a new connection
+ * without deepen-since. Similar story for deepen-not.
+ */
+ cannot_reuse = transport->cannot_reuse ||
+ deepen_since || deepen_not.nr;
+ if (cannot_reuse) {
+ gsecondary = prepare_transport(transport->remote, 0);
transport = gsecondary;
}
transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+ transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
fetch_refs(transport, ref_map);
if (gsecondary) {
@@ -868,9 +1089,10 @@ static int do_fetch(struct transport *transport,
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);
+ if (peer_item) {
+ struct object_id *old_oid = peer_item->util;
+ oidcpy(&rm->peer_ref->old_oid, old_oid);
+ }
}
}
@@ -931,17 +1153,15 @@ static int get_remote_group(const char *key, const char *value, void *priv)
{
struct remote_group_data *g = priv;
- if (starts_with(key, "remotes.") &&
- !strcmp(key + 8, g->name)) {
+ if (skip_prefix(key, "remotes.", &key) && !strcmp(key, g->name)) {
/* split list by white space */
- int space = strcspn(value, " \t\n");
while (*value) {
- if (space > 1) {
- string_list_append(g->list,
- xstrndup(value, space));
- }
- value += space + (value[space] != '\0');
- space = strcspn(value, " \t\n");
+ size_t wordlen = strcspn(value, " \t\n");
+
+ if (wordlen >= 1)
+ string_list_append_nodup(g->list,
+ xstrndup(value, wordlen));
+ value += wordlen + (value[wordlen] != '\0');
}
}
@@ -956,10 +1176,9 @@ static int add_remote_or_group(const char *name, struct string_list *list)
git_config(get_remote_group, &g);
if (list->nr == prev_nr) {
- struct remote *remote;
- if (!remote_is_configured(name))
+ struct remote *remote = remote_get(name);
+ if (!remote_is_configured(remote, 0))
return 0;
- remote = remote_get(name);
string_list_append(list, remote->name);
}
return 1;
@@ -1035,7 +1254,7 @@ 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."));
- gtransport = prepare_transport(remote);
+ gtransport = prepare_transport(remote, 1);
if (prune < 0) {
/* no command line request */
@@ -1050,19 +1269,14 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
if (argc > 0) {
int j = 0;
int i;
- refs = xcalloc(argc + 1, sizeof(const char *));
+ refs = xcalloc(st_add(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];
}
@@ -1083,12 +1297,10 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
int cmd_fetch(int argc, const char **argv, const char *prefix)
{
int i;
- struct string_list list = STRING_LIST_INIT_NODUP;
+ struct string_list list = STRING_LIST_INIT_DUP;
struct remote *remote;
int result = 0;
- static const char *argv_gc_auto[] = {
- "gc", "--auto", NULL,
- };
+ struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
packet_trace_identity("fetch");
@@ -1102,21 +1314,27 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);
+ if (deepen_relative) {
+ if (deepen_relative < 0)
+ die(_("Negative depth in --deepen is not supported"));
+ if (depth)
+ die(_("--deepen and --depth are mutually exclusive"));
+ depth = xstrfmt("%d", deepen_relative);
+ }
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;
- }
+ else
+ depth = xstrfmt("%d", INFINITE_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 (depth || deepen_since || deepen_not.nr)
+ deepen = 1;
if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
if (recurse_submodules_default) {
@@ -1166,15 +1384,20 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
result = fetch_populated_submodules(&options,
submodule_prefix,
recurse_submodules,
- verbosity < 0);
+ verbosity < 0,
+ max_children);
argv_array_clear(&options);
}
- /* All names were strdup()ed or strndup()ed */
- list.strdup_strings = 1;
string_list_clear(&list, 0);
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ close_all_packs();
+
+ 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 3906eda877..efab62fd85 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -1,5 +1,6 @@
#include "builtin.h"
#include "cache.h"
+#include "refs.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
@@ -10,7 +11,7 @@
#include "gpg-interface.h"
static const char * const fmt_merge_msg_usage[] = {
- N_("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
};
@@ -100,7 +101,8 @@ static int handle_line(char *line, struct merge_parents *merge_parents)
{
int i, len = strlen(line);
struct origin_data *origin_data;
- char *src, *origin;
+ char *src;
+ const char *origin;
struct src_data *src_data;
struct string_list_item *item;
int pulling_head = 0;
@@ -164,8 +166,7 @@ static int handle_line(char *line, struct merge_parents *merge_parents)
origin = line;
string_list_append(&src_data->tag, origin + 4);
src_data->head_status |= 2;
- } else if (starts_with(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 {
@@ -178,11 +179,8 @@ static int handle_line(char *line, struct merge_parents *merge_parents)
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;
- }
+ } 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;
@@ -219,23 +217,22 @@ static void add_branch_desc(struct strbuf *out, const char *name)
strbuf_addf(out, " : %.*s", (int)(ep - bp), bp);
bp = ep;
}
- if (out->buf[out->len - 1] != '\n')
- strbuf_addch(out, '\n');
+ 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)
+static void record_person_from_buf(int which, struct string_list *people,
+ const char *buffer)
{
char *name_buf, *name, *name_end;
struct string_list_item *elem;
const char *field;
field = (which == 'a') ? "\nauthor " : "\ncommitter ";
- name = strstr(commit->buffer, field);
+ name = strstr(buffer, field);
if (!name)
return;
name += strlen(field);
@@ -257,6 +254,15 @@ static void record_person(int which, struct string_list *people,
free(name_buf);
}
+
+static void record_person(int which, struct string_list *people,
+ struct commit *commit)
+{
+ const char *buffer = get_commit_buffer(commit, NULL);
+ record_person_from_buf(which, people, buffer);
+ unuse_commit_buffer(commit, buffer);
+}
+
static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
{
const struct string_list_item *a = a_, *b = b_;
@@ -266,7 +272,7 @@ static int cmp_string_list_util_as_integral(const void *a_, const void *b_)
static void add_people_count(struct strbuf *out, struct string_list *people)
{
if (people->nr == 1)
- strbuf_addf(out, "%s", people->items[0].string);
+ strbuf_addstr(out, people->items[0].string);
else if (people->nr == 2)
strbuf_addf(out, "%s (%d) and %s (%d)",
people->items[0].string,
@@ -297,8 +303,8 @@ static void credit_people(struct strbuf *out,
if (!them->nr ||
(them->nr == 1 &&
me &&
- (me = skip_prefix(me, them->items->string)) != NULL &&
- skip_prefix(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);
@@ -308,14 +314,10 @@ 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);
+ QSORT(authors->items, authors->nr,
+ cmp_string_list_util_as_integral);
+ QSORT(committers->items, committers->nr,
+ cmp_string_list_util_as_integral);
credit_people(out, authors, 'a');
credit_people(out, committers, 'c');
@@ -372,7 +374,7 @@ static void shortlog(const char *name,
if (!sb.len)
string_list_append(&subjects,
- sha1_to_hex(commit->object.sha1));
+ oid_to_hex(&commit->object.oid));
else
string_list_append(&subjects, strbuf_detach(&sb, NULL));
}
@@ -389,7 +391,7 @@ static void shortlog(const char *name,
for (i = 0; i < subjects.nr; i++)
if (i >= limit)
- strbuf_addf(out, " ...\n");
+ strbuf_addstr(out, " ...\n");
else
strbuf_addf(out, " %s\n", subjects.items[i].string);
@@ -531,7 +533,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
static void find_merge_parents(struct merge_parents *result,
struct strbuf *in, unsigned char *head)
{
- struct commit_list *parents, *next;
+ struct commit_list *parents;
struct commit *head_commit;
int pos = 0, i, j;
@@ -562,7 +564,7 @@ static void find_merge_parents(struct merge_parents *result,
if (!parent)
continue;
commit_list_insert(parent, &parents);
- add_merge_parent(result, obj->sha1, parent->object.sha1);
+ add_merge_parent(result, obj->oid.hash, parent->object.oid.hash);
}
head_commit = lookup_commit(head);
if (head_commit)
@@ -570,13 +572,10 @@ static void find_merge_parents(struct merge_parents *result,
parents = reduce_heads(parents);
while (parents) {
+ struct commit *cmit = pop_commit(&parents);
for (i = 0; i < result->nr; i++)
- if (!hashcmp(result->item[i].commit,
- parents->item->object.sha1))
+ if (!hashcmp(result->item[i].commit, cmit->object.oid.hash))
result->item[i].used = 1;
- next = parents->next;
- free(parents);
- parents = next;
}
for (i = j = 0; i < result->nr; i++) {
@@ -602,7 +601,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
/* get current branch */
current_branch = current_branch_to_free =
- resolve_refdup("HEAD", head_sha1, 1, NULL);
+ resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL);
if (!current_branch)
die("No current branch");
if (starts_with(current_branch, "refs/heads/"))
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 51798b48b5..df41fa0350 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -2,1076 +2,25 @@
#include "cache.h"
#include "refs.h"
#include "object.h"
-#include "tag.h"
-#include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "quote.h"
#include "parse-options.h"
-#include "remote.h"
-#include "color.h"
-
-/* Quoting styles */
-#define QUOTE_NONE 0
-#define QUOTE_SHELL 1
-#define QUOTE_PERL 2
-#define QUOTE_PYTHON 4
-#define QUOTE_TCL 8
-
-typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
-
-struct atom_value {
- const char *s;
- unsigned long ul; /* used for sorting when not FIELD_STR */
-};
-
-struct ref_sort {
- struct ref_sort *next;
- int atom; /* index into used_atom array */
- unsigned reverse : 1;
-};
-
-struct refinfo {
- char *refname;
- unsigned char objectname[20];
- int flag;
- const char *symref;
- struct atom_value *value;
-};
-
-static struct {
- const char *name;
- cmp_type cmp_type;
-} valid_atom[] = {
- { "refname" },
- { "objecttype" },
- { "objectsize", FIELD_ULONG },
- { "objectname" },
- { "tree" },
- { "parent" },
- { "numparent", FIELD_ULONG },
- { "object" },
- { "type" },
- { "tag" },
- { "author" },
- { "authorname" },
- { "authoremail" },
- { "authordate", FIELD_TIME },
- { "committer" },
- { "committername" },
- { "committeremail" },
- { "committerdate", FIELD_TIME },
- { "tagger" },
- { "taggername" },
- { "taggeremail" },
- { "taggerdate", FIELD_TIME },
- { "creator" },
- { "creatordate", FIELD_TIME },
- { "subject" },
- { "body" },
- { "contents" },
- { "contents:subject" },
- { "contents:body" },
- { "contents:signature" },
- { "upstream" },
- { "symref" },
- { "flag" },
- { "HEAD" },
- { "color" },
-};
-
-/*
- * An atom is a valid field atom listed above, possibly prefixed with
- * a "*" to denote deref_tag().
- *
- * We parse given format string and sort specifiers, and make a list
- * of properties that we need to extract out of objects. refinfo
- * structure will hold an array of values extracted that can be
- * indexed with the "atom number", which is an index into this
- * array.
- */
-static const char **used_atom;
-static cmp_type *used_atom_type;
-static int used_atom_cnt, need_tagged, need_symref;
-static int need_color_reset_at_eol;
-
-/*
- * Used to parse format string and sort specifiers
- */
-static int parse_atom(const char *atom, const char *ep)
-{
- const char *sp;
- int i, at;
-
- sp = atom;
- if (*sp == '*' && sp < ep)
- sp++; /* deref */
- if (ep <= sp)
- die("malformed field name: %.*s", (int)(ep-atom), atom);
-
- /* Do we have the atom already used elsewhere? */
- for (i = 0; i < used_atom_cnt; i++) {
- int len = strlen(used_atom[i]);
- if (len == ep - atom && !memcmp(used_atom[i], atom, len))
- return i;
- }
-
- /* Is the atom a valid one? */
- for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
- int len = strlen(valid_atom[i].name);
- /*
- * If the atom name has a colon, strip it and everything after
- * it off - it specifies the format for this entry, and
- * shouldn't be used for checking against the valid_atom
- * table.
- */
- const char *formatp = strchr(sp, ':');
- if (!formatp || ep < formatp)
- formatp = ep;
- if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
- break;
- }
-
- if (ARRAY_SIZE(valid_atom) <= i)
- die("unknown field name: %.*s", (int)(ep-atom), atom);
-
- /* 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));
- used_atom[at] = xmemdupz(atom, ep - atom);
- used_atom_type[at] = valid_atom[i].cmp_type;
- if (*atom == '*')
- need_tagged = 1;
- if (!strcmp(used_atom[at], "symref"))
- need_symref = 1;
- return at;
-}
-
-/*
- * In a format string, find the next occurrence of %(atom).
- */
-static const char *find_next(const char *cp)
-{
- while (*cp) {
- if (*cp == '%') {
- /*
- * %( is the start of an atom;
- * %% is a quoted per-cent.
- */
- if (cp[1] == '(')
- return cp;
- else if (cp[1] == '%')
- cp++; /* skip over two % */
- /* otherwise this is a singleton, literal % */
- }
- cp++;
- }
- return NULL;
-}
-
-/*
- * Make sure the format string is well formed, and parse out
- * the used atoms.
- */
-static int verify_format(const char *format)
-{
- const char *cp, *sp;
- static const char color_reset[] = "color:reset";
-
- need_color_reset_at_eol = 0;
- for (cp = format; *cp && (sp = find_next(cp)); ) {
- const char *ep = strchr(sp, ')');
- int at;
-
- if (!ep)
- return error("malformed format string %s", sp);
- /* sp points at "%(" and ep points at the closing ")" */
- at = parse_atom(sp + 2, ep);
- cp = ep + 1;
-
- if (!memcmp(used_atom[at], "color:", 6))
- need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset);
- }
- return 0;
-}
-
-/*
- * Given an object name, read the object data and size, and return a
- * "struct object". If the object data we are returning is also borrowed
- * by the "struct object" representation, set *eaten as well---it is a
- * signal from parse_object_buffer to us not to free the buffer.
- */
-static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten)
-{
- enum object_type type;
- void *buf = read_sha1_file(sha1, &type, sz);
-
- if (buf)
- *obj = parse_object_buffer(sha1, type, *sz, buf, eaten);
- else
- *obj = NULL;
- 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)
-{
- int i;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (!strcmp(name, "objecttype"))
- v->s = typename(obj->type);
- else if (!strcmp(name, "objectsize")) {
- char *s = xmalloc(40);
- sprintf(s, "%lu", sz);
- v->ul = sz;
- v->s = s;
- }
- else if (deref)
- grab_objectname(name, obj->sha1, v);
- }
-}
-
-/* See grab_values */
-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- int i;
- struct tag *tag = (struct tag *) obj;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (!strcmp(name, "tag"))
- v->s = tag->tag;
- else if (!strcmp(name, "type") && tag->tagged)
- v->s = typename(tag->tagged->type);
- else if (!strcmp(name, "object") && tag->tagged) {
- char *s = xmalloc(41);
- strcpy(s, sha1_to_hex(tag->tagged->sha1));
- v->s = s;
- }
- }
-}
-
-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)
-{
- int i;
- struct commit *commit = (struct commit *) obj;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (!strcmp(name, "tree")) {
- char *s = xmalloc(41);
- strcpy(s, sha1_to_hex(commit->tree->object.sha1));
- v->s = s;
- }
- if (!strcmp(name, "numparent")) {
- char *s = xmalloc(40);
- v->ul = num_parents(commit);
- sprintf(s, "%lu", v->ul);
- v->s = s;
- }
- else if (!strcmp(name, "parent")) {
- int num = num_parents(commit);
- int i;
- struct commit_list *parents;
- char *s = xmalloc(41 * num + 1);
- v->s = s;
- for (i = 0, parents = commit->parents;
- parents;
- parents = parents->next, i = i + 41) {
- struct commit *parent = parents->item;
- strcpy(s+i, sha1_to_hex(parent->object.sha1));
- if (parents->next)
- s[i+40] = ' ';
- }
- if (!i)
- *s = '\0';
- }
- }
-}
-
-static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz)
-{
- const char *eol;
- while (*buf) {
- if (!strncmp(buf, who, wholen) &&
- buf[wholen] == ' ')
- return buf + wholen + 1;
- eol = strchr(buf, '\n');
- if (!eol)
- return "";
- eol++;
- if (*eol == '\n')
- return ""; /* end of header */
- buf = eol;
- }
- return "";
-}
-
-static const char *copy_line(const char *buf)
-{
- const char *eol = strchrnul(buf, '\n');
- return xmemdupz(buf, eol - buf);
-}
-
-static const char *copy_name(const char *buf)
-{
- const char *cp;
- for (cp = buf; *cp && *cp != '\n'; cp++) {
- if (!strncmp(cp, " <", 2))
- return xmemdupz(buf, cp - buf);
- }
- return "";
-}
-
-static const char *copy_email(const char *buf)
-{
- const char *email = strchr(buf, '<');
- const char *eoemail;
- if (!email)
- return "";
- eoemail = strchr(email, '>');
- if (!eoemail)
- return "";
- 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, "> ");
- char *zone;
- unsigned long timestamp;
- long tz;
- enum date_mode date_mode = DATE_NORMAL;
- const char *formatp;
-
- /*
- * We got here because atomname ends in "date" or "date<something>";
- * it's not possible that <something> is not ":<format>" because
- * parse_atom() wouldn't have allowed it, so we can assume that no
- * ":" means no format is specified, and use the default.
- */
- formatp = strchr(atomname, ':');
- if (formatp != NULL) {
- formatp++;
- date_mode = parse_date_format(formatp);
- }
-
- if (!eoemail)
- goto bad;
- timestamp = strtoul(eoemail + 2, &zone, 10);
- if (timestamp == ULONG_MAX)
- goto bad;
- tz = strtol(zone, NULL, 10);
- if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
- goto bad;
- v->s = xstrdup(show_date(timestamp, tz, date_mode));
- v->ul = timestamp;
- return;
- bad:
- v->s = "";
- v->ul = 0;
-}
-
-/* See grab_values */
-static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- int i;
- int wholen = strlen(who);
- const char *wholine = NULL;
-
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (strncmp(who, name, wholen))
- continue;
- if (name[wholen] != 0 &&
- strcmp(name + wholen, "name") &&
- strcmp(name + wholen, "email") &&
- !starts_with(name + wholen, "date"))
- continue;
- if (!wholine)
- wholine = find_wholine(who, wholen, buf, sz);
- if (!wholine)
- return; /* no point looking for it */
- if (name[wholen] == 0)
- v->s = copy_line(wholine);
- else if (!strcmp(name + wholen, "name"))
- v->s = copy_name(wholine);
- else if (!strcmp(name + wholen, "email"))
- v->s = copy_email(wholine);
- else if (starts_with(name + wholen, "date"))
- grab_date(wholine, v, name);
- }
-
- /*
- * For a tag or a commit object, if "creator" or "creatordate" is
- * requested, do something special.
- */
- if (strcmp(who, "tagger") && strcmp(who, "committer"))
- return; /* "author" for commit object is not wanted */
- if (!wholine)
- wholine = find_wholine(who, wholen, buf, sz);
- if (!wholine)
- return;
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
-
- 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, unsigned long *sublen,
- const char **body, unsigned long *bodylen,
- unsigned long *nonsiglen,
- const char **sig, unsigned long *siglen)
-{
- 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++;
-
- /* 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++;
- *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, *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];
- struct atom_value *v = &val[i];
- if (!!deref != (*name == '*'))
- continue;
- if (deref)
- name++;
- if (strcmp(name, "subject") &&
- strcmp(name, "body") &&
- strcmp(name, "contents") &&
- strcmp(name, "contents:subject") &&
- strcmp(name, "contents:body") &&
- strcmp(name, "contents:signature"))
- continue;
- if (!subpos)
- find_subpos(buf, sz,
- &subpos, &sublen,
- &bodypos, &bodylen, &nonsiglen,
- &sigpos, &siglen);
-
- if (!strcmp(name, "subject"))
- 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 = 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);
- }
-}
-
-/*
- * We want to have empty print-string for field requests
- * that do not apply (e.g. "authordate" for a tag object)
- */
-static void fill_missing_values(struct atom_value *val)
-{
- int i;
- for (i = 0; i < used_atom_cnt; i++) {
- struct atom_value *v = &val[i];
- if (v->s == NULL)
- v->s = "";
- }
-}
-
-/*
- * val is a list of atom_value to hold returned values. Extract
- * the values for atoms in used_atom array out of (obj, buf, sz).
- * when deref is false, (obj, buf, sz) is the object that is
- * pointed at by the ref itself; otherwise it is the object the
- * ref (which is a tag) refers to.
- */
-static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
-{
- grab_common_values(val, deref, obj, buf, sz);
- switch (obj->type) {
- case OBJ_TAG:
- grab_tag_values(val, deref, obj, buf, sz);
- grab_sub_body_contents(val, deref, obj, buf, sz);
- grab_person("tagger", val, deref, obj, buf, sz);
- break;
- case OBJ_COMMIT:
- grab_commit_values(val, deref, obj, buf, sz);
- grab_sub_body_contents(val, deref, obj, buf, sz);
- grab_person("author", val, deref, obj, buf, sz);
- grab_person("committer", val, deref, obj, buf, sz);
- break;
- case OBJ_TREE:
- /* grab_tree_values(val, deref, obj, buf, sz); */
- break;
- case OBJ_BLOB:
- /* grab_blob_values(val, deref, obj, buf, sz); */
- break;
- default:
- die("Eh? Object of type %d?", obj->type);
- }
-}
-
-static inline char *copy_advance(char *dst, const char *src)
-{
- while (*src)
- *dst++ = *src++;
- return dst;
-}
-
-/*
- * Parse the object referred by ref, and grab needed value.
- */
-static void populate_value(struct refinfo *ref)
-{
- void *buf;
- struct object *obj;
- int eaten, i;
- unsigned long size;
- const unsigned char *tagged;
-
- ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt);
-
- if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
- unsigned char unused1[20];
- ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL);
- if (!ref->symref)
- ref->symref = "";
- }
-
- /* Fill in specials first */
- for (i = 0; i < used_atom_cnt; i++) {
- const char *name = used_atom[i];
- struct atom_value *v = &ref->value[i];
- int deref = 0;
- const char *refname;
- const char *formatp;
- struct branch *branch = NULL;
-
- if (*name == '*') {
- deref = 1;
- name++;
- }
-
- if (starts_with(name, "refname"))
- refname = ref->refname;
- else if (starts_with(name, "symref"))
- refname = ref->symref ? ref->symref : "";
- else if (starts_with(name, "upstream")) {
- /* only local branches may have an upstream */
- if (!starts_with(ref->refname, "refs/heads/"))
- continue;
- branch = branch_get(ref->refname + 11);
-
- if (!branch || !branch->merge || !branch->merge[0] ||
- !branch->merge[0]->dst)
- continue;
- refname = branch->merge[0]->dst;
- } else if (starts_with(name, "color:")) {
- char color[COLOR_MAXLEN] = "";
-
- color_parse(name + 6, "--format", color);
- v->s = xstrdup(color);
- continue;
- } else if (!strcmp(name, "flag")) {
- char buf[256], *cp = buf;
- if (ref->flag & REF_ISSYMREF)
- cp = copy_advance(cp, ",symref");
- if (ref->flag & REF_ISPACKED)
- cp = copy_advance(cp, ",packed");
- if (cp == buf)
- v->s = "";
- else {
- *cp = '\0';
- v->s = xstrdup(buf + 1);
- }
- continue;
- } else if (!deref && grab_objectname(name, ref->objectname, v)) {
- continue;
- } else if (!strcmp(name, "HEAD")) {
- const char *head;
- unsigned char sha1[20];
-
- head = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
- if (!strcmp(ref->refname, head))
- v->s = "*";
- else
- v->s = " ";
- continue;
- } else
- continue;
-
- formatp = strchr(name, ':');
- if (formatp) {
- int num_ours, num_theirs;
-
- formatp++;
- if (!strcmp(formatp, "short"))
- refname = shorten_unambiguous_ref(refname,
- warn_ambiguous_refs);
- else if (!strcmp(formatp, "track") &&
- starts_with(name, "upstream")) {
- char buf[40];
-
- stat_tracking_info(branch, &num_ours, &num_theirs);
- if (!num_ours && !num_theirs)
- v->s = "";
- else if (!num_ours) {
- sprintf(buf, "[behind %d]", num_theirs);
- v->s = xstrdup(buf);
- } else if (!num_theirs) {
- sprintf(buf, "[ahead %d]", num_ours);
- v->s = xstrdup(buf);
- } else {
- sprintf(buf, "[ahead %d, behind %d]",
- num_ours, num_theirs);
- v->s = xstrdup(buf);
- }
- continue;
- } else if (!strcmp(formatp, "trackshort") &&
- starts_with(name, "upstream")) {
- assert(branch);
- stat_tracking_info(branch, &num_ours, &num_theirs);
- if (!num_ours && !num_theirs)
- v->s = "=";
- else if (!num_ours)
- v->s = "<";
- else if (!num_theirs)
- v->s = ">";
- else
- v->s = "<>";
- continue;
- } else
- die("unknown %.*s format %s",
- (int)(formatp - name), name, formatp);
- }
-
- if (!deref)
- v->s = refname;
- else {
- int len = strlen(refname);
- char *s = xmalloc(len + 4);
- sprintf(s, "%s^{}", refname);
- v->s = s;
- }
- }
-
- for (i = 0; i < used_atom_cnt; i++) {
- struct atom_value *v = &ref->value[i];
- if (v->s == NULL)
- goto need_obj;
- }
- return;
-
- need_obj:
- buf = get_obj(ref->objectname, &obj, &size, &eaten);
- if (!buf)
- die("missing object %s for %s",
- sha1_to_hex(ref->objectname), ref->refname);
- if (!obj)
- die("parse_object_buffer failed on %s for %s",
- sha1_to_hex(ref->objectname), ref->refname);
-
- grab_values(ref->value, 0, obj, buf, size);
- if (!eaten)
- free(buf);
-
- /*
- * If there is no atom that wants to know about tagged
- * object, we are done.
- */
- if (!need_tagged || (obj->type != OBJ_TAG))
- return;
-
- /*
- * If it is a tag object, see if we use a value that derefs
- * the object, and if we do grab the object it refers to.
- */
- tagged = ((struct tag *)obj)->tagged->sha1;
-
- /*
- * NEEDSWORK: This derefs tag only once, which
- * is good to deal with chains of trust, but
- * is not consistent with what deref_tag() does
- * which peels the onion to the core.
- */
- buf = get_obj(tagged, &obj, &size, &eaten);
- if (!buf)
- die("missing object %s for %s",
- sha1_to_hex(tagged), ref->refname);
- if (!obj)
- die("parse_object_buffer failed on %s for %s",
- sha1_to_hex(tagged), ref->refname);
- grab_values(ref->value, 1, obj, buf, size);
- if (!eaten)
- free(buf);
-}
-
-/*
- * Given a ref, return the value for the atom. This lazily gets value
- * out of the object by calling populate value.
- */
-static void get_value(struct refinfo *ref, int atom, struct atom_value **v)
-{
- if (!ref->value) {
- populate_value(ref);
- fill_missing_values(ref->value);
- }
- *v = &ref->value[atom];
-}
-
-struct grab_ref_cbdata {
- struct refinfo **grab_array;
- const char **grab_pattern;
- int grab_cnt;
-};
-
-/*
- * A call-back given to for_each_ref(). Filter refs and keep them for
- * later object processing.
- */
-static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
- struct grab_ref_cbdata *cb = cb_data;
- struct refinfo *ref;
- int cnt;
-
- if (*cb->grab_pattern) {
- const char **pattern;
- int namelen = strlen(refname);
- for (pattern = cb->grab_pattern; *pattern; pattern++) {
- const char *p = *pattern;
- int plen = strlen(p);
-
- if ((plen <= namelen) &&
- !strncmp(refname, p, plen) &&
- (refname[plen] == '\0' ||
- refname[plen] == '/' ||
- p[plen-1] == '/'))
- break;
- if (!fnmatch(p, refname, FNM_PATHNAME))
- break;
- }
- if (!*pattern)
- return 0;
- }
-
- /*
- * We do not open the object yet; sort may only need refname
- * to do its job and the resulting list may yet to be pruned
- * by maxcount logic.
- */
- ref = xcalloc(1, sizeof(*ref));
- ref->refname = xstrdup(refname);
- hashcpy(ref->objectname, sha1);
- ref->flag = flag;
-
- cnt = cb->grab_cnt;
- cb->grab_array = xrealloc(cb->grab_array,
- sizeof(*cb->grab_array) * (cnt + 1));
- cb->grab_array[cnt++] = ref;
- cb->grab_cnt = cnt;
- return 0;
-}
-
-static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b)
-{
- struct atom_value *va, *vb;
- int cmp;
- cmp_type cmp_type = used_atom_type[s->atom];
-
- get_value(a, s->atom, &va);
- get_value(b, s->atom, &vb);
- switch (cmp_type) {
- case FIELD_STR:
- cmp = strcmp(va->s, vb->s);
- break;
- default:
- if (va->ul < vb->ul)
- cmp = -1;
- else if (va->ul == vb->ul)
- cmp = 0;
- else
- cmp = 1;
- break;
- }
- return (s->reverse) ? -cmp : cmp;
-}
-
-static struct ref_sort *ref_sort;
-static int compare_refs(const void *a_, const void *b_)
-{
- struct refinfo *a = *((struct refinfo **)a_);
- struct refinfo *b = *((struct refinfo **)b_);
- struct ref_sort *s;
-
- for (s = ref_sort; s; s = s->next) {
- int cmp = cmp_ref_sort(s, a, b);
- if (cmp)
- return cmp;
- }
- return 0;
-}
-
-static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs)
-{
- ref_sort = sort;
- qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs);
-}
-
-static void print_value(struct atom_value *v, int quote_style)
-{
- struct strbuf sb = STRBUF_INIT;
- switch (quote_style) {
- case QUOTE_NONE:
- fputs(v->s, stdout);
- break;
- case QUOTE_SHELL:
- sq_quote_buf(&sb, v->s);
- break;
- case QUOTE_PERL:
- perl_quote_buf(&sb, v->s);
- break;
- case QUOTE_PYTHON:
- python_quote_buf(&sb, v->s);
- break;
- case QUOTE_TCL:
- tcl_quote_buf(&sb, v->s);
- break;
- }
- if (quote_style != QUOTE_NONE) {
- fputs(sb.buf, stdout);
- strbuf_release(&sb);
- }
-}
-
-static int hex1(char ch)
-{
- if ('0' <= ch && ch <= '9')
- return ch - '0';
- else if ('a' <= ch && ch <= 'f')
- return ch - 'a' + 10;
- else if ('A' <= ch && ch <= 'F')
- return ch - 'A' + 10;
- return -1;
-}
-static int hex2(const char *cp)
-{
- if (cp[0] && cp[1])
- return (hex1(cp[0]) << 4) | hex1(cp[1]);
- else
- return -1;
-}
-
-static void emit(const char *cp, const char *ep)
-{
- while (*cp && (!ep || cp < ep)) {
- if (*cp == '%') {
- if (cp[1] == '%')
- cp++;
- else {
- int ch = hex2(cp + 1);
- if (0 <= ch) {
- putchar(ch);
- cp += 3;
- continue;
- }
- }
- }
- putchar(*cp);
- cp++;
- }
-}
-
-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);
- get_value(info, parse_atom(sp + 2, ep), &atomv);
- print_value(atomv, quote_style);
- }
- if (*cp) {
- sp = cp + strlen(cp);
- emit(cp, sp);
- }
- if (need_color_reset_at_eol) {
- struct atom_value resetv;
- char color[COLOR_MAXLEN] = "";
-
- color_parse("reset", "--format", color);
- resetv.s = color;
- print_value(&resetv, quote_style);
- }
- putchar('\n');
-}
-
-static struct ref_sort *default_sort(void)
-{
- static const char cstr_name[] = "refname";
-
- struct ref_sort *sort = xcalloc(1, sizeof(*sort));
-
- sort->next = NULL;
- sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name));
- return sort;
-}
-
-static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
-{
- struct ref_sort **sort_tail = opt->value;
- struct ref_sort *s;
- int len;
-
- if (!arg) /* should --no-sort void the list ? */
- return -1;
-
- s = xcalloc(1, sizeof(*s));
- s->next = *sort_tail;
- *sort_tail = s;
-
- if (*arg == '-') {
- s->reverse = 1;
- arg++;
- }
- len = strlen(arg);
- s->atom = parse_atom(arg, arg+len);
- return 0;
-}
+#include "ref-filter.h"
static char const * const for_each_ref_usage[] = {
- N_("git for-each-ref [options] [<pattern>]"),
+ N_("git for-each-ref [<options>] [<pattern>]"),
+ N_("git for-each-ref [--points-at <object>]"),
+ N_("git for-each-ref [(--merged | --no-merged) [<object>]]"),
+ N_("git for-each-ref [--contains [<object>]]"),
NULL
};
int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
{
- int i, num_refs;
+ int i;
const char *format = "%(objectname) %(objecttype)\t%(refname)";
- struct ref_sort *sort = NULL, **sort_tail = &sort;
- int maxcount = 0, quote_style = 0;
- struct refinfo **refs;
- struct grab_ref_cbdata cbdata;
+ struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+ int maxcount = 0, quote_style = 0, icase = 0;
+ struct ref_array array;
+ struct ref_filter filter;
struct option opts[] = {
OPT_BIT('s', "shell", &quote_style,
@@ -1081,16 +30,26 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
OPT_BIT(0 , "python", &quote_style,
N_("quote placeholders suitably for python"), QUOTE_PYTHON),
OPT_BIT(0 , "tcl", &quote_style,
- N_("quote placeholders suitably for tcl"), QUOTE_TCL),
+ N_("quote placeholders suitably for Tcl"), QUOTE_TCL),
OPT_GROUP(""),
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_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+ N_("field name to sort on"), &parse_opt_ref_sorting),
+ OPT_CALLBACK(0, "points-at", &filter.points_at,
+ N_("object"), N_("print only refs which points at the given object"),
+ parse_opt_object_name),
+ OPT_MERGED(&filter, N_("print only refs that are merged")),
+ OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
+ OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
+ OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_END(),
};
+ memset(&array, 0, sizeof(array));
+ memset(&filter, 0, sizeof(filter));
+
parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
if (maxcount < 0) {
error("invalid --count argument: `%d'", maxcount);
@@ -1100,26 +59,26 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
error("more than one quoting style?");
usage_with_options(for_each_ref_usage, opts);
}
- if (verify_format(format))
+ if (verify_ref_format(format))
usage_with_options(for_each_ref_usage, opts);
- if (!sort)
- sort = default_sort();
+ if (!sorting)
+ sorting = ref_default_sorting();
+ sorting->ignore_case = icase;
+ filter.ignore_case = icase;
/* for warn_ambiguous_refs */
git_config(git_default_config, NULL);
- memset(&cbdata, 0, sizeof(cbdata));
- cbdata.grab_pattern = argv;
- for_each_rawref(grab_single_ref, &cbdata);
- refs = cbdata.grab_array;
- num_refs = cbdata.grab_cnt;
-
- sort_refs(sort, refs, num_refs);
+ filter.name_patterns = argv;
+ filter.match_as_path = 1;
+ filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN);
+ ref_array_sort(sorting, &array);
- if (!maxcount || num_refs < maxcount)
- maxcount = num_refs;
+ if (!maxcount || array.nr < maxcount)
+ maxcount = array.nr;
for (i = 0; i < maxcount; i++)
- show_ref(refs[i], format, quote_style);
+ show_ref_array_item(array.items[i], format, quote_style);
+ ref_array_clear(&array);
return 0;
}
diff --git a/builtin/fsck.c b/builtin/fsck.c
index 1affdd5e92..1a5caccd0f 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -13,6 +13,7 @@
#include "dir.h"
#include "progress.h"
#include "streaming.h"
+#include "decorate.h"
#define REACHABLE 0x0001
#define SEEN 0x0002
@@ -23,60 +24,102 @@ static int show_tags;
static int show_unreachable;
static int include_reflogs = 1;
static int check_full = 1;
+static int connectivity_only;
static int check_strict;
static int keep_cache_objects;
-static unsigned char head_sha1[20];
+static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT;
+static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT;
+static struct object_id head_oid;
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;
+static int name_objects;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
+#define ERROR_REFS 010
-#ifdef NO_D_INO_IN_DIRENT
-#define SORT_DIRENT 0
-#define DIRENT_SORT_HINT(de) 0
-#else
-#define SORT_DIRENT 1
-#define DIRENT_SORT_HINT(de) ((de)->d_ino)
-#endif
+static const char *describe_object(struct object *obj)
+{
+ static struct strbuf buf = STRBUF_INIT;
+ char *name = name_objects ?
+ lookup_decoration(fsck_walk_options.object_names, obj) : NULL;
+
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, oid_to_hex(&obj->oid));
+ if (name)
+ strbuf_addf(&buf, " (%s)", name);
-static void objreport(struct object *obj, const char *severity,
- const char *err, va_list params)
+ return buf.buf;
+}
+
+static const char *printable_type(struct object *obj)
{
- fprintf(stderr, "%s in %s %s: ",
- severity, typename(obj->type), sha1_to_hex(obj->sha1));
- vfprintf(stderr, err, params);
- fputs("\n", stderr);
+ const char *ret;
+
+ if (obj->type == OBJ_NONE) {
+ enum object_type type = sha1_object_info(obj->oid.hash, NULL);
+ if (type > 0)
+ object_as_type(obj, type, 0);
+ }
+
+ ret = typename(obj->type);
+ if (!ret)
+ ret = "unknown";
+
+ return ret;
}
-__attribute__((format (printf, 2, 3)))
-static int objerror(struct object *obj, const char *err, ...)
+static int fsck_config(const char *var, const char *value, void *cb)
+{
+ if (strcmp(var, "fsck.skiplist") == 0) {
+ const char *path;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (git_config_pathname(&path, var, value))
+ return 1;
+ strbuf_addf(&sb, "skiplist=%s", path);
+ free((char *)path);
+ fsck_set_msg_types(&fsck_obj_options, sb.buf);
+ strbuf_release(&sb);
+ return 0;
+ }
+
+ if (skip_prefix(var, "fsck.", &var)) {
+ fsck_set_msg_type(&fsck_obj_options, var, value);
+ return 0;
+ }
+
+ return git_default_config(var, value, cb);
+}
+
+static void objreport(struct object *obj, const char *msg_type,
+ const char *err)
+{
+ fprintf(stderr, "%s in %s %s: %s\n",
+ msg_type, printable_type(obj), describe_object(obj), err);
+}
+
+static int objerror(struct object *obj, const char *err)
{
- va_list params;
- va_start(params, err);
errors_found |= ERROR_OBJECT;
- objreport(obj, "error", err, params);
- va_end(params);
+ objreport(obj, "error", err);
return -1;
}
-__attribute__((format (printf, 3, 4)))
-static int fsck_error_func(struct object *obj, int type, const char *err, ...)
+static int fsck_error_func(struct fsck_options *o,
+ struct object *obj, int type, const char *message)
{
- va_list params;
- va_start(params, err);
- objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params);
- va_end(params);
+ objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message);
return (type == FSCK_WARN) ? 0 : 1;
}
static struct object_array pending;
-static int mark_object(struct object *obj, int type, void *data)
+static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options)
{
struct object *parent = data;
@@ -88,7 +131,7 @@ static int mark_object(struct object *obj, int type, void *data)
if (!obj) {
/* ... these references to parent->fld are safe here */
printf("broken link from %7s %s\n",
- typename(parent->type), sha1_to_hex(parent->sha1));
+ printable_type(parent), describe_object(parent));
printf("broken link from %7s %s\n",
(type == OBJ_ANY ? "unknown" : typename(type)), "unknown");
errors_found |= ERROR_REACHABLE;
@@ -103,11 +146,11 @@ static int mark_object(struct object *obj, int type, void *data)
return 0;
obj->flags |= REACHABLE;
if (!(obj->flags & HAS_OBJ)) {
- if (parent && !has_sha1_file(obj->sha1)) {
+ if (parent && !has_object_file(&obj->oid)) {
printf("broken link from %7s %s\n",
- typename(parent->type), sha1_to_hex(parent->sha1));
+ printable_type(parent), describe_object(parent));
printf(" to %7s %s\n",
- typename(obj->type), sha1_to_hex(obj->sha1));
+ printable_type(obj), describe_object(obj));
errors_found |= ERROR_REACHABLE;
}
return 1;
@@ -119,7 +162,7 @@ static int mark_object(struct object *obj, int type, void *data)
static void mark_object_reachable(struct object *obj)
{
- mark_object(obj, OBJ_ANY, NULL);
+ mark_object(obj, OBJ_ANY, NULL, NULL);
}
static int traverse_one_object(struct object *obj)
@@ -132,7 +175,7 @@ static int traverse_one_object(struct object *obj)
if (parse_tree(tree) < 0)
return 1; /* error already displayed */
}
- result = fsck_walk(obj, mark_object, obj);
+ result = fsck_walk(obj, obj, &fsck_walk_options);
if (tree)
free_tree_buffer(tree);
return result;
@@ -144,7 +187,7 @@ static int traverse_reachable(void)
unsigned int nr = 0;
int result = 0;
if (show_progress)
- progress = start_progress_delay("Checking connectivity", 0, 0, 2);
+ progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2);
while (pending.nr) {
struct object_array_entry *entry;
struct object *obj;
@@ -158,7 +201,7 @@ static int traverse_reachable(void)
return !!result;
}
-static int mark_used(struct object *obj, int type, void *data)
+static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options)
{
if (!obj)
return 1;
@@ -177,9 +220,10 @@ static void check_reachable_object(struct object *obj)
* do a full fsck
*/
if (!(obj->flags & HAS_OBJ)) {
- if (has_sha1_pack(obj->sha1))
+ if (has_sha1_pack(obj->oid.hash))
return; /* it is in pack - forget about it */
- printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+ printf("missing %s %s\n", printable_type(obj),
+ describe_object(obj));
errors_found |= ERROR_REACHABLE;
return;
}
@@ -196,7 +240,7 @@ static void check_unreachable_object(struct object *obj)
* to complain about it being unreachable (since it does
* not exist).
*/
- if (!obj->parsed)
+ if (!(obj->flags & HAS_OBJ))
return;
/*
@@ -204,7 +248,8 @@ static void check_unreachable_object(struct object *obj)
* since this is something that is prunable.
*/
if (show_unreachable) {
- printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+ printf("unreachable %s %s\n", printable_type(obj),
+ describe_object(obj));
return;
}
@@ -222,28 +267,30 @@ static void check_unreachable_object(struct object *obj)
*/
if (!obj->used) {
if (show_dangling)
- printf("dangling %s %s\n", typename(obj->type),
- sha1_to_hex(obj->sha1));
+ printf("dangling %s %s\n", printable_type(obj),
+ describe_object(obj));
if (write_lost_and_found) {
- char *filename = git_path("lost-found/%s/%s",
+ char *filename = git_pathdup("lost-found/%s/%s",
obj->type == OBJ_COMMIT ? "commit" : "other",
- sha1_to_hex(obj->sha1));
+ describe_object(obj));
FILE *f;
- if (safe_create_leading_directories(filename)) {
+ if (safe_create_leading_directories_const(filename)) {
error("Could not create lost-found");
+ free(filename);
return;
}
if (!(f = fopen(filename, "w")))
die_errno("Could not open '%s'", filename);
if (obj->type == OBJ_BLOB) {
- if (stream_blob_to_fd(fileno(f), obj->sha1, NULL, 1))
+ if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1))
die_errno("Could not write '%s'", filename);
} else
- fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
+ fprintf(f, "%s\n", describe_object(obj));
if (fclose(f))
die_errno("Could not finish '%s'",
filename);
+ free(filename);
}
return;
}
@@ -258,7 +305,7 @@ static void check_unreachable_object(struct object *obj)
static void check_object(struct object *obj)
{
if (verbose)
- fprintf(stderr, "Checking %s\n", sha1_to_hex(obj->sha1));
+ fprintf(stderr, "Checking %s\n", describe_object(obj));
if (obj->flags & REACHABLE)
check_reachable_object(obj);
@@ -294,11 +341,11 @@ static int fsck_obj(struct object *obj)
if (verbose)
fprintf(stderr, "Checking %s %s\n",
- typename(obj->type), sha1_to_hex(obj->sha1));
+ printable_type(obj), describe_object(obj));
- if (fsck_walk(obj, mark_used, NULL))
+ if (fsck_walk(obj, NULL, &fsck_obj_options))
objerror(obj, "broken links");
- if (fsck_object(obj, check_strict, fsck_error_func))
+ if (fsck_object(obj, NULL, 0, &fsck_obj_options))
return -1;
if (obj->type == OBJ_TREE) {
@@ -310,40 +357,33 @@ static int fsck_obj(struct object *obj)
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));
+ printf("root %s\n", describe_object(&commit->object));
}
if (obj->type == OBJ_TAG) {
struct tag *tag = (struct tag *) obj;
if (show_tags && tag->tagged) {
- printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1));
- printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
+ printf("tagged %s %s", printable_type(tag->tagged),
+ describe_object(tag->tagged));
+ printf(" (%s) in %s\n", tag->tag,
+ describe_object(&tag->object));
}
}
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)
{
+ /*
+ * Note, buffer may be NULL if type is OBJ_BLOB. See
+ * verify_packfile(), data_valid variable for details.
+ */
struct object *obj;
obj = parse_object_buffer(sha1, type, size, buffer, eaten);
if (!obj) {
@@ -354,153 +394,72 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
return fsck_obj(obj);
}
-/*
- * This is the sorting chunk size: make it reasonably
- * big so that we can sort well..
- */
-#define MAX_SHA1_ENTRIES (1024)
-
-struct sha1_entry {
- unsigned long ino;
- unsigned char sha1[20];
-};
-
-static struct {
- unsigned long nr;
- struct sha1_entry *entry[MAX_SHA1_ENTRIES];
-} sha1_list;
-
-static int ino_compare(const void *_a, const void *_b)
-{
- const struct sha1_entry *a = _a, *b = _b;
- unsigned long ino1 = a->ino, ino2 = b->ino;
- return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
-}
-
-static void fsck_sha1_list(void)
-{
- int i, nr = sha1_list.nr;
-
- if (SORT_DIRENT)
- qsort(sha1_list.entry, nr,
- sizeof(struct sha1_entry *), ino_compare);
- for (i = 0; i < nr; i++) {
- struct sha1_entry *entry = sha1_list.entry[i];
- unsigned char *sha1 = entry->sha1;
-
- sha1_list.entry[i] = NULL;
- fsck_sha1(sha1);
- free(entry);
- }
- sha1_list.nr = 0;
-}
-
-static void add_sha1_list(unsigned char *sha1, unsigned long ino)
-{
- struct sha1_entry *entry = xmalloc(sizeof(*entry));
- int nr;
-
- entry->ino = ino;
- hashcpy(entry->sha1, sha1);
- nr = sha1_list.nr;
- if (nr == MAX_SHA1_ENTRIES) {
- fsck_sha1_list();
- nr = 0;
- }
- sha1_list.entry[nr] = entry;
- sha1_list.nr = ++nr;
-}
-
-static inline int is_loose_object_file(struct dirent *de,
- char *name, unsigned char *sha1)
-{
- if (strlen(de->d_name) != 38)
- return 0;
- memcpy(name + 2, de->d_name, 39);
- return !get_sha1_hex(name, sha1);
-}
+static int default_refs;
-static void fsck_dir(int i, char *path)
+static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1,
+ unsigned long timestamp)
{
- DIR *dir = opendir(path);
- struct dirent *de;
- char name[100];
-
- if (!dir)
- return;
-
- if (verbose)
- fprintf(stderr, "Checking directory %s\n", path);
-
- sprintf(name, "%02x", i);
- while ((de = readdir(dir)) != NULL) {
- unsigned char sha1[20];
+ struct object *obj;
- if (is_dot_or_dotdot(de->d_name))
- continue;
- if (is_loose_object_file(de, name, sha1)) {
- add_sha1_list(sha1, DIRENT_SORT_HINT(de));
- continue;
+ if (!is_null_sha1(sha1)) {
+ obj = lookup_object(sha1);
+ if (obj && (obj->flags & HAS_OBJ)) {
+ if (timestamp && name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj,
+ xstrfmt("%s@{%ld}", refname, timestamp));
+ obj->used = 1;
+ mark_object_reachable(obj);
+ } else {
+ error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1));
+ errors_found |= ERROR_REACHABLE;
}
- if (starts_with(de->d_name, "tmp_obj_"))
- continue;
- fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
}
- closedir(dir);
}
-static int default_refs;
-
static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
const char *email, unsigned long timestamp, int tz,
const char *message, void *cb_data)
{
- struct object *obj;
+ const char *refname = cb_data;
if (verbose)
fprintf(stderr, "Checking reflog %s->%s\n",
sha1_to_hex(osha1), sha1_to_hex(nsha1));
- if (!is_null_sha1(osha1)) {
- obj = lookup_object(osha1);
- if (obj) {
- obj->used = 1;
- mark_object_reachable(obj);
- }
- }
- obj = lookup_object(nsha1);
- if (obj) {
- obj->used = 1;
- mark_object_reachable(obj);
- }
+ fsck_handle_reflog_sha1(refname, osha1, 0);
+ fsck_handle_reflog_sha1(refname, nsha1, timestamp);
return 0;
}
-static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data)
+static int fsck_handle_reflog(const char *logname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL);
+ for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname);
return 0;
}
-static int is_branch(const char *refname)
-{
- return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
-}
-
-static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int fsck_handle_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
struct object *obj;
- obj = parse_object(sha1);
+ obj = parse_object(oid->hash);
if (!obj) {
- error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+ error("%s: invalid sha1 pointer %s", refname, oid_to_hex(oid));
+ errors_found |= ERROR_REACHABLE;
/* We'll continue with the rest despite the error.. */
return 0;
}
- if (obj->type != OBJ_COMMIT && is_branch(refname))
+ if (obj->type != OBJ_COMMIT && is_branch(refname)) {
error("%s: not a commit", refname);
+ errors_found |= ERROR_REFS;
+ }
default_refs++;
obj->used = 1;
+ if (name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj, xstrdup(refname));
mark_object_reachable(obj);
return 0;
@@ -508,9 +467,9 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
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);
+ if (head_points_at && !is_null_oid(&head_oid))
+ fsck_handle_ref("HEAD", &head_oid, 0, NULL);
+ for_each_rawref(fsck_handle_ref, NULL);
if (include_reflogs)
for_each_reflog(fsck_handle_reflog, NULL);
@@ -532,46 +491,99 @@ static void get_default_heads(void)
}
}
+static struct object *parse_loose_object(const unsigned char *sha1,
+ const char *path)
+{
+ struct object *obj;
+ void *contents;
+ enum object_type type;
+ unsigned long size;
+ int eaten;
+
+ if (read_loose_object(path, sha1, &type, &size, &contents) < 0)
+ return NULL;
+
+ if (!contents && type != OBJ_BLOB)
+ die("BUG: read_loose_object streamed a non-blob");
+
+ obj = parse_object_buffer(sha1, type, size, contents, &eaten);
+
+ if (!eaten)
+ free(contents);
+ return obj;
+}
+
+static int fsck_loose(const unsigned char *sha1, const char *path, void *data)
+{
+ struct object *obj = parse_loose_object(sha1, path);
+
+ if (!obj) {
+ errors_found |= ERROR_OBJECT;
+ error("%s: object corrupt or missing: %s",
+ sha1_to_hex(sha1), path);
+ return 0; /* keep checking other objects */
+ }
+
+ obj->flags = HAS_OBJ;
+ if (fsck_obj(obj))
+ errors_found |= ERROR_OBJECT;
+ return 0;
+}
+
+static int fsck_cruft(const char *basename, const char *path, void *data)
+{
+ if (!starts_with(basename, "tmp_obj_"))
+ fprintf(stderr, "bad sha1 file: %s\n", path);
+ return 0;
+}
+
+static int fsck_subdir(int nr, const char *path, void *progress)
+{
+ display_progress(progress, nr + 1);
+ return 0;
+}
+
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);
- }
+ progress = start_progress(_("Checking object directories"), 256);
+
+ for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir,
+ progress);
+ display_progress(progress, 256);
stop_progress(&progress);
- fsck_sha1_list();
}
static int fsck_head_link(void)
{
- int flag;
int null_is_error = 0;
if (verbose)
fprintf(stderr, "Checking HEAD link\n");
- head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag);
- if (!head_points_at)
+ head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, NULL);
+ if (!head_points_at) {
+ errors_found |= ERROR_REFS;
return error("Invalid HEAD");
+ }
if (!strcmp(head_points_at, "HEAD"))
/* detached HEAD */
null_is_error = 1;
- else if (!starts_with(head_points_at, "refs/heads/"))
+ else if (!starts_with(head_points_at, "refs/heads/")) {
+ errors_found |= ERROR_REFS;
return error("HEAD points to something strange (%s)",
head_points_at);
- if (is_null_sha1(head_sha1)) {
- if (null_is_error)
+ }
+ if (is_null_oid(&head_oid)) {
+ if (null_is_error) {
+ errors_found |= ERROR_REFS;
return error("HEAD: detached HEAD points at nothing");
+ }
fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n",
head_points_at + 11);
}
@@ -591,9 +603,13 @@ static int fsck_cache_tree(struct cache_tree *it)
if (!obj) {
error("%s: invalid sha1 pointer in cache-tree",
sha1_to_hex(it->sha1));
+ errors_found |= ERROR_REFS;
return 1;
}
obj->used = 1;
+ if (name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj, xstrdup(":"));
mark_object_reachable(obj);
if (obj->type != OBJ_TREE)
err |= objerror(obj, "non-tree in cache-tree");
@@ -603,8 +619,31 @@ static int fsck_cache_tree(struct cache_tree *it)
return err;
}
+static void mark_object_for_connectivity(const unsigned char *sha1)
+{
+ struct object *obj = lookup_unknown_object(sha1);
+ obj->flags |= HAS_OBJ;
+}
+
+static int mark_loose_for_connectivity(const unsigned char *sha1,
+ const char *path,
+ void *data)
+{
+ mark_object_for_connectivity(sha1);
+ return 0;
+}
+
+static int mark_packed_for_connectivity(const unsigned char *sha1,
+ struct packed_git *pack,
+ uint32_t pos,
+ void *data)
+{
+ mark_object_for_connectivity(sha1);
+ return 0;
+}
+
static char const * const fsck_usage[] = {
- N_("git fsck [options] [<object>...]"),
+ N_("git fsck [<options>] [<object>...]"),
NULL
};
@@ -617,10 +656,12 @@ static struct option fsck_opts[] = {
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, "connectivity-only", &connectivity_only, N_("check only connectivity")),
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_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")),
OPT_END(),
};
@@ -630,10 +671,16 @@ 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);
+ fsck_walk_options.walk = mark_object;
+ fsck_obj_options.walk = mark_used;
+ fsck_obj_options.error_func = fsck_error_func;
+ if (check_strict)
+ fsck_obj_options.strict = 1;
+
if (show_progress == -1)
show_progress = isatty(2);
if (verbose)
@@ -644,42 +691,48 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
include_reflogs = 0;
}
- fsck_head_link();
- fsck_object_dir(get_object_directory());
-
- prepare_alt_odb();
- for (alt = alt_odb_list; alt; alt = alt->next) {
- char namebuf[PATH_MAX];
- int namelen = alt->name - alt->base;
- memcpy(namebuf, alt->base, namelen);
- namebuf[namelen - 1] = 0;
- fsck_object_dir(namebuf);
- }
-
- if (check_full) {
- struct packed_git *p;
- uint32_t total = 0, count = 0;
- struct progress *progress = NULL;
+ if (name_objects)
+ fsck_walk_options.object_names =
+ xcalloc(1, sizeof(struct decoration));
- prepare_packed_git();
+ git_config(fsck_config, NULL);
- if (show_progress) {
+ fsck_head_link();
+ if (connectivity_only) {
+ for_each_loose_object(mark_loose_for_connectivity, NULL, 0);
+ for_each_packed_object(mark_packed_for_connectivity, NULL, 0);
+ } else {
+ fsck_object_dir(get_object_directory());
+
+ prepare_alt_odb();
+ for (alt = alt_odb_list; alt; alt = alt->next)
+ fsck_object_dir(alt->path);
+
+ if (check_full) {
+ struct packed_git *p;
+ uint32_t total = 0, count = 0;
+ struct progress *progress = NULL;
+
+ prepare_packed_git();
+
+ 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) {
- if (open_pack_index(p))
- continue;
- total += p->num_objects;
+ /* verify gives error messages itself */
+ if (verify_pack(p, fsck_obj_buffer,
+ progress, count))
+ errors_found |= ERROR_PACK;
+ count += p->num_objects;
}
-
- progress = start_progress("Checking objects", total);
- }
- for (p = packed_git; p; p = p->next) {
- /* 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);
}
- stop_progress(&progress);
}
heads = 0;
@@ -689,16 +742,22 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
if (!get_sha1(arg, sha1)) {
struct object *obj = lookup_object(sha1);
- /* Error is printed by lookup_object(). */
- if (!obj)
+ if (!obj || !(obj->flags & HAS_OBJ)) {
+ error("%s: object missing", sha1_to_hex(sha1));
+ errors_found |= ERROR_OBJECT;
continue;
+ }
obj->used = 1;
+ if (name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj, xstrdup(arg));
mark_object_reachable(obj);
heads++;
continue;
}
error("invalid parameter: expected sha1, got '%s'", arg);
+ errors_found |= ERROR_OBJECT;
}
/*
@@ -706,7 +765,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
* default ones from .git/refs. We also consider the index file
* in this case (ie this implies --cache).
*/
- if (!heads) {
+ if (!argc) {
get_default_heads();
keep_cache_objects = 1;
}
@@ -721,11 +780,15 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
mode = active_cache[i]->ce_mode;
if (S_ISGITLINK(mode))
continue;
- blob = lookup_blob(active_cache[i]->sha1);
+ blob = lookup_blob(active_cache[i]->oid.hash);
if (!blob)
continue;
obj = &blob->object;
obj->used = 1;
+ if (name_objects)
+ add_decoration(fsck_walk_options.object_names,
+ obj,
+ xstrfmt(":%s", active_cache[i]->name));
mark_object_reachable(obj);
}
if (active_cache_tree)
diff --git a/builtin/gc.c b/builtin/gc.c
index 5bbb5e3cc6..a2b9e8924e 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -11,7 +11,8 @@
*/
#include "builtin.h"
-#include "cache.h"
+#include "tempfile.h"
+#include "lockfile.h"
#include "parse-options.h"
#include "run-command.h"
#include "sigchain.h"
@@ -21,67 +22,120 @@
#define FAILED_RUN "failed to run %s"
static const char * const builtin_gc_usage[] = {
- N_("git gc [options]"),
+ N_("git gc [<options>]"),
NULL
};
static int pack_refs = 1;
+static int prune_reflogs = 1;
+static int aggressive_depth = 50;
static int aggressive_window = 250;
static int gc_auto_threshold = 6700;
static int gc_auto_pack_limit = 50;
+static int detach_auto = 1;
+static unsigned long gc_log_expire_time;
+static const char *gc_log_expire = "1.day.ago";
static const char *prune_expire = "2.weeks.ago";
+static const char *prune_worktrees_expire = "3.months.ago";
static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT;
static struct argv_array reflog = ARGV_ARRAY_INIT;
static struct argv_array repack = ARGV_ARRAY_INIT;
static struct argv_array prune = ARGV_ARRAY_INIT;
+static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
static struct argv_array rerere = ARGV_ARRAY_INIT;
-static char *pidfile;
+static struct tempfile pidfile;
+static struct lock_file log_lock;
-static void remove_pidfile(void)
+static struct string_list pack_garbage = STRING_LIST_INIT_DUP;
+
+static void clean_pack_garbage(void)
{
- if (pidfile)
- unlink(pidfile);
+ int i;
+ for (i = 0; i < pack_garbage.nr; i++)
+ unlink_or_warn(pack_garbage.items[i].string);
+ string_list_clear(&pack_garbage, 0);
}
-static void remove_pidfile_on_signal(int signo)
+static void report_pack_garbage(unsigned seen_bits, const char *path)
{
- remove_pidfile();
+ if (seen_bits == PACKDIR_FILE_IDX)
+ string_list_append(&pack_garbage, path);
+}
+
+static void git_config_date_string(const char *key, const char **output)
+{
+ if (git_config_get_string_const(key, output))
+ return;
+ if (strcmp(*output, "now")) {
+ unsigned long now = approxidate("now");
+ if (approxidate(*output) >= now)
+ git_die_config(key, _("Invalid %s: '%s'"), key, *output);
+ }
+}
+
+static void process_log_file(void)
+{
+ struct stat st;
+ if (fstat(get_lock_file_fd(&log_lock), &st)) {
+ /*
+ * Perhaps there was an i/o error or another
+ * unlikely situation. Try to make a note of
+ * this in gc.log along with any existing
+ * messages.
+ */
+ int saved_errno = errno;
+ fprintf(stderr, _("Failed to fstat %s: %s"),
+ get_tempfile_path(&log_lock.tempfile),
+ strerror(saved_errno));
+ fflush(stderr);
+ commit_lock_file(&log_lock);
+ errno = saved_errno;
+ } else if (st.st_size) {
+ /* There was some error recorded in the lock file */
+ commit_lock_file(&log_lock);
+ } else {
+ /* No error, clean up any old gc.log */
+ unlink(git_path("gc.log"));
+ rollback_lock_file(&log_lock);
+ }
+}
+
+static void process_log_file_at_exit(void)
+{
+ fflush(stderr);
+ process_log_file();
+}
+
+static void process_log_file_on_signal(int signo)
+{
+ process_log_file();
sigchain_pop(signo);
raise(signo);
}
-static int gc_config(const char *var, const char *value, void *cb)
+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;
- }
- 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")) {
- unsigned long now = approxidate("now");
- if (approxidate(value) >= now)
- return error(_("Invalid %s: '%s'"), var, value);
- }
- return git_config_string(&prune_expire, var, value);
+ pack_refs = git_config_bool("gc.packrefs", value);
}
- return git_default_config(var, value, cb);
+
+ 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);
+ git_config_date_string("gc.pruneexpire", &prune_expire);
+ git_config_date_string("gc.worktreepruneexpire", &prune_worktrees_expire);
+ git_config_date_string("gc.logexpiry", &gc_log_expire);
+
+ git_config(git_default_config, NULL);
}
static int too_many_loose_objects(void)
@@ -145,7 +199,7 @@ static int too_many_packs(void)
*/
cnt++;
}
- return gc_auto_pack_limit <= cnt;
+ return gc_auto_pack_limit < cnt;
}
static void add_repack_all_option(void)
@@ -159,6 +213,11 @@ static void add_repack_all_option(void)
}
}
+static void add_repack_incremental_option(void)
+{
+ argv_array_push(&repack, "--no-write-bitmap-index");
+}
+
static int need_to_gc(void)
{
/*
@@ -176,10 +235,12 @@ static int need_to_gc(void)
*/
if (too_many_packs())
add_repack_all_option();
- else if (!too_many_loose_objects())
+ else if (too_many_loose_objects())
+ add_repack_incremental_option();
+ else
return 0;
- if (run_hook(NULL, "pre-auto-gc", NULL))
+ if (run_hook_le(NULL, "pre-auto-gc", NULL))
return 0;
return 1;
}
@@ -194,20 +255,22 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
uintmax_t pid;
FILE *fp;
int fd;
+ char *pidfile_path;
- if (pidfile)
+ if (is_tempfile_active(&pidfile))
/* already locked */
return NULL;
if (gethostname(my_host, sizeof(my_host)))
- strcpy(my_host, "unknown");
+ xsnprintf(my_host, sizeof(my_host), "unknown");
- fd = hold_lock_file_for_update(&lock, git_path("gc.pid"),
+ pidfile_path = git_pathdup("gc.pid");
+ fd = hold_lock_file_for_update(&lock, pidfile_path,
LOCK_DIE_ON_ERROR);
if (!force) {
static char locking_host[128];
int should_exit;
- fp = fopen(git_path("gc.pid"), "r");
+ fp = fopen(pidfile_path, "r");
memset(locking_host, 0, sizeof(locking_host));
should_exit =
fp != NULL &&
@@ -222,7 +285,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
* running.
*/
time(NULL) - st.st_mtime <= 12 * 3600 &&
- fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 &&
+ fscanf(fp, "%"SCNuMAX" %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)
@@ -231,6 +294,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
if (fd >= 0)
rollback_lock_file(&lock);
*ret_pid = pid;
+ free(pidfile_path);
return locking_host;
}
}
@@ -240,12 +304,55 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
write_in_full(fd, sb.buf, sb.len);
strbuf_release(&sb);
commit_lock_file(&lock);
+ register_tempfile(&pidfile, pidfile_path);
+ free(pidfile_path);
+ return NULL;
+}
- pidfile = git_pathdup("gc.pid");
- sigchain_push_common(remove_pidfile_on_signal);
- atexit(remove_pidfile);
+static int report_last_gc_error(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int ret = 0;
+ struct stat st;
+ char *gc_log_path = git_pathdup("gc.log");
- return NULL;
+ if (stat(gc_log_path, &st)) {
+ if (errno == ENOENT)
+ goto done;
+
+ ret = error_errno(_("Can't stat %s"), gc_log_path);
+ goto done;
+ }
+
+ if (st.st_mtime < gc_log_expire_time)
+ goto done;
+
+ ret = strbuf_read_file(&sb, gc_log_path, 0);
+ if (ret > 0)
+ ret = error(_("The last gc run reported the following. "
+ "Please correct the root cause\n"
+ "and remove %s.\n"
+ "Automatic cleanup will not be performed "
+ "until the file is removed.\n\n"
+ "%s"),
+ gc_log_path, sb.buf);
+ strbuf_release(&sb);
+done:
+ free(gc_log_path);
+ return ret;
+}
+
+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)
@@ -256,6 +363,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
int force = 0;
const char *name;
pid_t pid;
+ int daemonized = 0;
struct option builtin_gc_options[] = {
OPT__QUIET(&quiet, N_("suppress progress reporting")),
@@ -274,10 +382,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL);
argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
- argv_array_pushl(&prune, "prune", "--expire", NULL );
+ argv_array_pushl(&prune, "prune", "--expire", NULL);
+ argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
argv_array_pushl(&rerere, "rerere", "gc", NULL);
- git_config(gc_config, NULL);
+ /* default expiry time, overwritten in gc_config */
+ gc_config();
+ if (parse_expiry_date(gc_log_expire, &gc_log_expire_time))
+ die(_("Failed to parse gc.logexpiry value %s"), gc_log_expire);
if (pack_refs < 0)
pack_refs = !is_bare_repository();
@@ -289,7 +401,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (aggressive) {
argv_array_push(&repack, "-f");
- argv_array_push(&repack, "--depth=250");
+ if (aggressive_depth > 0)
+ argv_array_pushf(&repack, "--depth=%d", aggressive_depth);
if (aggressive_window > 0)
argv_array_pushf(&repack, "--window=%d", aggressive_window);
}
@@ -302,11 +415,25 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
*/
if (!need_to_gc())
return 0;
- if (!quiet)
- fprintf(stderr,
- _("Auto packing the repository for optimum performance. You may also\n"
- "run \"git gc\" manually. See "
- "\"git help gc\" for more information.\n"));
+ if (!quiet) {
+ if (detach_auto)
+ fprintf(stderr, _("Auto packing the repository in background for optimum performance.\n"));
+ else
+ fprintf(stderr, _("Auto packing the repository for optimum performance.\n"));
+ fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n"));
+ }
+ if (detach_auto) {
+ if (report_last_gc_error())
+ return -1;
+
+ if (gc_before_repack())
+ return -1;
+ /*
+ * failure to daemonize is ok, we'll continue
+ * in foreground
+ */
+ daemonized = !daemonize();
+ }
} else
add_repack_all_option();
@@ -318,29 +445,51 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
name, (uintmax_t)pid);
}
- if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, pack_refs_cmd.argv[0]);
+ if (daemonized) {
+ hold_lock_file_for_update(&log_lock,
+ git_path("gc.log"),
+ LOCK_DIE_ON_ERROR);
+ dup2(get_lock_file_fd(&log_lock), 2);
+ sigchain_push_common(process_log_file_on_signal);
+ atexit(process_log_file_at_exit);
+ }
- if (run_command_v_opt(reflog.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, reflog.argv[0]);
+ if (gc_before_repack())
+ return -1;
+
+ if (!repository_format_precious_objects) {
+ if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, repack.argv[0]);
- if (run_command_v_opt(repack.argv, RUN_GIT_CMD))
- return error(FAILED_RUN, repack.argv[0]);
+ if (prune_expire) {
+ 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 (prune_expire) {
- 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 (prune_worktrees_expire) {
+ argv_array_push(&prune_worktrees, prune_worktrees_expire);
+ if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD))
+ return error(FAILED_RUN, prune_worktrees.argv[0]);
}
if (run_command_v_opt(rerere.argv, RUN_GIT_CMD))
return error(FAILED_RUN, rerere.argv[0]);
+ report_garbage = report_pack_garbage;
+ reprepare_packed_git();
+ if (pack_garbage.nr > 0)
+ clean_pack_garbage();
+
if (auto_gc && too_many_loose_objects())
warning(_("There are too many unreachable loose objects; "
"run 'git prune' to remove them."));
+ if (!daemonized)
+ unlink(git_path("gc.log"));
+
return 0;
}
diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c
index aa72596083..e21c5416cd 100644
--- a/builtin/get-tar-commit-id.c
+++ b/builtin/get-tar-commit-id.c
@@ -8,7 +8,7 @@
#include "quote.h"
static const char builtin_get_tar_commit_id_usage[] =
-"git get-tar-commit-id < <tarfile>";
+"git get-tar-commit-id";
/* ustar header + extended global header content */
#define RECORDSIZE (512)
@@ -19,6 +19,7 @@ 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)
@@ -29,10 +30,10 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
die("git get-tar-commit-id: read error");
if (header->typeflag[0] != 'g')
return 1;
- if (memcmp(content, "52 comment=", 11))
+ if (!skip_prefix(content, "52 comment=", &comment))
return 1;
- n = write_in_full(1, content + 11, 41);
+ n = write_in_full(1, comment, 41);
if (n < 41)
die_errno("git get-tar-commit-id: write error");
diff --git a/builtin/grep.c b/builtin/grep.c
index 69ac2d8797..9304c33e75 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -18,17 +18,27 @@
#include "quote.h"
#include "dir.h"
#include "pathspec.h"
+#include "submodule.h"
+#include "submodule-config.h"
static char const * const grep_usage[] = {
- N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
+ N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"),
NULL
};
-static int use_threads = 1;
+static const char *super_prefix;
+static int recurse_submodules;
+static struct argv_array submodule_options = ARGV_ARRAY_INIT;
+static const char *parent_basename;
+
+static int grep_submodule_launch(struct grep_opt *opt,
+ const struct grep_source *gs);
+
+#define GREP_NUM_THREADS_DEFAULT 8
+static int num_threads;
#ifndef NO_PTHREADS
-#define THREADS 8
-static pthread_t threads[THREADS];
+static pthread_t *threads;
/* We use one producer thread and THREADS consumer
* threads. The producer adds struct work_items to 'todo' and the
@@ -63,13 +73,13 @@ static pthread_mutex_t grep_mutex;
static inline void grep_lock(void)
{
- if (use_threads)
+ if (num_threads)
pthread_mutex_lock(&grep_mutex);
}
static inline void grep_unlock(void)
{
- if (use_threads)
+ if (num_threads)
pthread_mutex_unlock(&grep_mutex);
}
@@ -174,7 +184,10 @@ static void *run(void *arg)
break;
opt->output_priv = w;
- hit |= grep_source(opt, &w->source);
+ if (w->source.type == GREP_SOURCE_SUBMODULE)
+ hit |= grep_submodule_launch(opt, &w->source);
+ else
+ hit |= grep_source(opt, &w->source);
grep_source_clear_data(&w->source);
work_done(w);
}
@@ -206,7 +219,8 @@ static void start_threads(struct grep_opt *opt)
strbuf_init(&todo[i].out, 0);
}
- for (i = 0; i < ARRAY_SIZE(threads); i++) {
+ threads = xcalloc(num_threads, sizeof(*threads));
+ for (i = 0; i < num_threads; i++) {
int err;
struct grep_opt *o = grep_opt_dup(opt);
o->output = strbuf_out;
@@ -238,12 +252,14 @@ static int wait_all(void)
pthread_cond_broadcast(&cond_add);
grep_unlock();
- for (i = 0; i < ARRAY_SIZE(threads); i++) {
+ for (i = 0; i < num_threads; i++) {
void *h;
pthread_join(threads[i], &h);
hit |= (int) (intptr_t) h;
}
+ free(threads);
+
pthread_mutex_destroy(&grep_mutex);
pthread_mutex_destroy(&grep_read_mutex);
pthread_mutex_destroy(&grep_attr_mutex);
@@ -267,6 +283,14 @@ static int grep_cmd_config(const char *var, const char *value, void *cb)
int st = grep_config(var, value, cb);
if (git_color_default_config(var, value, cb) < 0)
st = -1;
+
+ if (!strcmp(var, "grep.threads")) {
+ num_threads = git_config_int(var, value);
+ if (num_threads < 0)
+ die(_("invalid number of threads specified (%d) for %s"),
+ num_threads, var);
+ }
+
return st;
}
@@ -289,12 +313,16 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
if (opt->relative && opt->prefix_length) {
quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
strbuf_insert(&pathbuf, 0, filename, tree_name_len);
+ } else if (super_prefix) {
+ strbuf_add(&pathbuf, filename, tree_name_len);
+ strbuf_addstr(&pathbuf, super_prefix);
+ strbuf_addstr(&pathbuf, filename + tree_name_len);
} else {
strbuf_addstr(&pathbuf, filename);
}
#ifndef NO_PTHREADS
- if (use_threads) {
+ if (num_threads) {
add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
strbuf_release(&pathbuf);
return 0;
@@ -317,13 +345,16 @@ static int grep_file(struct grep_opt *opt, const char *filename)
{
struct strbuf buf = STRBUF_INIT;
- if (opt->relative && opt->prefix_length)
+ if (opt->relative && opt->prefix_length) {
quote_path_relative(filename, opt->prefix, &buf);
- else
+ } else {
+ if (super_prefix)
+ strbuf_addstr(&buf, super_prefix);
strbuf_addstr(&buf, filename);
+ }
#ifndef NO_PTHREADS
- if (use_threads) {
+ if (num_threads) {
add_work(opt, GREP_SOURCE_FILE, buf.buf, filename, filename);
strbuf_release(&buf);
return 0;
@@ -354,45 +385,323 @@ static void append_path(struct grep_opt *opt, const void *data, size_t len)
static void run_pager(struct grep_opt *opt, const char *prefix)
{
struct string_list *path_list = opt->output_priv;
- const char **argv = xmalloc(sizeof(const char *) * (path_list->nr + 1));
+ struct child_process child = CHILD_PROCESS_INIT;
int i, status;
for (i = 0; i < path_list->nr; i++)
- argv[i] = path_list->items[i].string;
- argv[path_list->nr] = NULL;
+ argv_array_push(&child.args, path_list->items[i].string);
+ child.dir = prefix;
+ child.use_shell = 1;
- if (prefix && chdir(prefix))
- die(_("Failed to chdir: %s"), prefix);
- status = run_command_v_opt(argv, RUN_USING_SHELL);
+ status = run_command(&child);
if (status)
exit(status);
- free(argv);
}
-static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached)
+static void compile_submodule_options(const struct grep_opt *opt,
+ const struct pathspec *pathspec,
+ int cached, int untracked,
+ int opt_exclude, int use_index,
+ int pattern_type_arg)
+{
+ struct grep_pat *pattern;
+ int i;
+
+ if (recurse_submodules)
+ argv_array_push(&submodule_options, "--recurse-submodules");
+
+ if (cached)
+ argv_array_push(&submodule_options, "--cached");
+ if (!use_index)
+ argv_array_push(&submodule_options, "--no-index");
+ if (untracked)
+ argv_array_push(&submodule_options, "--untracked");
+ if (opt_exclude > 0)
+ argv_array_push(&submodule_options, "--exclude-standard");
+
+ if (opt->invert)
+ argv_array_push(&submodule_options, "-v");
+ if (opt->ignore_case)
+ argv_array_push(&submodule_options, "-i");
+ if (opt->word_regexp)
+ argv_array_push(&submodule_options, "-w");
+ switch (opt->binary) {
+ case GREP_BINARY_NOMATCH:
+ argv_array_push(&submodule_options, "-I");
+ break;
+ case GREP_BINARY_TEXT:
+ argv_array_push(&submodule_options, "-a");
+ break;
+ default:
+ break;
+ }
+ if (opt->allow_textconv)
+ argv_array_push(&submodule_options, "--textconv");
+ if (opt->max_depth != -1)
+ argv_array_pushf(&submodule_options, "--max-depth=%d",
+ opt->max_depth);
+ if (opt->linenum)
+ argv_array_push(&submodule_options, "-n");
+ if (!opt->pathname)
+ argv_array_push(&submodule_options, "-h");
+ if (!opt->relative)
+ argv_array_push(&submodule_options, "--full-name");
+ if (opt->name_only)
+ argv_array_push(&submodule_options, "-l");
+ if (opt->unmatch_name_only)
+ argv_array_push(&submodule_options, "-L");
+ if (opt->null_following_name)
+ argv_array_push(&submodule_options, "-z");
+ if (opt->count)
+ argv_array_push(&submodule_options, "-c");
+ if (opt->file_break)
+ argv_array_push(&submodule_options, "--break");
+ if (opt->heading)
+ argv_array_push(&submodule_options, "--heading");
+ if (opt->pre_context)
+ argv_array_pushf(&submodule_options, "--before-context=%d",
+ opt->pre_context);
+ if (opt->post_context)
+ argv_array_pushf(&submodule_options, "--after-context=%d",
+ opt->post_context);
+ if (opt->funcname)
+ argv_array_push(&submodule_options, "-p");
+ if (opt->funcbody)
+ argv_array_push(&submodule_options, "-W");
+ if (opt->all_match)
+ argv_array_push(&submodule_options, "--all-match");
+ if (opt->debug)
+ argv_array_push(&submodule_options, "--debug");
+ if (opt->status_only)
+ argv_array_push(&submodule_options, "-q");
+
+ switch (pattern_type_arg) {
+ case GREP_PATTERN_TYPE_BRE:
+ argv_array_push(&submodule_options, "-G");
+ break;
+ case GREP_PATTERN_TYPE_ERE:
+ argv_array_push(&submodule_options, "-E");
+ break;
+ case GREP_PATTERN_TYPE_FIXED:
+ argv_array_push(&submodule_options, "-F");
+ break;
+ case GREP_PATTERN_TYPE_PCRE:
+ argv_array_push(&submodule_options, "-P");
+ break;
+ case GREP_PATTERN_TYPE_UNSPECIFIED:
+ break;
+ }
+
+ for (pattern = opt->pattern_list; pattern != NULL;
+ pattern = pattern->next) {
+ switch (pattern->token) {
+ case GREP_PATTERN:
+ argv_array_pushf(&submodule_options, "-e%s",
+ pattern->pattern);
+ break;
+ case GREP_AND:
+ case GREP_OPEN_PAREN:
+ case GREP_CLOSE_PAREN:
+ case GREP_NOT:
+ case GREP_OR:
+ argv_array_push(&submodule_options, pattern->pattern);
+ break;
+ /* BODY and HEAD are not used by git-grep */
+ case GREP_PATTERN_BODY:
+ case GREP_PATTERN_HEAD:
+ break;
+ }
+ }
+
+ /*
+ * Limit number of threads for child process to use.
+ * This is to prevent potential fork-bomb behavior of git-grep as each
+ * submodule process has its own thread pool.
+ */
+ argv_array_pushf(&submodule_options, "--threads=%d",
+ (num_threads + 1) / 2);
+
+ /* Add Pathspecs */
+ argv_array_push(&submodule_options, "--");
+ for (i = 0; i < pathspec->nr; i++)
+ argv_array_push(&submodule_options,
+ pathspec->items[i].original);
+}
+
+/*
+ * Launch child process to grep contents of a submodule
+ */
+static int grep_submodule_launch(struct grep_opt *opt,
+ const struct grep_source *gs)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ int status, i;
+ const char *end_of_base;
+ const char *name;
+ struct work_item *w = opt->output_priv;
+
+ end_of_base = strchr(gs->name, ':');
+ if (gs->identifier && end_of_base)
+ name = end_of_base + 1;
+ else
+ name = gs->name;
+
+ prepare_submodule_repo_env(&cp.env_array);
+ argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT);
+
+ /* Add super prefix */
+ argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
+ super_prefix ? super_prefix : "",
+ name);
+ argv_array_push(&cp.args, "grep");
+
+ /*
+ * Add basename of parent project
+ * When performing grep on a tree object the filename is prefixed
+ * with the object's name: 'tree-name:filename'. In order to
+ * provide uniformity of output we want to pass the name of the
+ * parent project's object name to the submodule so the submodule can
+ * prefix its output with the parent's name and not its own SHA1.
+ */
+ if (gs->identifier && end_of_base)
+ argv_array_pushf(&cp.args, "--parent-basename=%.*s",
+ (int) (end_of_base - gs->name),
+ gs->name);
+
+ /* Add options */
+ for (i = 0; i < submodule_options.argc; i++) {
+ /*
+ * If there is a tree identifier for the submodule, add the
+ * rev after adding the submodule options but before the
+ * pathspecs. To do this we listen for the '--' and insert the
+ * sha1 before pushing the '--' onto the child process argv
+ * array.
+ */
+ if (gs->identifier &&
+ !strcmp("--", submodule_options.argv[i])) {
+ argv_array_push(&cp.args, sha1_to_hex(gs->identifier));
+ }
+
+ argv_array_push(&cp.args, submodule_options.argv[i]);
+ }
+
+ cp.git_cmd = 1;
+ cp.dir = gs->path;
+
+ /*
+ * Capture output to output buffer and check the return code from the
+ * child process. A '0' indicates a hit, a '1' indicates no hit and
+ * anything else is an error.
+ */
+ status = capture_command(&cp, &w->out, 0);
+ if (status && (status != 1)) {
+ /* flush the buffer */
+ write_or_die(1, w->out.buf, w->out.len);
+ die("process for submodule '%s' failed with exit code: %d",
+ gs->name, status);
+ }
+
+ /* invert the return code to make a hit equal to 1 */
+ return !status;
+}
+
+/*
+ * Prep grep structures for a submodule grep
+ * sha1: the sha1 of the submodule or NULL if using the working tree
+ * filename: name of the submodule including tree name of parent
+ * path: location of the submodule
+ */
+static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1,
+ const char *filename, const char *path)
+{
+ if (!is_submodule_initialized(path))
+ return 0;
+ if (!is_submodule_populated(path)) {
+ /*
+ * If searching history, check for the presense of the
+ * submodule's gitdir before skipping the submodule.
+ */
+ if (sha1) {
+ const struct submodule *sub =
+ submodule_from_path(null_sha1, path);
+ if (sub)
+ path = git_path("modules/%s", sub->name);
+
+ if (!(is_directory(path) && is_git_directory(path)))
+ return 0;
+ } else {
+ return 0;
+ }
+ }
+
+#ifndef NO_PTHREADS
+ if (num_threads) {
+ add_work(opt, GREP_SOURCE_SUBMODULE, filename, path, sha1);
+ return 0;
+ } else
+#endif
+ {
+ struct work_item w;
+ int hit;
+
+ grep_source_init(&w.source, GREP_SOURCE_SUBMODULE,
+ filename, path, sha1);
+ strbuf_init(&w.out, 0);
+ opt->output_priv = &w;
+ hit = grep_submodule_launch(opt, &w.source);
+
+ write_or_die(1, w.out.buf, w.out.len);
+
+ grep_source_clear(&w.source);
+ strbuf_release(&w.out);
+ return hit;
+ }
+}
+
+static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec,
+ int cached)
{
int hit = 0;
int nr;
+ struct strbuf name = STRBUF_INIT;
+ int name_base_len = 0;
+ if (super_prefix) {
+ name_base_len = strlen(super_prefix);
+ strbuf_addstr(&name, super_prefix);
+ }
+
read_cache();
for (nr = 0; nr < active_nr; nr++) {
const struct cache_entry *ce = active_cache[nr];
- if (!S_ISREG(ce->ce_mode))
- continue;
- if (!ce_path_match(ce, pathspec, NULL))
+ strbuf_setlen(&name, name_base_len);
+ strbuf_addstr(&name, ce->name);
+
+ if (S_ISREG(ce->ce_mode) &&
+ match_pathspec(pathspec, name.buf, name.len, 0, NULL,
+ S_ISDIR(ce->ce_mode) ||
+ S_ISGITLINK(ce->ce_mode))) {
+ /*
+ * If CE_VALID is on, we assume worktree file and its
+ * cache entry are identical, even if worktree file has
+ * been modified, so use cache version instead
+ */
+ if (cached || (ce->ce_flags & CE_VALID) ||
+ ce_skip_worktree(ce)) {
+ if (ce_stage(ce) || ce_intent_to_add(ce))
+ continue;
+ hit |= grep_sha1(opt, ce->oid.hash, ce->name,
+ 0, ce->name);
+ } else {
+ hit |= grep_file(opt, ce->name);
+ }
+ } else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
+ submodule_path_match(pathspec, name.buf, NULL)) {
+ hit |= grep_submodule(opt, NULL, ce->name, ce->name);
+ } else {
continue;
- /*
- * If CE_VALID is on, we assume worktree file and its cache entry
- * are identical, even if worktree file has been modified, so use
- * cache version instead
- */
- if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) {
- if (ce_stage(ce))
- continue;
- hit |= grep_sha1(opt, ce->sha1, ce->name, 0, ce->name);
}
- else
- hit |= grep_file(opt, ce->name);
+
if (ce_stage(ce)) {
do {
nr++;
@@ -403,6 +712,8 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
if (hit && opt->status_only)
break;
}
+
+ strbuf_release(&name);
return hit;
}
@@ -414,12 +725,22 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
enum interesting match = entry_not_interesting;
struct name_entry entry;
int old_baselen = base->len;
+ struct strbuf name = STRBUF_INIT;
+ int name_base_len = 0;
+ if (super_prefix) {
+ strbuf_addstr(&name, super_prefix);
+ name_base_len = name.len;
+ }
while (tree_entry(tree, &entry)) {
int te_len = tree_entry_len(&entry);
if (match != all_entries_interesting) {
- match = tree_entry_interesting(&entry, base, tn_len, pathspec);
+ strbuf_addstr(&name, base->buf + tn_len);
+ match = tree_entry_interesting(&entry, &name,
+ 0, pathspec);
+ strbuf_setlen(&name, name_base_len);
+
if (match == all_entries_not_interesting)
break;
if (match == entry_not_interesting)
@@ -429,39 +750,44 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
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.oid->hash, base->buf, tn_len,
check_attr ? base->buf + tn_len : NULL);
- }
- else if (S_ISDIR(entry.mode)) {
+ } else if (S_ISDIR(entry.mode)) {
enum object_type type;
struct tree_desc sub;
void *data;
unsigned long size;
- data = lock_and_read_sha1_file(entry.sha1, &type, &size);
+ data = lock_and_read_sha1_file(entry.oid->hash, &type, &size);
if (!data)
die(_("unable to read tree (%s)"),
- sha1_to_hex(entry.sha1));
+ oid_to_hex(entry.oid));
strbuf_addch(base, '/');
init_tree_desc(&sub, data, size);
hit |= grep_tree(opt, pathspec, &sub, base, tn_len,
check_attr);
free(data);
+ } else if (recurse_submodules && S_ISGITLINK(entry.mode)) {
+ hit |= grep_submodule(opt, entry.oid->hash, base->buf,
+ base->buf + tn_len);
}
+
strbuf_setlen(base, old_baselen);
if (hit && opt->status_only)
break;
}
+
+ strbuf_release(&name);
return hit;
}
static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
- struct object *obj, const char *name, struct object_context *oc)
+ struct object *obj, const char *name, const char *path)
{
if (obj->type == OBJ_BLOB)
- return grep_sha1(opt, obj->sha1, name, 0, oc ? oc->path : NULL);
+ return grep_sha1(opt, obj->oid.hash, name, 0, path);
if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
struct tree_desc tree;
void *data;
@@ -470,12 +796,16 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
int hit, len;
grep_read_lock();
- data = read_object_with_reference(obj->sha1, tree_type,
+ data = read_object_with_reference(obj->oid.hash, tree_type,
&size, NULL);
grep_read_unlock();
if (!data)
- die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
+ die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid));
+
+ /* Use parent's name as base when recursing submodules */
+ if (recurse_submodules && parent_basename)
+ name = parent_basename;
len = name ? strlen(name) : 0;
strbuf_init(&base, PATH_MAX + len + 1);
@@ -503,7 +833,13 @@ 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, list->objects[i].context)) {
+
+ /* load the gitmodules file for this rev */
+ if (recurse_submodules) {
+ submodule_free();
+ gitmodules_config_sha1(real_obj->oid.hash);
+ }
+ if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) {
hit = 1;
if (opt->status_only)
break;
@@ -513,12 +849,14 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
}
static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
- int exc_std)
+ int exc_std, int use_index)
{
struct dir_struct dir;
int i, hit = 0;
memset(&dir, 0, sizeof(dir));
+ if (!use_index)
+ dir.flags |= DIR_NO_GITLINKS;
if (exc_std)
setup_standard_excludes(&dir);
@@ -564,7 +902,7 @@ static int file_callback(const struct option *opt, const char *arg, int unset)
patterns = from_stdin ? stdin : fopen(arg, "r");
if (!patterns)
die_errno(_("cannot open '%s'"), arg);
- while (strbuf_getline(&sb, patterns, '\n') == 0) {
+ while (strbuf_getline(&sb, patterns) == 0) {
/* ignore empty line like grep does */
if (sb.len == 0)
continue;
@@ -614,11 +952,6 @@ static int pattern_callback(const struct option *opt, const char *arg,
return 0;
}
-static int help_callback(const struct option *opt, const char *arg, int unset)
-{
- return -1;
-}
-
int cmd_grep(int argc, const char **argv, const char *prefix)
{
int hit = 0;
@@ -634,6 +967,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
int dummy;
int use_index = 1;
int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
+ int allow_revs;
struct option options[] = {
OPT_BOOL(0, "cached", &cached,
@@ -643,7 +977,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "untracked", &untracked,
N_("search in both tracked and untracked files")),
OPT_SET_INT(0, "exclude-standard", &opt_exclude,
- N_("search also in ignored files"), 1),
+ N_("ignore files specified via '.gitignore'"), 1),
+ OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
+ N_("recursivley search in each submodule")),
+ OPT_STRING(0, "parent-basename", &parent_basename,
+ N_("basename"),
+ N_("prepend parent project's basename to output")),
OPT_GROUP(""),
OPT_BOOL('v', "invert-match", &opt.invert,
N_("show non-matching lines")),
@@ -704,6 +1043,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
N_("show <n> context lines before matches")),
OPT_INTEGER('A', "after-context", &opt.post_context,
N_("show <n> context lines after matches")),
+ OPT_INTEGER(0, "threads", &num_threads,
+ N_("use <n> worker threads")),
OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"),
context_callback),
OPT_BOOL('p', "show-function", &opt.funcname,
@@ -740,21 +1081,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager },
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()
};
- /*
- * 'git grep -h', unlike 'git grep -h <pattern>', is a request
- * to show usage information and exit.
- */
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage_with_options(grep_usage, options);
-
init_grep_defaults();
git_config(grep_cmd_config, NULL);
grep_init(&opt, prefix);
+ super_prefix = get_super_prefix();
/*
* If there is no -- then the paths must exist in the working
@@ -768,13 +1101,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
*/
argc = parse_options(argc, argv, prefix, options, grep_usage,
PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_STOP_AT_NON_OPTION |
- PARSE_OPT_NO_INTERNAL_HELP);
+ PARSE_OPT_STOP_AT_NON_OPTION);
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 */
- setup_git_directory();
+ if (use_index && !startup_info->have_repository) {
+ int fallback = 0;
+ git_config_get_bool("grep.fallbacktonoindex", &fallback);
+ if (fallback)
+ use_index = 0;
+ else
+ /* die the same way as if we did it at the beginning */
+ setup_git_directory();
+ }
/*
* skip a -- separator; we know it cannot be
@@ -803,7 +1141,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
opt.output_priv = &path_list;
opt.output = append_path;
string_list_append(&path_list, show_in_pager);
- use_threads = 0;
}
if (!opt.pattern_list)
@@ -813,35 +1150,82 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
compile_grep_patterns(&opt);
- /* Check revs and then paths */
+ /*
+ * We have to find "--" in a separate pass, because its presence
+ * influences how we will parse arguments that come before it.
+ */
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ seen_dashdash = 1;
+ break;
+ }
+ }
+
+ /*
+ * Resolve any rev arguments. If we have a dashdash, then everything up
+ * to it must resolve as a rev. If not, then we stop at the first
+ * non-rev and assume everything else is a path.
+ */
+ allow_revs = use_index && !untracked;
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_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_context(object, arg, &list, xmemdupz(&oc, sizeof(struct object_context)));
- continue;
- }
+ struct object *object;
+
if (!strcmp(arg, "--")) {
i++;
- seen_dashdash = 1;
+ break;
}
- break;
+
+ if (!allow_revs) {
+ if (seen_dashdash)
+ die(_("--no-index or --untracked cannot be used with revs"));
+ break;
+ }
+
+ if (get_sha1_with_context(arg, 0, sha1, &oc)) {
+ if (seen_dashdash)
+ die(_("unable to resolve revision: %s"), arg);
+ break;
+ }
+
+ 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);
+ }
+
+ /*
+ * Anything left over is presumed to be a path. But in the non-dashdash
+ * "do what I mean" case, we verify and complain when that isn't true.
+ */
+ if (!seen_dashdash) {
+ int j;
+ for (j = i; j < argc; j++)
+ verify_filename(prefix, argv[j], j == i && allow_revs);
}
+ 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;
+
#ifndef NO_PTHREADS
- if (list.nr || cached || online_cpus() == 1)
- use_threads = 0;
+ if (list.nr || cached || show_in_pager)
+ num_threads = 0;
+ else if (num_threads == 0)
+ num_threads = GREP_NUM_THREADS_DEFAULT;
+ else if (num_threads < 0)
+ die(_("invalid number of threads specified (%d)"), num_threads);
#else
- use_threads = 0;
+ num_threads = 0;
#endif
#ifndef NO_PTHREADS
- if (use_threads) {
+ if (num_threads) {
if (!(opt.name_only || opt.unmatch_name_only || opt.count)
&& (opt.pre_context || opt.post_context ||
opt.file_break || opt.funcbody))
@@ -850,20 +1234,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
}
#endif
- /* The rest are paths */
- if (!seen_dashdash) {
- int j;
- for (j = i; j < argc; j++)
- verify_filename(prefix, argv[j], j == i);
+ if (recurse_submodules) {
+ gitmodules_config();
+ compile_submodule_options(&opt, &pathspec, cached, untracked,
+ opt_exclude, use_index,
+ pattern_type_arg);
}
- 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;
-
if (show_in_pager && (cached || list.nr))
die(_("--open-files-in-pager only works on the worktree"));
@@ -874,6 +1251,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",
@@ -884,7 +1264,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
}
}
- if (!show_in_pager)
+ if (recurse_submodules && (!use_index || untracked))
+ die(_("option not supported with --recurse-submodules."));
+
+ if (!show_in_pager && !opt.status_only)
setup_pager();
if (!use_index && (untracked || cached))
@@ -892,9 +1275,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
if (!use_index || untracked) {
int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
- if (list.nr)
- die(_("--no-index or --untracked cannot be used with revs."));
- hit = grep_directory(&opt, &pathspec, use_exclude);
+ hit = grep_directory(&opt, &pathspec, use_exclude, use_index);
} else if (0 <= opt_exclude) {
die(_("--[no-]exclude-standard cannot be used for tracked contents."));
} else if (!list.nr) {
@@ -908,7 +1289,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
hit = grep_objects(&opt, &pathspec, &list);
}
- if (use_threads)
+ if (num_threads)
hit |= wait_all();
if (hit && show_in_pager)
run_pager(&opt, prefix);
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index d7fcf4c13c..9028e1fdcc 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -10,91 +10,112 @@
#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
+ ret = hash_sha1_file_literally(buf.buf, buf.len, type, sha1, flags);
+ 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;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf unquoted = STRBUF_INIT;
- while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ while (strbuf_getline(&buf, stdin) != EOF) {
if (buf.buf[0] == '"') {
- strbuf_reset(&nbuf);
- if (unquote_c_style(&nbuf, buf.buf, NULL))
+ strbuf_reset(&unquoted);
+ if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
+ strbuf_swap(&buf, &unquoted);
}
- 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);
+ strbuf_release(&unquoted);
}
-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
-};
-
-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, N_("type"), N_("object type")),
- OPT_BOOL('w', NULL, &write_object, N_("write the object into the object database")),
- OPT_COUNTUP( 0 , "stdin", &hashstdin, N_("read the object from stdin")),
- OPT_BOOL( 0 , "stdin-paths", &stdin_paths, N_("read file names from stdin")),
- OPT_BOOL( 0 , "no-filters", &no_filters, N_("store file as is without filters")),
- OPT_STRING( 0 , "path", &vpath, N_("file"), N_("process file as it were from this path")),
- OPT_END()
-};
-
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"),
+ NULL
+ };
+ const char *type = blob_type;
+ int hashstdin = 0;
+ int stdin_paths = 0;
+ int no_filters = 0;
+ int literally = 0;
+ int nongit = 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)
- vpath = prefix_filename(prefix, prefix_length, vpath);
- }
+ else
+ prefix = setup_git_directory_gently(&nongit);
+
+ prefix_length = prefix ? strlen(prefix) : 0;
+ if (vpath && prefix)
+ vpath = prefix_filename(prefix, prefix_length, vpath);
git_config(git_default_config, NULL);
@@ -119,19 +140,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 1fdefeb686..49f7a07f85 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -37,8 +37,10 @@ static int show_all = 0;
static int show_guides = 0;
static unsigned int colopts;
static enum help_format help_format = HELP_FORMAT_NONE;
+static int exclude_guides;
static struct option builtin_help_options[] = {
OPT_BOOL('a', "all", &show_all, N_("print all available commands")),
+ OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")),
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"),
@@ -49,7 +51,7 @@ static struct option builtin_help_options[] = {
};
static const char * const builtin_help_usage[] = {
- N_("git help [--all] [--guides] [--man|--web|--info] [command]"),
+ N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"),
NULL
};
@@ -79,12 +81,11 @@ 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;
@@ -128,7 +129,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_errno(_("failed to exec '%s'"), path);
}
}
@@ -141,22 +142,15 @@ static void exec_man_konqueror(const char *path, const char *page)
/* It's simpler to launch konqueror using kfmclient. */
if (path) {
- const char *file = strrchr(path, '/');
- if (file && !strcmp(file + 1, "konqueror")) {
- char *new = xstrdup(path);
- char *dest = strrchr(new, '/');
-
- /* strlen("konqueror") == strlen("kfmclient") */
- strcpy(dest + 1, "kfmclient");
- path = new;
- }
- if (file)
- filename = file;
+ size_t len;
+ if (strip_suffix(path, "/konqueror", &len))
+ path = xstrfmt("%.*s/kfmclient", (int)len, path);
+ filename = basename((char *)path);
} else
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_errno(_("failed to exec '%s'"), path);
}
}
@@ -165,26 +159,24 @@ 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_errno(_("failed to exec '%s'"), path);
}
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));
+ execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL);
+ warning(_("failed to exec '%s'"), cmd);
}
static void add_man_viewer(const char *name)
{
struct man_viewer_list **p = &man_viewer_list;
- size_t len = strlen(name);
while (*p)
p = &((*p)->next);
- *p = xcalloc(1, (sizeof(**p) + len + 1));
- strncpy((*p)->name, name, len);
+ FLEX_ALLOC_STR(*p, name, name);
}
static int supported_man_viewer(const char *name, size_t len)
@@ -198,9 +190,8 @@ static void do_add_man_viewer_info(const char *name,
size_t len,
const char *value)
{
- struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
-
- strncpy(new->name, name, len);
+ struct man_viewer_info_list *new;
+ FLEX_ALLOC_MEM(new, name, name, len);
new->info = xstrdup(value);
new->next = man_viewer_info_list;
man_viewer_info_list = new;
@@ -296,16 +287,6 @@ static int is_git_command(const char *s)
is_in_cmdlist(&other_cmds, s);
}
-static const char *prepend(const char *prefix, const char *cmd)
-{
- size_t pre_len = strlen(prefix);
- size_t cmd_len = strlen(cmd);
- char *p = xmalloc(pre_len + cmd_len + 1);
- memcpy(p, prefix, pre_len);
- strcpy(p + pre_len, cmd);
- return p;
-}
-
static const char *cmd_to_page(const char *git_cmd)
{
if (!git_cmd)
@@ -313,25 +294,27 @@ static const char *cmd_to_page(const char *git_cmd)
else if (starts_with(git_cmd, "git"))
return git_cmd;
else if (is_git_command(git_cmd))
- return prepend("git-", git_cmd);
+ return xstrfmt("git-%s", git_cmd);
else
- return prepend("git", git_cmd);
+ return xstrfmt("git%s", git_cmd);
}
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);
@@ -381,8 +364,10 @@ static void show_info_page(const char *git_cmd)
static void get_html_page_path(struct strbuf *page_path, const char *page)
{
struct stat st;
+ char *to_free = NULL;
+
if (!html_path)
- html_path = system_path(GIT_HTML_PATH);
+ html_path = to_free = system_path(GIT_HTML_PATH);
/* Check that we have a git documentation directory. */
if (!strstr(html_path, "://")) {
@@ -393,19 +378,13 @@ static void get_html_page_path(struct strbuf *page_path, const char *page)
strbuf_init(page_path, 0);
strbuf_addf(page_path, "%s/%s.html", html_path, page);
+ free(to_free);
}
-/*
- * If open_html is not defined in a platform-specific way (see for
- * example compat/mingw.h), we use the script web--browse to display
- * HTML.
- */
-#ifndef open_html
static void open_html(const char *path)
{
execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL);
}
-#endif
static void show_html_page(const char *git_cmd)
{
@@ -422,6 +401,7 @@ static struct {
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") },
@@ -448,10 +428,29 @@ static void list_common_guides_help(void)
putchar('\n');
}
+static const char *check_git_cmd(const char* cmd)
+{
+ char *alias;
+
+ if (is_git_command(cmd))
+ return cmd;
+
+ alias = alias_lookup(cmd);
+ if (alias) {
+ printf_ln(_("`git %s' is aliased to `%s'"), cmd, alias);
+ free(alias);
+ exit(0);
+ }
+
+ if (exclude_guides)
+ return help_unknown_cmd(cmd);
+
+ return cmd;
+}
+
int cmd_help(int argc, const char **argv, const char *prefix)
{
int nongit;
- const char *alias;
enum help_format parsed_help_format;
argc = parse_options(argc, argv, prefix, builtin_help_options,
@@ -491,11 +490,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)
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_ln(_("`git %s' is aliased to `%s'"), argv[0], alias);
- return 0;
- }
+ argv[0] = check_git_cmd(argv[0]);
switch (help_format) {
case HELP_FORMAT_NONE:
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 2f37a38fbc..f4b87c6c9f 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -18,16 +18,14 @@ static const char index_pack_usage[] =
struct object_entry {
struct pack_idx_entry idx;
unsigned long size;
- unsigned int hdr_size;
- enum object_type type;
- enum object_type real_type;
- unsigned delta_depth;
- int base_object_no;
+ unsigned char hdr_size;
+ signed char type;
+ signed char real_type;
};
-union delta_base {
- unsigned char sha1[20];
- off_t offset;
+struct object_stat {
+ unsigned delta_depth;
+ int base_object_no;
};
struct base_data {
@@ -40,45 +38,46 @@ struct base_data {
int ofs_first, ofs_last;
};
-#if !defined(NO_PTHREADS) && defined(NO_THREAD_SAFE_PREAD)
-/* pread() emulation is not thread-safe. Disable threading. */
-#define NO_PTHREADS
-#endif
-
struct thread_local {
#ifndef NO_PTHREADS
pthread_t thread;
#endif
struct base_data *base_cache;
size_t base_cache_used;
+ int pack_fd;
};
-/*
- * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
- * to memcmp() only the first 20 bytes.
- */
-#define UNION_BASE_SZ 20
-
#define FLAG_LINK (1u<<20)
#define FLAG_CHECKED (1u<<21)
-struct delta_entry {
- union delta_base base;
+struct ofs_delta_entry {
+ off_t offset;
+ int obj_no;
+};
+
+struct ref_delta_entry {
+ unsigned char sha1[20];
int obj_no;
};
static struct object_entry *objects;
-static struct delta_entry *deltas;
+static struct object_stat *obj_stat;
+static struct ofs_delta_entry *ofs_deltas;
+static struct ref_delta_entry *ref_deltas;
static struct thread_local nothread_data;
static int nr_objects;
-static int nr_deltas;
+static int nr_ofs_deltas;
+static int nr_ref_deltas;
+static int ref_deltas_alloc;
static int nr_resolved_deltas;
static int nr_threads;
static int from_stdin;
static int strict;
static int do_fsck_object;
+static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
static int verbose;
+static int show_resolving_progress;
static int show_stat;
static int check_self_contained_and_connected;
@@ -88,10 +87,12 @@ static struct progress *progress;
static unsigned char input_buffer[4096];
static unsigned int input_offset, input_len;
static off_t consumed_bytes;
+static off_t max_input_size;
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
@@ -115,6 +116,10 @@ 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)
@@ -134,26 +139,38 @@ static inline void unlock_mutex(pthread_mutex_t *mutex)
*/
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);
}
@@ -172,16 +189,19 @@ static void cleanup_thread(void)
#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)
+static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options)
{
if (!obj)
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"), oid_to_hex(&obj->oid));
obj->flags |= FLAG_LINK;
return 0;
@@ -199,9 +219,14 @@ static unsigned check_object(struct object *obj)
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"));
+ int type = sha1_object_info(obj->oid.hash, &size);
+ if (type <= 0)
+ die(_("did not receive expected object %s"),
+ oid_to_hex(&obj->oid));
+ if (type != obj->type)
+ die(_("object %s: expected type %s, found %s"),
+ oid_to_hex(&obj->oid),
+ typename(obj->type), typename(type));
obj->flags |= FLAG_CHECKED;
return 1;
}
@@ -273,6 +298,8 @@ static void use(int bytes)
if (signed_add_overflows(consumed_bytes, bytes))
die(_("pack too large for current definition of off_t"));
consumed_bytes += bytes;
+ if (max_input_size && consumed_bytes > max_input_size)
+ die(_("pack exceeds maximum allowed size"));
}
static const char *open_pack_file(const char *pack_name)
@@ -288,13 +315,13 @@ static const char *open_pack_file(const char *pack_name)
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;
+ 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);
output_fd = -1;
- pack_fd = input_fd;
+ nothread_data.pack_fd = input_fd;
}
git_SHA1_Init(&input_ctx);
return pack_name;
@@ -315,10 +342,10 @@ static void parse_pack_header(void)
use(sizeof(struct pack_header));
}
-static NORETURN void bad_object(unsigned long offset, const char *format,
+static NORETURN void bad_object(off_t offset, const char *format,
...) __attribute__((format (printf, 2, 3)));
-static NORETURN void bad_object(unsigned long offset, const char *format, ...)
+static NORETURN void bad_object(off_t offset, const char *format, ...)
{
va_list params;
char buf[1024];
@@ -326,7 +353,8 @@ 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 %"PRIuMAX": %s"),
+ (uintmax_t)offset, buf);
}
static inline struct thread_local *get_thread_data(void)
@@ -350,8 +378,7 @@ static void set_thread_data(struct thread_local *data)
static struct base_data *alloc_base_data(void)
{
- struct base_data *base = xmalloc(sizeof(struct base_data));
- memset(base, 0, sizeof(*base));
+ struct base_data *base = xcalloc(1, sizeof(struct base_data));
base->ref_last = -1;
base->ofs_last = -1;
return base;
@@ -407,7 +434,7 @@ 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,
+static void *unpack_entry_data(off_t offset, unsigned long size,
enum object_type type, unsigned char *sha1)
{
static char fixed_buf[8192];
@@ -419,7 +446,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size,
int hdrlen;
if (!is_delta_type(type)) {
- hdrlen = sprintf(hdr, "%s %lu", typename(type), size) + 1;
+ hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), size) + 1;
git_SHA1_Init(&c);
git_SHA1_Update(&c, hdr, hdrlen);
} else
@@ -427,7 +454,7 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size,
if (type == OBJ_BLOB && size > big_file_threshold)
buf = fixed_buf;
else
- buf = xmalloc(size);
+ buf = xmallocz(size);
memset(&stream, 0, sizeof(stream));
git_inflate_init(&stream);
@@ -456,7 +483,8 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size,
}
static void *unpack_raw_entry(struct object_entry *obj,
- union delta_base *delta_base,
+ off_t *ofs_offset,
+ unsigned char *ref_sha1,
unsigned char *sha1)
{
unsigned char *p;
@@ -485,11 +513,10 @@ static void *unpack_raw_entry(struct object_entry *obj,
switch (obj->type) {
case OBJ_REF_DELTA:
- hashcpy(delta_base->sha1, fill(20));
+ hashcpy(ref_sha1, fill(20));
use(20);
break;
case OBJ_OFS_DELTA:
- memset(delta_base, 0, sizeof(*delta_base));
p = fill(1);
c = *p;
use(1);
@@ -503,8 +530,8 @@ static void *unpack_raw_entry(struct object_entry *obj,
use(1);
base_offset = (base_offset << 7) + (c & 127);
}
- delta_base->offset = obj->idx.offset - base_offset;
- if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset)
+ *ofs_offset = obj->idx.offset - base_offset;
+ if (*ofs_offset <= 0 || *ofs_offset >= obj->idx.offset)
bad_object(obj->idx.offset, _("delta base offset is out of bound"));
break;
case OBJ_COMMIT:
@@ -527,13 +554,13 @@ static void *unpack_data(struct object_entry *obj,
void *cb_data)
{
off_t from = obj[0].idx.offset + obj[0].hdr_size;
- unsigned long len = obj[1].idx.offset - from;
+ off_t len = obj[1].idx.offset - from;
unsigned char *data, *inbuf;
git_zstream stream;
int status;
- data = xmalloc(consume ? 64*1024 : obj->size);
- inbuf = xmalloc((len < 64*1024) ? len : 64*1024);
+ data = xmallocz(consume ? 64*1024 : obj->size);
+ inbuf = xmalloc((len < 64*1024) ? (int)len : 64*1024);
memset(&stream, 0, sizeof(stream));
git_inflate_init(&stream);
@@ -541,15 +568,15 @@ static void *unpack_data(struct object_entry *obj,
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);
+ ssize_t n = (len < 64*1024) ? (ssize_t)len : 64*1024;
+ n = xpread(get_thread_data()->pack_fd, inbuf, n, from);
if (n < 0)
die_errno(_("cannot pread pack file"));
if (!n)
- die(Q_("premature end of pack file, %lu byte missing",
- "premature end of pack file, %lu bytes missing",
- len),
- len);
+ die(Q_("premature end of pack file, %"PRIuMAX" byte missing",
+ "premature end of pack file, %"PRIuMAX" bytes missing",
+ (unsigned int)len),
+ (uintmax_t)len);
from += n;
len -= n;
stream.next_in = inbuf;
@@ -588,55 +615,110 @@ static void *get_data_from_pack(struct object_entry *obj)
return unpack_data(obj, NULL, NULL);
}
-static int compare_delta_bases(const union delta_base *base1,
- const union delta_base *base2,
- enum object_type type1,
- enum object_type type2)
+static int compare_ofs_delta_bases(off_t offset1, off_t offset2,
+ enum object_type type1,
+ enum object_type type2)
{
int cmp = type1 - type2;
if (cmp)
return cmp;
- return memcmp(base1, base2, UNION_BASE_SZ);
+ return offset1 < offset2 ? -1 :
+ offset1 > offset2 ? 1 :
+ 0;
}
-static int find_delta(const union delta_base *base, enum object_type type)
+static int find_ofs_delta(const off_t offset, enum object_type type)
{
- int first = 0, last = nr_deltas;
-
- while (first < last) {
- int next = (first + last) / 2;
- struct delta_entry *delta = &deltas[next];
- int cmp;
-
- cmp = compare_delta_bases(base, &delta->base,
- type, objects[delta->obj_no].type);
- if (!cmp)
- return next;
- if (cmp < 0) {
- last = next;
- continue;
- }
- first = next+1;
- }
- return -first-1;
+ int first = 0, last = nr_ofs_deltas;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct ofs_delta_entry *delta = &ofs_deltas[next];
+ int cmp;
+
+ cmp = compare_ofs_delta_bases(offset, delta->offset,
+ type, objects[delta->obj_no].type);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
}
-static void find_delta_children(const union delta_base *base,
- int *first_index, int *last_index,
- enum object_type type)
+static void find_ofs_delta_children(off_t offset,
+ int *first_index, int *last_index,
+ enum object_type type)
{
- int first = find_delta(base, type);
+ int first = find_ofs_delta(offset, type);
int last = first;
- int end = nr_deltas - 1;
+ int end = nr_ofs_deltas - 1;
if (first < 0) {
*first_index = 0;
*last_index = -1;
return;
}
- while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ))
+ while (first > 0 && ofs_deltas[first - 1].offset == offset)
--first;
- while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ))
+ while (last < end && ofs_deltas[last + 1].offset == offset)
+ ++last;
+ *first_index = first;
+ *last_index = last;
+}
+
+static int compare_ref_delta_bases(const unsigned char *sha1,
+ const unsigned char *sha2,
+ enum object_type type1,
+ enum object_type type2)
+{
+ int cmp = type1 - type2;
+ if (cmp)
+ return cmp;
+ return hashcmp(sha1, sha2);
+}
+
+static int find_ref_delta(const unsigned char *sha1, enum object_type type)
+{
+ int first = 0, last = nr_ref_deltas;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct ref_delta_entry *delta = &ref_deltas[next];
+ int cmp;
+
+ cmp = compare_ref_delta_bases(sha1, delta->sha1,
+ type, objects[delta->obj_no].type);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
+}
+
+static void find_ref_delta_children(const unsigned char *sha1,
+ int *first_index, int *last_index,
+ enum object_type type)
+{
+ int first = find_ref_delta(sha1, type);
+ int last = first;
+ int end = nr_ref_deltas - 1;
+
+ if (first < 0) {
+ *first_index = 0;
+ *last_index = -1;
+ return;
+ }
+ while (first > 0 && !hashcmp(ref_deltas[first - 1].sha1, sha1))
+ --first;
+ while (last < end && !hashcmp(ref_deltas[last + 1].sha1, sha1))
++last;
*first_index = first;
*last_index = last;
@@ -705,13 +787,15 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
const unsigned char *sha1)
{
void *new_data = NULL;
- int collision_test_needed;
+ int collision_test_needed = 0;
assert(data || obj_entry);
- read_lock();
- collision_test_needed = has_sha1_file(sha1);
- read_unlock();
+ if (startup_info->have_repository) {
+ read_lock();
+ collision_test_needed = has_sha1_file_with_flags(sha1, HAS_SHA1_QUICK);
+ read_unlock();
+ }
if (collision_test_needed && !data) {
read_lock();
@@ -762,10 +846,10 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
if (!obj)
die(_("invalid %s"), typename(type));
if (do_fsck_object &&
- fsck_object(obj, 1, fsck_error_function))
+ fsck_object(obj, buf, size, &fsck_options))
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));
+ if (fsck_walk(obj, NULL, &fsck_options))
+ die(_("Not all child objects of %s are reachable"), oid_to_hex(&obj->oid));
if (obj->type == OBJ_TREE) {
struct tree *item = (struct tree *) obj;
@@ -774,7 +858,8 @@ static void sha1_object(const void *data, struct object_entry *obj_entry,
}
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;
}
@@ -850,15 +935,16 @@ static void resolve_delta(struct object_entry *delta_obj,
{
void *base_data, *delta_data;
- delta_obj->real_type = base->obj->real_type;
if (show_stat) {
- delta_obj->delta_depth = base->obj->delta_depth + 1;
+ int i = delta_obj - objects;
+ int j = base->obj - objects;
+ obj_stat[i].delta_depth = obj_stat[j].delta_depth + 1;
deepest_delta_lock();
- if (deepest_delta < delta_obj->delta_depth)
- deepest_delta = delta_obj->delta_depth;
+ if (deepest_delta < obj_stat[i].delta_depth)
+ deepest_delta = obj_stat[i].delta_depth;
deepest_delta_unlock();
+ obj_stat[i].base_object_no = j;
}
- delta_obj->base_object_no = base->obj - objects;
delta_data = get_data_from_pack(delta_obj);
base_data = get_base_data(base);
result->obj = delta_obj;
@@ -876,20 +962,37 @@ static void resolve_delta(struct object_entry *delta_obj,
counter_unlock();
}
+/*
+ * Standard boolean compare-and-swap: atomically check whether "*type" is
+ * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched
+ * and return false.
+ */
+static int compare_and_swap_type(signed char *type,
+ enum object_type want,
+ enum object_type set)
+{
+ enum object_type old;
+
+ type_cas_lock();
+ old = *type;
+ if (old == want)
+ *type = set;
+ type_cas_unlock();
+
+ return old == want;
+}
+
static struct base_data *find_unresolved_deltas_1(struct base_data *base,
struct base_data *prev_base)
{
if (base->ref_last == -1 && base->ofs_last == -1) {
- union delta_base base_spec;
-
- hashcpy(base_spec.sha1, base->obj->idx.sha1);
- find_delta_children(&base_spec,
- &base->ref_first, &base->ref_last, OBJ_REF_DELTA);
+ find_ref_delta_children(base->obj->idx.sha1,
+ &base->ref_first, &base->ref_last,
+ OBJ_REF_DELTA);
- memset(&base_spec, 0, sizeof(base_spec));
- base_spec.offset = base->obj->idx.offset;
- find_delta_children(&base_spec,
- &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA);
+ find_ofs_delta_children(base->obj->idx.offset,
+ &base->ofs_first, &base->ofs_last,
+ OBJ_OFS_DELTA);
if (base->ref_last == -1 && base->ofs_last == -1) {
free(base->data);
@@ -900,10 +1003,13 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
}
if (base->ref_first <= base->ref_last) {
- struct object_entry *child = objects + deltas[base->ref_first].obj_no;
+ struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no;
struct base_data *result = alloc_base_data();
- assert(child->real_type == OBJ_REF_DELTA);
+ if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
+ base->obj->real_type))
+ die("BUG: child->real_type != OBJ_REF_DELTA");
+
resolve_delta(child, base, result);
if (base->ref_first == base->ref_last && base->ofs_last == -1)
free_base_data(base);
@@ -913,10 +1019,11 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
}
if (base->ofs_first <= base->ofs_last) {
- struct object_entry *child = objects + deltas[base->ofs_first].obj_no;
+ struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no;
struct base_data *result = alloc_base_data();
assert(child->real_type == OBJ_OFS_DELTA);
+ child->real_type = base->obj->real_type;
resolve_delta(child, base, result);
if (base->ofs_first == base->ofs_last)
free_base_data(base);
@@ -948,15 +1055,22 @@ static void find_unresolved_deltas(struct base_data *base)
}
}
-static int compare_delta_entry(const void *a, const void *b)
+static int compare_ofs_delta_entry(const void *a, const void *b)
+{
+ const struct ofs_delta_entry *delta_a = a;
+ const struct ofs_delta_entry *delta_b = b;
+
+ return delta_a->offset < delta_b->offset ? -1 :
+ delta_a->offset > delta_b->offset ? 1 :
+ 0;
+}
+
+static int compare_ref_delta_entry(const void *a, const void *b)
{
- const struct delta_entry *delta_a = a;
- const struct delta_entry *delta_b = b;
+ const struct ref_delta_entry *delta_a = a;
+ const struct ref_delta_entry *delta_b = b;
- /* group by type (ref vs ofs) and then by value (sha-1 or offset) */
- return compare_delta_bases(&delta_a->base, &delta_b->base,
- objects[delta_a->obj_no].type,
- objects[delta_b->obj_no].type);
+ return hashcmp(delta_a->sha1, delta_b->sha1);
}
static void resolve_base(struct object_entry *obj)
@@ -1002,7 +1116,8 @@ static void *threaded_second_pass(void *data)
static void parse_pack_objects(unsigned char *sha1)
{
int i, nr_delays = 0;
- struct delta_entry *delta = deltas;
+ struct ofs_delta_entry *ofs_delta = ofs_deltas;
+ unsigned char ref_delta_sha1[20];
struct stat st;
if (verbose)
@@ -1011,12 +1126,18 @@ static void parse_pack_objects(unsigned char *sha1)
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1);
+ void *data = unpack_raw_entry(obj, &ofs_delta->offset,
+ ref_delta_sha1, obj->idx.sha1);
obj->real_type = obj->type;
- if (is_delta_type(obj->type)) {
- nr_deltas++;
- delta->obj_no = i;
- delta++;
+ if (obj->type == OBJ_OFS_DELTA) {
+ nr_ofs_deltas++;
+ ofs_delta->obj_no = i;
+ ofs_delta++;
+ } else if (obj->type == OBJ_REF_DELTA) {
+ ALLOC_GROW(ref_deltas, nr_ref_deltas + 1, ref_deltas_alloc);
+ hashcpy(ref_deltas[nr_ref_deltas].sha1, ref_delta_sha1);
+ ref_deltas[nr_ref_deltas].obj_no = i;
+ nr_ref_deltas++;
} else if (!data) {
/* large blobs, check later */
obj->real_type = OBJ_BAD;
@@ -1067,15 +1188,16 @@ static void resolve_deltas(void)
{
int i;
- if (!nr_deltas)
+ if (!nr_ofs_deltas && !nr_ref_deltas)
return;
/* Sort deltas by base SHA1/offset for fast searching */
- qsort(deltas, nr_deltas, sizeof(struct delta_entry),
- compare_delta_entry);
+ QSORT(ofs_deltas, nr_ofs_deltas, compare_ofs_delta_entry);
+ QSORT(ref_deltas, nr_ref_deltas, compare_ref_delta_entry);
- if (verbose)
- progress = start_progress(_("Resolving deltas"), nr_deltas);
+ if (verbose || show_resolving_progress)
+ progress = start_progress(_("Resolving deltas"),
+ nr_ref_deltas + nr_ofs_deltas);
#ifndef NO_PTHREADS
nr_dispatched = 0;
@@ -1110,10 +1232,10 @@ static void resolve_deltas(void)
* - 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 fix_unresolved_deltas(struct sha1file *f);
static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1)
{
- if (nr_deltas == nr_resolved_deltas) {
+ if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) {
stop_progress(&progress);
/* Flush remaining pack final 20-byte SHA1. */
flush();
@@ -1124,18 +1246,18 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
struct sha1file *f;
unsigned char read_sha1[20], tail_sha1[20];
struct strbuf msg = STRBUF_INIT;
- int nr_unresolved = nr_deltas - nr_resolved_deltas;
+ int nr_unresolved = nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas;
int nr_objects_initial = nr_objects;
if (nr_unresolved <= 0)
die(_("confusion beyond insanity"));
- objects = xrealloc(objects,
- (nr_objects + nr_unresolved + 1)
- * sizeof(*objects));
+ 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"),
+ fix_unresolved_deltas(f);
+ strbuf_addf(&msg, Q_("completed with %d local object",
+ "completed with %d local objects",
+ nr_objects - nr_objects_initial),
nr_objects - nr_objects_initial);
stop_progress_msg(&progress, msg.buf);
strbuf_release(&msg);
@@ -1148,11 +1270,11 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha
die(_("Unexpected tail checksum for %s "
"(disk corruption?)"), curr_pack);
}
- if (nr_deltas != nr_resolved_deltas)
+ if (nr_ofs_deltas + nr_ref_deltas != nr_resolved_deltas)
die(Q_("pack has %d unresolved delta",
"pack has %d unresolved deltas",
- nr_deltas - nr_resolved_deltas),
- nr_deltas - nr_resolved_deltas);
+ nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas),
+ nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas);
}
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
@@ -1161,7 +1283,6 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size)
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;
@@ -1212,15 +1333,15 @@ static struct object_entry *append_obj_to_pack(struct sha1file *f,
static int delta_pos_compare(const void *_a, const void *_b)
{
- struct delta_entry *a = *(struct delta_entry **)_a;
- struct delta_entry *b = *(struct delta_entry **)_b;
+ struct ref_delta_entry *a = *(struct ref_delta_entry **)_a;
+ struct ref_delta_entry *b = *(struct ref_delta_entry **)_b;
return a->obj_no - b->obj_no;
}
-static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
+static void fix_unresolved_deltas(struct sha1file *f)
{
- struct delta_entry **sorted_by_pos;
- int i, n = 0;
+ struct ref_delta_entry **sorted_by_pos;
+ int i;
/*
* Since many unresolved deltas may well be themselves base objects
@@ -1232,29 +1353,26 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
* before deltas depending on them, a good heuristic is to start
* resolving deltas in the same order as their position in the pack.
*/
- sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos));
- for (i = 0; i < nr_deltas; i++) {
- if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA)
- continue;
- sorted_by_pos[n++] = &deltas[i];
- }
- qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare);
+ ALLOC_ARRAY(sorted_by_pos, nr_ref_deltas);
+ for (i = 0; i < nr_ref_deltas; i++)
+ sorted_by_pos[i] = &ref_deltas[i];
+ QSORT(sorted_by_pos, nr_ref_deltas, delta_pos_compare);
- for (i = 0; i < n; i++) {
- struct delta_entry *d = sorted_by_pos[i];
+ for (i = 0; i < nr_ref_deltas; i++) {
+ struct ref_delta_entry *d = sorted_by_pos[i];
enum object_type type;
struct base_data *base_obj = alloc_base_data();
if (objects[d->obj_no].real_type != OBJ_REF_DELTA)
continue;
- base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size);
+ base_obj->data = read_sha1_file(d->sha1, &type, &base_obj->size);
if (!base_obj->data)
continue;
- if (check_sha1_signature(d->base.sha1, base_obj->data,
+ if (check_sha1_signature(d->sha1, base_obj->data,
base_obj->size, typename(type)))
- die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1));
- base_obj->obj = append_obj_to_pack(f, d->base.sha1,
+ die(_("local object %s is corrupt"), sha1_to_hex(d->sha1));
+ base_obj->obj = append_obj_to_pack(f, d->sha1,
base_obj->data, base_obj->size, type);
find_unresolved_deltas(base_obj);
display_progress(progress, nr_resolved_deltas);
@@ -1291,7 +1409,7 @@ 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);
+ keep_name ? keep_name : name);
} else {
if (keep_msg_len > 0) {
write_or_die(keep_fd, keep_msg, keep_msg_len);
@@ -1299,7 +1417,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
}
if (close(keep_fd) != 0)
die_errno(_("cannot close written keep file '%s'"),
- keep_name);
+ keep_name ? keep_name : name);
report = "keep";
}
}
@@ -1310,7 +1428,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
get_object_directory(), sha1_to_hex(sha1));
final_pack_name = name;
}
- if (move_temp_to_file(curr_pack_name, final_pack_name))
+ if (finalize_object_file(curr_pack_name, final_pack_name))
die(_("cannot store pack file"));
} else if (from_stdin)
chmod(final_pack_name, 0444);
@@ -1321,7 +1439,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
get_object_directory(), sha1_to_hex(sha1));
final_index_name = name;
}
- if (move_temp_to_file(curr_index_name, final_index_name))
+ if (finalize_object_file(curr_index_name, final_index_name))
die(_("cannot store index file"));
} else
chmod(final_index_name, 0444);
@@ -1403,6 +1521,7 @@ static void read_v2_anomalous_offsets(struct packed_git *p,
if (!(off & 0x80000000))
continue;
off = off & 0x7fffffff;
+ check_pack_index_ptr(p, &idx2[off * 2]);
if (idx2[off * 2])
continue;
/*
@@ -1414,8 +1533,7 @@ static void read_v2_anomalous_offsets(struct packed_git *p,
opts->anomaly[opts->anomaly_nr++] = ntohl(idx2[off * 2 + 1]);
}
- if (1 < opts->anomaly_nr)
- qsort(opts->anomaly, opts->anomaly_nr, sizeof(uint32_t), cmp_uint32);
+ QSORT(opts->anomaly, opts->anomaly_nr, cmp_uint32);
}
static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
@@ -1446,7 +1564,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
static void show_pack_info(int stat_only)
{
- int i, baseobjects = nr_objects - nr_deltas;
+ int i, baseobjects = nr_objects - nr_ref_deltas - nr_ofs_deltas;
unsigned long *chain_histogram = NULL;
if (deepest_delta)
@@ -1456,7 +1574,7 @@ static void show_pack_info(int stat_only)
struct object_entry *obj = &objects[i];
if (is_delta_type(obj->type))
- chain_histogram[obj->delta_depth - 1]++;
+ chain_histogram[obj_stat[i].delta_depth - 1]++;
if (stat_only)
continue;
printf("%s %-6s %lu %lu %"PRIuMAX,
@@ -1465,8 +1583,8 @@ static void show_pack_info(int stat_only)
(unsigned long)(obj[1].idx.offset - obj->idx.offset),
(uintmax_t)obj->idx.offset);
if (is_delta_type(obj->type)) {
- struct object_entry *bobj = &objects[obj->base_object_no];
- printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1));
+ struct object_entry *bobj = &objects[obj_stat[i].base_object_no];
+ printf(" %u %s", obj_stat[i].delta_depth, sha1_to_hex(bobj->idx.sha1));
}
putchar('\n');
}
@@ -1487,22 +1605,37 @@ static void show_pack_info(int stat_only)
}
}
+static const char *derive_filename(const char *pack_name, const char *suffix,
+ struct strbuf *buf)
+{
+ size_t len;
+ if (!strip_suffix(pack_name, ".pack", &len))
+ die(_("packfile name '%s' does not end with '.pack'"),
+ pack_name);
+ strbuf_add(buf, pack_name, len);
+ strbuf_addstr(buf, suffix);
+ return buf->buf;
+}
+
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
- const char *curr_pack, *curr_index;
+ 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 */
+ int report_end_of_input = 0;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(index_pack_usage);
- read_replace_refs = 0;
+ check_replace_refs = 0;
+ fsck_options.walk = mark_link;
reset_pack_idx_option(&opts);
git_config(git_index_pack_config, &opts);
@@ -1520,6 +1653,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
} else if (!strcmp(arg, "--strict")) {
strict = 1;
do_fsck_object = 1;
+ } else if (skip_prefix(arg, "--strict=", &arg)) {
+ strict = 1;
+ do_fsck_object = 1;
+ fsck_set_msg_types(&fsck_options, arg);
} else if (!strcmp(arg, "--check-self-contained-and-connected")) {
strict = 1;
check_self_contained_and_connected = 1;
@@ -1562,6 +1699,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
input_len = sizeof(*hdr);
} else if (!strcmp(arg, "-v")) {
verbose = 1;
+ } else if (!strcmp(arg, "--show-resolving-progress")) {
+ show_resolving_progress = 1;
+ } else if (!strcmp(arg, "--report-end-of-input")) {
+ report_end_of_input = 1;
} else if (!strcmp(arg, "-o")) {
if (index_name || (i+1) >= argc)
usage(index_pack_usage);
@@ -1575,6 +1716,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
opts.off32_limit = strtoul(c+1, &c, 0);
if (*c || opts.off32_limit & 0x80000000)
die(_("bad %s"), arg);
+ } else if (skip_prefix(arg, "--max-input-size=", &arg)) {
+ max_input_size = strtoumax(arg, NULL, 10);
} else
usage(index_pack_usage);
continue;
@@ -1589,26 +1732,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
usage(index_pack_usage);
if (fix_thin_pack && !from_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'"),
- 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;
- }
- 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'"),
- 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;
- }
+ if (from_stdin && !startup_info->have_repository)
+ die(_("--stdin requires a git repository"));
+ if (!index_name && pack_name)
+ index_name = derive_filename(pack_name, ".idx", &index_name_buf);
+ if (keep_msg && !keep_name && pack_name)
+ keep_name = derive_filename(pack_name, ".keep", &keep_name_buf);
+
if (verify) {
if (!index_name)
die(_("--verify with no packfile name given"));
@@ -1629,19 +1759,24 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
curr_pack = open_pack_file(pack_name);
parse_pack_header();
- objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
- deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
+ objects = xcalloc(st_add(nr_objects, 1), sizeof(struct object_entry));
+ if (show_stat)
+ obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct object_stat));
+ ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry));
parse_pack_objects(pack_sha1);
+ if (report_end_of_input)
+ write_in_full(2, "\0", 1);
resolve_deltas();
conclude_pack(fix_thin_pack, curr_pack, pack_sha1);
- free(deltas);
+ free(ofs_deltas);
+ free(ref_deltas);
if (strict)
foreign_nr = check_objects();
if (show_stat)
show_pack_info(stat_only);
- idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
+ ALLOC_ARRAY(idx_objects, nr_objects);
for (i = 0; i < nr_objects; i++)
idx_objects[i] = &objects[i].idx;
curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1);
@@ -1655,8 +1790,8 @@ 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)
diff --git a/builtin/init-db.c b/builtin/init-db.c
index c7c76bbf21..8a6acb0ec6 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -4,6 +4,7 @@
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
+#include "refs.h"
#include "builtin.h"
#include "exec_cmd.h"
#include "parse-options.h"
@@ -21,24 +22,12 @@
static int init_is_bare_repository = 0;
static int init_shared_repository = -1;
static const char *init_db_template_dir;
-static const char *git_link;
-static void safe_create_dir(const char *dir, int share)
-{
- if (mkdir(dir, 0777) < 0) {
- if (errno != EEXIST) {
- perror(dir);
- exit(1);
- }
- }
- else if (share && adjust_shared_perm(dir))
- die(_("Could not make %s writable by group"), dir);
-}
-
-static void copy_templates_1(char *path, int baselen,
- char *template, int template_baselen,
+static void copy_templates_1(struct strbuf *path, struct strbuf *template,
DIR *dir)
{
+ size_t path_baselen = path->len;
+ size_t template_baselen = template->len;
struct dirent *de;
/* Note: if ".git/hooks" file exists in the repository being
@@ -48,125 +37,115 @@ static void copy_templates_1(char *path, int baselen,
* with the way the namespace under .git/ is organized, should
* be really carefully chosen.
*/
- safe_create_dir(path, 1);
+ safe_create_dir(path->buf, 1);
while ((de = readdir(dir)) != NULL) {
struct stat st_git, st_template;
- int namelen;
int exists = 0;
+ strbuf_setlen(path, path_baselen);
+ strbuf_setlen(template, template_baselen);
+
if (de->d_name[0] == '.')
continue;
- namelen = strlen(de->d_name);
- if ((PATH_MAX <= baselen + namelen) ||
- (PATH_MAX <= template_baselen + namelen))
- die(_("insanely long template name %s"), de->d_name);
- memcpy(path + baselen, de->d_name, namelen+1);
- memcpy(template + template_baselen, de->d_name, namelen+1);
- if (lstat(path, &st_git)) {
+ strbuf_addstr(path, de->d_name);
+ strbuf_addstr(template, de->d_name);
+ if (lstat(path->buf, &st_git)) {
if (errno != ENOENT)
- die_errno(_("cannot stat '%s'"), path);
+ die_errno(_("cannot stat '%s'"), path->buf);
}
else
exists = 1;
- if (lstat(template, &st_template))
- die_errno(_("cannot stat template '%s'"), template);
+ if (lstat(template->buf, &st_template))
+ die_errno(_("cannot stat template '%s'"), template->buf);
if (S_ISDIR(st_template.st_mode)) {
- DIR *subdir = opendir(template);
- int baselen_sub = baselen + namelen;
- int template_baselen_sub = template_baselen + namelen;
+ DIR *subdir = opendir(template->buf);
if (!subdir)
- die_errno(_("cannot opendir '%s'"), template);
- path[baselen_sub++] =
- template[template_baselen_sub++] = '/';
- path[baselen_sub] =
- template[template_baselen_sub] = 0;
- copy_templates_1(path, baselen_sub,
- template, template_baselen_sub,
- subdir);
+ die_errno(_("cannot opendir '%s'"), template->buf);
+ strbuf_addch(path, '/');
+ strbuf_addch(template, '/');
+ copy_templates_1(path, template, subdir);
closedir(subdir);
}
else if (exists)
continue;
else if (S_ISLNK(st_template.st_mode)) {
- char lnk[256];
- int len;
- len = readlink(template, lnk, sizeof(lnk));
- if (len < 0)
- die_errno(_("cannot readlink '%s'"), template);
- if (sizeof(lnk) <= len)
- die(_("insanely long symlink %s"), template);
- lnk[len] = 0;
- if (symlink(lnk, path))
- die_errno(_("cannot symlink '%s' '%s'"), lnk, path);
+ struct strbuf lnk = STRBUF_INIT;
+ if (strbuf_readlink(&lnk, template->buf, 0) < 0)
+ die_errno(_("cannot readlink '%s'"), template->buf);
+ if (symlink(lnk.buf, path->buf))
+ die_errno(_("cannot symlink '%s' '%s'"),
+ lnk.buf, path->buf);
+ strbuf_release(&lnk);
}
else if (S_ISREG(st_template.st_mode)) {
- if (copy_file(path, template, st_template.st_mode))
- die_errno(_("cannot copy '%s' to '%s'"), template,
- path);
+ if (copy_file(path->buf, template->buf, st_template.st_mode))
+ die_errno(_("cannot copy '%s' to '%s'"),
+ template->buf, path->buf);
}
else
- error(_("ignoring template %s"), template);
+ error(_("ignoring template %s"), template->buf);
}
}
static void copy_templates(const char *template_dir)
{
- char path[PATH_MAX];
- char template_path[PATH_MAX];
- int template_len;
+ struct strbuf path = STRBUF_INIT;
+ struct strbuf template_path = STRBUF_INIT;
+ size_t template_len;
+ struct repository_format template_format;
+ struct strbuf err = STRBUF_INIT;
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);
- strcpy(template_path, template_dir);
- if (template_path[template_len-1] != '/') {
- template_path[template_len++] = '/';
- template_path[template_len] = 0;
}
- dir = opendir(template_path);
+
+ strbuf_addstr(&template_path, template_dir);
+ strbuf_complete(&template_path, '/');
+ template_len = template_path.len;
+
+ dir = opendir(template_path.buf);
if (!dir) {
warning(_("templates not found %s"), template_dir);
- return;
+ goto free_return;
}
/* Make sure that template is from the correct vintage */
- strcpy(template_path + template_len, "config");
- repository_format_version = 0;
- git_config_from_file(check_repository_format_version,
- template_path, NULL);
- template_path[template_len] = 0;
-
- if (repository_format_version &&
- repository_format_version != GIT_REPO_VERSION) {
- warning(_("not copying templates of "
- "a wrong format version %d from '%s'"),
- repository_format_version,
- template_dir);
- closedir(dir);
- return;
+ strbuf_addstr(&template_path, "config");
+ read_repository_format(&template_format, template_path.buf);
+ strbuf_setlen(&template_path, template_len);
+
+ /*
+ * No mention of version at all is OK, but anything else should be
+ * verified.
+ */
+ if (template_format.version >= 0 &&
+ verify_repository_format(&template_format, &err) < 0) {
+ warning(_("not copying templates from '%s': %s"),
+ template_dir, err.buf);
+ strbuf_release(&err);
+ goto close_free_return;
}
- memcpy(path, git_dir, len);
- if (len && path[len - 1] != '/')
- path[len++] = '/';
- path[len] = 0;
- copy_templates_1(path, len,
- template_path, template_len,
- dir);
+ strbuf_addstr(&path, get_git_common_dir());
+ strbuf_complete(&path, '/');
+ copy_templates_1(&path, &template_path, dir);
+close_free_return:
closedir(dir);
+free_return:
+ free(to_free);
+ strbuf_release(&path);
+ strbuf_release(&template_path);
}
static int git_init_db_config(const char *k, const char *v, void *cb)
@@ -177,63 +156,80 @@ static int git_init_db_config(const char *k, const char *v, void *cb)
return 0;
}
-static int create_default_files(const char *template_path)
+/*
+ * If the git_dir is not directly inside the working tree, then git will not
+ * find it by default, and we need to set the worktree explicitly.
+ */
+static int needs_work_tree_config(const char *git_dir, const char *work_tree)
+{
+ if (!strcmp(work_tree, "/") && !strcmp(git_dir, "/.git"))
+ return 0;
+ if (skip_prefix(git_dir, work_tree, &git_dir) &&
+ !strcmp(git_dir, "/.git"))
+ return 0;
+ return 1;
+}
+
+static int create_default_files(const char *template_path,
+ const char *original_git_dir)
{
- const char *git_dir = get_git_dir();
- unsigned len = strlen(git_dir);
- static char path[PATH_MAX];
struct stat st1;
+ struct strbuf buf = STRBUF_INIT;
+ char *path;
char repo_version_string[10];
char junk[2];
int reinit;
int filemode;
-
- if (len > sizeof(path)-50)
- die(_("insane git directory %s"), git_dir);
- memcpy(path, git_dir, len);
-
- if (len && path[len-1] != '/')
- path[len++] = '/';
-
- /*
- * Create .git/refs/{heads,tags}
- */
- safe_create_dir(git_path("refs"), 1);
- safe_create_dir(git_path("refs/heads"), 1);
- safe_create_dir(git_path("refs/tags"), 1);
+ struct strbuf err = STRBUF_INIT;
/* Just look for `init.templatedir` */
git_config(git_init_db_config, NULL);
- /* First copy the templates -- we might have the default
+ /*
+ * First copy the templates -- we might have the default
* config file there, in which case we would want to read
* from it after installing.
+ *
+ * Before reading that config, we also need to clear out any cached
+ * values (since we've just potentially changed what's available on
+ * disk).
*/
copy_templates(template_path);
-
+ git_config_clear();
+ reset_shared_repository();
git_config(git_default_config, NULL);
- is_bare_repository_cfg = init_is_bare_repository;
- /* reading existing config may have overwrote it */
+ /*
+ * We must make sure command-line options continue to override any
+ * values we might have just re-read from the config.
+ */
+ is_bare_repository_cfg = init_is_bare_repository;
if (init_shared_repository != -1)
- shared_repository = init_shared_repository;
+ set_shared_repository(init_shared_repository);
/*
* We would have created the above under user's umask -- under
* shared-repository settings, we would need to fix them up.
*/
- if (shared_repository) {
+ if (get_shared_repository()) {
adjust_shared_perm(get_git_dir());
- adjust_shared_perm(git_path("refs"));
- adjust_shared_perm(git_path("refs/heads"));
- adjust_shared_perm(git_path("refs/tags"));
}
/*
+ * We need to create a "refs" dir in any case so that older
+ * versions of git can tell that this is a repository.
+ */
+ safe_create_dir(git_path("refs"), 1);
+ adjust_shared_perm(git_path("refs"));
+
+ if (refs_init_db(&err))
+ die("failed to set up refs db: %s", err.buf);
+
+ /*
* Create the default symlink from ".git/HEAD" to the "master"
* branch, if it does not exist yet.
*/
- strcpy(path + len, "HEAD");
+ path = git_path_buf(&buf, "HEAD");
reinit = (!access(path, R_OK)
|| readlink(path, junk, sizeof(junk)-1) != -1);
if (!reinit) {
@@ -242,19 +238,21 @@ static int create_default_files(const char *template_path)
}
/* This forces creation of new config file */
- sprintf(repo_version_string, "%d", GIT_REPO_VERSION);
+ xsnprintf(repo_version_string, sizeof(repo_version_string),
+ "%d", GIT_REPO_VERSION);
git_config_set("core.repositoryformatversion", repo_version_string);
- path[len] = 0;
- strcpy(path + len, "config");
-
/* Check filemode trustability */
+ path = git_path_buf(&buf, "config");
filemode = TEST_FILEMODE;
if (TEST_FILEMODE && !lstat(path, &st1)) {
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");
@@ -264,18 +262,15 @@ static int create_default_files(const char *template_path)
const char *work_tree = get_git_work_tree();
git_config_set("core.bare", "false");
/* allow template config file to override the default */
- if (log_all_ref_updates == -1)
- git_config_set("core.logallrefupdates", "true");
- if (!starts_with(git_dir, work_tree) ||
- strcmp(git_dir + strlen(work_tree), "/.git")) {
+ if (log_all_ref_updates == LOG_REFS_UNSET)
+ git_config_set("core.logallrefupdates", "true");
+ if (needs_work_tree_config(original_git_dir, work_tree))
git_config_set("core.worktree", work_tree);
- }
}
if (!reinit) {
/* Check if symlink is supported in the work tree */
- path[len] = 0;
- strcpy(path + len, "tXXXXXX");
+ path = git_path_buf(&buf, "tXXXXXX");
if (!close(xmkstemp(path)) &&
!unlink(path) &&
!symlink("testing", path) &&
@@ -286,63 +281,40 @@ static int create_default_files(const char *template_path)
git_config_set("core.symlinks", "false");
/* Check if the filesystem is case-insensitive */
- path[len] = 0;
- strcpy(path + len, "CoNfIg");
+ path = git_path_buf(&buf, "CoNfIg");
if (!access(path, F_OK))
git_config_set("core.ignorecase", "true");
- probe_utf8_pathname_composition(path, len);
+ probe_utf8_pathname_composition();
}
+ strbuf_release(&buf);
return reinit;
}
static void create_object_directory(void)
{
- const char *object_directory = get_object_directory();
- int len = strlen(object_directory);
- char *path = xmalloc(len + 40);
-
- memcpy(path, object_directory, len);
+ struct strbuf path = STRBUF_INIT;
+ size_t baselen;
- safe_create_dir(object_directory, 1);
- strcpy(path+len, "/pack");
- safe_create_dir(path, 1);
- strcpy(path+len, "/info");
- safe_create_dir(path, 1);
+ strbuf_addstr(&path, get_object_directory());
+ baselen = path.len;
- free(path);
-}
-
-int set_git_dir_init(const char *git_dir, const char *real_git_dir,
- int exist_ok)
-{
- if (real_git_dir) {
- struct stat st;
+ safe_create_dir(path.buf, 1);
- if (!exist_ok && !stat(git_dir, &st))
- die(_("%s already exists"), git_dir);
+ strbuf_setlen(&path, baselen);
+ strbuf_addstr(&path, "/pack");
+ safe_create_dir(path.buf, 1);
- if (!exist_ok && !stat(real_git_dir, &st))
- die(_("%s already exists"), real_git_dir);
+ strbuf_setlen(&path, baselen);
+ strbuf_addstr(&path, "/info");
+ safe_create_dir(path.buf, 1);
- /*
- * make sure symlinks are resolved because we'll be
- * moving the target repo later on in separate_git_dir()
- */
- git_link = xstrdup(real_path(git_dir));
- }
- else {
- real_git_dir = real_path(git_dir);
- git_link = NULL;
- }
- set_git_dir(real_path(real_git_dir));
- return 0;
+ strbuf_release(&path);
}
-static void separate_git_dir(const char *git_dir)
+static void separate_git_dir(const char *git_dir, const char *git_link)
{
struct stat st;
- FILE *fp;
if (!stat(git_link, &st)) {
const char *src;
@@ -358,20 +330,34 @@ static void separate_git_dir(const char *git_dir)
die_errno(_("unable to move %s to %s"), src, git_dir);
}
- fp = fopen(git_link, "w");
- if (!fp)
- die(_("Could not create git link %s"), git_link);
- fprintf(fp, "gitdir: %s\n", git_dir);
- fclose(fp);
+ write_file(git_link, "gitdir: %s", git_dir);
}
-int init_db(const char *template_dir, unsigned int flags)
+int init_db(const char *git_dir, const char *real_git_dir,
+ const char *template_dir, unsigned int flags)
{
int reinit;
- const char *git_dir = get_git_dir();
+ int exist_ok = flags & INIT_DB_EXIST_OK;
+ char *original_git_dir = real_pathdup(git_dir, 1);
+
+ if (real_git_dir) {
+ struct stat st;
+
+ if (!exist_ok && !stat(git_dir, &st))
+ die(_("%s already exists"), git_dir);
+
+ if (!exist_ok && !stat(real_git_dir, &st))
+ die(_("%s already exists"), real_git_dir);
- if (git_link)
- separate_git_dir(git_dir);
+ set_git_dir(real_path(real_git_dir));
+ git_dir = get_git_dir();
+ separate_git_dir(git_dir, original_git_dir);
+ }
+ else {
+ set_git_dir(real_path(git_dir));
+ git_dir = get_git_dir();
+ }
+ startup_info->have_repository = 1;
safe_create_dir(git_dir, 0);
@@ -384,11 +370,11 @@ int init_db(const char *template_dir, unsigned int flags)
*/
check_repository_format();
- reinit = create_default_files(template_dir);
+ reinit = create_default_files(template_dir, original_git_dir);
create_object_directory();
- if (shared_repository) {
+ if (get_shared_repository()) {
char buf[10];
/* We do not spell "group" and such, so that
* the configuration can be read by older version
@@ -396,15 +382,15 @@ int init_db(const char *template_dir, unsigned int flags)
* and compatibility values for PERM_GROUP and
* PERM_EVERYBODY.
*/
- if (shared_repository < 0)
+ if (get_shared_repository() < 0)
/* force to the mode value */
- sprintf(buf, "0%o", -shared_repository);
- else if (shared_repository == PERM_GROUP)
- sprintf(buf, "%d", OLD_PERM_GROUP);
- else if (shared_repository == PERM_EVERYBODY)
- sprintf(buf, "%d", OLD_PERM_EVERYBODY);
+ xsnprintf(buf, sizeof(buf), "0%o", -get_shared_repository());
+ else if (get_shared_repository() == PERM_GROUP)
+ xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP);
+ else if (get_shared_repository() == PERM_EVERYBODY)
+ xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY);
else
- die("oops");
+ die("BUG: invalid value for shared_repository");
git_config_set("core.sharedrepository", buf);
git_config_set("receive.denyNonFastforwards", "true");
}
@@ -412,24 +398,27 @@ 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.
- */
- printf(_("%s%s Git repository in %s%s\n"),
- reinit ? _("Reinitialized existing") : _("Initialized empty"),
- shared_repository ? _(" shared") : "",
- git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ if (reinit)
+ printf(get_shared_repository()
+ ? _("Reinitialized existing shared Git repository in %s%s\n")
+ : _("Reinitialized existing Git repository in %s%s\n"),
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ else
+ printf(get_shared_repository()
+ ? _("Initialized empty shared Git repository in %s%s\n")
+ : _("Initialized empty Git repository in %s%s\n"),
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
}
+ free(original_git_dir);
return 0;
}
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.
@@ -437,9 +426,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.
@@ -464,7 +454,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset)
}
static const char *const init_db_usage[] = {
- N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]"),
+ N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [<directory>]"),
NULL
};
@@ -499,7 +489,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
if (real_git_dir && !is_absolute_path(real_git_dir))
- real_git_dir = xstrdup(real_path(real_git_dir));
+ real_git_dir = real_pathdup(real_git_dir, 1);
if (argc == 1) {
int mkdir_tried = 0;
@@ -512,8 +502,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
* and we know shared_repository should always be 0;
* but just in case we play safe.
*/
- saved = shared_repository;
- shared_repository = 0;
+ saved = get_shared_repository();
+ set_shared_repository(0);
switch (safe_create_leading_directories_const(argv[0])) {
case SCLD_OK:
case SCLD_PERMS:
@@ -525,7 +515,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
die_errno(_("cannot mkdir %s"), argv[0]);
break;
}
- shared_repository = saved;
+ set_shared_repository(saved);
if (mkdir(argv[0], 0777) < 0)
die_errno(_("cannot mkdir %s"), argv[0]);
mkdir_tried = 1;
@@ -537,14 +527,13 @@ 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)
- shared_repository = init_shared_repository;
+ set_shared_repository(init_shared_repository);
/*
* GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
@@ -571,16 +560,13 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
const char *git_dir_parent = strrchr(git_dir, '/');
if (git_dir_parent) {
char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
- git_work_tree_cfg = xstrdup(real_path(rel));
+ git_work_tree_cfg = real_pathdup(rel, 1);
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))
@@ -589,10 +575,9 @@ 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);
-
- return init_db(template_dir, flags);
+ flags |= INIT_DB_EXIST_OK;
+ return init_db(git_dir, real_git_dir, template_dir, flags);
}
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
new file mode 100644
index 0000000000..175f14797b
--- /dev/null
+++ b/builtin/interpret-trailers.c
@@ -0,0 +1,49 @@
+/*
+ * 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 [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"),
+ NULL
+};
+
+int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
+{
+ int in_place = 0;
+ int trim_empty = 0;
+ struct string_list trailers = STRING_LIST_INIT_NODUP;
+
+ struct option options[] = {
+ OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")),
+ 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], in_place, trim_empty, &trailers);
+ } else {
+ if (in_place)
+ die(_("no input file given for in-place editing"));
+ process_trailers(NULL, in_place, trim_empty, &trailers);
+ }
+
+ string_list_clear(&trailers, 0);
+
+ return 0;
+}
diff --git a/builtin/log.c b/builtin/log.c
index b97373da3b..281af8c1ec 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -5,6 +5,7 @@
* 2006 Junio Hamano
*/
#include "cache.h"
+#include "refs.h"
#include "color.h"
#include "commit.h"
#include "diff.h"
@@ -31,6 +32,8 @@ static const char *default_date_mode = NULL;
static int default_abbrev_commit;
static int default_show_root = 1;
+static int default_follow;
+static int default_show_signature;
static int decoration_style;
static int decoration_given;
static int use_mailmap_config;
@@ -38,8 +41,8 @@ static const char *fmt_patch_subject_prefix = "PATCH";
static const char *fmt_pretty;
static const char * const builtin_log_usage[] = {
- N_("git log [<options>] [<revision range>] [[--] <path>...]\n")
- N_(" or: git show [options] <object>..."),
+ N_("git log [<options>] [<revision-range>] [[--] <path>...]"),
+ N_("git show [<options>] <object>..."),
NULL
};
@@ -63,6 +66,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;
}
@@ -76,7 +81,7 @@ 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;
@@ -96,10 +101,18 @@ static int log_line_range_callback(const struct option *option, const char *arg,
return 0;
}
+static void init_log_defaults(void)
+{
+ init_grep_defaults();
+ init_diff_ui_defaults();
+}
+
static void cmd_log_init_defaults(struct rev_info *rev)
{
if (fmt_pretty)
get_commit_format(fmt_pretty, rev);
+ if (default_follow)
+ DIFF_OPT_SET(&rev->diffopt, DEFAULT_FOLLOW_RENAMES);
rev->verbose_header = 1;
DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
rev->diffopt.stat_width = -1; /* use full terminal width */
@@ -107,10 +120,11 @@ static void cmd_log_init_defaults(struct rev_info *rev)
rev->abbrev_commit = default_abbrev_commit;
rev->show_root_diff = default_show_root;
rev->subject_prefix = fmt_patch_subject_prefix;
+ rev->show_signature = default_show_signature;
DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
if (default_date_mode)
- rev->date_mode = parse_date_format(default_date_mode);
+ parse_date_format(default_date_mode, &rev->date_mode);
rev->diffopt.touched_flags = 0;
}
@@ -128,7 +142,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
{ OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback},
OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
- "Process line range n,m in file, counting from 1",
+ N_("Process line range n,m in file, counting from 1"),
log_line_range_callback),
OPT_END()
};
@@ -148,7 +162,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
/* 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);
@@ -158,13 +172,9 @@ 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)
+ if (rev->diffopt.pickaxe || rev->diffopt.filter ||
+ DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES))
rev->always_show_header = 0;
- if (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;
@@ -228,16 +238,17 @@ static void show_early_header(struct rev_info *rev, const char *stage, int nr)
if (rev->commit_format != CMIT_FMT_ONELINE)
putchar(rev->diffopt.line_termination);
}
- printf(_("Final output: %d %s\n"), nr, stage);
+ fprintf(rev->diffopt.file, _("Final output: %d %s\n"), nr, stage);
}
static struct itimerval early_output_timer;
static void log_show_early(struct rev_info *revs, struct commit_list *list)
{
- int i = revs->early_output;
+ int i = revs->early_output, close_file = revs->diffopt.close_file;
int show_header = 1;
+ revs->diffopt.close_file = 0;
sort_in_topological_order(&list, revs->sort_order);
while (list && i) {
struct commit *commit = list->item;
@@ -254,14 +265,19 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
case commit_ignore:
break;
case commit_error:
+ if (close_file)
+ fclose(revs->diffopt.file);
return;
}
list = list->next;
}
/* Did we already get enough commits for the early output? */
- if (!i)
+ if (!i) {
+ if (close_file)
+ fclose(revs->diffopt.file);
return;
+ }
/*
* ..if no, then repeat it twice a second until we
@@ -323,7 +339,7 @@ static int cmd_log_walk(struct rev_info *rev)
{
struct commit *commit;
int saved_nrl = 0;
- int saved_dcctc = 0;
+ int saved_dcctc = 0, close_file = rev->diffopt.close_file;
if (rev->early_output)
setup_early_output(rev);
@@ -339,9 +355,9 @@ static int cmd_log_walk(struct rev_info *rev)
* and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
* retain that state information if replacing rev->diffopt in this loop
*/
+ rev->diffopt.close_file = 0;
while ((commit = get_revision(rev)) != NULL) {
- if (!log_tree_commit(rev, commit) &&
- rev->max_count >= 0)
+ if (!log_tree_commit(rev, commit) && rev->max_count >= 0)
/*
* We decremented max_count in get_revision,
* but we didn't actually show the commit.
@@ -349,8 +365,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;
@@ -361,6 +376,8 @@ static int cmd_log_walk(struct rev_info *rev)
}
rev->diffopt.degraded_cc_to_c = saved_dcctc;
rev->diffopt.needed_rename_limit = saved_nrl;
+ if (close_file)
+ fclose(rev->diffopt.file);
if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
@@ -371,6 +388,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"))
@@ -391,12 +410,20 @@ static int git_log_config(const char *var, const char *value, void *cb)
default_show_root = git_config_bool(var, value);
return 0;
}
- if (starts_with(var, "color.decorate."))
- return parse_decorate_color_config(var, 15, value);
+ if (!strcmp(var, "log.follow")) {
+ default_follow = git_config_bool(var, value);
+ return 0;
+ }
+ 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 (!strcmp(var, "log.showsignature")) {
+ default_show_signature = git_config_bool(var, value);
+ return 0;
+ }
if (grep_config(var, value, cb) < 0)
return -1;
@@ -410,7 +437,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct setup_revision_opt opt;
- init_grep_defaults();
+ init_log_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
@@ -433,44 +460,44 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
pp.fmt = rev->commit_format;
pp.date_mode = rev->date_mode;
pp_user_info(&pp, "Tagger", &out, buf, get_log_output_encoding());
- printf("%s", out.buf);
+ fprintf(rev->diffopt.file, "%s", out.buf);
strbuf_release(&out);
}
-static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, const char *obj_name)
+static int show_blob_object(const struct object_id *oid, struct rev_info *rev, const char *obj_name)
{
- unsigned char sha1c[20];
+ struct object_id oidc;
struct object_context obj_context;
char *buf;
unsigned long size;
- fflush(stdout);
+ fflush(rev->diffopt.file);
if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) ||
!DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV))
- return stream_blob_to_fd(1, sha1, NULL, 0);
+ return stream_blob_to_fd(1, oid, NULL, 0);
- if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context))
- die("Not a valid object name %s", obj_name);
+ if (get_sha1_with_context(obj_name, 0, oidc.hash, &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);
+ !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size))
+ return stream_blob_to_fd(1, oid, NULL, 0);
if (!buf)
- die("git show %s: bad file", obj_name);
+ 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)
+static int show_tag_object(const struct object_id *oid, struct rev_info *rev)
{
unsigned long size;
enum object_type type;
- char *buf = read_sha1_file(sha1, &type, &size);
+ char *buf = read_sha1_file(oid->hash, &type, &size);
int offset = 0;
if (!buf)
- return error(_("Could not read object %s"), sha1_to_hex(sha1));
+ return error(_("Could not read object %s"), oid_to_hex(oid));
assert(type == OBJ_TAG);
while (offset < size && buf[offset] != '\n') {
@@ -484,26 +511,28 @@ static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
}
if (offset < size)
- fwrite(buf + offset, size - offset, 1, stdout);
+ fwrite(buf + offset, size - offset, 1, rev->diffopt.file);
free(buf);
return 0;
}
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) ? "/" : "");
+ FILE *file = context;
+ fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
return 0;
}
-static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)
+static void show_setup_revisions_tweak(struct rev_info *rev,
+ struct setup_revision_opt *opt)
{
if (rev->ignore_merges) {
/* There was no "-m" on the command line */
rev->ignore_merges = 0;
if (!rev->first_parent_only && !rev->combine_merges) {
- /* No "--first-parent", "-c", nor "--cc" */
+ /* No "--first-parent", "-c", or "--cc" */
rev->combine_merges = 1;
rev->dense_combined_merges = 1;
}
@@ -520,7 +549,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
struct pathspec match_all;
int i, count, ret = 0;
- init_grep_defaults();
+ init_log_defaults();
git_config(git_log_config, NULL);
memset(&match_all, 0, sizeof(match_all));
@@ -532,7 +561,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
- opt.tweak = show_rev_tweak_rev;
+ opt.tweak = show_setup_revisions_tweak;
cmd_log_init(argc, argv, prefix, &rev, &opt);
if (!rev.no_walk)
@@ -545,25 +574,25 @@ int cmd_show(int argc, const char **argv, const char *prefix)
const char *name = objects[i].name;
switch (o->type) {
case OBJ_BLOB:
- ret = show_blob_object(o->sha1, &rev, name);
+ ret = show_blob_object(&o->oid, &rev, name);
break;
case OBJ_TAG: {
struct tag *t = (struct tag *)o;
if (rev.shown_one)
putchar('\n');
- printf("%stag %s%s\n",
+ fprintf(rev.diffopt.file, "%stag %s%s\n",
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
t->tag,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
- ret = show_tag_object(o->sha1, &rev);
+ ret = show_tag_object(&o->oid, &rev);
rev.shown_one = 1;
if (ret)
break;
- o = parse_object(t->tagged->sha1);
+ o = parse_object(t->tagged->oid.hash);
if (!o)
ret = error(_("Could not read object %s"),
- sha1_to_hex(t->tagged->sha1));
+ oid_to_hex(&t->tagged->oid));
objects[i].item = o;
i--;
break;
@@ -571,12 +600,12 @@ int cmd_show(int argc, const char **argv, const char *prefix)
case OBJ_TREE:
if (rev.shown_one)
putchar('\n');
- printf("%stree %s%s\n\n",
+ fprintf(rev.diffopt.file, "%stree %s%s\n\n",
diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
name,
diff_get_color_opt(&rev.diffopt, DIFF_RESET));
read_tree_recursive((struct tree *)o, "", 0, 0, &match_all,
- show_tree_object, NULL);
+ show_tree_object, rev.diffopt.file);
rev.shown_one = 1;
break;
case OBJ_COMMIT:
@@ -601,7 +630,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();
+ init_log_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
@@ -619,12 +648,28 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
return cmd_log_walk(&rev);
}
+static void log_setup_revisions_tweak(struct rev_info *rev,
+ struct setup_revision_opt *opt)
+{
+ if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) &&
+ rev->prune_data.nr == 1)
+ DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES);
+
+ /* Turn --cc/-c into -p --cc/-c when -p was not given */
+ if (!rev->diffopt.output_format && rev->combine_merges)
+ rev->diffopt.output_format = DIFF_FORMAT_PATCH;
+
+ /* Turn -m on when --cc/-c was given */
+ if (rev->combine_merges)
+ rev->ignore_merges = 0;
+}
+
int cmd_log(int argc, const char **argv, const char *prefix)
{
struct rev_info rev;
struct setup_revision_opt opt;
- init_grep_defaults();
+ init_log_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
@@ -632,6 +677,7 @@ int cmd_log(int argc, const char **argv, const char *prefix)
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
opt.revarg_opt = REVARG_COMMITTISH;
+ opt.tweak = log_setup_revisions_tweak;
cmd_log_init(argc, argv, prefix, &rev, &opt);
return cmd_log_walk(&rev);
}
@@ -644,9 +690,9 @@ static int auto_number = 1;
static char *default_attach = NULL;
-static struct string_list extra_hdr;
-static struct string_list extra_to;
-static struct string_list extra_cc;
+static struct string_list extra_hdr = STRING_LIST_INIT_NODUP;
+static struct string_list extra_to = STRING_LIST_INIT_NODUP;
+static struct string_list extra_cc = STRING_LIST_INIT_NODUP;
static void add_header(const char *value)
{
@@ -672,8 +718,12 @@ static void add_header(const char *value)
#define THREAD_DEEP 2
static int thread;
static int do_signoff;
+static int base_auto;
+static char *from;
static const char *signature = git_version_string;
+static const char *signature_file;
static int config_cover_letter;
+static const char *config_output_directory;
enum {
COVER_UNSET,
@@ -705,7 +755,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")) {
@@ -742,6 +792,8 @@ 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;
@@ -750,15 +802,31 @@ static int git_format_config(const char *var, const char *value, void *cb)
config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.outputdirectory"))
+ return git_config_string(&config_output_directory, var, value);
+ if (!strcmp(var, "format.useautobase")) {
+ base_auto = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "format.from")) {
+ int b = git_config_maybe_bool(var, value);
+ free(from);
+ if (b < 0)
+ from = xstrdup(value);
+ else if (b)
+ from = xstrdup(git_committer_info(IDENT_NO_DATE));
+ else
+ from = NULL;
+ return 0;
+ }
return git_log_config(var, value, cb);
}
-static FILE *realstdout = NULL;
static const char *output_directory = NULL;
static int outdir_offset;
-static int reopen_stdout(struct commit *commit, const char *subject,
+static int open_next_file(struct commit *commit, const char *subject,
struct rev_info *rev, int quiet)
{
struct strbuf filename = STRBUF_INIT;
@@ -769,8 +837,7 @@ static int reopen_stdout(struct commit *commit, const char *subject,
if (filename.len >=
PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
return error(_("name of output directory is too long"));
- if (filename.buf[filename.len - 1] != '/')
- strbuf_addch(&filename, '/');
+ strbuf_complete(&filename, '/');
}
if (rev->numbered_files)
@@ -781,9 +848,9 @@ static int reopen_stdout(struct commit *commit, const char *subject,
fmt_output_subject(&filename, subject, rev);
if (!quiet)
- fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
+ printf("%s\n", filename.buf + outdir_offset);
- if (freopen(filename.buf, "w", stdout) == NULL)
+ if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL)
return error(_("Cannot open patch file %s"), filename.buf);
strbuf_release(&filename);
@@ -793,7 +860,7 @@ static int reopen_stdout(struct commit *commit, const char *subject,
static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
{
struct rev_info check_rev;
- struct commit *commit;
+ struct commit *commit, *c1, *c2;
struct object *o1, *o2;
unsigned flags1, flags2;
@@ -801,9 +868,11 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
die(_("Need exactly one range."));
o1 = rev->pending.objects[0].item;
- flags1 = o1->flags;
o2 = rev->pending.objects[1].item;
+ flags1 = o1->flags;
flags2 = o2->flags;
+ c1 = lookup_commit_reference(o1->oid.hash);
+ c2 = lookup_commit_reference(o2->oid.hash);
if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
die(_("Not a range."));
@@ -825,10 +894,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
}
/* reset for next revision walk */
- clear_commit_marks((struct commit *)o1,
- SEEN | UNINTERESTING | SHOWN | ADDED);
- clear_commit_marks((struct commit *)o2,
- SEEN | UNINTERESTING | SHOWN | ADDED);
+ clear_commit_marks(c1, SEEN | UNINTERESTING | SHOWN | ADDED);
+ clear_commit_marks(c2, SEEN | UNINTERESTING | SHOWN | ADDED);
o1->flags = flags1;
o2->flags = flags2;
}
@@ -842,10 +909,15 @@ static void gen_message_id(struct rev_info *info, char *base)
info->message_id = strbuf_detach(&buf, NULL);
}
-static void print_signature(void)
+static void print_signature(FILE *file)
{
- if (signature && *signature)
- printf("-- \n%s\n\n", signature);
+ if (!signature || !*signature)
+ return;
+
+ fprintf(file, "-- \n%s", signature);
+ if (signature[strlen(signature)-1] != '\n')
+ putc('\n', file);
+ putc('\n', file);
}
static void add_branch_description(struct strbuf *buf, const char *branch_name)
@@ -856,17 +928,18 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name)
read_branch_desc(&desc, branch_name);
if (desc.len) {
strbuf_addch(buf, '\n');
- strbuf_add(buf, desc.buf, desc.len);
+ 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;
+ struct object_id branch_oid;
+ const struct object_id *tip_oid;
+ const char *ref, *v;
char *full_ref, *branch = NULL;
for (i = 0; i < rev->cmdline.nr; i++) {
@@ -880,11 +953,11 @@ static char *find_branch_name(struct rev_info *rev)
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) &&
- starts_with(full_ref, "refs/heads/") &&
- !hashcmp(tip_sha1, branch_sha1))
- branch = xstrdup(full_ref + strlen("refs/heads/"));
+ tip_oid = &rev->cmdline.rev[positive].item->oid;
+ if (dwim_ref(ref, strlen(ref), branch_oid.hash, &full_ref) &&
+ skip_prefix(full_ref, "refs/heads/", &v) &&
+ !oidcmp(tip_oid, &branch_oid))
+ branch = xstrdup(v);
free(full_ref);
return branch;
}
@@ -907,33 +980,37 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
struct pretty_print_context pp = {0};
struct commit *head = list[0];
- if (rev->commit_format != CMIT_FMT_EMAIL)
+ if (!cmit_fmt_is_mail(rev->commit_format))
die(_("Cover letter needs email format"));
committer = git_committer_info(0);
if (!use_stdout &&
- reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
+ open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
return;
- log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
- &need_8bit_cte);
+ log_write_email_headers(rev, head, &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;
- pp.date_mode = DATE_RFC2822;
+ pp.date_mode.type = DATE_RFC2822;
+ pp.rev = rev;
+ pp.print_email_subject = 1;
pp_user_info(&pp, NULL, &sb, committer, encoding);
pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
pp_remainder(&pp, &msg, &sb, 0);
add_branch_description(&sb, branch_name);
- printf("%s\n", sb.buf);
+ fprintf(rev->diffopt.file, "%s\n", sb.buf);
strbuf_release(&sb);
@@ -942,6 +1019,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
log.wrap = 72;
log.in1 = 2;
log.in2 = 4;
+ log.file = rev->diffopt.file;
for (i = 0; i < nr; i++)
shortlog_add_commit(&log, list[i]);
@@ -958,14 +1036,13 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
diff_setup_done(&opts);
- diff_tree_sha1(origin->tree->object.sha1,
- head->tree->object.sha1,
+ diff_tree_sha1(origin->tree->object.oid.hash,
+ head->tree->object.oid.hash,
"", &opts);
diffcore_std(&opts);
diff_flush(&opts);
- printf("\n");
- print_signature();
+ fprintf(rev->diffopt.file, "\n");
}
static const char *clean_message_id(const char *msg_id)
@@ -1012,7 +1089,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory)
}
static const char * const builtin_format_patch_usage[] = {
- N_("git format-patch [options] [<since> | <revision range>]"),
+ N_("git format-patch [<options>] [<since> | <revision-range>]"),
NULL
};
@@ -1035,6 +1112,11 @@ static int subject_prefix_callback(const struct option *opt, const char *arg,
return 0;
}
+static int rfc_callback(const struct option *opt, const char *arg, int unset)
+{
+ return subject_prefix_callback(opt, "RFC PATCH", unset);
+}
+
static int numbered_cmdline_opt = 0;
static int numbered_callback(const struct option *opt, const char *arg,
@@ -1147,6 +1229,155 @@ static int from_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
+struct base_tree_info {
+ struct object_id base_commit;
+ int nr_patch_id, alloc_patch_id;
+ struct object_id *patch_id;
+};
+
+static struct commit *get_base_commit(const char *base_commit,
+ struct commit **list,
+ int total)
+{
+ struct commit *base = NULL;
+ struct commit **rev;
+ int i = 0, rev_nr = 0;
+
+ if (base_commit && strcmp(base_commit, "auto")) {
+ base = lookup_commit_reference_by_name(base_commit);
+ if (!base)
+ die(_("Unknown commit %s"), base_commit);
+ } else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) {
+ struct branch *curr_branch = branch_get(NULL);
+ const char *upstream = branch_get_upstream(curr_branch, NULL);
+ if (upstream) {
+ struct commit_list *base_list;
+ struct commit *commit;
+ struct object_id oid;
+
+ if (get_oid(upstream, &oid))
+ die(_("Failed to resolve '%s' as a valid ref."), upstream);
+ commit = lookup_commit_or_die(oid.hash, "upstream base");
+ base_list = get_merge_bases_many(commit, total, list);
+ /* There should be one and only one merge base. */
+ if (!base_list || base_list->next)
+ die(_("Could not find exact merge base."));
+ base = base_list->item;
+ free_commit_list(base_list);
+ } else {
+ die(_("Failed to get upstream, if you want to record base commit automatically,\n"
+ "please use git branch --set-upstream-to to track a remote branch.\n"
+ "Or you could specify base commit by --base=<base-commit-id> manually."));
+ }
+ }
+
+ ALLOC_ARRAY(rev, total);
+ for (i = 0; i < total; i++)
+ rev[i] = list[i];
+
+ rev_nr = total;
+ /*
+ * Get merge base through pair-wise computations
+ * and store it in rev[0].
+ */
+ while (rev_nr > 1) {
+ for (i = 0; i < rev_nr / 2; i++) {
+ struct commit_list *merge_base;
+ merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]);
+ if (!merge_base || merge_base->next)
+ die(_("Failed to find exact merge base"));
+
+ rev[i] = merge_base->item;
+ }
+
+ if (rev_nr % 2)
+ rev[i] = rev[2 * i];
+ rev_nr = (rev_nr + 1) / 2;
+ }
+
+ if (!in_merge_bases(base, rev[0]))
+ die(_("base commit should be the ancestor of revision list"));
+
+ for (i = 0; i < total; i++) {
+ if (base == list[i])
+ die(_("base commit shouldn't be in revision list"));
+ }
+
+ free(rev);
+ return base;
+}
+
+static void prepare_bases(struct base_tree_info *bases,
+ struct commit *base,
+ struct commit **list,
+ int total)
+{
+ struct commit *commit;
+ struct rev_info revs;
+ struct diff_options diffopt;
+ int i;
+
+ if (!base)
+ return;
+
+ diff_setup(&diffopt);
+ DIFF_OPT_SET(&diffopt, RECURSIVE);
+ diff_setup_done(&diffopt);
+
+ oidcpy(&bases->base_commit, &base->object.oid);
+
+ init_revisions(&revs, NULL);
+ revs.max_parents = 1;
+ revs.topo_order = 1;
+ for (i = 0; i < total; i++) {
+ list[i]->object.flags &= ~UNINTERESTING;
+ add_pending_object(&revs, &list[i]->object, "rev_list");
+ list[i]->util = (void *)1;
+ }
+ base->object.flags |= UNINTERESTING;
+ add_pending_object(&revs, &base->object, "base");
+
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
+ /*
+ * Traverse the commits list, get prerequisite patch ids
+ * and stuff them in bases structure.
+ */
+ while ((commit = get_revision(&revs)) != NULL) {
+ struct object_id oid;
+ struct object_id *patch_id;
+ if (commit->util)
+ continue;
+ if (commit_patch_id(commit, &diffopt, oid.hash, 0))
+ die(_("cannot get patch id"));
+ ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id);
+ patch_id = bases->patch_id + bases->nr_patch_id;
+ oidcpy(patch_id, &oid);
+ bases->nr_patch_id++;
+ }
+}
+
+static void print_bases(struct base_tree_info *bases, FILE *file)
+{
+ int i;
+
+ /* Only do this once, either for the cover or for the first one */
+ if (is_null_oid(&bases->base_commit))
+ return;
+
+ /* Show the base commit */
+ fprintf(file, "\nbase-commit: %s\n", oid_to_hex(&bases->base_commit));
+
+ /* Show the prerequisite patches */
+ for (i = bases->nr_patch_id - 1; i >= 0; i--)
+ fprintf(file, "prerequisite-patch-id: %s\n", oid_to_hex(&bases->patch_id[i]));
+
+ free(bases->patch_id);
+ bases->nr_patch_id = 0;
+ bases->alloc_patch_id = 0;
+ oidclr(&bases->base_commit);
+}
+
int cmd_format_patch(int argc, const char **argv, const char *prefix)
{
struct commit *commit;
@@ -1161,6 +1392,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int cover_letter = -1;
int boundary_count = 0;
int no_binary_diff = 0;
+ int zero_commit = 0;
struct commit *origin = NULL;
const char *in_reply_to = NULL;
struct patch_ids ids;
@@ -1169,7 +1401,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int quiet = 0;
int reroll_count = -1;
char *branch_name = NULL;
- char *from = NULL;
+ char *base_commit = NULL;
+ struct base_tree_info bases;
+
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
N_("use [PATCH n/m] even with a single patch"),
@@ -1190,6 +1424,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
N_("start numbering patches at <n> instead of 1")),
OPT_INTEGER('v', "reroll-count", &reroll_count,
N_("mark the series as Nth re-roll")),
+ { OPTION_CALLBACK, 0, "rfc", &rev, NULL,
+ N_("Use [RFC PATCH] instead of [PATCH]"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
{ OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
N_("Use [<prefix>] instead of [PATCH]"),
PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1201,6 +1438,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
OPT_BOOL(0, "no-binary", &no_binary_diff,
N_("don't output binary diffs")),
+ OPT_BOOL(0, "zero-commit", &zero_commit,
+ N_("output all-zero hash in From header")),
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,
@@ -1230,6 +1469,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, thread_callback },
OPT_STRING(0, "signature", &signature, N_("signature"),
N_("add a signature")),
+ OPT_STRING(0, "base", &base_commit, N_("base-commit"),
+ N_("add prerequisite tree info to the patch series")),
+ 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()
};
@@ -1237,10 +1480,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
extra_hdr.strdup_strings = 1;
extra_to.strdup_strings = 1;
extra_cc.strdup_strings = 1;
- init_grep_defaults();
+ init_log_defaults();
git_config(git_format_config, NULL);
init_revisions(&rev, prefix);
rev.commit_format = CMIT_FMT_EMAIL;
+ rev.expand_tabs_in_log_default = 0;
rev.verbose_header = 1;
rev.diff = 1;
rev.max_parents = 1;
@@ -1321,7 +1565,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (numbered && keep_subject)
die (_("-n and -k are mutually exclusive."));
if (keep_subject && subject_prefix)
- die (_("--subject-prefix and -k are mutually exclusive."));
+ die (_("--subject-prefix/--rfc and -k are mutually exclusive."));
rev.preserve_subject = keep_subject;
argc = setup_revisions(argc, argv, &rev, &s_r_opt);
@@ -1343,18 +1587,25 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
/* Always generate a patch */
rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
+ rev.zero_commit = zero_commit;
+
if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
DIFF_OPT_SET(&rev.diffopt, BINARY);
if (rev.show_notes)
init_display_notes(&rev.notes_opt);
+ if (!output_directory && !use_stdout)
+ output_directory = config_output_directory;
+
if (!use_stdout)
output_directory = set_outdir(prefix, output_directory);
else
setup_pager();
if (output_directory) {
+ if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
+ rev.diffopt.use_color = GIT_COLOR_NEVER;
if (use_stdout)
die(_("standard output, or directory, which one?"));
if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
@@ -1385,11 +1636,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
check_head = 1;
if (check_head) {
- unsigned char sha1[20];
- const char *ref;
- ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
- if (ref && starts_with(ref, "refs/heads/"))
- branch_name = xstrdup(ref + strlen("refs/heads/"));
+ struct object_id oid;
+ const char *ref, *v;
+ ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+ oid.hash, NULL);
+ if (ref && skip_prefix(ref, "refs/heads/", &v))
+ branch_name = xstrdup(v);
else
branch_name = xstrdup(""); /* no branch */
}
@@ -1405,15 +1657,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
/* Don't say anything if head and upstream are the same. */
if (rev.pending.nr == 2) {
struct object_array_entry *o = rev.pending.objects;
- if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
+ if (oidcmp(&o[0].item->oid, &o[1].item->oid) == 0)
return 0;
}
get_patch_ids(&rev, &ids);
}
- if (!use_stdout)
- realstdout = xfdopen(xdup(1), "w");
-
if (prepare_revision_walk(&rev))
die(_("revision walk setup failed"));
rev.boundary = 1;
@@ -1424,28 +1673,46 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
continue;
}
- if (ignore_if_in_upstream &&
- has_commit_patch_id(commit, &ids))
+ if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids))
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 (!keep_subject && auto_number && (total > 1 || cover_letter))
+ numbered = 1;
+ if (numbered)
+ rev.total = total + start_number - 1;
+
+ 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);
+ }
+
+ memset(&bases, 0, sizeof(bases));
+ if (base_commit || base_auto) {
+ struct commit *base = get_base_commit(base_commit, list, nr);
+ reset_revision_walk();
+ prepare_bases(&bases, base, list, nr);
+ }
if (in_reply_to || thread || cover_letter)
rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
@@ -1460,6 +1727,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, use_stdout,
origin, nr, list, branch_name, quiet);
+ print_bases(&bases, rev.diffopt.file);
+ print_signature(rev.diffopt.file);
total++;
start_number--;
}
@@ -1501,15 +1770,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
string_list_append(rev.ref_message_ids,
rev.message_id);
}
- gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
+ gen_message_id(&rev, oid_to_hex(&commit->object.oid));
}
if (!use_stdout &&
- reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet))
+ open_next_file(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
@@ -1520,15 +1788,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (!use_stdout)
rev.shown_one = 0;
if (shown) {
+ print_bases(&bases, rev.diffopt.file);
if (rev.mime_boundary)
- printf("\n--%s%s--\n\n\n",
+ fprintf(rev.diffopt.file, "\n--%s%s--\n\n\n",
mime_boundary_leader,
rev.mime_boundary);
else
- print_signature();
+ print_signature(rev.diffopt.file);
}
if (!use_stdout)
- fclose(stdout);
+ fclose(rev.diffopt.file);
}
free(list);
free(branch_name);
@@ -1542,9 +1811,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
static int add_pending_commit(const char *arg, struct rev_info *revs, int flags)
{
- unsigned char sha1[20];
- if (get_sha1(arg, sha1) == 0) {
- struct commit *commit = lookup_commit_reference(sha1);
+ struct object_id oid;
+ if (get_oid(arg, &oid) == 0) {
+ struct commit *commit = lookup_commit_reference(oid.hash);
if (commit) {
commit->object.flags |= flags;
add_pending_object(revs, &commit->object, arg);
@@ -1560,16 +1829,16 @@ static const char * const cherry_usage[] = {
};
static void print_commit(char sign, struct commit *commit, int verbose,
- int abbrev)
+ int abbrev, FILE *file)
{
if (!verbose) {
- printf("%c %s\n", sign,
- find_unique_abbrev(commit->object.sha1, abbrev));
+ fprintf(file, "%c %s\n", sign,
+ find_unique_abbrev(commit->object.oid.hash, abbrev));
} else {
struct strbuf buf = STRBUF_INIT;
pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf);
- printf("%c %s %s\n", sign,
- find_unique_abbrev(commit->object.sha1, abbrev),
+ fprintf(file, "%c %s %s\n", sign,
+ find_unique_abbrev(commit->object.oid.hash, abbrev),
buf.buf);
strbuf_release(&buf);
}
@@ -1607,16 +1876,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
break;
default:
current_branch = branch_get(NULL);
- if (!current_branch || !current_branch->merge
- || !current_branch->merge[0]
- || !current_branch->merge[0]->dst) {
+ upstream = branch_get_upstream(current_branch, NULL);
+ if (!upstream) {
fprintf(stderr, _("Could not find a tracked"
" remote branch, please"
" specify <upstream> manually.\n"));
usage_with_options(cherry_usage, options);
}
-
- upstream = current_branch->merge[0]->dst;
}
init_revisions(&revs, prefix);
@@ -1630,7 +1896,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
/* Don't say anything if head and upstream are the same. */
if (revs.pending.nr == 2) {
struct object_array_entry *o = revs.pending.objects;
- if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
+ if (oidcmp(&o[0].item->oid, &o[1].item->oid) == 0)
return 0;
}
@@ -1652,7 +1918,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
commit = list->item;
if (has_commit_patch_id(commit, &ids))
sign = '-';
- print_commit(sign, commit, verbose, abbrev);
+ print_commit(sign, commit, verbose, abbrev, revs.diffopt.file);
list = list->next;
}
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 47c38808a2..1c0f057d02 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -14,6 +14,7 @@
#include "resolve-undo.h"
#include "string-list.h"
#include "pathspec.h"
+#include "run-command.h"
static int abbrev;
static int show_deleted;
@@ -27,8 +28,12 @@ static int show_killed;
static int show_valid_bit;
static int line_terminator = '\n';
static int debug_mode;
+static int show_eol;
+static int recurse_submodules;
+static struct argv_array submodules_options = ARGV_ARRAY_INIT;
static const char *prefix;
+static const char *super_prefix;
static int max_prefix_len;
static int prefix_len;
static struct pathspec pathspec;
@@ -47,14 +52,44 @@ static const char *tag_modified = "";
static const char *tag_skip_worktree = "";
static const char *tag_resolve_undo = "";
+static void write_eolinfo(const struct cache_entry *ce, const char *path)
+{
+ if (!show_eol)
+ return;
+ else {
+ struct stat st;
+ const char *i_txt = "";
+ const char *w_txt = "";
+ const char *a_txt = get_convert_attr_ascii(path);
+ if (ce && S_ISREG(ce->ce_mode))
+ i_txt = get_cached_convert_stats_ascii(ce->name);
+ if (!lstat(path, &st) && S_ISREG(st.st_mode))
+ w_txt = get_wt_convert_stats_ascii(path);
+ printf("i/%-5s w/%-5s attr/%-17s\t", i_txt, w_txt, a_txt);
+ }
+}
+
static void write_name(const char *name)
{
/*
+ * Prepend the super_prefix to name to construct the full_name to be
+ * written.
+ */
+ struct strbuf full_name = STRBUF_INIT;
+ if (super_prefix) {
+ strbuf_addstr(&full_name, super_prefix);
+ strbuf_addstr(&full_name, name);
+ name = full_name.buf;
+ }
+
+ /*
* 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);
+
+ strbuf_release(&full_name);
}
static void show_dir_entry(const char *tag, struct dir_entry *ent)
@@ -68,6 +103,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
return;
fputs(tag, stdout);
+ write_eolinfo(NULL, ent->name);
write_name(ent->name);
}
@@ -99,7 +135,8 @@ static void show_killed_files(struct dir_struct *dir)
*/
pos = cache_name_pos(ent->name, ent->len);
if (0 <= pos)
- die("bug in show-killed-files");
+ die("BUG: killed-file %.*s not found",
+ ent->len, ent->name);
pos = -pos - 1;
while (pos < active_nr &&
ce_stage(active_cache[pos]))
@@ -132,54 +169,117 @@ static void show_killed_files(struct dir_struct *dir)
}
}
+/*
+ * Compile an argv_array with all of the options supported by --recurse_submodules
+ */
+static void compile_submodule_options(const struct dir_struct *dir, int show_tag)
+{
+ if (line_terminator == '\0')
+ argv_array_push(&submodules_options, "-z");
+ if (show_tag)
+ argv_array_push(&submodules_options, "-t");
+ if (show_valid_bit)
+ argv_array_push(&submodules_options, "-v");
+ if (show_cached)
+ argv_array_push(&submodules_options, "--cached");
+ if (show_eol)
+ argv_array_push(&submodules_options, "--eol");
+ if (debug_mode)
+ argv_array_push(&submodules_options, "--debug");
+}
+
+/**
+ * Recursively call ls-files on a submodule
+ */
+static void show_gitlink(const struct cache_entry *ce)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ int status;
+ int i;
+
+ argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
+ super_prefix ? super_prefix : "",
+ ce->name);
+ argv_array_push(&cp.args, "ls-files");
+ argv_array_push(&cp.args, "--recurse-submodules");
+
+ /* add supported options */
+ argv_array_pushv(&cp.args, submodules_options.argv);
+
+ /*
+ * Pass in the original pathspec args. The submodule will be
+ * responsible for prepending the 'submodule_prefix' prior to comparing
+ * against the pathspec for matches.
+ */
+ argv_array_push(&cp.args, "--");
+ for (i = 0; i < pathspec.nr; i++)
+ argv_array_push(&cp.args, pathspec.items[i].original);
+
+ cp.git_cmd = 1;
+ cp.dir = ce->name;
+ status = run_command(&cp);
+ if (status)
+ exit(status);
+}
+
static void show_ce_entry(const char *tag, const struct cache_entry *ce)
{
+ struct strbuf name = STRBUF_INIT;
int len = max_prefix_len;
+ if (super_prefix)
+ strbuf_addstr(&name, super_prefix);
+ strbuf_addstr(&name, ce->name);
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,
- S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode)))
- return;
+ if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
+ submodule_path_match(&pathspec, name.buf, ps_matched)) {
+ show_gitlink(ce);
+ } else if (match_pathspec(&pathspec, name.buf, name.len,
+ len, ps_matched,
+ S_ISDIR(ce->ce_mode) ||
+ S_ISGITLINK(ce->ce_mode))) {
+ if (tag && *tag && show_valid_bit &&
+ (ce->ce_flags & CE_VALID)) {
+ static char alttag[4];
+ memcpy(alttag, tag, 3);
+ if (isalpha(tag[0]))
+ alttag[0] = tolower(tag[0]);
+ else if (tag[0] == '?')
+ alttag[0] = '!';
+ else {
+ alttag[0] = 'v';
+ alttag[1] = tag[0];
+ alttag[2] = ' ';
+ alttag[3] = 0;
+ }
+ tag = alttag;
+ }
- if (tag && *tag && show_valid_bit &&
- (ce->ce_flags & CE_VALID)) {
- static char alttag[4];
- memcpy(alttag, tag, 3);
- if (isalpha(tag[0]))
- alttag[0] = tolower(tag[0]);
- else if (tag[0] == '?')
- alttag[0] = '!';
- else {
- alttag[0] = 'v';
- alttag[1] = tag[0];
- alttag[2] = ' ';
- alttag[3] = 0;
+ if (!show_stage) {
+ fputs(tag, stdout);
+ } else {
+ printf("%s%06o %s %d\t",
+ tag,
+ ce->ce_mode,
+ find_unique_abbrev(ce->oid.hash, abbrev),
+ ce_stage(ce));
+ }
+ write_eolinfo(ce, ce->name);
+ write_name(ce->name);
+ if (debug_mode) {
+ 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);
}
- tag = alttag;
}
- if (!show_stage) {
- fputs(tag, stdout);
- } else {
- printf("%s%06o %s %d\t",
- tag,
- ce->ce_mode,
- find_unique_abbrev(ce->sha1,abbrev),
- ce_stage(ce));
- }
- write_name(ce->name);
- if (debug_mode) {
- 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);
- }
+ strbuf_release(&name);
}
static void show_ru_info(void)
@@ -269,28 +369,30 @@ static void show_files(struct dir_struct *dir)
/*
* Prune the index to only contain stuff starting with "prefix"
*/
-static void prune_cache(const char *prefix)
+static void prune_cache(const char *prefix, size_t prefixlen)
{
- int pos = cache_name_pos(prefix, max_prefix_len);
+ int pos;
unsigned int first, last;
+ if (!prefix)
+ return;
+ pos = cache_name_pos(prefix, prefixlen);
if (pos < 0)
pos = -pos-1;
- memmove(active_cache, active_cache + pos,
- (active_nr - pos) * sizeof(struct cache_entry *));
- active_nr -= pos;
- first = 0;
+ first = pos;
last = active_nr;
while (last > first) {
int next = (last + first) >> 1;
const struct cache_entry *ce = active_cache[next];
- if (!strncmp(ce->name, prefix, max_prefix_len)) {
+ if (!strncmp(ce->name, prefix, prefixlen)) {
first = next+1;
continue;
}
last = next;
}
- active_nr = last;
+ memmove(active_cache, active_cache + pos,
+ (last - pos) * sizeof(struct cache_entry *));
+ active_nr = last - pos;
}
/*
@@ -354,62 +456,11 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix)
}
}
-int report_path_error(const char *ps_matched,
- const struct pathspec *pathspec,
- const char *prefix)
-{
- /*
- * Make sure all pathspec matched; otherwise it is an error.
- */
- struct strbuf sb = STRBUF_INIT;
- int num, errors = 0;
- for (num = 0; num < pathspec->nr; 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.
- * FIXME: parse_pathspec should have eliminated
- * duplicate pathspec.
- */
- for (found_dup = other = 0;
- !found_dup && other < pathspec->nr;
- other++) {
- if (other == num || !ps_matched[other])
- continue;
- if (!strcmp(pathspec->items[other].original,
- pathspec->items[num].original))
- /*
- * Ok, we have a match already.
- */
- found_dup = 1;
- }
- if (found_dup)
- continue;
-
- error("pathspec '%s' did not match any file(s) known to git.",
- pathspec->items[num].original);
- errors++;
- }
- strbuf_release(&sb);
- return errors;
-}
-
static const char * const ls_files_usage[] = {
- N_("git ls-files [options] [<file>...]"),
+ N_("git ls-files [<options>] [<file>...]"),
NULL
};
-static int option_parse_z(const struct option *opt,
- const char *arg, int unset)
-{
- line_terminator = unset ? '\n' : '\0';
-
- return 0;
-}
-
static int option_parse_exclude(const struct option *opt,
const char *arg, int unset)
{
@@ -451,9 +502,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
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,
- N_("paths are separated with NUL character"),
- PARSE_OPT_NOARG, option_parse_z },
+ /* Think twice before adding "--nul" synonym to this */
+ OPT_SET_INT('z', NULL, &line_terminator,
+ N_("paths are separated with NUL character"), '\0'),
OPT_BOOL('t', NULL, &show_tag,
N_("identify the file status with tags")),
OPT_BOOL('v', NULL, &show_valid_bit,
@@ -474,8 +525,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
OPT_BOOL('k', "killed", &show_killed,
N_("show files on the filesystem that need to be removed")),
OPT_BIT(0, "directory", &dir.flags,
- N_("show 'other' directories' name only"),
+ N_("show 'other' directories' names only"),
DIR_SHOW_OTHER_DIRECTORIES),
+ OPT_BOOL(0, "eol", &show_eol, N_("show line endings of files")),
OPT_NEGBIT(0, "empty-directory", &dir.flags,
N_("don't show empty directories"),
DIR_HIDE_EMPTY_DIRECTORIES),
@@ -497,6 +549,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
{ OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
N_("make the output relative to the project top directory"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
+ OPT_BOOL(0, "recurse-submodules", &recurse_submodules,
+ N_("recurse through submodules")),
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"),
@@ -513,6 +567,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
prefix = cmd_prefix;
if (prefix)
prefix_len = strlen(prefix);
+ super_prefix = get_super_prefix();
git_config(git_default_config, NULL);
if (read_cache() < 0)
@@ -548,18 +603,37 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (require_work_tree && !is_inside_work_tree())
setup_work_tree();
+ if (recurse_submodules)
+ compile_submodule_options(&dir, show_tag);
+
+ if (recurse_submodules &&
+ (show_stage || show_deleted || show_others || show_unmerged ||
+ show_killed || show_modified || show_resolve_undo || with_tree))
+ die("ls-files --recurse-submodules unsupported mode");
+
+ if (recurse_submodules && error_unmatch)
+ die("ls-files --recurse-submodules does not support "
+ "--error-unmatch");
+
parse_pathspec(&pathspec, 0,
PATHSPEC_PREFER_CWD |
PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
prefix, argv);
- /* Find common prefix for all pathspec's */
- max_prefix = common_prefix(&pathspec);
+ /*
+ * Find common prefix for all pathspec's
+ * This is used as a performance optimization which unfortunately cannot
+ * be done when recursing into submodules
+ */
+ if (recurse_submodules)
+ max_prefix = NULL;
+ else
+ max_prefix = common_prefix(&pathspec);
max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
/* Treat unmatching pathspec elements as errors */
if (pathspec.nr && error_unmatch)
- ps_matched = xcalloc(1, pathspec.nr);
+ ps_matched = xcalloc(pathspec.nr, 1);
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
die("ls-files --ignored needs some exclude pattern");
@@ -569,8 +643,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
show_killed || show_modified || show_resolve_undo))
show_cached = 1;
- if (max_prefix)
- prune_cache(max_prefix);
+ prune_cache(max_prefix, max_prefix_len);
if (with_tree) {
/*
* Basic sanity check; show-stages and show-unmerged
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 39e5144b9e..66cdd45cc1 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -3,9 +3,12 @@
#include "transport.h"
#include "remote.h"
-static const char ls_remote_usage[] =
-"git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n"
-" [-q|--quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]";
+static const char * const ls_remote_usage[] = {
+ N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n"
+ " [-q | --quiet] [--exit-code] [--get-url]\n"
+ " [--symref] [<repository> [<refs>...]]"),
+ NULL
+};
/*
* Is there one among the list of patterns that match the tail part
@@ -22,7 +25,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;
@@ -30,12 +33,12 @@ static int tail_match(const char **pattern, const char *path)
int cmd_ls_remote(int argc, const char **argv, const char *prefix)
{
- int i;
const char *dest = NULL;
unsigned flags = 0;
int get_url = 0;
int quiet = 0;
int status = 0;
+ int show_symref_target = 0;
const char *uploadpack = NULL;
const char **pattern = NULL;
@@ -43,63 +46,36 @@ 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);
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("do not print remote URL")),
+ OPT_STRING(0, "upload-pack", &uploadpack, N_("exec"),
+ N_("path of git-upload-pack on the remote host")),
+ { OPTION_STRING, 0, "exec", &uploadpack, N_("exec"),
+ N_("path of git-upload-pack on the remote host"),
+ PARSE_OPT_HIDDEN },
+ OPT_BIT('t', "tags", &flags, N_("limit to tags"), REF_TAGS),
+ OPT_BIT('h', "heads", &flags, N_("limit to heads"), REF_HEADS),
+ OPT_BIT(0, "refs", &flags, N_("do not show peeled tags"), REF_NORMAL),
+ OPT_BOOL(0, "get-url", &get_url,
+ N_("take url.<base>.insteadOf into account")),
+ OPT_SET_INT(0, "exit-code", &status,
+ N_("exit with exit code 2 if no matching refs are found"), 2),
+ OPT_BOOL(0, "symref", &show_symref_target,
+ N_("show underlying ref in addition to the object pointed by it")),
+ OPT_END()
+ };
- for (i = 1; i < argc; i++) {
- const char *arg = argv[i];
+ argc = parse_options(argc, argv, prefix, options, ls_remote_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ dest = argv[0];
- if (*arg == '-') {
- if (starts_with(arg, "--upload-pack=")) {
- uploadpack = arg + 14;
- continue;
- }
- if (starts_with(arg, "--exec=")) {
- uploadpack = arg + 7;
- continue;
- }
- if (!strcmp("--tags", arg) || !strcmp("-t", arg)) {
- flags |= REF_TAGS;
- continue;
- }
- if (!strcmp("--heads", arg) || !strcmp("-h", arg)) {
- flags |= REF_HEADS;
- continue;
- }
- if (!strcmp("--refs", arg)) {
- flags |= REF_NORMAL;
- continue;
- }
- if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
- quiet = 1;
- continue;
- }
- if (!strcmp("--get-url", arg)) {
- get_url = 1;
- continue;
- }
- if (!strcmp("--exit-code", arg)) {
- /* return this code if no refs are reported */
- status = 2;
- continue;
- }
- usage(ls_remote_usage);
- }
- dest = arg;
- i++;
- break;
+ if (argc > 1) {
+ int i;
+ pattern = xcalloc(argc, sizeof(const char *));
+ for (i = 1; i < argc; i++)
+ pattern[i - 1] = xstrfmt("*/%s", argv[i]);
}
- if (argv[i]) {
- int j;
- pattern = xcalloc(sizeof(const char *), argc - i + 1);
- for (j = i; j < argc; j++) {
- int len = strlen(argv[j]);
- char *p = xmalloc(len + 3);
- sprintf(p, "*/%s", argv[j]);
- pattern[j - i] = p;
- }
- }
remote = remote_get(dest);
if (!remote) {
if (dest)
@@ -129,7 +105,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
continue;
if (!tail_match(pattern, ref->name))
continue;
- printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+ if (show_symref_target && ref->symref)
+ printf("ref: %s\t%s\n", ref->symref, ref->name);
+ printf("%s\t%s\n", oid_to_hex(&ref->old_oid), ref->name);
status = 0; /* we found something */
}
return status;
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 51184dfa2e..d7ebeb4ce6 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -31,21 +31,18 @@ static const char * const ls_tree_usage[] = {
static int show_recursive(const char *base, int baselen, const char *pathname)
{
- const char **s;
+ int i;
if (ls_options & LS_RECURSIVE)
return 1;
- s = pathspec._raw;
- if (!s)
+ if (!pathspec.nr)
return 0;
- for (;;) {
- const char *spec = *s++;
+ for (i = 0; i < pathspec.nr; i++) {
+ const char *spec = pathspec.items[i].match;
int len, speclen;
- if (!spec)
- return 0;
if (strncmp(base, spec, baselen))
continue;
len = strlen(pathname);
@@ -59,12 +56,14 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
continue;
return 1;
}
+ return 0;
}
-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)) {
@@ -79,7 +78,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;
@@ -89,22 +88,19 @@ 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];
if (!strcmp(type, blob_type)) {
unsigned long size;
if (sha1_object_info(sha1, &size) == OBJ_BAD)
- strcpy(size_text, "BAD");
+ xsnprintf(size_text, sizeof(size_text),
+ "BAD");
else
- snprintf(size_text, sizeof(size_text),
- "%lu", size);
+ xsnprintf(size_text, sizeof(size_text),
+ "%lu", size);
} else
- strcpy(size_text, "-");
+ xsnprintf(size_text, sizeof(size_text), "-");
printf("%06o %s %s %7s\t", mode, type,
find_unique_abbrev(sha1, abbrev),
size_text);
@@ -112,8 +108,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;
}
@@ -173,7 +173,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
* cannot be lifted until it is converted to use
* match_pathspec() or tree_entry_interesting()
*/
- parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE,
+ parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC &
+ ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL),
PATHSPEC_PREFER_CWD,
prefix, argv + 1);
for (i = 0; i < pathspec.nr; i++)
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index 2c3cd8eab7..e3b62f2fc7 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -6,1028 +6,49 @@
#include "builtin.h"
#include "utf8.h"
#include "strbuf.h"
+#include "mailinfo.h"
-static FILE *cmitmsg, *patchfile, *fin, *fout;
-
-static int keep_subject;
-static int keep_non_patch_brackets_in_subject;
-static const char *metainfo_charset;
-static struct strbuf line = STRBUF_INIT;
-static struct strbuf name = STRBUF_INIT;
-static struct strbuf email = STRBUF_INIT;
-
-static enum {
- TE_DONTCARE, TE_QP, TE_BASE64
-} transfer_encoding;
-
-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 use_inbody_headers = 1;
-
-#define MAX_HDR_PARSED 10
-#define MAX_BOUNDARIES 5
-
-static void cleanup_space(struct strbuf *sb);
-
-
-static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
-{
- struct strbuf *src = name;
- if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
- strchr(name->buf, '<') || strchr(name->buf, '>'))
- src = email;
- else if (name == out)
- return;
- strbuf_reset(out);
- strbuf_addbuf(out, src);
-}
-
-static void parse_bogus_from(const struct strbuf *line)
-{
- /* John Doe <johndoe> */
-
- char *bra, *ket;
- /* This is fallback, so do not bother if we already have an
- * e-mail address.
- */
- if (email.len)
- return;
-
- bra = strchr(line->buf, '<');
- if (!bra)
- return;
- ket = strchr(bra, '>');
- if (!ket)
- return;
-
- strbuf_reset(&email);
- strbuf_add(&email, bra + 1, ket - bra - 1);
-
- strbuf_reset(&name);
- strbuf_add(&name, line->buf, bra - line->buf);
- strbuf_trim(&name);
- get_sane_name(&name, &name, &email);
-}
-
-static void handle_from(const struct strbuf *from)
-{
- char *at;
- size_t el;
- struct strbuf f;
-
- strbuf_init(&f, from->len);
- strbuf_addbuf(&f, from);
-
- at = strchr(f.buf, '@');
- if (!at) {
- parse_bogus_from(from);
- return;
- }
-
- /*
- * If we already have one email, don't take any confusing lines
- */
- if (email.len && strchr(at + 1, '@')) {
- strbuf_release(&f);
- return;
- }
-
- /* Pick up the string around '@', possibly delimited with <>
- * pair; that is the email part.
- */
- while (at > f.buf) {
- char c = at[-1];
- if (isspace(c))
- break;
- if (c == '<') {
- at[-1] = ' ';
- break;
- }
- at--;
- }
- el = strcspn(at, " \n\t\r\v\f>");
- strbuf_reset(&email);
- strbuf_add(&email, at, el);
- strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
-
- /* The remainder is name. It could be
- *
- * - "John Doe <john.doe@xz>" (a), or
- * - "john.doe@xz (John Doe)" (b), or
- * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
- *
- * but we have removed the email part, so
- *
- * - remove extra spaces which could stay after email (case 'c'), and
- * - trim from both ends, possibly removing the () pair at the end
- * (cases 'a' and 'b').
- */
- cleanup_space(&f);
- strbuf_trim(&f);
- if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
- strbuf_remove(&f, 0, 1);
- strbuf_setlen(&f, f.len - 1);
- }
-
- get_sane_name(&name, &f, &email);
- strbuf_release(&f);
-}
-
-static void handle_header(struct strbuf **out, const struct strbuf *line)
-{
- if (!*out) {
- *out = xmalloc(sizeof(struct strbuf));
- strbuf_init(*out, line->len);
- } else
- strbuf_reset(*out);
-
- strbuf_addbuf(*out, line);
-}
-
-/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
- * to have enough heuristics to grok MIME encoded patches often found
- * on our mailing lists. For example, we do not even treat header lines
- * case insensitively.
- */
-
-static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
-{
- const char *ends, *ap = strcasestr(line, name);
- size_t sz;
-
- strbuf_setlen(attr, 0);
- if (!ap)
- return 0;
- ap += strlen(name);
- if (*ap == '"') {
- ap++;
- ends = "\"";
- }
- else
- ends = "; \t";
- sz = strcspn(ap, ends);
- strbuf_add(attr, ap, sz);
- return 1;
-}
-
-static struct strbuf *content[MAX_BOUNDARIES];
-
-static struct strbuf **content_top = content;
-
-static void handle_content_type(struct strbuf *line)
-{
- struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
- strbuf_init(boundary, line->len);
-
- if (slurp_attr(line->buf, "boundary=", boundary)) {
- strbuf_insert(boundary, 0, "--", 2);
- if (++content_top > &content[MAX_BOUNDARIES]) {
- fprintf(stderr, "Too many boundaries to handle\n");
- exit(1);
- }
- *content_top = boundary;
- boundary = NULL;
- }
- slurp_attr(line->buf, "charset=", &charset);
-
- if (boundary) {
- strbuf_release(boundary);
- free(boundary);
- }
-}
-
-static void handle_content_transfer_encoding(const struct strbuf *line)
-{
- if (strcasestr(line->buf, "base64"))
- transfer_encoding = TE_BASE64;
- else if (strcasestr(line->buf, "quoted-printable"))
- transfer_encoding = TE_QP;
- else
- transfer_encoding = TE_DONTCARE;
-}
-
-static int is_multipart_boundary(const struct strbuf *line)
-{
- return (((*content_top)->len <= line->len) &&
- !memcmp(line->buf, (*content_top)->buf, (*content_top)->len));
-}
-
-static void cleanup_subject(struct strbuf *subject)
-{
- size_t at = 0;
-
- while (at < subject->len) {
- char *pos;
- size_t remove;
-
- switch (subject->buf[at]) {
- case 'r': case 'R':
- if (subject->len <= at + 3)
- break;
- if ((subject->buf[at + 1] == 'e' ||
- subject->buf[at + 1] == 'E') &&
- subject->buf[at + 2] == ':') {
- strbuf_remove(subject, at, 3);
- continue;
- }
- at++;
- break;
- case ' ': case '\t': case ':':
- strbuf_remove(subject, at, 1);
- continue;
- case '[':
- pos = strchr(subject->buf + at, ']');
- if (!pos)
- break;
- remove = pos - subject->buf + at + 1;
- if (!keep_non_patch_brackets_in_subject ||
- (7 <= remove &&
- memmem(subject->buf + at, remove, "PATCH", 5)))
- strbuf_remove(subject, at, remove);
- 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;
- }
- strbuf_trim(subject);
-}
-
-static void cleanup_space(struct strbuf *sb)
-{
- size_t pos, cnt;
- for (pos = 0; pos < sb->len; pos++) {
- if (isspace(sb->buf[pos])) {
- sb->buf[pos] = ' ';
- for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
- strbuf_remove(sb, pos + 1, cnt);
- }
- }
-}
-
-static void decode_header(struct strbuf *line);
-static const char *header[MAX_HDR_PARSED] = {
- "From","Subject","Date",
-};
-
-static inline int cmp_header(const struct strbuf *line, const char *hdr)
-{
- int len = strlen(hdr);
- return !strncasecmp(line->buf, hdr, len) && line->len > len &&
- line->buf[len] == ':' && isspace(line->buf[len + 1]);
-}
-
-static int check_header(const struct strbuf *line,
- struct strbuf *hdr_data[], int overwrite)
-{
- int i, ret = 0, len;
- struct strbuf sb = STRBUF_INIT;
- /* search for the interesting parts */
- for (i = 0; header[i]; i++) {
- int len = strlen(header[i]);
- if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
- /* Unwrap inline B and Q encoding, and optionally
- * normalize the meta information to utf8.
- */
- strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
- decode_header(&sb);
- handle_header(&hdr_data[i], &sb);
- ret = 1;
- goto check_header_out;
- }
- }
-
- /* Content stuff */
- if (cmp_header(line, "Content-Type")) {
- len = strlen("Content-Type: ");
- strbuf_add(&sb, line->buf + len, line->len - len);
- decode_header(&sb);
- strbuf_insert(&sb, 0, "Content-Type: ", len);
- handle_content_type(&sb);
- ret = 1;
- goto check_header_out;
- }
- if (cmp_header(line, "Content-Transfer-Encoding")) {
- len = strlen("Content-Transfer-Encoding: ");
- strbuf_add(&sb, line->buf + len, line->len - len);
- decode_header(&sb);
- handle_content_transfer_encoding(&sb);
- ret = 1;
- goto check_header_out;
- }
-
- /* for inbody stuff */
- if (starts_with(line->buf, ">From") && isspace(line->buf[5])) {
- ret = 1; /* Should this return 0? */
- goto check_header_out;
- }
- if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
- for (i = 0; header[i]; i++) {
- if (!memcmp("Subject", header[i], 7)) {
- handle_header(&hdr_data[i], line);
- ret = 1;
- goto check_header_out;
- }
- }
- }
-
-check_header_out:
- strbuf_release(&sb);
- return ret;
-}
-
-static int is_rfc2822_header(const struct strbuf *line)
-{
- /*
- * The section that defines the loosest possible
- * field name is "3.6.8 Optional fields".
- *
- * optional-field = field-name ":" unstructured CRLF
- * field-name = 1*ftext
- * ftext = %d33-57 / %59-126
- */
- int ch;
- char *cp = line->buf;
-
- /* Count mbox From headers as headers */
- if (starts_with(cp, "From ") || starts_with(cp, ">From "))
- return 1;
-
- while ((ch = *cp++)) {
- if (ch == ':')
- return 1;
- if ((33 <= ch && ch <= 57) ||
- (59 <= ch && ch <= 126))
- continue;
- break;
- }
- return 0;
-}
-
-static int read_one_header_line(struct strbuf *line, FILE *in)
-{
- /* Get the first part of the line. */
- if (strbuf_getline(line, in, '\n'))
- return 0;
-
- /*
- * Is it an empty line or not a valid rfc2822 header?
- * If so, stop here, and return false ("not a header")
- */
- strbuf_rtrim(line);
- if (!line->len || !is_rfc2822_header(line)) {
- /* Re-add the newline */
- strbuf_addch(line, '\n');
- return 0;
- }
-
- /*
- * Now we need to eat all the continuation lines..
- * Yuck, 2822 header "folding"
- */
- for (;;) {
- int peek;
- struct strbuf continuation = STRBUF_INIT;
-
- peek = fgetc(in); ungetc(peek, in);
- if (peek != ' ' && peek != '\t')
- break;
- if (strbuf_getline(&continuation, in, '\n'))
- break;
- continuation.buf[0] = ' ';
- strbuf_rtrim(&continuation);
- strbuf_addbuf(line, &continuation);
- }
-
- return 1;
-}
-
-static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
-{
- const char *in = q_seg->buf;
- int c;
- struct strbuf *out = xmalloc(sizeof(struct strbuf));
- strbuf_init(out, q_seg->len);
-
- while ((c = *in++) != 0) {
- if (c == '=') {
- int d = *in++;
- if (d == '\n' || !d)
- break; /* drop trailing newline */
- strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
- continue;
- }
- if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
- c = 0x20;
- strbuf_addch(out, c);
- }
- return out;
-}
-
-static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
-{
- /* Decode in..ep, possibly in-place to ot */
- int c, pos = 0, acc = 0;
- const char *in = b_seg->buf;
- struct strbuf *out = xmalloc(sizeof(struct strbuf));
- strbuf_init(out, b_seg->len);
-
- while ((c = *in++) != 0) {
- if (c == '+')
- c = 62;
- else if (c == '/')
- c = 63;
- else if ('A' <= c && c <= 'Z')
- c -= 'A';
- else if ('a' <= c && c <= 'z')
- c -= 'a' - 26;
- else if ('0' <= c && c <= '9')
- c -= '0' - 52;
- else
- continue; /* garbage */
- switch (pos++) {
- case 0:
- acc = (c << 2);
- break;
- case 1:
- strbuf_addch(out, (acc | (c >> 4)));
- acc = (c & 15) << 4;
- break;
- case 2:
- strbuf_addch(out, (acc | (c >> 2)));
- acc = (c & 3) << 6;
- break;
- case 3:
- strbuf_addch(out, (acc | c));
- acc = pos = 0;
- break;
- }
- }
- return out;
-}
-
-static void convert_to_utf8(struct strbuf *line, const char *charset)
-{
- char *out;
-
- if (!charset || !*charset)
- return;
-
- if (same_encoding(metainfo_charset, charset))
- return;
- out = reencode_string(line->buf, metainfo_charset, charset);
- if (!out)
- die("cannot convert from %s to %s",
- charset, metainfo_charset);
- strbuf_attach(line, out, strlen(out), strlen(out));
-}
-
-static int decode_header_bq(struct strbuf *it)
-{
- char *in, *ep, *cp;
- struct strbuf outbuf = STRBUF_INIT, *dec;
- struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
- int rfc2047 = 0;
-
- in = it->buf;
- while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
- int encoding;
- strbuf_reset(&charset_q);
- strbuf_reset(&piecebuf);
- rfc2047 = 1;
-
- if (in != ep) {
- /*
- * We are about to process an encoded-word
- * that begins at ep, but there is something
- * before the encoded word.
- */
- char *scan;
- for (scan = in; scan < ep; scan++)
- if (!isspace(*scan))
- break;
-
- if (scan != ep || in == it->buf) {
- /*
- * We should not lose that "something",
- * unless we have just processed an
- * encoded-word, and there is only LWS
- * before the one we are about to process.
- */
- strbuf_add(&outbuf, in, ep - in);
- }
- }
- /* E.g.
- * ep : "=?iso-2022-jp?B?GyR...?= foo"
- * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
- */
- ep += 2;
-
- if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
- goto decode_header_bq_out;
-
- if (cp + 3 - it->buf > it->len)
- goto decode_header_bq_out;
- strbuf_add(&charset_q, ep, cp - ep);
-
- encoding = cp[1];
- if (!encoding || cp[2] != '?')
- goto decode_header_bq_out;
- ep = strstr(cp + 3, "?=");
- if (!ep)
- goto decode_header_bq_out;
- strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
- switch (tolower(encoding)) {
- default:
- goto decode_header_bq_out;
- case 'b':
- dec = decode_b_segment(&piecebuf);
- break;
- case 'q':
- dec = decode_q_segment(&piecebuf, 1);
- break;
- }
- if (metainfo_charset)
- convert_to_utf8(dec, charset_q.buf);
-
- strbuf_addbuf(&outbuf, dec);
- strbuf_release(dec);
- free(dec);
- in = ep + 2;
- }
- strbuf_addstr(&outbuf, in);
- strbuf_reset(it);
- strbuf_addbuf(it, &outbuf);
-decode_header_bq_out:
- strbuf_release(&outbuf);
- strbuf_release(&charset_q);
- strbuf_release(&piecebuf);
- return rfc2047;
-}
-
-static void decode_header(struct strbuf *it)
-{
- if (decode_header_bq(it))
- return;
- /* otherwise "it" is a straight copy of the input.
- * This can be binary guck but there is no charset specified.
- */
- if (metainfo_charset)
- convert_to_utf8(it, "");
-}
-
-static void decode_transfer_encoding(struct strbuf *line)
-{
- struct strbuf *ret;
-
- switch (transfer_encoding) {
- case TE_QP:
- ret = decode_q_segment(line, 0);
- break;
- case TE_BASE64:
- ret = decode_b_segment(line);
- break;
- case TE_DONTCARE:
- default:
- return;
- }
- strbuf_reset(line);
- strbuf_addbuf(line, ret);
- strbuf_release(ret);
- free(ret);
-}
-
-static void handle_filter(struct strbuf *line);
-
-static int find_boundary(void)
-{
- while (!strbuf_getline(&line, fin, '\n')) {
- if (*content_top && is_multipart_boundary(&line))
- return 1;
- }
- return 0;
-}
-
-static int handle_boundary(void)
-{
- struct strbuf newline = STRBUF_INIT;
-
- strbuf_addch(&newline, '\n');
-again:
- if (line.len >= (*content_top)->len + 2 &&
- !memcmp(line.buf + (*content_top)->len, "--", 2)) {
- /* we hit an end boundary */
- /* pop the current boundary off the stack */
- strbuf_release(*content_top);
- free(*content_top);
- *content_top = NULL;
-
- /* technically won't happen as is_multipart_boundary()
- will fail first. But just in case..
- */
- if (--content_top < content) {
- fprintf(stderr, "Detected mismatched boundaries, "
- "can't recover\n");
- exit(1);
- }
- handle_filter(&newline);
- strbuf_release(&newline);
-
- /* skip to the next boundary */
- if (!find_boundary())
- return 0;
- goto again;
- }
-
- /* set some defaults */
- transfer_encoding = TE_DONTCARE;
- strbuf_reset(&charset);
-
- /* slurp in this section's info */
- while (read_one_header_line(&line, fin))
- check_header(&line, p_hdr_data, 0);
-
- strbuf_release(&newline);
- /* replenish line */
- if (strbuf_getline(&line, fin, '\n'))
- return 0;
- strbuf_addch(&line, '\n');
- return 1;
-}
-
-static inline int patchbreak(const struct strbuf *line)
-{
- size_t i;
-
- /* Beginning of a "diff -" header? */
- if (starts_with(line->buf, "diff -"))
- return 1;
-
- /* CVS "Index: " line? */
- if (starts_with(line->buf, "Index: "))
- return 1;
-
- /*
- * "--- <filename>" starts patches without headers
- * "---<sp>*" is a manual separator
- */
- if (line->len < 4)
- return 0;
-
- if (starts_with(line->buf, "---")) {
- /* space followed by a filename? */
- if (line->buf[3] == ' ' && !isspace(line->buf[4]))
- return 1;
- /* Just whitespace? */
- for (i = 3; i < line->len; i++) {
- unsigned char c = line->buf[i];
- if (c == '\n')
- return 1;
- if (!isspace(c))
- break;
- }
- return 0;
- }
- return 0;
-}
-
-static int is_scissors_line(const struct strbuf *line)
-{
- size_t i, len = line->len;
- int scissors = 0, gap = 0;
- int first_nonblank = -1;
- int last_nonblank = 0, visible, perforation = 0, in_perforation = 0;
- const char *buf = line->buf;
-
- for (i = 0; i < len; i++) {
- if (isspace(buf[i])) {
- if (in_perforation) {
- perforation++;
- gap++;
- }
- continue;
- }
- last_nonblank = i;
- if (first_nonblank < 0)
- first_nonblank = i;
- if (buf[i] == '-') {
- in_perforation = 1;
- perforation++;
- continue;
- }
- if (i + 1 < len &&
- (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) ||
- !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) {
- in_perforation = 1;
- perforation += 2;
- scissors += 2;
- i++;
- continue;
- }
- in_perforation = 0;
- }
-
- /*
- * The mark must be at least 8 bytes long (e.g. "-- >8 --").
- * Even though there can be arbitrary cruft on the same line
- * (e.g. "cut here"), in order to avoid misidentification, the
- * perforation must occupy more than a third of the visible
- * width of the line, and dashes and scissors must occupy more
- * than half of the perforation.
- */
-
- visible = last_nonblank - first_nonblank + 1;
- return (scissors && 8 <= visible &&
- visible < perforation * 3 &&
- gap * 2 < perforation);
-}
-
-static int handle_commit_msg(struct strbuf *line)
-{
- static int still_looking = 1;
-
- if (!cmitmsg)
- return 0;
-
- if (still_looking) {
- if (!line->len || (line->len == 1 && line->buf[0] == '\n'))
- return 0;
- }
-
- if (use_inbody_headers && still_looking) {
- still_looking = check_header(line, s_hdr_data, 0);
- if (still_looking)
- return 0;
- } else
- /* Only trim the first (blank) line of the commit message
- * when ignoring in-body headers.
- */
- still_looking = 0;
-
- /* normalize the log message to UTF-8. */
- if (metainfo_charset)
- convert_to_utf8(line, charset.buf);
-
- if (use_scissors && is_scissors_line(line)) {
- int i;
- if (fseek(cmitmsg, 0L, SEEK_SET))
- die_errno("Could not rewind output message file");
- if (ftruncate(fileno(cmitmsg), 0))
- die_errno("Could not truncate output message file at scissors");
- still_looking = 1;
-
- /*
- * We may have already read "secondary headers"; purge
- * them to give ourselves a clean restart.
- */
- for (i = 0; header[i]; i++) {
- if (s_hdr_data[i])
- strbuf_release(s_hdr_data[i]);
- s_hdr_data[i] = NULL;
- }
- return 0;
- }
-
- if (patchbreak(line)) {
- fclose(cmitmsg);
- cmitmsg = NULL;
- return 1;
- }
-
- fputs(line->buf, cmitmsg);
- return 0;
-}
-
-static void handle_patch(const struct strbuf *line)
-{
- fwrite(line->buf, 1, line->len, patchfile);
- patch_lines++;
-}
-
-static void handle_filter(struct strbuf *line)
-{
- static int filter = 0;
-
- /* filter tells us which part we left off on */
- switch (filter) {
- case 0:
- if (!handle_commit_msg(line))
- break;
- filter++;
- case 1:
- handle_patch(line);
- break;
- }
-}
-
-static void handle_body(void)
-{
- struct strbuf prev = STRBUF_INIT;
-
- /* Skip up to the first boundary */
- if (*content_top) {
- if (!find_boundary())
- goto handle_body_out;
- }
-
- do {
- /* process any boundary lines */
- if (*content_top && is_multipart_boundary(&line)) {
- /* flush any leftover */
- if (prev.len) {
- handle_filter(&prev);
- strbuf_reset(&prev);
- }
- if (!handle_boundary())
- goto handle_body_out;
- }
-
- /* Unwrap transfer encoding */
- decode_transfer_encoding(&line);
-
- switch (transfer_encoding) {
- case TE_BASE64:
- case TE_QP:
- {
- struct strbuf **lines, **it, *sb;
-
- /* Prepend any previous partial lines */
- strbuf_insert(&line, 0, prev.buf, prev.len);
- strbuf_reset(&prev);
-
- /*
- * This is a decoded line that may contain
- * multiple new lines. Pass only one chunk
- * at a time to handle_filter()
- */
- lines = strbuf_split(&line, '\n');
- for (it = lines; (sb = *it); it++) {
- if (*(it + 1) == NULL) /* The last line */
- if (sb->buf[sb->len - 1] != '\n') {
- /* Partial line, save it for later. */
- strbuf_addbuf(&prev, sb);
- break;
- }
- handle_filter(sb);
- }
- /*
- * The partial chunk is saved in "prev" and will be
- * appended by the next iteration of read_line_with_nul().
- */
- strbuf_list_free(lines);
- break;
- }
- default:
- handle_filter(&line);
- }
-
- } while (!strbuf_getwholeline(&line, fin, '\n'));
-
-handle_body_out:
- strbuf_release(&prev);
-}
-
-static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
-{
- const char *sp = data->buf;
- while (1) {
- char *ep = strchr(sp, '\n');
- int len;
- if (!ep)
- len = strlen(sp);
- else
- len = ep - sp;
- fprintf(fout, "%s: %.*s\n", hdr, len, sp);
- if (!ep)
- break;
- sp = ep + 1;
- }
-}
-
-static void handle_info(void)
-{
- struct strbuf *hdr;
- int i;
-
- for (i = 0; header[i]; i++) {
- /* only print inbody headers if we output a patch file */
- if (patch_lines && s_hdr_data[i])
- hdr = s_hdr_data[i];
- else if (p_hdr_data[i])
- hdr = p_hdr_data[i];
- else
- continue;
-
- if (!memcmp(header[i], "Subject", 7)) {
- if (!keep_subject) {
- cleanup_subject(hdr);
- cleanup_space(hdr);
- }
- output_header_lines(fout, "Subject", hdr);
- } else if (!memcmp(header[i], "From", 4)) {
- cleanup_space(hdr);
- handle_from(hdr);
- fprintf(fout, "Author: %s\n", name.buf);
- fprintf(fout, "Email: %s\n", email.buf);
- } else {
- cleanup_space(hdr);
- fprintf(fout, "%s: %s\n", header[i], hdr->buf);
- }
- }
- fprintf(fout, "\n");
-}
-
-static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch)
-{
- int peek;
- fin = in;
- fout = out;
-
- cmitmsg = fopen(msg, "w");
- if (!cmitmsg) {
- perror(msg);
- return -1;
- }
- patchfile = fopen(patch, "w");
- if (!patchfile) {
- perror(patch);
- fclose(cmitmsg);
- return -1;
- }
-
- p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
- s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
-
- do {
- peek = fgetc(in);
- } while (isspace(peek));
- ungetc(peek, in);
-
- /* process the email header */
- while (read_one_header_line(&line, fin))
- check_header(&line, p_hdr_data, 1);
-
- handle_body();
- handle_info();
-
- return 0;
-}
+static const char mailinfo_usage[] =
+ "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info";
-static int git_mailinfo_config(const char *var, const char *value, void *unused)
+static char *prefix_copy(const char *prefix, const char *filename)
{
- if (!starts_with(var, "mailinfo."))
- return git_default_config(var, value, unused);
- if (!strcmp(var, "mailinfo.scissors")) {
- use_scissors = git_config_bool(var, value);
- return 0;
- }
- /* perhaps others here */
- return 0;
+ if (!prefix || is_absolute_path(filename))
+ return xstrdup(filename);
+ return xstrdup(prefix_filename(prefix, strlen(prefix), filename));
}
-static const char mailinfo_usage[] =
- "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
-
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
{
const char *def_charset;
+ struct mailinfo mi;
+ int status;
+ char *msgfile, *patchfile;
- /* NEEDSWORK: might want to do the optional .git/ directory
- * discovery
- */
- git_config(git_mailinfo_config, NULL);
+ setup_mailinfo(&mi);
def_charset = get_commit_output_encoding();
- metainfo_charset = def_charset;
+ mi.metainfo_charset = def_charset;
while (1 < argc && argv[1][0] == '-') {
if (!strcmp(argv[1], "-k"))
- keep_subject = 1;
+ mi.keep_subject = 1;
else if (!strcmp(argv[1], "-b"))
- keep_non_patch_brackets_in_subject = 1;
+ mi.keep_non_patch_brackets_in_subject = 1;
+ else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id"))
+ mi.add_message_id = 1;
else if (!strcmp(argv[1], "-u"))
- metainfo_charset = def_charset;
+ mi.metainfo_charset = def_charset;
else if (!strcmp(argv[1], "-n"))
- metainfo_charset = NULL;
+ mi.metainfo_charset = NULL;
else if (starts_with(argv[1], "--encoding="))
- metainfo_charset = argv[1] + 11;
+ mi.metainfo_charset = argv[1] + 11;
else if (!strcmp(argv[1], "--scissors"))
- use_scissors = 1;
+ mi.use_scissors = 1;
else if (!strcmp(argv[1], "--no-scissors"))
- use_scissors = 0;
+ mi.use_scissors = 0;
else if (!strcmp(argv[1], "--no-inbody-headers"))
- use_inbody_headers = 0;
+ mi.use_inbody_headers = 0;
else
usage(mailinfo_usage);
argc--; argv++;
@@ -1036,5 +57,16 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
if (argc != 3)
usage(mailinfo_usage);
- return !!mailinfo(stdin, stdout, argv[1], argv[2]);
+ mi.input = stdin;
+ mi.output = stdout;
+
+ msgfile = prefix_copy(prefix, argv[1]);
+ patchfile = prefix_copy(prefix, argv[2]);
+
+ status = !!mailinfo(&mi, msgfile, patchfile);
+ clear_mailinfo(&mi);
+
+ free(msgfile);
+ free(patchfile);
+ return status;
}
diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c
index 06296d4bdf..30681681c1 100644
--- a/builtin/mailsplit.c
+++ b/builtin/mailsplit.c
@@ -45,6 +45,19 @@ static int is_from_line(const char *line, int len)
static struct strbuf buf = STRBUF_INIT;
static int keep_cr;
+static int mboxrd;
+
+static int is_gtfrom(const struct strbuf *buf)
+{
+ size_t min = strlen(">From ");
+ size_t ngt;
+
+ if (buf->len < min)
+ return 0;
+
+ ngt = strspn(buf->buf, ">");
+ return ngt && starts_with(buf->buf + ngt, "From ");
+}
/* Called with the first line (potentially partial)
* already in buf[] -- normally that should begin with
@@ -53,14 +66,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);
@@ -76,6 +90,9 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
strbuf_addch(&buf, '\n');
}
+ if (mboxrd && is_gtfrom(&buf))
+ strbuf_remove(&buf, 0, 1);
+
if (fwrite(buf.buf, 1, buf.len, output) != buf.len)
die_errno("cannot write output");
@@ -91,43 +108,43 @@ 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)
{
DIR *dir;
struct dirent *dent;
- char name[PATH_MAX];
+ char *name = NULL;
char *subs[] = { "cur", "new", NULL };
char **sub;
+ int ret = -1;
for (sub = subs; *sub; ++sub) {
- snprintf(name, sizeof(name), "%s/%s", path, *sub);
+ free(name);
+ name = xstrfmt("%s/%s", path, *sub);
if ((dir = opendir(name)) == NULL) {
if (errno == ENOENT)
continue;
- error("cannot opendir %s (%s)", name, strerror(errno));
- return -1;
+ error_errno("cannot opendir %s", name);
+ goto out;
}
while ((dent = readdir(dir)) != NULL) {
if (dent->d_name[0] == '.')
continue;
- snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
+ free(name);
+ name = xstrfmt("%s/%s", *sub, dent->d_name);
string_list_insert(list, name);
}
closedir(dir);
}
- return 0;
+ ret = 0;
+
+out:
+ free(name);
+ return ret;
}
static int maildir_filename_cmp(const char *a, const char *b)
@@ -154,8 +171,8 @@ static int maildir_filename_cmp(const char *a, const char *b)
static int split_maildir(const char *maildir, const char *dir,
int nr_prec, int skip)
{
- char file[PATH_MAX];
- char name[PATH_MAX];
+ char *file = NULL;
+ FILE *f = NULL;
int ret = -1;
int i;
struct string_list list = STRING_LIST_INIT_DUP;
@@ -166,27 +183,35 @@ static int split_maildir(const char *maildir, const char *dir,
goto out;
for (i = 0; i < list.nr; i++) {
- FILE *f;
- snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string);
+ char *name;
+
+ free(file);
+ file = xstrfmt("%s/%s", maildir, list.items[i].string);
+
f = fopen(file, "r");
if (!f) {
- error("cannot open mail %s (%s)", file, strerror(errno));
+ error_errno("cannot open mail %s", file);
goto out;
}
if (strbuf_getwholeline(&buf, f, '\n')) {
- error("cannot read mail %s (%s)", file, strerror(errno));
+ error_errno("cannot read mail %s", file);
goto out;
}
- sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+ name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
split_one(f, name, 1);
+ free(name);
fclose(f);
+ f = NULL;
}
ret = skip;
out:
+ if (f)
+ fclose(f);
+ free(file);
string_list_clear(&list, 1);
return ret;
}
@@ -194,7 +219,6 @@ out:
static int split_mbox(const char *file, const char *dir, int allow_bare,
int nr_prec, int skip)
{
- char name[PATH_MAX];
int ret = -1;
int peek;
@@ -202,7 +226,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
int file_done = 0;
if (!f) {
- error("cannot open mbox %s", file);
+ error_errno("cannot open mbox %s", file);
goto out;
}
@@ -221,8 +245,9 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
}
while (!file_done) {
- sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+ char *name = xstrfmt("%s/%0*d", dir, nr_prec, ++skip);
file_done = split_one(f, name, allow_bare);
+ free(name);
}
if (f != stdin)
@@ -262,6 +287,8 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix)
keep_cr = 1;
} else if ( arg[1] == 'o' && arg[2] ) {
dir = arg+2;
+ } else if (!strcmp(arg, "--mboxrd")) {
+ mboxrd = 1;
} else if ( arg[1] == '-' && !arg[2] ) {
argp++; /* -- marks end of options */
break;
@@ -309,7 +336,7 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix)
}
if (stat(arg, &argstat) == -1) {
- error("cannot stat %s (%s)", arg, strerror(errno));
+ error_errno("cannot stat %s", arg);
return 1;
}
diff --git a/builtin/merge-base.c b/builtin/merge-base.c
index 0ecde8da30..b572a37c26 100644
--- a/builtin/merge-base.c
+++ b/builtin/merge-base.c
@@ -10,13 +10,13 @@ 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;
while (result) {
- printf("%s\n", sha1_to_hex(result->item->object.sha1));
+ printf("%s\n", oid_to_hex(&result->item->object.oid));
if (!show_all)
return 0;
result = result->next;
@@ -26,8 +26,8 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
}
static const char * const merge_base_usage[] = {
- N_("git merge-base [-a|--all] <commit> <commit>..."),
- N_("git merge-base [-a|--all] --octopus <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>]"),
@@ -62,7 +62,7 @@ static int handle_independent(int count, const char **args)
return 1;
while (result) {
- printf("%s\n", sha1_to_hex(result->item->object.sha1));
+ printf("%s\n", oid_to_hex(&result->item->object.oid));
result = result->next;
}
return 0;
@@ -83,7 +83,7 @@ static int handle_octopus(int count, const char **args, int show_all)
return 1;
while (result) {
- printf("%s\n", sha1_to_hex(result->item->object.sha1));
+ printf("%s\n", oid_to_hex(&result->item->object.oid));
if (!show_all)
return 0;
result = result->next;
@@ -173,10 +173,13 @@ static int handle_fork_point(int argc, const char **argv)
revs.initial = 1;
for_each_reflog_ent(refname, collect_one_reflog_ent, &revs);
+ if (!revs.nr && !get_sha1(refname, sha1))
+ add_one_commit(sha1, &revs);
+
for (i = 0; i < revs.nr; i++)
revs.commit[i]->object.flags &= ~TMP_MARK;
- bases = get_merge_bases_many(derived, revs.nr, revs.commit, 0);
+ bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit);
/*
* There should be one and only one merge base, when we found
@@ -196,7 +199,7 @@ static int handle_fork_point(int argc, const char **argv)
goto cleanup_return;
}
- printf("%s\n", sha1_to_hex(bases->item->object.sha1));
+ printf("%s\n", oid_to_hex(&bases->item->object.oid));
cleanup_return:
free_commit_list(bases);
@@ -252,7 +255,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix)
if (argc < 2)
usage_with_options(merge_base_usage, options);
- rev = xmalloc(argc * sizeof(*rev));
+ ALLOC_ARRAY(rev, argc);
while (argc-- > 0)
rev[rev_nr++] = get_commit_reference(*argv++);
return show_merge_base(rev, rev_nr, show_all);
diff --git a/builtin/merge-file.c b/builtin/merge-file.c
index 844f84f40b..13e22a2f0b 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[] = {
- N_("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
};
@@ -42,7 +42,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
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),
+ N_("set labels for file1/orig-file/file2"), &label_cb),
OPT_END(),
};
@@ -62,8 +62,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
usage_with_options(merge_file_usage, options);
if (quiet) {
if (!freopen("/dev/null", "w", stderr))
- return error("failed to redirect stderr to /dev/null: "
- "%s", strerror(errno));
+ return error_errno("failed to redirect stderr to /dev/null");
}
if (prefix)
@@ -75,7 +74,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
names[i] = argv[i];
if (read_mmfile(mmfs + i, fname))
return -1;
- if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
+ if (mmfs[i].size > MAX_XDIFF_SIZE ||
+ buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
return error("Cannot merge binary files: %s",
argv[i]);
}
@@ -90,17 +90,22 @@ 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);
+ ret = error_errno("Could not open %s for writing",
+ filename);
else if (result.size &&
fwrite(result.ptr, result.size, 1, f) != 1)
- ret = error("Could not write to %s", filename);
+ ret = error_errno("Could not write to %s", filename);
else if (fclose(f))
- ret = error("Could not close %s", filename);
+ ret = error_errno("Could not close %s", filename);
free(result.ptr);
}
+ if (ret > 127)
+ ret = 127;
+
return ret;
}
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
index b416d92849..2d1b6db6bd 100644
--- a/builtin/merge-index.c
+++ b/builtin/merge-index.c
@@ -9,7 +9,7 @@ static int merge_entry(int pos, const char *path)
{
int found;
const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL };
- char hexbuf[4][60];
+ char hexbuf[4][GIT_SHA1_HEXSZ + 1];
char ownbuf[4][60];
if (pos >= active_nr)
@@ -22,8 +22,8 @@ static int merge_entry(int pos, const char *path)
if (strcmp(ce->name, path))
break;
found++;
- strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
- sprintf(ownbuf[stage], "%o", ce->ce_mode);
+ oid_to_hex_r(hexbuf[stage], &ce->oid);
+ xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode);
arguments[stage] = hexbuf[stage];
arguments[stage + 4] = ownbuf[stage];
} while (++pos < active_nr);
@@ -75,7 +75,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)
signal(SIGCHLD, SIG_DFL);
if (argc < 3)
- usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)");
+ usage("git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])");
read_cache();
diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
index a90f28f34d..0dd9021958 100644
--- a/builtin/merge-recursive.c
+++ b/builtin/merge-recursive.c
@@ -9,22 +9,22 @@ static const char builtin_merge_recursive_usage[] =
static const char *better_branch_name(const char *branch)
{
- static char githead_env[8 + 40 + 1];
+ static char githead_env[8 + GIT_SHA1_HEXSZ + 1];
char *name;
- if (strlen(branch) != 40)
+ if (strlen(branch) != GIT_SHA1_HEXSZ)
return branch;
- sprintf(githead_env, "GITHEAD_%s", branch);
+ xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch);
name = getenv(githead_env);
return name ? name : branch;
}
int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
{
- const unsigned char *bases[21];
+ const struct object_id *bases[21];
unsigned bases_count = 0;
int i, failed;
- unsigned char h1[20], h2[20];
+ struct object_id h1, h2;
struct merge_options o;
struct commit *result;
@@ -42,38 +42,41 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
if (!arg[2])
break;
if (parse_merge_opt(&o, arg + 2))
- die("Unknown option %s", arg);
+ die(_("unknown option %s"), arg);
continue;
}
if (bases_count < ARRAY_SIZE(bases)-1) {
- unsigned char *sha = xmalloc(20);
- if (get_sha1(argv[i], sha))
- die("Could not parse object '%s'", argv[i]);
- bases[bases_count++] = sha;
+ struct object_id *oid = xmalloc(sizeof(struct object_id));
+ if (get_oid(argv[i], oid))
+ die(_("could not parse object '%s'"), argv[i]);
+ bases[bases_count++] = oid;
}
else
- warning("Cannot handle more than %d bases. "
- "Ignoring %s.",
+ warning(Q_("cannot handle more than %d base. "
+ "Ignoring %s.",
+ "cannot handle more than %d bases. "
+ "Ignoring %s.",
+ (int)ARRAY_SIZE(bases)-1),
(int)ARRAY_SIZE(bases)-1, argv[i]);
}
if (argc - i != 3) /* "--" "<head>" "<remote>" */
- die("Not handling anything other than two heads merge.");
+ die(_("not handling anything other than two heads merge."));
o.branch1 = argv[++i];
o.branch2 = argv[++i];
- if (get_sha1(o.branch1, h1))
- die("Could not resolve ref '%s'", o.branch1);
- if (get_sha1(o.branch2, h2))
- die("Could not resolve ref '%s'", o.branch2);
+ if (get_oid(o.branch1, &h1))
+ die(_("could not resolve ref '%s'"), o.branch1);
+ if (get_oid(o.branch2, &h2))
+ die(_("could not resolve ref '%s'"), o.branch2);
o.branch1 = better_branch_name(o.branch1);
o.branch2 = better_branch_name(o.branch2);
if (o.verbosity >= 3)
- printf("Merging %s with %s\n", o.branch1, o.branch2);
+ printf(_("Merging %s with %s\n"), o.branch1, o.branch2);
- failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result);
+ failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result);
if (failed < 0)
return 128; /* die() error code */
return failed;
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c
index 61cbde4094..5b7ab9b967 100644
--- a/builtin/merge-tree.c
+++ b/builtin/merge-tree.c
@@ -25,7 +25,7 @@ static void add_merge_entry(struct merge_list *entry)
merge_result_end = &entry->next;
}
-static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict);
+static void merge_trees(struct tree_desc t[3], const char *base);
static const char *explanation(struct merge_list *entry)
{
@@ -60,7 +60,7 @@ static void *result(struct merge_list *entry, unsigned long *size)
const char *path = entry->path;
if (!entry->stage)
- return read_sha1_file(entry->blob->object.sha1, &type, size);
+ return read_sha1_file(entry->blob->object.oid.hash, &type, size);
base = NULL;
if (entry->stage == 1) {
base = entry->blob;
@@ -82,7 +82,7 @@ static void *origin(struct merge_list *entry, unsigned long *size)
enum object_type type;
while (entry) {
if (entry->stage == 2)
- return read_sha1_file(entry->blob->object.sha1, &type, size);
+ return read_sha1_file(entry->blob->object.oid.hash, &type, size);
entry = entry->link;
}
return NULL;
@@ -118,7 +118,8 @@ static void show_diff(struct merge_list *entry)
if (!dst.ptr)
size = 0;
dst.size = size;
- xdi_diff(&src, &dst, &xpp, &xecfg, &ecb);
+ if (xdi_diff(&src, &dst, &xpp, &xecfg, &ecb))
+ die("unable to generate diff");
free(src.ptr);
free(dst.ptr);
}
@@ -129,7 +130,7 @@ static void show_result_list(struct merge_list *entry)
do {
struct merge_list *link = entry->link;
static const char *desc[4] = { "result", "base", "our", "their" };
- printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path);
+ printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, oid_to_hex(&entry->blob->object.oid), entry->path);
entry = link;
} while (entry);
}
@@ -149,15 +150,15 @@ static void show_result(void)
/* An empty entry never compares same, not even to another empty entry */
static int same_entry(struct name_entry *a, struct name_entry *b)
{
- return a->sha1 &&
- b->sha1 &&
- !hashcmp(a->sha1, b->sha1) &&
+ return a->oid &&
+ b->oid &&
+ !oidcmp(a->oid, b->oid) &&
a->mode == b->mode;
}
static int both_empty(struct name_entry *a, struct name_entry *b)
{
- return !(a->sha1 || b->sha1);
+ return !(a->oid || b->oid);
}
static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path)
@@ -173,7 +174,7 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsi
static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
{
- char *path = xmalloc(traverse_path_len(info, n) + 1);
+ char *path = xmallocz(traverse_path_len(info, n));
return make_traverse_path(path, info, n);
}
@@ -187,16 +188,16 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s
return;
path = traverse_path(info, result);
- orig = create_entry(2, ours->mode, ours->sha1, path);
- final = create_entry(0, result->mode, result->sha1, path);
+ orig = create_entry(2, ours->mode, ours->oid->hash, path);
+ final = create_entry(0, result->mode, result->oid->hash, path);
final->link = orig;
add_merge_entry(final);
}
-static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3],
- int df_conflict)
+static void unresolved_directory(const struct traverse_info *info,
+ struct name_entry n[3])
{
char *newbase;
struct name_entry *p;
@@ -212,13 +213,13 @@ static void unresolved_directory(const struct traverse_info *info, struct name_e
newbase = traverse_path(info, p);
-#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->sha1 : NULL)
+#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid->hash : 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_recursive(t, newbase, df_conflict);
+ merge_trees(t, newbase);
free(buf0);
free(buf1);
@@ -238,7 +239,7 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info
path = entry->path;
else
path = traverse_path(info, n);
- link = create_entry(stage, n->mode, n->sha1, path);
+ link = create_entry(stage, n->mode, n->oid->hash, path);
link->link = entry;
return link;
}
@@ -259,7 +260,7 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3])
dirmask |= (1 << i);
}
- unresolved_directory(info, n, dirmask && (dirmask != mask));
+ unresolved_directory(info, n);
if (dirmask == mask)
return;
@@ -313,7 +314,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s
}
if (same_entry(entry+0, entry+1)) {
- if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
+ if (entry[2].oid && !S_ISDIR(entry[2].mode)) {
/* We did not touch, they modified -- take theirs */
resolve(info, entry+1, entry+2);
return mask;
@@ -335,21 +336,15 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s
return mask;
}
-static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict)
+static void merge_trees(struct tree_desc t[3], const char *base)
{
struct traverse_info info;
setup_traverse_info(&info, base);
- info.data = &df_conflict;
info.fn = threeway_callback;
traverse_trees(3, t, &info);
}
-static void merge_trees(struct tree_desc t[3], const char *base)
-{
- merge_trees_recursive(t, base, 0);
-}
-
static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
{
unsigned char sha1[20];
diff --git a/builtin/merge.c b/builtin/merge.c
index f0cf1205fa..a96d4fb501 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"
@@ -28,6 +29,8 @@
#include "remote.h"
#include "fmt-merge-msg.h"
#include "gpg-interface.h"
+#include "sequencer.h"
+#include "string-list.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -40,9 +43,10 @@ struct strategy {
};
static const char * const builtin_merge_usage[] = {
- N_("git merge [options] [<commit>...]"),
- N_("git merge [options] <msg> HEAD <commit>"),
+ N_("git merge [<options>] [<commit>...]"),
+ N_("git merge [<options>] <msg> HEAD <commit>"),
N_("git merge --abort"),
+ N_("git merge --continue"),
NULL
};
@@ -62,8 +66,10 @@ static int option_renormalize;
static int verbosity;
static int allow_rerere_auto;
static int abort_current_merge;
+static int continue_current_merge;
+static int allow_unrelated_histories;
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[] = {
@@ -208,7 +214,7 @@ static struct option builtin_merge_options[] = {
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY },
OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
OPT_BOOL(0, "verify-signatures", &verify_signatures,
- N_("Verify that the named commit has a valid GPG signature")),
+ 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"),
@@ -219,8 +225,12 @@ static struct option builtin_merge_options[] = {
OPT__VERBOSITY(&verbosity),
OPT_BOOL(0, "abort", &abort_current_merge,
N_("abort the current in-progress merge")),
+ OPT_BOOL(0, "continue", &continue_current_merge,
+ N_("continue the current in-progress merge")),
+ OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories,
+ N_("allow merging unrelated histories")),
OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1),
- { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"),
+ { 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()
@@ -229,19 +239,18 @@ static struct option builtin_merge_options[] = {
/* Cleans up metadata that is uninteresting after a succeeded merge. */
static void drop_save(void)
{
- unlink(git_path("MERGE_HEAD"));
- unlink(git_path("MERGE_MSG"));
- unlink(git_path("MERGE_MODE"));
+ unlink(git_path_merge_head());
+ unlink(git_path_merge_msg());
+ unlink(git_path_merge_mode());
}
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;
@@ -332,15 +341,9 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead
struct rev_info rev;
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"));
- filename = git_path("SQUASH_MSG");
- fd = open(filename, O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno(_("Could not write to '%s'"), filename);
init_revisions(&rev, NULL);
rev.ignore_merges = 1;
@@ -364,13 +367,10 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead
while ((commit = get_revision(&rev)) != NULL) {
strbuf_addch(&out, '\n');
strbuf_addf(&out, "commit %s\n",
- sha1_to_hex(commit->object.sha1));
+ oid_to_hex(&commit->object.oid));
pretty_print_commit(&ctx, commit, &out);
}
- if (write_in_full(fd, out.buf, out.len) != out.len)
- die_errno(_("Writing SQUASH_MSG"));
- if (close(fd))
- die_errno(_("Finishing SQUASH_MSG"));
+ write_file_buf(git_path_squash_msg(), out.buf, out.len);
strbuf_release(&out);
}
@@ -379,7 +379,7 @@ static void finish(struct commit *head_commit,
const unsigned char *new_head, const char *msg)
{
struct strbuf reflog_message = STRBUF_INIT;
- const unsigned char *head = head_commit->object.sha1;
+ const unsigned char *head = head_commit->object.oid.hash;
if (!msg)
strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
@@ -398,11 +398,12 @@ static void finish(struct commit *head_commit,
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.
*/
+ close_all_packs();
run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
}
}
@@ -421,7 +422,7 @@ static void finish(struct commit *head_commit,
}
/* 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);
}
@@ -491,40 +492,18 @@ static void merge_name(const char *remote, struct strbuf *msg)
}
if (len) {
struct strbuf truname = STRBUF_INIT;
- strbuf_addstr(&truname, "refs/heads/");
- strbuf_addstr(&truname, remote);
+ strbuf_addf(&truname, "refs/heads/%s", remote);
strbuf_setlen(&truname, truname.len - len);
if (ref_exists(truname.buf)) {
strbuf_addf(msg,
"%s\t\tbranch '%s'%s of .\n",
- sha1_to_hex(remote_head->object.sha1),
+ oid_to_hex(&remote_head->object.oid),
truname.buf + 11,
(early ? " (early part)" : ""));
strbuf_release(&truname);
goto cleanup;
}
- }
-
- if (!strcmp(remote, "FETCH_HEAD") &&
- !access(git_path("FETCH_HEAD"), R_OK)) {
- const char *filename;
- FILE *fp;
- struct strbuf line = STRBUF_INIT;
- char *ptr;
-
- filename = git_path("FETCH_HEAD");
- fp = fopen(filename, "r");
- if (!fp)
- die_errno(_("could not open '%s' for reading"),
- filename);
- strbuf_getline(&line, fp, '\n');
- fclose(fp);
- ptr = strstr(line.buf, "\tnot-for-merge\t");
- if (ptr)
- strbuf_remove(&line, ptr-line.buf+1, 13);
- strbuf_addbuf(msg, &line);
- strbuf_release(&line);
- goto cleanup;
+ strbuf_release(&truname);
}
if (remote_head->util) {
@@ -532,7 +511,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
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),
+ oid_to_hex(&desc->obj->oid),
typename(desc->obj->type),
remote);
goto cleanup;
@@ -540,7 +519,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
}
strbuf_addf(msg, "%s\t\tcommit '%s'\n",
- sha1_to_hex(remote_head->object.sha1), remote);
+ oid_to_hex(&remote_head->object.oid), remote);
cleanup:
strbuf_release(&buf);
strbuf_release(&bname);
@@ -557,7 +536,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";
@@ -657,22 +636,18 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
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, LOCK_DIE_ON_ERROR);
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;
@@ -700,14 +675,15 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
for (j = common; j; j = j->next)
commit_list_insert(j->item, &reversed);
- index_fd = hold_locked_index(lock, 1);
+ hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
clean = merge_recursive(&o, head,
remoteheads->item, reversed, &result);
+ if (clean < 0)
+ exit(128);
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,
@@ -734,42 +710,17 @@ static int count_unmerged_entries(void)
return ret;
}
-static void split_merge_strategies(const char *string, struct strategy **list,
- int *nr, int *alloc)
-{
- char *p, *q, *buf;
-
- if (!string)
- return;
-
- buf = xstrdup(string);
- q = buf;
- for (;;) {
- p = strchr(q, ' ');
- if (!p) {
- ALLOC_GROW(*list, *nr + 1, *alloc);
- (*list)[(*nr)++].name = xstrdup(q);
- free(buf);
- return;
- } else {
- *p = '\0';
- ALLOC_GROW(*list, *nr + 1, *alloc);
- (*list)[(*nr)++].name = xstrdup(q);
- q = ++p;
- }
- }
-}
-
static void add_strategies(const char *string, unsigned attr)
{
- struct strategy *list = NULL;
- int list_alloc = 0, list_nr = 0, i;
-
- memset(&list, 0, sizeof(list));
- split_merge_strategies(string, &list, &list_nr, &list_alloc);
- if (list) {
- for (i = 0; i < list_nr; i++)
- append_strategy(get_strategy(list[i].name));
+ int i;
+
+ if (string) {
+ struct string_list list = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ string_list_split(&list, string, ' ', -1);
+ for_each_string_list_item(item, &list)
+ append_strategy(get_strategy(item->string));
+ string_list_clear(&list, 0);
return;
}
for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
@@ -778,21 +729,9 @@ static void add_strategies(const char *string, unsigned attr)
}
-static void write_merge_msg(struct strbuf *msg)
-{
- 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"),
- 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(struct strbuf *msg)
{
- const char *filename = 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);
@@ -823,16 +762,16 @@ static void prepare_to_commit(struct commit_list *remoteheads)
strbuf_addch(&msg, '\n');
if (0 < option_edit)
strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char);
- write_merge_msg(&msg);
- if (run_hook(get_index_file(), "prepare-commit-msg",
- git_path("MERGE_MSG"), "merge", NULL, NULL))
+ write_file_buf(git_path_merge_msg(), msg.buf, msg.len);
+ 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))
+ if (launch_editor(git_path_merge_msg(), NULL, NULL))
abort_commit(remoteheads, NULL);
}
read_merge_msg(&msg);
- stripspace(&msg, 0 < option_edit);
+ strbuf_stripspace(&msg, 0 < option_edit);
if (!msg.len)
abort_commit(remoteheads, _("Empty commit message."));
strbuf_release(&merge_msg);
@@ -843,17 +782,23 @@ static void prepare_to_commit(struct commit_list *remoteheads)
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;
+ static struct lock_file lock;
+
+ hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
+ refresh_cache(REFRESH_QUIET);
+ if (active_cache_changed &&
+ write_locked_index(&the_index, &lock, COMMIT_LOCK))
+ return error(_("Unable to write index."));
+ rollback_lock_file(&lock);
write_tree_trivial(result_tree);
printf(_("Wonderful.\n"));
- parent->item = head;
- parent->next = xmalloc(sizeof(*parent->next));
- parent->next->item = remoteheads->item;
- parent->next->next = NULL;
+ pptr = commit_list_append(head, pptr);
+ pptr = commit_list_append(remoteheads->item, pptr);
prepare_to_commit(remoteheads);
- if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
- sign_commit))
+ 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();
@@ -877,8 +822,8 @@ static int finish_automerge(struct commit *head,
commit_list_insert(head, &parents);
strbuf_addch(&merge_msg, '\n');
prepare_to_commit(remoteheads);
- if (commit_tree(&merge_msg, result_tree, parents, result_commit,
- NULL, sign_commit))
+ 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(head, remoteheads, result_commit, buf.buf);
@@ -887,28 +832,20 @@ static int finish_automerge(struct commit *head,
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;
- filename = git_path("MERGE_MSG");
+ filename = git_path_merge_msg();
fp = fopen(filename, "a");
if (!fp)
die_errno(_("Could not open '%s' for writing"), filename);
- fprintf(fp, "\nConflicts:\n");
- for (pos = 0; pos < active_nr; pos++) {
- const 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++;
- }
- }
+
+ append_conflicts_hint(&msgbuf);
+ fputs(msgbuf.buf, fp);
+ strbuf_release(&msgbuf);
fclose(fp);
rerere(allow_rerere_auto);
printf(_("Automatic merge failed; "
@@ -928,7 +865,7 @@ static struct commit *is_old_style_invocation(int argc, const char **argv,
second_token = lookup_commit_reference_gently(second_sha1, 0);
if (!second_token)
die(_("'%s' is not a commit"), argv[1]);
- if (hashcmp(second_token->object.sha1, head))
+ if (hashcmp(second_token->object.oid.hash, head))
return NULL;
}
return second_token;
@@ -969,12 +906,12 @@ static int setup_with_upstream(const char ***argv)
if (!branch)
die(_("No current branch."));
- if (!branch->remote)
+ if (!branch->remote_name)
die(_("No remote for the current branch."));
if (!branch->merge_nr)
die(_("No default upstream defined for the current branch."));
- args = xcalloc(branch->merge_nr + 1, sizeof(char *));
+ args = xcalloc(st_add(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"),
@@ -988,41 +925,27 @@ static int setup_with_upstream(const char ***argv)
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 object_id *oid;
struct commit *c = j->item;
if (c->util && merge_remote_util(c)->obj) {
- sha1 = merge_remote_util(c)->obj->sha1;
+ oid = &merge_remote_util(c)->obj->oid;
} else {
- sha1 = c->object.sha1;
+ oid = &c->object.oid;
}
- strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+ strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
}
- 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);
+ write_file_buf(git_path_merge_head(), buf.buf, buf.len);
strbuf_addch(&merge_msg, '\n');
- write_merge_msg(&merge_msg);
+ write_file_buf(git_path_merge_msg(), merge_msg.buf, merge_msg.len);
- 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);
+ strbuf_addstr(&buf, "no-ff");
+ write_file_buf(git_path_merge_mode(), buf.buf, buf.len);
}
static int default_edit_option(void)
@@ -1038,7 +961,7 @@ static int default_edit_option(void)
if (e) {
int v = git_config_maybe_bool(name, e);
if (v < 0)
- die("Bad value '%s' in environment '%s'", e, name);
+ die(_("Bad value '%s' in environment '%s'"), e, name);
return v;
}
@@ -1051,38 +974,144 @@ static int default_edit_option(void)
st_stdin.st_mode == st_stdout.st_mode);
}
+static struct commit_list *reduce_parents(struct commit *head_commit,
+ int *head_subsumed,
+ struct commit_list *remoteheads)
+{
+ struct commit_list *parents, **remotes;
+
+ /*
+ * Is the current HEAD reachable from another commit being
+ * merged? If so we do not want to record it as a parent of
+ * the resulting merge, unless --no-ff is given. We will flip
+ * this variable to 0 when we find HEAD among the independent
+ * tips being merged.
+ */
+ *head_subsumed = 1;
+
+ /* Find what parents to record by checking independent ones. */
+ parents = reduce_heads(remoteheads);
+
+ remoteheads = NULL;
+ remotes = &remoteheads;
+ while (parents) {
+ struct commit *commit = pop_commit(&parents);
+ if (commit == head_commit)
+ *head_subsumed = 0;
+ else
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+ return remoteheads;
+}
+
+static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *merge_msg)
+{
+ struct fmt_merge_msg_opts opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.add_title = !have_message;
+ opts.shortlog_len = shortlog_len;
+ opts.credit_people = (0 < option_edit);
+
+ fmt_merge_msg(merge_names, merge_msg, &opts);
+ if (merge_msg->len)
+ strbuf_setlen(merge_msg, merge_msg->len - 1);
+}
+
+static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names)
+{
+ const char *filename;
+ int fd, pos, npos;
+ struct strbuf fetch_head_file = STRBUF_INIT;
+
+ if (!merge_names)
+ merge_names = &fetch_head_file;
+
+ filename = git_path_fetch_head();
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ die_errno(_("could not open '%s' for reading"), filename);
+
+ if (strbuf_read(merge_names, fd, 0) < 0)
+ die_errno(_("could not read '%s'"), filename);
+ if (close(fd) < 0)
+ die_errno(_("could not close '%s'"), filename);
+
+ for (pos = 0; pos < merge_names->len; pos = npos) {
+ unsigned char sha1[20];
+ char *ptr;
+ struct commit *commit;
+
+ ptr = strchr(merge_names->buf + pos, '\n');
+ if (ptr)
+ npos = ptr - merge_names->buf + 1;
+ else
+ npos = merge_names->len;
+
+ if (npos - pos < 40 + 2 ||
+ get_sha1_hex(merge_names->buf + pos, sha1))
+ commit = NULL; /* bad */
+ else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
+ continue; /* not-for-merge */
+ else {
+ char saved = merge_names->buf[pos + 40];
+ merge_names->buf[pos + 40] = '\0';
+ commit = get_merge_parent(merge_names->buf + pos);
+ merge_names->buf[pos + 40] = saved;
+ }
+ if (!commit) {
+ if (ptr)
+ *ptr = '\0';
+ die(_("not something we can merge in %s: %s"),
+ filename, merge_names->buf + pos);
+ }
+ remotes = &commit_list_insert(commit, remotes)->next;
+ }
+
+ if (merge_names == &fetch_head_file)
+ strbuf_release(&fetch_head_file);
+}
+
static struct commit_list *collect_parents(struct commit *head_commit,
int *head_subsumed,
- int argc, const char **argv)
+ int argc, const char **argv,
+ struct strbuf *merge_msg)
{
int i;
- struct commit_list *remoteheads = NULL, *parents, *next;
+ struct commit_list *remoteheads = NULL;
struct commit_list **remotes = &remoteheads;
+ struct strbuf merge_names = STRBUF_INIT, *autogen = NULL;
+
+ if (merge_msg && (!have_message || shortlog_len))
+ autogen = &merge_names;
if (head_commit)
remotes = &commit_list_insert(head_commit, remotes)->next;
- 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
+ if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) {
+ handle_fetch_head(remotes, autogen);
+ remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+ } else {
+ for (i = 0; i < argc; i++) {
+ struct commit *commit = get_merge_parent(argv[i]);
+ if (!commit)
+ help_unknown_ref(argv[i], "merge",
+ _("not something we can merge"));
remotes = &commit_list_insert(commit, remotes)->next;
+ }
+ remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
+ if (autogen) {
+ struct commit_list *p;
+ for (p = remoteheads; p; p = p->next)
+ merge_name(merge_remote_util(p->item)->name, autogen);
+ }
}
+
+ if (autogen) {
+ prepare_merge_message(autogen, merge_msg);
+ strbuf_release(autogen);
+ }
+
return remoteheads;
}
@@ -1094,12 +1123,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
struct commit *head_commit;
struct strbuf buf = STRBUF_INIT;
const char *head_arg;
- int flag, i, ret = 0, head_subsumed;
+ int 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 *remoteheads, *p;
void *branch_to_free;
+ int orig_argc = argc;
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_merge_usage, builtin_merge_options);
@@ -1108,7 +1138,7 @@ 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 = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag);
+ branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL);
if (branch && starts_with(branch, "refs/heads/"))
branch += 11;
if (!branch || is_null_sha1(head_sha1))
@@ -1116,6 +1146,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else
head_commit = lookup_commit_or_die(head_sha1, "HEAD");
+ init_diff_ui_defaults();
git_config(git_merge_config, NULL);
if (branch_mergeoptions)
@@ -1132,7 +1163,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
int nargc = 2;
const char *nargv[] = {"reset", "--merge", NULL};
- if (!file_exists(git_path("MERGE_HEAD")))
+ if (orig_argc != 2)
+ usage_msg_opt(_("--abort expects no arguments"),
+ builtin_merge_usage, builtin_merge_options);
+
+ if (!file_exists(git_path_merge_head()))
die(_("There is no merge to abort (MERGE_HEAD missing)."));
/* Invoke 'git reset --merge' */
@@ -1140,24 +1175,40 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
goto done;
}
+ if (continue_current_merge) {
+ int nargc = 1;
+ const char *nargv[] = {"commit", NULL};
+
+ if (orig_argc != 2)
+ usage_msg_opt(_("--continue expects no arguments"),
+ builtin_merge_usage, builtin_merge_options);
+
+ if (!file_exists(git_path_merge_head()))
+ die(_("There is no merge in progress (MERGE_HEAD missing)."));
+
+ /* Invoke 'git commit' */
+ ret = cmd_commit(nargc, nargv, prefix);
+ goto done;
+ }
+
if (read_cache_unmerged())
die_resolve_conflict("merge");
- if (file_exists(git_path("MERGE_HEAD"))) {
+ if (file_exists(git_path_merge_head())) {
/*
* There is no unmerged entry, don't advise 'git
* add/rm <file>', just 'git commit'.
*/
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 (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)."));
}
@@ -1172,61 +1223,62 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
option_commit = 0;
}
- if (!abort_current_merge) {
- if (!argc) {
- if (default_to_upstream)
- argc = setup_with_upstream(&argv);
- else
- die(_("No commit specified and merge.defaultToUpstream not set."));
- } else if (argc == 1 && !strcmp(argv[0], "-"))
- argv[0] = "@{-1}";
+ if (!argc) {
+ if (default_to_upstream)
+ argc = setup_with_upstream(&argv);
+ else
+ die(_("No commit specified and merge.defaultToUpstream not set."));
+ } else if (argc == 1 && !strcmp(argv[0], "-")) {
+ argv[0] = "@{-1}";
}
+
if (!argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
- /*
- * This could be traditional "merge <msg> HEAD <commit>..." and
- * the way we can tell it is to see if the second token is HEAD,
- * but some people might have misused the interface and used a
- * commit-ish that is the same as HEAD there instead.
- * Traditional format never would have "-m" so it is an
- * additional safety measure to check for it.
- */
-
- if (!have_message && head_commit &&
- is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
- strbuf_addstr(&merge_msg, argv[0]);
- head_arg = argv[1];
- argv += 2;
- argc -= 2;
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
- } else if (!head_commit) {
- struct commit *remote_head;
+ if (!head_commit) {
/*
* If the merged head is a valid one there is no reason
* to forbid "git merge" into a branch yet to be born.
* We do the same for "git pull".
*/
- if (argc != 1)
- die(_("Can merge only exactly one commit into "
- "empty head"));
+ unsigned char *remote_head_sha1;
if (squash)
die(_("Squash commit into empty head not supported yet"));
if (fast_forward == FF_NO)
die(_("Non-fast-forward commit does not make sense into "
"an empty head"));
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
- remote_head = remoteheads->item;
- if (!remote_head)
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, NULL);
+ if (!remoteheads)
die(_("%s - not something we can merge"), argv[0]);
- read_empty(remote_head->object.sha1, 0);
- update_ref("initial pull", "HEAD", remote_head->object.sha1,
- NULL, 0, DIE_ON_ERR);
+ if (remoteheads->next)
+ die(_("Can merge only exactly one commit into empty head"));
+ remote_head_sha1 = remoteheads->item->object.oid.hash;
+ read_empty(remote_head_sha1, 0);
+ update_ref("initial pull", "HEAD", remote_head_sha1,
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
goto done;
- } else {
- struct strbuf merge_names = STRBUF_INIT;
+ }
+ /*
+ * This could be traditional "merge <msg> HEAD <commit>..." and
+ * the way we can tell it is to see if the second token is HEAD,
+ * but some people might have misused the interface and used a
+ * commit-ish that is the same as HEAD there instead.
+ * Traditional format never would have "-m" so it is an
+ * additional safety measure to check for it.
+ */
+ if (!have_message &&
+ is_old_style_invocation(argc, argv, head_commit->object.oid.hash)) {
+ warning("old-style 'git merge <msg> HEAD <commit>' is deprecated.");
+ strbuf_addstr(&merge_msg, argv[0]);
+ head_arg = argv[1];
+ argv += 2;
+ argc -= 2;
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, NULL);
+ } else {
/* We are invoked directly as the first-class UI. */
head_arg = "HEAD";
@@ -1235,21 +1287,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* the standard merge summary message to be appended
* to the given message.
*/
- remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
- for (p = remoteheads; p; p = p->next)
- merge_name(merge_remote_util(p->item)->name, &merge_names);
-
- if (!have_message || shortlog_len) {
- struct fmt_merge_msg_opts opts;
- memset(&opts, 0, sizeof(opts));
- opts.add_title = !have_message;
- opts.shortlog_len = shortlog_len;
- opts.credit_people = (0 < option_edit);
-
- fmt_merge_msg(&merge_names, &merge_msg, &opts);
- if (merge_msg.len)
- strbuf_setlen(&merge_msg, merge_msg.len - 1);
- }
+ remoteheads = collect_parents(head_commit, &head_subsumed,
+ argc, argv, &merge_msg);
}
if (!head_commit || !argc)
@@ -1259,13 +1298,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (verify_signatures) {
for (p = remoteheads; p; p = p->next) {
struct commit *commit = p->item;
- char hex[41];
+ char hex[GIT_SHA1_HEXSZ + 1];
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));
+ find_unique_abbrev_r(hex, commit->object.oid.hash, DEFAULT_ABBREV);
switch (signature_check.result) {
case 'G':
break;
@@ -1282,10 +1321,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
printf(_("Commit %s has a good GPG signature by %s\n"),
hex, signature_check.signer);
- free(signature_check.gpg_output);
- free(signature_check.gpg_status);
- free(signature_check.signer);
- free(signature_check.key);
+ signature_check_clear(&signature_check);
}
}
@@ -1298,7 +1334,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
for (p = remoteheads; p; p = p->next) {
struct commit *commit = p->item;
strbuf_addf(&buf, "GITHEAD_%s",
- sha1_to_hex(commit->object.sha1));
+ oid_to_hex(&commit->object.oid));
setenv(buf.buf, merge_remote_util(commit)->name, 1);
strbuf_reset(&buf);
if (fast_forward != FF_ONLY &&
@@ -1330,7 +1366,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (!remoteheads)
; /* already up-to-date */
else if (!remoteheads->next)
- common = get_merge_bases(head_commit, remoteheads->item, 1);
+ common = get_merge_bases(head_commit, remoteheads->item);
else {
struct commit_list *list = remoteheads;
commit_list_insert(head_commit, &list);
@@ -1338,35 +1374,37 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
free(list);
}
- update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
- NULL, 0, DIE_ON_ERR);
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash,
+ NULL, 0, UPDATE_REFS_DIE_ON_ERR);
- if (remoteheads && !common)
- ; /* No common ancestors found. We need a real merge. */
- else if (!remoteheads ||
+ if (remoteheads && !common) {
+ /* No common ancestors found. */
+ if (!allow_unrelated_histories)
+ die(_("refusing to merge unrelated histories"));
+ /* otherwise, we need a real merge. */
+ } 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.");
+ finish_up_to_date(_("Already up-to-date."));
goto done;
} else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
- !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
+ !oidcmp(&common->item->object.oid, &head_commit->object.oid)) {
/* Again the most common case of merging one remote. */
struct strbuf msg = STRBUF_INIT;
struct commit *commit;
- char hex[41];
-
- strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
- if (verbosity >= 0)
+ if (verbosity >= 0) {
printf(_("Updating %s..%s\n"),
- hex,
- find_unique_abbrev(remoteheads->item->object.sha1,
- DEFAULT_ABBREV));
+ find_unique_abbrev(head_commit->object.oid.hash,
+ DEFAULT_ABBREV),
+ find_unique_abbrev(remoteheads->item->object.oid.hash,
+ DEFAULT_ABBREV));
+ }
strbuf_addstr(&msg, "Fast-forward");
if (have_message)
strbuf_addstr(&msg,
@@ -1377,14 +1415,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
goto done;
}
- if (checkout_fast_forward(head_commit->object.sha1,
- commit->object.sha1,
+ if (checkout_fast_forward(head_commit->object.oid.hash,
+ commit->object.oid.hash,
overwrite_ignore)) {
ret = 1;
goto done;
}
- finish(head_commit, remoteheads, commit->object.sha1, msg.buf);
+ finish(head_commit, remoteheads, commit->object.oid.hash, msg.buf);
drop_save();
goto done;
} else if (!remoteheads->next && common->next)
@@ -1403,9 +1441,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
/* See if it is really trivial. */
git_committer_info(IDENT_STRICT);
printf(_("Trying really trivial in-index merge...\n"));
- if (!read_tree_trivial(common->item->object.sha1,
- head_commit->object.sha1,
- remoteheads->item->object.sha1)) {
+ if (!read_tree_trivial(common->item->object.oid.hash,
+ head_commit->object.oid.hash,
+ remoteheads->item->object.oid.hash)) {
ret = merge_trivial(head_commit, remoteheads);
goto done;
}
@@ -1427,15 +1465,14 @@ 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(head_commit, j->item, 1);
- if (hashcmp(common_one->item->object.sha1,
- j->item->object.sha1)) {
+ common_one = get_merge_bases(head_commit, j->item);
+ if (oidcmp(&common_one->item->object.oid, &j->item->object.oid)) {
up_to_date = 0;
break;
}
}
if (up_to_date) {
- finish_up_to_date("Already up-to-date. Yeeah!");
+ finish_up_to_date(_("Already up-to-date. Yeeah!"));
goto done;
}
}
@@ -1459,13 +1496,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* Stash away the local changes so that we can try more than one.
*/
save_state(stash))
- hashcpy(stash, null_sha1);
+ hashclr(stash);
for (i = 0; i < use_strategies_nr; i++) {
int ret;
if (i) {
printf(_("Rewinding the tree to pristine...\n"));
- restore_state(head_commit->object.sha1, stash);
+ restore_state(head_commit->object.oid.hash, stash);
}
if (use_strategies_nr != 1)
printf(_("Trying merge strategy %s...\n"),
@@ -1531,7 +1568,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* it up.
*/
if (!best_strategy) {
- restore_state(head_commit->object.sha1, stash);
+ restore_state(head_commit->object.oid.hash, stash);
if (use_strategies_nr > 1)
fprintf(stderr,
_("No merge strategy handled the merge.\n"));
@@ -1544,7 +1581,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
; /* We already have its result in the working tree. */
else {
printf(_("Rewinding the tree to pristine...\n"));
- restore_state(head_commit->object.sha1, stash);
+ restore_state(head_commit->object.oid.hash, stash);
printf(_("Using the %s to prepare resolving by hand.\n"),
best_strategy);
try_merge_strategy(best_strategy, common, remoteheads,
@@ -1560,7 +1597,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
fprintf(stderr, _("Automatic merge went well; "
"stopped before committing as requested\n"));
else
- ret = suggest_conflicts(option_renormalize);
+ ret = suggest_conflicts();
done:
free(branch_to_free);
diff --git a/builtin/mktag.c b/builtin/mktag.c
index 640ab64f41..031b750f06 100644
--- a/builtin/mktag.c
+++ b/builtin/mktag.c
@@ -154,7 +154,7 @@ int cmd_mktag(int argc, const char **argv, const char *prefix)
unsigned char result_sha1[20];
if (argc != 1)
- usage("git mktag < signaturefile");
+ usage("git mktag");
if (strbuf_read(&buf, 0, 4096) < 0) {
die_errno("could not read from stdin");
diff --git a/builtin/mktree.c b/builtin/mktree.c
index f92ba404ab..de9b40fc63 100644
--- a/builtin/mktree.c
+++ b/builtin/mktree.c
@@ -19,19 +19,17 @@ static int alloc, used;
static void append_to_tree(unsigned mode, unsigned char *sha1, char *path)
{
struct treeent *ent;
- int len = strlen(path);
+ size_t len = strlen(path);
if (strchr(path, '/'))
die("path %s contains slash", path);
- if (alloc <= used) {
- alloc = alloc_nr(used);
- entries = xrealloc(entries, sizeof(*entries) * alloc);
- }
- ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1);
+ FLEX_ALLOC_MEM(ent, name, path, len);
ent->mode = mode;
ent->len = len;
hashcpy(ent->sha1, sha1);
- memcpy(ent->name, path, len+1);
+
+ ALLOC_GROW(entries, used + 1, alloc);
+ entries[used++] = ent;
}
static int ent_compare(const void *a_, const void *b_)
@@ -48,7 +46,7 @@ static void write_tree(unsigned char *sha1)
size_t size;
int i;
- qsort(entries, used, sizeof(*entries), ent_compare);
+ QSORT(entries, used, ent_compare);
for (size = i = 0; i < used; i++)
size += 32 + entries[i]->len;
@@ -68,7 +66,7 @@ static const char *mktree_usage[] = {
NULL
};
-static void mktree_line(char *buf, size_t len, int line_termination, int allow_missing)
+static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_missing)
{
char *ptr, *ntr;
unsigned mode;
@@ -100,7 +98,7 @@ static void mktree_line(char *buf, size_t len, int line_termination, int allow_m
*ntr++ = 0; /* now at the beginning of SHA1 */
path = ntr + 41; /* at the beginning of name */
- if (line_termination && path[0] == '"') {
+ if (!nul_term_line && path[0] == '"') {
struct strbuf p_uq = STRBUF_INIT;
if (unquote_c_style(&p_uq, path, NULL))
die("invalid quoting");
@@ -144,23 +142,25 @@ int cmd_mktree(int ac, const char **av, const char *prefix)
{
struct strbuf sb = STRBUF_INIT;
unsigned char sha1[20];
- int line_termination = '\n';
+ int nul_term_line = 0;
int allow_missing = 0;
int is_batch_mode = 0;
int got_eof = 0;
+ strbuf_getline_fn getline_fn;
const struct option option[] = {
- OPT_SET_INT('z', NULL, &line_termination, N_("input is NUL terminated"), '\0'),
+ OPT_BOOL('z', NULL, &nul_term_line, N_("input is NUL terminated")),
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()
};
ac = parse_options(ac, av, prefix, option, mktree_usage, 0);
+ getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
while (!got_eof) {
while (1) {
- if (strbuf_getline(&sb, stdin, line_termination) == EOF) {
+ if (getline_fn(&sb, stdin) == EOF) {
got_eof = 1;
break;
}
@@ -170,7 +170,7 @@ int cmd_mktree(int ac, const char **av, const char *prefix)
break;
die("input format error: (blank line only valid in batch mode)");
}
- mktree_line(sb.buf, sb.len, line_termination, allow_missing);
+ mktree_line(sb.buf, sb.len, nul_term_line, allow_missing);
}
if (is_batch_mode && got_eof && used < 1) {
/*
diff --git a/builtin/mv.c b/builtin/mv.c
index 21c46d1636..61d20037ad 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -3,8 +3,9 @@
*
* Copyright (C) 2006 Johannes Schindelin
*/
-#include "cache.h"
#include "builtin.h"
+#include "pathspec.h"
+#include "lockfile.h"
#include "dir.h"
#include "cache-tree.h"
#include "string-list.h"
@@ -12,44 +13,56 @@
#include "submodule.h"
static const char * const builtin_mv_usage[] = {
- N_("git mv [options] <source>... <destination>"),
+ N_("git mv [<options>] <source>... <destination>"),
NULL
};
#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)
+static const char **internal_prefix_pathspec(const char *prefix,
+ const char **pathspec,
+ int count, unsigned flags)
{
int i;
- const char **result = xmalloc((count + 1) * sizeof(const char *));
- memcpy(result, pathspec, count * sizeof(const char *));
- result[count] = NULL;
+ const char **result;
+ int prefixlen = prefix ? strlen(prefix) : 0;
+ ALLOC_ARRAY(result, count + 1);
+
+ /* Create an intermediate copy of the pathspec based on the flags */
for (i = 0; i < count; i++) {
- int length = strlen(result[i]);
+ int length = strlen(pathspec[i]);
int to_copy = length;
+ char *it;
while (!(flags & KEEP_TRAILING_SLASH) &&
- to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
+ to_copy > 0 && is_dir_sep(pathspec[i][to_copy - 1]))
to_copy--;
- if (to_copy != length || flags & DUP_BASENAME) {
- char *it = xmemdupz(result[i], to_copy);
- if (flags & DUP_BASENAME) {
- result[i] = xstrdup(basename(it));
- free(it);
- } else
- result[i] = it;
+
+ it = xmemdupz(pathspec[i], to_copy);
+ if (flags & DUP_BASENAME) {
+ result[i] = xstrdup(basename(it));
+ free(it);
+ } else {
+ result[i] = it;
}
}
- return get_pathspec(prefix, result);
+ result[count] = NULL;
+
+ /* Prefix the pathspec and free the old intermediate strings */
+ for (i = 0; i < count; i++) {
+ const char *match = prefix_path(prefix, prefixlen, result[i]);
+ free((char *) result[i]);
+ result[i] = match;
+ }
+
+ return result;
}
static const char *add_slash(const char *path)
{
- int len = strlen(path);
+ size_t len = strlen(path);
if (path[len - 1] != '/') {
- char *with_slash = xmalloc(len + 2);
+ char *with_slash = xmalloc(st_add(len, 2));
memcpy(with_slash, path, len);
with_slash[len++] = '/';
with_slash[len] = 0;
@@ -61,9 +74,49 @@ 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, gitmodules_modified = 0;
+ int i, flags, gitmodules_modified = 0;
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
@@ -85,30 +138,33 @@ 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, LOCK_DIE_ON_ERROR);
if (read_cache() < 0)
die(_("index file corrupt"));
- source = internal_copy_pathspec(prefix, argv, argc, 0);
+ source = internal_prefix_pathspec(prefix, argv, argc, 0);
modes = xcalloc(argc, sizeof(enum update_mode));
/*
* Keep trailing slash, needed to let
- * "git mv file no-such-dir/" error out.
+ * "git mv file no-such-dir/" error out, except in the case
+ * "git mv directory no-such-dir/".
*/
- dest_path = internal_copy_pathspec(prefix, argv + argc, 1,
- KEEP_TRAILING_SLASH);
+ flags = KEEP_TRAILING_SLASH;
+ if (argc == 1 && is_directory(argv[0]) && !is_directory(argv[1]))
+ flags = 0;
+ dest_path = internal_prefix_pathspec(prefix, argv + argc, 1, flags);
submodule_gitfile = xcalloc(argc, sizeof(char *));
if (dest_path[0][0] == '\0')
/* special case: "." was normalized to "" */
- destination = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
+ destination = internal_prefix_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 = internal_copy_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
+ destination = internal_prefix_pathspec(dest_path[0], argv, argc, DUP_BASENAME);
} else {
if (argc != 1)
- die("destination '%s' is not a directory", dest_path[0]);
+ die(_("destination '%s' is not a directory"), dest_path[0]);
destination = dest_path;
}
@@ -131,74 +187,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) {
- int first = cache_name_pos(src, length);
- if (first >= 0) {
- struct strbuf submodule_dotgit = STRBUF_INIT;
- if (!S_ISGITLINK(active_cache[first]->ce_mode))
- die (_("Huh? Directory %s is in index and no submodule?"), src);
- if (!is_staging_gitmodules_ok())
- die (_("Please, stage your changes to .gitmodules or stash them to proceed"));
- strbuf_addf(&submodule_dotgit, "%s/.git", src);
- submodule_gitfile[i] = read_gitfile(submodule_dotgit.buf);
- if (submodule_gitfile[i])
- submodule_gitfile[i] = xstrdup(submodule_gitfile[i]);
- else
- submodule_gitfile[i] = SUBMODULE_WITH_GITDIR;
- strbuf_release(&submodule_dotgit);
- } else {
- const char *src_w_slash = add_slash(src);
- int last, len_w_slash = length + 1;
+ int first = cache_name_pos(src, length), last;
+
+ if (first >= 0)
+ 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 { /* 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);
- 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);
+ dst = add_slash(dst);
+ dst_len = strlen(dst);
- first = -1 - first;
- for (last = first; last < active_nr; last++) {
- const char *path = active_cache[last]->name;
- if (strncmp(path, src_w_slash, len_w_slash))
- break;
- }
- free((char *)src_w_slash);
-
- if (last - first < 1)
- bad = _("source directory is empty");
- else {
- int j, dst_len;
-
- if (last - first > 0) {
- source = xrealloc(source,
- (argc + last - first)
- * sizeof(char *));
- destination = xrealloc(destination,
- (argc + last - first)
- * sizeof(char *));
- modes = xrealloc(modes,
- (argc + last - first)
- * sizeof(enum update_mode));
- }
-
- dst = add_slash(dst);
- dst_len = strlen(dst);
-
- for (j = 0; j < last - first; j++) {
- const char *path =
- active_cache[first + j]->name;
- source[argc + j] = path;
- destination[argc + j] =
- prefix_path(dst, dst_len,
- path + length + 1);
- modes[argc + j] = INDEX;
- }
- argc += last - first;
+ for (j = 0; j < last - first; j++) {
+ const char *path = active_cache[first + j]->name;
+ source[argc + j] = path;
+ destination[argc + j] =
+ prefix_path(dst, dst_len, path + length + 1);
+ modes[argc + j] = INDEX;
+ 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) {
/*
@@ -219,19 +242,22 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
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--;
}
}
@@ -241,15 +267,18 @@ 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) {
- 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 (show_only)
+ continue;
+ if (mode != INDEX && rename(src, dst) < 0) {
+ if (ignore_errors)
+ continue;
+ 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)
@@ -264,11 +293,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
if (gitmodules_modified)
stage_updated_gitmodules();
- 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 (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 0b21d7e5b2..8bdc3eaa6f 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -10,6 +10,7 @@
typedef struct rev_name {
const char *tip_name;
+ unsigned long taggerdate;
int generation;
int distance;
} rev_name;
@@ -20,7 +21,8 @@ static long cutoff = LONG_MAX;
#define MERGE_TRAVERSAL_WEIGHT 65535
static void name_rev(struct commit *commit,
- const char *tip_name, int generation, int distance,
+ const char *tip_name, unsigned long taggerdate,
+ int generation, int distance,
int deref)
{
struct rev_name *name = (struct rev_name *)commit->util;
@@ -33,10 +35,7 @@ static void name_rev(struct commit *commit,
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);
@@ -46,9 +45,12 @@ static void name_rev(struct commit *commit,
name = xmalloc(sizeof(rev_name));
commit->util = name;
goto copy_data;
- } else if (name->distance > distance) {
+ } else if (name->taggerdate > taggerdate ||
+ (name->taggerdate == taggerdate &&
+ name->distance > distance)) {
copy_data:
name->tip_name = tip_name;
+ name->taggerdate = taggerdate;
name->generation = generation;
name->distance = distance;
} else
@@ -58,26 +60,22 @@ copy_data:
parents;
parents = parents->next, parent_number++) {
if (parent_number > 1) {
- int len = strlen(tip_name);
- char *new_name = xmalloc(len +
- 1 + decimal_length(generation) + /* ~<n> */
- 1 + 2 + /* ^NN */
- 1);
-
- if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
- len -= 2;
+ size_t len;
+ char *new_name;
+
+ strip_suffix(tip_name, "^0", &len);
if (generation > 0)
- sprintf(new_name, "%.*s~%d^%d", len, tip_name,
- generation, parent_number);
+ new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name,
+ generation, parent_number);
else
- sprintf(new_name, "%.*s^%d", len, tip_name,
- parent_number);
+ new_name = xstrfmt("%.*s^%d", (int)len, tip_name,
+ parent_number);
- name_rev(parents->item, new_name, 0,
+ name_rev(parents->item, new_name, taggerdate, 0,
distance + MERGE_TRAVERSAL_WEIGHT, 0);
} else {
- name_rev(parents->item, tip_name, generation + 1,
- distance + 1, 0);
+ name_rev(parents->item, tip_name, taggerdate,
+ generation + 1, distance + 1, 0);
}
}
}
@@ -87,7 +85,7 @@ static int subpath_matches(const char *path, const char *filter)
const char *subpath = path;
while (subpath) {
- if (!fnmatch(filter, subpath, 0))
+ if (!wildmatch(filter, subpath, 0, NULL))
return subpath - path;
subpath = strchr(subpath, '/');
if (subpath)
@@ -110,7 +108,8 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
struct name_ref_data {
int tags_only;
int name_only;
- const char *ref_filter;
+ struct string_list ref_filters;
+ struct string_list exclude_filters;
};
static struct tip_table {
@@ -141,42 +140,75 @@ static int tipcmp(const void *a_, const void *b_)
return hashcmp(a->sha1, b->sha1);
}
-static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
+static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data)
{
- struct object *o = parse_object(sha1);
+ struct object *o = parse_object(oid->hash);
struct name_ref_data *data = cb_data;
int can_abbreviate_output = data->tags_only && data->name_only;
int deref = 0;
+ unsigned long taggerdate = ULONG_MAX;
if (data->tags_only && !starts_with(path, "refs/tags/"))
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;
+ if (data->exclude_filters.nr) {
+ struct string_list_item *item;
+
+ for_each_string_list_item(item, &data->exclude_filters) {
+ if (subpath_matches(path, item->string) >= 0)
+ return 0;
+ }
+ }
+
+ if (data->ref_filters.nr) {
+ struct string_list_item *item;
+ int matched = 0;
+
+ /* See if any of the patterns match. */
+ for_each_string_list_item(item, &data->ref_filters) {
+ /*
+ * Check all patterns even after finding a match, so
+ * that we can see if a match with a subpath exists.
+ * When a user asked for 'refs/tags/v*' and 'v1.*',
+ * both of which match, the user is showing her
+ * willingness to accept a shortened output by having
+ * the 'v1.*' in the acceptable refnames, so we
+ * shouldn't stop when seeing 'refs/tags/v1.4' matches
+ * 'refs/tags/v*'. We should show it as 'v1.4'.
+ */
+ switch (subpath_matches(path, item->string)) {
+ case -1: /* did not match */
+ break;
+ case 0: /* matched fully */
+ matched = 1;
+ break;
+ default: /* matched subpath */
+ matched = 1;
+ can_abbreviate_output = 1;
+ break;
+ }
}
+
+ /* If none of the patterns matched, stop now */
+ if (!matched)
+ return 0;
}
- add_to_tip_table(sha1, path, can_abbreviate_output);
+ add_to_tip_table(oid->hash, path, can_abbreviate_output);
while (o && o->type == OBJ_TAG) {
struct tag *t = (struct tag *) o;
if (!t->tagged)
break; /* broken repository */
- o = parse_object(t->tagged->sha1);
+ o = parse_object(t->tagged->oid.hash);
deref = 1;
+ taggerdate = t->date;
}
if (o && o->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)o;
path = name_ref_abbrev(path, can_abbreviate_output);
- name_rev(commit, xstrdup(path), 0, 0, deref);
+ name_rev(commit, xstrdup(path), taggerdate, 0, 0, deref);
}
return 0;
}
@@ -195,12 +227,11 @@ static const char *get_exact_ref_match(const struct object *o)
return NULL;
if (!tip_table.sorted) {
- qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table),
- tipcmp);
+ QSORT(tip_table.table, tip_table.nr, tipcmp);
tip_table.sorted = 1;
}
- found = sha1_pos(o->sha1, tip_table.table, tip_table.nr,
+ found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr,
nth_tip_table_ent);
if (0 <= found)
return tip_table.table[found].refname;
@@ -239,25 +270,25 @@ static void show_name(const struct object *obj,
int always, int allow_undefined, int name_only)
{
const char *name;
- const unsigned char *sha1 = obj->sha1;
+ const struct object_id *oid = &obj->oid;
if (!name_only)
- printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1));
+ printf("%s ", caller_name ? caller_name : oid_to_hex(oid));
name = get_rev_name(obj);
if (name)
printf("%s\n", name);
else if (allow_undefined)
printf("undefined\n");
else if (always)
- printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV));
+ printf("%s\n", find_unique_abbrev(oid->hash, DEFAULT_ABBREV));
else
- die("cannot describe '%s'", sha1_to_hex(sha1));
+ die("cannot describe '%s'", oid_to_hex(oid));
}
static char const * const name_rev_usage[] = {
- N_("git name-rev [options] <commit>..."),
- N_("git name-rev [options] --all"),
- N_("git name-rev [options] --stdin"),
+ N_("git name-rev [<options>] <commit>..."),
+ N_("git name-rev [<options>] --all"),
+ N_("git name-rev [<options>] --stdin"),
NULL
};
@@ -307,12 +338,14 @@ 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, peel_tag = 0;
- struct name_ref_data data = { 0, 0, NULL };
+ struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
struct option opts[] = {
OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
- OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"),
+ OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"),
N_("only use refs matching <pattern>")),
+ OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"),
+ N_("ignore refs matching <pattern>")),
OPT_GROUP(""),
OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
diff --git a/builtin/notes.c b/builtin/notes.c
index 2b24d059b5..4b492abd41 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -19,20 +19,21 @@
#include "string-list.h"
#include "notes-merge.h"
#include "notes-utils.h"
+#include "worktree.h"
static const char * const git_notes_usage[] = {
- N_("git notes [--ref <notes_ref>] [list [<object>]]"),
- N_("git notes [--ref <notes_ref>] add [-f] [-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 [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"),
- N_("git notes [--ref <notes_ref>] edit [<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 [--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"),
+ 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
};
@@ -68,7 +69,7 @@ static const char * const git_notes_show_usage[] = {
};
static const char * const git_notes_merge_usage[] = {
- N_("git notes merge [<options>] <notes_ref>"),
+ N_("git notes merge [<options>] <notes-ref>"),
N_("git notes merge --commit [<options>]"),
N_("git notes merge --abort [<options>]"),
NULL
@@ -90,14 +91,24 @@ static const char * const git_notes_get_ref_usage[] = {
};
static const char note_template[] =
- "\nWrite/edit the notes for the following object:\n";
+ N_("Write/edit the notes for the following object:");
-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)
@@ -106,7 +117,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;
@@ -122,12 +133,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;
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;
@@ -150,29 +160,27 @@ static void write_commented_object(int fd, const unsigned char *object)
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 (msg->given)
- write_or_die(fd, msg->buf.buf, msg->buf.len);
- else if (prev && !append_only)
- write_note_data(fd, prev);
+ if (d->given)
+ write_or_die(fd, d->buf.buf, d->buf.len);
+ else if (old_note)
+ copy_obj_to_fd(fd, old_note);
strbuf_addch(&buf, '\n');
- strbuf_add_commented_lines(&buf, note_template, strlen(note_template));
+ strbuf_add_commented_lines(&buf, "\n", strlen("\n"));
+ strbuf_add_commented_lines(&buf, _(note_template), strlen(_(note_template)));
strbuf_addch(&buf, '\n');
write_or_die(fd, buf.buf, buf.len);
@@ -180,108 +188,89 @@ static void create_note(const unsigned char *object, struct msg_arg *msg,
close(fd);
strbuf_release(&buf);
- strbuf_reset(&(msg->buf));
+ strbuf_reset(&d->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);
- }
-
- 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"));
}
+ strbuf_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);
+ strbuf_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);
+ strbuf_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) {
+ die(_("failed to resolve '%s' as a valid ref."), arg);
+ if (!(buf = read_sha1_file(object, &type, &len))) {
free(buf);
- die(_("Failed to read object '%s'."), arg);;
+ die(_("failed to read object '%s'."), arg);
}
- strbuf_add(&(msg->buf), buf, len);
+ if (type != OBJ_BLOB) {
+ free(buf);
+ die(_("cannot read note data from non-blob object '%s'."), arg);
+ }
+ 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);
}
@@ -298,24 +287,24 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
if (!c)
return 0;
} else {
- init_notes(NULL, NULL, NULL, 0);
+ init_notes(NULL, NULL, NULL, NOTES_INIT_WRITABLE);
t = &default_notes_tree;
}
- while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ while (strbuf_getline_lf(&buf, stdin) != EOF) {
unsigned char from_obj[20], to_obj[20];
struct strbuf **split;
int err;
split = strbuf_split(&buf, ' ');
if (!split[0] || !split[1])
- die(_("Malformed input line: '%s'."), buf.buf);
+ die(_("malformed input line: '%s'."), buf.buf);
strbuf_rtrim(split[0]);
strbuf_rtrim(split[1]);
if (get_sha1(split[0]->buf, from_obj))
- die(_("Failed to resolve '%s' as a valid ref."), split[0]->buf);
+ die(_("failed to resolve '%s' as a valid ref."), split[0]->buf);
if (get_sha1(split[1]->buf, to_obj))
- die(_("Failed to resolve '%s' as a valid ref."), split[1]->buf);
+ die(_("failed to resolve '%s' as a valid ref."), split[1]->buf);
if (rewrite_cmd)
err = copy_note_for_rewrite(c, from_obj, to_obj);
@@ -324,7 +313,7 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
combine_notes_overwrite);
if (err) {
- error(_("Failed to copy notes from '%s' to '%s'"),
+ error(_("failed to copy notes from '%s' to '%s'"),
split[0]->buf, split[1]->buf);
ret = 1;
}
@@ -341,15 +330,20 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd)
return ret;
}
-static struct notes_tree *init_notes_check(const char *subcommand)
+static struct notes_tree *init_notes_check(const char *subcommand,
+ int flags)
{
struct notes_tree *t;
- init_notes(NULL, NULL, NULL, 0);
+ const char *ref;
+ init_notes(NULL, NULL, NULL, flags);
t = &default_notes_tree;
- if (!starts_with(t->ref, "refs/notes/"))
- die("Refusing to %s notes in %s (outside of refs/notes/)",
- subcommand, t->ref);
+ ref = (flags & NOTES_INIT_WRITABLE) ? t->update_ref : t->ref;
+ if (!starts_with(ref, "refs/notes/"))
+ /* TRANSLATORS: the first %s will be replaced by a
+ git notes command: 'add', 'merge', 'remove', etc.*/
+ die(_("refusing to %s notes in %s (outside of refs/notes/)"),
+ subcommand, ref);
return t;
}
@@ -372,16 +366,16 @@ static int list(int argc, const char **argv, const char *prefix)
usage_with_options(git_notes_list_usage, options);
}
- t = init_notes_check("list");
+ t = init_notes_check("list", 0);
if (argc) {
if (get_sha1(argv[0], object))
- die(_("Failed to resolve '%s' as a valid ref."), argv[0]);
+ die(_("failed to resolve '%s' as a valid ref."), argv[0]);
note = get_note(t, object);
if (note) {
puts(sha1_to_hex(note));
retval = 0;
} else
- retval = error(_("No note found for object %s."),
+ retval = error(_("no note found for object %s."),
sha1_to_hex(object));
} else
retval = for_each_note(t, 0, list_each_note, NULL);
@@ -394,26 +388,27 @@ 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, N_("message"),
+ { OPTION_CALLBACK, 'm', "message", &d, N_("message"),
N_("note contents as a string"), PARSE_OPT_NONEG,
parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &msg, N_("file"),
+ { 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, N_("object"),
+ { 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, N_("object"),
+ { 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__FORCE(&force, N_("replace existing notes")),
OPT_END()
};
@@ -429,48 +424,51 @@ static int add(int argc, const char **argv, const char *prefix)
object_ref = argc > 1 ? argv[1] : "HEAD";
if (get_sha1(object_ref, object))
- die(_("Failed to resolve '%s' as a valid ref."), object_ref);
+ die(_("failed to resolve '%s' as a valid ref."), object_ref);
- t = init_notes_check("add");
+ t = init_notes_check("add", NOTES_INIT_WRITABLE);
note = get_note(t, object);
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)
@@ -512,14 +510,14 @@ static int copy(int argc, const char **argv, const char *prefix)
}
if (get_sha1(argv[0], from_obj))
- die(_("Failed to resolve '%s' as a valid ref."), argv[0]);
+ die(_("failed to resolve '%s' as a valid ref."), argv[0]);
object_ref = 1 < argc ? argv[1] : "HEAD";
if (get_sha1(object_ref, object))
- die(_("Failed to resolve '%s' as a valid ref."), object_ref);
+ die(_("failed to resolve '%s' as a valid ref."), object_ref);
- t = init_notes_check("copy");
+ t = init_notes_check("copy", NOTES_INIT_WRITABLE);
note = get_note(t, object);
if (note) {
@@ -536,7 +534,7 @@ static int copy(int argc, const char **argv, const char *prefix)
from_note = get_note(t, from_obj);
if (!from_note) {
- retval = error(_("Missing notes on source object %s. Cannot "
+ retval = error(_("missing notes on source object %s. Cannot "
"copy."), sha1_to_hex(from_obj));
goto out;
}
@@ -551,26 +549,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, N_("message"),
+ { OPTION_CALLBACK, 'm', "message", &d, N_("message"),
N_("note contents as a string"), PARSE_OPT_NONEG,
parse_msg_arg},
- { OPTION_CALLBACK, 'F', "file", &msg, N_("file"),
+ { 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, N_("object"),
+ { 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, N_("object"),
+ { 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");
@@ -584,7 +585,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"));
@@ -592,23 +593,44 @@ static int append_edit(int argc, const char **argv, const char *prefix)
object_ref = 1 < argc ? argv[1] : "HEAD";
if (get_sha1(object_ref, object))
- die(_("Failed to resolve '%s' as a valid ref."), object_ref);
+ die(_("failed to resolve '%s' as a valid ref."), object_ref);
- t = init_notes_check(argv[0]);
+ t = init_notes_check(argv[0], NOTES_INIT_WRITABLE);
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);
+
+ 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);
+ }
- snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
- is_null_sha1(new_note) ? "removed" : "added", argv[0]);
+ 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;
}
@@ -634,13 +656,13 @@ static int show(int argc, const char **argv, const char *prefix)
object_ref = argc ? argv[0] : "HEAD";
if (get_sha1(object_ref, object))
- die(_("Failed to resolve '%s' as a valid ref."), object_ref);
+ die(_("failed to resolve '%s' as a valid ref."), object_ref);
- t = init_notes_check("show");
+ t = init_notes_check("show", 0);
note = get_note(t, object);
if (!note)
- retval = error(_("No note found for object %s."),
+ retval = error(_("no note found for object %s."),
sha1_to_hex(object));
else {
const char *show_args[3] = {"show", sha1_to_hex(note), NULL};
@@ -659,12 +681,12 @@ static int merge_abort(struct notes_merge_options *o)
* notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
*/
- if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
- ret += error("Failed to delete ref NOTES_MERGE_PARTIAL");
- if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
- ret += error("Failed to delete ref NOTES_MERGE_REF");
+ if (delete_ref(NULL, "NOTES_MERGE_PARTIAL", NULL, 0))
+ ret += error(_("failed to delete ref NOTES_MERGE_PARTIAL"));
+ if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NODEREF))
+ ret += error(_("failed to delete ref NOTES_MERGE_REF"));
if (notes_merge_abort(o))
- ret += error("Failed to remove 'git notes merge' worktree");
+ ret += error(_("failed to remove 'git notes merge' worktree"));
return ret;
}
@@ -684,14 +706,14 @@ static int merge_commit(struct notes_merge_options *o)
*/
if (get_sha1("NOTES_MERGE_PARTIAL", sha1))
- die("Failed to read ref NOTES_MERGE_PARTIAL");
+ die(_("failed to read ref NOTES_MERGE_PARTIAL"));
else if (!(partial = lookup_commit_reference(sha1)))
- die("Could not find commit from NOTES_MERGE_PARTIAL.");
+ die(_("could not find commit from NOTES_MERGE_PARTIAL."));
else if (parse_commit(partial))
- die("Could not parse commit from NOTES_MERGE_PARTIAL.");
+ die(_("could not parse commit from NOTES_MERGE_PARTIAL."));
if (partial->parents)
- hashcpy(parent_sha1, partial->parents->item->object.sha1);
+ hashcpy(parent_sha1, partial->parents->item->object.oid.hash);
else
hashclr(parent_sha1);
@@ -699,12 +721,12 @@ static int merge_commit(struct notes_merge_options *o)
init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
o->local_ref = local_ref_to_free =
- resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL);
+ resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL);
if (!o->local_ref)
- die("Failed to resolve NOTES_MERGE_REF");
+ die(_("failed to resolve NOTES_MERGE_REF"));
if (notes_merge_commit(o, t, partial, sha1))
- die("Failed to finalize notes merge");
+ die(_("failed to finalize notes merge"));
/* Reuse existing commit message in reflog message */
memset(&pretty_ctx, 0, sizeof(pretty_ctx));
@@ -713,7 +735,7 @@ 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);
@@ -722,6 +744,20 @@ static int merge_commit(struct notes_merge_options *o)
return ret;
}
+static int git_config_get_notes_strategy(const char *key,
+ enum notes_merge_strategy *strategy)
+{
+ char *value;
+
+ if (git_config_get_string(key, &value))
+ return 1;
+ if (parse_notes_merge_strategy(value, strategy))
+ git_die_config(key, _("unknown notes merge strategy %s"), value);
+
+ free(value);
+ return 0;
+}
+
static int merge(int argc, const char **argv, const char *prefix)
{
struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
@@ -755,15 +791,15 @@ static int merge(int argc, const char **argv, const char *prefix)
if (strategy || do_commit + do_abort == 0)
do_merge = 1;
if (do_merge + do_commit + do_abort != 1) {
- error("cannot mix --commit, --abort or -s/--strategy");
+ error(_("cannot mix --commit, --abort or -s/--strategy"));
usage_with_options(git_notes_merge_usage, options);
}
if (do_merge && argc != 1) {
- error("Must specify a notes ref to merge");
+ error(_("must specify a notes ref to merge"));
usage_with_options(git_notes_merge_usage, options);
} else if (!do_merge && argc) {
- error("too many parameters");
+ error(_("too many parameters"));
usage_with_options(git_notes_merge_usage, options);
}
@@ -777,27 +813,31 @@ static int merge(int argc, const char **argv, const char *prefix)
o.local_ref = default_notes_ref();
strbuf_addstr(&remote_ref, argv[0]);
- expand_notes_ref(&remote_ref);
+ expand_loose_notes_ref(&remote_ref);
o.remote_ref = remote_ref.buf;
+ t = init_notes_check("merge", NOTES_INIT_WRITABLE);
+
if (strategy) {
- if (!strcmp(strategy, "manual"))
- o.strategy = NOTES_MERGE_RESOLVE_MANUAL;
- else if (!strcmp(strategy, "ours"))
- o.strategy = NOTES_MERGE_RESOLVE_OURS;
- else if (!strcmp(strategy, "theirs"))
- o.strategy = NOTES_MERGE_RESOLVE_THEIRS;
- else if (!strcmp(strategy, "union"))
- o.strategy = NOTES_MERGE_RESOLVE_UNION;
- else if (!strcmp(strategy, "cat_sort_uniq"))
- o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
- else {
- error("Unknown -s/--strategy: %s", strategy);
+ if (parse_notes_merge_strategy(strategy, &o.strategy)) {
+ error(_("unknown -s/--strategy: %s"), strategy);
usage_with_options(git_notes_merge_usage, options);
}
- }
+ } else {
+ struct strbuf merge_key = STRBUF_INIT;
+ const char *short_ref = NULL;
+
+ if (!skip_prefix(o.local_ref, "refs/notes/", &short_ref))
+ die("BUG: local ref %s is outside of refs/notes/",
+ o.local_ref);
- t = init_notes_check("merge");
+ strbuf_addf(&merge_key, "notes.%s.mergeStrategy", short_ref);
+
+ if (git_config_get_notes_strategy(merge_key.buf, &o.strategy))
+ git_config_get_notes_strategy("notes.mergeStrategy", &o.strategy);
+
+ strbuf_release(&merge_key);
+ }
strbuf_addf(&msg, "notes: Merged notes from %s into %s",
remote_ref.buf, default_notes_ref());
@@ -808,18 +848,23 @@ 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 */
+ const struct worktree *wt;
/* 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 */
+ wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
+ if (wt)
+ die(_("a notes merge into %s is already in-progress at %s"),
+ default_notes_ref(), wt->path);
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
- die("Failed to store link to current notes ref (%s)",
+ die(_("failed to store link to current notes ref (%s)"),
default_notes_ref());
- printf("Automatic notes merge failed. Fix conflicts in %s and "
- "commit the result with 'git notes merge --commit', or "
- "abort the merge with 'git notes merge --abort'.\n",
+ printf(_("Automatic notes merge failed. Fix conflicts in %s and "
+ "commit the result with 'git notes merge --commit', or "
+ "abort the merge with 'git notes merge --abort'.\n"),
git_path(NOTES_MERGE_WORKTREE));
}
@@ -863,7 +908,7 @@ static int remove_cmd(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options,
git_notes_remove_usage, 0);
- t = init_notes_check("remove");
+ t = init_notes_check("remove", NOTES_INIT_WRITABLE);
if (!argc && !from_stdin) {
retval = remove_one_note(t, "HEAD", flag);
@@ -892,8 +937,8 @@ static int prune(int argc, const char **argv, const char *prefix)
struct notes_tree *t;
int show_only = 0, verbose = 0;
struct option options[] = {
- OPT__DRY_RUN(&show_only, "do not remove, show only"),
- OPT__VERBOSE(&verbose, "report pruned notes"),
+ OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
+ OPT__VERBOSE(&verbose, N_("report pruned notes")),
OPT_END()
};
@@ -905,7 +950,7 @@ static int prune(int argc, const char **argv, const char *prefix)
usage_with_options(git_notes_prune_usage, options);
}
- t = init_notes_check("prune");
+ t = init_notes_check("prune", NOTES_INIT_WRITABLE);
prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) |
(show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) );
@@ -922,7 +967,7 @@ static int get_ref(int argc, const char **argv, const char *prefix)
git_notes_get_ref_usage, 0);
if (argc) {
- error("too many parameters");
+ error(_("too many parameters"));
usage_with_options(git_notes_get_ref_usage, options);
}
@@ -935,8 +980,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, N_("notes_ref"),
- N_("use notes from <notes_ref>")),
+ OPT_STRING(0, "ref", &override_notes_ref, N_("notes-ref"),
+ N_("use notes from <notes-ref>")),
OPT_END()
};
@@ -971,7 +1016,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
else if (!strcmp(argv[0], "get-ref"))
result = get_ref(argc, argv, prefix);
else {
- result = error(_("Unknown subcommand: %s"), argv[0]);
+ result = error(_("unknown subcommand: %s"), argv[0]);
usage_with_options(git_notes_usage, options);
}
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index c73337931b..f294dcffa9 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -20,10 +20,14 @@
#include "streaming.h"
#include "thread-utils.h"
#include "pack-bitmap.h"
+#include "reachable.h"
+#include "sha1-array.h"
+#include "argv-array.h"
+#include "mru.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]"),
+ N_("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]"),
+ N_("git pack-objects [<options>...] <base-name> [< <ref-list> | < <object-list>]"),
NULL
};
@@ -41,7 +45,9 @@ 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 pack_loose_unreachable;
static int local;
+static int have_non_local_packs;
static int incremental;
static int ignore_packed_keep;
static int allow_ofs_delta;
@@ -55,14 +61,13 @@ static int delta_search_threads;
static int pack_to_stdout;
static int num_preferred_base;
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 use_bitmap_index_default = 1;
+static int use_bitmap_index = -1;
static int write_bitmap_index;
static uint16_t write_bitmap_options;
@@ -89,8 +94,7 @@ static void index_commit_for_bitmap(struct commit *commit)
{
if (indexed_commits_nr >= indexed_commits_alloc) {
indexed_commits_alloc = (indexed_commits_alloc + 32) * 2;
- indexed_commits = xrealloc(indexed_commits,
- indexed_commits_alloc * sizeof(struct commit *));
+ REALLOC_ARRAY(indexed_commits, indexed_commits_alloc);
}
indexed_commits[indexed_commits_nr++] = commit;
@@ -123,7 +127,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);
@@ -151,7 +154,6 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct sha1fi
unsigned char obuf[1024 * 16];
unsigned long olen = 0;
- memset(&stream, 0, sizeof(stream));
git_deflate_init(&stream, pack_compression_level);
for (;;) {
@@ -341,15 +343,15 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent
}
/* 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)
+static off_t 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;
+ off_t datalen;
unsigned char header[10], dheader[10];
unsigned hdrlen;
@@ -415,11 +417,12 @@ static unsigned long write_reuse_object(struct sha1file *f, struct object_entry
}
/* 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 off_t write_object(struct sha1file *f,
+ struct object_entry *entry,
+ off_t write_offset)
{
- unsigned long limit, len;
+ unsigned long limit;
+ off_t len;
int usable_delta, to_reuse;
if (!pack_to_stdout)
@@ -491,7 +494,7 @@ static enum write_one_status write_one(struct sha1file *f,
struct object_entry *e,
off_t *offset)
{
- unsigned long size;
+ off_t size;
int recursing;
/*
@@ -540,11 +543,11 @@ static enum write_one_status write_one(struct sha1file *f,
return WRITE_ONE_WRITTEN;
}
-static int mark_tagged(const char *path, const unsigned char *sha1, int flag,
+static int mark_tagged(const char *path, const struct object_id *oid, int flag,
void *cb_data)
{
unsigned char peeled[20];
- struct object_entry *entry = packlist_find(&to_pack, sha1, NULL);
+ struct object_entry *entry = packlist_find(&to_pack, oid->hash, NULL);
if (entry)
entry->tagged = 1;
@@ -624,7 +627,7 @@ static struct object_entry **compute_write_order(void)
{
unsigned int i, wo_end, last_untagged;
- struct object_entry **wo = xmalloc(to_pack.nr_objects * sizeof(*wo));
+ struct object_entry **wo;
struct object_entry *objects = to_pack.objects;
for (i = 0; i < to_pack.nr_objects; i++) {
@@ -657,6 +660,7 @@ static struct object_entry **compute_write_order(void)
* Give the objects in the original recency order until
* we see a tagged tip.
*/
+ ALLOC_ARRAY(wo, to_pack.nr_objects);
for (i = wo_end = 0; i < to_pack.nr_objects; i++) {
if (objects[i].tagged)
break;
@@ -708,13 +712,13 @@ static struct object_entry **compute_write_order(void)
static off_t write_reused_pack(struct sha1file *f)
{
unsigned char buffer[8192];
- off_t to_write;
+ 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);
+ fd = git_open(reuse_packfile->pack_name);
if (fd < 0)
die_errno("unable to open packfile for reuse: %s",
reuse_packfile->pack_name);
@@ -725,7 +729,7 @@ static off_t write_reused_pack(struct sha1file *f)
if (reuse_packfile_offset < 0)
reuse_packfile_offset = reuse_packfile->pack_size - 20;
- to_write = reuse_packfile_offset - sizeof(struct pack_header);
+ total = to_write = reuse_packfile_offset - sizeof(struct pack_header);
while (to_write) {
int read_pack = xread(fd, buffer, sizeof(buffer));
@@ -738,13 +742,30 @@ static off_t write_reused_pack(struct sha1file *f)
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;
+ written = reuse_packfile_objects;
+ display_progress(progress_state, written);
return reuse_packfile_offset - sizeof(struct pack_header);
}
+static const char no_split_warning[] = N_(
+"disabling bitmap writing, packs are split due to pack.packSizeLimit"
+);
+
static void write_pack_file(void)
{
uint32_t i = 0, j;
@@ -755,8 +776,8 @@ static void write_pack_file(void)
struct object_entry **write_order;
if (progress > pack_to_stdout)
- progress_state = start_progress("Writing objects", nr_result);
- written_list = xmalloc(to_pack.nr_objects * sizeof(*written_list));
+ progress_state = start_progress(_("Writing objects"), nr_result);
+ ALLOC_ARRAY(written_list, to_pack.nr_objects);
write_order = compute_write_order();
do {
@@ -799,11 +820,15 @@ static void write_pack_file(void)
fixup_pack_header_footer(fd, sha1, pack_tmp_name,
nr_written, sha1, offset);
close(fd);
+ if (write_bitmap_index) {
+ warning(_(no_split_warning));
+ write_bitmap_index = 0;
+ }
}
if (!pack_to_stdout) {
struct stat st;
- char tmpname[PATH_MAX];
+ struct strbuf tmpname = STRBUF_INIT;
/*
* Packs are runtime accessed in their mtime
@@ -813,8 +838,7 @@ static void write_pack_file(void)
* to preserve this property.
*/
if (stat(pack_tmp_name, &st) < 0) {
- warning("failed to stat %s: %s",
- pack_tmp_name, strerror(errno));
+ warning_errno("failed to stat %s", pack_tmp_name);
} else if (!last_mtime) {
last_mtime = st.st_mtime;
} else {
@@ -822,27 +846,22 @@ static void write_pack_file(void)
utb.actime = st.st_atime;
utb.modtime = --last_mtime;
if (utime(pack_tmp_name, &utb) < 0)
- warning("failed utime() on %s: %s",
- tmpname, strerror(errno));
+ warning_errno("failed utime() on %s", pack_tmp_name);
}
- /* Enough space for "-<sha-1>.pack"? */
- if (sizeof(tmpname) <= strlen(base_name) + 50)
- die("pack base name '%s' too long", base_name);
- snprintf(tmpname, sizeof(tmpname), "%s-", base_name);
+ strbuf_addf(&tmpname, "%s-", base_name);
if (write_bitmap_index) {
bitmap_writer_set_checksum(sha1);
bitmap_writer_build_type_index(written_list, nr_written);
}
- finish_tmp_packfile(tmpname, pack_tmp_name,
+ finish_tmp_packfile(&tmpname, pack_tmp_name,
written_list, nr_written,
&pack_idx_opts, sha1);
if (write_bitmap_index) {
- char *end_of_name_prefix = strrchr(tmpname, 0);
- sprintf(end_of_name_prefix, "%s.bitmap", sha1_to_hex(sha1));
+ strbuf_addf(&tmpname, "%s.bitmap", sha1_to_hex(sha1));
stop_progress(&progress_state);
@@ -851,10 +870,11 @@ static void write_pack_file(void)
bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
bitmap_writer_build(&to_pack);
bitmap_writer_finish(written_list, nr_written,
- tmpname, write_bitmap_options);
+ tmpname.buf, write_bitmap_options);
write_bitmap_index = 0;
}
+ strbuf_release(&tmpname);
free(pack_tmp_name);
puts(sha1_to_hex(sha1));
}
@@ -874,24 +894,15 @@ static void write_pack_file(void)
written, nr_result);
}
-static void setup_delta_attr_check(struct git_attr_check *check)
-{
- static struct git_attr *attr_delta;
-
- if (!attr_delta)
- attr_delta = git_attr("delta");
-
- check[0].attr = attr_delta;
-}
-
static int no_try_delta(const char *path)
{
- struct git_attr_check check[1];
+ static struct attr_check *check;
- setup_delta_attr_check(check);
- if (git_check_attr(path, ARRAY_SIZE(check), check))
+ if (!check)
+ check = attr_check_initl("delta", NULL);
+ if (git_check_attr(path, check))
return 0;
- if (ATTR_FALSE(check->value))
+ if (ATTR_FALSE(check->items[0].value))
return 1;
return 0;
}
@@ -925,46 +936,92 @@ static int have_duplicate_entry(const unsigned char *sha1,
return 1;
}
+static int want_found_object(int exclude, struct packed_git *p)
+{
+ if (exclude)
+ return 1;
+ if (incremental)
+ return 0;
+
+ /*
+ * When asked to do --local (do not include an object that appears in a
+ * pack we borrow from elsewhere) or --honor-pack-keep (do not include
+ * an object that appears in a pack marked with .keep), finding a pack
+ * that matches the criteria is sufficient for us to decide to omit it.
+ * However, even if this pack does not satisfy the criteria, we need to
+ * make sure no copy of this object appears in _any_ pack that makes us
+ * to omit the object, so we need to check all the packs.
+ *
+ * We can however first check whether these options can possible matter;
+ * if they do not matter we know we want the object in generated pack.
+ * Otherwise, we signal "-1" at the end to tell the caller that we do
+ * not know either way, and it needs to check more packs.
+ */
+ if (!ignore_packed_keep &&
+ (!local || !have_non_local_packs))
+ return 1;
+
+ if (local && !p->pack_local)
+ return 0;
+ if (ignore_packed_keep && p->pack_local && p->pack_keep)
+ return 0;
+
+ /* we don't know yet; keep looking for more packs */
+ 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.
+ * If the caller already knows an existing pack it wants to take the object
+ * from, that is passed in *found_pack and *found_offset; otherwise this
+ * function finds if there is any pack that has the object and returns the pack
+ * and its offset in these variables.
*/
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;
+ struct mru_entry *entry;
+ int want;
if (!exclude && local && has_loose_object_nonlocal(sha1))
return 0;
- *found_pack = NULL;
- *found_offset = 0;
+ /*
+ * If we already know the pack object lives in, start checks from that
+ * pack - in the usual case when neither --local was given nor .keep files
+ * are present we will determine the answer right now.
+ */
+ if (*found_pack) {
+ want = want_found_object(exclude, *found_pack);
+ if (want != -1)
+ return want;
+ }
+
+ for (entry = packed_git_mru->head; entry; entry = entry->next) {
+ struct packed_git *p = entry->item;
+ off_t offset;
+
+ if (p == *found_pack)
+ offset = *found_offset;
+ else
+ offset = find_pack_entry_one(sha1, p);
- for (p = packed_git; p; p = p->next) {
- off_t offset = find_pack_entry_one(sha1, p);
if (offset) {
if (!*found_pack) {
- if (!is_pack_valid(p)) {
- warning("packfile %s cannot be accessed", p->pack_name);
+ if (!is_pack_valid(p))
continue;
- }
*found_offset = offset;
*found_pack = p;
}
- if (exclude)
- return 1;
- if (incremental)
- return 0;
- if (local && !p->pack_local)
- return 0;
- if (ignore_packed_keep && p->pack_local && p->pack_keep)
- return 0;
+ want = want_found_object(exclude, p);
+ if (!exclude && want > 0)
+ mru_mark(packed_git_mru, entry);
+ if (want != -1)
+ return want;
}
}
@@ -998,24 +1055,34 @@ static void create_object_entry(const unsigned char *sha1,
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;
+ struct packed_git *found_pack = NULL;
+ off_t found_offset = 0;
uint32_t index_pos;
if (have_duplicate_entry(sha1, exclude, &index_pos))
return 0;
- if (!want_object_in_pack(sha1, exclude, &found_pack, &found_offset))
+ 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;
+ }
create_object_entry(sha1, type, pack_name_hash(name),
exclude, name && no_try_delta(name),
index_pos, found_pack, found_offset);
- display_progress(progress_state, to_pack.nr_objects);
+ display_progress(progress_state, nr_result);
return 1;
}
@@ -1029,9 +1096,12 @@ static int add_object_entry_from_bitmap(const unsigned char *sha1,
if (have_duplicate_entry(sha1, 0, &index_pos))
return 0;
+ if (!want_object_in_pack(sha1, 0, &pack, &offset))
+ return 0;
+
create_object_entry(sha1, type, name_hash, 0, 0, index_pos, pack, offset);
- display_progress(progress_state, to_pack.nr_objects);
+ display_progress(progress_state, nr_result);
return 1;
}
@@ -1056,7 +1126,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.
*/
@@ -1166,7 +1236,7 @@ static void add_pbase_object(struct tree_desc *tree,
if (cmp < 0)
return;
if (name[cmplen] != '/') {
- add_object_entry(entry.sha1,
+ add_object_entry(entry.oid->hash,
object_type(entry.mode),
fullname, 1);
return;
@@ -1177,7 +1247,7 @@ static void add_pbase_object(struct tree_desc *tree,
const char *down = name+cmplen+1;
int downlen = name_cmp_len(down);
- tree = pbase_tree_get(entry.sha1);
+ tree = pbase_tree_get(entry.oid->hash);
if (!tree)
return;
init_tree_desc(&sub, tree->tree_data, tree->tree_size);
@@ -1213,12 +1283,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,
@@ -1453,6 +1520,170 @@ static int pack_offset_sort(const void *_a, const void *_b)
(a->in_pack_offset > b->in_pack_offset);
}
+/*
+ * Drop an on-disk delta we were planning to reuse. Naively, this would
+ * just involve blanking out the "delta" field, but we have to deal
+ * with some extra book-keeping:
+ *
+ * 1. Removing ourselves from the delta_sibling linked list.
+ *
+ * 2. Updating our size/type to the non-delta representation. These were
+ * either not recorded initially (size) or overwritten with the delta type
+ * (type) when check_object() decided to reuse the delta.
+ *
+ * 3. Resetting our delta depth, as we are now a base object.
+ */
+static void drop_reused_delta(struct object_entry *entry)
+{
+ struct object_entry **p = &entry->delta->delta_child;
+ struct object_info oi = OBJECT_INFO_INIT;
+
+ while (*p) {
+ if (*p == entry)
+ *p = (*p)->delta_sibling;
+ else
+ p = &(*p)->delta_sibling;
+ }
+ entry->delta = NULL;
+ entry->depth = 0;
+
+ oi.sizep = &entry->size;
+ oi.typep = &entry->type;
+ if (packed_object_info(entry->in_pack, entry->in_pack_offset, &oi) < 0) {
+ /*
+ * We failed to get the info from this pack for some reason;
+ * fall back to sha1_object_info, which may find another copy.
+ * And if that fails, the error will be recorded in entry->type
+ * and dealt with in prepare_pack().
+ */
+ entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
+ }
+}
+
+/*
+ * Follow the chain of deltas from this entry onward, throwing away any links
+ * that cause us to hit a cycle (as determined by the DFS state flags in
+ * the entries).
+ *
+ * We also detect too-long reused chains that would violate our --depth
+ * limit.
+ */
+static void break_delta_chains(struct object_entry *entry)
+{
+ /*
+ * The actual depth of each object we will write is stored as an int,
+ * as it cannot exceed our int "depth" limit. But before we break
+ * changes based no that limit, we may potentially go as deep as the
+ * number of objects, which is elsewhere bounded to a uint32_t.
+ */
+ uint32_t total_depth;
+ struct object_entry *cur, *next;
+
+ for (cur = entry, total_depth = 0;
+ cur;
+ cur = cur->delta, total_depth++) {
+ if (cur->dfs_state == DFS_DONE) {
+ /*
+ * We've already seen this object and know it isn't
+ * part of a cycle. We do need to append its depth
+ * to our count.
+ */
+ total_depth += cur->depth;
+ break;
+ }
+
+ /*
+ * We break cycles before looping, so an ACTIVE state (or any
+ * other cruft which made its way into the state variable)
+ * is a bug.
+ */
+ if (cur->dfs_state != DFS_NONE)
+ die("BUG: confusing delta dfs state in first pass: %d",
+ cur->dfs_state);
+
+ /*
+ * Now we know this is the first time we've seen the object. If
+ * it's not a delta, we're done traversing, but we'll mark it
+ * done to save time on future traversals.
+ */
+ if (!cur->delta) {
+ cur->dfs_state = DFS_DONE;
+ break;
+ }
+
+ /*
+ * Mark ourselves as active and see if the next step causes
+ * us to cycle to another active object. It's important to do
+ * this _before_ we loop, because it impacts where we make the
+ * cut, and thus how our total_depth counter works.
+ * E.g., We may see a partial loop like:
+ *
+ * A -> B -> C -> D -> B
+ *
+ * Cutting B->C breaks the cycle. But now the depth of A is
+ * only 1, and our total_depth counter is at 3. The size of the
+ * error is always one less than the size of the cycle we
+ * broke. Commits C and D were "lost" from A's chain.
+ *
+ * If we instead cut D->B, then the depth of A is correct at 3.
+ * We keep all commits in the chain that we examined.
+ */
+ cur->dfs_state = DFS_ACTIVE;
+ if (cur->delta->dfs_state == DFS_ACTIVE) {
+ drop_reused_delta(cur);
+ cur->dfs_state = DFS_DONE;
+ break;
+ }
+ }
+
+ /*
+ * And now that we've gone all the way to the bottom of the chain, we
+ * need to clear the active flags and set the depth fields as
+ * appropriate. Unlike the loop above, which can quit when it drops a
+ * delta, we need to keep going to look for more depth cuts. So we need
+ * an extra "next" pointer to keep going after we reset cur->delta.
+ */
+ for (cur = entry; cur; cur = next) {
+ next = cur->delta;
+
+ /*
+ * We should have a chain of zero or more ACTIVE states down to
+ * a final DONE. We can quit after the DONE, because either it
+ * has no bases, or we've already handled them in a previous
+ * call.
+ */
+ if (cur->dfs_state == DFS_DONE)
+ break;
+ else if (cur->dfs_state != DFS_ACTIVE)
+ die("BUG: confusing delta dfs state in second pass: %d",
+ cur->dfs_state);
+
+ /*
+ * If the total_depth is more than depth, then we need to snip
+ * the chain into two or more smaller chains that don't exceed
+ * the maximum depth. Most of the resulting chains will contain
+ * (depth + 1) entries (i.e., depth deltas plus one base), and
+ * the last chain (i.e., the one containing entry) will contain
+ * whatever entries are left over, namely
+ * (total_depth % (depth + 1)) of them.
+ *
+ * Since we are iterating towards decreasing depth, we need to
+ * decrement total_depth as we go, and we need to write to the
+ * entry what its final depth will be after all of the
+ * snipping. Since we're snipping into chains of length (depth
+ * + 1) entries, the final depth of an entry will be its
+ * original depth modulo (depth + 1). Any time we encounter an
+ * entry whose final depth is supposed to be zero, we snip it
+ * from its delta base, thereby making it so.
+ */
+ cur->depth = (total_depth--) % (depth + 1);
+ if (!cur->depth)
+ drop_reused_delta(cur);
+
+ cur->dfs_state = DFS_DONE;
+ }
+}
+
static void get_object_details(void)
{
uint32_t i;
@@ -1461,7 +1692,7 @@ static void get_object_details(void)
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);
+ QSORT(sorted_by_offset, to_pack.nr_objects, pack_offset_sort);
for (i = 0; i < to_pack.nr_objects; i++) {
struct object_entry *entry = sorted_by_offset[i];
@@ -1470,6 +1701,13 @@ static void get_object_details(void)
entry->no_try_delta = 1;
}
+ /*
+ * This must happen in a second pass, since we rely on the delta
+ * information for the whole list being completed.
+ */
+ for (i = 0; i < to_pack.nr_objects; i++)
+ break_delta_chains(&to_pack.objects[i]);
+
free(sorted_by_offset);
}
@@ -1956,8 +2194,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();
@@ -2083,14 +2319,43 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p)
#endif
-static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+static void add_tag_chain(const struct object_id *oid)
{
- unsigned char peeled[20];
+ struct tag *tag;
+
+ /*
+ * We catch duplicates already in add_object_entry(), but we'd
+ * prefer to do this extra check to avoid having to parse the
+ * tag at all if we already know that it's being packed (e.g., if
+ * it was included via bitmaps, we would not have parsed it
+ * previously).
+ */
+ if (packlist_find(&to_pack, oid->hash, NULL))
+ return;
+
+ tag = lookup_tag(oid->hash);
+ while (1) {
+ if (!tag || parse_tag(tag) || !tag->tagged)
+ die("unable to pack objects reachable from tag %s",
+ oid_to_hex(oid));
+
+ add_object_entry(tag->object.oid.hash, OBJ_TAG, NULL, 0);
+
+ if (tag->tagged->type != OBJ_TAG)
+ return;
+
+ tag = (struct tag *)tag->tagged;
+ }
+}
+
+static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data)
+{
+ struct object_id peeled;
if (starts_with(path, "refs/tags/") && /* is a tag? */
- !peel_ref(path, peeled) && /* peelable? */
- packlist_find(&to_pack, peeled, NULL)) /* object packed? */
- add_object_entry(sha1, OBJ_TAG, NULL, 0);
+ !peel_ref(path, peeled.hash) && /* peelable? */
+ packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */
+ add_tag_chain(oid);
return 0;
}
@@ -2115,7 +2380,7 @@ static void prepare_pack(int window, int depth)
if (!to_pack.nr_objects || !window || !depth)
return;
- delta_list = xmalloc(to_pack.nr_objects * sizeof(*delta_list));
+ ALLOC_ARRAY(delta_list, to_pack.nr_objects);
nr_deltas = n = 0;
for (i = 0; i < to_pack.nr_objects; i++) {
@@ -2154,9 +2419,9 @@ 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);
+ QSORT(delta_list, n, type_size_sort);
ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
stop_progress(&progress_state);
if (nr_done != nr_deltas)
@@ -2179,16 +2444,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)
depth = git_config_int(k, v);
return 0;
}
- if (!strcmp(k, "pack.compression")) {
- int level = git_config_int(k, v);
- 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;
- pack_compression_seen = 1;
- return 0;
- }
if (!strcmp(k, "pack.deltacachesize")) {
max_delta_cache_size = git_config_int(k, v);
return 0;
@@ -2197,10 +2452,6 @@ static int git_pack_config(const char *k, const char *v, void *cb)
cache_max_small_delta_size = git_config_int(k, v);
return 0;
}
- if (!strcmp(k, "pack.writebitmaps")) {
- write_bitmap_index = git_config_bool(k, v);
- return 0;
- }
if (!strcmp(k, "pack.writebitmaphashcache")) {
if (git_config_bool(k, v))
write_bitmap_options |= BITMAP_OPT_HASH_CACHE;
@@ -2208,7 +2459,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)
write_bitmap_options &= ~BITMAP_OPT_HASH_CACHE;
}
if (!strcmp(k, "pack.usebitmaps")) {
- use_bitmap_index = git_config_bool(k, v);
+ use_bitmap_index_default = git_config_bool(k, v);
return 0;
}
if (!strcmp(k, "pack.threads")) {
@@ -2267,33 +2518,23 @@ static void read_object_list_from_stdin(void)
static void show_commit(struct commit *commit, void *data)
{
- add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
+ add_object_entry(commit->object.oid.hash, 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,
- void *data)
+static void show_object(struct object *obj, const char *name, void *data)
{
- char *name = path_name(path, last);
-
add_preferred_base_object(name);
- add_object_entry(obj->sha1, obj->type, name, 0);
+ add_object_entry(obj->oid.hash, obj->type, name, 0);
obj->flags |= OBJECT_ADDED;
-
- /*
- * We will have generated the hash from the name,
- * but not saved a pointer to it - we can free it
- */
- free((char *)name);
}
static void show_edge(struct commit *commit)
{
- add_preferred_base(commit->object.sha1);
+ add_preferred_base(commit->object.oid.hash);
}
struct in_pack_object {
@@ -2309,7 +2550,7 @@ struct in_pack {
static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
{
- in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p);
+ in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->oid.hash, p);
in_pack->array[in_pack->nr].object = object;
in_pack->nr++;
}
@@ -2328,7 +2569,7 @@ static int ofscmp(const void *a_, const void *b_)
else if (a->offset > b->offset)
return 1;
else
- return hashcmp(a->object->sha1, b->object->sha1);
+ return oidcmp(&a->object->oid, &b->object->oid);
}
static void add_objects_in_unpacked_packs(struct rev_info *revs)
@@ -2362,16 +2603,41 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
}
if (in_pack.nr) {
- qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]),
- ofscmp);
+ QSORT(in_pack.array, in_pack.nr, ofscmp);
for (i = 0; i < in_pack.nr; i++) {
struct object *o = in_pack.array[i].object;
- add_object_entry(o->sha1, o->type, "", 0);
+ add_object_entry(o->oid.hash, o->type, "", 0);
}
}
free(in_pack.array);
}
+static int add_loose_object(const unsigned char *sha1, const char *path,
+ void *data)
+{
+ enum object_type type = sha1_object_info(sha1, NULL);
+
+ if (type < 0) {
+ warning("loose object at %s could not be examined", path);
+ return 0;
+ }
+
+ add_object_entry(sha1, type, "", 0);
+ return 0;
+}
+
+/*
+ * We actually don't even have to worry about reachability here.
+ * add_object_entry will weed out duplicates, so we just add every
+ * loose object we find.
+ */
+static void add_unreachable_loose_objects(void)
+{
+ for_each_loose_file_in_objdir(get_object_directory(),
+ add_loose_object,
+ NULL, NULL, NULL);
+}
+
static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1)
{
static struct packed_git *last_found = (void *)1;
@@ -2395,6 +2661,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;
@@ -2405,46 +2692,61 @@ static void loosen_unused_packed_objects(struct rev_info *revs)
if (!p->pack_local || p->pack_keep)
continue;
- if (unpack_unreachable_expiration &&
- p->mtime < unpack_unreachable_expiration)
- continue;
-
if (open_pack_index(p))
die("cannot open pack index");
for (i = 0; i < p->num_objects; i++) {
sha1 = nth_packed_object_sha1(p, i);
if (!packlist_find(&to_pack, sha1, NULL) &&
- !has_sha1_pack_kept_or_nonlocal(sha1))
+ !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 pack-reuse code expects to be on, or 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 pack_to_stdout && allow_ofs_delta;
+}
+
static int get_object_list_from_bitmap(struct rev_info *revs)
{
if (prepare_bitmap_walk(revs) < 0)
return -1;
- if (!reuse_partial_packfile_from_bitmap(
+ 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;
-
- if (progress) {
- fprintf(stderr, "Reusing existing pack: %d, done.\n",
- reuse_packfile_objects);
- fflush(stderr);
- }
+ 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 char *name,
+ void *data)
+{
+ sha1_array_append(&recent_objects, obj->oid.hash);
+}
+
+static void record_recent_commit(struct commit *commit, void *data)
+{
+ sha1_array_append(&recent_objects, commit->object.oid.hash);
+}
+
static void get_object_list(int ac, const char **av)
{
struct rev_info revs;
@@ -2455,6 +2757,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')
@@ -2467,6 +2772,14 @@ static void get_object_list(int ac, const char **av)
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, REVARG_CANNOT_BE_FILENAME))
@@ -2481,10 +2794,25 @@ static void get_object_list(int ac, const char **av)
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 (pack_loose_unreachable)
+ add_unreachable_loose_objects();
if (unpack_unreachable)
loosen_unused_packed_objects(&revs);
+
+ sha1_array_clear(&recent_objects);
}
static int option_parse_index_version(const struct option *opt,
@@ -2517,31 +2845,15 @@ static int option_parse_unpack_unreachable(const struct option *opt,
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;
- const char *rp_av[6];
- int rp_ac = 0;
+ 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),
@@ -2555,16 +2867,16 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{ 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_MAGNITUDE(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_MAGNITUDE(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,
@@ -2588,17 +2900,24 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{ 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")),
+ OPT_BOOL(0, "pack-loose-unreachable", &pack_loose_unreachable,
+ N_("pack loose 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,
@@ -2612,12 +2931,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
OPT_END(),
};
- read_replace_refs = 0;
+ check_replace_refs = 0;
reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
- if (!pack_compression_seen && core_compression_seen)
- pack_compression_level = core_compression_level;
progress = isatty(2);
argc = parse_options(argc, argv, prefix, pack_objects_options,
@@ -2630,24 +2947,30 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (pack_to_stdout != !base_name || argc)
usage_with_options(pack_usage, pack_objects_options);
- rp_av[rp_ac++] = "pack-objects";
+ argv_array_push(&rp, "pack-objects");
if (thin) {
use_internal_rev_list = 1;
- rp_av[rp_ac++] = "--objects-edge";
+ argv_array_push(&rp, shallow
+ ? "--objects-edge-aggressive"
+ : "--objects-edge");
} else
- rp_av[rp_ac++] = "--objects";
+ argv_array_push(&rp, "--objects");
if (rev_list_all) {
use_internal_rev_list = 1;
- rp_av[rp_ac++] = "--all";
+ argv_array_push(&rp, "--all");
}
if (rev_list_reflog) {
use_internal_rev_list = 1;
- rp_av[rp_ac++] = "--reflog";
+ 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;
- rp_av[rp_ac++] = "--unpacked";
+ argv_array_push(&rp, "--unpacked");
}
if (!reuse_object)
@@ -2656,6 +2979,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
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");
@@ -2674,8 +3001,26 @@ 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())
+ /*
+ * "soft" reasons not to use bitmaps - for on-disk repack by default we want
+ *
+ * - to produce good pack (with bitmap index not-yet-packed objects are
+ * packed in suboptimal order).
+ *
+ * - to use more robust pack-generation codepath (avoiding possible
+ * bugs in bitmap code and possible bitmap index corruption).
+ */
+ if (!pack_to_stdout)
+ use_bitmap_index_default = 0;
+
+ if (use_bitmap_index < 0)
+ use_bitmap_index = use_bitmap_index_default;
+
+ /* "hard" reasons not to use bitmaps; these just won't work at all */
+ if (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) || is_repository_shallow())
use_bitmap_index = 0;
if (pack_to_stdout || !rev_list_all)
@@ -2685,14 +3030,36 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
progress = 2;
prepare_packed_git();
+ if (ignore_packed_keep) {
+ struct packed_git *p;
+ for (p = packed_git; p; p = p->next)
+ if (p->pack_local && p->pack_keep)
+ break;
+ if (!p) /* no keep-able packs found */
+ ignore_packed_keep = 0;
+ }
+ if (local) {
+ /*
+ * unlike ignore_packed_keep above, we do not want to
+ * unset "local" based on looking at packs, as it
+ * also covers non-local objects
+ */
+ struct packed_git *p;
+ for (p = packed_git; p; p = p->next) {
+ if (!p->pack_local) {
+ have_non_local_packs = 1;
+ break;
+ }
+ }
+ }
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 649c3aaa93..72c815844d 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -11,7 +11,7 @@
#define BLKSIZE 512
static const char pack_redundant_usage[] =
-"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
+"git pack-redundant [--verbose] [--alt-odb] (--all | <filename.pack>...)";
static int load_all_packs, verbose, alt_odb;
@@ -53,7 +53,7 @@ static inline struct llist_item *llist_item_get(void)
free_nodes = free_nodes->next;
} else {
int i = 1;
- new = xmalloc(sizeof(struct llist_item) * BLKSIZE);
+ ALLOC_ARRAY(new, BLKSIZE);
for (; i < BLKSIZE; i++)
llist_item_put(&new[i]);
}
diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c
index b20b1ec4c1..39f9a55d16 100644
--- a/builtin/pack-refs.c
+++ b/builtin/pack-refs.c
@@ -3,7 +3,7 @@
#include "refs.h"
static char const * const pack_refs_usage[] = {
- N_("git pack-refs [options]"),
+ N_("git pack-refs [<options>]"),
NULL
};
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index 3cfe02d5a5..a84d0003a3 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, struct object_id *id, struct object_id *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);
+ memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1);
+ printf("%s %s\n", oid_to_hex(result), name);
}
static int remove_space(char *line)
@@ -56,39 +53,57 @@ 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, struct strbuf *line_buf)
+static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx)
+{
+ unsigned char hash[GIT_SHA1_RAWSZ];
+ unsigned short carry = 0;
+ int i;
+
+ git_SHA1_Final(hash, ctx);
+ git_SHA1_Init(ctx);
+ /* 20-byte sum, with carry */
+ for (i = 0; i < GIT_SHA1_RAWSZ; ++i) {
+ carry += result->hash[i] + hash[i];
+ result->hash[i] = carry;
+ carry >>= 8;
+ }
+}
+
+static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
+ struct strbuf *line_buf, int stable)
{
int patchlen = 0, found_next = 0;
int before = -1, after = -1;
+ git_SHA_CTX ctx;
+
+ git_SHA1_Init(&ctx);
+ oidclr(result);
while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
char *line = line_buf->buf;
- char *p = line;
+ const char *p = line;
int len;
- if (!memcmp(line, "diff-tree ", 10))
- p += 10;
- else if (!memcmp(line, "commit ", 7))
- p += 7;
- else if (!memcmp(line, "From ", 5))
- p += 5;
- else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line))
+ if (!skip_prefix(line, "diff-tree ", &p) &&
+ !skip_prefix(line, "commit ", &p) &&
+ !skip_prefix(line, "From ", &p) &&
+ starts_with(line, "\\ ") && 12 < strlen(line))
continue;
- if (!get_sha1_hex(p, next_sha1)) {
+ if (!get_oid_hex(p, next_oid)) {
found_next = 1;
break;
}
/* Ignore commit comments */
- if (!patchlen && memcmp(line, "diff ", 5))
+ if (!patchlen && !starts_with(line, "diff "))
continue;
/* Parsing diff header? */
if (before == -1) {
- if (!memcmp(line, "index ", 6))
+ if (starts_with(line, "index "))
continue;
- else if (!memcmp(line, "--- ", 4))
+ else if (starts_with(line, "--- "))
before = after = 1;
else if (!isalpha(line[0]))
break;
@@ -96,17 +111,19 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st
/* Looking for a valid hunk header? */
if (before == 0 && after == 0) {
- if (!memcmp(line, "@@ -", 4)) {
+ if (starts_with(line, "@@ -")) {
/* Parse next hunk, but ignore line numbers. */
scan_hunk_header(line, &before, &after);
continue;
}
/* Split at the end of the patch. */
- if (memcmp(line, "diff ", 5))
+ if (!starts_with(line, "diff "))
break;
/* Else we're parsing another header. */
+ if (stable)
+ flush_one_hunk(result, &ctx);
before = after = -1;
}
@@ -119,39 +136,63 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st
/* 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);
+ oidclr(next_oid);
+
+ 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;
+ struct object_id oid, n, result;
int patchlen;
struct strbuf line_buf = STRBUF_INIT;
- git_SHA1_Init(&ctx);
- hashclr(sha1);
+ oidclr(&oid);
while (!feof(stdin)) {
- patchlen = get_one_patchid(n, &ctx, &line_buf);
- flush_current_id(patchlen, sha1, &ctx);
- hashcpy(sha1, n);
+ patchlen = get_one_patchid(&n, &result, &line_buf, stable);
+ flush_current_id(patchlen, &oid, &result);
+ oidcpy(&oid, &n);
}
strbuf_release(&line_buf);
}
-static const char patch_id_usage[] = "git patch-id < patch";
+static const char patch_id_usage[] = "git patch-id [--stable | --unstable]";
+
+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 fcf5fb6129..7cf900ea07 100644
--- a/builtin/prune-packed.c
+++ b/builtin/prune-packed.c
@@ -4,69 +4,47 @@
#include "parse-options.h"
static const char * const prune_packed_usage[] = {
- N_("git prune-packed [-n|--dry-run] [-q|--quiet]"),
+ N_("git prune-packed [-n | --dry-run] [-q | --quiet]"),
NULL
};
static struct progress *progress;
-static void prune_dir(int i, DIR *dir, struct strbuf *pathname, int opts)
+static int prune_subdir(int nr, const char *path, void *data)
{
- struct dirent *de;
- char hex[40];
- int top_len = pathname->len;
+ 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)
+{
+ int *opts = data;
- 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;
+ if (!has_sha1_pack(sha1))
+ return 0;
- strbuf_add(pathname, de->d_name, 38);
- if (opts & PRUNE_PACKED_DRY_RUN)
- printf("rm -f %s\n", pathname->buf);
- else
- unlink_or_warn(pathname->buf);
- display_progress(progress, i + 1);
- strbuf_setlen(pathname, top_len);
- }
+ 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;
- const char *dir = get_object_directory();
- struct strbuf pathname = STRBUF_INIT;
- int top_len;
-
- strbuf_addstr(&pathname, dir);
if (opts & PRUNE_PACKED_VERBOSE)
- progress = start_progress_delay("Removing duplicate objects",
+ progress = start_progress_delay(_("Removing duplicate objects"),
256, 95, 2);
- if (pathname.len && pathname.buf[pathname.len - 1] != '/')
- strbuf_addch(&pathname, '/');
-
- top_len = 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);
- strbuf_setlen(&pathname, top_len);
- strbuf_addf(&pathname, "%02x/", i);
- d = opendir(pathname.buf);
- if (!d)
- continue;
- prune_dir(i, d, &pathname, opts);
- closedir(d);
- strbuf_setlen(&pathname, top_len + 2);
- rmdir(pathname.buf);
- }
+ /* Ensure we show 100% before finishing progress */
+ display_progress(progress, 256);
stop_progress(&progress);
}
diff --git a/builtin/prune.c b/builtin/prune.c
index de43b26cfe..8f4f052285 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -6,7 +6,6 @@
#include "reachable.h"
#include "parse-options.h"
#include "progress.h"
-#include "dir.h"
static const char * const prune_usage[] = {
N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
@@ -31,11 +30,23 @@ static int prune_tmp_file(const char *fullpath)
return 0;
}
-static int prune_object(const char *fullpath, const unsigned char *sha1)
+static int prune_object(const unsigned char *sha1, const char *fullpath,
+ void *data)
{
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) {
@@ -48,68 +59,20 @@ static int prune_object(const char *fullpath, const unsigned char *sha1)
return 0;
}
-static int prune_dir(int i, struct strbuf *path)
+static int prune_cruft(const char *basename, const char *path, void *data)
{
- size_t baselen = path->len;
- DIR *dir = opendir(path->buf);
- 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;
-
- strbuf_addf(path, "/%s", de->d_name);
- prune_object(path->buf, sha1);
- strbuf_setlen(path, baselen);
- continue;
- }
- if (starts_with(de->d_name, "tmp_obj_")) {
- strbuf_addf(path, "/%s", de->d_name);
- prune_tmp_file(path->buf);
- strbuf_setlen(path, baselen);
- continue;
- }
- fprintf(stderr, "bad sha1 file: %s/%s\n", path->buf, de->d_name);
- }
- closedir(dir);
- if (!show_only)
- rmdir(path->buf);
+ 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)
{
- struct strbuf buf = STRBUF_INIT;
- size_t baselen;
- int i;
-
- strbuf_addstr(&buf, path);
- strbuf_addch(&buf, '/');
- baselen = buf.len;
-
- for (i = 0; i < 256; i++) {
- strbuf_addf(&buf, "%02x", i);
- prune_dir(i, &buf);
- strbuf_setlen(&buf, baselen);
- }
+ if (!show_only)
+ rmdir(path);
+ return 0;
}
/*
@@ -150,10 +113,15 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
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);
+
+ if (repository_format_precious_objects)
+ die(_("cannot prune in a precious-objects repo"));
+
while (argc--) {
unsigned char sha1[20];
const char *name = *argv++;
@@ -169,11 +137,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
if (show_progress == -1)
show_progress = isatty(2);
if (show_progress)
- progress = start_progress_delay("Checking connectivity", 0, 0, 2);
+ progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2);
- mark_reachable_objects(&revs, 1, progress);
+ mark_reachable_objects(&revs, 1, expire, progress);
stop_progress(&progress);
- prune_object_dir(get_object_directory());
+ 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());
diff --git a/builtin/pull.c b/builtin/pull.c
new file mode 100644
index 0000000000..3ecb881b0b
--- /dev/null
+++ b/builtin/pull.c
@@ -0,0 +1,880 @@
+/*
+ * Builtin "git pull"
+ *
+ * Based on git-pull.sh by Junio C Hamano
+ *
+ * Fetch one or more remote refs and merge it/them into the current HEAD.
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "exec_cmd.h"
+#include "run-command.h"
+#include "sha1-array.h"
+#include "remote.h"
+#include "dir.h"
+#include "refs.h"
+#include "revision.h"
+#include "tempfile.h"
+#include "lockfile.h"
+#include "wt-status.h"
+
+enum rebase_type {
+ REBASE_INVALID = -1,
+ REBASE_FALSE = 0,
+ REBASE_TRUE,
+ REBASE_PRESERVE,
+ REBASE_INTERACTIVE
+};
+
+/**
+ * Parses the value of --rebase. If value is a false value, returns
+ * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
+ * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with
+ * a fatal error if fatal is true, otherwise returns REBASE_INVALID.
+ */
+static enum rebase_type parse_config_rebase(const char *key, const char *value,
+ int fatal)
+{
+ int v = git_config_maybe_bool("pull.rebase", value);
+
+ if (!v)
+ return REBASE_FALSE;
+ else if (v > 0)
+ return REBASE_TRUE;
+ else if (!strcmp(value, "preserve"))
+ return REBASE_PRESERVE;
+ else if (!strcmp(value, "interactive"))
+ return REBASE_INTERACTIVE;
+
+ if (fatal)
+ die(_("Invalid value for %s: %s"), key, value);
+ else
+ error(_("Invalid value for %s: %s"), key, value);
+
+ return REBASE_INVALID;
+}
+
+/**
+ * Callback for --rebase, which parses arg with parse_config_rebase().
+ */
+static int parse_opt_rebase(const struct option *opt, const char *arg, int unset)
+{
+ enum rebase_type *value = opt->value;
+
+ if (arg)
+ *value = parse_config_rebase("--rebase", arg, 0);
+ else
+ *value = unset ? REBASE_FALSE : REBASE_TRUE;
+ return *value == REBASE_INVALID ? -1 : 0;
+}
+
+static const char * const pull_usage[] = {
+ N_("git pull [<options>] [<repository> [<refspec>...]]"),
+ NULL
+};
+
+/* Shared options */
+static int opt_verbosity;
+static char *opt_progress;
+
+/* Options passed to git-merge or git-rebase */
+static enum rebase_type opt_rebase = -1;
+static char *opt_diffstat;
+static char *opt_log;
+static char *opt_squash;
+static char *opt_commit;
+static char *opt_edit;
+static char *opt_ff;
+static char *opt_verify_signatures;
+static int opt_autostash = -1;
+static int config_autostash;
+static struct argv_array opt_strategies = ARGV_ARRAY_INIT;
+static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT;
+static char *opt_gpg_sign;
+static int opt_allow_unrelated_histories;
+
+/* Options passed to git-fetch */
+static char *opt_all;
+static char *opt_append;
+static char *opt_upload_pack;
+static int opt_force;
+static char *opt_tags;
+static char *opt_prune;
+static char *opt_recurse_submodules;
+static char *max_children;
+static int opt_dry_run;
+static char *opt_keep;
+static char *opt_depth;
+static char *opt_unshallow;
+static char *opt_update_shallow;
+static char *opt_refmap;
+
+static struct option pull_options[] = {
+ /* Shared options */
+ OPT__VERBOSITY(&opt_verbosity),
+ OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
+ N_("force progress reporting"),
+ PARSE_OPT_NOARG),
+
+ /* Options passed to git-merge or git-rebase */
+ OPT_GROUP(N_("Options related to merging")),
+ { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
+ "false|true|preserve|interactive",
+ N_("incorporate changes by rebasing rather than merging"),
+ PARSE_OPT_OPTARG, parse_opt_rebase },
+ OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
+ N_("do not show a diffstat at the end of the merge"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+ OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL,
+ N_("show a diffstat at the end of the merge"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL,
+ N_("(synonym to --stat)"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN),
+ OPT_PASSTHRU(0, "log", &opt_log, N_("n"),
+ N_("add (at most <n>) entries from shortlog to merge commit message"),
+ PARSE_OPT_OPTARG),
+ OPT_PASSTHRU(0, "squash", &opt_squash, NULL,
+ N_("create a single commit instead of doing a merge"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "commit", &opt_commit, NULL,
+ N_("perform a commit if the merge succeeds (default)"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "edit", &opt_edit, NULL,
+ N_("edit message before committing"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
+ N_("allow fast-forward"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL,
+ N_("abort if fast-forward is not possible"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+ OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL,
+ N_("verify that the named commit has a valid GPG signature"),
+ PARSE_OPT_NOARG),
+ OPT_BOOL(0, "autostash", &opt_autostash,
+ N_("automatically stash/stash pop before and after rebase")),
+ OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"),
+ N_("merge strategy to use"),
+ 0),
+ OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts,
+ N_("option=value"),
+ N_("option for selected merge strategy"),
+ 0),
+ OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"),
+ N_("GPG sign commit"),
+ PARSE_OPT_OPTARG),
+ OPT_SET_INT(0, "allow-unrelated-histories",
+ &opt_allow_unrelated_histories,
+ N_("allow merging unrelated histories"), 1),
+
+ /* Options passed to git-fetch */
+ OPT_GROUP(N_("Options related to fetching")),
+ OPT_PASSTHRU(0, "all", &opt_all, NULL,
+ N_("fetch from all remotes"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU('a', "append", &opt_append, NULL,
+ N_("append to .git/FETCH_HEAD instead of overwriting"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"),
+ N_("path to upload pack on remote end"),
+ 0),
+ OPT__FORCE(&opt_force, N_("force overwrite of local branch")),
+ OPT_PASSTHRU('t', "tags", &opt_tags, NULL,
+ N_("fetch all tags and associated objects"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU('p', "prune", &opt_prune, NULL,
+ N_("prune remote-tracking branches no longer on remote"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "recurse-submodules", &opt_recurse_submodules,
+ N_("on-demand"),
+ N_("control recursive fetching of submodules"),
+ PARSE_OPT_OPTARG),
+ OPT_PASSTHRU('j', "jobs", &max_children, N_("n"),
+ N_("number of submodules pulled in parallel"),
+ PARSE_OPT_OPTARG),
+ OPT_BOOL(0, "dry-run", &opt_dry_run,
+ N_("dry run")),
+ OPT_PASSTHRU('k', "keep", &opt_keep, NULL,
+ N_("keep downloaded pack"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
+ N_("deepen history of shallow clone"),
+ 0),
+ OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
+ N_("convert to a complete repository"),
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL,
+ N_("accept refs that update .git/shallow"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
+ N_("specify fetch refmap"),
+ PARSE_OPT_NONEG),
+
+ OPT_END()
+};
+
+/**
+ * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level.
+ */
+static void argv_push_verbosity(struct argv_array *arr)
+{
+ int verbosity;
+
+ for (verbosity = opt_verbosity; verbosity > 0; verbosity--)
+ argv_array_push(arr, "-v");
+
+ for (verbosity = opt_verbosity; verbosity < 0; verbosity++)
+ argv_array_push(arr, "-q");
+}
+
+/**
+ * Pushes "-f" switches into arr to match the opt_force level.
+ */
+static void argv_push_force(struct argv_array *arr)
+{
+ int force = opt_force;
+ while (force-- > 0)
+ argv_array_push(arr, "-f");
+}
+
+/**
+ * Sets the GIT_REFLOG_ACTION environment variable to the concatenation of argv
+ */
+static void set_reflog_message(int argc, const char **argv)
+{
+ int i;
+ struct strbuf msg = STRBUF_INIT;
+
+ for (i = 0; i < argc; i++) {
+ if (i)
+ strbuf_addch(&msg, ' ');
+ strbuf_addstr(&msg, argv[i]);
+ }
+
+ setenv("GIT_REFLOG_ACTION", msg.buf, 0);
+
+ strbuf_release(&msg);
+}
+
+/**
+ * If pull.ff is unset, returns NULL. If pull.ff is "true", returns "--ff". If
+ * pull.ff is "false", returns "--no-ff". If pull.ff is "only", returns
+ * "--ff-only". Otherwise, if pull.ff is set to an invalid value, die with an
+ * error.
+ */
+static const char *config_get_ff(void)
+{
+ const char *value;
+
+ if (git_config_get_value("pull.ff", &value))
+ return NULL;
+
+ switch (git_config_maybe_bool("pull.ff", value)) {
+ case 0:
+ return "--no-ff";
+ case 1:
+ return "--ff";
+ }
+
+ if (!strcmp(value, "only"))
+ return "--ff-only";
+
+ die(_("Invalid value for pull.ff: %s"), value);
+}
+
+/**
+ * Returns the default configured value for --rebase. It first looks for the
+ * value of "branch.$curr_branch.rebase", where $curr_branch is the current
+ * branch, and if HEAD is detached or the configuration key does not exist,
+ * looks for the value of "pull.rebase". If both configuration keys do not
+ * exist, returns REBASE_FALSE.
+ */
+static enum rebase_type config_get_rebase(void)
+{
+ struct branch *curr_branch = branch_get("HEAD");
+ const char *value;
+
+ if (curr_branch) {
+ char *key = xstrfmt("branch.%s.rebase", curr_branch->name);
+
+ if (!git_config_get_value(key, &value)) {
+ enum rebase_type ret = parse_config_rebase(key, value, 1);
+ free(key);
+ return ret;
+ }
+
+ free(key);
+ }
+
+ if (!git_config_get_value("pull.rebase", &value))
+ return parse_config_rebase("pull.rebase", value, 1);
+
+ return REBASE_FALSE;
+}
+
+/**
+ * Read config variables.
+ */
+static int git_pull_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "rebase.autostash")) {
+ config_autostash = git_config_bool(var, value);
+ return 0;
+ }
+ return git_default_config(var, value, cb);
+}
+
+/**
+ * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge
+ * into merge_heads.
+ */
+static void get_merge_heads(struct sha1_array *merge_heads)
+{
+ const char *filename = git_path("FETCH_HEAD");
+ FILE *fp;
+ struct strbuf sb = STRBUF_INIT;
+ unsigned char sha1[GIT_SHA1_RAWSZ];
+
+ if (!(fp = fopen(filename, "r")))
+ die_errno(_("could not open '%s' for reading"), filename);
+ while (strbuf_getline_lf(&sb, fp) != EOF) {
+ if (get_sha1_hex(sb.buf, sha1))
+ continue; /* invalid line: does not start with SHA1 */
+ if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t"))
+ continue; /* ref is not-for-merge */
+ sha1_array_append(merge_heads, sha1);
+ }
+ fclose(fp);
+ strbuf_release(&sb);
+}
+
+/**
+ * Used by die_no_merge_candidates() as a for_each_remote() callback to
+ * retrieve the name of the remote if the repository only has one remote.
+ */
+static int get_only_remote(struct remote *remote, void *cb_data)
+{
+ const char **remote_name = cb_data;
+
+ if (*remote_name)
+ return -1;
+
+ *remote_name = remote->name;
+ return 0;
+}
+
+/**
+ * Dies with the appropriate reason for why there are no merge candidates:
+ *
+ * 1. We fetched from a specific remote, and a refspec was given, but it ended
+ * up not fetching anything. This is usually because the user provided a
+ * wildcard refspec which had no matches on the remote end.
+ *
+ * 2. We fetched from a non-default remote, but didn't specify a branch to
+ * merge. We can't use the configured one because it applies to the default
+ * remote, thus the user must specify the branches to merge.
+ *
+ * 3. We fetched from the branch's or repo's default remote, but:
+ *
+ * a. We are not on a branch, so there will never be a configured branch to
+ * merge with.
+ *
+ * b. We are on a branch, but there is no configured branch to merge with.
+ *
+ * 4. We fetched from the branch's or repo's default remote, but the configured
+ * branch to merge didn't get fetched. (Either it doesn't exist, or wasn't
+ * part of the configured fetch refspec.)
+ */
+static void NORETURN die_no_merge_candidates(const char *repo, const char **refspecs)
+{
+ struct branch *curr_branch = branch_get("HEAD");
+ const char *remote = curr_branch ? curr_branch->remote_name : NULL;
+
+ if (*refspecs) {
+ if (opt_rebase)
+ fprintf_ln(stderr, _("There is no candidate for rebasing against among the refs that you just fetched."));
+ else
+ fprintf_ln(stderr, _("There are no candidates for merging among the refs that you just fetched."));
+ fprintf_ln(stderr, _("Generally this means that you provided a wildcard refspec which had no\n"
+ "matches on the remote end."));
+ } else if (repo && curr_branch && (!remote || strcmp(repo, remote))) {
+ fprintf_ln(stderr, _("You asked to pull from the remote '%s', but did not specify\n"
+ "a branch. Because this is not the default configured remote\n"
+ "for your current branch, you must specify a branch on the command line."),
+ repo);
+ } else if (!curr_branch) {
+ fprintf_ln(stderr, _("You are not currently on a branch."));
+ if (opt_rebase)
+ fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
+ else
+ fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
+ fprintf_ln(stderr, _("See git-pull(1) for details."));
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, " git pull %s %s", _("<remote>"), _("<branch>"));
+ fprintf(stderr, "\n");
+ } else if (!curr_branch->merge_nr) {
+ const char *remote_name = NULL;
+
+ if (for_each_remote(get_only_remote, &remote_name) || !remote_name)
+ remote_name = _("<remote>");
+
+ fprintf_ln(stderr, _("There is no tracking information for the current branch."));
+ if (opt_rebase)
+ fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
+ else
+ fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
+ fprintf_ln(stderr, _("See git-pull(1) for details."));
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, " git pull %s %s", _("<remote>"), _("<branch>"));
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:"));
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, " git branch --set-upstream-to=%s/%s %s\n",
+ remote_name, _("<branch>"), curr_branch->name);
+ } else
+ fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n"
+ "from the remote, but no such ref was fetched."),
+ *curr_branch->merge_name);
+ exit(1);
+}
+
+/**
+ * Parses argv into [<repo> [<refspecs>...]], returning their values in `repo`
+ * as a string and `refspecs` as a null-terminated array of strings. If `repo`
+ * is not provided in argv, it is set to NULL.
+ */
+static void parse_repo_refspecs(int argc, const char **argv, const char **repo,
+ const char ***refspecs)
+{
+ if (argc > 0) {
+ *repo = *argv++;
+ argc--;
+ } else
+ *repo = NULL;
+ *refspecs = argv;
+}
+
+/**
+ * Runs git-fetch, returning its exit status. `repo` and `refspecs` are the
+ * repository and refspecs to fetch, or NULL if they are not provided.
+ */
+static int run_fetch(const char *repo, const char **refspecs)
+{
+ struct argv_array args = ARGV_ARRAY_INIT;
+ int ret;
+
+ argv_array_pushl(&args, "fetch", "--update-head-ok", NULL);
+
+ /* Shared options */
+ argv_push_verbosity(&args);
+ if (opt_progress)
+ argv_array_push(&args, opt_progress);
+
+ /* Options passed to git-fetch */
+ if (opt_all)
+ argv_array_push(&args, opt_all);
+ if (opt_append)
+ argv_array_push(&args, opt_append);
+ if (opt_upload_pack)
+ argv_array_push(&args, opt_upload_pack);
+ argv_push_force(&args);
+ if (opt_tags)
+ argv_array_push(&args, opt_tags);
+ if (opt_prune)
+ argv_array_push(&args, opt_prune);
+ if (opt_recurse_submodules)
+ argv_array_push(&args, opt_recurse_submodules);
+ if (max_children)
+ argv_array_push(&args, max_children);
+ if (opt_dry_run)
+ argv_array_push(&args, "--dry-run");
+ if (opt_keep)
+ argv_array_push(&args, opt_keep);
+ if (opt_depth)
+ argv_array_push(&args, opt_depth);
+ if (opt_unshallow)
+ argv_array_push(&args, opt_unshallow);
+ if (opt_update_shallow)
+ argv_array_push(&args, opt_update_shallow);
+ if (opt_refmap)
+ argv_array_push(&args, opt_refmap);
+
+ if (repo) {
+ argv_array_push(&args, repo);
+ argv_array_pushv(&args, refspecs);
+ } else if (*refspecs)
+ die("BUG: refspecs without repo?");
+ ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+ argv_array_clear(&args);
+ return ret;
+}
+
+/**
+ * "Pulls into void" by branching off merge_head.
+ */
+static int pull_into_void(const unsigned char *merge_head,
+ const unsigned char *curr_head)
+{
+ /*
+ * Two-way merge: we treat the index as based on an empty tree,
+ * and try to fast-forward to HEAD. This ensures we will not lose
+ * index/worktree changes that the user already made on the unborn
+ * branch.
+ */
+ if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, merge_head, 0))
+ return 1;
+
+ if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * Runs git-merge, returning its exit status.
+ */
+static int run_merge(void)
+{
+ int ret;
+ struct argv_array args = ARGV_ARRAY_INIT;
+
+ argv_array_pushl(&args, "merge", NULL);
+
+ /* Shared options */
+ argv_push_verbosity(&args);
+ if (opt_progress)
+ argv_array_push(&args, opt_progress);
+
+ /* Options passed to git-merge */
+ if (opt_diffstat)
+ argv_array_push(&args, opt_diffstat);
+ if (opt_log)
+ argv_array_push(&args, opt_log);
+ if (opt_squash)
+ argv_array_push(&args, opt_squash);
+ if (opt_commit)
+ argv_array_push(&args, opt_commit);
+ if (opt_edit)
+ argv_array_push(&args, opt_edit);
+ if (opt_ff)
+ argv_array_push(&args, opt_ff);
+ if (opt_verify_signatures)
+ argv_array_push(&args, opt_verify_signatures);
+ argv_array_pushv(&args, opt_strategies.argv);
+ argv_array_pushv(&args, opt_strategy_opts.argv);
+ if (opt_gpg_sign)
+ argv_array_push(&args, opt_gpg_sign);
+ if (opt_allow_unrelated_histories > 0)
+ argv_array_push(&args, "--allow-unrelated-histories");
+
+ argv_array_push(&args, "FETCH_HEAD");
+ ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+ argv_array_clear(&args);
+ return ret;
+}
+
+/**
+ * Returns remote's upstream branch for the current branch. If remote is NULL,
+ * the current branch's configured default remote is used. Returns NULL if
+ * `remote` does not name a valid remote, HEAD does not point to a branch,
+ * remote is not the branch's configured remote or the branch does not have any
+ * configured upstream branch.
+ */
+static const char *get_upstream_branch(const char *remote)
+{
+ struct remote *rm;
+ struct branch *curr_branch;
+ const char *curr_branch_remote;
+
+ rm = remote_get(remote);
+ if (!rm)
+ return NULL;
+
+ curr_branch = branch_get("HEAD");
+ if (!curr_branch)
+ return NULL;
+
+ curr_branch_remote = remote_for_branch(curr_branch, NULL);
+ assert(curr_branch_remote);
+
+ if (strcmp(curr_branch_remote, rm->name))
+ return NULL;
+
+ return branch_get_upstream(curr_branch, NULL);
+}
+
+/**
+ * Derives the remote tracking branch from the remote and refspec.
+ *
+ * FIXME: The current implementation assumes the default mapping of
+ * refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>.
+ */
+static const char *get_tracking_branch(const char *remote, const char *refspec)
+{
+ struct refspec *spec;
+ const char *spec_src;
+ const char *merge_branch;
+
+ spec = parse_fetch_refspec(1, &refspec);
+ spec_src = spec->src;
+ if (!*spec_src || !strcmp(spec_src, "HEAD"))
+ spec_src = "HEAD";
+ else if (skip_prefix(spec_src, "heads/", &spec_src))
+ ;
+ else if (skip_prefix(spec_src, "refs/heads/", &spec_src))
+ ;
+ else if (starts_with(spec_src, "refs/") ||
+ starts_with(spec_src, "tags/") ||
+ starts_with(spec_src, "remotes/"))
+ spec_src = "";
+
+ if (*spec_src) {
+ if (!strcmp(remote, "."))
+ merge_branch = mkpath("refs/heads/%s", spec_src);
+ else
+ merge_branch = mkpath("refs/remotes/%s/%s", remote, spec_src);
+ } else
+ merge_branch = NULL;
+
+ free_refspec(1, spec);
+ return merge_branch;
+}
+
+/**
+ * Given the repo and refspecs, sets fork_point to the point at which the
+ * current branch forked from its remote tracking branch. Returns 0 on success,
+ * -1 on failure.
+ */
+static int get_rebase_fork_point(unsigned char *fork_point, const char *repo,
+ const char *refspec)
+{
+ int ret;
+ struct branch *curr_branch;
+ const char *remote_branch;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf sb = STRBUF_INIT;
+
+ curr_branch = branch_get("HEAD");
+ if (!curr_branch)
+ return -1;
+
+ if (refspec)
+ remote_branch = get_tracking_branch(repo, refspec);
+ else
+ remote_branch = get_upstream_branch(repo);
+
+ if (!remote_branch)
+ return -1;
+
+ argv_array_pushl(&cp.args, "merge-base", "--fork-point",
+ remote_branch, curr_branch->name, NULL);
+ cp.no_stdin = 1;
+ cp.no_stderr = 1;
+ cp.git_cmd = 1;
+
+ ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ);
+ if (ret)
+ goto cleanup;
+
+ ret = get_sha1_hex(sb.buf, fork_point);
+ if (ret)
+ goto cleanup;
+
+cleanup:
+ strbuf_release(&sb);
+ return ret ? -1 : 0;
+}
+
+/**
+ * Sets merge_base to the octopus merge base of curr_head, merge_head and
+ * fork_point. Returns 0 if a merge base is found, 1 otherwise.
+ */
+static int get_octopus_merge_base(unsigned char *merge_base,
+ const unsigned char *curr_head,
+ const unsigned char *merge_head,
+ const unsigned char *fork_point)
+{
+ struct commit_list *revs = NULL, *result;
+
+ commit_list_insert(lookup_commit_reference(curr_head), &revs);
+ commit_list_insert(lookup_commit_reference(merge_head), &revs);
+ if (!is_null_sha1(fork_point))
+ commit_list_insert(lookup_commit_reference(fork_point), &revs);
+
+ result = reduce_heads(get_octopus_merge_bases(revs));
+ free_commit_list(revs);
+ if (!result)
+ return 1;
+
+ hashcpy(merge_base, result->item->object.oid.hash);
+ return 0;
+}
+
+/**
+ * Given the current HEAD SHA1, the merge head returned from git-fetch and the
+ * fork point calculated by get_rebase_fork_point(), runs git-rebase with the
+ * appropriate arguments and returns its exit status.
+ */
+static int run_rebase(const unsigned char *curr_head,
+ const unsigned char *merge_head,
+ const unsigned char *fork_point)
+{
+ int ret;
+ unsigned char oct_merge_base[GIT_SHA1_RAWSZ];
+ struct argv_array args = ARGV_ARRAY_INIT;
+
+ if (!get_octopus_merge_base(oct_merge_base, curr_head, merge_head, fork_point))
+ if (!is_null_sha1(fork_point) && !hashcmp(oct_merge_base, fork_point))
+ fork_point = NULL;
+
+ argv_array_push(&args, "rebase");
+
+ /* Shared options */
+ argv_push_verbosity(&args);
+
+ /* Options passed to git-rebase */
+ if (opt_rebase == REBASE_PRESERVE)
+ argv_array_push(&args, "--preserve-merges");
+ else if (opt_rebase == REBASE_INTERACTIVE)
+ argv_array_push(&args, "--interactive");
+ if (opt_diffstat)
+ argv_array_push(&args, opt_diffstat);
+ argv_array_pushv(&args, opt_strategies.argv);
+ argv_array_pushv(&args, opt_strategy_opts.argv);
+ if (opt_gpg_sign)
+ argv_array_push(&args, opt_gpg_sign);
+ if (opt_autostash == 0)
+ argv_array_push(&args, "--no-autostash");
+ else if (opt_autostash == 1)
+ argv_array_push(&args, "--autostash");
+ if (opt_verify_signatures &&
+ !strcmp(opt_verify_signatures, "--verify-signatures"))
+ warning(_("ignoring --verify-signatures for rebase"));
+
+ argv_array_push(&args, "--onto");
+ argv_array_push(&args, sha1_to_hex(merge_head));
+
+ if (fork_point && !is_null_sha1(fork_point))
+ argv_array_push(&args, sha1_to_hex(fork_point));
+ else
+ argv_array_push(&args, sha1_to_hex(merge_head));
+
+ ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+ argv_array_clear(&args);
+ return ret;
+}
+
+int cmd_pull(int argc, const char **argv, const char *prefix)
+{
+ const char *repo, **refspecs;
+ struct sha1_array merge_heads = SHA1_ARRAY_INIT;
+ unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ];
+ unsigned char rebase_fork_point[GIT_SHA1_RAWSZ];
+
+ if (!getenv("GIT_REFLOG_ACTION"))
+ set_reflog_message(argc, argv);
+
+ argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
+
+ parse_repo_refspecs(argc, argv, &repo, &refspecs);
+
+ if (!opt_ff)
+ opt_ff = xstrdup_or_null(config_get_ff());
+
+ if (opt_rebase < 0)
+ opt_rebase = config_get_rebase();
+
+ git_config(git_pull_config, NULL);
+
+ if (read_cache_unmerged())
+ die_resolve_conflict("pull");
+
+ if (file_exists(git_path("MERGE_HEAD")))
+ die_conclude_merge();
+
+ if (get_sha1("HEAD", orig_head))
+ hashclr(orig_head);
+
+ if (!opt_rebase && opt_autostash != -1)
+ die(_("--[no-]autostash option is only valid with --rebase."));
+
+ if (opt_rebase) {
+ int autostash = config_autostash;
+ if (opt_autostash != -1)
+ autostash = opt_autostash;
+
+ if (is_null_sha1(orig_head) && !is_cache_unborn())
+ die(_("Updating an unborn branch with changes added to the index."));
+
+ if (!autostash)
+ require_clean_work_tree(N_("pull with rebase"),
+ _("please commit or stash them."), 1, 0);
+
+ if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs))
+ hashclr(rebase_fork_point);
+ }
+
+ if (run_fetch(repo, refspecs))
+ return 1;
+
+ if (opt_dry_run)
+ return 0;
+
+ if (get_sha1("HEAD", curr_head))
+ hashclr(curr_head);
+
+ if (!is_null_sha1(orig_head) && !is_null_sha1(curr_head) &&
+ hashcmp(orig_head, curr_head)) {
+ /*
+ * The fetch involved updating the current branch.
+ *
+ * The working tree and the index file are still based on
+ * orig_head commit, but we are merging into curr_head.
+ * Update the working tree to match curr_head.
+ */
+
+ warning(_("fetch updated the current branch head.\n"
+ "fast-forwarding your working tree from\n"
+ "commit %s."), sha1_to_hex(orig_head));
+
+ if (checkout_fast_forward(orig_head, curr_head, 0))
+ die(_("Cannot fast-forward your working tree.\n"
+ "After making sure that you saved anything precious from\n"
+ "$ git diff %s\n"
+ "output, run\n"
+ "$ git reset --hard\n"
+ "to recover."), sha1_to_hex(orig_head));
+ }
+
+ get_merge_heads(&merge_heads);
+
+ if (!merge_heads.nr)
+ die_no_merge_candidates(repo, refspecs);
+
+ if (is_null_sha1(orig_head)) {
+ if (merge_heads.nr > 1)
+ die(_("Cannot merge multiple branches into empty head."));
+ return pull_into_void(*merge_heads.sha1, curr_head);
+ }
+ if (opt_rebase && merge_heads.nr > 1)
+ die(_("Cannot rebase onto multiple branches."));
+
+ if (opt_rebase) {
+ struct commit_list *list = NULL;
+ struct commit *merge_head, *head;
+
+ head = lookup_commit_reference(orig_head);
+ commit_list_insert(head, &list);
+ merge_head = lookup_commit_reference(merge_heads.sha1[0]);
+ if (is_descendant_of(merge_head, list)) {
+ /* we can fast-forward this without invoking rebase */
+ opt_ff = "--ff-only";
+ return run_merge();
+ }
+ return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point);
+ } else {
+ return run_merge();
+ }
+}
diff --git a/builtin/push.c b/builtin/push.c
index 0e50ddbb01..5c22e9f2e5 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -9,6 +9,8 @@
#include "transport.h"
#include "parse-options.h"
#include "submodule.h"
+#include "submodule-config.h"
+#include "send-pack.h"
static const char * const push_usage[] = {
N_("git push [<options>] [<repository> [<refspec>...]]"),
@@ -20,13 +22,14 @@ static int deleterefs;
static const char *receivepack;
static int verbosity;
static int progress = -1;
+static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
+static enum transport_family family;
static struct push_cas_option cas;
static const char **refspec;
static int refspec_nr;
static int refspec_alloc;
-static int default_matching_used;
static void add_refspec(const char *ref)
{
@@ -59,7 +62,7 @@ static const char *map_refspec(const char *ref,
}
if (push_default == PUSH_DEFAULT_UPSTREAM &&
- !prefixcmp(matched->name, "refs/heads/")) {
+ 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;
@@ -128,11 +131,10 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote
* them the big ugly fully qualified ref.
*/
const char *advice_maybe = "";
- const char *short_upstream =
- skip_prefix(branch->merge[0]->src, "refs/heads/");
+ const char *short_upstream = branch->merge[0]->src;
+
+ skip_prefix(short_upstream, "refs/heads/", &short_upstream);
- if (!short_upstream)
- short_upstream = branch->merge[0]->src;
/*
* Don't show advice for people who explicitly set
* push.default.
@@ -163,7 +165,7 @@ static const char message_detached_head_die[] =
" git push %s HEAD:<name-of-remote-branch>\n");
static void setup_push_upstream(struct remote *remote, struct branch *branch,
- int triangular)
+ int triangular, int simple)
{
struct strbuf refspec = STRBUF_INIT;
@@ -186,52 +188,24 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch,
"to update which remote branch."),
remote->name, branch->name);
- if (push_default == PUSH_DEFAULT_SIMPLE) {
+ 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);
+ strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src);
add_refspec(refspec.buf);
}
static void setup_push_current(struct remote *remote, struct branch *branch)
{
+ struct strbuf refspec = STRBUF_INIT;
+
if (!branch)
die(_(message_detached_head_die), remote->name);
- add_refspec(branch->name);
-}
-
-static char warn_unspecified_push_default_msg[] =
-N_("push.default is unset; its implicit value is changing in\n"
- "Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
- "and maintain the current behavior after the default changes, use:\n"
- "\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"
- "In Git 2.0, Git will default to the more conservative 'simple'\n"
- "behavior, which only pushes the current branch to the corresponding\n"
- "remote branch that 'git pull' uses to update the current branch.\n"
- "\n"
- "See 'git help config' and search for 'push.default' for further information.\n"
- "(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode\n"
- "'current' instead of 'simple' if you sometimes use older versions of Git)");
-
-static void warn_unspecified_push_default_configuration(void)
-{
- static int warn_once;
-
- if (warn_once++)
- return;
- warning("%s\n", _(warn_unspecified_push_default_msg));
+ strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname);
+ add_refspec(refspec.buf);
}
static int is_workflow_triangular(struct remote *remote)
@@ -247,23 +221,20 @@ static void setup_default_push_refspecs(struct remote *remote)
switch (push_default) {
default:
- case PUSH_DEFAULT_UNSPECIFIED:
- default_matching_used = 1;
- warn_unspecified_push_default_configuration();
- /* fallthru */
case PUSH_DEFAULT_MATCHING:
add_refspec(":");
break;
+ case PUSH_DEFAULT_UNSPECIFIED:
case PUSH_DEFAULT_SIMPLE:
if (triangular)
setup_push_current(remote, branch);
else
- setup_push_upstream(remote, branch, triangular);
+ setup_push_upstream(remote, branch, triangular, 1);
break;
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote, branch, triangular);
+ setup_push_upstream(remote, branch, triangular, 0);
break;
case PUSH_DEFAULT_CURRENT:
@@ -283,12 +254,6 @@ static const char message_advice_pull_before_push[] =
"'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
-static const char message_advice_use_upstream[] =
- N_("Updates were rejected because a pushed branch tip is behind its remote\n"
- "counterpart. If you did not intend to push that branch, you may want to\n"
- "specify branches to push or set the 'push.default' configuration variable\n"
- "to 'simple', 'current' or 'upstream' to push only the current branch.");
-
static const char message_advice_checkout_pull_push[] =
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
"counterpart. Check out this branch and integrate the remote changes\n"
@@ -317,13 +282,6 @@ static void advise_pull_before_push(void)
advise(_(message_advice_pull_before_push));
}
-static void advise_use_upstream(void)
-{
- if (!advice_push_non_ff_default || !advice_push_update_rejected)
- return;
- advise(_(message_advice_use_upstream));
-}
-
static void advise_checkout_pull_push(void)
{
if (!advice_push_non_ff_matching || !advice_push_update_rejected)
@@ -358,6 +316,7 @@ static int push_with_options(struct transport *transport, int flags)
unsigned int reject_reasons;
transport_set_verbosity(transport, verbosity, progress);
+ transport->family = family;
if (receivepack)
transport_set_option(transport,
@@ -385,10 +344,7 @@ static int push_with_options(struct transport *transport, int flags)
if (reject_reasons & REJECT_NON_FF_HEAD) {
advise_pull_before_push();
} else if (reject_reasons & REJECT_NON_FF_OTHER) {
- if (default_matching_used)
- advise_use_upstream();
- else
- advise_checkout_pull_push();
+ advise_checkout_pull_push();
} else if (reject_reasons & REJECT_ALREADY_EXISTS) {
advise_ref_already_exists();
} else if (reject_reasons & REJECT_FETCH_FIRST) {
@@ -400,7 +356,8 @@ static int push_with_options(struct transport *transport, int flags)
return 1;
}
-static int do_push(const char *repo, int flags)
+static int do_push(const char *repo, int flags,
+ const struct string_list *push_options)
{
int i, errs;
struct remote *remote = pushremote_get(repo);
@@ -423,6 +380,9 @@ static int do_push(const char *repo, int flags)
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ if (push_options->nr)
+ flags |= TRANSPORT_PUSH_OPTIONS;
+
if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
return error(_("--all and --tags are incompatible"));
@@ -453,13 +413,16 @@ static int do_push(const char *repo, int flags)
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
} else {
struct transport *transport =
transport_get(remote, NULL);
-
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
@@ -469,39 +432,94 @@ static int do_push(const char *repo, int flags)
static int option_parse_recurse_submodules(const struct option *opt,
const char *arg, int unset)
{
- int *flags = opt->value;
+ int *recurse_submodules = opt->value;
+
+ if (unset)
+ *recurse_submodules = RECURSE_SUBMODULES_OFF;
+ else if (arg)
+ *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg);
+ else
+ die("%s missing parameter", opt->long_name);
+
+ return 0;
+}
+
+static void set_push_cert_flags(int *flags, int v)
+{
+ switch (v) {
+ case SEND_PACK_PUSH_CERT_NEVER:
+ *flags &= ~(TRANSPORT_PUSH_CERT_ALWAYS | TRANSPORT_PUSH_CERT_IF_ASKED);
+ break;
+ case SEND_PACK_PUSH_CERT_ALWAYS:
+ *flags |= TRANSPORT_PUSH_CERT_ALWAYS;
+ *flags &= ~TRANSPORT_PUSH_CERT_IF_ASKED;
+ break;
+ case SEND_PACK_PUSH_CERT_IF_ASKED:
+ *flags |= TRANSPORT_PUSH_CERT_IF_ASKED;
+ *flags &= ~TRANSPORT_PUSH_CERT_ALWAYS;
+ break;
+ }
+}
+
+
+static int git_push_config(const char *k, const char *v, void *cb)
+{
+ int *flags = cb;
+ int status;
- if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |
- TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))
- die("%s can only be used once.", opt->long_name);
+ status = git_gpg_config(k, v, NULL);
+ if (status)
+ return status;
- if (arg) {
- if (!strcmp(arg, "check"))
- *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
- else if (!strcmp(arg, "on-demand"))
- *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
+ if (!strcmp(k, "push.followtags")) {
+ if (git_config_bool(k, v))
+ *flags |= TRANSPORT_PUSH_FOLLOW_TAGS;
else
- die("bad %s argument: %s", opt->long_name, arg);
- } else
- die("option %s needs an argument (check|on-demand)",
- opt->long_name);
+ *flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS;
+ return 0;
+ } else if (!strcmp(k, "push.gpgsign")) {
+ const char *value;
+ if (!git_config_get_value("push.gpgsign", &value)) {
+ switch (git_config_maybe_bool("push.gpgsign", value)) {
+ case 0:
+ set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_NEVER);
+ break;
+ case 1:
+ set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_ALWAYS);
+ break;
+ default:
+ if (value && !strcasecmp(value, "if-asked"))
+ set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED);
+ else
+ return error("Invalid value for '%s'", k);
+ }
+ }
+ } else if (!strcmp(k, "push.recursesubmodules")) {
+ const char *value;
+ if (!git_config_get_value("push.recursesubmodules", &value))
+ recurse_submodules = parse_push_recurse_submodules_arg(k, value);
+ }
- return 0;
+ return git_default_config(k, v, NULL);
}
int cmd_push(int argc, const char **argv, const char *prefix)
{
int flags = 0;
int tags = 0;
+ int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
+ static struct string_list push_options = STRING_LIST_INIT_DUP;
+ static struct string_list_item *item;
+
struct option options[] = {
OPT__VERBOSITY(&verbosity),
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_BOOL( 0, "delete", &deleterefs, N_("delete refs")),
+ OPT_BOOL('d', "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),
@@ -510,7 +528,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
0, CAS_OPT_NAME, &cas, N_("refname>:<expect"),
N_("require old value of ref to be at this value"),
PARSE_OPT_OPTARG, parseopt_push_cas_option },
- { OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"),
+ { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, "check|on-demand|no",
N_("control recursive pushing of submodules"),
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
OPT_BOOL( 0 , "thin", &thin, N_("use thin pack")),
@@ -524,18 +542,35 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
TRANSPORT_PUSH_FOLLOW_TAGS),
+ { OPTION_CALLBACK,
+ 0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
+ PARSE_OPT_OPTARG, option_parse_push_signed },
+ OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
+ OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")),
+ OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
+ TRANSPORT_FAMILY_IPV4),
+ OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
+ TRANSPORT_FAMILY_IPV6),
OPT_END()
};
packet_trace_identity("push");
- git_config(git_default_config, NULL);
+ git_config(git_push_config, &flags);
argc = parse_options(argc, argv, prefix, options, push_usage, 0);
+ set_push_cert_flags(&flags, push_cert);
if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR))))
die(_("--delete is incompatible with --all, --mirror and --tags"));
if (deleterefs && argc < 2)
die(_("--delete doesn't make sense without any refs"));
+ if (recurse_submodules == RECURSE_SUBMODULES_CHECK)
+ flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
+ else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)
+ flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
+ else if (recurse_submodules == RECURSE_SUBMODULES_ONLY)
+ flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY;
+
if (tags)
add_refspec("refs/tags/*");
@@ -544,7 +579,11 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
- rc = do_push(repo, flags);
+ for_each_string_list_item(item, &push_options)
+ if (strchr(item->string, '\n'))
+ die(_("push options must not have new line characters"));
+
+ rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
else
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 0d7ef847a7..8ba64bc785 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[] = {
- 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>]])"),
+ 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
};
@@ -77,7 +78,7 @@ static void debug_stage(const char *label, const struct cache_entry *ce,
else
printf("%06o #%d %s %.8s\n",
ce->ce_mode, ce_stage(ce), ce->name,
- sha1_to_hex(ce->sha1));
+ oid_to_hex(&ce->oid));
}
static int debug_merge(const struct cache_entry * const *stages,
@@ -89,7 +90,7 @@ static int debug_merge(const struct cache_entry * const *stages,
debug_stage("index", stages[0], o);
for (i = 1; i <= o->merge_size; i++) {
char buf[24];
- sprintf(buf, "ent#%d", i);
+ xsnprintf(buf, sizeof(buf), "ent#%d", i);
debug_stage(buf, stages[i], o);
}
return 0;
@@ -99,7 +100,7 @@ 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;
@@ -108,34 +109,34 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
{ 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,
- N_("only empty the index"), 1),
+ OPT_BOOL(0, "empty", &read_empty,
+ N_("only empty the index")),
OPT__VERBOSE(&opts.verbose_update, N_("be verbose")),
OPT_GROUP(N_("Merging")),
- OPT_SET_INT('m', NULL, &opts.merge,
- N_("perform a merge in addition to a read"), 1),
- OPT_SET_INT(0, "trivial", &opts.trivial_merges_only,
- N_("3-way merge if no file level merging required"), 1),
- OPT_SET_INT(0, "aggressive", &opts.aggressive,
- N_("3-way merge in presence of adds and removes"), 1),
- OPT_SET_INT(0, "reset", &opts.reset,
- N_("same as -m, but discard unmerged entries"), 1),
+ OPT_BOOL('m', NULL, &opts.merge,
+ N_("perform a merge in addition to a read")),
+ OPT_BOOL(0, "trivial", &opts.trivial_merges_only,
+ N_("3-way merge if no file level merging required")),
+ OPT_BOOL(0, "aggressive", &opts.aggressive,
+ N_("3-way merge in presence of adds and removes")),
+ OPT_BOOL(0, "reset", &opts.reset,
+ N_("same as -m, but discard unmerged entries")),
{ 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,
- N_("update working tree with merge result"), 1),
+ OPT_BOOL('u', NULL, &opts.update,
+ N_("update working tree with merge result")),
{ OPTION_CALLBACK, 0, "exclude-per-directory", &opts,
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,
- N_("don't check the working tree after merging"), 1),
+ OPT_BOOL('i', NULL, &opts.index_only,
+ N_("don't check the working tree after merging")),
OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")),
- OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
- N_("skip applying sparse checkout filter"), 1),
- OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
- N_("debug unpack-trees"), 1),
+ OPT_BOOL(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
+ N_("skip applying sparse checkout filter")),
+ OPT_BOOL(0, "debug-unpack", &opts.debug_unpack,
+ N_("debug unpack-trees")),
OPT_END()
};
@@ -149,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, LOCK_DIE_ON_ERROR);
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");
@@ -231,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/rebase--helper.c b/builtin/rebase--helper.c
new file mode 100644
index 0000000000..ca1ebb2fa1
--- /dev/null
+++ b/builtin/rebase--helper.c
@@ -0,0 +1,40 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "sequencer.h"
+
+static const char * const builtin_rebase_helper_usage[] = {
+ N_("git rebase--helper [<options>]"),
+ NULL
+};
+
+int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
+{
+ struct replay_opts opts = REPLAY_OPTS_INIT;
+ enum {
+ CONTINUE = 1, ABORT
+ } command = 0;
+ struct option options[] = {
+ OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
+ OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
+ CONTINUE),
+ OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
+ ABORT),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+
+ opts.action = REPLAY_INTERACTIVE_REBASE;
+ opts.allow_ff = 1;
+ opts.allow_empty = 1;
+
+ argc = parse_options(argc, argv, NULL, options,
+ builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0);
+
+ if (command == CONTINUE && argc == 1)
+ return !!sequencer_continue(&opts);
+ if (command == ABORT && argc == 1)
+ return !!sequencer_remove_state(&opts);
+ usage_with_options(builtin_rebase_helper_usage, options);
+}
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 85bba356fa..9ed8fbbfad 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"
@@ -15,14 +16,24 @@
#include "connected.h"
#include "argv-array.h"
#include "version.h"
-
-static const char receive_pack_usage[] = "git receive-pack <git-dir>";
+#include "tag.h"
+#include "gpg-interface.h"
+#include "sigchain.h"
+#include "fsck.h"
+#include "tmp-objdir.h"
+#include "oidset.h"
+
+static const char * const receive_pack_usage[] = {
+ N_("git receive-pack <git-dir>"),
+ NULL
+};
enum deny_action {
DENY_UNCONFIGURED,
DENY_IGNORE,
DENY_WARN,
- DENY_REFUSE
+ DENY_REFUSE,
+ DENY_UPDATE_INSTEAD
};
static int deny_deletes;
@@ -31,21 +42,53 @@ static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
static int receive_fsck_objects = -1;
static int transfer_fsck_objects = -1;
+static struct strbuf fsck_msg_types = STRBUF_INIT;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
+static int advertise_atomic_push = 1;
+static int advertise_push_options;
static int unpack_limit = 100;
+static off_t max_input_size;
static int report_status;
static int use_sideband;
+static int use_atomic;
+static int use_push_options;
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 reject_thin;
+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 struct ref_transaction *transaction;
+
+static enum {
+ KEEPALIVE_NEVER = 0,
+ KEEPALIVE_AFTER_NUL,
+ KEEPALIVE_ALWAYS
+} use_keepalive;
+static int keepalive_in_sec = 5;
+
+static struct tmp_objdir *tmp_objdir;
static enum deny_action parse_deny_action(const char *var, const char *value)
{
@@ -56,6 +99,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;
@@ -89,6 +134,26 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "receive.fsck.skiplist") == 0) {
+ const char *path;
+
+ if (git_config_pathname(&path, var, value))
+ return 1;
+ strbuf_addf(&fsck_msg_types, "%cskiplist=%s",
+ fsck_msg_types.len ? ',' : '=', path);
+ free((char *)path);
+ return 0;
+ }
+
+ if (skip_prefix(var, "receive.fsck.", &var)) {
+ if (is_valid_msg_type(var, value))
+ strbuf_addf(&fsck_msg_types, "%c%s=%s",
+ fsck_msg_types.len ? ',' : '=', var, value);
+ else
+ warning("Skipping unknown msg id '%s'", var);
+ return 0;
+ }
+
if (strcmp(var, "receive.fsckobjects") == 0) {
receive_fsck_objects = git_config_bool(var, value);
return 0;
@@ -129,60 +194,106 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
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;
+ }
+
+ if (strcmp(var, "receive.advertiseatomic") == 0) {
+ advertise_atomic_push = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.advertisepushoptions") == 0) {
+ advertise_push_options = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.keepalive") == 0) {
+ keepalive_in_sec = git_config_int(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.maxinputsize") == 0) {
+ max_input_size = git_config_int64(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
static void show_ref(const char *path, const unsigned char *sha1)
{
- 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 agent=%s\n",
- sha1_to_hex(sha1), path, 0,
- " report-status delete-refs side-band-64k quiet",
- prefer_ofs_delta ? " ofs-delta" : "",
- git_user_agent_sanitized());
- sent_capabilities = 1;
+ if (sent_capabilities) {
+ packet_write_fmt(1, "%s %s\n", sha1_to_hex(sha1), path);
+ } else {
+ struct strbuf cap = STRBUF_INIT;
+
+ strbuf_addstr(&cap,
+ "report-status delete-refs side-band-64k quiet");
+ if (advertise_atomic_push)
+ strbuf_addstr(&cap, " atomic");
+ if (prefer_ofs_delta)
+ strbuf_addstr(&cap, " ofs-delta");
+ if (push_cert_nonce)
+ strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
+ if (advertise_push_options)
+ strbuf_addstr(&cap, " push-options");
+ strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
+ packet_write_fmt(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 *unused)
+static int show_ref_cb(const char *path_full, const struct object_id *oid,
+ int flag, void *data)
{
- path = strip_namespace(path);
+ struct oidset *seen = data;
+ const char *path = strip_namespace(path_full);
+
+ if (ref_is_hidden(path, path_full))
+ return 0;
+
/*
* Advertise refs outside our current namespace as ".have"
* refs, so that the client can use them to minimize data
- * transfer but will otherwise ignore them. This happens to
- * cover ".have" that are thrown in by add_one_alternate_ref()
- * to mark histories that are complete in our alternates as
- * well.
+ * transfer but will otherwise ignore them.
*/
- if (!path)
+ if (!path) {
+ if (oidset_insert(seen, oid))
+ return 0;
path = ".have";
- show_ref(path, sha1);
+ } else {
+ oidset_insert(seen, oid);
+ }
+ show_ref(path, oid->hash);
return 0;
}
-static void show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
+static void show_one_alternate_ref(const char *refname,
+ const struct object_id *oid,
+ void *data)
{
- show_ref(".have", sha1);
-}
+ struct oidset *seen = data;
-static void collect_one_alternate_ref(const struct ref *ref, void *data)
-{
- struct sha1_array *sa = data;
- sha1_array_append(sa, ref->old_sha1);
+ if (oidset_insert(seen, oid))
+ return;
+
+ show_ref(".have", oid->hash);
}
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);
+ static struct oidset seen = OIDSET_INIT;
+
+ for_each_ref(show_ref_cb, &seen);
+ for_each_alternate_ref(show_one_alternate_ref, &seen);
+ oidset_clear(&seen);
if (!sent_capabilities)
show_ref("capabilities^{}", null_sha1);
@@ -208,10 +319,10 @@ static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2
static void report_message(const char *prefix, const char *err, va_list params)
{
- int sz = strlen(prefix);
+ int sz;
char msg[4096];
- strncpy(msg, prefix, sz);
+ sz = xsnprintf(msg, sizeof(msg), "%s", prefix);
sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
if (sz > (sizeof(msg) - 1))
sz = sizeof(msg) - 1;
@@ -242,20 +353,299 @@ static void rp_error(const char *err, ...)
static int copy_to_sideband(int in, int out, void *arg)
{
char data[128];
+ int keepalive_active = 0;
+
+ if (keepalive_in_sec <= 0)
+ use_keepalive = KEEPALIVE_NEVER;
+ if (use_keepalive == KEEPALIVE_ALWAYS)
+ keepalive_active = 1;
+
while (1) {
- ssize_t sz = xread(in, data, sizeof(data));
+ ssize_t sz;
+
+ if (keepalive_active) {
+ struct pollfd pfd;
+ int ret;
+
+ pfd.fd = in;
+ pfd.events = POLLIN;
+ ret = poll(&pfd, 1, 1000 * keepalive_in_sec);
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ break;
+ } else if (ret == 0) {
+ /* no data; send a keepalive packet */
+ static const char buf[] = "0005\1";
+ write_or_die(1, buf, sizeof(buf) - 1);
+ continue;
+ } /* else there is actual data to read */
+ }
+
+ sz = xread(in, data, sizeof(data));
if (sz <= 0)
break;
+
+ if (use_keepalive == KEEPALIVE_AFTER_NUL && !keepalive_active) {
+ const char *p = memchr(data, '\0', sz);
+ if (p) {
+ /*
+ * The NUL tells us to start sending keepalives. Make
+ * sure we send any other data we read along
+ * with it.
+ */
+ keepalive_active = 1;
+ send_sideband(1, 2, data, p - data, use_sideband);
+ send_sideband(1, 2, p + 1, sz - (p - data + 1), use_sideband);
+ continue;
+ }
+ }
+
+ /*
+ * Either we're not looking for a NUL signal, or we didn't see
+ * it yet; just pass along the data.
+ */
send_sideband(1, 2, data, sz, use_sideband);
}
close(in);
return 0;
}
+#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)
+{
+ 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);
+ }
+
+ /* 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;
+ }
+
+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);
+ }
+ }
+}
+
+struct receive_hook_feed_state {
+ struct command *cmd;
+ int skip_broken;
+ struct strbuf buf;
+ const struct string_list *push_options;
+};
+
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)
+static int run_and_feed_hook(const char *hook_name, feed_fn feed,
+ struct receive_hook_feed_state *feed_state)
{
- struct child_process proc;
+ struct child_process proc = CHILD_PROCESS_INIT;
struct async muxer;
const char *argv[2];
int code;
@@ -266,10 +656,22 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
argv[1] = NULL;
- memset(&proc, 0, sizeof(proc));
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
+ if (feed_state->push_options) {
+ int i;
+ for (i = 0; i < feed_state->push_options->nr; i++)
+ argv_array_pushf(&proc.env_array,
+ "GIT_PUSH_OPTION_%d=%s", i,
+ feed_state->push_options->items[i].string);
+ argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
+ feed_state->push_options->nr);
+ } else
+ argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
+
+ if (tmp_objdir)
+ argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
@@ -281,6 +683,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
proc.err = muxer.in;
}
+ prepare_push_cert_sha1(&proc);
+
code = start_command(&proc);
if (code) {
if (use_sideband)
@@ -288,6 +692,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
return code;
}
+ sigchain_push(SIGPIPE, SIG_IGN);
+
while (1) {
const char *buf;
size_t n;
@@ -299,15 +705,12 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
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_;
@@ -330,8 +733,10 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
return 0;
}
-static int run_receive_hook(struct command *commands, const char *hook_name,
- int skip_broken)
+static int run_receive_hook(struct command *commands,
+ const char *hook_name,
+ int skip_broken,
+ const struct string_list *push_options)
{
struct receive_hook_feed_state state;
int status;
@@ -342,6 +747,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
if (feed_receive_hook(&state, NULL, NULL))
return 0;
state.cmd = commands;
+ state.push_options = push_options;
status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
strbuf_release(&state.buf);
return status;
@@ -350,7 +756,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
static int run_update_hook(struct command *cmd)
{
const char *argv[5];
- struct child_process proc;
+ struct child_process proc = CHILD_PROCESS_INIT;
int code;
argv[0] = find_hook("update");
@@ -362,11 +768,11 @@ static int run_update_hook(struct command *cmd)
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;
proc.argv = argv;
+ proc.env = tmp_objdir_env(tmp_objdir);
code = start_command(&proc);
if (code)
@@ -386,47 +792,39 @@ static int is_ref_checked_out(const char *ref)
return !strcmp(head_name, ref);
}
-static char *refuse_unconfigured_deny_msg[] = {
- "By default, updating the current branch in a non-bare repository",
- "is denied, because it will make the index and work tree inconsistent",
- "with what you pushed, and will require 'git reset --hard' to match",
- "the work tree to HEAD.",
- "",
- "You can set 'receive.denyCurrentBranch' configuration variable to",
- "'ignore' or 'warn' in the remote repository to allow pushing into",
- "its current branch; however, this is not recommended unless you",
- "arranged to update its work tree to match what you pushed in some",
- "other way.",
- "",
- "To squelch this message and still keep the default behaviour, set",
- "'receive.denyCurrentBranch' configuration variable to 'refuse'."
-};
+static char *refuse_unconfigured_deny_msg =
+ N_("By default, updating the current branch in a non-bare repository\n"
+ "is denied, because it will make the index and work tree inconsistent\n"
+ "with what you pushed, and will require 'git reset --hard' to match\n"
+ "the work tree to HEAD.\n"
+ "\n"
+ "You can set the 'receive.denyCurrentBranch' configuration variable\n"
+ "to 'ignore' or 'warn' in the remote repository to allow pushing into\n"
+ "its current branch; however, this is not recommended unless you\n"
+ "arranged to update its work tree to match what you pushed in some\n"
+ "other way.\n"
+ "\n"
+ "To squelch this message and still keep the default behaviour, set\n"
+ "'receive.denyCurrentBranch' configuration variable to 'refuse'.");
static void refuse_unconfigured_deny(void)
{
- int i;
- for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
- rp_error("%s", refuse_unconfigured_deny_msg[i]);
+ rp_error("%s", _(refuse_unconfigured_deny_msg));
}
-static char *refuse_unconfigured_deny_delete_current_msg[] = {
- "By default, deleting the current branch is denied, because the next",
- "'git clone' won't result in any file checked out, causing confusion.",
- "",
- "You can set 'receive.denyDeleteCurrent' configuration variable to",
- "'warn' or 'ignore' in the remote repository to allow deleting the",
- "current branch, with or without a warning message.",
- "",
- "To squelch this message, you can set it to 'refuse'."
-};
+static char *refuse_unconfigured_deny_delete_current_msg =
+ N_("By default, deleting the current branch is denied, because the next\n"
+ "'git clone' won't result in any file checked out, causing confusion.\n"
+ "\n"
+ "You can set 'receive.denyDeleteCurrent' configuration variable to\n"
+ "'warn' or 'ignore' in the remote repository to allow deleting the\n"
+ "current branch, with or without a warning message.\n"
+ "\n"
+ "To squelch this message, you can set it to 'refuse'.");
static void refuse_unconfigured_deny_delete_current(void)
{
- int i;
- for (i = 0;
- i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
- i++)
- rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
+ rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg));
}
static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]);
@@ -434,11 +832,11 @@ 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;
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
uint32_t mask = 1 << (cmd->index % 32);
int i;
- trace_printf_key("GIT_TRACE_SHALLOW",
+ 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] &&
@@ -446,9 +844,9 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
!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)) {
+ opt.env = tmp_objdir_env(tmp_objdir);
+ setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
+ if (check_connected(command_singleton_iterator, cmd, &opt)) {
rollback_lock_file(&shallow_lock);
sha1_array_clear(&extra);
return -1;
@@ -468,14 +866,130 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
return 0;
}
+/*
+ * NEEDSWORK: we should consolidate various implementions of "are we
+ * on an unborn branch?" test into one, and make the unified one more
+ * robust. !get_sha1() based check used here and elsewhere would not
+ * allow us to tell an unborn branch from corrupt ref, for example.
+ * For the purpose of fixing "deploy-to-update does not work when
+ * pushing into an empty repository" issue, this should suffice for
+ * now.
+ */
+static int head_has_history(void)
+{
+ unsigned char sha1[20];
+
+ return !get_sha1("HEAD", sha1);
+}
+
+static const char *push_to_deploy(unsigned char *sha1,
+ struct argv_array *env,
+ const char *work_tree)
+{
+ 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",
+ NULL, "--", NULL
+ };
+ const char *read_tree[] = {
+ "read-tree", "-u", "-m", NULL, NULL
+ };
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ 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))
+ 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))
+ return "Working directory has unstaged changes";
+
+ /* diff-index with either HEAD or an empty tree */
+ diff_index[4] = head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX;
+
+ 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))
+ 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))
+ return "Could not update working tree to new HEAD";
+
+ return NULL;
+}
+
+static const char *push_to_checkout_hook = "push-to-checkout";
+
+static const char *push_to_checkout(unsigned char *sha1,
+ struct argv_array *env,
+ const char *work_tree)
+{
+ argv_array_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
+ if (run_hook_le(env->argv, push_to_checkout_hook,
+ sha1_to_hex(sha1), NULL))
+ return "push-to-checkout hook declined";
+ else
+ return NULL;
+}
+
+static const char *update_worktree(unsigned char *sha1)
+{
+ const char *retval;
+ const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
+ struct argv_array env = ARGV_ARRAY_INIT;
+
+ if (is_bare_repository())
+ return "denyCurrentBranch = updateInstead needs a worktree";
+
+ argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+
+ if (!find_hook(push_to_checkout_hook))
+ retval = push_to_deploy(sha1, &env, work_tree);
+ else
+ retval = push_to_checkout(sha1, &env, work_tree);
+
+ argv_array_clear(&env);
+ return retval;
+}
+
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 (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
@@ -499,6 +1013,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)
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;
}
}
@@ -514,7 +1033,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
return "deletion prohibited";
}
- if (!strcmp(namespaced_name, head_name)) {
+ if (head_name && !strcmp(namespaced_name, head_name)) {
switch (deny_delete_current) {
case DENY_IGNORE:
break;
@@ -523,10 +1042,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)
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";
}
}
}
@@ -560,6 +1082,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
}
if (is_null_sha1(new_sha1)) {
+ struct strbuf err = STRBUF_INIT;
if (!parse_object(old_sha1)) {
old_sha1 = NULL;
if (ref_exists(name)) {
@@ -569,26 +1092,35 @@ static const char *update(struct command *cmd, struct shallow_info *si)
cmd->did_not_exist = 1;
}
}
- if (delete_ref(namespaced_name, old_sha1, 0)) {
- rp_error("failed to delete %s", name);
+ if (ref_transaction_delete(transaction,
+ namespaced_name,
+ old_sha1,
+ 0, "push", &err)) {
+ rp_error("%s", err.buf);
+ strbuf_release(&err);
return "failed to delete";
}
+ strbuf_release(&err);
return NULL; /* good */
}
else {
+ struct strbuf err = STRBUF_INIT;
if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si))
return "shallow error";
- lock = lock_any_ref_for_update(namespaced_name, old_sha1,
- 0, NULL);
- if (!lock) {
- rp_error("failed to lock %s", name);
- return "failed to lock";
- }
- if (write_ref_sha1(lock, new_sha1, "push")) {
- return "failed to write"; /* error() already called */
+ if (ref_transaction_update(transaction,
+ namespaced_name,
+ new_sha1, old_sha1,
+ 0, "push",
+ &err)) {
+ rp_error("%s", err.buf);
+ strbuf_release(&err);
+
+ return "failed to update ref";
}
+ strbuf_release(&err);
+
return NULL; /* good */
}
}
@@ -597,9 +1129,8 @@ static void run_update_post_hook(struct command *commands)
{
struct command *cmd;
int argc;
- const char **argv;
- struct child_process proc;
- char *hook;
+ struct child_process proc = CHILD_PROCESS_INIT;
+ const char *hook;
hook = find_hook("post-update");
for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
@@ -610,25 +1141,16 @@ static void run_update_post_hook(struct command *commands)
if (!argc || !hook)
return;
- argv = xmalloc(sizeof(*argv) * (2 + argc));
- argv[0] = hook;
-
- for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
- char *p;
+ argv_array_push(&proc.args, hook);
+ for (cmd = commands; cmd; cmd = cmd->next) {
if (cmd->error_string || cmd->did_not_exist)
continue;
- p = xmalloc(strlen(cmd->ref_name) + 1);
- strcpy(p, cmd->ref_name);
- argv[argc] = p;
- argc++;
+ argv_array_push(&proc.args, cmd->ref_name);
}
- argv[argc] = NULL;
- memset(&proc, 0, sizeof(proc));
proc.no_stdin = 1;
proc.stdout_to_stderr = 1;
proc.err = use_sideband ? -1 : 0;
- proc.argv = argv;
if (!start_command(&proc)) {
if (use_sideband)
@@ -643,24 +1165,23 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
const char *dst_name;
struct string_list_item *item;
struct command *dst_cmd;
- unsigned char sha1[20];
- char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
+ unsigned char sha1[GIT_SHA1_RAWSZ];
int flag;
strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
- dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);
+ dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag);
strbuf_release(&buf);
if (!(flag & REF_ISSYMREF))
return;
- dst_name = strip_namespace(dst_name);
if (!dst_name) {
rp_error("refusing update to broken symref '%s'", cmd->ref_name);
cmd->skip_update = 1;
cmd->error_string = "broken symref";
return;
}
+ dst_name = strip_namespace(dst_name);
if ((item = string_list_lookup(list, dst_name)) == NULL)
return;
@@ -675,14 +1196,14 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
dst_cmd->skip_update = 1;
- strcpy(cmd_oldh, find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV));
- strcpy(cmd_newh, find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV));
- strcpy(dst_oldh, find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV));
- strcpy(dst_newh, find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV));
rp_error("refusing inconsistent update between symref '%s' (%s..%s) and"
" its target '%s' (%s..%s)",
- cmd->ref_name, cmd_oldh, cmd_newh,
- dst_cmd->ref_name, dst_oldh, dst_newh);
+ cmd->ref_name,
+ find_unique_abbrev(cmd->old_sha1, DEFAULT_ABBREV),
+ find_unique_abbrev(cmd->new_sha1, DEFAULT_ABBREV),
+ dst_cmd->ref_name,
+ find_unique_abbrev(dst_cmd->old_sha1, DEFAULT_ABBREV),
+ find_unique_abbrev(dst_cmd->new_sha1, DEFAULT_ABBREV));
cmd->error_string = dst_cmd->error_string =
"inconsistent aliased update";
@@ -698,7 +1219,7 @@ 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) {
if (!cmd->error_string)
@@ -727,12 +1248,17 @@ static void set_connectivity_errors(struct command *commands,
for (cmd = commands; cmd; cmd = cmd->next) {
struct command *singleton = cmd;
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
+
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))
+
+ opt.env = tmp_objdir_env(tmp_objdir);
+ if (!check_connected(command_singleton_iterator, &singleton,
+ &opt))
continue;
+
cmd->error_string = "missing necessary objects";
}
}
@@ -764,26 +1290,137 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
static void reject_updates_to_hidden(struct command *commands)
{
+ struct strbuf refname_full = STRBUF_INIT;
+ size_t prefix_len;
struct command *cmd;
+ strbuf_addstr(&refname_full, get_git_namespace());
+ prefix_len = refname_full.len;
+
for (cmd = commands; cmd; cmd = cmd->next) {
- if (cmd->error_string || !ref_is_hidden(cmd->ref_name))
+ if (cmd->error_string)
+ continue;
+
+ strbuf_setlen(&refname_full, prefix_len);
+ strbuf_addstr(&refname_full, cmd->ref_name);
+
+ if (!ref_is_hidden(cmd->ref_name, refname_full.buf))
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";
}
+
+ strbuf_release(&refname_full);
+}
+
+static int should_process_cmd(struct command *cmd)
+{
+ return !cmd->error_string && !cmd->skip_update;
+}
+
+static void warn_if_skipped_connectivity_check(struct command *commands,
+ struct shallow_info *si)
+{
+ struct command *cmd;
+ int checked_connectivity = 1;
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) {
+ error("BUG: connectivity check has not been run on ref %s",
+ cmd->ref_name);
+ checked_connectivity = 0;
+ }
+ }
+ if (!checked_connectivity)
+ die("BUG: connectivity check skipped???");
+}
+
+static void execute_commands_non_atomic(struct command *commands,
+ struct shallow_info *si)
+{
+ struct command *cmd;
+ struct strbuf err = STRBUF_INIT;
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!should_process_cmd(cmd))
+ continue;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ rp_error("%s", err.buf);
+ strbuf_reset(&err);
+ cmd->error_string = "transaction failed to start";
+ continue;
+ }
+
+ cmd->error_string = update(cmd, si);
+
+ if (!cmd->error_string
+ && ref_transaction_commit(transaction, &err)) {
+ rp_error("%s", err.buf);
+ strbuf_reset(&err);
+ cmd->error_string = "failed to update ref";
+ }
+ ref_transaction_free(transaction);
+ }
+ strbuf_release(&err);
+}
+
+static void execute_commands_atomic(struct command *commands,
+ struct shallow_info *si)
+{
+ struct command *cmd;
+ struct strbuf err = STRBUF_INIT;
+ const char *reported_error = "atomic push failure";
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ rp_error("%s", err.buf);
+ strbuf_reset(&err);
+ reported_error = "transaction failed to start";
+ goto failure;
+ }
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!should_process_cmd(cmd))
+ continue;
+
+ cmd->error_string = update(cmd, si);
+
+ if (cmd->error_string)
+ goto failure;
+ }
+
+ if (ref_transaction_commit(transaction, &err)) {
+ rp_error("%s", err.buf);
+ reported_error = "atomic transaction failed";
+ goto failure;
+ }
+ goto cleanup;
+
+failure:
+ for (cmd = commands; cmd; cmd = cmd->next)
+ if (!cmd->error_string)
+ cmd->error_string = reported_error;
+
+cleanup:
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
}
static void execute_commands(struct command *commands,
const char *unpacker_error,
- struct shallow_info *si)
+ struct shallow_info *si,
+ const struct string_list *push_options)
{
- int checked_connectivity;
+ struct check_connected_options opt = CHECK_CONNECTED_INIT;
struct command *cmd;
unsigned char sha1[20];
struct iterate_data data;
+ struct async muxer;
+ int err_fd = 0;
if (unpacker_error) {
for (cmd = commands; cmd; cmd = cmd->next)
@@ -791,14 +1428,29 @@ static void execute_commands(struct command *commands,
return;
}
+ if (use_sideband) {
+ memset(&muxer, 0, sizeof(muxer));
+ muxer.proc = copy_to_sideband;
+ muxer.in = -1;
+ if (!start_async(&muxer))
+ err_fd = muxer.in;
+ /* ...else, continue without relaying sideband */
+ }
+
data.cmds = commands;
data.si = si;
- if (check_everything_connected(iterate_receive_command_list, 0, &data))
+ opt.err_fd = err_fd;
+ opt.progress = err_fd && !quiet;
+ opt.env = tmp_objdir_env(tmp_objdir);
+ if (check_connected(iterate_receive_command_list, &data, &opt))
set_connectivity_errors(commands, si);
+ if (use_sideband)
+ finish_async(&muxer);
+
reject_updates_to_hidden(commands);
- if (run_receive_hook(commands, "pre-receive", 0)) {
+ if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "pre-receive hook declined";
@@ -806,35 +1458,77 @@ static void execute_commands(struct command *commands,
return;
}
+ /*
+ * Now we'll start writing out refs, which means the objects need
+ * to be in their final positions so that other processes can see them.
+ */
+ if (tmp_objdir_migrate(tmp_objdir) < 0) {
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->error_string)
+ cmd->error_string = "unable to migrate objects to permanent storage";
+ }
+ return;
+ }
+ tmp_objdir = NULL;
+
check_aliased_updates(commands);
free(head_name_to_free);
- head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
+ head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
- checked_connectivity = 1;
- for (cmd = commands; cmd; cmd = cmd->next) {
- if (cmd->error_string)
- continue;
+ if (use_atomic)
+ execute_commands_atomic(commands, si);
+ else
+ execute_commands_non_atomic(commands, si);
- if (cmd->skip_update)
- continue;
+ if (shallow_update)
+ warn_if_skipped_connectivity_check(commands, si);
+}
- 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;
- }
- }
+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;
+ FLEX_ALLOC_MEM(cmd, ref_name, refname, reflen);
+ hashcpy(cmd->old_sha1, old_sha1);
+ hashcpy(cmd->new_sha1, new_sha1);
+ *tail = cmd;
+ return &cmd->next;
+}
- if (shallow_update) {
- if (!checked_connectivity)
- error("BUG: run 'git fsck' for safety.\n"
- "If there are errors, try to remove "
- "the reported refs above");
- if (alt_shallow_file && *alt_shallow_file)
- unlink(alt_shallow_file);
+static 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;
}
}
@@ -844,51 +1538,83 @@ static struct command *read_head_info(struct sha1_array *shallow)
struct command **p = &commands;
for (;;) {
char *line;
- unsigned char old_sha1[20], new_sha1[20];
- struct command *cmd;
- char *refname;
- int len, reflen;
+ int len, linelen;
line = packet_read_line(0, &len);
if (!line)
break;
if (len == 48 && starts_with(line, "shallow ")) {
- if (get_sha1_hex(line + 8, old_sha1))
- die("protocol error: expected shallow sha, got '%s'", line + 8);
- sha1_array_append(shallow, old_sha1);
+ 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;
}
- 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) {
- const char *feature_list = refname + reflen + 1;
+ 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 (parse_feature_request(feature_list, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
if (parse_feature_request(feature_list, "quiet"))
quiet = 1;
+ if (advertise_atomic_push
+ && parse_feature_request(feature_list, "atomic"))
+ use_atomic = 1;
+ if (advertise_push_options
+ && parse_feature_request(feature_list, "push-options"))
+ use_push_options = 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;
}
+static void read_push_options(struct string_list *options)
+{
+ while (1) {
+ char *line;
+ int len;
+
+ line = packet_read_line(0, &len);
+
+ if (!line)
+ break;
+
+ string_list_append(options, line);
+ }
+}
+
static const char *parse_pack_header(struct pack_header *hdr)
{
switch (read_pack_header(0, hdr)) {
@@ -914,11 +1640,10 @@ static const char *pack_lockfile;
static const char *unpack(int err_fd, struct shallow_info *si)
{
struct pack_header hdr;
- struct argv_array av = ARGV_ARRAY_INIT;
const char *hdr_err;
int status;
char hdr_arg[38];
- struct child_process child;
+ struct child_process child = CHILD_PROCESS_INIT;
int fsck_objects = (receive_fsck_objects >= 0
? receive_fsck_objects
: transfer_fsck_objects >= 0
@@ -937,17 +1662,32 @@ static const char *unpack(int err_fd, struct shallow_info *si)
if (si->nr_ours || si->nr_theirs) {
alt_shallow_file = setup_temporary_shallow(si->shallow);
- argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL);
+ argv_array_push(&child.args, "--shallow-file");
+ argv_array_push(&child.args, alt_shallow_file);
}
- memset(&child, 0, sizeof(child));
+ tmp_objdir = tmp_objdir_create();
+ if (!tmp_objdir)
+ return "unable to create temporary object directory";
+ child.env = tmp_objdir_env(tmp_objdir);
+
+ /*
+ * Normally we just pass the tmp_objdir environment to the child
+ * processes that do the heavy lifting, but we may need to see these
+ * objects ourselves to set up shallow information.
+ */
+ tmp_objdir_add_as_alternate(tmp_objdir);
+
if (ntohl(hdr.hdr_entries) < unpack_limit) {
- argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL);
+ argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
if (quiet)
- argv_array_push(&av, "-q");
+ argv_array_push(&child.args, "-q");
if (fsck_objects)
- argv_array_push(&av, "--strict");
- child.argv = av.argv;
+ argv_array_pushf(&child.args, "--strict%s",
+ fsck_msg_types.buf);
+ if (max_input_size)
+ argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX,
+ (uintmax_t)max_input_size);
child.no_stdout = 1;
child.err = err_fd;
child.git_cmd = 1;
@@ -955,20 +1695,30 @@ static const char *unpack(int err_fd, struct shallow_info *si)
if (status)
return "unpack-objects abnormal exit";
} else {
- int s;
- char keep_arg[256];
+ char hostname[256];
- 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");
+ argv_array_pushl(&child.args, "index-pack",
+ "--stdin", hdr_arg, NULL);
- argv_array_pushl(&av, "index-pack",
- "--stdin", hdr_arg, keep_arg, NULL);
+ if (gethostname(hostname, sizeof(hostname)))
+ xsnprintf(hostname, sizeof(hostname), "localhost");
+ argv_array_pushf(&child.args,
+ "--keep=receive-pack %"PRIuMAX" on %s",
+ (uintmax_t)getpid(),
+ hostname);
+
+ if (!quiet && err_fd)
+ argv_array_push(&child.args, "--show-resolving-progress");
+ if (use_sideband)
+ argv_array_push(&child.args, "--report-end-of-input");
if (fsck_objects)
- argv_array_push(&av, "--strict");
- if (fix_thin)
- argv_array_push(&av, "--fix-thin");
- child.argv = av.argv;
+ argv_array_pushf(&child.args, "--strict%s",
+ fsck_msg_types.buf);
+ if (!reject_thin)
+ argv_array_push(&child.args, "--fix-thin");
+ if (max_input_size)
+ argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX,
+ (uintmax_t)max_input_size);
child.out = -1;
child.err = err_fd;
child.git_cmd = 1;
@@ -993,6 +1743,7 @@ static const char *unpack_with_sideband(struct shallow_info *si)
if (!use_sideband)
return unpack(0, si);
+ use_keepalive = KEEPALIVE_AFTER_NUL;
memset(&muxer, 0, sizeof(muxer));
muxer.proc = copy_to_sideband;
muxer.in = -1;
@@ -1010,8 +1761,7 @@ static void prepare_shallow_update(struct command *commands,
{
int i, j, k, bitmap_size = (si->ref->nr + 31) / 32;
- si->used_shallow = xmalloc(sizeof(*si->used_shallow) *
- si->shallow->nr);
+ ALLOC_ARRAY(si->used_shallow, si->shallow->nr);
assign_shallow_commits_to_refs(si, si->used_shallow, NULL);
si->need_reachability_test =
@@ -1031,7 +1781,7 @@ static void prepare_shallow_update(struct command *commands,
continue;
si->need_reachability_test[i]++;
for (k = 0; k < 32; k++)
- if (si->used_shallow[i][j] & (1 << k))
+ if (si->used_shallow[i][j] & (1U << k))
si->shallow_ref[j * 32 + k]++;
}
@@ -1077,7 +1827,7 @@ static void update_shallow_info(struct command *commands,
return;
}
- ref_status = xmalloc(sizeof(*ref_status) * ref->nr);
+ ALLOC_ARRAY(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))
@@ -1087,10 +1837,6 @@ static void update_shallow_info(struct command *commands,
cmd->skip_update = 1;
}
}
- if (alt_shallow_file && *alt_shallow_file) {
- unlink(alt_shallow_file);
- alt_shallow_file = NULL;
- }
free(ref_status);
}
@@ -1131,54 +1877,38 @@ static int delete_only(struct command *commands)
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;
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("quiet")),
+ OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL),
+ OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL),
+ OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL),
+ OPT_END()
+ };
+
packet_trace_identity("receive-pack");
- argv++;
- for (i = 1; i < argc; i++) {
- const char *arg = *argv++;
+ argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);
- if (*arg == '-') {
- if (!strcmp(arg, "--quiet")) {
- quiet = 1;
- continue;
- }
+ if (argc > 1)
+ usage_msg_opt(_("Too many arguments."), receive_pack_usage, options);
+ if (argc == 0)
+ usage_msg_opt(_("You must specify a directory."), receive_pack_usage, options);
- if (!strcmp(arg, "--advertise-refs")) {
- advertise_refs = 1;
- continue;
- }
- if (!strcmp(arg, "--stateless-rpc")) {
- stateless_rpc = 1;
- continue;
- }
- if (!strcmp(arg, "--reject-thin-pack-for-testing")) {
- fix_thin = 0;
- continue;
- }
-
- usage(receive_pack_usage);
- }
- if (dir)
- usage(receive_pack_usage);
- dir = xstrdup(arg);
- }
- if (!dir)
- usage(receive_pack_usage);
+ service_dir = argv[0];
setup_path();
- if (!enter_repo(dir, 0))
- die("'%s' does not appear to be a git repository", dir);
+ 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;
@@ -1193,6 +1923,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if ((commands = read_head_info(&shallow)) != NULL) {
const char *unpack_status = NULL;
+ struct string_list push_options = STRING_LIST_INIT_DUP;
+
+ if (use_push_options)
+ read_push_options(&push_options);
prepare_shallow_info(&si, &shallow);
if (!si.nr_ours && !si.nr_theirs)
@@ -1201,19 +1935,35 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
unpack_status = unpack_with_sideband(&si);
update_shallow_info(commands, &si, &ref);
}
- execute_commands(commands, unpack_status, &si);
+ use_keepalive = KEEPALIVE_ALWAYS;
+ execute_commands(commands, unpack_status, &si,
+ &push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
if (report_status)
report(commands, unpack_status);
- run_receive_hook(commands, "post-receive", 1);
+ run_receive_hook(commands, "post-receive", 1,
+ &push_options);
run_update_post_hook(commands);
+ string_list_clear(&push_options, 0);
if (auto_gc) {
const char *argv_gc_auto[] = {
"gc", "--auto", "--quiet", NULL,
};
- int opt = RUN_GIT_CMD | RUN_COMMAND_STDOUT_TO_STDERR;
- run_command_v_opt(argv_gc_auto, opt);
+ struct child_process proc = CHILD_PROCESS_INIT;
+
+ proc.no_stdin = 1;
+ proc.stdout_to_stderr = 1;
+ proc.err = use_sideband ? -1 : 0;
+ proc.git_cmd = 1;
+ proc.argv = argv_gc_auto;
+
+ close_all_packs();
+ if (!start_command(&proc)) {
+ if (use_sideband)
+ copy_to_sideband(proc.err, -1, NULL);
+ finish_command(&proc);
+ }
}
if (auto_update_server_info)
update_server_info(0);
@@ -1223,5 +1973,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
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 852cff60b7..7a7136e53e 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"
@@ -8,32 +8,26 @@
#include "revision.h"
#include "reachable.h"
-/*
- * reflog expire
- */
-
+/* NEEDSWORK: switch to using parse_options */
static const char reflog_expire_usage[] =
-"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
static const char reflog_delete_usage[] =
-"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>...";
+"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
+static const char reflog_exists_usage[] =
+"git reflog exists <ref>";
static unsigned long default_reflog_expire;
static unsigned long default_reflog_expire_unreachable;
struct cmd_reflog_expire_cb {
struct rev_info revs;
- int dry_run;
int stalefix;
- int rewrite;
- int updateref;
- int verbose;
unsigned long expire_total;
unsigned long expire_unreachable;
int recno;
};
-struct expire_reflog_cb {
- FILE *newlog;
+struct expire_reflog_policy_cb {
enum {
UE_NORMAL,
UE_ALWAYS,
@@ -41,14 +35,16 @@ struct expire_reflog_cb {
} unreachable_expire_kind;
struct commit_list *mark_list;
unsigned long mark_limit;
- struct cmd_reflog_expire_cb *cmd;
- unsigned char last_kept_sha1[20];
+ struct cmd_reflog_expire_cb cmd;
+ struct commit *tip_commit;
+ struct commit_list *tips;
};
struct collected_reflog {
unsigned char sha1[20];
char reflog[FLEX_ARRAY];
};
+
struct collect_reflog_cb {
struct collected_reflog **e;
int alloc;
@@ -88,8 +84,8 @@ static int tree_is_complete(const unsigned char *sha1)
init_tree_desc(&desc, tree->buffer, tree->size);
complete = 1;
while (tree_entry(&desc, &entry)) {
- if (!has_sha1_file(entry.sha1) ||
- (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
+ if (!has_sha1_file(entry.oid->hash) ||
+ (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid->hash))) {
tree->object.flags |= INCOMPLETE;
complete = 0;
}
@@ -130,7 +126,7 @@ static int commit_is_complete(struct commit *commit)
struct commit_list *parent;
c = (struct commit *)study.objects[--study.nr].item;
- if (!c->object.parsed && !parse_object(c->object.sha1))
+ if (!c->object.parsed && !parse_object(c->object.oid.hash))
c->object.flags |= INCOMPLETE;
if (c->object.flags & INCOMPLETE) {
@@ -156,7 +152,7 @@ static int commit_is_complete(struct commit *commit)
for (i = 0; i < found.nr; i++) {
struct commit *c =
(struct commit *)found.objects[i].item;
- if (!tree_is_complete(c->tree->object.sha1)) {
+ if (!tree_is_complete(c->tree->object.oid.hash)) {
is_incomplete = 1;
c->object.flags |= INCOMPLETE;
}
@@ -220,9 +216,8 @@ static int keep_entry(struct commit **it, unsigned char *sha1)
* the expire_limit and queue them back, so that the caller can call
* us again to restart the traversal with longer expire_limit.
*/
-static void mark_reachable(struct expire_reflog_cb *cb)
+static void mark_reachable(struct expire_reflog_policy_cb *cb)
{
- struct commit *commit;
struct commit_list *pending;
unsigned long expire_limit = cb->mark_limit;
struct commit_list *leftover = NULL;
@@ -232,11 +227,8 @@ static void mark_reachable(struct expire_reflog_cb *cb)
pending = cb->mark_list;
while (pending) {
- struct commit_list *entry = pending;
struct commit_list *parent;
- pending = entry->next;
- commit = entry->item;
- free(entry);
+ struct commit *commit = pop_commit(&pending);
if (commit->object.flags & REACHABLE)
continue;
if (parse_commit(commit))
@@ -259,7 +251,7 @@ static void mark_reachable(struct expire_reflog_cb *cb)
cb->mark_list = leftover;
}
-static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, unsigned char *sha1)
{
/*
* We may or may not have the commit yet - if not, look it
@@ -288,176 +280,111 @@ static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsig
return !(commit->object.flags & REACHABLE);
}
-static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
- const char *email, unsigned long timestamp, int tz,
- const char *message, void *cb_data)
+/*
+ * Return true iff the specified reflog entry should be expired.
+ */
+static int should_expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
{
- struct expire_reflog_cb *cb = cb_data;
+ struct expire_reflog_policy_cb *cb = cb_data;
struct commit *old, *new;
- if (timestamp < cb->cmd->expire_total)
- goto prune;
-
- if (cb->cmd->rewrite)
- osha1 = cb->last_kept_sha1;
+ if (timestamp < cb->cmd.expire_total)
+ return 1;
old = new = NULL;
- if (cb->cmd->stalefix &&
+ if (cb->cmd.stalefix &&
(!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
- goto prune;
+ return 1;
- if (timestamp < cb->cmd->expire_unreachable) {
+ if (timestamp < cb->cmd.expire_unreachable) {
if (cb->unreachable_expire_kind == UE_ALWAYS)
- goto prune;
+ return 1;
if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
- goto prune;
+ return 1;
}
- if (cb->cmd->recno && --(cb->cmd->recno) == 0)
- goto prune;
-
- if (cb->newlog) {
- char sign = (tz < 0) ? '-' : '+';
- int zone = (tz < 0) ? (-tz) : tz;
- fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
- sha1_to_hex(osha1), sha1_to_hex(nsha1),
- email, timestamp, sign, zone,
- message);
- hashcpy(cb->last_kept_sha1, nsha1);
- }
- if (cb->cmd->verbose)
- printf("keep %s", message);
- return 0;
- prune:
- if (!cb->newlog)
- printf("would prune %s", message);
- else if (cb->cmd->verbose)
- printf("prune %s", message);
+ if (cb->cmd.recno && --(cb->cmd.recno) == 0)
+ return 1;
+
return 0;
}
-static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+static int push_tip_to_list(const char *refname, const struct object_id *oid,
+ int flags, void *cb_data)
{
struct commit_list **list = cb_data;
struct commit *tip_commit;
if (flags & REF_ISSYMREF)
return 0;
- tip_commit = lookup_commit_reference_gently(sha1, 1);
+ tip_commit = lookup_commit_reference_gently(oid->hash, 1);
if (!tip_commit)
return 0;
commit_list_insert(tip_commit, list);
return 0;
}
-static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+static void reflog_expiry_prepare(const char *refname,
+ const unsigned char *sha1,
+ void *cb_data)
{
- struct cmd_reflog_expire_cb *cmd = cb_data;
- struct expire_reflog_cb cb;
- struct ref_lock *lock;
- char *log_file, *newlog_path = NULL;
- struct commit *tip_commit;
- struct commit_list *tips;
- int status = 0;
-
- memset(&cb, 0, sizeof(cb));
-
- /*
- * we take the lock for the ref itself to prevent it from
- * getting updated.
- */
- 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))
- goto finish;
- if (!cmd->dry_run) {
- newlog_path = git_pathdup("logs/%s.lock", ref);
- cb.newlog = fopen(newlog_path, "w");
- }
+ struct expire_reflog_policy_cb *cb = cb_data;
- cb.cmd = cmd;
-
- if (!cmd->expire_unreachable || !strcmp(ref, "HEAD")) {
- tip_commit = NULL;
- cb.unreachable_expire_kind = UE_HEAD;
+ if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) {
+ cb->tip_commit = NULL;
+ cb->unreachable_expire_kind = UE_HEAD;
} else {
- tip_commit = lookup_commit_reference_gently(sha1, 1);
- if (!tip_commit)
- cb.unreachable_expire_kind = UE_ALWAYS;
+ cb->tip_commit = lookup_commit_reference_gently(sha1, 1);
+ if (!cb->tip_commit)
+ cb->unreachable_expire_kind = UE_ALWAYS;
else
- cb.unreachable_expire_kind = UE_NORMAL;
+ cb->unreachable_expire_kind = UE_NORMAL;
}
- if (cmd->expire_unreachable <= cmd->expire_total)
- cb.unreachable_expire_kind = UE_ALWAYS;
+ if (cb->cmd.expire_unreachable <= cb->cmd.expire_total)
+ cb->unreachable_expire_kind = UE_ALWAYS;
- cb.mark_list = NULL;
- tips = NULL;
- if (cb.unreachable_expire_kind != UE_ALWAYS) {
- if (cb.unreachable_expire_kind == UE_HEAD) {
+ cb->mark_list = NULL;
+ cb->tips = NULL;
+ if (cb->unreachable_expire_kind != UE_ALWAYS) {
+ if (cb->unreachable_expire_kind == UE_HEAD) {
struct commit_list *elem;
- for_each_ref(push_tip_to_list, &tips);
- for (elem = tips; elem; elem = elem->next)
- commit_list_insert(elem->item, &cb.mark_list);
+
+ for_each_ref(push_tip_to_list, &cb->tips);
+ for (elem = cb->tips; elem; elem = elem->next)
+ commit_list_insert(elem->item, &cb->mark_list);
} else {
- commit_list_insert(tip_commit, &cb.mark_list);
+ commit_list_insert(cb->tip_commit, &cb->mark_list);
}
- cb.mark_limit = cmd->expire_total;
- mark_reachable(&cb);
+ cb->mark_limit = cb->cmd.expire_total;
+ mark_reachable(cb);
}
+}
- for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+static void reflog_expiry_cleanup(void *cb_data)
+{
+ struct expire_reflog_policy_cb *cb = cb_data;
- if (cb.unreachable_expire_kind != UE_ALWAYS) {
- if (cb.unreachable_expire_kind == UE_HEAD) {
+ if (cb->unreachable_expire_kind != UE_ALWAYS) {
+ if (cb->unreachable_expire_kind == UE_HEAD) {
struct commit_list *elem;
- for (elem = tips; elem; elem = elem->next)
+ for (elem = cb->tips; elem; elem = elem->next)
clear_commit_marks(elem->item, REACHABLE);
- free_commit_list(tips);
+ free_commit_list(cb->tips);
} else {
- clear_commit_marks(tip_commit, REACHABLE);
+ clear_commit_marks(cb->tip_commit, REACHABLE);
}
}
- finish:
- if (cb.newlog) {
- if (fclose(cb.newlog)) {
- status |= error("%s: %s", strerror(errno),
- newlog_path);
- unlink(newlog_path);
- } else if (cmd->updateref &&
- (write_in_full(lock->lock_fd,
- sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
- write_str_in_full(lock->lock_fd, "\n") != 1 ||
- close_ref(lock) < 0)) {
- status |= error("Couldn't write %s",
- lock->lk->filename);
- unlink(newlog_path);
- } else if (rename(newlog_path, log_file)) {
- status |= error("cannot rename %s to %s",
- newlog_path, log_file);
- unlink(newlog_path);
- } else if (cmd->updateref && commit_ref(lock)) {
- status |= error("Couldn't set %s", lock->ref_name);
- } else {
- adjust_shared_perm(log_file);
- }
- }
- free(newlog_path);
- free(log_file);
- unlock_ref(lock);
- return status;
}
-static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data)
{
struct collected_reflog *e;
struct collect_reflog_cb *cb = cb_data;
- size_t namelen = strlen(ref);
- e = xmalloc(sizeof(*e) + namelen + 1);
- hashcpy(e->sha1, sha1);
- memcpy(e->reflog, ref, namelen + 1);
+ FLEX_ALLOC_STR(e, reflog, ref);
+ hashcpy(e->sha1, oid->hash);
ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc);
cb->e[cb->nr++] = e;
return 0;
@@ -467,7 +394,6 @@ static struct reflog_expire_cfg {
struct reflog_expire_cfg *next;
unsigned long expire_total;
unsigned long expire_unreachable;
- size_t len;
char pattern[FLEX_ARRAY];
} *reflog_expire_cfg, **reflog_expire_cfg_tail;
@@ -479,13 +405,11 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)
reflog_expire_cfg_tail = &reflog_expire_cfg;
for (ent = reflog_expire_cfg; ent; ent = ent->next)
- if (ent->len == len &&
- !memcmp(ent->pattern, pattern, len))
+ if (!strncmp(ent->pattern, pattern, len) &&
+ ent->pattern[len] == '\0')
return ent;
- ent = xcalloc(1, (sizeof(*ent) + len));
- memcpy(ent->pattern, pattern, len);
- ent->len = len;
+ FLEX_ALLOC_MEM(ent, pattern, pattern, len);
*reflog_expire_cfg_tail = ent;
reflog_expire_cfg_tail = &(ent->next);
return ent;
@@ -496,7 +420,7 @@ static int parse_expire_cfg_value(const char *var, const char *value, unsigned l
if (!value)
return config_error_nonbool(var);
if (parse_expiry_date(value, expire))
- return error(_("%s' for '%s' is not a valid timestamp"),
+ return error(_("'%s' for '%s' is not a valid timestamp"),
value, var);
return 0;
}
@@ -561,7 +485,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))
@@ -590,10 +514,11 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c
static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
{
- struct cmd_reflog_expire_cb cb;
+ struct expire_reflog_policy_cb cb;
unsigned long now = time(NULL);
int i, status, do_all;
int explicit_expiry = 0;
+ unsigned int flags = 0;
default_reflog_expire_unreachable = now - 30 * 24 * 3600;
default_reflog_expire = now - 90 * 24 * 3600;
@@ -603,33 +528,33 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
do_all = status = 0;
memset(&cb, 0, sizeof(cb));
- cb.expire_total = default_reflog_expire;
- cb.expire_unreachable = default_reflog_expire_unreachable;
+ cb.cmd.expire_total = default_reflog_expire;
+ cb.cmd.expire_unreachable = default_reflog_expire_unreachable;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
- cb.dry_run = 1;
+ flags |= EXPIRE_REFLOGS_DRY_RUN;
else if (starts_with(arg, "--expire=")) {
- if (parse_expiry_date(arg + 9, &cb.expire_total))
+ if (parse_expiry_date(arg + 9, &cb.cmd.expire_total))
die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_TOTAL;
}
else if (starts_with(arg, "--expire-unreachable=")) {
- if (parse_expiry_date(arg + 21, &cb.expire_unreachable))
+ if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable))
die(_("'%s' is not a valid timestamp"), arg);
explicit_expiry |= EXPIRE_UNREACH;
}
else if (!strcmp(arg, "--stale-fix"))
- cb.stalefix = 1;
+ cb.cmd.stalefix = 1;
else if (!strcmp(arg, "--rewrite"))
- cb.rewrite = 1;
+ flags |= EXPIRE_REFLOGS_REWRITE;
else if (!strcmp(arg, "--updateref"))
- cb.updateref = 1;
+ flags |= EXPIRE_REFLOGS_UPDATE_REF;
else if (!strcmp(arg, "--all"))
do_all = 1;
else if (!strcmp(arg, "--verbose"))
- cb.verbose = 1;
+ flags |= EXPIRE_REFLOGS_VERBOSE;
else if (!strcmp(arg, "--")) {
i++;
break;
@@ -645,12 +570,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
* even in older repository. We cannot trust what's reachable
* from reflog if the repository was pruned with older git.
*/
- if (cb.stalefix) {
- init_revisions(&cb.revs, prefix);
- if (cb.verbose)
+ if (cb.cmd.stalefix) {
+ init_revisions(&cb.cmd.revs, prefix);
+ if (flags & EXPIRE_REFLOGS_VERBOSE)
printf("Marking reachable objects...");
- mark_reachable_objects(&cb.revs, 0, NULL);
- if (cb.verbose)
+ mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL);
+ if (flags & EXPIRE_REFLOGS_VERBOSE)
putchar('\n');
}
@@ -662,8 +587,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
for_each_reflog(collect_reflog, &collected);
for (i = 0; i < collected.nr; i++) {
struct collected_reflog *e = collected.e[i];
- set_reflog_expiry_param(&cb, explicit_expiry, e->reflog);
- status |= expire_reflog(e->reflog, e->sha1, 0, &cb);
+ set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog);
+ status |= reflog_expire(e->reflog, e->sha1, flags,
+ reflog_expiry_prepare,
+ should_expire_reflog_ent,
+ reflog_expiry_cleanup,
+ &cb);
free(e);
}
free(collected.e);
@@ -676,8 +605,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
status |= error("%s points nowhere!", argv[i]);
continue;
}
- set_reflog_expiry_param(&cb, explicit_expiry, ref);
- status |= expire_reflog(ref, sha1, 0, &cb);
+ set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref);
+ status |= reflog_expire(ref, sha1, flags,
+ reflog_expiry_prepare,
+ should_expire_reflog_ent,
+ reflog_expiry_cleanup,
+ &cb);
}
return status;
}
@@ -686,29 +619,30 @@ static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
const char *email, unsigned long timestamp, int tz,
const char *message, void *cb_data)
{
- struct cmd_reflog_expire_cb *cb = cb_data;
- if (!cb->expire_total || timestamp < cb->expire_total)
- cb->recno++;
+ struct expire_reflog_policy_cb *cb = cb_data;
+ if (!cb->cmd.expire_total || timestamp < cb->cmd.expire_total)
+ cb->cmd.recno++;
return 0;
}
static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
{
- struct cmd_reflog_expire_cb cb;
+ struct expire_reflog_policy_cb cb;
int i, status = 0;
+ unsigned int flags = 0;
memset(&cb, 0, sizeof(cb));
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
- cb.dry_run = 1;
+ flags |= EXPIRE_REFLOGS_DRY_RUN;
else if (!strcmp(arg, "--rewrite"))
- cb.rewrite = 1;
+ flags |= EXPIRE_REFLOGS_REWRITE;
else if (!strcmp(arg, "--updateref"))
- cb.updateref = 1;
+ flags |= EXPIRE_REFLOGS_UPDATE_REF;
else if (!strcmp(arg, "--verbose"))
- cb.verbose = 1;
+ flags |= EXPIRE_REFLOGS_VERBOSE;
else if (!strcmp(arg, "--")) {
i++;
break;
@@ -740,26 +674,56 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
recno = strtoul(spec + 2, &ep, 10);
if (*ep == '}') {
- cb.recno = -recno;
+ cb.cmd.recno = -recno;
for_each_reflog_ent(ref, count_reflog_ent, &cb);
} else {
- cb.expire_total = approxidate(spec + 2);
+ cb.cmd.expire_total = approxidate(spec + 2);
for_each_reflog_ent(ref, count_reflog_ent, &cb);
- cb.expire_total = 0;
+ cb.cmd.expire_total = 0;
}
- status |= expire_reflog(ref, sha1, 0, &cb);
+ status |= reflog_expire(ref, sha1, flags,
+ reflog_expiry_prepare,
+ should_expire_reflog_ent,
+ reflog_expiry_cleanup,
+ &cb);
free(ref);
}
return status;
}
+static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
+{
+ int i, start = 0;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ else if (arg[0] == '-')
+ usage(reflog_exists_usage);
+ else
+ break;
+ }
+
+ start = i;
+
+ if (argc - start != 1)
+ usage(reflog_exists_usage);
+
+ if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL))
+ die("invalid ref format: %s", argv[start]);
+ return !reflog_exists(argv[start]);
+}
+
/*
* main "reflog"
*/
static const char reflog_usage[] =
-"git reflog [ show | expire | delete ]";
+"git reflog [ show | expire | delete | exists ]";
int cmd_reflog(int argc, const char **argv, const char *prefix)
{
@@ -779,5 +743,8 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
if (!strcmp(argv[1], "delete"))
return cmd_reflog_delete(argc - 1, argv + 1, prefix);
+ if (!strcmp(argv[1], "exists"))
+ return cmd_reflog_exists(argc - 1, argv + 1, prefix);
+
return cmd_log_reflog(argc, argv, prefix);
}
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
index 692c834d9d..11b48bfb41 100644
--- a/builtin/remote-ext.c
+++ b/builtin/remote-ext.c
@@ -1,6 +1,7 @@
#include "builtin.h"
#include "transport.h"
#include "run-command.h"
+#include "pkt-line.h"
/*
* URL syntax:
@@ -30,16 +31,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 +84,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);
@@ -115,77 +114,35 @@ static char *strip_escapes(const char *str, const char *service,
}
}
-/* Should be enough... */
-#define MAXARGUMENTS 256
-
-static const char **parse_argv(const char *arg, const char *service)
+static void parse_argv(struct argv_array *out, const char *arg, const char *service)
{
- int arguments = 0;
- int i;
- const char **ret;
- char *temparray[MAXARGUMENTS + 1];
-
while (*arg) {
- char *expanded;
- if (arguments == MAXARGUMENTS)
- die("remote-ext command has too many arguments");
- expanded = strip_escapes(arg, service, &arg);
+ char *expanded = strip_escapes(arg, service, &arg);
if (expanded)
- temparray[arguments++] = expanded;
+ argv_array_push(out, expanded);
+ free(expanded);
}
-
- ret = xmalloc((arguments + 1) * sizeof(char *));
- for (i = 0; i < arguments; i++)
- ret[i] = temparray[i];
- ret[arguments] = NULL;
- return ret;
}
static void send_git_request(int stdin_fd, const char *serv, const char *repo,
const char *vhost)
{
- size_t bufferspace;
- size_t wpos = 0;
- char *buffer;
-
- /*
- * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
- * 6 bytes extra (xxxx \0) if there is no vhost.
- */
- if (vhost)
- bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
+ if (!vhost)
+ packet_write_fmt(stdin_fd, "%s %s%c", serv, repo, 0);
else
- bufferspace = strlen(serv) + strlen(repo) + 6;
-
- if (bufferspace > 0xFFFF)
- die("Request too large to send");
- buffer = xmalloc(bufferspace);
-
- /* Make the packet. */
- wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
- serv, repo, 0);
-
- /* Add vhost if any. */
- if (vhost)
- sprintf(buffer + wpos, "host=%s%c", vhost, 0);
-
- /* Send the request */
- if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
- die_errno("Failed to send request");
-
- free(buffer);
+ packet_write_fmt(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0,
+ vhost, 0);
}
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;
- child.argv = parse_argv(arg, service);
+ parse_argv(&child.args, arg, service);
if (start_command(&child) < 0)
die("Can't run specified command");
@@ -211,7 +168,7 @@ static int command_loop(const char *child)
size_t i;
if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
if (ferror(stdin))
- die("Comammand input error");
+ die("Command input error");
exit(0);
}
/* Strip end of line characters. */
diff --git a/builtin/remote.c b/builtin/remote.c
index b3ab4cf8f6..addf97ad29 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -10,14 +10,15 @@
static const char * const builtin_remote_usage[] = {
N_("git remote [-v | --verbose]"),
- N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>"),
+ N_("git remote 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 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 get-url [--push] [--all] <name>"),
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>"),
@@ -65,6 +66,11 @@ static const char * const builtin_remote_update_usage[] = {
NULL
};
+static const char * const builtin_remote_geturl_usage[] = {
+ N_("git remote get-url [--push] [--all] <name>"),
+ NULL
+};
+
static const char * const builtin_remote_seturl_usage[] = {
N_("git remote set-url [--push] <name> <newurl> [<oldurl>]"),
N_("git remote set-url --add <name> <newurl>"),
@@ -102,8 +108,8 @@ enum {
#define MIRROR_PUSH 2
#define MIRROR_BOTH (MIRROR_FETCH|MIRROR_PUSH)
-static int add_branch(const char *key, const char *branchname,
- const char *remotename, int mirror, struct strbuf *tmp)
+static void add_branch(const char *key, const char *branchname,
+ const char *remotename, int mirror, struct strbuf *tmp)
{
strbuf_reset(tmp);
strbuf_addch(tmp, '+');
@@ -113,7 +119,7 @@ static int add_branch(const char *key, const char *branchname,
else
strbuf_addf(tmp, "refs/heads/%s:refs/remotes/%s/%s",
branchname, remotename, branchname);
- return git_config_set_multivar(key, tmp->buf, "^$", 0);
+ git_config_set_multivar(key, tmp->buf, "^$", 0);
}
static const char mirror_advice[] =
@@ -180,8 +186,7 @@ static int add(int argc, const char **argv)
url = argv[1];
remote = remote_get(name);
- if (remote && (remote->url_nr > 1 || strcmp(name, remote->url[0]) ||
- remote->fetch_refspec_nr))
+ if (remote_is_configured(remote, 1))
die(_("remote %s already exists."), name);
strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name);
@@ -189,8 +194,7 @@ static int add(int argc, const char **argv)
die(_("'%s' is not a valid remote name"), name);
strbuf_addf(&buf, "remote.%s.url", name);
- if (git_config_set(buf.buf, url))
- return 1;
+ git_config_set(buf.buf, url);
if (!mirror || mirror & MIRROR_FETCH) {
strbuf_reset(&buf);
@@ -198,25 +202,22 @@ static int add(int argc, const char **argv)
if (track.nr == 0)
string_list_append(&track, "*");
for (i = 0; i < track.nr; i++) {
- if (add_branch(buf.buf, track.items[i].string,
- name, mirror, &buf2))
- return 1;
+ add_branch(buf.buf, track.items[i].string,
+ name, mirror, &buf2);
}
}
if (mirror & MIRROR_PUSH) {
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.mirror", name);
- if (git_config_set(buf.buf, "true"))
- return 1;
+ git_config_set(buf.buf, "true");
}
if (fetch_tags != TAGS_DEFAULT) {
strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.tagopt", name);
- if (git_config_set(buf.buf,
- fetch_tags == TAGS_SET ? "--tags" : "--no-tags"))
- return 1;
+ git_config_set(buf.buf,
+ fetch_tags == TAGS_SET ? "--tags" : "--no-tags");
}
if (fetch && fetch_remote(name))
@@ -243,16 +244,14 @@ static int add(int argc, const char **argv)
struct branch_info {
char *remote_name;
struct string_list merge;
- int rebase;
+ enum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase;
};
-static struct string_list branch_list;
+static struct string_list branch_list = STRING_LIST_INIT_NODUP;
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/")
@@ -265,16 +264,17 @@ static int config_read_branches(const char *key, const char *value, void *cb)
struct string_list_item *item;
struct branch_info *info;
enum { REMOTE, MERGE, REBASE } type;
+ size_t key_len;
key += 7;
- if (ends_with(key, ".remote")) {
- name = xstrndup(key, strlen(key) - 7);
+ if (strip_suffix(key, ".remote", &key_len)) {
+ name = xmemdupz(key, key_len);
type = REMOTE;
- } else if (ends_with(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 (ends_with(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;
@@ -282,7 +282,7 @@ 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)
@@ -304,7 +304,9 @@ static int config_read_branches(const char *key, const char *value, void *cb)
if (v >= 0)
info->rebase = v;
else if (!strcmp(value, "preserve"))
- info->rebase = 1;
+ info->rebase = NORMAL_REBASE;
+ else if (!strcmp(value, "interactive"))
+ info->rebase = INTERACTIVE_REBASE;
}
}
return 0;
@@ -353,9 +355,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;
}
@@ -394,23 +396,23 @@ static int get_push_ref_states(const struct ref *remote_refs,
if (!ref->peer_ref)
continue;
- hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+ oidcpy(&ref->new_oid, &ref->peer_ref->new_oid);
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));
- if (is_null_sha1(ref->new_sha1)) {
+ if (is_null_oid(&ref->new_oid)) {
info->status = PUSH_STATUS_DELETE;
- } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
+ } else if (!oidcmp(&ref->old_oid, &ref->new_oid))
info->status = PUSH_STATUS_UPTODATE;
- else if (is_null_sha1(ref->old_sha1))
+ else if (is_null_oid(&ref->old_oid))
info->status = PUSH_STATUS_CREATE;
- else if (has_sha1_file(ref->old_sha1) &&
- ref_newer(ref->new_sha1, ref->old_sha1))
+ else if (has_object_file(&ref->old_oid) &&
+ ref_newer(&ref->new_oid, &ref->old_oid))
info->status = PUSH_STATUS_FASTFORWARD;
else
info->status = PUSH_STATUS_OUTOFDATE;
@@ -433,7 +435,7 @@ 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);
+ info = item->util = xcalloc(1, sizeof(struct push_info));
info->status = PUSH_STATUS_NOTQUERIED;
info->dest = xstrdup(item->string);
}
@@ -446,7 +448,7 @@ static int get_push_ref_states_noquery(struct ref_states *states)
else
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);
@@ -508,11 +510,10 @@ struct branches_for_remote {
};
static int add_branch_for_removal(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
+ const struct object_id *oid, int flags, void *cb_data)
{
struct branches_for_remote *branches = cb_data;
struct refspec refspec;
- struct string_list_item *item;
struct known_remote *kr;
memset(&refspec, 0, sizeof(refspec));
@@ -538,13 +539,7 @@ static int add_branch_for_removal(const char *refname,
return 0;
}
- /* make sure that symrefs are deleted */
- if (flags & REF_ISSYMREF)
- return unlink(git_path("%s", refname));
-
- item = string_list_append(branches->branches, refname);
- item->util = xmalloc(20);
- hashcpy(item->util, sha1);
+ string_list_append(branches->branches, refname);
return 0;
}
@@ -556,19 +551,20 @@ struct rename_info {
};
static int read_remote_branches(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
+ const struct object_id *oid, int flags, void *cb_data)
{
struct rename_info *rename = cb_data;
struct strbuf buf = STRBUF_INIT;
struct string_list_item *item;
int flag;
- unsigned char orig_sha1[20];
+ struct object_id orig_oid;
const char *symref;
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_unsafe(refname, orig_sha1, 1, &flag);
+ symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,
+ orig_oid.hash, &flag);
if (flag & REF_ISSYMREF)
item->util = xstrdup(symref);
else
@@ -582,31 +578,23 @@ static int migrate_file(struct remote *remote)
{
struct strbuf buf = STRBUF_INIT;
int i;
- char *path = NULL;
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'"),
- remote->url[i], buf.buf);
+ git_config_set_multivar(buf.buf, remote->url[i], "^$", 0);
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'"),
- remote->push_refspec[i], buf.buf);
+ git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0);
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'"),
- remote->fetch_refspec[i], buf.buf);
+ git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0);
if (remote->origin == REMOTE_REMOTES)
- path = git_path("remotes/%s", remote->name);
+ unlink_or_warn(git_path("remotes/%s", remote->name));
else if (remote->origin == REMOTE_BRANCHES)
- path = git_path("branches/%s", remote->name);
- if (path)
- unlink_or_warn(path);
+ unlink_or_warn(git_path("branches/%s", remote->name));
+
return 0;
}
@@ -630,14 +618,14 @@ static int mv(int argc, const char **argv)
rename.remote_branches = &remote_branches;
oldremote = remote_get(rename.old);
- if (!oldremote)
+ if (!remote_is_configured(oldremote, 1))
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))
+ if (remote_is_configured(newremote, 1))
die(_("remote %s already exists."), rename.new);
strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new);
@@ -653,8 +641,7 @@ static int mv(int argc, const char **argv)
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);
+ git_config_set_multivar(buf.buf, NULL, NULL, 1);
strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
char *ptr;
@@ -674,8 +661,7 @@ static int mv(int argc, const char **argv)
"\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);
+ git_config_set_multivar(buf.buf, buf2.buf, "^$", 0);
}
read_branches();
@@ -685,9 +671,7 @@ static int mv(int argc, const char **argv)
if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
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);
- }
+ git_config_set(buf.buf, rename.new);
}
}
@@ -702,12 +686,12 @@ static int mv(int argc, const char **argv)
for (i = 0; i < remote_branches.nr; i++) {
struct string_list_item *item = remote_branches.items + i;
int flag = 0;
- unsigned char sha1[20];
+ struct object_id oid;
- read_ref_full(item->string, sha1, 1, &flag);
+ read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag);
if (!(flag & REF_ISSYMREF))
continue;
- if (delete_ref(item->string, NULL, REF_NODEREF))
+ if (delete_ref(NULL, item->string, NULL, REF_NODEREF))
die(_("deleting '%s' failed"), item->string);
}
for (i = 0; i < remote_branches.nr; i++) {
@@ -747,20 +731,6 @@ static int mv(int argc, const char **argv)
return 0;
}
-static int remove_branches(struct string_list *branches)
-{
- int i, result = 0;
- 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);
- }
- return result;
-}
-
static int rm(int argc, const char **argv)
{
struct option options[] = {
@@ -783,16 +753,12 @@ static int rm(int argc, const char **argv)
usage_with_options(builtin_remote_rm_usage, options);
remote = remote_get(argv[1]);
- if (!remote)
+ if (!remote_is_configured(remote, 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;
@@ -803,10 +769,9 @@ static int rm(int argc, const char **argv)
strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.%s",
item->string, *k);
- if (git_config_set(buf.buf, NULL)) {
- strbuf_release(&buf);
- return -1;
- }
+ result = git_config_set_gently(buf.buf, NULL);
+ if (result && result != CONFIG_NOTHING_SET)
+ die(_("could not unset '%s'"), buf.buf);
}
}
}
@@ -821,8 +786,8 @@ static int rm(int argc, const char **argv)
strbuf_release(&buf);
if (!result)
- result = remove_branches(&branches);
- string_list_clear(&branches, 1);
+ result = delete_refs(&branches, REF_NODEREF);
+ string_list_clear(&branches, 0);
if (skipped.nr) {
fprintf_ln(stderr,
@@ -837,6 +802,12 @@ static int rm(int argc, const char **argv)
}
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;
}
@@ -857,7 +828,7 @@ static void free_remote_ref_states(struct ref_states *states)
}
static int append_ref_to_tracked_list(const char *refname,
- const unsigned char *sha1, int flags, void *cb_data)
+ const struct object_id *oid, int flags, void *cb_data)
{
struct ref_states *states = cb_data;
struct refspec refspec;
@@ -901,7 +872,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);
}
@@ -979,7 +950,7 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
struct show_info *show_info = cb_data;
struct branch_info *branch_info = item->util;
struct string_list *merge = &branch_info->merge;
- const char *also;
+ int width = show_info->width + 4;
int i;
if (branch_info->rebase && branch_info->merge.nr > 1) {
@@ -990,17 +961,18 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
printf(" %-*s ", show_info->width, item->string);
if (branch_info->rebase) {
- printf_ln(_("rebases onto remote %s"), merge->items[0].string);
+ printf_ln(branch_info->rebase == INTERACTIVE_REBASE
+ ? _("rebases interactively onto remote %s")
+ : _("rebases onto remote %s"), merge->items[0].string);
return 0;
} else if (show_info->any_rebase) {
printf_ln(_(" merges with remote %s"), merge->items[0].string);
- also = _(" and with remote");
+ width++;
} else {
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,
+ printf(_("%-*s and with remote %s\n"), width, "",
merge->items[i].string);
return 0;
@@ -1120,7 +1092,7 @@ static int show_all(void)
if (!result) {
int i;
- sort_string_list(&list);
+ string_list_sort(&list);
for (i = 0; i < list.nr; i++) {
struct string_list_item *item = list.items + i;
if (verbose)
@@ -1179,13 +1151,15 @@ static int show(int argc, const char **argv)
url_nr = states.remote->url_nr;
}
for (i = 0; i < url_nr; i++)
+ /* TRANSLATORS: the colon ':' should align with
+ the one in " Fetch URL: %s" translation */
printf_ln(_(" Push URL: %s"), url[i]);
if (!i)
- printf_ln(_(" Push URL: %s"), "(no URL)");
+ printf_ln(_(" Push URL: %s"), _("(no URL)"));
if (no_query)
- printf_ln(_(" HEAD branch: %s"), "(not queried)");
+ printf_ln(_(" HEAD branch: %s"), _("(not queried)"));
else if (!states.heads.nr)
- printf_ln(_(" HEAD branch: %s"), "(unknown)");
+ printf_ln(_(" HEAD branch: %s"), _("(unknown)"));
else if (states.heads.nr == 1)
printf_ln(_(" HEAD branch: %s"), states.heads.items[0].string);
else {
@@ -1225,8 +1199,7 @@ static int show(int argc, const char **argv)
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);
+ QSORT(info.list->items, info.list->nr, cmp_string_with_push);
if (info.list->nr)
printf_ln(Q_(" Local ref configured for 'git push'%s:",
" Local refs configured for 'git push'%s:",
@@ -1277,7 +1250,7 @@ static int set_head(int argc, const char **argv)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (delete_ref(buf.buf, NULL, REF_NODEREF))
+ if (delete_ref(NULL, buf.buf, NULL, REF_NODEREF))
result |= error(_("Could not delete %s"), buf.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
@@ -1301,8 +1274,10 @@ static int set_head(int argc, const char **argv)
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!")
: _(" %s has become dangling!");
@@ -1310,19 +1285,26 @@ static int prune_remote(const char *remote, int dry_run)
memset(&states, 0, sizeof(states));
get_remote_ref_states(remote, &states, GET_REF_STATES);
- if (states.stale.nr) {
- printf_ln(_("Pruning %s"), remote);
- printf_ln(_("URL: %s"),
- states.remote->url_nr
- ? states.remote->url[0]
- : _("(no URL)"));
+ if (!states.stale.nr) {
+ free_remote_ref_states(&states);
+ return 0;
}
- for (i = 0; i < states.stale.nr; i++) {
- const char *refname = states.stale.items[i].util;
+ 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)
- result |= delete_ref(refname, NULL, 0);
+ if (!dry_run)
+ result |= delete_refs(&refs_to_prune, 0);
+
+ for_each_string_list_item(item, &states.stale) {
+ const char *refname = item->util;
if (dry_run)
printf_ln(_(" * [would prune] %s"),
@@ -1330,9 +1312,11 @@ static int prune_remote(const char *remote, int dry_run)
else
printf_ln(_(" * [pruned] %s"),
abbrev_ref(refname, "refs/remotes/"));
- warn_dangling_symref(stdout, dangling_msg, refname);
}
+ warn_dangling_symrefs(stdout, dangling_msg, &refs_to_prune);
+
+ string_list_clear(&refs_to_prune, 0);
free_remote_ref_states(&states);
return result;
}
@@ -1408,24 +1392,20 @@ static int update(int argc, const char **argv)
static int remove_all_fetch_refspecs(const char *remote, const char *key)
{
- return git_config_set_multivar(key, NULL, NULL, 1);
+ return git_config_set_multivar_gently(key, NULL, NULL, 1);
}
-static int add_branches(struct remote *remote, const char **branches,
- const char *key)
+static void add_branches(struct remote *remote, const char **branches,
+ const char *key)
{
const char *remotename = remote->name;
int mirror = remote->mirror;
struct strbuf refspec = STRBUF_INIT;
for (; *branches; branches++)
- if (add_branch(key, *branches, remotename, mirror, &refspec)) {
- strbuf_release(&refspec);
- return 1;
- }
+ add_branch(key, *branches, remotename, mirror, &refspec);
strbuf_release(&refspec);
- return 0;
}
static int set_remote_branches(const char *remotename, const char **branches,
@@ -1436,18 +1416,15 @@ 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);
remote = remote_get(remotename);
+ if (!remote_is_configured(remote, 1))
+ die(_("No such remote '%s'"), remotename);
if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) {
strbuf_release(&key);
return 1;
}
- if (add_branches(remote, branches, key.buf)) {
- strbuf_release(&key);
- return 1;
- }
+ add_branches(remote, branches, key.buf);
strbuf_release(&key);
return 0;
@@ -1472,6 +1449,57 @@ static int set_branches(int argc, const char **argv)
return set_remote_branches(argv[0], argv + 1, add_mode);
}
+static int get_url(int argc, const char **argv)
+{
+ int i, push_mode = 0, all_mode = 0;
+ const char *remotename = NULL;
+ struct remote *remote;
+ const char **url;
+ int url_nr;
+ struct option options[] = {
+ OPT_BOOL('\0', "push", &push_mode,
+ N_("query push URLs rather than fetch URLs")),
+ OPT_BOOL('\0', "all", &all_mode,
+ N_("return all URLs")),
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, NULL, options, builtin_remote_geturl_usage, 0);
+
+ if (argc != 1)
+ usage_with_options(builtin_remote_geturl_usage, options);
+
+ remotename = argv[0];
+
+ remote = remote_get(remotename);
+ if (!remote_is_configured(remote, 1))
+ die(_("No such remote '%s'"), remotename);
+
+ url_nr = 0;
+ if (push_mode) {
+ url = remote->pushurl;
+ url_nr = remote->pushurl_nr;
+ }
+ /* else fetch mode */
+
+ /* Use the fetch URL when no push URLs were found or requested. */
+ if (!url_nr) {
+ url = remote->url;
+ url_nr = remote->url_nr;
+ }
+
+ if (!url_nr)
+ die(_("no URLs configured for remote '%s'"), remotename);
+
+ if (all_mode) {
+ for (i = 0; i < url_nr; i++)
+ printf_ln("%s", url[i]);
+ } else {
+ printf_ln("%s", *url);
+ }
+
+ return 0;
+}
+
static int set_url(int argc, const char **argv)
{
int i, push_mode = 0, add_mode = 0, delete_mode = 0;
@@ -1510,9 +1538,9 @@ static int set_url(int argc, const char **argv)
if (delete_mode)
oldurl = newurl;
- if (!remote_is_configured(remotename))
- die(_("No such remote '%s'"), remotename);
remote = remote_get(remotename);
+ if (!remote_is_configured(remote, 1))
+ die(_("No such remote '%s'"), remotename);
if (push_mode) {
strbuf_addf(&name_buf, "remote.%s.pushurl", remotename);
@@ -1528,10 +1556,11 @@ static int set_url(int argc, const char **argv)
if ((!oldurl && !delete_mode) || add_mode) {
if (add_mode)
git_config_set_multivar(name_buf.buf, newurl,
- "^$", 0);
+ "^$", 0);
else
git_config_set(name_buf.buf, newurl);
strbuf_release(&name_buf);
+
return 0;
}
@@ -1581,6 +1610,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
result = set_head(argc, argv);
else if (!strcmp(argv[0], "set-branches"))
result = set_branches(argc, argv);
+ else if (!strcmp(argv[0], "get-url"))
+ result = get_url(argc, argv);
else if (!strcmp(argv[0], "set-url"))
result = set_url(argc, argv);
else if (!strcmp(argv[0], "show"))
diff --git a/builtin/repack.c b/builtin/repack.c
index 49f5857627..677bc7c81a 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -9,19 +9,36 @@
#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]"),
+ N_("git repack [<options>]"),
NULL
};
+static const char incremental_bitmap_conflict_error[] = N_(
+"Incremental repacks are incompatible with bitmap indexes. Use\n"
+"--no-write-bitmap-index or disable the pack.writebitmaps configuration."
+);
+
+
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);
}
@@ -72,16 +89,15 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list)
DIR *dir;
struct dirent *e;
char *fname;
- size_t len;
if (!(dir = opendir(packdir)))
return;
while ((e = readdir(dir)) != NULL) {
- if (!ends_with(e->d_name, ".pack"))
+ size_t len;
+ if (!strip_suffix(e->d_name, ".pack", &len))
continue;
- len = strlen(e->d_name) - strlen(".pack");
fname = xmemdupz(e->d_name, len);
if (!file_exists(mkpath("%s/%s.keep", packdir, fname)))
@@ -123,9 +139,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
{".idx"},
{".bitmap", 1},
};
- struct child_process cmd;
+ struct child_process cmd = CHILD_PROCESS_INIT;
struct string_list_item *item;
- struct argv_array cmd_args = ARGV_ARRAY_INIT;
struct string_list names = STRING_LIST_INIT_DUP;
struct string_list rollback = STRING_LIST_INIT_NODUP;
struct string_list existing_packs = STRING_LIST_INIT_DUP;
@@ -137,6 +152,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
int pack_everything = 0;
int delete_redundant = 0;
const char *unpack_unreachable = NULL;
+ int keep_unreachable = 0;
const char *window = NULL, *window_memory = NULL;
const char *depth = NULL;
const char *max_pack_size = NULL;
@@ -144,7 +160,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
int no_update_server_info = 0;
int quiet = 0;
int local = 0;
- int write_bitmap = -1;
struct option builtin_repack_options[] = {
OPT_BIT('a', NULL, &pack_everything,
@@ -163,10 +178,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
OPT__QUIET(&quiet, N_("be quiet")),
OPT_BOOL('l', "local", &local,
N_("pass --local to git-pack-objects")),
- OPT_BOOL('b', "write-bitmap-index", &write_bitmap,
+ 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_BOOL('k', "keep-unreachable", &keep_unreachable,
+ N_("with -a, repack unreachable objects")),
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"),
@@ -175,6 +192,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
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()
};
@@ -183,61 +202,80 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, builtin_repack_options,
git_repack_usage, 0);
+ if (delete_redundant && repository_format_precious_objects)
+ die(_("cannot delete packs in a precious-objects repo"));
+
+ if (keep_unreachable &&
+ (unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE)))
+ die(_("--keep-unreachable and -A are incompatible"));
+
+ if (pack_kept_objects < 0)
+ pack_kept_objects = write_bitmaps;
+
+ if (write_bitmaps && !(pack_everything & ALL_INTO_ONE))
+ die(_(incremental_bitmap_conflict_error));
+
packdir = mkpathdup("%s/pack", get_object_directory());
packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid());
sigchain_push_common(remove_pack_on_signal);
- argv_array_push(&cmd_args, "pack-objects");
- argv_array_push(&cmd_args, "--keep-true-parents");
- argv_array_push(&cmd_args, "--honor-pack-keep");
- argv_array_push(&cmd_args, "--non-empty");
- argv_array_push(&cmd_args, "--all");
- argv_array_push(&cmd_args, "--reflog");
+ 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);
+ argv_array_pushf(&cmd.args, "--window=%s", window);
if (window_memory)
- argv_array_pushf(&cmd_args, "--window-memory=%s", window_memory);
+ argv_array_pushf(&cmd.args, "--window-memory=%s", window_memory);
if (depth)
- argv_array_pushf(&cmd_args, "--depth=%s", 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);
+ argv_array_pushf(&cmd.args, "--max-pack-size=%s", max_pack_size);
if (no_reuse_delta)
- argv_array_pushf(&cmd_args, "--no-reuse-delta");
+ argv_array_pushf(&cmd.args, "--no-reuse-delta");
if (no_reuse_object)
- argv_array_pushf(&cmd_args, "--no-reuse-object");
- if (write_bitmap >= 0)
- argv_array_pushf(&cmd_args, "--%swrite-bitmap-index",
- write_bitmap ? "" : "no-");
+ 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,
+ if (unpack_unreachable) {
+ argv_array_pushf(&cmd.args,
"--unpack-unreachable=%s",
unpack_unreachable);
- else if (pack_everything & LOOSEN_UNREACHABLE)
- argv_array_push(&cmd_args,
+ argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1");
+ } else if (pack_everything & LOOSEN_UNREACHABLE) {
+ argv_array_push(&cmd.args,
"--unpack-unreachable");
+ } else if (keep_unreachable) {
+ argv_array_push(&cmd.args, "--keep-unreachable");
+ argv_array_push(&cmd.args, "--pack-loose-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");
+ argv_array_push(&cmd.args, "--unpacked");
+ argv_array_push(&cmd.args, "--incremental");
}
if (local)
- argv_array_push(&cmd_args, "--local");
+ argv_array_push(&cmd.args, "--local");
if (quiet)
- argv_array_push(&cmd_args, "--quiet");
+ argv_array_push(&cmd.args, "--quiet");
if (delta_base_offset)
- argv_array_push(&cmd_args, "--delta-base-offset");
+ argv_array_push(&cmd.args, "--delta-base-offset");
- argv_array_push(&cmd_args, packtmp);
+ argv_array_push(&cmd.args, packtmp);
- memset(&cmd, 0, sizeof(cmd));
- cmd.argv = cmd_args.argv;
cmd.git_cmd = 1;
cmd.out = -1;
cmd.no_stdin = 1;
@@ -247,7 +285,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
return ret;
out = xfdopen(cmd.out, "r");
- while (strbuf_getline(&line, out, '\n') != EOF) {
+ while (strbuf_getline_lf(&line, out) != EOF) {
if (line.len != 40)
die("repack: Expecting 40 character sha1 lines only from pack-objects.");
string_list_append(&names, line.buf);
@@ -256,7 +294,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
ret = finish_command(&cmd);
if (ret)
return ret;
- argv_array_clear(&cmd_args);
if (!names.nr && !quiet)
printf("Nothing new to pack.\n");
@@ -278,7 +315,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
continue;
}
- fname_old = mkpath("%s/old-%s%s", packdir,
+ fname_old = mkpathdup("%s/old-%s%s", packdir,
item->string, exts[ext].name);
if (file_exists(fname_old))
if (unlink(fname_old))
@@ -286,10 +323,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (!failed && rename(fname, fname_old)) {
free(fname);
+ free(fname_old);
failed = 1;
break;
} else {
string_list_append(&rollback, fname);
+ free(fname_old);
}
}
if (failed)
@@ -300,10 +339,11 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
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);
+ fname_old = mkpathdup("%s/old-%s", packdir, item->string);
if (rename(fname_old, fname))
string_list_append(&rollback_failure, fname);
free(fname);
+ free(fname_old);
}
if (rollback_failure.nr) {
@@ -352,19 +392,21 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
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);
+ fname = mkpathdup("%s/old-%s%s",
+ packdir,
+ item->string,
+ exts[ext].name);
if (remove_path(fname))
- warning(_("removing '%s' failed"), fname);
+ warning(_("failed to remove '%s'"), fname);
+ free(fname);
}
}
/* End of pack replacement. */
if (delete_redundant) {
- sort_string_list(&names);
+ int opts = 0;
+ string_list_sort(&names);
for_each_string_list_item(item, &existing_packs) {
char *sha1;
size_t len = strlen(item->string);
@@ -374,25 +416,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
if (!string_list_has_string(&names, sha1))
remove_redundant_pack(packdir, item->string);
}
- argv_array_push(&cmd_args, "prune-packed");
- if (quiet)
- argv_array_push(&cmd_args, "--quiet");
-
- memset(&cmd, 0, sizeof(cmd));
- cmd.argv = cmd_args.argv;
- cmd.git_cmd = 1;
- run_command(&cmd);
- argv_array_clear(&cmd_args);
+ if (!quiet && isatty(2))
+ opts |= PRUNE_PACKED_VERBOSE;
+ prune_packed_objects(opts);
}
- if (!no_update_server_info) {
- argv_array_push(&cmd_args, "update-server-info");
- memset(&cmd, 0, sizeof(cmd));
- cmd.argv = cmd_args.argv;
- cmd.git_cmd = 1;
- run_command(&cmd);
- argv_array_clear(&cmd_args);
- }
+ if (!no_update_server_info)
+ update_server_info(0);
remove_temporary_files();
string_list_clear(&names, 0);
string_list_clear(&rollback, 0);
diff --git a/builtin/replace.c b/builtin/replace.c
index 2336325ce3..226d0f9523 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -12,18 +12,22 @@
#include "builtin.h"
#include "refs.h"
#include "parse-options.h"
+#include "run-command.h"
+#include "tag.h"
static const char * const git_replace_usage[] = {
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
+ REPLACE_FORMAT_SHORT,
+ REPLACE_FORMAT_MEDIUM,
+ REPLACE_FORMAT_LONG
};
struct show_data {
@@ -31,28 +35,28 @@ struct show_data {
enum replace_format format;
};
-static int show_reference(const char *refname, const unsigned char *sha1,
+static int show_reference(const char *refname, const struct object_id *oid,
int flag, void *cb_data)
{
struct show_data *data = cb_data;
- if (!fnmatch(data->pattern, refname, 0)) {
+ 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));
+ printf("%s -> %s\n", refname, oid_to_hex(oid));
else { /* data->format == REPLACE_FORMAT_LONG */
- unsigned char object[20];
+ struct object_id object;
enum object_type obj_type, repl_type;
- if (get_sha1(refname, object))
+ if (get_sha1(refname, object.hash))
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);
+ obj_type = sha1_object_info(object.hash, NULL);
+ repl_type = sha1_object_info(oid->hash, NULL);
printf("%s (%s) -> %s (%s)\n", refname, typename(obj_type),
- sha1_to_hex(sha1), typename(repl_type));
+ oid_to_hex(oid), typename(repl_type));
}
}
@@ -78,7 +82,7 @@ static int list_replace_refs(const char *pattern, const char *format)
"valid formats are 'short', 'medium' and 'long'\n",
format);
- for_each_replace_ref(show_reference, (void *) &data);
+ for_each_replace_ref(show_reference, (void *)&data);
return 0;
}
@@ -100,9 +104,9 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
continue;
}
full_hex = sha1_to_hex(sha1);
- snprintf(ref, sizeof(ref), "refs/replace/%s", full_hex);
+ snprintf(ref, sizeof(ref), "%s%s", git_replace_ref_base, full_hex);
/* read_ref() may reuse the buffer */
- full_hex = ref + strlen("refs/replace/");
+ full_hex = ref + strlen(git_replace_ref_base);
if (read_ref(ref, sha1)) {
error("replace ref '%s' not found.", full_hex);
had_error = 1;
@@ -117,32 +121,43 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
static int delete_replace_ref(const char *name, const char *ref,
const unsigned char *sha1)
{
- if (delete_ref(ref, sha1, 0))
+ if (delete_ref(NULL, ref, sha1, 0))
return 1;
printf("Deleted replace ref '%s'\n", name);
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];
- enum object_type obj_type, repl_type;
- 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),
- "refs/replace/%s",
- sha1_to_hex(object)) > sizeof(ref) - 1)
+ if (snprintf(ref, ref_size,
+ "%s%s", git_replace_ref_base,
+ sha1_to_hex(object)) > ref_size - 1)
die("replace ref name too long: %.*s...", 50, ref);
if (check_refname_format(ref, 0))
die("'%s' is not a valid ref name.", ref);
+ 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)
@@ -152,74 +167,333 @@ static int replace_object(const char *object_ref, const char *replace_ref,
object_ref, typename(obj_type),
replace_ref, typename(repl_type));
- if (read_ref(ref, prev))
- hashclr(prev);
- else if (!force)
- die("replace ref '%s' already exists", ref);
+ check_ref_valid(object, prev, ref, sizeof(ref), force);
- lock = lock_any_ref_for_update(ref, prev, 0, NULL);
- if (!lock)
- die("%s: cannot lock the ref", ref);
- if (write_ref_sha1(lock, repl, NULL) < 0)
- die("%s: cannot update the ref", ref);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref, repl, prev,
+ 0, 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++) {
+ struct object_id oid;
+ if (get_sha1(mergetag_data->argv[i], oid.hash) < 0)
+ die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]);
+ if (!oidcmp(&tag->tagged->oid, &oid))
+ 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_BOOL('l', "list", &list, N_("list replace refs")),
- OPT_BOOL('d', "delete", &delete, N_("delete replace refs")),
+ 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()
};
- read_replace_refs = 0;
+ check_replace_refs = 0;
+ git_config(git_default_config, NULL);
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 (format && delete)
- usage_msg_opt("--format and -d cannot be used together",
+ 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);
- if (format)
- usage_msg_opt("--format cannot be used when not listing",
- 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);
- return list_replace_refs(argv[0], format);
+ 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);
+
+ 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 4e51addb3e..1bf72423bf 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -9,7 +9,7 @@
#include "pathspec.h"
static const char * const rerere_usage[] = {
- N_("git rerere [clear | forget path... | status | remaining | diff | gc]"),
+ N_("git rerere [clear | forget <path>... | status | remaining | diff | gc]"),
NULL,
};
@@ -29,9 +29,10 @@ static int diff_two(const char *file1, const char *label1,
xdemitconf_t xecfg;
xdemitcb_t ecb;
mmfile_t minus, plus;
+ int ret;
if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
- return 1;
+ return -1;
printf("--- a/%s\n+++ b/%s\n", label1, label2);
fflush(stdout);
@@ -40,17 +41,17 @@ static int diff_two(const char *file1, const char *label1,
memset(&xecfg, 0, sizeof(xecfg));
xecfg.ctxlen = 3;
ecb.outf = outf;
- xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+ ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
free(minus.ptr);
free(plus.ptr);
- return 0;
+ return ret;
}
int cmd_rerere(int argc, const char **argv, const char *prefix)
{
struct string_list merge_rr = STRING_LIST_INIT_DUP;
- int i, fd, autoupdate = -1, flags = 0;
+ int i, autoupdate = -1, flags = 0;
struct option options[] = {
OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
@@ -60,6 +61,8 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
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)
@@ -77,18 +80,16 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
return rerere_forget(&pathspec);
}
- fd = setup_rerere(&merge_rr, flags);
- if (fd < 0)
- return 0;
-
if (!strcmp(argv[0], "clear")) {
rerere_clear(&merge_rr);
} else if (!strcmp(argv[0], "gc"))
rerere_gc(&merge_rr);
- else if (!strcmp(argv[0], "status"))
+ else if (!strcmp(argv[0], "status")) {
+ if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0)
+ return 0;
for (i = 0; i < merge_rr.nr; i++)
printf("%s\n", merge_rr.items[i].string);
- else if (!strcmp(argv[0], "remaining")) {
+ } else if (!strcmp(argv[0], "remaining")) {
rerere_remaining(&merge_rr);
for (i = 0; i < merge_rr.nr; i++) {
if (merge_rr.items[i].util != RERERE_RESOLVED)
@@ -98,13 +99,16 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
* string_list_clear() */
merge_rr.items[i].util = NULL;
}
- } else if (!strcmp(argv[0], "diff"))
+ } else if (!strcmp(argv[0], "diff")) {
+ if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0)
+ return 0;
for (i = 0; i < merge_rr.nr; i++) {
const char *path = merge_rr.items[i].string;
- const char *name = (const char *)merge_rr.items[i].util;
- diff_two(rerere_path(name, "preimage"), path, path, path);
+ const struct rerere_id *id = merge_rr.items[i].util;
+ if (diff_two(rerere_path(id, "preimage"), path, path, path))
+ die("unable to generate diff for %s", rerere_path(id, NULL));
}
- else
+ } else
usage_with_options(rerere_usage, options);
string_list_clear(&merge_rr, 1);
diff --git a/builtin/reset.c b/builtin/reset.c
index 4fd1c6c51d..fc3b906c47 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"
@@ -23,7 +24,7 @@
static const char * const git_reset_usage[] = {
N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"),
- N_("git reset [-q] <tree-ish> [--] <paths>..."),
+ N_("git reset [-q] [<tree-ish>] [--] <paths>..."),
N_("git reset --patch [<tree-ish>] [--] [<paths>...]"),
NULL
};
@@ -35,10 +36,10 @@ static const char *reset_type_names[] = {
static inline int is_merge(void)
{
- return !access(git_path("MERGE_HEAD"), F_OK);
+ return !access(git_path_merge_head(), F_OK);
}
-static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
+static int reset_index(const struct object_id *oid, int reset_type, int quiet)
{
int nr = 1;
struct tree_desc desc[2];
@@ -68,23 +69,23 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
read_cache_unmerged();
if (reset_type == KEEP) {
- unsigned char head_sha1[20];
- if (get_sha1("HEAD", head_sha1))
+ struct object_id head_oid;
+ if (get_oid("HEAD", &head_oid))
return error(_("You do not have a valid HEAD."));
- if (!fill_tree_descriptor(desc, head_sha1))
+ if (!fill_tree_descriptor(desc, head_oid.hash))
return error(_("Failed to find tree of HEAD."));
nr++;
opts.fn = twoway_merge;
}
- if (!fill_tree_descriptor(desc + nr - 1, sha1))
- return error(_("Failed to find tree of %s."), sha1_to_hex(sha1));
+ if (!fill_tree_descriptor(desc + nr - 1, oid->hash))
+ return error(_("Failed to find tree of %s."), oid_to_hex(oid));
if (unpack_trees(nr, desc, &opts))
return -1;
if (reset_type == MIXED || reset_type == HARD) {
- tree = parse_tree_indirect(sha1);
- prime_cache_tree(&active_cache_tree, tree);
+ tree = parse_tree_indirect(oid->hash);
+ prime_cache_tree(&the_index, tree);
}
return 0;
@@ -93,23 +94,23 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
static void print_new_head_line(struct commit *commit)
{
const char *hex, *body;
- char *msg;
+ const char *msg;
- hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+ hex = find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV);
printf(_("HEAD is now at %s"), hex);
msg = logmsg_reencode(commit, NULL, get_log_output_encoding());
body = strstr(msg, "\n\n");
if (body) {
const char *eol;
size_t len;
- body += 2;
+ body = skip_blank_lines(body + 2);
eol = strchr(body, '\n');
len = eol ? eol - body : strlen(body);
printf(" %.*s\n", (int) len, body);
}
else
printf("\n");
- logmsg_free(msg, commit);
+ unuse_commit_buffer(commit, msg);
}
static void update_index_from_diff(struct diff_queue_struct *q,
@@ -120,7 +121,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,
for (i = 0; i < q->nr; i++) {
struct diff_filespec *one = q->queue[i]->one;
- int is_missing = !(one->mode && !is_null_sha1(one->sha1));
+ int is_missing = !(one->mode && !is_null_oid(&one->oid));
struct cache_entry *ce;
if (is_missing && !intent_to_add) {
@@ -128,7 +129,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,
continue;
}
- ce = make_cache_entry(one->mode, one->sha1, one->path,
+ ce = make_cache_entry(one->mode, one->oid.hash, one->path,
0, 0);
if (!ce)
die(_("make_cache_entry failed for path '%s'"),
@@ -142,7 +143,7 @@ static void update_index_from_diff(struct diff_queue_struct *q,
}
static int read_from_tree(const struct pathspec *pathspec,
- unsigned char *tree_sha1,
+ struct object_id *tree_oid,
int intent_to_add)
{
struct diff_options opt;
@@ -153,11 +154,11 @@ static int read_from_tree(const struct pathspec *pathspec,
opt.format_callback = update_index_from_diff;
opt.format_callback_data = &intent_to_add;
- if (do_diff_cache(tree_sha1, &opt))
+ if (do_diff_cache(tree_oid->hash, &opt))
return 1;
diffcore_std(&opt);
diff_flush(&opt);
- free_pathspec(&opt.pathspec);
+ clear_pathspec(&opt.pathspec);
return 0;
}
@@ -190,7 +191,7 @@ static void parse_args(struct pathspec *pathspec,
const char **rev_ret)
{
const char *rev = "HEAD";
- unsigned char unused[20];
+ struct object_id unused;
/*
* Possible arguments are:
*
@@ -215,8 +216,8 @@ static void parse_args(struct pathspec *pathspec,
* has to be unambiguous. If there is a single argument, it
* can not be a tree
*/
- else if ((!argv[1] && !get_sha1_committish(argv[0], unused)) ||
- (argv[1] && !get_sha1_treeish(argv[0], unused))) {
+ else if ((!argv[1] && !get_sha1_committish(argv[0], unused.hash)) ||
+ (argv[1] && !get_sha1_treeish(argv[0], unused.hash))) {
/*
* Ok, argv[0] looks like a commit/tree; it should not
* be a filename.
@@ -240,23 +241,25 @@ static void parse_args(struct pathspec *pathspec,
prefix, argv);
}
-static int reset_refs(const char *rev, const unsigned char *sha1)
+static int reset_refs(const char *rev, const struct object_id *oid)
{
int update_ref_status;
struct strbuf msg = STRBUF_INIT;
- unsigned char *orig = NULL, sha1_orig[20],
- *old_orig = NULL, sha1_old_orig[20];
+ struct object_id *orig = NULL, oid_orig,
+ *old_orig = NULL, oid_old_orig;
- if (!get_sha1("ORIG_HEAD", sha1_old_orig))
- old_orig = sha1_old_orig;
- if (!get_sha1("HEAD", sha1_orig)) {
- orig = sha1_orig;
+ if (!get_oid("ORIG_HEAD", &oid_old_orig))
+ old_orig = &oid_old_orig;
+ if (!get_oid("HEAD", &oid_orig)) {
+ orig = &oid_orig;
set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
- update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+ update_ref_oid(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
+ UPDATE_REFS_MSG_ON_ERR);
} else if (old_orig)
- delete_ref("ORIG_HEAD", old_orig, 0);
+ delete_ref(NULL, "ORIG_HEAD", old_orig->hash, 0);
set_reflog_message(&msg, "updating HEAD", rev);
- update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+ update_ref_status = update_ref_oid(msg.buf, "HEAD", oid, orig, 0,
+ UPDATE_REFS_MSG_ON_ERR);
strbuf_release(&msg);
return update_ref_status;
}
@@ -266,7 +269,7 @@ 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 object_id oid;
struct pathspec pathspec;
int intent_to_add = 0;
const struct option options[] = {
@@ -292,26 +295,26 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_DASHDASH);
parse_args(&pathspec, argv, prefix, patch_mode, &rev);
- unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
+ unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", oid.hash);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
- hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
+ hashcpy(oid.hash, EMPTY_TREE_SHA1_BIN);
} else if (!pathspec.nr) {
struct commit *commit;
- if (get_sha1_committish(rev, sha1))
+ if (get_sha1_committish(rev, oid.hash))
die(_("Failed to resolve '%s' as a valid revision."), rev);
- commit = lookup_commit_reference(sha1);
+ commit = lookup_commit_reference(oid.hash);
if (!commit)
die(_("Could not parse object '%s'."), rev);
- hashcpy(sha1, commit->object.sha1);
+ oidcpy(&oid, &commit->object.oid);
} else {
struct tree *tree;
- if (get_sha1_treeish(rev, sha1))
+ if (get_sha1_treeish(rev, oid.hash))
die(_("Failed to resolve '%s' as a valid tree."), rev);
- tree = parse_tree_indirect(sha1);
+ tree = parse_tree_indirect(oid.hash);
if (!tree)
die(_("Could not parse object '%s'."), rev);
- hashcpy(sha1, tree->object.sha1);
+ oidcpy(&oid, &tree->object.oid);
}
if (patch_mode) {
@@ -333,7 +336,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
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())
@@ -351,33 +354,33 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
if (reset_type != SOFT) {
struct lock_file *lock = xcalloc(1, sizeof(*lock));
- int newfd = hold_locked_index(lock, 1);
+ hold_locked_index(lock, LOCK_DIE_ON_ERROR);
if (reset_type == MIXED) {
int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
- if (read_from_tree(&pathspec, sha1, intent_to_add))
+ if (read_from_tree(&pathspec, &oid, intent_to_add))
return 1;
- refresh_index(&the_index, flags, NULL, NULL,
- _("Unstaged changes after reset:"));
+ 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);
+ int err = reset_index(&oid, reset_type, quiet);
if (reset_type == KEEP && !err)
- err = reset_index(sha1, MIXED, quiet);
+ err = reset_index(&oid, MIXED, quiet);
if (err)
die(_("Could not reset index file to revision '%s'."), rev);
}
- if (write_cache(newfd, active_cache, active_nr) ||
- commit_locked_index(lock))
+ if (write_locked_index(&the_index, lock, COMMIT_LOCK))
die(_("Could not write new index file."));
}
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);
+ update_ref_status = reset_refs(rev, &oid);
if (reset_type == HARD && !update_ref_status && !quiet)
- print_new_head_line(lookup_commit_reference(sha1));
+ print_new_head_line(lookup_commit_reference(oid.hash));
}
if (!pathspec.nr)
remove_branch_state();
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 9f92905379..0aa93d5891 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -9,6 +9,7 @@
#include "log-tree.h"
#include "graph.h"
#include "bisect.h"
+#include "progress.h"
static const char rev_list_usage[] =
"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -42,18 +43,24 @@ static const char rev_list_usage[] =
" --abbrev=<n> | --no-abbrev\n"
" --abbrev-commit\n"
" --left-right\n"
+" --count\n"
" special purpose:\n"
" --bisect\n"
" --bisect-vars\n"
" --bisect-all"
;
+static struct progress *progress;
+static unsigned progress_counter;
+
static void finish_commit(struct commit *commit, void *data);
static void show_commit(struct commit *commit, void *data)
{
struct rev_list_info *info = data;
struct rev_info *revs = info->revs;
+ display_progress(progress, ++progress_counter);
+
if (info->flags & REV_LIST_QUIET) {
finish_commit(commit, data);
return;
@@ -80,14 +87,14 @@ static void show_commit(struct commit *commit, void *data)
if (!revs->graph)
fputs(get_revision_mark(revs, commit), stdout);
if (revs->abbrev_commit && revs->abbrev)
- fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
+ fputs(find_unique_abbrev(commit->object.oid.hash, revs->abbrev),
stdout);
else
- fputs(sha1_to_hex(commit->object.sha1), stdout);
+ fputs(oid_to_hex(&commit->object.oid), stdout);
if (revs->print_parents) {
struct commit_list *parents = commit->parents;
while (parents) {
- printf(" %s", sha1_to_hex(parents->item->object.sha1));
+ printf(" %s", oid_to_hex(&parents->item->object.oid));
parents = parents->next;
}
}
@@ -96,7 +103,7 @@ static void show_commit(struct commit *commit, void *data)
children = lookup_decoration(&revs->children, &commit->object);
while (children) {
- printf(" %s", sha1_to_hex(children->item->object.sha1));
+ printf(" %s", oid_to_hex(&children->item->object.oid));
children = children->next;
}
}
@@ -106,7 +113,7 @@ 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;
@@ -115,48 +122,40 @@ static void show_commit(struct commit *commit, void *data)
ctx.fmt = revs->commit_format;
ctx.output_encoding = get_log_output_encoding();
pretty_print_commit(&ctx, commit, &buf);
- if (revs->graph) {
- if (buf.len) {
- if (revs->commit_format != CMIT_FMT_ONELINE)
- graph_show_oneline(revs->graph);
-
- graph_show_commit_msg(revs->graph, &buf);
-
- /*
- * Add a newline after the commit message.
- *
- * Usually, this newline produces a blank
- * padding line between entries, in which case
- * we need to add graph padding on this line.
- *
- * However, the commit message may not end in a
- * newline. In this case the newline simply
- * ends the last line of the commit message,
- * and we don't need any graph output. (This
- * always happens with CMIT_FMT_ONELINE, and it
- * happens with CMIT_FMT_USERFORMAT when the
- * format doesn't explicitly end in a newline.)
- */
- if (buf.len && buf.buf[buf.len - 1] == '\n')
- graph_show_padding(revs->graph);
- putchar('\n');
- } else {
- /*
- * If the message buffer is empty, just show
- * the rest of the graph output for this
- * commit.
- */
- if (graph_show_remainder(revs->graph))
- putchar('\n');
- if (revs->commit_format == CMIT_FMT_ONELINE)
- putchar('\n');
- }
+ if (buf.len) {
+ if (revs->commit_format != CMIT_FMT_ONELINE)
+ graph_show_oneline(revs->graph);
+
+ graph_show_commit_msg(revs->graph, stdout, &buf);
+
+ /*
+ * Add a newline after the commit message.
+ *
+ * Usually, this newline produces a blank
+ * padding line between entries, in which case
+ * we need to add graph padding on this line.
+ *
+ * However, the commit message may not end in a
+ * newline. In this case the newline simply
+ * ends the last line of the commit message,
+ * and we don't need any graph output. (This
+ * always happens with CMIT_FMT_ONELINE, and it
+ * happens with CMIT_FMT_USERFORMAT when the
+ * format doesn't explicitly end in a newline.)
+ */
+ if (buf.len && buf.buf[buf.len - 1] == '\n')
+ graph_show_padding(revs->graph);
+ putchar(info->hdr_termination);
} else {
- if (revs->commit_format != CMIT_FMT_USERFORMAT ||
- buf.len) {
- fwrite(buf.buf, 1, buf.len, stdout);
- putchar(info->hdr_termination);
- }
+ /*
+ * If the message buffer is empty, just show
+ * the rest of the graph output for this
+ * commit.
+ */
+ if (graph_show_remainder(revs->graph))
+ putchar('\n');
+ if (revs->commit_format == CMIT_FMT_ONELINE)
+ putchar('\n');
}
strbuf_release(&buf);
} else {
@@ -173,35 +172,31 @@ 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,
- void *cb_data)
+static void finish_object(struct object *obj, 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 (obj->type == OBJ_BLOB && !has_object_file(&obj->oid))
+ die("missing blob object '%s'", oid_to_hex(&obj->oid));
if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT)
- parse_object(obj->sha1);
+ parse_object(obj->oid.hash);
}
-static void show_object(struct object *obj,
- const struct name_path *path, const char *component,
- void *cb_data)
+static void show_object(struct object *obj, const char *name, void *cb_data)
{
struct rev_list_info *info = cb_data;
- finish_object(obj, path, component, cb_data);
+ finish_object(obj, name, cb_data);
+ display_progress(progress, ++progress_counter);
if (info->flags & REV_LIST_QUIET)
return;
- show_object_with_name(stdout, obj, path, component);
+ show_object_with_name(stdout, obj, name);
}
static void show_edge(struct commit *commit)
{
- printf("-%s\n", sha1_to_hex(commit->object.sha1));
+ printf("-%s\n", oid_to_hex(&commit->object.oid));
}
static void print_var_str(const char *var, const char *val)
@@ -217,7 +212,7 @@ 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->flags;
- char hex[41] = "";
+ char hex[GIT_SHA1_HEXSZ + 1] = "";
struct commit_list *tried;
struct rev_info *revs = info->revs;
@@ -242,7 +237,7 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
cnt = reaches;
if (revs->commits)
- strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
+ oid_to_hex_r(hex, &revs->commits->item->object.oid);
if (flags & BISECT_SHOW_ALL) {
traverse_commit_list(revs, show_commit, show_object, info);
@@ -280,6 +275,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
int bisect_show_vars = 0;
int bisect_find_all = 0;
int use_bitmap_index = 0;
+ const char *show_progress = NULL;
git_config(git_default_config, NULL);
init_revisions(&revs, prefix);
@@ -329,6 +325,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
test_bitmap_walk(&revs);
return 0;
}
+ if (skip_prefix(arg, "--progress=", &arg)) {
+ show_progress = arg;
+ continue;
+ }
usage(rev_list_usage);
}
@@ -350,21 +350,31 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
revs.diff)
usage(rev_list_usage);
+ if (revs.show_notes)
+ die(_("rev-list does not support display of notes"));
+
save_commit_buffer = (revs.verbose_header ||
revs.grep_filter.pattern_list ||
revs.grep_filter.header_list);
if (bisect_list)
revs.limited = 1;
- if (use_bitmap_index) {
+ if (show_progress)
+ progress = start_progress_delay(show_progress, 0, 0, 2);
+
+ if (use_bitmap_index && !revs.prune) {
if (revs.count && !revs.left_right && !revs.cherry_mark) {
uint32_t commit_count;
+ int max_count = revs.max_count;
if (!prepare_bitmap_walk(&revs)) {
count_bitmap_commit_list(&commit_count, NULL, NULL, NULL);
+ if (max_count >= 0 && max_count < commit_count)
+ commit_count = max_count;
printf("%d\n", commit_count);
return 0;
}
- } else if (revs.tag_objects && revs.tree_objects && revs.blob_objects) {
+ } else if (revs.max_count < 0 &&
+ revs.tag_objects && revs.tree_objects && revs.blob_objects) {
if (!prepare_bitmap_walk(&revs)) {
traverse_bitmap_commit_list(&show_object_fast);
return 0;
@@ -389,6 +399,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
traverse_commit_list(&revs, show_commit, show_object, &info);
+ stop_progress(&progress);
+
if (revs.count) {
if (revs.left_right && revs.cherry_mark)
printf("%d\t%d\t%d\n", revs.count_left, revs.count_right, revs.count_same);
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 45901df371..e08677e559 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -11,6 +11,7 @@
#include "parse-options.h"
#include "diff.h"
#include "revision.h"
+#include "split-index.h"
#define DO_REVS 1
#define DO_NOREV 2
@@ -150,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);
}
@@ -188,17 +190,17 @@ static int show_default(void)
return 0;
}
-static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data)
{
if (ref_excluded(ref_excludes, refname))
return 0;
- show_rev(NORMAL, sha1, refname);
+ show_rev(NORMAL, oid->hash, refname);
return 0;
}
-static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int anti_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data)
{
- show_rev(REVERSED, sha1, refname);
+ show_rev(REVERSED, oid->hash, refname);
return 0;
}
@@ -277,13 +279,10 @@ 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,
- exclude->item->object.sha1,NULL);
- free(exclude);
- exclude = n;
+ struct commit *commit = pop_commit(&exclude);
+ show_rev(REVERSED, commit->object.oid.hash, NULL);
}
}
*dotdot = '.';
@@ -299,14 +298,30 @@ static int try_parent_shorthands(const char *arg)
unsigned char sha1[20];
struct commit *commit;
struct commit_list *parents;
- int parents_only;
-
- if ((dotdot = strstr(arg, "^!")))
- parents_only = 0;
- else if ((dotdot = strstr(arg, "^@")))
- parents_only = 1;
-
- if (!dotdot || dotdot[2])
+ int parent_number;
+ int include_rev = 0;
+ int include_parents = 0;
+ int exclude_parent = 0;
+
+ if ((dotdot = strstr(arg, "^!"))) {
+ include_rev = 1;
+ if (dotdot[2])
+ return 0;
+ } else if ((dotdot = strstr(arg, "^@"))) {
+ include_parents = 1;
+ if (dotdot[2])
+ return 0;
+ } else if ((dotdot = strstr(arg, "^-"))) {
+ include_rev = 1;
+ exclude_parent = 1;
+
+ if (dotdot[2]) {
+ char *end;
+ exclude_parent = strtoul(dotdot + 2, &end, 10);
+ if (*end != '\0' || !exclude_parent)
+ return 0;
+ }
+ } else
return 0;
*dotdot = 0;
@@ -315,12 +330,29 @@ static int try_parent_shorthands(const char *arg)
return 0;
}
- if (!parents_only)
- show_rev(NORMAL, sha1, arg);
commit = lookup_commit_reference(sha1);
- for (parents = commit->parents; parents; parents = parents->next)
- show_rev(parents_only ? NORMAL : REVERSED,
- parents->item->object.sha1, arg);
+ if (exclude_parent &&
+ exclude_parent > commit_list_count(commit->parents)) {
+ *dotdot = '^';
+ return 0;
+ }
+
+ if (include_rev)
+ show_rev(NORMAL, sha1, arg);
+ for (parents = commit->parents, parent_number = 1;
+ parents;
+ parents = parents->next, parent_number++) {
+ char *name = NULL;
+
+ if (exclude_parent && parent_number != exclude_parent)
+ continue;
+
+ if (symbolic)
+ name = xstrfmt("%s^%d", arg, parent_number);
+ show_rev(include_parents ? NORMAL : REVERSED,
+ parents->item->object.oid.hash, name);
+ free(name);
+ }
*dotdot = '^';
return 1;
@@ -356,7 +388,7 @@ 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[] = {
- N_("git rev-parse --parseopt [options] -- [<args>...]"),
+ N_("git rev-parse --parseopt [<options>] -- [<args>...]"),
NULL
};
static struct option parseopt_opts[] = {
@@ -369,6 +401,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
N_("output in stuck long form")),
OPT_END(),
};
+ static const char * const flag_chars = "*=?!";
struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
const char **usage = NULL;
@@ -383,7 +416,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
/* get the usage up to the first line with a -- on it */
for (;;) {
- if (strbuf_getline(&sb, stdin, '\n') == EOF)
+ if (strbuf_getline(&sb, stdin) == EOF)
die("premature end of input");
ALLOC_GROW(usage, unb + 1, usz);
if (!strcmp("--", sb.buf)) {
@@ -395,9 +428,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> */
- while (strbuf_getline(&sb, stdin, '\n') != EOF) {
+ /* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */
+ while (strbuf_getline(&sb, stdin) != EOF) {
const char *s;
+ const char *help;
struct option *o;
if (!sb.len)
@@ -407,44 +441,56 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
memset(opts + onb, 0, sizeof(opts[onb]));
o = &opts[onb++];
- s = strchr(sb.buf, ' ');
- if (!s || *sb.buf == ' ') {
+ help = strchr(sb.buf, ' ');
+ if (!help || *sb.buf == ' ') {
o->type = OPTION_GROUP;
o->help = xstrdup(skipspaces(sb.buf));
continue;
}
o->type = OPTION_CALLBACK;
- o->help = xstrdup(skipspaces(s));
+ o->help = xstrdup(skipspaces(help));
o->value = &parsed;
o->flags = PARSE_OPT_NOARG;
o->callback = &parseopt_dump;
- while (s > sb.buf && strchr("*=?!", s[-1])) {
- switch (*--s) {
+
+ /* name(s) */
+ s = strpbrk(sb.buf, flag_chars);
+ if (s == NULL)
+ s = help;
+
+ if (s - sb.buf == 1) /* short option only */
+ o->short_name = *sb.buf;
+ else if (sb.buf[1] != ',') /* long option only */
+ o->long_name = xmemdupz(sb.buf, s - sb.buf);
+ else {
+ o->short_name = *sb.buf;
+ o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
+ }
+
+ /* flags */
+ while (s < help) {
+ switch (*s++) {
case '=':
o->flags &= ~PARSE_OPT_NOARG;
- break;
+ continue;
case '?':
o->flags &= ~PARSE_OPT_NOARG;
o->flags |= PARSE_OPT_OPTARG;
- break;
+ continue;
case '!':
o->flags |= PARSE_OPT_NONEG;
- break;
+ continue;
case '*':
o->flags |= PARSE_OPT_HIDDEN;
- break;
+ continue;
}
+ s--;
+ break;
}
- if (s - sb.buf == 1) /* short option only */
- o->short_name = *sb.buf;
- else if (sb.buf[1] != ',') /* long option only */
- o->long_name = xmemdupz(sb.buf, s - sb.buf);
- else {
- o->short_name = *sb.buf;
- o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);
- }
+ if (s < help)
+ o->argh = xmemdupz(s, help - s);
}
strbuf_release(&sb);
@@ -456,7 +502,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
(stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) |
PARSE_OPT_SHELL_EVAL);
- strbuf_addf(&parsed, " --");
+ strbuf_addstr(&parsed, " --");
sq_quote_argv(&parsed, argv, 0);
puts(parsed.buf);
return 0;
@@ -483,19 +529,23 @@ static void die_no_single_rev(int quiet)
}
static const char builtin_rev_parse_usage[] =
-N_("git rev-parse --parseopt [options] -- [<args>...]\n"
+N_("git rev-parse --parseopt [<options>] -- [<args>...]\n"
" or: git rev-parse --sq-quote [<arg>...]\n"
- " or: git rev-parse [options] [<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 did_repo_setup = 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;
+ struct strbuf buf = STRBUF_INIT;
if (argc > 1 && !strcmp("--parseopt", argv[1]))
return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -513,11 +563,49 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
}
}
- prefix = setup_git_directory();
- git_config(git_default_config, NULL);
+ /* No options; just report on whether we're in a git repo or not. */
+ if (argc == 1) {
+ setup_git_directory();
+ git_config(git_default_config, NULL);
+ return 0;
+ }
+
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+ if (!strcmp(arg, "--local-env-vars")) {
+ int i;
+ for (i = 0; local_repo_env[i]; i++)
+ printf("%s\n", local_repo_env[i]);
+ continue;
+ }
+ if (!strcmp(arg, "--resolve-git-dir")) {
+ const char *gitdir = argv[++i];
+ if (!gitdir)
+ die("--resolve-git-dir requires an argument");
+ gitdir = resolve_gitdir(gitdir);
+ if (!gitdir)
+ die("not a gitdir '%s'", argv[i]);
+ puts(gitdir);
+ continue;
+ }
+
+ /* The rest of the options require a git repository. */
+ if (!did_repo_setup) {
+ prefix = setup_git_directory();
+ git_config(git_default_config, NULL);
+ did_repo_setup = 1;
+ }
+
+ if (!strcmp(arg, "--git-path")) {
+ if (!argv[i + 1])
+ die("--git-path requires an argument");
+ strbuf_reset(&buf);
+ puts(relative_path(git_path("%s", argv[i + 1]),
+ prefix, &buf));
+ i++;
+ continue;
+ }
if (as_is) {
if (show_file(arg, output_prefix) && as_is < 2)
verify_filename(prefix, arg, 0);
@@ -583,6 +671,7 @@ 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") ||
@@ -590,8 +679,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
filter &= ~(DO_FLAGS|DO_NOREV);
verify = 1;
abbrev = DEFAULT_ABBREV;
- if (arg[7] == '=')
- abbrev = strtoul(arg + 8, NULL, 10);
+ if (!arg[7])
+ continue;
+ abbrev = strtoul(arg + 8, NULL, 10);
if (abbrev < MINIMUM_ABBREV)
abbrev = MINIMUM_ABBREV;
else if (40 <= abbrev)
@@ -683,12 +773,6 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
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")) {
const char *work_tree = get_git_work_tree();
if (work_tree)
@@ -721,32 +805,38 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
putchar('\n');
continue;
}
- if (!strcmp(arg, "--git-dir")) {
+ if (!strcmp(arg, "--git-dir") ||
+ !strcmp(arg, "--absolute-git-dir")) {
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
- static char cwd[PATH_MAX];
+ char *cwd;
int len;
- if (gitdir) {
- puts(gitdir);
- continue;
- }
- if (!prefix) {
- puts(".git");
- continue;
+ if (arg[2] == 'g') { /* --git-dir */
+ if (gitdir) {
+ puts(gitdir);
+ continue;
+ }
+ if (!prefix) {
+ puts(".git");
+ continue;
+ }
+ } else { /* --absolute-git-dir */
+ if (!gitdir && !prefix)
+ gitdir = ".git";
+ if (gitdir) {
+ puts(real_path(gitdir));
+ 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);
+ if (!strcmp(arg, "--git-common-dir")) {
+ strbuf_reset(&buf);
+ puts(relative_path(get_git_common_dir(),
+ prefix, &buf));
continue;
}
if (!strcmp(arg, "--is-inside-git-dir")) {
@@ -764,6 +854,17 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
: "false");
continue;
}
+ 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;
+ const char *path = git_path("sharedindex.%s", sha1_to_hex(sha1));
+ strbuf_reset(&buf);
+ puts(relative_path(path, prefix, &buf));
+ }
+ continue;
+ }
if (starts_with(arg, "--since=")) {
show_datestring("--max-age=", arg+8);
continue;
@@ -796,7 +897,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
@@ -812,6 +913,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
verify_filename(prefix, arg, 1);
}
+ strbuf_release(&buf);
if (verify) {
if (revs_count == 1) {
show_rev(type, sha1, name);
diff --git a/builtin/revert.c b/builtin/revert.c
index 065d88dd05..4ca5b51544 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -19,13 +19,13 @@
*/
static const char * const revert_usage[] = {
- N_("git revert [options] <commit-ish>..."),
+ N_("git revert [<options>] <commit-ish>..."),
N_("git revert <subcommand>"),
NULL
};
static const char * const cherry_pick_usage[] = {
- N_("git cherry-pick [options] <commit-ish>..."),
+ N_("git cherry-pick [<options>] <commit-ish>..."),
N_("git cherry-pick <subcommand>"),
NULL
};
@@ -71,12 +71,12 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...)
die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
}
-static void parse_args(int argc, const char **argv, struct replay_opts *opts)
+static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
{
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
const char *me = action_name(opts);
int cmd = 0;
- struct option options[] = {
+ struct option base_options[] = {
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'),
@@ -89,15 +89,11 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
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"),
+ { 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(),
+ OPT_END()
};
+ struct option *options = base_options;
if (opts->action == REPLAY_PICK) {
struct option cp_extra[] = {
@@ -108,8 +104,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
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"));
+ options = parse_options_concat(options, cp_extra);
}
argc = parse_options(argc, argv, NULL, options, usage_str,
@@ -120,25 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
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) {
+ if (cmd) {
char *this_operation;
- if (opts->subcommand == REPLAY_REMOVE_STATE)
+ if (cmd == 'q')
this_operation = "--quit";
- else if (opts->subcommand == REPLAY_CONTINUE)
+ else if (cmd == 'c')
this_operation = "--continue";
else {
- assert(opts->subcommand == REPLAY_ROLLBACK);
+ assert(cmd == 'a');
this_operation = "--abort";
}
@@ -161,7 +146,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
"--edit", opts->edit,
NULL);
- if (opts->subcommand != REPLAY_NONE) {
+ if (cmd) {
opts->revs = NULL;
} else {
struct setup_revision_opt s_r_opt;
@@ -179,20 +164,30 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
if (argc > 1)
usage_with_options(usage_str, options);
+
+ /* These option values will be free()d */
+ opts->gpg_sign = xstrdup_or_null(opts->gpg_sign);
+ opts->strategy = xstrdup_or_null(opts->strategy);
+
+ if (cmd == 'q')
+ return sequencer_remove_state(opts);
+ if (cmd == 'c')
+ return sequencer_continue(opts);
+ if (cmd == 'a')
+ return sequencer_rollback(opts);
+ return sequencer_pick_revisions(opts);
}
int cmd_revert(int argc, const char **argv, const char *prefix)
{
- struct replay_opts opts;
+ struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
- 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);
+ res = run_sequencer(argc, argv, &opts);
if (res < 0)
die(_("revert failed"));
return res;
@@ -200,14 +195,12 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
{
- struct replay_opts opts;
+ struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
- memset(&opts, 0, sizeof(opts));
opts.action = REPLAY_PICK;
git_config(git_default_config, NULL);
- parse_args(argc, argv, &opts);
- res = sequencer_pick_revisions(&opts);
+ res = run_sequencer(argc, argv, &opts);
if (res < 0)
die(_("cherry-pick failed"));
return res;
diff --git a/builtin/rm.c b/builtin/rm.c
index 960634dd0c..fb79dcab18 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -3,8 +3,8 @@
*
* 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"
@@ -14,7 +14,7 @@
#include "pathspec.h"
static const char * const builtin_rm_usage[] = {
- N_("git rm [options] [--] <file>..."),
+ N_("git rm [<options>] [--] <file>..."),
NULL
};
@@ -59,32 +59,13 @@ static void print_error_files(struct string_list *files_list,
}
}
-static void error_removing_concrete_submodules(struct string_list *files, int *errs)
-{
- print_error_files(files,
- Q_("the following submodule (or one of its nested "
- "submodules)\n"
- "uses a .git directory:",
- "the following submodules (or one of its nested "
- "submodules)\n"
- "use a .git directory:", files->nr),
- _("\n(use 'rm -rf' if you really want to remove "
- "it including all of its history)"),
- errs);
- string_list_clear(files, 0);
-}
-
-static int check_submodules_use_gitfiles(void)
+static void submodules_absorb_gitdir_if_needed(const char *prefix)
{
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) {
@@ -95,20 +76,17 @@ static int check_submodules_use_gitfiles(void)
ce = active_cache[pos];
if (!S_ISGITLINK(ce->ce_mode) ||
- (lstat(ce->name, &st) < 0) ||
+ !file_exists(ce->name) ||
is_empty_dir(name))
continue;
if (!submodule_uses_gitfile(name))
- string_list_append(&files, name);
+ absorb_git_dir_into_superproject(prefix, name,
+ ABSORB_GITDIR_RECURSE_SUBMODULES);
}
-
- error_removing_concrete_submodules(&files, &errs);
-
- return errs;
}
-static int check_local_mod(unsigned char *head, int index_only)
+static int check_local_mod(struct object_id *head, int index_only)
{
/*
* Items in list are already sorted in the cache order,
@@ -121,16 +99,15 @@ static int check_local_mod(unsigned char *head, int index_only)
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);
+ no_head = is_null_oid(head);
for (i = 0; i < list.nr; i++) {
struct stat st;
int pos;
const struct cache_entry *ce;
const char *name = list.entry[i].name;
- unsigned char sha1[20];
+ struct object_id oid;
unsigned mode;
int local_changes = 0;
int staged_changes = 0;
@@ -153,7 +130,7 @@ static int check_local_mod(unsigned char *head, int index_only)
if (lstat(ce->name, &st) < 0) {
if (errno != ENOENT && errno != ENOTDIR)
- warning("'%s': %s", ce->name, strerror(errno));
+ warning_errno(_("failed to stat '%s'"), ce->name);
/* It already vanished from the working tree */
continue;
}
@@ -188,7 +165,9 @@ static int check_local_mod(unsigned char *head, int index_only)
*/
if (ce_match_stat(ce, &st, 0) ||
(S_ISGITLINK(ce->ce_mode) &&
- !ok_to_remove_submodule(ce->name)))
+ bad_to_remove_submodule(ce->name,
+ SUBMODULE_REMOVAL_DIE_ON_ERROR |
+ SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)))
local_changes = 1;
/*
@@ -198,9 +177,9 @@ static int check_local_mod(unsigned char *head, int index_only)
* way as changed from the HEAD.
*/
if (no_head
- || get_tree_entry(head, name, sha1, &mode)
+ || get_tree_entry(head->hash, name, oid.hash, &mode)
|| ce->ce_mode != create_ce_mode(mode)
- || hashcmp(ce->sha1, sha1))
+ || oidcmp(&ce->oid, &oid))
staged_changes = 1;
/*
@@ -212,19 +191,14 @@ static int check_local_mod(unsigned char *head, int index_only)
* "intent to add" entry.
*/
if (local_changes && staged_changes) {
- if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
+ if (!index_only || !ce_intent_to_add(ce))
string_list_append(&files_staged, name);
}
else if (!index_only) {
if (staged_changes)
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);
- }
+ if (local_changes)
+ string_list_append(&files_local, name);
}
}
print_error_files(&files_staged,
@@ -246,8 +220,6 @@ static int check_local_mod(unsigned char *head, int index_only)
&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:",
@@ -278,7 +250,7 @@ static struct option builtin_rm_options[] = {
int cmd_rm(int argc, const char **argv, const char *prefix)
{
- int i, newfd;
+ int i;
struct pathspec pathspec;
char *seen;
@@ -293,7 +265,7 @@ 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, LOCK_DIE_ON_ERROR);
if (read_cache() < 0)
die(_("index file corrupt"));
@@ -315,7 +287,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
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"));
+ die (_("Please stage your changes to .gitmodules or stash them to proceed"));
}
if (pathspec.nr) {
@@ -341,6 +313,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
exit(0);
}
+ if (!index_only)
+ submodules_absorb_gitdir_if_needed(prefix);
+
/*
* If not forced, the file, the index and the HEAD (if exists)
* must match; but the file can already been removed, since
@@ -352,13 +327,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
* report no changes unless forced.
*/
if (!force) {
- unsigned char sha1[20];
- if (get_sha1("HEAD", sha1))
- hashclr(sha1);
- if (check_local_mod(sha1, index_only))
- exit(1);
- } else if (!index_only) {
- if (check_submodules_use_gitfiles())
+ struct object_id oid;
+ if (get_oid("HEAD", &oid))
+ oidclr(&oid);
+ if (check_local_mod(&oid, index_only))
exit(1);
}
@@ -388,32 +360,19 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
*/
if (!index_only) {
int removed = 0, gitmodules_modified = 0;
+ struct strbuf buf = STRBUF_INIT;
for (i = 0; i < list.nr; i++) {
const char *path = list.entry[i].name;
if (list.entry[i].is_submodule) {
- if (is_empty_dir(path)) {
- if (!rmdir(path)) {
- removed = 1;
- if (!remove_path_from_gitmodules(path))
- gitmodules_modified = 1;
- continue;
- }
- } else {
- 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. */
- }
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, path);
+ if (remove_dir_recursively(&buf, 0))
+ die(_("could not remove '%s'"), path);
+
+ removed = 1;
+ if (!remove_path_from_gitmodules(path))
+ gitmodules_modified = 1;
+ continue;
}
if (!remove_path(path)) {
removed = 1;
@@ -422,13 +381,13 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
if (!removed)
die_errno("git rm: '%s'", path);
}
+ strbuf_release(&buf);
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 f420b74665..1ff5a67538 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -11,10 +11,16 @@
#include "transport.h"
#include "version.h"
#include "sha1-array.h"
+#include "gpg-interface.h"
+#include "gettext.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"
-" --all and explicit <ref> specification are mutually exclusive.";
+static const char * const send_pack_usage[] = {
+ N_("git send-pack [--all | --mirror] [--dry-run] [--force] "
+ "[--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] "
+ "[<host>:]<directory> [<ref>...]\n"
+ " --all and explicit <ref> specification are mutually exclusive."),
+ NULL,
+};
static struct send_pack_args args;
@@ -91,6 +97,31 @@ static void print_helper_status(struct ref *ref)
strbuf_release(&buf);
}
+static int send_pack_config(const char *k, const char *v, void *cb)
+{
+ git_gpg_config(k, v, NULL);
+
+ if (!strcmp(k, "push.gpgsign")) {
+ const char *value;
+ if (!git_config_get_value("push.gpgsign", &value)) {
+ switch (git_config_maybe_bool("push.gpgsign", value)) {
+ case 0:
+ args.push_cert = SEND_PACK_PUSH_CERT_NEVER;
+ break;
+ case 1:
+ args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
+ break;
+ default:
+ if (value && !strcasecmp(value, "if-asked"))
+ args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED;
+ else
+ return error("Invalid value for '%s'", k);
+ }
+ }
+ }
+ return 0;
+}
+
int cmd_send_pack(int argc, const char **argv, const char *prefix)
{
int i, nr_refspecs = 0;
@@ -106,108 +137,97 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
int ret;
int helper_status = 0;
int send_all = 0;
+ int verbose = 0;
const char *receivepack = "git-receive-pack";
+ unsigned dry_run = 0;
+ unsigned send_mirror = 0;
+ unsigned force_update = 0;
+ unsigned quiet = 0;
+ int push_cert = 0;
+ unsigned use_thin_pack = 0;
+ unsigned atomic = 0;
+ unsigned stateless_rpc = 0;
int flags;
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;
+ struct option options[] = {
+ OPT__VERBOSITY(&verbose),
+ OPT_STRING(0, "receive-pack", &receivepack, "receive-pack", N_("receive pack program")),
+ OPT_STRING(0, "exec", &receivepack, "receive-pack", N_("receive pack program")),
+ OPT_STRING(0, "remote", &remote_name, "remote", N_("remote name")),
+ OPT_BOOL(0, "all", &send_all, N_("push all refs")),
+ OPT_BOOL('n' , "dry-run", &dry_run, N_("dry run")),
+ OPT_BOOL(0, "mirror", &send_mirror, N_("mirror all refs")),
+ OPT_BOOL('f', "force", &force_update, N_("force updates")),
+ { OPTION_CALLBACK,
+ 0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
+ PARSE_OPT_OPTARG, option_parse_push_signed },
+ OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
+ OPT_BOOL(0, "thin", &use_thin_pack, N_("use thin pack")),
+ OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")),
+ OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("use stateless RPC protocol")),
+ OPT_BOOL(0, "stdin", &from_stdin, N_("read refs from stdin")),
+ OPT_BOOL(0, "helper-status", &helper_status, N_("print status from remote helper")),
+ { OPTION_CALLBACK,
+ 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"),
+ N_("require old value of ref to be at this value"),
+ PARSE_OPT_OPTARG, parseopt_push_cas_option },
+ OPT_END()
+ };
+
+ git_config(send_pack_config, NULL);
+ argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0);
+ if (argc > 0) {
+ dest = argv[0];
+ refspecs = (const char **)(argv + 1);
+ nr_refspecs = argc - 1;
+ }
- if (*arg == '-') {
- if (starts_with(arg, "--receive-pack=")) {
- receivepack = arg + 15;
- continue;
- }
- if (starts_with(arg, "--exec=")) {
- receivepack = arg + 7;
- continue;
- }
- if (starts_with(arg, "--remote=")) {
- remote_name = arg + 9;
- continue;
- }
- if (!strcmp(arg, "--all")) {
- send_all = 1;
- continue;
- }
- if (!strcmp(arg, "--dry-run")) {
- args.dry_run = 1;
- continue;
- }
- if (!strcmp(arg, "--mirror")) {
- args.send_mirror = 1;
- continue;
- }
- if (!strcmp(arg, "--force")) {
- args.force_update = 1;
- continue;
- }
- if (!strcmp(arg, "--quiet")) {
- args.quiet = 1;
- continue;
- }
- if (!strcmp(arg, "--verbose")) {
- args.verbose = 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;
- }
- if (!strcmp(arg, "--stateless-rpc")) {
- args.stateless_rpc = 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) {
- dest = arg;
- continue;
+ if (!dest)
+ usage_with_options(send_pack_usage, options);
+
+ args.verbose = verbose;
+ args.dry_run = dry_run;
+ args.send_mirror = send_mirror;
+ args.force_update = force_update;
+ args.quiet = quiet;
+ args.push_cert = push_cert;
+ args.progress = progress;
+ args.use_thin_pack = use_thin_pack;
+ args.atomic = atomic;
+ args.stateless_rpc = stateless_rpc;
+
+ 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) != EOF)
+ argv_array_push(&all_refspecs, line.buf);
+ strbuf_release(&line);
}
- refspecs = (const char **) argv;
- nr_refspecs = argc - i;
- break;
+
+ refspecs = all_refspecs.argv;
+ nr_refspecs = all_refspecs.argc;
}
- if (!dest)
- usage(send_pack_usage);
+
/*
* --all and --mirror are incompatible; neither makes sense
* with any refspecs.
*/
- if ((refspecs && (send_all || args.send_mirror)) ||
+ if ((nr_refspecs > 0 && (send_all || args.send_mirror)) ||
(send_all && args.send_mirror))
- usage(send_pack_usage);
+ usage_with_options(send_pack_usage, options);
if (remote_name) {
remote = remote_get(remote_name);
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 4b7e53623f..f78bb4818d 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -10,11 +10,30 @@
#include "parse-options.h"
static char const * const shortlog_usage[] = {
- N_("git shortlog [<options>] [<revision range>] [[--] [<path>...]]"),
+ N_("git shortlog [<options>] [<revision-range>] [[--] [<path>...]]"),
NULL
};
-static int compare_by_number(const void *a1, const void *a2)
+/*
+ * The util field of our string_list_items will contain one of two things:
+ *
+ * - if --summary is not in use, it will point to a string list of the
+ * oneline subjects assigned to this author
+ *
+ * - if --summary is in use, we don't need that list; we only need to know
+ * its size. So we abuse the pointer slot to store our integer counter.
+ *
+ * This macro accesses the latter.
+ */
+#define UTIL_TO_INT(x) ((intptr_t)(x)->util)
+
+static int compare_by_counter(const void *a1, const void *a2)
+{
+ const struct string_list_item *i1 = a1, *i2 = a2;
+ return UTIL_TO_INT(i2) - UTIL_TO_INT(i1);
+}
+
+static int compare_by_list(const void *a1, const void *a2)
{
const struct string_list_item *i1 = a1, *i2 = a2;
const struct string_list *l1 = i1->util, *l2 = i2->util;
@@ -31,13 +50,9 @@ static void insert_one_record(struct shortlog *log,
const char *author,
const char *oneline)
{
- const char *dot3 = log->common_repo_prefix;
- char *buffer, *p;
struct string_list_item *item;
const char *mailbuf, *namebuf;
size_t namelen, maillen;
- const char *eol;
- struct strbuf subject = STRBUF_INIT;
struct strbuf namemailbuf = STRBUF_INIT;
struct ident_split ident;
@@ -56,98 +71,102 @@ static void insert_one_record(struct shortlog *log,
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));
-
- /* Skip any leading whitespace, including any blank lines. */
- while (*oneline && isspace(*oneline))
- oneline++;
- eol = strchr(oneline, '\n');
- if (!eol)
- eol = oneline + strlen(oneline);
- if (starts_with(oneline, "[PATCH")) {
- char *eob = strchr(oneline, ']');
- if (eob && (!eol || eob < eol))
- oneline = eob + 1;
- }
- while (*oneline && isspace(*oneline) && *oneline != '\n')
- oneline++;
- format_subject(&subject, oneline, " ");
- buffer = strbuf_detach(&subject, NULL);
-
- if (dot3) {
- int dot3len = strlen(dot3);
- if (dot3len > 5) {
- while ((p = strstr(buffer, dot3)) != NULL) {
- int taillen = strlen(p) - dot3len;
- memcpy(p, "/.../", 5);
- memmove(p + 5, p + dot3len, taillen + 1);
+
+ if (log->summary)
+ item->util = (void *)(UTIL_TO_INT(item) + 1);
+ else {
+ const char *dot3 = log->common_repo_prefix;
+ char *buffer, *p;
+ struct strbuf subject = STRBUF_INIT;
+ const char *eol;
+
+ /* Skip any leading whitespace, including any blank lines. */
+ while (*oneline && isspace(*oneline))
+ oneline++;
+ eol = strchr(oneline, '\n');
+ if (!eol)
+ eol = oneline + strlen(oneline);
+ if (starts_with(oneline, "[PATCH")) {
+ char *eob = strchr(oneline, ']');
+ if (eob && (!eol || eob < eol))
+ oneline = eob + 1;
+ }
+ while (*oneline && isspace(*oneline) && *oneline != '\n')
+ oneline++;
+ format_subject(&subject, oneline, " ");
+ buffer = strbuf_detach(&subject, NULL);
+
+ if (dot3) {
+ int dot3len = strlen(dot3);
+ if (dot3len > 5) {
+ while ((p = strstr(buffer, dot3)) != NULL) {
+ int taillen = strlen(p) - dot3len;
+ memcpy(p, "/.../", 5);
+ memmove(p + 5, p + dot3len, taillen + 1);
+ }
}
}
- }
- string_list_append(item->util, buffer);
+ if (item->util == NULL)
+ item->util = xcalloc(1, sizeof(struct string_list));
+ string_list_append(item->util, buffer);
+ }
}
static void read_from_stdin(struct shortlog *log)
{
- char author[1024], oneline[1024];
-
- while (fgets(author, sizeof(author), stdin) != NULL) {
- if (!(author[0] == 'A' || author[0] == 'a') ||
- !starts_with(author + 1, "uthor: "))
+ struct strbuf author = STRBUF_INIT;
+ struct strbuf oneline = STRBUF_INIT;
+ static const char *author_match[2] = { "Author: ", "author " };
+ static const char *committer_match[2] = { "Commit: ", "committer " };
+ const char **match;
+
+ match = log->committer ? committer_match : author_match;
+ while (strbuf_getline_lf(&author, stdin) != EOF) {
+ const char *v;
+ if (!skip_prefix(author.buf, match[0], &v) &&
+ !skip_prefix(author.buf, match[1], &v))
continue;
- while (fgets(oneline, sizeof(oneline), stdin) &&
- oneline[0] != '\n')
+ while (strbuf_getline_lf(&oneline, stdin) != EOF &&
+ oneline.len)
; /* discard headers */
- while (fgets(oneline, sizeof(oneline), stdin) &&
- oneline[0] == '\n')
+ while (strbuf_getline_lf(&oneline, stdin) != EOF &&
+ !oneline.len)
; /* discard blanks */
- insert_one_record(log, author + 8, oneline);
+ insert_one_record(log, v, oneline.buf);
}
+ strbuf_release(&author);
+ strbuf_release(&oneline);
}
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
- const char *author = NULL, *buffer;
- struct strbuf buf = STRBUF_INIT;
- struct strbuf ufbuf = STRBUF_INIT;
-
- pp_commit_easy(CMIT_FMT_RAW, commit, &buf);
- buffer = buf.buf;
- while (*buffer && *buffer != '\n') {
- const char *eol = strchr(buffer, '\n');
-
- if (eol == NULL)
- eol = buffer + strlen(buffer);
+ struct strbuf author = STRBUF_INIT;
+ struct strbuf oneline = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ const char *fmt;
+
+ ctx.fmt = CMIT_FMT_USERFORMAT;
+ ctx.abbrev = log->abbrev;
+ ctx.print_email_subject = 1;
+ ctx.after_subject = "";
+ ctx.date_mode.type = DATE_NORMAL;
+ ctx.output_encoding = get_log_output_encoding();
+
+ fmt = log->committer ? "%cn <%ce>" : "%an <%ae>";
+
+ format_commit_message(commit, fmt, &author, &ctx);
+ if (!log->summary) {
+ if (log->user_format)
+ pretty_print_commit(&ctx, commit, &oneline);
else
- eol++;
-
- if (starts_with(buffer, "author "))
- author = buffer + 7;
- buffer = eol;
- }
- 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;
- ctx.abbrev = log->abbrev;
- 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) {
- buffer++;
+ format_commit_message(commit, "%s", &oneline, &ctx);
}
- insert_one_record(log, author, !*buffer ? "<none>" : buffer);
- strbuf_release(&ufbuf);
- strbuf_release(&buf);
+
+ insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>");
+
+ strbuf_release(&author);
+ strbuf_release(&oneline);
}
static void get_from_rev(struct rev_info *rev, struct shortlog *log)
@@ -221,11 +240,13 @@ void shortlog_init(struct shortlog *log)
int cmd_shortlog(int argc, const char **argv, const char *prefix)
{
- static struct shortlog log;
- static struct rev_info rev;
+ struct shortlog log = { STRING_LIST_INIT_NODUP };
+ struct rev_info rev;
int nongit = !startup_info->have_repository;
- static const struct option options[] = {
+ const struct option options[] = {
+ OPT_BOOL('c', "committer", &log.committer,
+ N_("Group by committer rather than author")),
OPT_BOOL('n', "numbered", &log.sort_by_number,
N_("sort output according to the number of commits per author")),
OPT_BOOL('s', "summary", &log.summary,
@@ -264,6 +285,7 @@ parse_done:
log.user_format = rev.commit_format == CMIT_FMT_USERFORMAT;
log.abbrev = rev.abbrev;
+ log.file = rev.diffopt.file;
/* assume HEAD if from a tty */
if (!nongit && !rev.pending.nr && isatty(0))
@@ -277,6 +299,8 @@ parse_done:
get_from_rev(&rev, &log);
shortlog_output(&log);
+ if (log.file != stdout)
+ fclose(log.file);
return 0;
}
@@ -293,32 +317,34 @@ void shortlog_output(struct shortlog *log)
struct strbuf sb = STRBUF_INIT;
if (log->sort_by_number)
- qsort(log->list.items, log->list.nr, sizeof(struct string_list_item),
- compare_by_number);
+ QSORT(log->list.items, log->list.nr,
+ log->summary ? compare_by_counter : compare_by_list);
for (i = 0; i < log->list.nr; i++) {
- struct string_list *onelines = log->list.items[i].util;
-
+ const struct string_list_item *item = &log->list.items[i];
if (log->summary) {
- printf("%6d\t%s\n", onelines->nr, log->list.items[i].string);
+ fprintf(log->file, "%6d\t%s\n",
+ (int)UTIL_TO_INT(item), item->string);
} else {
- printf("%s (%d):\n", log->list.items[i].string, onelines->nr);
+ struct string_list *onelines = item->util;
+ fprintf(log->file, "%s (%d):\n",
+ item->string, onelines->nr);
for (j = onelines->nr - 1; j >= 0; j--) {
const char *msg = onelines->items[j].string;
if (log->wrap_lines) {
strbuf_reset(&sb);
add_wrapped_shortlog_msg(&sb, msg, log);
- fwrite(sb.buf, sb.len, 1, stdout);
+ fwrite(sb.buf, sb.len, 1, log->file);
}
else
- printf(" %s\n", msg);
+ fprintf(log->file, " %s\n", msg);
}
- putchar('\n');
+ putc('\n', log->file);
+ onelines->strdup_strings = 1;
+ string_list_clear(onelines, 0);
+ free(onelines);
}
- onelines->strdup_strings = 1;
- string_list_clear(onelines, 0);
- free(onelines);
log->list.items[i].util = NULL;
}
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index d9217ce1e1..19756595d5 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -3,19 +3,21 @@
#include "refs.h"
#include "builtin.h"
#include "color.h"
+#include "argv-array.h"
#include "parse-options.h"
static const char* show_branch_usage[] = {
- N_("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>)...]"),
- N_("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
};
static int showbranch_use_color = -1;
-static int default_num;
-static int default_alloc;
-static const char **default_arg;
+static struct argv_array default_args = ARGV_ARRAY_INIT;
#define UNINTERESTING 01
@@ -50,17 +52,6 @@ static struct commit *interesting(struct commit_list *list)
return NULL;
}
-static struct commit *pop_one_commit(struct commit_list **list_p)
-{
- struct commit *commit;
- struct commit_list *list;
- list = *list_p;
- commit = list->item;
- *list_p = list->next;
- free(list);
- return commit;
-}
-
struct commit_name {
const char *head_name; /* which head's ancestor? */
int generation; /* how many parents away from head_name */
@@ -210,7 +201,7 @@ static void join_revs(struct commit_list **list_p,
while (*list_p) {
struct commit_list *parents;
int still_interesting = !!interesting(*list_p);
- struct commit *commit = pop_one_commit(list_p);
+ struct commit *commit = pop_commit(list_p);
int flags = commit->object.flags & all_mask;
if (!still_interesting && extra <= 0)
@@ -284,8 +275,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 (starts_with(pretty_str, "[PATCH] "))
- pretty_str += 8;
+ skip_prefix(pretty_str, "[PATCH] ", &pretty_str);
if (!no_name) {
if (name && name->head_name) {
@@ -300,7 +290,7 @@ static void show_one_commit(struct commit *commit, int no_name)
}
else
printf("[%s] ",
- find_unique_abbrev(commit->object.sha1,
+ find_unique_abbrev(commit->object.oid.hash,
DEFAULT_ABBREV));
}
puts(pretty_str);
@@ -362,14 +352,13 @@ static int compare_ref_name(const void *a_, const void *b_)
static void sort_ref_range(int bottom, int top)
{
- qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
- compare_ref_name);
+ QSORT(ref_name + bottom, top - bottom, compare_ref_name);
}
-static int append_ref(const char *refname, const unsigned char *sha1,
+static int append_ref(const char *refname, const struct object_id *oid,
int allow_dups)
{
- struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ struct commit *commit = lookup_commit_reference_gently(oid->hash, 1);
int i;
if (!commit)
@@ -382,8 +371,9 @@ static int append_ref(const char *refname, const unsigned char *sha1,
return 0;
}
if (MAX_REVS <= ref_name_cnt) {
- warning("ignoring %s; cannot handle more than %d refs",
- refname, MAX_REVS);
+ warning(Q_("ignoring %s; cannot handle more than %d ref",
+ "ignoring %s; cannot handle more than %d refs",
+ MAX_REVS), refname, MAX_REVS);
return 0;
}
ref_name[ref_name_cnt++] = xstrdup(refname);
@@ -391,39 +381,42 @@ static int append_ref(const char *refname, const unsigned char *sha1,
return 0;
}
-static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_head_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- unsigned char tmp[20];
+ struct object_id tmp;
int ofs = 11;
if (!starts_with(refname, "refs/heads/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
- if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ if (get_sha1(refname + ofs, tmp.hash) || oidcmp(&tmp, oid))
ofs = 5;
- return append_ref(refname + ofs, sha1, 0);
+ return append_ref(refname + ofs, oid, 0);
}
-static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_remote_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
- unsigned char tmp[20];
+ struct object_id tmp;
int ofs = 13;
if (!starts_with(refname, "refs/remotes/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
- if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ if (get_sha1(refname + ofs, tmp.hash) || oidcmp(&tmp, oid))
ofs = 5;
- return append_ref(refname + ofs, sha1, 0);
+ return append_ref(refname + ofs, oid, 0);
}
-static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_tag_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
if (!starts_with(refname, "refs/tags/"))
return 0;
- return append_ref(refname + 5, sha1, 0);
+ return append_ref(refname + 5, oid, 0);
}
static const char *match_ref_pattern = NULL;
@@ -437,7 +430,8 @@ static int count_slash(const char *s)
return cnt;
}
-static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_matching_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
{
/* we want to allow pattern hold/<asterisk> to show all
* branches under refs/heads/hold/, and v0.99.9? to show
@@ -450,41 +444,39 @@ 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 (starts_with(refname, "refs/heads/"))
- return append_head_ref(refname, sha1, flag, cb_data);
+ return append_head_ref(refname, oid, flag, cb_data);
if (starts_with(refname, "refs/tags/"))
- return append_tag_ref(refname, sha1, flag, cb_data);
- return append_ref(refname, sha1, 0);
+ return append_tag_ref(refname, oid, flag, cb_data);
+ return append_ref(refname, oid, 0);
}
static void snarf_refs(int head, int remotes)
{
if (head) {
int orig_cnt = ref_name_cnt;
+
for_each_ref(append_head_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
if (remotes) {
int orig_cnt = ref_name_cnt;
+
for_each_ref(append_remote_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
}
-static int rev_is_head(char *head, int headlen, char *name,
+static int rev_is_head(const char *head, const char *name,
unsigned char *head_sha1, unsigned char *sha1)
{
- if ((!head[0]) ||
- (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
+ if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
return 0;
- if (starts_with(head, "refs/heads/"))
- head += 11;
- if (starts_with(name, "refs/heads/"))
- name += 11;
- else if (starts_with(name, "heads/"))
- name += 6;
+ skip_prefix(head, "refs/heads/", &head);
+ if (!skip_prefix(name, "refs/heads/", &name))
+ skip_prefix(name, "heads/", &name);
return !strcmp(head, name);
}
@@ -495,11 +487,11 @@ static int show_merge_base(struct commit_list *seen, int num_rev)
int exit_status = 1;
while (seen) {
- struct commit *commit = pop_one_commit(&seen);
+ struct commit *commit = pop_commit(&seen);
int flags = commit->object.flags & all_mask;
if (!(flags & UNINTERESTING) &&
((flags & all_revs) == all_revs)) {
- puts(sha1_to_hex(commit->object.sha1));
+ puts(oid_to_hex(&commit->object.oid));
exit_status = 0;
commit->object.flags |= UNINTERESTING;
}
@@ -519,7 +511,7 @@ static int show_independent(struct commit **rev,
unsigned int flag = rev_mask[i];
if (commit->object.flags == flag)
- puts(sha1_to_hex(commit->object.sha1));
+ puts(oid_to_hex(&commit->object.oid));
commit->object.flags |= UNINTERESTING;
}
return 0;
@@ -527,22 +519,22 @@ static int show_independent(struct commit **rev,
static void append_one_rev(const char *av)
{
- unsigned char revkey[20];
- if (!get_sha1(av, revkey)) {
- append_ref(av, revkey, 0);
+ struct object_id revkey;
+ if (!get_sha1(av, revkey.hash)) {
+ append_ref(av, &revkey, 0);
return;
}
if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
/* glob style match */
int saved_matches = ref_name_cnt;
+
match_ref_pattern = av;
match_ref_slash = count_slash(av);
for_each_ref(append_matching_ref, NULL);
if (saved_matches == ref_name_cnt &&
ref_name_cnt < MAX_REVS)
- error("no matching refs with %s", av);
- if (saved_matches + 1 < ref_name_cnt)
- sort_ref_range(saved_matches, ref_name_cnt);
+ error(_("no matching refs with %s"), av);
+ sort_ref_range(saved_matches, ref_name_cnt);
return;
}
die("bad sha1 reference %s", av);
@@ -557,16 +549,9 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)
* default_arg is now passed to parse_options(), so we need to
* mimic the real argv a bit better.
*/
- if (!default_num) {
- default_alloc = 20;
- default_arg = xcalloc(default_alloc, sizeof(*default_arg));
- 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);
- }
- default_arg[default_num++] = xstrdup(value);
- default_arg[default_num] = NULL;
+ if (!default_args.argc)
+ argv_array_push(&default_args, "show-branch");
+ argv_array_push(&default_args, value);
return 0;
}
@@ -630,10 +615,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int all_heads = 0, all_remotes = 0;
int all_mask, all_revs;
enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
- char head[128];
- const char *head_p;
- int head_len;
- unsigned char head_sha1[20];
+ char *head;
+ struct object_id head_oid;
int merge_base = 0;
int independent = 0;
int no_name = 0;
@@ -686,9 +669,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
git_config(git_show_branch_config, NULL);
/* If nothing is specified, try the default first */
- if (ac == 1 && default_num) {
- ac = default_num;
- av = default_arg;
+ if (ac == 1 && default_args.argc) {
+ ac = default_args.argc;
+ av = default_args.argv;
}
ac = parse_options(ac, av, prefix, builtin_show_branch_options,
@@ -710,36 +693,41 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
*
* Also --all and --remotes do not make sense either.
*/
- die("--reflog is incompatible with --all, --remotes, "
- "--independent or --merge-base");
+ die(_("--reflog is incompatible with --all, --remotes, "
+ "--independent or --merge-base"));
}
/* If nothing is specified, show all branches by default */
- if (ac + all_heads + all_remotes == 0)
+ if (ac <= topics && all_heads + all_remotes == 0)
all_heads = 1;
if (reflog) {
- unsigned char sha1[20];
- char nth_desc[256];
+ struct object_id oid;
char *ref;
int base = 0;
+ unsigned int flags = 0;
if (ac == 0) {
static const char *fake_av[2];
- fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL);
+ fake_av[0] = resolve_refdup("HEAD",
+ RESOLVE_REF_READING,
+ oid.hash, NULL);
fake_av[1] = NULL;
av = fake_av;
ac = 1;
+ if (!*av)
+ die(_("no branches given, and HEAD is not valid"));
}
if (ac != 1)
- die("--reflog option needs one branch name");
+ die(_("--reflog option needs one branch name"));
if (MAX_REVS < reflog)
- die("Only %d entries can be shown at one time.",
- MAX_REVS);
- if (!dwim_ref(*av, strlen(*av), sha1, &ref))
- die("No such ref %s", *av);
+ die(Q_("only %d entry can be shown at one time.",
+ "only %d entries can be shown at one time.",
+ MAX_REVS), MAX_REVS);
+ if (!dwim_ref(*av, strlen(*av), oid.hash, &ref))
+ die(_("no such ref %s"), *av);
/* Has the base been specified? */
if (reflog_base) {
@@ -749,18 +737,19 @@ 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, oid.hash, NULL,
NULL, NULL, &base);
}
}
for (i = 0; i < reflog; i++) {
- char *logmsg, *m;
+ char *logmsg;
+ char *nth_desc;
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, oid.hash, &logmsg,
&timestamp, &tz, NULL)) {
reflog = i;
break;
@@ -770,50 +759,45 @@ 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,
+ DATE_MODE(RELATIVE)),
+ msg);
free(logmsg);
- sprintf(nth_desc, "%s@{%d}", *av, base+i);
- append_ref(nth_desc, sha1, 1);
+
+ nth_desc = xstrfmt("%s@{%d}", *av, base+i);
+ append_ref(nth_desc, &oid, 1);
+ free(nth_desc);
}
+ free(ref);
}
- else if (all_heads + all_remotes)
- snarf_refs(all_heads, all_remotes);
else {
while (0 < ac) {
append_one_rev(*av);
ac--; av++;
}
+ if (all_heads + all_remotes)
+ snarf_refs(all_heads, all_remotes);
}
- head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL);
- if (head_p) {
- head_len = strlen(head_p);
- memcpy(head, head_p, head_len + 1);
- }
- else {
- head_len = 0;
- head[0] = 0;
- }
+ head = resolve_refdup("HEAD", RESOLVE_REF_READING,
+ head_oid.hash, NULL);
- if (with_current_branch && head_p) {
+ if (with_current_branch && head) {
int has_head = 0;
for (i = 0; !has_head && i < ref_name_cnt; i++) {
/* We are only interested in adding the branch
* HEAD points at.
*/
if (rev_is_head(head,
- head_len,
ref_name[i],
- head_sha1, NULL))
+ head_oid.hash, NULL))
has_head++;
}
if (!has_head) {
- int offset = starts_with(head, "refs/heads/") ? 11 : 0;
- append_one_rev(head + offset);
+ const char *name = head;
+ skip_prefix(name, "refs/heads/", &name);
+ append_one_rev(name);
}
}
@@ -823,17 +807,19 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
}
for (num_rev = 0; ref_name[num_rev]; num_rev++) {
- unsigned char revkey[20];
+ struct object_id revkey;
unsigned int flag = 1u << (num_rev + REV_SHIFT);
if (MAX_REVS <= num_rev)
- die("cannot handle more than %d revs.", MAX_REVS);
- if (get_sha1(ref_name[num_rev], revkey))
- die("'%s' is not a valid ref.", ref_name[num_rev]);
- commit = lookup_commit_reference(revkey);
+ die(Q_("cannot handle more than %d rev.",
+ "cannot handle more than %d revs.",
+ MAX_REVS), MAX_REVS);
+ if (get_sha1(ref_name[num_rev], revkey.hash))
+ die(_("'%s' is not a valid ref."), ref_name[num_rev]);
+ commit = lookup_commit_reference(revkey.hash);
if (!commit)
- die("cannot find commit %s (%s)",
- ref_name[num_rev], revkey);
+ die(_("cannot find commit %s (%s)"),
+ ref_name[num_rev], oid_to_hex(&revkey));
parse_commit(commit);
mark_seen(commit, &seen);
@@ -865,10 +851,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
for (i = 0; i < num_rev; i++) {
int j;
int is_head = rev_is_head(head,
- head_len,
ref_name[i],
- head_sha1,
- rev[i]->object.sha1);
+ head_oid.hash,
+ rev[i]->object.oid.hash);
if (extra < 0)
printf("%c [%s] ",
is_head ? '*' : ' ', ref_name[i]);
@@ -911,7 +896,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
while (seen) {
- struct commit *commit = pop_one_commit(&seen);
+ struct commit *commit = pop_commit(&seen);
int this_flag = commit->object.flags;
int is_merge_point = ((this_flag & all_revs) == all_revs);
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 5ba1f30838..013d241abc 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[] = {
- 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"),
+ 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>]"),
NULL
};
@@ -17,20 +17,36 @@ static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
static const char **pattern;
static const char *exclude_existing_arg;
-static void show_one(const char *refname, const unsigned char *sha1)
+static void show_one(const char *refname, const struct object_id *oid)
{
- const char *hex = find_unique_abbrev(sha1, abbrev);
+ const char *hex;
+ struct object_id peeled;
+
+ if (!has_sha1_file(oid->hash))
+ die("git show-ref: bad ref %s (%s)", refname,
+ oid_to_hex(oid));
+
+ if (quiet)
+ return;
+
+ hex = find_unique_abbrev(oid->hash, abbrev);
if (hash_only)
printf("%s\n", hex);
else
printf("%s %s\n", hex, refname);
+
+ if (!deref_tags)
+ return;
+
+ if (!peel_ref(refname, peeled.hash)) {
+ hex = find_unique_abbrev(peeled.hash, abbrev);
+ printf("%s %s^{}\n", hex, refname);
+ }
}
-static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+static int show_ref(const char *refname, const struct object_id *oid,
+ int flag, void *cbdata)
{
- const char *hex;
- unsigned char peeled[20];
-
if (show_head && !strcmp(refname, "HEAD"))
goto match;
@@ -53,9 +69,6 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
continue;
if (len == reflen)
goto match;
- /* "--verify" requires an exact match */
- if (verify)
- continue;
if (refname[reflen - len - 1] == '/')
goto match;
}
@@ -65,30 +78,13 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
match:
found_match++;
- /* This changes the semantics slightly that even under quiet we
- * detect and return error if the repository is corrupt and
- * ref points at a nonexistent object.
- */
- if (!has_sha1_file(sha1))
- die("git show-ref: bad ref %s (%s)", refname,
- sha1_to_hex(sha1));
-
- if (quiet)
- return 0;
-
- show_one(refname, sha1);
-
- if (!deref_tags)
- return 0;
+ show_one(refname, oid);
- if (!peel_ref(refname, peeled)) {
- hex = find_unique_abbrev(peeled, abbrev);
- printf("%s %s^{}\n", hex, refname);
- }
return 0;
}
-static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+static int add_existing(const char *refname, const struct object_id *oid,
+ int flag, void *cbdata)
{
struct string_list *list = (struct string_list *)cbdata;
string_list_insert(list, refname);
@@ -159,11 +155,6 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,
return 0;
}
-static int help_callback(const struct option *opt, const char *arg, int unset)
-{
- return -1;
-}
-
static const struct option show_ref_options[] = {
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)")),
@@ -184,18 +175,13 @@ static const struct option show_ref_options[] = {
{ OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg,
N_("pattern"), N_("show refs from stdin that aren't in local repository"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback },
- { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"),
- PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
OPT_END()
};
int cmd_show_ref(int argc, const char **argv, const char *prefix)
{
- if (argc == 2 && !strcmp(argv[1], "-h"))
- usage_with_options(show_ref_usage, show_ref_options);
-
argc = parse_options(argc, argv, prefix, show_ref_options,
- show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP);
+ show_ref_usage, 0);
if (exclude_arg)
return exclude_existing(exclude_existing_arg);
@@ -208,12 +194,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
if (!pattern)
die("--verify requires a reference");
while (*pattern) {
- unsigned char sha1[20];
+ struct object_id oid;
- if (starts_with(*pattern, "refs/") &&
- !read_ref(*pattern, sha1)) {
- if (!quiet)
- show_one(*pattern, sha1);
+ if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) &&
+ !read_ref(*pattern, oid.hash)) {
+ show_one(*pattern, &oid);
}
else if (!quiet)
die("'%s' - not a valid ref", *pattern);
diff --git a/builtin/stripspace.c b/builtin/stripspace.c
index 1259ed708b..1e62a008cb 100644
--- a/builtin/stripspace.c
+++ b/builtin/stripspace.c
@@ -1,71 +1,7 @@
#include "builtin.h"
#include "cache.h"
-
-/*
- * Returns the length of a line, without trailing spaces.
- *
- * If the line ends with newline, it will be removed too.
- */
-static size_t cleanup(char *line, size_t len)
-{
- while (len) {
- unsigned char c = line[len - 1];
- if (!isspace(c))
- break;
- len--;
- }
-
- return len;
-}
-
-/*
- * Remove empty lines from the beginning and end
- * and also trailing spaces from every line.
- *
- * Turn multiple consecutive empty lines between paragraphs
- * into just one empty line.
- *
- * If the input has only empty lines and spaces,
- * no output will be produced.
- *
- * If last line does not have a newline at the end, one is added.
- *
- * Enable skip_comments to skip every line starting with comment
- * character.
- */
-void stripspace(struct strbuf *sb, int skip_comments)
-{
- int empties = 0;
- size_t i, j, len, newlen;
- char *eol;
-
- /* We may have to add a newline. */
- strbuf_grow(sb, 1);
-
- for (i = j = 0; i < sb->len; i += len, j += newlen) {
- 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] == comment_line_char) {
- newlen = 0;
- continue;
- }
- newlen = cleanup(sb->buf + i, len);
-
- /* Not just an empty line? */
- if (newlen) {
- if (empties > 0 && j > 0)
- sb->buf[j++] = '\n';
- empties = 0;
- memmove(sb->buf + j, sb->buf + i, newlen);
- sb->buf[newlen + j++] = '\n';
- } else {
- empties++;
- }
- }
-
- strbuf_setlen(sb, j);
-}
+#include "parse-options.h"
+#include "strbuf.h"
static void comment_lines(struct strbuf *buf)
{
@@ -77,41 +13,47 @@ static void comment_lines(struct strbuf *buf)
free(msg);
}
-static const char *usage_msg = "\n"
-" git stripspace [-s | --strip-comments] < input\n"
-" git stripspace [-c | --comment-lines] < input";
+static const char * const stripspace_usage[] = {
+ N_("git stripspace [-s | --strip-comments]"),
+ N_("git stripspace [-c | --comment-lines]"),
+ NULL
+};
+
+enum stripspace_mode {
+ STRIP_DEFAULT = 0,
+ STRIP_COMMENTS,
+ COMMENT_LINES
+};
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 (strip_comments || mode == COMMENT_LINES)
+ enum stripspace_mode mode = STRIP_DEFAULT;
+
+ const struct option options[] = {
+ OPT_CMDMODE('s', "strip-comments", &mode,
+ N_("skip and remove all lines starting with comment character"),
+ STRIP_COMMENTS),
+ OPT_CMDMODE('c', "comment-lines", &mode,
+ N_("prepend comment character and space to each line"),
+ COMMENT_LINES),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, stripspace_usage, 0);
+ if (argc)
+ usage_with_options(stripspace_usage, options);
+
+ if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) {
+ setup_git_directory_gently(NULL);
git_config(git_default_config, NULL);
+ }
if (strbuf_read(&buf, 0, 1024) < 0)
die_errno("could not read the input");
- if (mode == STRIP_SPACE)
- stripspace(&buf, strip_comments);
+ if (mode == STRIP_DEFAULT || mode == STRIP_COMMENTS)
+ strbuf_stripspace(&buf, mode == STRIP_COMMENTS);
else
comment_lines(&buf);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
new file mode 100644
index 0000000000..15a5430c00
--- /dev/null
+++ b/builtin/submodule--helper.c
@@ -0,0 +1,1170 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "quote.h"
+#include "pathspec.h"
+#include "dir.h"
+#include "utf8.h"
+#include "submodule.h"
+#include "submodule-config.h"
+#include "string-list.h"
+#include "run-command.h"
+#include "remote.h"
+#include "refs.h"
+#include "connect.h"
+
+static char *get_default_remote(void)
+{
+ char *dest = NULL, *ret;
+ unsigned char sha1[20];
+ struct strbuf sb = STRBUF_INIT;
+ const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
+
+ if (!refname)
+ die(_("No such ref: %s"), "HEAD");
+
+ /* detached HEAD */
+ if (!strcmp(refname, "HEAD"))
+ return xstrdup("origin");
+
+ if (!skip_prefix(refname, "refs/heads/", &refname))
+ die(_("Expecting a full ref name, got %s"), refname);
+
+ strbuf_addf(&sb, "branch.%s.remote", refname);
+ if (git_config_get_string(sb.buf, &dest))
+ ret = xstrdup("origin");
+ else
+ ret = dest;
+
+ strbuf_release(&sb);
+ return ret;
+}
+
+static int starts_with_dot_slash(const char *str)
+{
+ return str[0] == '.' && is_dir_sep(str[1]);
+}
+
+static int starts_with_dot_dot_slash(const char *str)
+{
+ return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]);
+}
+
+/*
+ * Returns 1 if it was the last chop before ':'.
+ */
+static int chop_last_dir(char **remoteurl, int is_relative)
+{
+ char *rfind = find_last_dir_sep(*remoteurl);
+ if (rfind) {
+ *rfind = '\0';
+ return 0;
+ }
+
+ rfind = strrchr(*remoteurl, ':');
+ if (rfind) {
+ *rfind = '\0';
+ return 1;
+ }
+
+ if (is_relative || !strcmp(".", *remoteurl))
+ die(_("cannot strip one component off url '%s'"),
+ *remoteurl);
+
+ free(*remoteurl);
+ *remoteurl = xstrdup(".");
+ return 0;
+}
+
+/*
+ * The `url` argument is the URL that navigates to the submodule origin
+ * repo. When relative, this URL is relative to the superproject origin
+ * URL repo. The `up_path` argument, if specified, is the relative
+ * path that navigates from the submodule working tree to the superproject
+ * working tree. Returns the origin URL of the submodule.
+ *
+ * Return either an absolute URL or filesystem path (if the superproject
+ * origin URL is an absolute URL or filesystem path, respectively) or a
+ * relative file system path (if the superproject origin URL is a relative
+ * file system path).
+ *
+ * When the output is a relative file system path, the path is either
+ * relative to the submodule working tree, if up_path is specified, or to
+ * the superproject working tree otherwise.
+ *
+ * NEEDSWORK: This works incorrectly on the domain and protocol part.
+ * remote_url url outcome expectation
+ * http://a.com/b ../c http://a.com/c as is
+ * http://a.com/b/ ../c http://a.com/c same as previous line, but
+ * ignore trailing slash in url
+ * http://a.com/b ../../c http://c error out
+ * http://a.com/b ../../../c http:/c error out
+ * http://a.com/b ../../../../c http:c error out
+ * http://a.com/b ../../../../../c .:c error out
+ * NEEDSWORK: Given how chop_last_dir() works, this function is broken
+ * when a local part has a colon in its path component, too.
+ */
+static char *relative_url(const char *remote_url,
+ const char *url,
+ const char *up_path)
+{
+ int is_relative = 0;
+ int colonsep = 0;
+ char *out;
+ char *remoteurl = xstrdup(remote_url);
+ struct strbuf sb = STRBUF_INIT;
+ size_t len = strlen(remoteurl);
+
+ if (is_dir_sep(remoteurl[len-1]))
+ remoteurl[len-1] = '\0';
+
+ if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl))
+ is_relative = 0;
+ else {
+ is_relative = 1;
+ /*
+ * Prepend a './' to ensure all relative
+ * remoteurls start with './' or '../'
+ */
+ if (!starts_with_dot_slash(remoteurl) &&
+ !starts_with_dot_dot_slash(remoteurl)) {
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "./%s", remoteurl);
+ free(remoteurl);
+ remoteurl = strbuf_detach(&sb, NULL);
+ }
+ }
+ /*
+ * When the url starts with '../', remove that and the
+ * last directory in remoteurl.
+ */
+ while (url) {
+ if (starts_with_dot_dot_slash(url)) {
+ url += 3;
+ colonsep |= chop_last_dir(&remoteurl, is_relative);
+ } else if (starts_with_dot_slash(url))
+ url += 2;
+ else
+ break;
+ }
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url);
+ if (ends_with(url, "/"))
+ strbuf_setlen(&sb, sb.len - 1);
+ free(remoteurl);
+
+ if (starts_with_dot_slash(sb.buf))
+ out = xstrdup(sb.buf + 2);
+ else
+ out = xstrdup(sb.buf);
+ strbuf_reset(&sb);
+
+ if (!up_path || !is_relative)
+ return out;
+
+ strbuf_addf(&sb, "%s%s", up_path, out);
+ free(out);
+ return strbuf_detach(&sb, NULL);
+}
+
+static int resolve_relative_url(int argc, const char **argv, const char *prefix)
+{
+ char *remoteurl = NULL;
+ char *remote = get_default_remote();
+ const char *up_path = NULL;
+ char *res;
+ const char *url;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (argc != 2 && argc != 3)
+ die("resolve-relative-url only accepts one or two arguments");
+
+ url = argv[1];
+ strbuf_addf(&sb, "remote.%s.url", remote);
+ free(remote);
+
+ if (git_config_get_string(sb.buf, &remoteurl))
+ /* the repository is its own authoritative upstream */
+ remoteurl = xgetcwd();
+
+ if (argc == 3)
+ up_path = argv[2];
+
+ res = relative_url(remoteurl, url, up_path);
+ puts(res);
+ free(res);
+ free(remoteurl);
+ return 0;
+}
+
+static int resolve_relative_url_test(int argc, const char **argv, const char *prefix)
+{
+ char *remoteurl, *res;
+ const char *up_path, *url;
+
+ if (argc != 4)
+ die("resolve-relative-url-test only accepts three arguments: <up_path> <remoteurl> <url>");
+
+ up_path = argv[1];
+ remoteurl = xstrdup(argv[2]);
+ url = argv[3];
+
+ if (!strcmp(up_path, "(null)"))
+ up_path = NULL;
+
+ res = relative_url(remoteurl, url, up_path);
+ puts(res);
+ free(res);
+ free(remoteurl);
+ return 0;
+}
+
+struct module_list {
+ const struct cache_entry **entries;
+ int alloc, nr;
+};
+#define MODULE_LIST_INIT { NULL, 0, 0 }
+
+static int module_list_compute(int argc, const char **argv,
+ const char *prefix,
+ struct pathspec *pathspec,
+ struct module_list *list)
+{
+ int i, result = 0;
+ char *ps_matched = NULL;
+ parse_pathspec(pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+ prefix, argv);
+
+ if (pathspec->nr)
+ ps_matched = xcalloc(pathspec->nr, 1);
+
+ if (read_cache() < 0)
+ die(_("index file corrupt"));
+
+ for (i = 0; i < active_nr; i++) {
+ const struct cache_entry *ce = active_cache[i];
+
+ if (!match_pathspec(pathspec, ce->name, ce_namelen(ce),
+ 0, ps_matched, 1) ||
+ !S_ISGITLINK(ce->ce_mode))
+ continue;
+
+ ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
+ list->entries[list->nr++] = ce;
+ while (i + 1 < active_nr &&
+ !strcmp(ce->name, active_cache[i + 1]->name))
+ /*
+ * Skip entries with the same name in different stages
+ * to make sure an entry is returned only once.
+ */
+ i++;
+ }
+
+ if (ps_matched && report_path_error(ps_matched, pathspec, prefix))
+ result = -1;
+
+ free(ps_matched);
+
+ return result;
+}
+
+static int module_list(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct pathspec pathspec;
+ struct module_list list = MODULE_LIST_INIT;
+
+ struct option module_list_options[] = {
+ OPT_STRING(0, "prefix", &prefix,
+ N_("path"),
+ N_("alternative anchor for relative paths")),
+ OPT_END()
+ };
+
+ const char *const git_submodule_helper_usage[] = {
+ N_("git submodule--helper list [--prefix=<path>] [<path>...]"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, module_list_options,
+ git_submodule_helper_usage, 0);
+
+ if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
+ return 1;
+
+ for (i = 0; i < list.nr; i++) {
+ const struct cache_entry *ce = list.entries[i];
+
+ if (ce_stage(ce))
+ printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1));
+ else
+ printf("%06o %s %d\t", ce->ce_mode,
+ oid_to_hex(&ce->oid), ce_stage(ce));
+
+ utf8_fprintf(stdout, "%s\n", ce->name);
+ }
+ return 0;
+}
+
+static void init_submodule(const char *path, const char *prefix, int quiet)
+{
+ const struct submodule *sub;
+ struct strbuf sb = STRBUF_INIT;
+ char *upd = NULL, *url = NULL, *displaypath;
+
+ /* Only loads from .gitmodules, no overlay with .git/config */
+ gitmodules_config();
+
+ if (prefix && get_super_prefix())
+ die("BUG: cannot have prefix and superprefix");
+ else if (prefix)
+ displaypath = xstrdup(relative_path(path, prefix, &sb));
+ else if (get_super_prefix()) {
+ strbuf_addf(&sb, "%s%s", get_super_prefix(), path);
+ displaypath = strbuf_detach(&sb, NULL);
+ } else
+ displaypath = xstrdup(path);
+
+ sub = submodule_from_path(null_sha1, path);
+
+ if (!sub)
+ die(_("No url found for submodule path '%s' in .gitmodules"),
+ displaypath);
+
+ /*
+ * Copy url setting when it is not set yet.
+ * To look up the url in .git/config, we must not fall back to
+ * .gitmodules, so look it up directly.
+ */
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "submodule.%s.url", sub->name);
+ if (git_config_get_string(sb.buf, &url)) {
+ url = xstrdup(sub->url);
+
+ if (!url)
+ die(_("No url found for submodule path '%s' in .gitmodules"),
+ displaypath);
+
+ /* Possibly a url relative to parent */
+ if (starts_with_dot_dot_slash(url) ||
+ starts_with_dot_slash(url)) {
+ char *remoteurl, *relurl;
+ char *remote = get_default_remote();
+ struct strbuf remotesb = STRBUF_INIT;
+ strbuf_addf(&remotesb, "remote.%s.url", remote);
+ free(remote);
+
+ if (git_config_get_string(remotesb.buf, &remoteurl)) {
+ warning(_("could not lookup configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf);
+ remoteurl = xgetcwd();
+ }
+ relurl = relative_url(remoteurl, url, NULL);
+ strbuf_release(&remotesb);
+ free(remoteurl);
+ free(url);
+ url = relurl;
+ }
+
+ if (git_config_set_gently(sb.buf, url))
+ die(_("Failed to register url for submodule path '%s'"),
+ displaypath);
+ if (!quiet)
+ fprintf(stderr,
+ _("Submodule '%s' (%s) registered for path '%s'\n"),
+ sub->name, url, displaypath);
+ }
+
+ /* Copy "update" setting when it is not set yet */
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "submodule.%s.update", sub->name);
+ if (git_config_get_string(sb.buf, &upd) &&
+ sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
+ if (sub->update_strategy.type == SM_UPDATE_COMMAND) {
+ fprintf(stderr, _("warning: command update mode suggested for submodule '%s'\n"),
+ sub->name);
+ upd = xstrdup("none");
+ } else
+ upd = xstrdup(submodule_strategy_to_string(&sub->update_strategy));
+
+ if (git_config_set_gently(sb.buf, upd))
+ die(_("Failed to register update mode for submodule path '%s'"), displaypath);
+ }
+ strbuf_release(&sb);
+ free(displaypath);
+ free(url);
+ free(upd);
+}
+
+static int module_init(int argc, const char **argv, const char *prefix)
+{
+ struct pathspec pathspec;
+ struct module_list list = MODULE_LIST_INIT;
+ int quiet = 0;
+ int i;
+
+ struct option module_init_options[] = {
+ OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
+ OPT_END()
+ };
+
+ const char *const git_submodule_helper_usage[] = {
+ N_("git submodule--helper init [<path>]"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, module_init_options,
+ git_submodule_helper_usage, 0);
+
+ if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
+ return 1;
+
+ for (i = 0; i < list.nr; i++)
+ init_submodule(list.entries[i]->name, prefix, quiet);
+
+ return 0;
+}
+
+static int module_name(int argc, const char **argv, const char *prefix)
+{
+ const struct submodule *sub;
+
+ if (argc != 2)
+ usage(_("git submodule--helper name <path>"));
+
+ gitmodules_config();
+ sub = submodule_from_path(null_sha1, argv[1]);
+
+ if (!sub)
+ die(_("no submodule mapping found in .gitmodules for path '%s'"),
+ argv[1]);
+
+ printf("%s\n", sub->name);
+
+ return 0;
+}
+
+static int clone_submodule(const char *path, const char *gitdir, const char *url,
+ const char *depth, struct string_list *reference,
+ int quiet, int progress)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ argv_array_push(&cp.args, "clone");
+ argv_array_push(&cp.args, "--no-checkout");
+ if (quiet)
+ argv_array_push(&cp.args, "--quiet");
+ if (progress)
+ argv_array_push(&cp.args, "--progress");
+ if (depth && *depth)
+ argv_array_pushl(&cp.args, "--depth", depth, NULL);
+ if (reference->nr) {
+ struct string_list_item *item;
+ for_each_string_list_item(item, reference)
+ argv_array_pushl(&cp.args, "--reference",
+ item->string, NULL);
+ }
+ if (gitdir && *gitdir)
+ argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
+
+ argv_array_push(&cp.args, url);
+ argv_array_push(&cp.args, path);
+
+ cp.git_cmd = 1;
+ prepare_submodule_repo_env(&cp.env_array);
+ cp.no_stdin = 1;
+
+ return run_command(&cp);
+}
+
+struct submodule_alternate_setup {
+ const char *submodule_name;
+ enum SUBMODULE_ALTERNATE_ERROR_MODE {
+ SUBMODULE_ALTERNATE_ERROR_DIE,
+ SUBMODULE_ALTERNATE_ERROR_INFO,
+ SUBMODULE_ALTERNATE_ERROR_IGNORE
+ } error_mode;
+ struct string_list *reference;
+};
+#define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \
+ SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL }
+
+static int add_possible_reference_from_superproject(
+ struct alternate_object_database *alt, void *sas_cb)
+{
+ struct submodule_alternate_setup *sas = sas_cb;
+
+ /*
+ * If the alternate object store is another repository, try the
+ * standard layout with .git/(modules/<name>)+/objects
+ */
+ if (ends_with(alt->path, "/objects")) {
+ char *sm_alternate;
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
+ strbuf_add(&sb, alt->path, strlen(alt->path) - strlen("objects"));
+
+ /*
+ * We need to end the new path with '/' to mark it as a dir,
+ * otherwise a submodule name containing '/' will be broken
+ * as the last part of a missing submodule reference would
+ * be taken as a file name.
+ */
+ strbuf_addf(&sb, "modules/%s/", sas->submodule_name);
+
+ sm_alternate = compute_alternate_path(sb.buf, &err);
+ if (sm_alternate) {
+ string_list_append(sas->reference, xstrdup(sb.buf));
+ free(sm_alternate);
+ } else {
+ switch (sas->error_mode) {
+ case SUBMODULE_ALTERNATE_ERROR_DIE:
+ die(_("submodule '%s' cannot add alternate: %s"),
+ sas->submodule_name, err.buf);
+ case SUBMODULE_ALTERNATE_ERROR_INFO:
+ fprintf(stderr, _("submodule '%s' cannot add alternate: %s"),
+ sas->submodule_name, err.buf);
+ case SUBMODULE_ALTERNATE_ERROR_IGNORE:
+ ; /* nothing */
+ }
+ }
+ strbuf_release(&sb);
+ }
+
+ return 0;
+}
+
+static void prepare_possible_alternates(const char *sm_name,
+ struct string_list *reference)
+{
+ char *sm_alternate = NULL, *error_strategy = NULL;
+ struct submodule_alternate_setup sas = SUBMODULE_ALTERNATE_SETUP_INIT;
+
+ git_config_get_string("submodule.alternateLocation", &sm_alternate);
+ if (!sm_alternate)
+ return;
+
+ git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
+
+ if (!error_strategy)
+ error_strategy = xstrdup("die");
+
+ sas.submodule_name = sm_name;
+ sas.reference = reference;
+ if (!strcmp(error_strategy, "die"))
+ sas.error_mode = SUBMODULE_ALTERNATE_ERROR_DIE;
+ else if (!strcmp(error_strategy, "info"))
+ sas.error_mode = SUBMODULE_ALTERNATE_ERROR_INFO;
+ else if (!strcmp(error_strategy, "ignore"))
+ sas.error_mode = SUBMODULE_ALTERNATE_ERROR_IGNORE;
+ else
+ die(_("Value '%s' for submodule.alternateErrorStrategy is not recognized"), error_strategy);
+
+ if (!strcmp(sm_alternate, "superproject"))
+ foreach_alt_odb(add_possible_reference_from_superproject, &sas);
+ else if (!strcmp(sm_alternate, "no"))
+ ; /* do nothing */
+ else
+ die(_("Value '%s' for submodule.alternateLocation is not recognized"), sm_alternate);
+
+ free(sm_alternate);
+ free(error_strategy);
+}
+
+static int module_clone(int argc, const char **argv, const char *prefix)
+{
+ const char *name = NULL, *url = NULL, *depth = NULL;
+ int quiet = 0;
+ int progress = 0;
+ FILE *submodule_dot_git;
+ char *p, *path = NULL, *sm_gitdir;
+ struct strbuf rel_path = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ struct string_list reference = STRING_LIST_INIT_NODUP;
+ char *sm_alternate = NULL, *error_strategy = NULL;
+
+ struct option module_clone_options[] = {
+ OPT_STRING(0, "prefix", &prefix,
+ N_("path"),
+ N_("alternative anchor for relative paths")),
+ OPT_STRING(0, "path", &path,
+ N_("path"),
+ N_("where the new submodule will be cloned to")),
+ OPT_STRING(0, "name", &name,
+ N_("string"),
+ N_("name of the new submodule")),
+ OPT_STRING(0, "url", &url,
+ N_("string"),
+ N_("url where to clone the submodule from")),
+ OPT_STRING_LIST(0, "reference", &reference,
+ N_("repo"),
+ N_("reference repository")),
+ OPT_STRING(0, "depth", &depth,
+ N_("string"),
+ N_("depth for shallow clones")),
+ OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
+ OPT_BOOL(0, "progress", &progress,
+ N_("force cloning progress")),
+ OPT_END()
+ };
+
+ const char *const git_submodule_helper_usage[] = {
+ N_("git submodule--helper clone [--prefix=<path>] [--quiet] "
+ "[--reference <repository>] [--name <name>] [--depth <depth>] "
+ "--url <url> --path <path>"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, module_clone_options,
+ git_submodule_helper_usage, 0);
+
+ if (argc || !url || !path || !*path)
+ usage_with_options(git_submodule_helper_usage,
+ module_clone_options);
+
+ strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
+ sm_gitdir = absolute_pathdup(sb.buf);
+ strbuf_reset(&sb);
+
+ if (!is_absolute_path(path)) {
+ strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path);
+ path = strbuf_detach(&sb, NULL);
+ } else
+ path = xstrdup(path);
+
+ if (!file_exists(sm_gitdir)) {
+ if (safe_create_leading_directories_const(sm_gitdir) < 0)
+ die(_("could not create directory '%s'"), sm_gitdir);
+
+ prepare_possible_alternates(name, &reference);
+
+ if (clone_submodule(path, sm_gitdir, url, depth, &reference,
+ quiet, progress))
+ die(_("clone of '%s' into submodule path '%s' failed"),
+ url, path);
+ } else {
+ if (safe_create_leading_directories_const(path) < 0)
+ die(_("could not create directory '%s'"), path);
+ strbuf_addf(&sb, "%s/index", sm_gitdir);
+ unlink_or_warn(sb.buf);
+ strbuf_reset(&sb);
+ }
+
+ /* Write a .git file in the submodule to redirect to the superproject. */
+ strbuf_addf(&sb, "%s/.git", path);
+ if (safe_create_leading_directories_const(sb.buf) < 0)
+ die(_("could not create leading directories of '%s'"), sb.buf);
+ submodule_dot_git = fopen(sb.buf, "w");
+ if (!submodule_dot_git)
+ die_errno(_("cannot open file '%s'"), sb.buf);
+
+ fprintf_or_die(submodule_dot_git, "gitdir: %s\n",
+ relative_path(sm_gitdir, path, &rel_path));
+ if (fclose(submodule_dot_git))
+ die(_("could not close file %s"), sb.buf);
+ strbuf_reset(&sb);
+ strbuf_reset(&rel_path);
+
+ /* Redirect the worktree of the submodule in the superproject's config */
+ p = git_pathdup_submodule(path, "config");
+ if (!p)
+ die(_("could not get submodule directory for '%s'"), path);
+ git_config_set_in_file(p, "core.worktree",
+ relative_path(path, sm_gitdir, &rel_path));
+
+ /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */
+ git_config_get_string("submodule.alternateLocation", &sm_alternate);
+ if (sm_alternate)
+ git_config_set_in_file(p, "submodule.alternateLocation",
+ sm_alternate);
+ git_config_get_string("submodule.alternateErrorStrategy", &error_strategy);
+ if (error_strategy)
+ git_config_set_in_file(p, "submodule.alternateErrorStrategy",
+ error_strategy);
+
+ free(sm_alternate);
+ free(error_strategy);
+
+ strbuf_release(&sb);
+ strbuf_release(&rel_path);
+ free(sm_gitdir);
+ free(path);
+ free(p);
+ return 0;
+}
+
+struct submodule_update_clone {
+ /* index into 'list', the list of submodules to look into for cloning */
+ int current;
+ struct module_list list;
+ unsigned warn_if_uninitialized : 1;
+
+ /* update parameter passed via commandline */
+ struct submodule_update_strategy update;
+
+ /* configuration parameters which are passed on to the children */
+ int progress;
+ int quiet;
+ int recommend_shallow;
+ struct string_list references;
+ const char *depth;
+ const char *recursive_prefix;
+ const char *prefix;
+
+ /* Machine-readable status lines to be consumed by git-submodule.sh */
+ struct string_list projectlines;
+
+ /* If we want to stop as fast as possible and return an error */
+ unsigned quickstop : 1;
+
+ /* failed clones to be retried again */
+ const struct cache_entry **failed_clones;
+ int failed_clones_nr, failed_clones_alloc;
+};
+#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
+ SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
+ NULL, NULL, NULL, \
+ STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
+
+
+static void next_submodule_warn_missing(struct submodule_update_clone *suc,
+ struct strbuf *out, const char *displaypath)
+{
+ /*
+ * Only mention uninitialized submodules when their
+ * paths have been specified.
+ */
+ if (suc->warn_if_uninitialized) {
+ strbuf_addf(out,
+ _("Submodule path '%s' not initialized"),
+ displaypath);
+ strbuf_addch(out, '\n');
+ strbuf_addstr(out,
+ _("Maybe you want to use 'update --init'?"));
+ strbuf_addch(out, '\n');
+ }
+}
+
+/**
+ * Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to
+ * run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise.
+ */
+static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
+ struct child_process *child,
+ struct submodule_update_clone *suc,
+ struct strbuf *out)
+{
+ const struct submodule *sub = NULL;
+ struct strbuf displaypath_sb = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ const char *displaypath = NULL;
+ char *url = NULL;
+ int needs_cloning = 0;
+
+ if (ce_stage(ce)) {
+ if (suc->recursive_prefix)
+ strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name);
+ else
+ strbuf_addstr(&sb, ce->name);
+ strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf);
+ strbuf_addch(out, '\n');
+ goto cleanup;
+ }
+
+ sub = submodule_from_path(null_sha1, ce->name);
+
+ if (suc->recursive_prefix)
+ displaypath = relative_path(suc->recursive_prefix,
+ ce->name, &displaypath_sb);
+ else
+ displaypath = ce->name;
+
+ if (!sub) {
+ next_submodule_warn_missing(suc, out, displaypath);
+ goto cleanup;
+ }
+
+ if (suc->update.type == SM_UPDATE_NONE
+ || (suc->update.type == SM_UPDATE_UNSPECIFIED
+ && sub->update_strategy.type == SM_UPDATE_NONE)) {
+ strbuf_addf(out, _("Skipping submodule '%s'"), displaypath);
+ strbuf_addch(out, '\n');
+ goto cleanup;
+ }
+
+ /*
+ * Looking up the url in .git/config.
+ * We must not fall back to .gitmodules as we only want
+ * to process configured submodules.
+ */
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "submodule.%s.url", sub->name);
+ git_config_get_string(sb.buf, &url);
+ if (!url) {
+ next_submodule_warn_missing(suc, out, displaypath);
+ goto cleanup;
+ }
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/.git", ce->name);
+ needs_cloning = !file_exists(sb.buf);
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode,
+ oid_to_hex(&ce->oid), ce_stage(ce),
+ needs_cloning, ce->name);
+ string_list_append(&suc->projectlines, sb.buf);
+
+ if (!needs_cloning)
+ goto cleanup;
+
+ child->git_cmd = 1;
+ child->no_stdin = 1;
+ child->stdout_to_stderr = 1;
+ child->err = -1;
+ argv_array_push(&child->args, "submodule--helper");
+ argv_array_push(&child->args, "clone");
+ if (suc->progress)
+ argv_array_push(&child->args, "--progress");
+ if (suc->quiet)
+ argv_array_push(&child->args, "--quiet");
+ if (suc->prefix)
+ argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
+ if (suc->recommend_shallow && sub->recommend_shallow == 1)
+ argv_array_push(&child->args, "--depth=1");
+ argv_array_pushl(&child->args, "--path", sub->path, NULL);
+ argv_array_pushl(&child->args, "--name", sub->name, NULL);
+ argv_array_pushl(&child->args, "--url", url, NULL);
+ if (suc->references.nr) {
+ struct string_list_item *item;
+ for_each_string_list_item(item, &suc->references)
+ argv_array_pushl(&child->args, "--reference", item->string, NULL);
+ }
+ if (suc->depth)
+ argv_array_push(&child->args, suc->depth);
+
+cleanup:
+ free(url);
+ strbuf_reset(&displaypath_sb);
+ strbuf_reset(&sb);
+
+ return needs_cloning;
+}
+
+static int update_clone_get_next_task(struct child_process *child,
+ struct strbuf *err,
+ void *suc_cb,
+ void **idx_task_cb)
+{
+ struct submodule_update_clone *suc = suc_cb;
+ const struct cache_entry *ce;
+ int index;
+
+ for (; suc->current < suc->list.nr; suc->current++) {
+ ce = suc->list.entries[suc->current];
+ if (prepare_to_clone_next_submodule(ce, child, suc, err)) {
+ int *p = xmalloc(sizeof(*p));
+ *p = suc->current;
+ *idx_task_cb = p;
+ suc->current++;
+ return 1;
+ }
+ }
+
+ /*
+ * The loop above tried cloning each submodule once, now try the
+ * stragglers again, which we can imagine as an extension of the
+ * entry list.
+ */
+ index = suc->current - suc->list.nr;
+ if (index < suc->failed_clones_nr) {
+ int *p;
+ ce = suc->failed_clones[index];
+ if (!prepare_to_clone_next_submodule(ce, child, suc, err)) {
+ suc->current ++;
+ strbuf_addstr(err, "BUG: submodule considered for "
+ "cloning, doesn't need cloning "
+ "any more?\n");
+ return 0;
+ }
+ p = xmalloc(sizeof(*p));
+ *p = suc->current;
+ *idx_task_cb = p;
+ suc->current ++;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int update_clone_start_failure(struct strbuf *err,
+ void *suc_cb,
+ void *idx_task_cb)
+{
+ struct submodule_update_clone *suc = suc_cb;
+ suc->quickstop = 1;
+ return 1;
+}
+
+static int update_clone_task_finished(int result,
+ struct strbuf *err,
+ void *suc_cb,
+ void *idx_task_cb)
+{
+ const struct cache_entry *ce;
+ struct submodule_update_clone *suc = suc_cb;
+
+ int *idxP = *(int**)idx_task_cb;
+ int idx = *idxP;
+ free(idxP);
+
+ if (!result)
+ return 0;
+
+ if (idx < suc->list.nr) {
+ ce = suc->list.entries[idx];
+ strbuf_addf(err, _("Failed to clone '%s'. Retry scheduled"),
+ ce->name);
+ strbuf_addch(err, '\n');
+ ALLOC_GROW(suc->failed_clones,
+ suc->failed_clones_nr + 1,
+ suc->failed_clones_alloc);
+ suc->failed_clones[suc->failed_clones_nr++] = ce;
+ return 0;
+ } else {
+ idx -= suc->list.nr;
+ ce = suc->failed_clones[idx];
+ strbuf_addf(err, _("Failed to clone '%s' a second time, aborting"),
+ ce->name);
+ strbuf_addch(err, '\n');
+ suc->quickstop = 1;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int update_clone(int argc, const char **argv, const char *prefix)
+{
+ const char *update = NULL;
+ int max_jobs = -1;
+ struct string_list_item *item;
+ struct pathspec pathspec;
+ struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+
+ struct option module_update_clone_options[] = {
+ OPT_STRING(0, "prefix", &prefix,
+ N_("path"),
+ N_("path into the working tree")),
+ OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix,
+ N_("path"),
+ N_("path into the working tree, across nested "
+ "submodule boundaries")),
+ OPT_STRING(0, "update", &update,
+ N_("string"),
+ N_("rebase, merge, checkout or none")),
+ OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
+ N_("reference repository")),
+ OPT_STRING(0, "depth", &suc.depth, "<depth>",
+ N_("Create a shallow clone truncated to the "
+ "specified number of revisions")),
+ OPT_INTEGER('j', "jobs", &max_jobs,
+ N_("parallel jobs")),
+ OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow,
+ N_("whether the initial clone should follow the shallow recommendation")),
+ OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
+ OPT_BOOL(0, "progress", &suc.progress,
+ N_("force cloning progress")),
+ OPT_END()
+ };
+
+ const char *const git_submodule_helper_usage[] = {
+ N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"),
+ NULL
+ };
+ suc.prefix = prefix;
+
+ argc = parse_options(argc, argv, prefix, module_update_clone_options,
+ git_submodule_helper_usage, 0);
+
+ if (update)
+ if (parse_submodule_update_strategy(update, &suc.update) < 0)
+ die(_("bad value for update parameter"));
+
+ if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)
+ return 1;
+
+ if (pathspec.nr)
+ suc.warn_if_uninitialized = 1;
+
+ /* Overlay the parsed .gitmodules file with .git/config */
+ gitmodules_config();
+ git_config(submodule_config, NULL);
+
+ if (max_jobs < 0)
+ max_jobs = parallel_submodules();
+
+ run_processes_parallel(max_jobs,
+ update_clone_get_next_task,
+ update_clone_start_failure,
+ update_clone_task_finished,
+ &suc);
+
+ /*
+ * We saved the output and put it out all at once now.
+ * That means:
+ * - the listener does not have to interleave their (checkout)
+ * work with our fetching. The writes involved in a
+ * checkout involve more straightforward sequential I/O.
+ * - the listener can avoid doing any work if fetching failed.
+ */
+ if (suc.quickstop)
+ return 1;
+
+ for_each_string_list_item(item, &suc.projectlines)
+ utf8_fprintf(stdout, "%s", item->string);
+
+ return 0;
+}
+
+static int resolve_relative_path(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf sb = STRBUF_INIT;
+ if (argc != 3)
+ die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc);
+
+ printf("%s", relative_path(argv[1], argv[2], &sb));
+ strbuf_release(&sb);
+ return 0;
+}
+
+static const char *remote_submodule_branch(const char *path)
+{
+ const struct submodule *sub;
+ gitmodules_config();
+ git_config(submodule_config, NULL);
+
+ sub = submodule_from_path(null_sha1, path);
+ if (!sub)
+ return NULL;
+
+ if (!sub->branch)
+ return "master";
+
+ if (!strcmp(sub->branch, ".")) {
+ unsigned char sha1[20];
+ const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
+
+ if (!refname)
+ die(_("No such ref: %s"), "HEAD");
+
+ /* detached HEAD */
+ if (!strcmp(refname, "HEAD"))
+ die(_("Submodule (%s) branch configured to inherit "
+ "branch from superproject, but the superproject "
+ "is not on any branch"), sub->name);
+
+ if (!skip_prefix(refname, "refs/heads/", &refname))
+ die(_("Expecting a full ref name, got %s"), refname);
+ return refname;
+ }
+
+ return sub->branch;
+}
+
+static int resolve_remote_submodule_branch(int argc, const char **argv,
+ const char *prefix)
+{
+ const char *ret;
+ struct strbuf sb = STRBUF_INIT;
+ if (argc != 2)
+ die("submodule--helper remote-branch takes exactly one arguments, got %d", argc);
+
+ ret = remote_submodule_branch(argv[1]);
+ if (!ret)
+ die("submodule %s doesn't exist", argv[1]);
+
+ printf("%s", ret);
+ strbuf_release(&sb);
+ return 0;
+}
+
+static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct pathspec pathspec;
+ struct module_list list = MODULE_LIST_INIT;
+ unsigned flags = ABSORB_GITDIR_RECURSE_SUBMODULES;
+
+ struct option embed_gitdir_options[] = {
+ OPT_STRING(0, "prefix", &prefix,
+ N_("path"),
+ N_("path into the working tree")),
+ OPT_BIT(0, "--recursive", &flags, N_("recurse into submodules"),
+ ABSORB_GITDIR_RECURSE_SUBMODULES),
+ OPT_END()
+ };
+
+ const char *const git_submodule_helper_usage[] = {
+ N_("git submodule--helper embed-git-dir [<path>...]"),
+ NULL
+ };
+
+ argc = parse_options(argc, argv, prefix, embed_gitdir_options,
+ git_submodule_helper_usage, 0);
+
+ gitmodules_config();
+ git_config(submodule_config, NULL);
+
+ if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
+ return 1;
+
+ for (i = 0; i < list.nr; i++)
+ absorb_git_dir_into_superproject(prefix,
+ list.entries[i]->name, flags);
+
+ return 0;
+}
+
+#define SUPPORT_SUPER_PREFIX (1<<0)
+
+struct cmd_struct {
+ const char *cmd;
+ int (*fn)(int, const char **, const char *);
+ unsigned option;
+};
+
+static struct cmd_struct commands[] = {
+ {"list", module_list, 0},
+ {"name", module_name, 0},
+ {"clone", module_clone, 0},
+ {"update-clone", update_clone, 0},
+ {"relative-path", resolve_relative_path, 0},
+ {"resolve-relative-url", resolve_relative_url, 0},
+ {"resolve-relative-url-test", resolve_relative_url_test, 0},
+ {"init", module_init, SUPPORT_SUPER_PREFIX},
+ {"remote-branch", resolve_remote_submodule_branch, 0},
+ {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
+};
+
+int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ if (argc < 2)
+ die(_("submodule--helper subcommand must be "
+ "called with a subcommand"));
+
+ for (i = 0; i < ARRAY_SIZE(commands); i++) {
+ if (!strcmp(argv[1], commands[i].cmd)) {
+ if (get_super_prefix() &&
+ !(commands[i].option & SUPPORT_SUPER_PREFIX))
+ die(_("%s doesn't support --super-prefix"),
+ commands[i].cmd);
+ return commands[i].fn(argc - 1, argv + 1, prefix);
+ }
+ }
+
+ die(_("'%s' is not a valid submodule--helper "
+ "subcommand"), argv[1]);
+}
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index b6a711d319..70addef158 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -4,8 +4,8 @@
#include "parse-options.h"
static const char * const git_symbolic_ref_usage[] = {
- N_("git symbolic-ref [options] name [ref]"),
- N_("git symbolic-ref -d [-q] name"),
+ N_("git symbolic-ref [<options>] <name> [<ref>]"),
+ N_("git symbolic-ref -d [-q] <name>"),
NULL
};
@@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)
{
unsigned char sha1[20];
int flag;
- const char *refname = resolve_ref_unsafe(HEAD, sha1, 0, &flag);
+ const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag);
if (!refname)
die("No such ref: %s", HEAD);
@@ -56,7 +56,9 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
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);
+ if (!strcmp(argv[0], "HEAD"))
+ die("deleting '%s' is not allowed", argv[0]);
+ return delete_ref(NULL, argv[0], NULL, REF_NODEREF);
}
switch (argc) {
@@ -67,7 +69,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
if (!strcmp(argv[0], "HEAD") &&
!starts_with(argv[1], "refs/"))
die("Refusing to point HEAD outside of refs/");
- create_symref(argv[0], argv[1], msg);
+ ret = !!create_symref(argv[0], argv[1], msg);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
diff --git a/builtin/tag.c b/builtin/tag.c
index 74d3780b77..ad29be6923 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -17,184 +17,59 @@
#include "gpg-interface.h"
#include "sha1-array.h"
#include "column.h"
+#include "ref-filter.h"
static const char * const git_tag_usage[] = {
- N_("git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]"),
+ 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>..."),
+ N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]"
+ "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"),
+ N_("git tag -v [--format=<format>] <tagname>..."),
NULL
};
-struct tag_filter {
- const char **patterns;
- int lines;
- struct commit_list *with_commit;
-};
-
-static struct sha1_array points_at;
static unsigned int colopts;
+static int force_sign_annotate;
-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))
- 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)
- if (!hashcmp(want->item->object.sha1, c->object.sha1))
- return 1;
- return 0;
-}
-
-static int contains_recurse(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;
- /* or marked as not possibly containing a want commit? */
- if (candidate->object.flags & UNINTERESTING)
- return 0;
- /* or are we it? */
- if (in_commit_list(want, candidate))
- 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;
- }
- }
- candidate->object.flags |= UNINTERESTING;
- return 0;
-}
-
-static int contains(struct commit *candidate, const struct commit_list *want)
-{
- return contains_recurse(candidate, want);
-}
-
-static void show_tag_lines(const unsigned char *sha1, int lines)
+static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
{
+ struct ref_array array;
+ char *to_free = NULL;
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));
+ memset(&array, 0, sizeof(array));
- /* 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);
-}
+ if (filter->lines == -1)
+ filter->lines = 0;
-static int show_reference(const char *refname, const unsigned char *sha1,
- int flag, void *cb_data)
-{
- struct tag_filter *filter = cb_data;
-
- if (match_pattern(filter->patterns, refname)) {
- if (filter->with_commit) {
- struct commit *commit;
-
- commit = lookup_commit_reference_gently(sha1, 1);
- if (!commit)
- return 0;
- if (!contains(commit, filter->with_commit))
- return 0;
- }
-
- if (points_at.nr && !match_points_at(refname, sha1))
- return 0;
-
- if (!filter->lines) {
- printf("%s\n", refname);
- return 0;
- }
- printf("%-15s ", refname);
- show_tag_lines(sha1, filter->lines);
- putchar('\n');
+ if (!format) {
+ if (filter->lines) {
+ to_free = xstrfmt("%s %%(contents:lines=%d)",
+ "%(align:15)%(refname:lstrip=2)%(end)",
+ filter->lines);
+ format = to_free;
+ } else
+ format = "%(refname:lstrip=2)";
}
- return 0;
-}
-
-static int list_tags(const char **patterns, int lines,
- struct commit_list *with_commit)
-{
- struct tag_filter filter;
-
- filter.patterns = patterns;
- filter.lines = lines;
- filter.with_commit = with_commit;
+ verify_ref_format(format);
+ filter->with_commit_tag_algo = 1;
+ filter_refs(&array, filter, FILTER_REFS_TAGS);
+ ref_array_sort(sorting, &array);
- for_each_tag_ref(show_reference, (void *) &filter);
+ for (i = 0; i < array.nr; i++)
+ show_ref_array_item(array.items[i], format, 0);
+ ref_array_clear(&array);
+ free(to_free);
return 0;
}
typedef int (*each_tag_name_fn)(const char *name, const char *ref,
- const unsigned char *sha1);
+ const unsigned char *sha1, const void *cb_data);
-static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
+static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
+ const void *cb_data)
{
const char **p;
char ref[PATH_MAX];
@@ -213,30 +88,37 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
had_error = 1;
continue;
}
- if (fn(*p, ref, sha1))
+ if (fn(*p, ref, sha1, cb_data))
had_error = 1;
}
return had_error;
}
static int delete_tag(const char *name, const char *ref,
- const unsigned char *sha1)
+ const unsigned char *sha1, const void *cb_data)
{
- if (delete_ref(ref, sha1, 0))
+ if (delete_ref(NULL, ref, sha1, 0))
return 1;
printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
return 0;
}
static int verify_tag(const char *name, const char *ref,
- const unsigned char *sha1)
+ const unsigned char *sha1, const void *cb_data)
{
- const char *argv_verify_tag[] = {"verify-tag",
- "-v", "SHA1_HEX", NULL};
- argv_verify_tag[2] = sha1_to_hex(sha1);
+ int flags;
+ const char *fmt_pretty = cb_data;
+ flags = GPG_VERIFY_VERBOSE;
+
+ if (fmt_pretty)
+ flags = GPG_VERIFY_OMIT_STATUS;
+
+ if (gpg_verify_tag(sha1, name, flags))
+ return -1;
+
+ if (fmt_pretty)
+ pretty_print_ref(name, sha1, fmt_pretty);
- if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD))
- return error(_("could not verify the tag '%s'"), name);
return 0;
}
@@ -246,19 +128,58 @@ static int do_sign(struct strbuf *buffer)
}
static const char tag_template[] =
- N_("\nWrite a tag message\n"
+ N_("\nWrite a message for tag:\n %s\n"
"Lines starting with '%c' will be ignored.\n");
static const char tag_template_nocleanup[] =
- N_("\nWrite a tag message\n"
+ 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 arg given and add it the ref_sorting array */
+static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail)
+{
+ struct ref_sorting *s;
+ int len;
+
+ s = xcalloc(1, sizeof(*s));
+ s->next = *sorting_tail;
+ *sorting_tail = s;
+
+ if (*arg == '-') {
+ s->reverse = 1;
+ arg++;
+ }
+ if (skip_prefix(arg, "version:", &arg) ||
+ skip_prefix(arg, "v:", &arg))
+ s->version = 1;
+
+ len = strlen(arg);
+ s->atom = parse_ref_filter_atom(arg, arg+len);
+
+ return 0;
+}
+
static int git_tag_config(const char *var, const char *value, void *cb)
{
- int status = git_gpg_config(var, value, cb);
+ int status;
+ struct ref_sorting **sorting_tail = (struct ref_sorting **)cb;
+
+ if (!strcmp(var, "tag.sort")) {
+ if (!value)
+ return config_error_nonbool(var);
+ parse_sorting_string(value, sorting_tail);
+ return 0;
+ }
+
+ status = git_gpg_config(var, value, cb);
if (status)
return status;
+ if (!strcmp(var, "tag.forcesignannotated")) {
+ force_sign_annotate = git_config_bool(var, value);
+ return 0;
+ }
+
if (starts_with(var, "column."))
return git_column_config(var, value, "tag", &colopts);
return git_default_config(var, value, cb);
@@ -346,9 +267,9 @@ static void create_tag(const unsigned char *object, const char *tag,
struct strbuf buf = STRBUF_INIT;
strbuf_addch(&buf, '\n');
if (opt->cleanup_mode == CLEANUP_ALL)
- strbuf_commented_addf(&buf, _(tag_template), comment_line_char);
+ strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char);
else
- strbuf_commented_addf(&buf, _(tag_template_nocleanup), comment_line_char);
+ strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char);
write_or_die(fd, buf.buf, buf.len);
strbuf_release(&buf);
}
@@ -362,7 +283,7 @@ static void create_tag(const unsigned char *object, const char *tag,
}
if (opt->cleanup_mode != CLEANUP_NONE)
- stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
+ strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL);
if (!opt->message_given && !buf->len)
die(_("no tag message?"));
@@ -381,6 +302,54 @@ static void create_tag(const unsigned char *object, const char *tag,
}
}
+static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb)
+{
+ enum object_type type;
+ struct commit *c;
+ char *buf;
+ unsigned long size;
+ int subject_len = 0;
+ const char *subject_start;
+
+ char *rla = getenv("GIT_REFLOG_ACTION");
+ if (rla) {
+ strbuf_addstr(sb, rla);
+ } else {
+ strbuf_addstr(sb, _("tag: tagging "));
+ strbuf_add_unique_abbrev(sb, sha1, DEFAULT_ABBREV);
+ }
+
+ strbuf_addstr(sb, " (");
+ type = sha1_object_info(sha1, NULL);
+ switch (type) {
+ default:
+ strbuf_addstr(sb, _("object of unknown type"));
+ break;
+ case OBJ_COMMIT:
+ if ((buf = read_sha1_file(sha1, &type, &size)) != NULL) {
+ subject_len = find_commit_subject(buf, &subject_start);
+ strbuf_insert(sb, sb->len, subject_start, subject_len);
+ } else {
+ strbuf_addstr(sb, _("commit object"));
+ }
+ free(buf);
+
+ if ((c = lookup_commit_reference(sha1)) != NULL)
+ strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
+ break;
+ case OBJ_TREE:
+ strbuf_addstr(sb, _("tree object"));
+ break;
+ case OBJ_BLOB:
+ strbuf_addstr(sb, _("blob object"));
+ break;
+ case OBJ_TAG:
+ strbuf_addstr(sb, _("other tag object"));
+ break;
+ }
+ strbuf_addch(sb, ')');
+}
+
struct msg_arg {
int given;
struct strbuf buf;
@@ -410,40 +379,29 @@ static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
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;
-}
-
int cmd_tag(int argc, const char **argv, const char *prefix)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf ref = STRBUF_INIT;
+ struct strbuf reflog_msg = STRBUF_INIT;
unsigned char object[20], prev[20];
const char *object_ref, *tag;
- struct ref_lock *lock;
struct create_tag_options opt;
char *cleanup_arg = NULL;
- int annotate = 0, force = 0, lines = -1;
- int cmdmode = 0;
+ int create_reflog = 0;
+ int annotate = 0, force = 0;
+ int cmdmode = 0, create_tag_object = 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 ref_filter filter;
+ static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
+ const char *format = NULL;
+ int icase = 0;
struct option options[] = {
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
- { OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
+ { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"),
N_("print <n> lines of each tag message"),
PARSE_OPT_OPTARG, NULL, 1 },
OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'),
@@ -458,28 +416,35 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
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"),
+ 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_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
+ OPT_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")),
OPT_GROUP(N_("Tag listing options")),
+ OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
+ OPT_CONTAINS(&filter.with_commit, N_("print only tags that contain the commit")),
+ OPT_WITH(&filter.with_commit, N_("print only tags that contain the commit")),
+ OPT_MERGED(&filter, N_("print only tags that are merged")),
+ OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
+ OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
+ N_("field name to sort on"), &parse_opt_ref_sorting),
{
- 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, "points-at", NULL, N_("object"),
- N_("print only tags of the object"), 0, parse_opt_points_at
+ OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
+ N_("print only tags of the object"), 0, parse_opt_object_name
},
+ OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
+ OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
OPT_END()
};
- git_config(git_tag_config, NULL);
+ setup_ref_filter_porcelain_msg();
+
+ git_config(git_tag_config, sorting_tail);
memset(&opt, 0, sizeof(opt));
+ memset(&filter, 0, sizeof(filter));
+ filter.lines = -1;
argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0);
@@ -487,20 +452,24 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
opt.sign = 1;
set_signing_key(keyid);
}
- if (opt.sign)
- annotate = 1;
+ create_tag_object = (opt.sign || annotate || msg.given || msgfile);
+
if (argc == 0 && !cmdmode)
cmdmode = 'l';
- if ((annotate || msg.given || msgfile || force) && (cmdmode != 0))
+ if ((create_tag_object || force) && (cmdmode != 0))
usage_with_options(git_tag_usage, options);
finalize_colopts(&colopts, -1);
- if (cmdmode == 'l' && lines != -1) {
+ if (cmdmode == 'l' && filter.lines != -1) {
if (explicitly_enable_column(colopts))
die(_("--column and -n are incompatible"));
colopts = 0;
}
+ if (!sorting)
+ sorting = ref_default_sorting();
+ sorting->ignore_case = icase;
+ filter.ignore_case = icase;
if (cmdmode == 'l') {
int ret;
if (column_active(colopts)) {
@@ -509,26 +478,31 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
copts.padding = 2;
run_column_filter(colopts, &copts);
}
- ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
+ filter.name_patterns = argv;
+ ret = list_tags(&filter, sorting, format);
if (column_active(colopts))
stop_column_filter();
return ret;
}
- if (lines != -1)
+ if (filter.lines != -1)
die(_("-n option is only allowed with -l."));
- if (with_commit)
+ if (filter.with_commit)
die(_("--contains option is only allowed with -l."));
- if (points_at.nr)
+ if (filter.points_at.nr)
die(_("--points-at option is only allowed with -l."));
+ if (filter.merge_commit)
+ die(_("--merged and --no-merged option are only allowed with -l"));
if (cmdmode == 'd')
- return for_each_tag_name(argv, delete_tag);
- if (cmdmode == 'v')
- return for_each_tag_name(argv, verify_tag);
+ return for_each_tag_name(argv, delete_tag, NULL);
+ if (cmdmode == 'v') {
+ if (format)
+ verify_ref_format(format);
+ return for_each_tag_name(argv, verify_tag, format);
+ }
if (msg.given || msgfile) {
if (msg.given && msgfile)
die(_("only one -F or -m option is allowed."));
- annotate = 1;
if (msg.given)
strbuf_addbuf(&buf, &(msg.buf));
else {
@@ -571,18 +545,28 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
else
die(_("Invalid cleanup mode %s"), cleanup_arg);
- if (annotate)
+ create_reflog_msg(object, &reflog_msg);
+
+ if (create_tag_object) {
+ if (force_sign_annotate && !annotate)
+ opt.sign = 1;
create_tag(object, tag, &buf, &opt, prev, object);
+ }
- lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
- if (!lock)
- die(_("%s: cannot lock the ref"), ref.buf);
- if (write_ref_sha1(lock, object, NULL) < 0)
- die(_("%s: cannot update the ref"), ref.buf);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref.buf, object, prev,
+ create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
+ reflog_msg.buf, &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);
+ strbuf_release(&reflog_msg);
return 0;
}
diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c
index 19200291a2..6fc6bcdf7f 100644
--- a/builtin/unpack-file.c
+++ b/builtin/unpack-file.c
@@ -12,7 +12,7 @@ static char *create_temp_file(unsigned char *sha1)
if (!buf || type != OBJ_BLOB)
die("unable to read blob object %s", sha1_to_hex(sha1));
- strcpy(path, ".merge_file_XXXXXX");
+ xsnprintf(path, sizeof(path), ".merge_file_XXXXXX");
fd = xmkstemp(path);
if (write_in_full(fd, buf, size) != size)
die_errno("unable to write temp-file");
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c
index 62ff673f68..4532aa0831 100644
--- a/builtin/unpack-objects.c
+++ b/builtin/unpack-objects.c
@@ -13,13 +13,15 @@
#include "fsck.h"
static int dry_run, quiet, recover, has_errors, strict;
-static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] < pack-file";
+static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict]";
/* We always read in 4kB chunks. */
static unsigned char buffer[4096];
static unsigned int offset, len;
static off_t consumed_bytes;
+static off_t max_input_size;
static git_SHA_CTX ctx;
+static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT;
/*
* When running under --strict mode, objects whose reachability are
@@ -45,7 +47,7 @@ static void add_object_buffer(struct object *object, char *buffer, unsigned long
obj->buffer = buffer;
obj->size = size;
if (add_decoration(&obj_decorate, object, obj))
- die("object %s tried to add buffer twice!", sha1_to_hex(object->sha1));
+ die("object %s tried to add buffer twice!", oid_to_hex(&object->oid));
}
/*
@@ -86,12 +88,14 @@ static void use(int bytes)
if (signed_add_overflows(consumed_bytes, bytes))
die("pack too large for current definition of off_t");
consumed_bytes += bytes;
+ if (max_input_size && consumed_bytes > max_input_size)
+ die(_("pack exceeds maximum allowed size"));
}
static void *get_data(unsigned long size)
{
git_zstream stream;
- void *buf = xmalloc(size);
+ void *buf = xmallocz(size);
memset(&stream, 0, sizeof(stream));
@@ -164,12 +168,12 @@ 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));
+ die("failed to write object %s", oid_to_hex(&obj->oid));
obj->flags |= FLAG_WRITTEN;
}
@@ -178,8 +182,10 @@ static void write_cached_object(struct object *obj)
* that have reachability requirements and calls this function.
* Verify its reachability and validity recursively and write it out.
*/
-static int check_object(struct object *obj, int type, void *data)
+static int check_object(struct object *obj, int type, void *data, struct fsck_options *options)
{
+ struct obj_buffer *obj_buf;
+
if (!obj)
return 1;
@@ -191,18 +197,22 @@ static int check_object(struct object *obj, int type, void *data)
if (!(obj->flags & FLAG_OPEN)) {
unsigned long size;
- int type = sha1_object_info(obj->sha1, &size);
+ int type = sha1_object_info(obj->oid.hash, &size);
if (type != obj->type || type <= 0)
die("object of unexpected type");
obj->flags |= FLAG_WRITTEN;
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'", oid_to_hex(&obj->oid));
+ if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options))
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);
+ fsck_options.walk = check_object;
+ if (fsck_walk(obj, NULL, &fsck_options))
+ die("Error on reachable objects of %s", oid_to_hex(&obj->oid));
+ write_cached_object(obj, obj_buf);
return 0;
}
@@ -211,7 +221,7 @@ static void write_rest(void)
unsigned i;
for (i = 0; i < nr_objects; i++) {
if (obj_list[i].obj)
- check_object(obj_list[i].obj, OBJ_ANY, NULL);
+ check_object(obj_list[i].obj, OBJ_ANY, NULL, NULL);
}
}
@@ -348,7 +358,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
return; /* we are done */
else {
/* cannot resolve yet --- queue it */
- hashcpy(obj_list[nr].sha1, null_sha1);
+ hashclr(obj_list[nr].sha1);
add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
return;
}
@@ -399,7 +409,7 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
* The delta base object is itself a delta that
* has not been resolved yet.
*/
- hashcpy(obj_list[nr].sha1, null_sha1);
+ hashclr(obj_list[nr].sha1);
add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
return;
}
@@ -480,7 +490,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 +507,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,6 +533,11 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
strict = 1;
continue;
}
+ if (skip_prefix(arg, "--strict=", &arg)) {
+ strict = 1;
+ fsck_set_msg_types(&fsck_options, arg);
+ continue;
+ }
if (starts_with(arg, "--pack_header=")) {
struct pack_header *hdr;
char *c;
@@ -538,6 +553,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
len = sizeof(*hdr);
continue;
}
+ if (skip_prefix(arg, "--max-input-size=", &arg)) {
+ max_input_size = strtoumax(arg, NULL, 10);
+ continue;
+ }
usage(unpack_usage);
}
diff --git a/builtin/update-index.c b/builtin/update-index.c
index d12ad95f3e..d530e89368 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"
@@ -13,6 +14,7 @@
#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
@@ -31,6 +33,16 @@ static int mark_valid_only;
static int mark_skip_worktree_only;
#define MARK_FLAG 1
#define UNMARK_FLAG 2
+static struct strbuf mtime_dir = STRBUF_INIT;
+
+/* Untracked cache mode */
+enum uc_mode {
+ UC_UNSPECIFIED = -1,
+ UC_DISABLE = 0,
+ UC_ENABLE,
+ UC_TEST,
+ UC_FORCE
+};
__attribute__((format (printf, 1, 2)))
static void report(const char *fmt, ...)
@@ -46,6 +58,166 @@ static void report(const char *fmt, ...)
va_end(vp);
}
+static void remove_test_directory(void)
+{
+ if (mtime_dir.len)
+ remove_dir_recursively(&mtime_dir, 0);
+}
+
+static const char *get_mtime_path(const char *path)
+{
+ static struct strbuf sb = STRBUF_INIT;
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/%s", mtime_dir.buf, path);
+ return sb.buf;
+}
+
+static void xmkdir(const char *path)
+{
+ path = get_mtime_path(path);
+ if (mkdir(path, 0700))
+ die_errno(_("failed to create directory %s"), path);
+}
+
+static int xstat_mtime_dir(struct stat *st)
+{
+ if (stat(mtime_dir.buf, st))
+ die_errno(_("failed to stat %s"), mtime_dir.buf);
+ return 0;
+}
+
+static int create_file(const char *path)
+{
+ int fd;
+ path = get_mtime_path(path);
+ fd = open(path, O_CREAT | O_RDWR, 0644);
+ if (fd < 0)
+ die_errno(_("failed to create file %s"), path);
+ return fd;
+}
+
+static void xunlink(const char *path)
+{
+ path = get_mtime_path(path);
+ if (unlink(path))
+ die_errno(_("failed to delete file %s"), path);
+}
+
+static void xrmdir(const char *path)
+{
+ path = get_mtime_path(path);
+ if (rmdir(path))
+ die_errno(_("failed to delete directory %s"), path);
+}
+
+static void avoid_racy(void)
+{
+ /*
+ * not use if we could usleep(10) if USE_NSEC is defined. The
+ * field nsec could be there, but the OS could choose to
+ * ignore it?
+ */
+ sleep(1);
+}
+
+static int test_if_untracked_cache_is_supported(void)
+{
+ struct stat st;
+ struct stat_data base;
+ int fd, ret = 0;
+
+ strbuf_addstr(&mtime_dir, "mtime-test-XXXXXX");
+ if (!mkdtemp(mtime_dir.buf))
+ die_errno("Could not make temporary directory");
+
+ fprintf(stderr, _("Testing mtime in '%s' "), xgetcwd());
+ atexit(remove_test_directory);
+ xstat_mtime_dir(&st);
+ fill_stat_data(&base, &st);
+ fputc('.', stderr);
+
+ avoid_racy();
+ fd = create_file("newfile");
+ xstat_mtime_dir(&st);
+ if (!match_stat_data(&base, &st)) {
+ close(fd);
+ fputc('\n', stderr);
+ fprintf_ln(stderr,_("directory stat info does not "
+ "change after adding a new file"));
+ goto done;
+ }
+ fill_stat_data(&base, &st);
+ fputc('.', stderr);
+
+ avoid_racy();
+ xmkdir("new-dir");
+ xstat_mtime_dir(&st);
+ if (!match_stat_data(&base, &st)) {
+ close(fd);
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info does not change "
+ "after adding a new directory"));
+ goto done;
+ }
+ fill_stat_data(&base, &st);
+ fputc('.', stderr);
+
+ avoid_racy();
+ write_or_die(fd, "data", 4);
+ close(fd);
+ xstat_mtime_dir(&st);
+ if (match_stat_data(&base, &st)) {
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info changes "
+ "after updating a file"));
+ goto done;
+ }
+ fputc('.', stderr);
+
+ avoid_racy();
+ close(create_file("new-dir/new"));
+ xstat_mtime_dir(&st);
+ if (match_stat_data(&base, &st)) {
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info changes after "
+ "adding a file inside subdirectory"));
+ goto done;
+ }
+ fputc('.', stderr);
+
+ avoid_racy();
+ xunlink("newfile");
+ xstat_mtime_dir(&st);
+ if (!match_stat_data(&base, &st)) {
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info does not "
+ "change after deleting a file"));
+ goto done;
+ }
+ fill_stat_data(&base, &st);
+ fputc('.', stderr);
+
+ avoid_racy();
+ xunlink("new-dir/new");
+ xrmdir("new-dir");
+ xstat_mtime_dir(&st);
+ if (!match_stat_data(&base, &st)) {
+ fputc('\n', stderr);
+ fprintf_ln(stderr, _("directory stat info does not "
+ "change after deleting a directory"));
+ goto done;
+ }
+
+ if (rmdir(mtime_dir.buf))
+ die_errno(_("failed to delete directory %s"), mtime_dir.buf);
+ fprintf_ln(stderr, _(" OK"));
+ ret = 1;
+
+done:
+ strbuf_release(&mtime_dir);
+ return ret;
+}
+
static int mark_ce_flags(const char *path, int flag, int mark)
{
int namelen = strlen(path);
@@ -55,8 +227,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;
@@ -82,7 +255,7 @@ static int process_lstat_error(const char *path, int err)
{
if (err == ENOENT || err == ENOTDIR)
return remove_one_path(path);
- return error("lstat(\"%s\"): %s", path, strerror(errno));
+ return error("lstat(\"%s\"): %s", path, strerror(err));
}
static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st)
@@ -102,7 +275,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len
fill_stat_cache_info(ce, st);
ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
- if (index_path(ce->sha1, path, st,
+ if (index_path(ce->oid.hash, path, st,
info_only ? 0 : HASH_WRITE_OBJECT)) {
free(ce);
return -1;
@@ -139,7 +312,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len
*/
static int process_directory(const char *path, int len, struct stat *st)
{
- unsigned char sha1[20];
+ struct object_id oid;
int pos = cache_name_pos(path, len);
/* Exact match: file or existing gitlink */
@@ -148,7 +321,7 @@ static int process_directory(const char *path, int len, struct stat *st)
if (S_ISGITLINK(ce->ce_mode)) {
/* Do nothing to the index if there is no HEAD! */
- if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
+ if (resolve_gitlink_ref(path, "HEAD", oid.hash) < 0)
return 0;
return add_one_path(ce, path, len, st);
@@ -174,7 +347,7 @@ static int process_directory(const char *path, int len, struct stat *st)
}
/* No match - should we add it as a gitlink? */
- if (!resolve_gitlink_ref(path, "HEAD", sha1))
+ if (!resolve_gitlink_ref(path, "HEAD", oid.hash))
return add_one_path(NULL, path, len, st);
/* Error out. */
@@ -217,7 +390,7 @@ static int process_path(const char *path)
return add_one_path(ce, path, len, &st);
}
-static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
+static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
const char *path, int stage)
{
int size, len, option;
@@ -230,7 +403,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
size = cache_entry_size(len);
ce = xcalloc(1, size);
- hashcpy(ce->sha1, sha1);
+ oidcpy(&ce->oid, oid);
memcpy(ce->name, path, len);
ce->ce_flags = create_ce_flags(stage);
ce->ce_namelen = len;
@@ -246,29 +419,18 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
return 0;
}
-static void chmod_path(int flip, const char *path)
+static void chmod_path(char flip, const char *path)
{
int pos;
struct cache_entry *ce;
- unsigned int mode;
pos = cache_name_pos(path, strlen(path));
if (pos < 0)
goto fail;
ce = active_cache[pos];
- mode = ce->ce_mode;
- if (!S_ISREG(mode))
- goto fail;
- switch (flip) {
- case '+':
- ce->ce_mode |= 0111; break;
- case '-':
- ce->ce_mode &= ~0111; break;
- default:
+ if (chmod_cache_entry(ce, flip) < 0)
goto fail;
- }
- cache_tree_invalidate_path(active_cache_tree, path);
- active_cache_changed = 1;
+
report("chmod %cx '%s'", flip, path);
return;
fail:
@@ -303,15 +465,17 @@ static void update_one(const char *path)
report("add '%s'", path);
}
-static void read_index_info(int line_termination)
+static void read_index_info(int nul_term_line)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf uq = STRBUF_INIT;
+ strbuf_getline_fn getline_fn;
- while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+ getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
+ while (getline_fn(&buf, stdin) != EOF) {
char *ptr, *tab;
char *path_name;
- unsigned char sha1[20];
+ struct object_id oid;
unsigned int mode;
unsigned long ul;
int stage;
@@ -340,7 +504,7 @@ static void read_index_info(int line_termination)
mode = ul;
tab = strchr(ptr, '\t');
- if (!tab || tab - ptr < 41)
+ if (!tab || tab - ptr < GIT_SHA1_HEXSZ + 1)
goto bad_line;
if (tab[-2] == ' ' && '0' <= tab[-1] && tab[-1] <= '3') {
@@ -353,11 +517,12 @@ static void read_index_info(int line_termination)
ptr = tab + 1; /* point at the head of path */
}
- if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ')
+ if (get_oid_hex(tab - GIT_SHA1_HEXSZ, &oid) ||
+ tab[-(GIT_SHA1_HEXSZ + 1)] != ' ')
goto bad_line;
path_name = ptr;
- if (line_termination && path_name[0] == '"') {
+ if (!nul_term_line && path_name[0] == '"') {
strbuf_reset(&uq);
if (unquote_c_style(&uq, path_name, NULL)) {
die("git update-index: bad quoting of path name");
@@ -381,8 +546,8 @@ static void read_index_info(int line_termination)
* ptr[-1] points at tab,
* ptr[-41] is at the beginning of sha1
*/
- ptr[-42] = ptr[-1] = 0;
- if (add_cacheinfo(mode, sha1, path_name, stage))
+ ptr[-(GIT_SHA1_HEXSZ + 2)] = ptr[-1] = 0;
+ if (add_cacheinfo(mode, &oid, path_name, stage))
die("git update-index: unable to update %s",
path_name);
}
@@ -396,23 +561,23 @@ static void read_index_info(int line_termination)
}
static const char * const update_index_usage[] = {
- N_("git update-index [options] [--] [<file>...]"),
+ N_("git update-index [<options>] [--] [<file>...]"),
NULL
};
-static unsigned char head_sha1[20];
-static unsigned char merge_head_sha1[20];
+static struct object_id head_oid;
+static struct object_id merge_head_oid;
static struct cache_entry *read_one_ent(const char *which,
- unsigned char *ent, const char *path,
+ struct object_id *ent, const char *path,
int namelen, int stage)
{
unsigned mode;
- unsigned char sha1[20];
+ struct object_id oid;
int size;
struct cache_entry *ce;
- if (get_tree_entry(ent, path, sha1, &mode)) {
+ if (get_tree_entry(ent->hash, path, oid.hash, &mode)) {
if (which)
error("%s: not in %s branch.", path, which);
return NULL;
@@ -425,7 +590,7 @@ static struct cache_entry *read_one_ent(const char *which,
size = cache_entry_size(namelen);
ce = xcalloc(1, size);
- hashcpy(ce->sha1, sha1);
+ oidcpy(&ce->oid, &oid);
memcpy(ce->name, path, namelen);
ce->ce_flags = create_ce_flags(stage);
ce->ce_namelen = namelen;
@@ -475,14 +640,14 @@ static int unresolve_one(const char *path)
* stuff HEAD version in stage #2,
* stuff MERGE_HEAD version in stage #3.
*/
- ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
- ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
+ ce_2 = read_one_ent("our", &head_oid, path, namelen, 2);
+ ce_3 = read_one_ent("their", &merge_head_oid, path, namelen, 3);
if (!ce_2 || !ce_3) {
ret = -1;
goto free_return;
}
- if (!hashcmp(ce_2->sha1, ce_3->sha1) &&
+ if (!oidcmp(&ce_2->oid, &ce_3->oid) &&
ce_2->ce_mode == ce_3->ce_mode) {
fprintf(stderr, "%s: identical in both, skipping.\n",
path);
@@ -507,9 +672,9 @@ static int unresolve_one(const char *path)
static void read_head_pointers(void)
{
- if (read_ref("HEAD", head_sha1))
+ if (read_ref("HEAD", head_oid.hash))
die("No HEAD -- no initial commit yet?");
- if (read_ref("MERGE_HEAD", merge_head_sha1)) {
+ if (read_ref("MERGE_HEAD", merge_head_oid.hash)) {
fprintf(stderr, "Not in the middle of a merge.\n");
exit(0);
}
@@ -528,10 +693,9 @@ static int do_unresolve(int ac, const char **av,
for (i = 1; i < ac; i++) {
const char *arg = av[i];
- const char *p = prefix_path(prefix, prefix_length, arg);
+ char *p = prefix_path(prefix, prefix_length, arg);
err |= unresolve_one(p);
- if (p < arg || p > arg + strlen(arg))
- free((char *)p);
+ free(p);
}
return err;
}
@@ -550,7 +714,7 @@ static int do_reupdate(int ac, const char **av,
PATHSPEC_PREFER_CWD,
prefix, av + 1);
- if (read_ref("HEAD", head_sha1))
+ if (read_ref("HEAD", head_oid.hash))
/* If there is no HEAD, that means it is an initial
* commit. Update everything in the index.
*/
@@ -565,10 +729,10 @@ static int do_reupdate(int ac, const char **av,
if (ce_stage(ce) || !ce_path_match(ce, &pathspec, NULL))
continue;
if (has_head)
- old = read_one_ent(NULL, head_sha1,
+ old = read_one_ent(NULL, &head_oid,
ce->name, ce_namelen(ce), 0);
if (old && ce->ce_mode == old->ce_mode &&
- !hashcmp(ce->sha1, old->sha1)) {
+ !oidcmp(&ce->oid, &old->oid)) {
free(old);
continue; /* unchanged */
}
@@ -580,10 +744,11 @@ static int do_reupdate(int ac, const char **av,
path = xstrdup(ce->name);
update_one(path);
free(path);
+ free(old);
if (save_nr != active_nr)
goto redo;
}
- free_pathspec(&pathspec);
+ clear_pathspec(&pathspec);
return 0;
}
@@ -629,17 +794,48 @@ static int resolve_undo_clear_callback(const struct option *opt,
return 0;
}
+static int parse_new_style_cacheinfo(const char *arg,
+ unsigned int *mode,
+ struct object_id *oid,
+ 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_oid_hex(endp, oid) || endp[GIT_SHA1_HEXSZ] != ',')
+ return -1;
+ *path = endp + GIT_SHA1_HEXSZ + 1;
+ return 0;
+}
+
static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
const struct option *opt, int unset)
{
- unsigned char sha1[20];
+ struct object_id oid;
unsigned int mode;
+ const char *path;
+ if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) {
+ if (add_cacheinfo(mode, &oid, 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))
+ get_oid_hex(*++ctx->argv, &oid) ||
+ add_cacheinfo(mode, &oid, *++ctx->argv, 0))
die("git update-index: --cacheinfo cannot add %s", *ctx->argv);
ctx->argc -= 3;
return 0;
@@ -648,12 +844,12 @@ static int cacheinfo_callback(struct parse_opt_ctx_t *ctx,
static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx,
const struct option *opt, int unset)
{
- int *line_termination = opt->value;
+ int *nul_term_line = opt->value;
if (ctx->argc != 1)
return error("option '%s' must be the last argument", opt->long_name);
allow_add = allow_replace = allow_remove = 1;
- read_index_info(*line_termination);
+ read_index_info(*nul_term_line);
return 0;
}
@@ -705,15 +901,18 @@ static int reupdate_callback(struct parse_opt_ctx_t *ctx,
int cmd_update_index(int argc, const char **argv, const char *prefix)
{
- int newfd, entries, has_errors = 0, line_termination = '\n';
+ int newfd, entries, has_errors = 0, nul_term_line = 0;
+ enum uc_mode untracked_cache = UC_UNSPECIFIED;
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;
+ strbuf_getline_fn getline_fn;
int parseopt_state = PARSE_OPT_UNKNOWN;
struct option options[] = {
OPT_BIT('q', NULL, &refresh_args.flags,
@@ -740,9 +939,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
really_refresh_callback},
{OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL,
- N_("<mode> <object> <path>"),
+ N_("<mode>,<object>,<path>"),
N_("add the specified entry to the index"),
- PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */
+ 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, N_("(+/-)x"),
@@ -765,13 +964,13 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
N_("add to index only; do not add content to object database"), 1),
OPT_SET_INT(0, "force-remove", &force_remove,
N_("remove named paths even if present in worktree"), 1),
- OPT_SET_INT('z', NULL, &line_termination,
- N_("with --stdin: input lines are terminated by null bytes"), '\0'),
+ OPT_BOOL('z', NULL, &nul_term_line,
+ N_("with --stdin: input lines are terminated by null bytes")),
{OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL,
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,
+ {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL,
N_("add entries from standard input to the index"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
(parse_opt_cb *) stdin_cacheinfo_callback},
@@ -794,6 +993,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
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_BOOL(0, "untracked-cache", &untracked_cache,
+ N_("enable/disable untracked cache")),
+ OPT_SET_INT(0, "test-untracked-cache", &untracked_cache,
+ N_("test if the filesystem supports untracked cache"), UC_TEST),
+ OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
+ N_("enable untracked cache without testing the filesystem"), UC_FORCE),
OPT_END()
};
@@ -805,6 +1012,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
/* We can't free this memory, it becomes part of a linked list parsed atexit() */
lock_file = xcalloc(1, sizeof(struct lock_file));
+ /* we will diagnose later if it turns out that we need to update it */
newfd = hold_locked_index(lock_file, 0);
if (newfd < 0)
lock_error = errno;
@@ -832,14 +1040,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
case PARSE_OPT_DONE:
{
const char *path = ctx.argv[0];
- const char *p;
+ char *p;
setup_work_tree();
p = prefix_path(prefix, prefix_length, path);
update_one(p);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
- free((char *)p);
+ free(p);
ctx.argc--;
ctx.argv++;
break;
@@ -853,6 +1061,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
}
}
argc = parse_options_end(&ctx);
+
+ getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf;
if (preferred_index_format) {
if (preferred_index_format < INDEX_FORMAT_LB ||
INDEX_FORMAT_UB < preferred_index_format)
@@ -861,40 +1071,81 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
INDEX_FORMAT_LB, INDEX_FORMAT_UB);
if (the_index.version != preferred_index_format)
- active_cache_changed = 1;
+ active_cache_changed |= SOMETHING_CHANGED;
the_index.version = preferred_index_format;
}
if (read_from_stdin) {
- struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf unquoted = STRBUF_INIT;
setup_work_tree();
- while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
- const char *p;
- if (line_termination && buf.buf[0] == '"') {
- strbuf_reset(&nbuf);
- if (unquote_c_style(&nbuf, buf.buf, NULL))
+ while (getline_fn(&buf, stdin) != EOF) {
+ char *p;
+ if (!nul_term_line && buf.buf[0] == '"') {
+ strbuf_reset(&unquoted);
+ if (unquote_c_style(&unquoted, buf.buf, NULL))
die("line is badly quoted");
- strbuf_swap(&buf, &nbuf);
+ strbuf_swap(&buf, &unquoted);
}
p = prefix_path(prefix, prefix_length, buf.buf);
update_one(p);
if (set_executable_bit)
chmod_path(set_executable_bit, p);
- free((char *)p);
+ free(p);
}
- strbuf_release(&nbuf);
+ strbuf_release(&unquoted);
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;
+ }
+
+ switch (untracked_cache) {
+ case UC_UNSPECIFIED:
+ break;
+ case UC_DISABLE:
+ if (git_config_get_untracked_cache() == 1)
+ warning(_("core.untrackedCache is set to true; "
+ "remove or change it, if you really want to "
+ "disable the untracked cache"));
+ remove_untracked_cache(&the_index);
+ report(_("Untracked cache disabled"));
+ break;
+ case UC_TEST:
+ setup_work_tree();
+ return !test_if_untracked_cache_is_supported();
+ case UC_ENABLE:
+ case UC_FORCE:
+ if (git_config_get_untracked_cache() == 0)
+ warning(_("core.untrackedCache is set to false; "
+ "remove or change it, if you really want to "
+ "enable the untracked cache"));
+ add_untracked_cache(&the_index);
+ report(_("Untracked cache enabled for '%s'"), get_git_work_tree());
+ break;
+ default:
+ die("BUG: bad untracked_cache value: %d", untracked_cache);
+ }
+
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 1292cfea11..0b2ecf41ae 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -6,250 +6,358 @@
#include "argv-array.h"
static const char * const git_update_ref_usage[] = {
- N_("git update-ref [options] -d <refname> [<oldval>]"),
- N_("git update-ref [options] <refname> <newval> [<oldval>]"),
- N_("git update-ref [options] --stdin [-z]"),
+ N_("git update-ref [<options>] -d <refname> [<old-val>]"),
+ N_("git update-ref [<options>] <refname> <new-val> [<old-val>]"),
+ N_("git update-ref [<options>] --stdin [-z]"),
NULL
};
-static int updates_alloc;
-static int updates_count;
-static const struct ref_update **updates;
-
static char line_termination = '\n';
static int update_flags;
-
-static struct ref_update *update_alloc(void)
-{
- struct ref_update *update;
-
- /* Allocate and zero-init a struct ref_update */
- update = xcalloc(1, sizeof(*update));
- ALLOC_GROW(updates, updates_count + 1, updates_alloc);
- updates[updates_count++] = update;
-
- /* Store and reset accumulated options */
- update->flags = update_flags;
- update_flags = 0;
-
- return update;
-}
-
-static void update_store_ref_name(struct ref_update *update,
- const char *ref_name)
-{
- if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
- die("invalid ref format: %s", ref_name);
- update->ref_name = xstrdup(ref_name);
-}
-
-static void update_store_new_sha1(struct ref_update *update,
- const char *newvalue)
-{
- if (*newvalue && get_sha1(newvalue, update->new_sha1))
- die("invalid new value for ref %s: %s",
- update->ref_name, newvalue);
-}
-
-static void update_store_old_sha1(struct ref_update *update,
- const char *oldvalue)
-{
- if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
- die("invalid old value for ref %s: %s",
- update->ref_name, oldvalue);
-
- /* We have an old value if non-empty, or if empty without -z */
- update->have_old = *oldvalue || line_termination;
-}
-
+static unsigned create_reflog_flag;
+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)
{
- /* Parse SP-terminated, possibly C-quoted argument */
- if (*next != '"')
+ 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++);
- else if (unquote_c_style(arg, next, &next))
- die("badly quoted argument: %s", next);
+ }
- /* Return position after the argument */
return next;
}
-static const char *parse_first_arg(const char *next, struct strbuf *arg)
+/*
+ * Parse 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)
{
- /* Parse argument immediately after "command SP" */
- strbuf_reset(arg);
+ struct strbuf ref = STRBUF_INIT;
+
if (line_termination) {
/* Without -z, use the next argument */
- next = parse_arg(next, arg);
+ *next = parse_arg(*next, &ref);
} else {
- /* With -z, use rest of first NUL-terminated line */
- strbuf_addstr(arg, next);
- next = next + arg->len;
+ /* With -z, use everything up to the next NUL */
+ strbuf_addstr(&ref, *next);
+ *next += ref.len;
}
- return next;
+
+ 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);
}
-static const char *parse_next_arg(const char *next, struct strbuf *arg)
+/*
+ * 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)
{
- /* Parse next SP-terminated or NUL-terminated argument, if any */
- strbuf_reset(arg);
+ 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)
- return NULL;
- if (*next != ' ')
- die("expected SP but got: %s", next);
- next = parse_arg(next + 1, arg);
+ 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("expected NUL but got: %s", next);
- if (strbuf_getline(arg, stdin, '\0') == EOF)
- return NULL;
- next = arg->buf + arg->len;
+ 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;
+ }
}
- return next;
+
+ 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);
}
-static void parse_cmd_update(const char *next)
+
+/*
+ * 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 ref = STRBUF_INIT;
- struct strbuf newvalue = STRBUF_INIT;
- struct strbuf oldvalue = STRBUF_INIT;
- struct ref_update *update;
+ struct strbuf err = STRBUF_INIT;
+ char *refname;
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+ int have_old;
- update = update_alloc();
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("update: missing <ref>");
- if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
- update_store_ref_name(update, ref.buf);
- else
- die("update line missing <ref>");
+ if (parse_next_sha1(input, &next, new_sha1, "update", refname,
+ PARSE_SHA1_ALLOW_EMPTY))
+ die("update %s: missing <newvalue>", refname);
- if ((next = parse_next_arg(next, &newvalue)) != NULL)
- update_store_new_sha1(update, newvalue.buf);
- else
- die("update %s missing <newvalue>", ref.buf);
+ 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 ((next = parse_next_arg(next, &oldvalue)) != NULL)
- update_store_old_sha1(update, oldvalue.buf);
- else if(!line_termination)
- die("update %s missing [<oldvalue>] NUL", ref.buf);
+ if (ref_transaction_update(transaction, refname,
+ new_sha1, have_old ? old_sha1 : NULL,
+ update_flags | create_reflog_flag,
+ msg, &err))
+ die("%s", err.buf);
+
+ update_flags = 0;
+ free(refname);
+ strbuf_release(&err);
- if (next && *next)
- die("update %s has extra input: %s", ref.buf, next);
+ return next;
}
-static void parse_cmd_create(const char *next)
+static const char *parse_cmd_create(struct ref_transaction *transaction,
+ struct strbuf *input, const char *next)
{
- struct strbuf ref = STRBUF_INIT;
- struct strbuf newvalue = STRBUF_INIT;
- struct ref_update *update;
+ struct strbuf err = STRBUF_INIT;
+ char *refname;
+ unsigned char new_sha1[20];
- update = update_alloc();
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("create: missing <ref>");
- if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
- update_store_ref_name(update, ref.buf);
- else
- die("create line missing <ref>");
+ if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0))
+ die("create %s: missing <newvalue>", refname);
- if ((next = parse_next_arg(next, &newvalue)) != NULL)
- update_store_new_sha1(update, newvalue.buf);
- else
- die("create %s missing <newvalue>", ref.buf);
- if (is_null_sha1(update->new_sha1))
- die("create %s given zero new value", ref.buf);
+ if (is_null_sha1(new_sha1))
+ die("create %s: zero <newvalue>", refname);
+
+ if (*next != line_termination)
+ die("create %s: extra input: %s", refname, next);
- if (next && *next)
- die("create %s has extra input: %s", ref.buf, next);
+ if (ref_transaction_create(transaction, refname, new_sha1,
+ update_flags | create_reflog_flag,
+ msg, &err))
+ die("%s", err.buf);
+
+ update_flags = 0;
+ free(refname);
+ strbuf_release(&err);
+
+ return next;
}
-static void parse_cmd_delete(const char *next)
+static const char *parse_cmd_delete(struct ref_transaction *transaction,
+ struct strbuf *input, const char *next)
{
- struct strbuf ref = STRBUF_INIT;
- struct strbuf oldvalue = STRBUF_INIT;
- struct ref_update *update;
+ 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;
+ }
- update = update_alloc();
+ if (*next != line_termination)
+ die("delete %s: extra input: %s", refname, next);
- if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
- update_store_ref_name(update, ref.buf);
- else
- die("delete line missing <ref>");
+ if (ref_transaction_delete(transaction, refname,
+ have_old ? old_sha1 : NULL,
+ update_flags, msg, &err))
+ die("%s", err.buf);
- if ((next = parse_next_arg(next, &oldvalue)) != NULL)
- update_store_old_sha1(update, oldvalue.buf);
- else if(!line_termination)
- die("delete %s missing [<oldvalue>] NUL", ref.buf);
- if (update->have_old && is_null_sha1(update->old_sha1))
- die("delete %s given zero old value", ref.buf);
+ update_flags = 0;
+ free(refname);
+ strbuf_release(&err);
- if (next && *next)
- die("delete %s has extra input: %s", ref.buf, next);
+ return next;
}
-static void parse_cmd_verify(const char *next)
+static const char *parse_cmd_verify(struct ref_transaction *transaction,
+ struct strbuf *input, const char *next)
{
- struct strbuf ref = STRBUF_INIT;
- struct strbuf value = STRBUF_INIT;
- struct ref_update *update;
+ struct strbuf err = STRBUF_INIT;
+ char *refname;
+ unsigned char old_sha1[20];
- update = update_alloc();
+ refname = parse_refname(input, &next);
+ if (!refname)
+ die("verify: missing <ref>");
- if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
- update_store_ref_name(update, ref.buf);
- else
- die("verify line missing <ref>");
+ if (parse_next_sha1(input, &next, old_sha1, "verify", refname,
+ PARSE_SHA1_OLD))
+ hashclr(old_sha1);
+
+ if (*next != line_termination)
+ die("verify %s: extra input: %s", refname, next);
- if ((next = parse_next_arg(next, &value)) != NULL) {
- update_store_old_sha1(update, value.buf);
- update_store_new_sha1(update, value.buf);
- } else if(!line_termination)
- die("verify %s missing [<oldvalue>] NUL", ref.buf);
+ if (ref_transaction_verify(transaction, refname, old_sha1,
+ update_flags, &err))
+ die("%s", err.buf);
- if (next && *next)
- die("verify %s has extra input: %s", ref.buf, next);
+ update_flags = 0;
+ free(refname);
+ strbuf_release(&err);
+
+ return next;
}
-static void parse_cmd_option(const char *next)
+static const char *parse_cmd_option(struct strbuf *input, const char *next)
{
- if (!strcmp(next, "no-deref"))
+ 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(void)
+static void update_refs_stdin(struct ref_transaction *transaction)
{
- struct strbuf cmd = STRBUF_INIT;
+ 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 (strbuf_getline(&cmd, stdin, line_termination) != EOF)
- if (!cmd.buf[0])
+ while (next < input.buf + input.len) {
+ if (*next == line_termination)
die("empty command in input");
- else if (isspace(*cmd.buf))
- die("whitespace before command: %s", cmd.buf);
- else if (starts_with(cmd.buf, "update "))
- parse_cmd_update(cmd.buf + 7);
- else if (starts_with(cmd.buf, "create "))
- parse_cmd_create(cmd.buf + 7);
- else if (starts_with(cmd.buf, "delete "))
- parse_cmd_delete(cmd.buf + 7);
- else if (starts_with(cmd.buf, "verify "))
- parse_cmd_verify(cmd.buf + 7);
- else if (starts_with(cmd.buf, "option "))
- parse_cmd_option(cmd.buf + 7);
+ else 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", cmd.buf);
+ die("unknown command: %s", next);
+
+ next++;
+ }
- strbuf_release(&cmd);
+ 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, read_stdin = 0, end_null = 0, flags = 0;
+ int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
+ unsigned int flags = 0;
+ int create_reflog = 0;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
@@ -257,6 +365,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
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_BOOL( 0 , "create-reflog", &create_reflog, N_("create a reflog")),
OPT_END(),
};
@@ -266,13 +375,25 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
if (msg && !*msg)
die("Refusing to perform update with empty message.");
+ create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0;
+
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();
- return update_refs(msg, updates, updates_count, DIE_ON_ERR);
+ 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)
@@ -294,15 +415,29 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("%s: not a valid SHA1", value);
}
- hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */
- if (oldval && *oldval && get_sha1(oldval, oldsha1))
- die("%s: not a valid old SHA1", oldval);
+ if (oldval) {
+ if (!*oldval)
+ /*
+ * The empty string implies that the reference
+ * must not already exist:
+ */
+ hashclr(oldsha1);
+ else if (get_sha1(oldval, oldsha1))
+ die("%s: not a valid old SHA1", oldval);
+ }
if (no_deref)
flags = REF_NODEREF;
if (delete)
- return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
+ /*
+ * For purposes of backwards compatibility, we treat
+ * NULL_SHA1 as "don't care" here:
+ */
+ return delete_ref(msg, refname,
+ (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL,
+ flags);
else
return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
- flags, DIE_ON_ERR);
+ flags | create_reflog_flag,
+ UPDATE_REFS_DIE_ON_ERR);
}
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c
index 32ab94cd06..cde06977b7 100644
--- a/builtin/upload-archive.c
+++ b/builtin/upload-archive.c
@@ -43,21 +43,20 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
}
/* parse all options sent by the client */
- return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1);
+ return write_archive(sent_argv.argc, sent_argv.argv, prefix, NULL, 1);
}
__attribute__((format (printf, 1, 2)))
static void error_clnt(const char *fmt, ...)
{
- char buf[1024];
+ struct strbuf buf = STRBUF_INIT;
va_list params;
- int len;
va_start(params, fmt);
- len = vsprintf(buf, fmt, params);
+ strbuf_vaddf(&buf, fmt, params);
va_end(params);
- send_sideband(1, 3, buf, len, LARGE_PACKET_MAX);
- die("sent error to the client: %s", buf);
+ send_sideband(1, 3, buf.buf, buf.len, LARGE_PACKET_MAX);
+ die("sent error to the client: %s", buf.buf);
}
static ssize_t process_input(int child_fd, int band)
@@ -89,11 +88,11 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
writer.git_cmd = 1;
if (start_command(&writer)) {
int err = errno;
- packet_write(1, "NACK unable to spawn subprocess\n");
+ packet_write_fmt(1, "NACK unable to spawn subprocess\n");
die("upload-archive: %s", strerror(err));
}
- packet_write(1, "ACK\n");
+ packet_write_fmt(1, "ACK\n");
packet_flush(1);
while (1) {
@@ -105,8 +104,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix)
pfd[1].events = POLLIN;
if (poll(pfd, 2, -1) < 0) {
if (errno != EINTR) {
- error("poll failed resuming: %s",
- strerror(errno));
+ error_errno("poll failed resuming");
sleep(1);
}
continue;
diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c
new file mode 100644
index 0000000000..38bedf8f9f
--- /dev/null
+++ b/builtin/verify-commit.c
@@ -0,0 +1,94 @@
+/*
+ * 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, unsigned flags)
+{
+ struct signature_check signature_check;
+ int ret;
+
+ memset(&signature_check, 0, sizeof(signature_check));
+
+ ret = check_commit_signature(lookup_commit(sha1), &signature_check);
+ print_signature_buffer(&signature_check, flags);
+
+ signature_check_clear(&signature_check);
+ return ret;
+}
+
+static int verify_commit(const char *name, unsigned flags)
+{
+ 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, flags);
+
+ 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;
+ unsigned flags = 0;
+ const struct option verify_commit_options[] = {
+ OPT__VERBOSE(&verbose, N_("print commit contents")),
+ OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
+ 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);
+
+ if (verbose)
+ flags |= GPG_VERIFY_VERBOSE;
+
+ /* 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++], flags))
+ had_error = 1;
+ return had_error;
+}
diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c
index 66cd2df0f8..c94e156932 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[] = {
- N_("git verify-pack [-v|--verbose] [-s|--stat-only] <pack>..."),
+ N_("git verify-pack [-v | --verbose] [-s | --stat-only] <pack>..."),
NULL
};
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 9cdf332333..5199553d91 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -12,52 +12,13 @@
#include <signal.h>
#include "parse-options.h"
#include "gpg-interface.h"
+#include "ref-filter.h"
static const char * const verify_tag_usage[] = {
- N_("git verify-tag [-v|--verbose] <tag>..."),
+ N_("git verify-tag [-v | --verbose] [--format=<format>] <tag>..."),
NULL
};
-static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
-{
- int len;
-
- len = parse_signature(buf, size);
- if (verbose)
- write_in_full(1, buf, len);
-
- if (size == len)
- return error("no signature found");
-
- return verify_signed_buffer(buf, len, buf + len, size - len, NULL, NULL);
-}
-
-static int verify_tag(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("tag '%s' not found.", name);
-
- type = sha1_object_info(sha1, NULL);
- if (type != OBJ_TAG)
- return error("%s: cannot verify a non-tag object of type %s.",
- name, typename(type));
-
- buf = read_sha1_file(sha1, &type, &size);
- if (!buf)
- return error("%s: unable to read file.", name);
-
- ret = run_gpg_verify(buf, size, verbose);
-
- free(buf);
- return ret;
-}
-
static int git_verify_tag_config(const char *var, const char *value, void *cb)
{
int status = git_gpg_config(var, value, cb);
@@ -69,8 +30,12 @@ static int git_verify_tag_config(const char *var, const char *value, void *cb)
int cmd_verify_tag(int argc, const char **argv, const char *prefix)
{
int i = 1, verbose = 0, had_error = 0;
+ unsigned flags = 0;
+ char *fmt_pretty = NULL;
const struct option verify_tag_options[] = {
OPT__VERBOSE(&verbose, N_("print tag contents")),
+ OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW),
+ OPT_STRING( 0 , "format", &fmt_pretty, N_("format"), N_("format to use for the output")),
OPT_END()
};
@@ -81,11 +46,29 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix)
if (argc <= i)
usage_with_options(verify_tag_usage, verify_tag_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_tag(argv[i++], verbose))
+ if (verbose)
+ flags |= GPG_VERIFY_VERBOSE;
+
+ if (fmt_pretty) {
+ verify_ref_format(fmt_pretty);
+ flags |= GPG_VERIFY_OMIT_STATUS;
+ }
+
+ while (i < argc) {
+ unsigned char sha1[20];
+ const char *name = argv[i++];
+ if (get_sha1(name, sha1)) {
+ had_error = !!error("tag '%s' not found.", name);
+ continue;
+ }
+
+ if (gpg_verify_tag(sha1, name, flags)) {
had_error = 1;
+ continue;
+ }
+
+ if (fmt_pretty)
+ pretty_print_ref(name, sha1, fmt_pretty);
+ }
return had_error;
}
diff --git a/builtin/worktree.c b/builtin/worktree.c
new file mode 100644
index 0000000000..831fe058a5
--- /dev/null
+++ b/builtin/worktree.c
@@ -0,0 +1,550 @@
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "parse-options.h"
+#include "argv-array.h"
+#include "branch.h"
+#include "refs.h"
+#include "run-command.h"
+#include "sigchain.h"
+#include "refs.h"
+#include "utf8.h"
+#include "worktree.h"
+
+static const char * const worktree_usage[] = {
+ N_("git worktree add [<options>] <path> [<branch>]"),
+ N_("git worktree list [<options>]"),
+ N_("git worktree lock [<options>] <path>"),
+ N_("git worktree prune [<options>]"),
+ N_("git worktree unlock <path>"),
+ NULL
+};
+
+struct add_opts {
+ int force;
+ int detach;
+ int checkout;
+ const char *new_branch;
+ int force_new_branch;
+};
+
+static int show_only;
+static int verbose;
+static unsigned long expire;
+
+static int prune_worktree(const char *id, struct strbuf *reason)
+{
+ struct stat st;
+ char *path;
+ int fd, len;
+
+ if (!is_directory(git_path("worktrees/%s", id))) {
+ strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id);
+ return 1;
+ }
+ if (file_exists(git_path("worktrees/%s/locked", id)))
+ return 0;
+ if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id);
+ return 1;
+ }
+ fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
+ if (fd < 0) {
+ strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"),
+ id, strerror(errno));
+ return 1;
+ }
+ len = st.st_size;
+ path = xmallocz(len);
+ read_in_full(fd, path, len);
+ close(fd);
+ while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
+ len--;
+ if (!len) {
+ strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id);
+ free(path);
+ return 1;
+ }
+ path[len] = '\0';
+ if (!file_exists(path)) {
+ struct stat st_link;
+ free(path);
+ /*
+ * the repo is moved manually and has not been
+ * accessed since?
+ */
+ if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
+ st_link.st_nlink > 1)
+ return 0;
+ if (st.st_mtime <= expire) {
+ strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ free(path);
+ return 0;
+}
+
+static void prune_worktrees(void)
+{
+ struct strbuf reason = STRBUF_INIT;
+ struct strbuf path = STRBUF_INIT;
+ DIR *dir = opendir(git_path("worktrees"));
+ struct dirent *d;
+ int ret;
+ if (!dir)
+ return;
+ while ((d = readdir(dir)) != NULL) {
+ if (is_dot_or_dotdot(d->d_name))
+ continue;
+ strbuf_reset(&reason);
+ if (!prune_worktree(d->d_name, &reason))
+ continue;
+ if (show_only || verbose)
+ printf("%s\n", reason.buf);
+ if (show_only)
+ continue;
+ strbuf_reset(&path);
+ strbuf_addstr(&path, git_path("worktrees/%s", d->d_name));
+ ret = remove_dir_recursively(&path, 0);
+ if (ret < 0 && errno == ENOTDIR)
+ ret = unlink(path.buf);
+ if (ret)
+ error_errno(_("failed to remove '%s'"), path.buf);
+ }
+ closedir(dir);
+ if (!show_only)
+ rmdir(git_path("worktrees"));
+ strbuf_release(&reason);
+ strbuf_release(&path);
+}
+
+static int prune(int ac, const char **av, const char *prefix)
+{
+ struct option options[] = {
+ OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
+ OPT__VERBOSE(&verbose, N_("report pruned working trees")),
+ OPT_EXPIRY_DATE(0, "expire", &expire,
+ N_("expire working trees older than <time>")),
+ OPT_END()
+ };
+
+ expire = ULONG_MAX;
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (ac)
+ usage_with_options(worktree_usage, options);
+ prune_worktrees();
+ return 0;
+}
+
+static char *junk_work_tree;
+static char *junk_git_dir;
+static int is_junk;
+static pid_t junk_pid;
+
+static void remove_junk(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ if (!is_junk || getpid() != junk_pid)
+ return;
+ if (junk_git_dir) {
+ strbuf_addstr(&sb, junk_git_dir);
+ remove_dir_recursively(&sb, 0);
+ strbuf_reset(&sb);
+ }
+ if (junk_work_tree) {
+ strbuf_addstr(&sb, junk_work_tree);
+ remove_dir_recursively(&sb, 0);
+ }
+ strbuf_release(&sb);
+}
+
+static void remove_junk_on_signal(int signo)
+{
+ remove_junk();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+static const char *worktree_basename(const char *path, int *olen)
+{
+ const char *name;
+ int len;
+
+ len = strlen(path);
+ while (len && is_dir_sep(path[len - 1]))
+ len--;
+
+ for (name = path + len - 1; name > path; name--)
+ if (is_dir_sep(*name)) {
+ name++;
+ break;
+ }
+
+ *olen = len;
+ return name;
+}
+
+static int add_worktree(const char *path, const char *refname,
+ const struct add_opts *opts)
+{
+ struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ const char *name;
+ struct stat st;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct argv_array child_env = ARGV_ARRAY_INIT;
+ int counter = 0, len, ret;
+ struct strbuf symref = STRBUF_INIT;
+ struct commit *commit = NULL;
+
+ if (file_exists(path) && !is_empty_dir(path))
+ die(_("'%s' already exists"), path);
+
+ /* is 'refname' a branch or commit? */
+ if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
+ ref_exists(symref.buf)) { /* it's a branch */
+ if (!opts->force)
+ die_if_checked_out(symref.buf, 0);
+ } else { /* must be a commit */
+ commit = lookup_commit_reference_by_name(refname);
+ if (!commit)
+ die(_("invalid reference: %s"), refname);
+ }
+
+ name = worktree_basename(path, &len);
+ strbuf_addstr(&sb_repo,
+ git_path("worktrees/%.*s", (int)(path + len - name), name));
+ len = sb_repo.len;
+ if (safe_create_leading_directories_const(sb_repo.buf))
+ die_errno(_("could not create leading directories of '%s'"),
+ sb_repo.buf);
+ while (!stat(sb_repo.buf, &st)) {
+ counter++;
+ strbuf_setlen(&sb_repo, len);
+ strbuf_addf(&sb_repo, "%d", counter);
+ }
+ name = strrchr(sb_repo.buf, '/') + 1;
+
+ junk_pid = getpid();
+ atexit(remove_junk);
+ sigchain_push_common(remove_junk_on_signal);
+
+ if (mkdir(sb_repo.buf, 0777))
+ die_errno(_("could not create directory of '%s'"), sb_repo.buf);
+ junk_git_dir = xstrdup(sb_repo.buf);
+ is_junk = 1;
+
+ /*
+ * lock the incomplete repo so prune won't delete it, unlock
+ * after the preparation is over.
+ */
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ write_file(sb.buf, "initializing");
+
+ strbuf_addf(&sb_git, "%s/.git", path);
+ if (safe_create_leading_directories_const(sb_git.buf))
+ die_errno(_("could not create leading directories of '%s'"),
+ sb_git.buf);
+ junk_work_tree = xstrdup(path);
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+ write_file(sb.buf, "%s", real_path(sb_git.buf));
+ write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
+ real_path(get_git_common_dir()), name);
+ /*
+ * This is to keep resolve_ref() happy. We need a valid HEAD
+ * or is_git_directory() will reject the directory. Any value which
+ * looks like an object ID will do since it will be immediately
+ * replaced by the symbolic-ref or update-ref invocation in the new
+ * worktree.
+ */
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
+ write_file(sb.buf, "%s", sha1_to_hex(null_sha1));
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
+ write_file(sb.buf, "../..");
+
+ fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name);
+
+ argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
+ argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
+ cp.git_cmd = 1;
+
+ if (commit)
+ argv_array_pushl(&cp.args, "update-ref", "HEAD",
+ oid_to_hex(&commit->object.oid), NULL);
+ else
+ argv_array_pushl(&cp.args, "symbolic-ref", "HEAD",
+ symref.buf, NULL);
+ cp.env = child_env.argv;
+ ret = run_command(&cp);
+ if (ret)
+ goto done;
+
+ if (opts->checkout) {
+ cp.argv = NULL;
+ argv_array_clear(&cp.args);
+ argv_array_pushl(&cp.args, "reset", "--hard", NULL);
+ cp.env = child_env.argv;
+ ret = run_command(&cp);
+ if (ret)
+ goto done;
+ }
+
+ is_junk = 0;
+ free(junk_work_tree);
+ free(junk_git_dir);
+ junk_work_tree = NULL;
+ junk_git_dir = NULL;
+
+done:
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+ unlink_or_warn(sb.buf);
+ argv_array_clear(&child_env);
+ strbuf_release(&sb);
+ strbuf_release(&symref);
+ strbuf_release(&sb_repo);
+ strbuf_release(&sb_git);
+ return ret;
+}
+
+static int add(int ac, const char **av, const char *prefix)
+{
+ struct add_opts opts;
+ const char *new_branch_force = NULL;
+ const char *path, *branch;
+ struct option options[] = {
+ OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
+ OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+ N_("create a new branch")),
+ OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
+ N_("create or reset a branch")),
+ OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
+ OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")),
+ OPT_END()
+ };
+
+ memset(&opts, 0, sizeof(opts));
+ opts.checkout = 1;
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1)
+ die(_("-b, -B, and --detach are mutually exclusive"));
+ if (ac < 1 || ac > 2)
+ usage_with_options(worktree_usage, options);
+
+ path = prefix_filename(prefix, strlen(prefix), av[0]);
+ branch = ac < 2 ? "HEAD" : av[1];
+
+ if (!strcmp(branch, "-"))
+ branch = "@{-1}";
+
+ opts.force_new_branch = !!new_branch_force;
+ if (opts.force_new_branch) {
+ struct strbuf symref = STRBUF_INIT;
+
+ opts.new_branch = new_branch_force;
+
+ if (!opts.force &&
+ !strbuf_check_branch_ref(&symref, opts.new_branch) &&
+ ref_exists(symref.buf))
+ die_if_checked_out(symref.buf, 0);
+ strbuf_release(&symref);
+ }
+
+ if (ac < 2 && !opts.new_branch && !opts.detach) {
+ int n;
+ const char *s = worktree_basename(path, &n);
+ opts.new_branch = xstrndup(s, n);
+ }
+
+ if (opts.new_branch) {
+ struct child_process cp = CHILD_PROCESS_INIT;
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "branch");
+ if (opts.force_new_branch)
+ argv_array_push(&cp.args, "--force");
+ argv_array_push(&cp.args, opts.new_branch);
+ argv_array_push(&cp.args, branch);
+ if (run_command(&cp))
+ return -1;
+ branch = opts.new_branch;
+ }
+
+ return add_worktree(path, branch, &opts);
+}
+
+static void show_worktree_porcelain(struct worktree *wt)
+{
+ printf("worktree %s\n", wt->path);
+ if (wt->is_bare)
+ printf("bare\n");
+ else {
+ printf("HEAD %s\n", sha1_to_hex(wt->head_sha1));
+ if (wt->is_detached)
+ printf("detached\n");
+ else if (wt->head_ref)
+ printf("branch %s\n", wt->head_ref);
+ }
+ printf("\n");
+}
+
+static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int cur_path_len = strlen(wt->path);
+ int path_adj = cur_path_len - utf8_strwidth(wt->path);
+
+ strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
+ if (wt->is_bare)
+ strbuf_addstr(&sb, "(bare)");
+ else {
+ strbuf_addf(&sb, "%-*s ", abbrev_len,
+ find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV));
+ if (wt->is_detached)
+ strbuf_addstr(&sb, "(detached HEAD)");
+ else if (wt->head_ref)
+ strbuf_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0));
+ else
+ strbuf_addstr(&sb, "(error)");
+ }
+ printf("%s\n", sb.buf);
+
+ strbuf_release(&sb);
+}
+
+static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
+{
+ int i;
+
+ for (i = 0; wt[i]; i++) {
+ int sha1_len;
+ int path_len = strlen(wt[i]->path);
+
+ if (path_len > *maxlen)
+ *maxlen = path_len;
+ sha1_len = strlen(find_unique_abbrev(wt[i]->head_sha1, *abbrev));
+ if (sha1_len > *abbrev)
+ *abbrev = sha1_len;
+ }
+}
+
+static int list(int ac, const char **av, const char *prefix)
+{
+ int porcelain = 0;
+
+ struct option options[] = {
+ OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
+ OPT_END()
+ };
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (ac)
+ usage_with_options(worktree_usage, options);
+ else {
+ struct worktree **worktrees = get_worktrees(GWT_SORT_LINKED);
+ int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
+
+ if (!porcelain)
+ measure_widths(worktrees, &abbrev, &path_maxlen);
+
+ for (i = 0; worktrees[i]; i++) {
+ if (porcelain)
+ show_worktree_porcelain(worktrees[i]);
+ else
+ show_worktree(worktrees[i], path_maxlen, abbrev);
+ }
+ free_worktrees(worktrees);
+ }
+ return 0;
+}
+
+static int lock_worktree(int ac, const char **av, const char *prefix)
+{
+ const char *reason = "", *old_reason;
+ struct option options[] = {
+ OPT_STRING(0, "reason", &reason, N_("string"),
+ N_("reason for locking")),
+ OPT_END()
+ };
+ struct worktree **worktrees, *wt;
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (ac != 1)
+ usage_with_options(worktree_usage, options);
+
+ worktrees = get_worktrees(0);
+ wt = find_worktree(worktrees, prefix, av[0]);
+ if (!wt)
+ die(_("'%s' is not a working tree"), av[0]);
+ if (is_main_worktree(wt))
+ die(_("The main working tree cannot be locked or unlocked"));
+
+ old_reason = is_worktree_locked(wt);
+ if (old_reason) {
+ if (*old_reason)
+ die(_("'%s' is already locked, reason: %s"),
+ av[0], old_reason);
+ die(_("'%s' is already locked"), av[0]);
+ }
+
+ write_file(git_common_path("worktrees/%s/locked", wt->id),
+ "%s", reason);
+ free_worktrees(worktrees);
+ return 0;
+}
+
+static int unlock_worktree(int ac, const char **av, const char *prefix)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+ struct worktree **worktrees, *wt;
+ int ret;
+
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (ac != 1)
+ usage_with_options(worktree_usage, options);
+
+ worktrees = get_worktrees(0);
+ wt = find_worktree(worktrees, prefix, av[0]);
+ if (!wt)
+ die(_("'%s' is not a working tree"), av[0]);
+ if (is_main_worktree(wt))
+ die(_("The main working tree cannot be locked or unlocked"));
+ if (!is_worktree_locked(wt))
+ die(_("'%s' is not locked"), av[0]);
+ ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
+ free_worktrees(worktrees);
+ return ret;
+}
+
+int cmd_worktree(int ac, const char **av, const char *prefix)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+
+ if (ac < 2)
+ usage_with_options(worktree_usage, options);
+ if (!prefix)
+ prefix = "";
+ if (!strcmp(av[1], "add"))
+ return add(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "prune"))
+ return prune(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "list"))
+ return list(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "lock"))
+ return lock_worktree(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "unlock"))
+ return unlock_worktree(ac - 1, av + 1, prefix);
+ usage_with_options(worktree_usage, options);
+}