summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/add.c1
-rw-r--r--builtin/apply.c17
-rw-r--r--builtin/cat-file.c210
-rw-r--r--builtin/check-mailmap.c66
-rw-r--r--builtin/checkout.c19
-rw-r--r--builtin/clean.c780
-rw-r--r--builtin/clone.c28
-rw-r--r--builtin/commit.c85
-rw-r--r--builtin/config.c31
-rw-r--r--builtin/grep.c7
-rw-r--r--builtin/log.c26
-rw-r--r--builtin/ls-files.c40
-rw-r--r--builtin/merge-index.c4
-rw-r--r--builtin/merge.c61
-rw-r--r--builtin/push.c55
-rw-r--r--builtin/reset.c5
-rw-r--r--builtin/rev-list.c1
-rw-r--r--builtin/revert.c2
-rw-r--r--builtin/rm.c6
-rw-r--r--builtin/shortlog.c1
-rw-r--r--builtin/show-branch.c16
-rw-r--r--builtin/show-ref.c8
-rw-r--r--builtin/update-index.c14
23 files changed, 1277 insertions, 206 deletions
diff --git a/builtin/add.c b/builtin/add.c
index f45d9d4865..8266a9cb70 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -343,6 +343,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix)
argc = setup_revisions(argc, argv, &rev, NULL);
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev.diffopt.use_color = 0;
DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
out = open(file, O_CREAT | O_WRONLY, 0666);
if (out < 0)
diff --git a/builtin/apply.c b/builtin/apply.c
index 0e9b631db6..64310cd678 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -722,7 +722,7 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
static char *find_name_traditional(const char *line, char *def, int p_value)
{
- size_t len = strlen(line);
+ size_t len;
size_t date_len;
if (*line == '"') {
@@ -2999,7 +2999,7 @@ static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsig
return 0;
}
-static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
+static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf)
{
if (!ce)
return 0;
@@ -3117,7 +3117,7 @@ static struct patch *previous_patch(struct patch *patch, int *gone)
return previous;
}
-static int verify_index_match(struct cache_entry *ce, struct stat *st)
+static int verify_index_match(const struct cache_entry *ce, struct stat *st)
{
if (S_ISGITLINK(ce->ce_mode)) {
if (!S_ISDIR(st->st_mode))
@@ -3130,7 +3130,7 @@ static int verify_index_match(struct cache_entry *ce, struct stat *st)
#define SUBMODULE_PATCH_WITHOUT_INDEX 1
static int load_patch_target(struct strbuf *buf,
- struct cache_entry *ce,
+ const struct cache_entry *ce,
struct stat *st,
const char *name,
unsigned expected_mode)
@@ -3160,7 +3160,8 @@ static int load_patch_target(struct strbuf *buf,
* we read from the result of a previous diff.
*/
static int load_preimage(struct image *image,
- struct patch *patch, struct stat *st, struct cache_entry *ce)
+ struct patch *patch, struct stat *st,
+ const struct cache_entry *ce)
{
struct strbuf buf = STRBUF_INIT;
size_t len;
@@ -3273,7 +3274,7 @@ static int load_current(struct image *image, struct patch *patch)
}
static int try_threeway(struct image *image, struct patch *patch,
- struct stat *st, struct cache_entry *ce)
+ struct stat *st, const struct cache_entry *ce)
{
unsigned char pre_sha1[20], post_sha1[20], our_sha1[20];
struct strbuf buf = STRBUF_INIT;
@@ -3343,7 +3344,7 @@ static int try_threeway(struct image *image, struct patch *patch,
return 0;
}
-static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
+static int apply_data(struct patch *patch, struct stat *st, const struct cache_entry *ce)
{
struct image image;
@@ -3847,7 +3848,7 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
const char *s = buf;
if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
- die(_("corrupt patch for subproject %s"), path);
+ die(_("corrupt patch for submodule %s"), path);
} else {
if (!cached) {
if (lstat(path, &st) < 0)
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 045cee7bce..0e64b4159c 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -13,9 +13,6 @@
#include "userdiff.h"
#include "streaming.h"
-#define BATCH 1
-#define BATCH_CHECK 2
-
static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
{
unsigned char sha1[20];
@@ -117,54 +114,174 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
return 0;
}
-static int batch_one_object(const char *obj_name, int print_contents)
-{
+struct expand_data {
unsigned char sha1[20];
- enum object_type type = 0;
+ enum object_type type;
unsigned long size;
- void *contents = NULL;
+ unsigned long disk_size;
+ const char *rest;
+
+ /*
+ * If mark_query is true, we do not expand anything, but rather
+ * just mark the object_info with items we wish to query.
+ */
+ int mark_query;
+
+ /*
+ * After a mark_query run, this object_info is set up to be
+ * passed to sha1_object_info_extended. It will point to the data
+ * elements above, so you can retrieve the response from there.
+ */
+ struct object_info info;
+};
+
+static int is_atom(const char *atom, const char *s, int slen)
+{
+ int alen = strlen(atom);
+ return alen == slen && !memcmp(atom, s, alen);
+}
+
+static void expand_atom(struct strbuf *sb, const char *atom, int len,
+ void *vdata)
+{
+ struct expand_data *data = vdata;
+
+ if (is_atom("objectname", atom, len)) {
+ if (!data->mark_query)
+ strbuf_addstr(sb, sha1_to_hex(data->sha1));
+ } else if (is_atom("objecttype", atom, len)) {
+ if (!data->mark_query)
+ strbuf_addstr(sb, typename(data->type));
+ } else if (is_atom("objectsize", atom, len)) {
+ if (data->mark_query)
+ data->info.sizep = &data->size;
+ else
+ strbuf_addf(sb, "%lu", data->size);
+ } else if (is_atom("objectsize:disk", atom, len)) {
+ if (data->mark_query)
+ data->info.disk_sizep = &data->disk_size;
+ else
+ strbuf_addf(sb, "%lu", data->disk_size);
+ } else if (is_atom("rest", atom, len)) {
+ if (!data->mark_query && data->rest)
+ strbuf_addstr(sb, data->rest);
+ } else
+ die("unknown format element: %.*s", len, atom);
+}
+
+static size_t expand_format(struct strbuf *sb, const char *start, void *data)
+{
+ const char *end;
+
+ if (*start != '(')
+ return 0;
+ end = strchr(start + 1, ')');
+ if (!end)
+ die("format element '%s' does not end in ')'", start);
+
+ expand_atom(sb, start + 1, end - start - 1, data);
+
+ return end - start + 1;
+}
+
+static void print_object_or_die(int fd, const unsigned char *sha1,
+ enum object_type type, unsigned long size)
+{
+ if (type == OBJ_BLOB) {
+ if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0)
+ die("unable to stream %s to stdout", sha1_to_hex(sha1));
+ }
+ else {
+ enum object_type rtype;
+ unsigned long rsize;
+ void *contents;
+
+ contents = read_sha1_file(sha1, &rtype, &rsize);
+ if (!contents)
+ die("object %s disappeared", sha1_to_hex(sha1));
+ if (rtype != type)
+ die("object %s changed type!?", sha1_to_hex(sha1));
+ if (rsize != size)
+ die("object %s change size!?", sha1_to_hex(sha1));
+
+ write_or_die(fd, contents, size);
+ free(contents);
+ }
+}
+
+struct batch_options {
+ int enabled;
+ int print_contents;
+ const char *format;
+};
+
+static int batch_one_object(const char *obj_name, struct batch_options *opt,
+ struct expand_data *data)
+{
+ struct strbuf buf = STRBUF_INIT;
if (!obj_name)
return 1;
- if (get_sha1(obj_name, sha1)) {
+ if (get_sha1(obj_name, data->sha1)) {
printf("%s missing\n", obj_name);
fflush(stdout);
return 0;
}
- if (print_contents == BATCH)
- contents = read_sha1_file(sha1, &type, &size);
- else
- type = sha1_object_info(sha1, &size);
-
- if (type <= 0) {
+ data->type = sha1_object_info_extended(data->sha1, &data->info);
+ if (data->type <= 0) {
printf("%s missing\n", obj_name);
fflush(stdout);
- if (print_contents == BATCH)
- free(contents);
return 0;
}
- printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
- fflush(stdout);
+ strbuf_expand(&buf, opt->format, expand_format, data);
+ strbuf_addch(&buf, '\n');
+ write_or_die(1, buf.buf, buf.len);
+ strbuf_release(&buf);
- if (print_contents == BATCH) {
- write_or_die(1, contents, size);
- printf("\n");
- fflush(stdout);
- free(contents);
+ if (opt->print_contents) {
+ print_object_or_die(1, data->sha1, data->type, data->size);
+ write_or_die(1, "\n", 1);
}
-
return 0;
}
-static int batch_objects(int print_contents)
+static int batch_objects(struct batch_options *opt)
{
struct strbuf buf = STRBUF_INIT;
+ struct expand_data data;
+
+ if (!opt->format)
+ opt->format = "%(objectname) %(objecttype) %(objectsize)";
+
+ /*
+ * Expand once with our special mark_query flag, which will prime the
+ * object_info to be handed to sha1_object_info_extended for each
+ * object.
+ */
+ memset(&data, 0, sizeof(data));
+ data.mark_query = 1;
+ strbuf_expand(&buf, opt->format, expand_format, &data);
+ data.mark_query = 0;
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
- int error = batch_one_object(buf.buf, print_contents);
+ char *p;
+ int error;
+
+ /*
+ * Split at first whitespace, tying off the beginning of the
+ * string and saving the remainder (or NULL) in data.rest.
+ */
+ p = strpbrk(buf.buf, " \t");
+ if (p) {
+ while (*p && strchr(" \t", *p))
+ *p++ = '\0';
+ }
+ data.rest = p;
+
+ error = batch_one_object(buf.buf, opt, &data);
if (error)
return error;
}
@@ -186,10 +303,29 @@ static int git_cat_file_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
+static int batch_option_callback(const struct option *opt,
+ const char *arg,
+ int unset)
+{
+ struct batch_options *bo = opt->value;
+
+ if (unset) {
+ memset(bo, 0, sizeof(*bo));
+ return 0;
+ }
+
+ bo->enabled = 1;
+ bo->print_contents = !strcmp(opt->long_name, "batch");
+ bo->format = arg;
+
+ return 0;
+}
+
int cmd_cat_file(int argc, const char **argv, const char *prefix)
{
- int opt = 0, batch = 0;
+ int opt = 0;
const char *exp_type = NULL, *obj_name = NULL;
+ struct batch_options batch = {0};
const struct option options[] = {
OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
@@ -200,12 +336,12 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
OPT_SET_INT(0, "textconv", &opt,
N_("for blob objects, run textconv on object's content"), 'c'),
- OPT_SET_INT(0, "batch", &batch,
- N_("show info and content of objects fed from the standard input"),
- BATCH),
- OPT_SET_INT(0, "batch-check", &batch,
- N_("show info about objects fed from the standard input"),
- BATCH_CHECK),
+ { OPTION_CALLBACK, 0, "batch", &batch, "format",
+ N_("show info and content of objects fed from the standard input"),
+ PARSE_OPT_OPTARG, batch_option_callback },
+ { OPTION_CALLBACK, 0, "batch-check", &batch, "format",
+ N_("show info about objects fed from the standard input"),
+ PARSE_OPT_OPTARG, batch_option_callback },
OPT_END()
};
@@ -222,19 +358,19 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)
else
usage_with_options(cat_file_usage, options);
}
- if (!opt && !batch) {
+ if (!opt && !batch.enabled) {
if (argc == 2) {
exp_type = argv[0];
obj_name = argv[1];
} else
usage_with_options(cat_file_usage, options);
}
- if (batch && (opt || argc)) {
+ if (batch.enabled && (opt || argc)) {
usage_with_options(cat_file_usage, options);
}
- if (batch)
- return batch_objects(batch);
+ if (batch.enabled)
+ return batch_objects(&batch);
return cat_one_file(opt, exp_type, obj_name);
}
diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c
new file mode 100644
index 0000000000..8f4d809bd8
--- /dev/null
+++ b/builtin/check-mailmap.c
@@ -0,0 +1,66 @@
+#include "builtin.h"
+#include "mailmap.h"
+#include "parse-options.h"
+#include "string-list.h"
+
+static int use_stdin;
+static const char * const check_mailmap_usage[] = {
+N_("git check-mailmap [options] <contact>..."),
+NULL
+};
+
+static const struct option check_mailmap_options[] = {
+ OPT_BOOL(0, "stdin", &use_stdin, N_("also read contacts from stdin")),
+ OPT_END()
+};
+
+static void check_mailmap(struct string_list *mailmap, const char *contact)
+{
+ const char *name, *mail;
+ size_t namelen, maillen;
+ struct ident_split ident;
+
+ if (split_ident_line(&ident, contact, strlen(contact)))
+ die(_("unable to parse contact: %s"), contact);
+
+ name = ident.name_begin;
+ namelen = ident.name_end - ident.name_begin;
+ mail = ident.mail_begin;
+ maillen = ident.mail_end - ident.mail_begin;
+
+ map_user(mailmap, &mail, &maillen, &name, &namelen);
+
+ if (namelen)
+ printf("%.*s ", (int)namelen, name);
+ printf("<%.*s>\n", (int)maillen, mail);
+}
+
+int cmd_check_mailmap(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ struct string_list mailmap = STRING_LIST_INIT_NODUP;
+
+ git_config(git_default_config, NULL);
+ argc = parse_options(argc, argv, prefix, check_mailmap_options,
+ check_mailmap_usage, 0);
+ if (argc == 0 && !use_stdin)
+ die(_("no contacts specified"));
+
+ read_mailmap(&mailmap, NULL);
+
+ for (i = 0; i < argc; ++i)
+ check_mailmap(&mailmap, argv[i]);
+ maybe_flush_or_die(stdout, "stdout");
+
+ if (use_stdin) {
+ struct strbuf buf = STRBUF_INIT;
+ while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+ check_mailmap(&mailmap, buf.buf);
+ maybe_flush_or_die(stdout, "stdout");
+ }
+ strbuf_release(&buf);
+ }
+
+ clear_mailmap(&mailmap);
+ return 0;
+}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3be0018821..7025938ae3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -97,7 +97,7 @@ static int read_tree_some(struct tree *tree, const char **pathspec)
return 0;
}
-static int skip_same_name(struct cache_entry *ce, int pos)
+static int skip_same_name(const struct cache_entry *ce, int pos)
{
while (++pos < active_nr &&
!strcmp(active_cache[pos]->name, ce->name))
@@ -105,7 +105,7 @@ static int skip_same_name(struct cache_entry *ce, int pos)
return pos;
}
-static int check_stage(int stage, struct cache_entry *ce, int pos)
+static int check_stage(int stage, const struct cache_entry *ce, int pos)
{
while (pos < active_nr &&
!strcmp(active_cache[pos]->name, ce->name)) {
@@ -119,7 +119,7 @@ static int check_stage(int stage, struct cache_entry *ce, int pos)
return error(_("path '%s' does not have their version"), ce->name);
}
-static int check_stages(unsigned stages, struct cache_entry *ce, int pos)
+static int check_stages(unsigned stages, const struct cache_entry *ce, int pos)
{
unsigned seen = 0;
const char *name = ce->name;
@@ -321,7 +321,7 @@ static int checkout_paths(const struct checkout_opts *opts,
/* Any unmerged paths? */
for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (ce->ce_flags & CE_MATCHED) {
if (!ce_stage(ce))
continue;
@@ -587,7 +587,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
struct branch_info *new)
{
struct strbuf msg = STRBUF_INIT;
- const char *old_desc;
+ const char *old_desc, *reflog_msg;
if (opts->new_branch) {
if (opts->new_orphan_branch) {
if (opts->new_branch_log && !log_all_ref_updates) {
@@ -620,8 +620,13 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
old_desc = old->name;
if (!old_desc && old->commit)
old_desc = sha1_to_hex(old->commit->object.sha1);
- strbuf_addf(&msg, "checkout: moving from %s to %s",
- old_desc ? old_desc : "(invalid)", new->name);
+
+ reflog_msg = getenv("GIT_REFLOG_ACTION");
+ if (!reflog_msg)
+ strbuf_addf(&msg, "checkout: moving from %s to %s",
+ old_desc ? old_desc : "(invalid)", new->name);
+ else
+ strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg));
if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
/* Nothing to do. */
diff --git a/builtin/clean.c b/builtin/clean.c
index 04e396b17a..dba8387747 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -13,11 +13,16 @@
#include "refs.h"
#include "string-list.h"
#include "quote.h"
+#include "column.h"
+#include "color.h"
static int force = -1; /* unset */
+static int interactive;
+static struct string_list del_list = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
static const char *const builtin_clean_usage[] = {
- N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
+ N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
NULL
};
@@ -27,11 +32,112 @@ static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
static const char *msg_warn_remove_failed = N_("failed to remove %s");
+static int clean_use_color = -1;
+static char clean_colors[][COLOR_MAXLEN] = {
+ GIT_COLOR_RESET,
+ GIT_COLOR_NORMAL, /* PLAIN */
+ GIT_COLOR_BOLD_BLUE, /* PROMPT */
+ GIT_COLOR_BOLD, /* HEADER */
+ GIT_COLOR_BOLD_RED, /* HELP */
+ GIT_COLOR_BOLD_RED, /* ERROR */
+};
+enum color_clean {
+ CLEAN_COLOR_RESET = 0,
+ CLEAN_COLOR_PLAIN = 1,
+ CLEAN_COLOR_PROMPT = 2,
+ CLEAN_COLOR_HEADER = 3,
+ CLEAN_COLOR_HELP = 4,
+ CLEAN_COLOR_ERROR = 5,
+};
+
+#define MENU_OPTS_SINGLETON 01
+#define MENU_OPTS_IMMEDIATE 02
+#define MENU_OPTS_LIST_ONLY 04
+
+struct menu_opts {
+ const char *header;
+ const char *prompt;
+ int flags;
+};
+
+#define MENU_RETURN_NO_LOOP 10
+
+struct menu_item {
+ char hotkey;
+ const char *title;
+ int selected;
+ int (*fn)();
+};
+
+enum menu_stuff_type {
+ MENU_STUFF_TYPE_STRING_LIST = 1,
+ MENU_STUFF_TYPE_MENU_ITEM
+};
+
+struct menu_stuff {
+ enum menu_stuff_type type;
+ int nr;
+ void *stuff;
+};
+
+static int parse_clean_color_slot(const char *var)
+{
+ if (!strcasecmp(var, "reset"))
+ return CLEAN_COLOR_RESET;
+ if (!strcasecmp(var, "plain"))
+ return CLEAN_COLOR_PLAIN;
+ if (!strcasecmp(var, "prompt"))
+ return CLEAN_COLOR_PROMPT;
+ if (!strcasecmp(var, "header"))
+ return CLEAN_COLOR_HEADER;
+ if (!strcasecmp(var, "help"))
+ return CLEAN_COLOR_HELP;
+ if (!strcasecmp(var, "error"))
+ return CLEAN_COLOR_ERROR;
+ return -1;
+}
+
static int git_clean_config(const char *var, const char *value, void *cb)
{
- if (!strcmp(var, "clean.requireforce"))
+ if (!prefixcmp(var, "column."))
+ return git_column_config(var, value, "clean", &colopts);
+
+ /* honors the color.interactive* config variables which also
+ applied in git-add--interactive and git-stash */
+ if (!strcmp(var, "color.interactive")) {
+ clean_use_color = git_config_colorbool(var, value);
+ return 0;
+ }
+ if (!prefixcmp(var, "color.interactive.")) {
+ int slot = parse_clean_color_slot(var +
+ strlen("color.interactive."));
+ if (slot < 0)
+ return 0;
+ if (!value)
+ return config_error_nonbool(var);
+ color_parse(value, var, clean_colors[slot]);
+ return 0;
+ }
+
+ if (!strcmp(var, "clean.requireforce")) {
force = !git_config_bool(var, value);
- return git_default_config(var, value, cb);
+ return 0;
+ }
+
+ /* inspect the color.ui config variable and others */
+ return git_color_default_config(var, value, cb);
+}
+
+static const char *clean_get_color(enum color_clean ix)
+{
+ if (want_color(clean_use_color))
+ return clean_colors[ix];
+ return "";
+}
+
+static void clean_print_color(enum color_clean ix)
+{
+ printf("%s", clean_get_color(ix));
}
static int exclude_cb(const struct option *opt, const char *arg, int unset)
@@ -56,7 +162,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
if (!quiet) {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
quoted.buf);
}
@@ -70,7 +176,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
/* an empty dir could be removed even if it is unreadble */
res = dry_run ? 0 : rmdir(path->buf);
if (res) {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
warning(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
}
@@ -94,7 +200,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
ret = 1;
if (gone) {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
string_list_append(&dels, quoted.buf);
} else
*dir_gone = 0;
@@ -102,10 +208,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
} else {
res = dry_run ? 0 : unlink(path->buf);
if (!res) {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
string_list_append(&dels, quoted.buf);
} else {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
warning(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = 1;
@@ -127,7 +233,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
if (!res)
*dir_gone = 1;
else {
- quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+ quote_path_relative(path->buf, prefix, &quoted);
warning(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
ret = 1;
@@ -142,24 +248,609 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
return ret;
}
+static void pretty_print_dels(void)
+{
+ struct string_list list = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ struct strbuf buf = STRBUF_INIT;
+ const char *qname;
+ struct column_options copts;
+
+ for_each_string_list_item(item, &del_list) {
+ qname = quote_path_relative(item->string, NULL, &buf);
+ string_list_append(&list, qname);
+ }
+
+ /*
+ * always enable column display, we only consult column.*
+ * about layout strategy and stuff
+ */
+ colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+ memset(&copts, 0, sizeof(copts));
+ copts.indent = " ";
+ copts.padding = 2;
+ print_columns(&list, colopts, &copts);
+ strbuf_release(&buf);
+ string_list_clear(&list, 0);
+}
+
+static void pretty_print_menus(struct string_list *menu_list)
+{
+ unsigned int local_colopts = 0;
+ struct column_options copts;
+
+ local_colopts = COL_ENABLED | COL_ROW;
+ memset(&copts, 0, sizeof(copts));
+ copts.indent = " ";
+ copts.padding = 2;
+ print_columns(menu_list, local_colopts, &copts);
+}
+
+static void prompt_help_cmd(int singleton)
+{
+ clean_print_color(CLEAN_COLOR_HELP);
+ printf_ln(singleton ?
+ _("Prompt help:\n"
+ "1 - select a numbered item\n"
+ "foo - select item based on unique prefix\n"
+ " - (empty) select nothing") :
+ _("Prompt help:\n"
+ "1 - select a single item\n"
+ "3-5 - select a range of items\n"
+ "2-3,6-9 - select multiple ranges\n"
+ "foo - select item based on unique prefix\n"
+ "-... - unselect specified items\n"
+ "* - choose all items\n"
+ " - (empty) finish selecting"));
+ clean_print_color(CLEAN_COLOR_RESET);
+}
+
+/*
+ * display menu stuff with number prefix and hotkey highlight
+ */
+static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
+{
+ struct string_list menu_list = STRING_LIST_INIT_DUP;
+ struct strbuf menu = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct menu_item *menu_item;
+ struct string_list_item *string_list_item;
+ int i;
+
+ switch (stuff->type) {
+ default:
+ die("Bad type of menu_staff when print menu");
+ case MENU_STUFF_TYPE_MENU_ITEM:
+ menu_item = (struct menu_item *)stuff->stuff;
+ for (i = 0; i < stuff->nr; i++, menu_item++) {
+ const char *p;
+ int highlighted = 0;
+
+ p = menu_item->title;
+ if ((*chosen)[i] < 0)
+ (*chosen)[i] = menu_item->selected ? 1 : 0;
+ strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1);
+ for (; *p; p++) {
+ if (!highlighted && *p == menu_item->hotkey) {
+ strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));
+ strbuf_addch(&menu, *p);
+ strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));
+ highlighted = 1;
+ } else {
+ strbuf_addch(&menu, *p);
+ }
+ }
+ string_list_append(&menu_list, menu.buf);
+ strbuf_reset(&menu);
+ }
+ break;
+ case MENU_STUFF_TYPE_STRING_LIST:
+ i = 0;
+ for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) {
+ if ((*chosen)[i] < 0)
+ (*chosen)[i] = 0;
+ strbuf_addf(&menu, "%s%2d: %s",
+ (*chosen)[i] ? "*" : " ", i+1, string_list_item->string);
+ string_list_append(&menu_list, menu.buf);
+ strbuf_reset(&menu);
+ i++;
+ }
+ break;
+ }
+
+ pretty_print_menus(&menu_list);
+
+ strbuf_release(&menu);
+ strbuf_release(&buf);
+ string_list_clear(&menu_list, 0);
+}
+
+/*
+ * Parse user input, and return choice(s) for menu (menu_stuff).
+ *
+ * Input
+ * (for single choice)
+ * 1 - select a numbered item
+ * foo - select item based on menu title
+ * - (empty) select nothing
+ *
+ * (for multiple choice)
+ * 1 - select a single item
+ * 3-5 - select a range of items
+ * 2-3,6-9 - select multiple ranges
+ * foo - select item based on menu title
+ * -... - unselect specified items
+ * * - choose all items
+ * - (empty) finish selecting
+ *
+ * The parse result will be saved in array **chosen, and
+ * return number of total selections.
+ */
+static int parse_choice(struct menu_stuff *menu_stuff,
+ int is_single,
+ struct strbuf input,
+ int **chosen)
+{
+ struct strbuf **choice_list, **ptr;
+ struct menu_item *menu_item;
+ struct string_list_item *string_list_item;
+ int nr = 0;
+ int i;
+
+ if (is_single) {
+ choice_list = strbuf_split_max(&input, '\n', 0);
+ } else {
+ char *p = input.buf;
+ do {
+ if (*p == ',')
+ *p = ' ';
+ } while (*p++);
+ choice_list = strbuf_split_max(&input, ' ', 0);
+ }
+
+ for (ptr = choice_list; *ptr; ptr++) {
+ char *p;
+ int choose = 1;
+ int bottom = 0, top = 0;
+ int is_range, is_number;
+
+ strbuf_trim(*ptr);
+ if (!(*ptr)->len)
+ continue;
+
+ /* Input that begins with '-'; unchoose */
+ if (*(*ptr)->buf == '-') {
+ choose = 0;
+ strbuf_remove((*ptr), 0, 1);
+ }
+
+ is_range = 0;
+ is_number = 1;
+ for (p = (*ptr)->buf; *p; p++) {
+ if ('-' == *p) {
+ if (!is_range) {
+ is_range = 1;
+ is_number = 0;
+ } else {
+ is_number = 0;
+ is_range = 0;
+ break;
+ }
+ } else if (!isdigit(*p)) {
+ is_number = 0;
+ is_range = 0;
+ break;
+ }
+ }
+
+ if (is_number) {
+ bottom = atoi((*ptr)->buf);
+ top = bottom;
+ } else if (is_range) {
+ bottom = atoi((*ptr)->buf);
+ /* a range can be specified like 5-7 or 5- */
+ if (!*(strchr((*ptr)->buf, '-') + 1))
+ top = menu_stuff->nr;
+ else
+ top = atoi(strchr((*ptr)->buf, '-') + 1);
+ } else if (!strcmp((*ptr)->buf, "*")) {
+ bottom = 1;
+ top = menu_stuff->nr;
+ } else {
+ switch (menu_stuff->type) {
+ default:
+ die("Bad type of menu_stuff when parse choice");
+ case MENU_STUFF_TYPE_MENU_ITEM:
+ menu_item = (struct menu_item *)menu_stuff->stuff;
+ for (i = 0; i < menu_stuff->nr; i++, menu_item++) {
+ if (((*ptr)->len == 1 &&
+ *(*ptr)->buf == menu_item->hotkey) ||
+ !strcasecmp((*ptr)->buf, menu_item->title)) {
+ bottom = i + 1;
+ top = bottom;
+ break;
+ }
+ }
+ break;
+ case MENU_STUFF_TYPE_STRING_LIST:
+ string_list_item = ((struct string_list *)menu_stuff->stuff)->items;
+ for (i = 0; i < menu_stuff->nr; i++, string_list_item++) {
+ if (!strcasecmp((*ptr)->buf, string_list_item->string)) {
+ bottom = i + 1;
+ top = bottom;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||
+ (is_single && bottom != top)) {
+ clean_print_color(CLEAN_COLOR_ERROR);
+ printf_ln(_("Huh (%s)?"), (*ptr)->buf);
+ clean_print_color(CLEAN_COLOR_RESET);
+ continue;
+ }
+
+ for (i = bottom; i <= top; i++)
+ (*chosen)[i-1] = choose;
+ }
+
+ strbuf_list_free(choice_list);
+
+ for (i = 0; i < menu_stuff->nr; i++)
+ nr += (*chosen)[i];
+ return nr;
+}
+
+/*
+ * Implement a git-add-interactive compatible UI, which is borrowed
+ * from git-add--interactive.perl.
+ *
+ * Return value:
+ *
+ * - Return an array of integers
+ * - , and it is up to you to free the allocated memory.
+ * - The array ends with EOF.
+ * - If user pressed CTRL-D (i.e. EOF), no selection returned.
+ */
+static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
+{
+ struct strbuf choice = STRBUF_INIT;
+ int *chosen, *result;
+ int nr = 0;
+ int eof = 0;
+ int i;
+
+ chosen = xmalloc(sizeof(int) * stuff->nr);
+ /* set chosen as uninitialized */
+ for (i = 0; i < stuff->nr; i++)
+ chosen[i] = -1;
+
+ for (;;) {
+ if (opts->header) {
+ printf_ln("%s%s%s",
+ clean_get_color(CLEAN_COLOR_HEADER),
+ _(opts->header),
+ clean_get_color(CLEAN_COLOR_RESET));
+ }
+
+ /* chosen will be initialized by print_highlight_menu_stuff */
+ print_highlight_menu_stuff(stuff, &chosen);
+
+ if (opts->flags & MENU_OPTS_LIST_ONLY)
+ break;
+
+ if (opts->prompt) {
+ printf("%s%s%s%s",
+ clean_get_color(CLEAN_COLOR_PROMPT),
+ _(opts->prompt),
+ opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ",
+ clean_get_color(CLEAN_COLOR_RESET));
+ }
+
+ if (strbuf_getline(&choice, stdin, '\n') != EOF) {
+ strbuf_trim(&choice);
+ } else {
+ eof = 1;
+ break;
+ }
+
+ /* help for prompt */
+ if (!strcmp(choice.buf, "?")) {
+ prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON);
+ continue;
+ }
+
+ /* for a multiple-choice menu, press ENTER (empty) will return back */
+ if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len)
+ break;
+
+ nr = parse_choice(stuff,
+ opts->flags & MENU_OPTS_SINGLETON,
+ choice,
+ &chosen);
+
+ if (opts->flags & MENU_OPTS_SINGLETON) {
+ if (nr)
+ break;
+ } else if (opts->flags & MENU_OPTS_IMMEDIATE) {
+ break;
+ }
+ }
+
+ if (eof) {
+ result = xmalloc(sizeof(int));
+ *result = EOF;
+ } else {
+ int j = 0;
+
+ /*
+ * recalculate nr, if return back from menu directly with
+ * default selections.
+ */
+ if (!nr) {
+ for (i = 0; i < stuff->nr; i++)
+ nr += chosen[i];
+ }
+
+ result = xmalloc(sizeof(int) * (nr + 1));
+ memset(result, 0, sizeof(int) * (nr + 1));
+ for (i = 0; i < stuff->nr && j < nr; i++) {
+ if (chosen[i])
+ result[j++] = i;
+ }
+ result[j] = EOF;
+ }
+
+ free(chosen);
+ strbuf_release(&choice);
+ return result;
+}
+
+static int clean_cmd(void)
+{
+ return MENU_RETURN_NO_LOOP;
+}
+
+static int filter_by_patterns_cmd(void)
+{
+ struct dir_struct dir;
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf **ignore_list;
+ struct string_list_item *item;
+ struct exclude_list *el;
+ int changed = -1, i;
+
+ for (;;) {
+ if (!del_list.nr)
+ break;
+
+ if (changed)
+ pretty_print_dels();
+
+ clean_print_color(CLEAN_COLOR_PROMPT);
+ printf(_("Input ignore patterns>> "));
+ clean_print_color(CLEAN_COLOR_RESET);
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF)
+ strbuf_trim(&confirm);
+ else
+ putchar('\n');
+
+ /* quit filter_by_pattern mode if press ENTER or Ctrl-D */
+ if (!confirm.len)
+ break;
+
+ memset(&dir, 0, sizeof(dir));
+ el = add_exclude_list(&dir, EXC_CMDL, "manual exclude");
+ ignore_list = strbuf_split_max(&confirm, ' ', 0);
+
+ for (i = 0; ignore_list[i]; i++) {
+ strbuf_trim(ignore_list[i]);
+ if (!ignore_list[i]->len)
+ continue;
+
+ add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1));
+ }
+
+ changed = 0;
+ for_each_string_list_item(item, &del_list) {
+ int dtype = DT_UNKNOWN;
+
+ if (is_excluded(&dir, item->string, &dtype)) {
+ *item->string = '\0';
+ changed++;
+ }
+ }
+
+ if (changed) {
+ string_list_remove_empty_items(&del_list, 0);
+ } else {
+ clean_print_color(CLEAN_COLOR_ERROR);
+ printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
+ clean_print_color(CLEAN_COLOR_RESET);
+ }
+
+ strbuf_list_free(ignore_list);
+ clear_directory(&dir);
+ }
+
+ strbuf_release(&confirm);
+ return 0;
+}
+
+static int select_by_numbers_cmd(void)
+{
+ struct menu_opts menu_opts;
+ struct menu_stuff menu_stuff;
+ struct string_list_item *items;
+ int *chosen;
+ int i, j;
+
+ menu_opts.header = NULL;
+ menu_opts.prompt = N_("Select items to delete");
+ menu_opts.flags = 0;
+
+ menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST;
+ menu_stuff.stuff = &del_list;
+ menu_stuff.nr = del_list.nr;
+
+ chosen = list_and_choose(&menu_opts, &menu_stuff);
+ items = del_list.items;
+ for (i = 0, j = 0; i < del_list.nr; i++) {
+ if (i < chosen[j]) {
+ *(items[i].string) = '\0';
+ } else if (i == chosen[j]) {
+ /* delete selected item */
+ j++;
+ continue;
+ } else {
+ /* end of chosen (chosen[j] == EOF), won't delete */
+ *(items[i].string) = '\0';
+ }
+ }
+
+ string_list_remove_empty_items(&del_list, 0);
+
+ free(chosen);
+ return 0;
+}
+
+static int ask_each_cmd(void)
+{
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list_item *item;
+ const char *qname;
+ int changed = 0, eof = 0;
+
+ for_each_string_list_item(item, &del_list) {
+ /* Ctrl-D should stop removing files */
+ if (!eof) {
+ qname = quote_path_relative(item->string, NULL, &buf);
+ printf(_("remove %s? "), qname);
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+ strbuf_trim(&confirm);
+ } else {
+ putchar('\n');
+ eof = 1;
+ }
+ }
+ if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) {
+ *item->string = '\0';
+ changed++;
+ }
+ }
+
+ if (changed)
+ string_list_remove_empty_items(&del_list, 0);
+
+ strbuf_release(&buf);
+ strbuf_release(&confirm);
+ return MENU_RETURN_NO_LOOP;
+}
+
+static int quit_cmd(void)
+{
+ string_list_clear(&del_list, 0);
+ printf_ln(_("Bye."));
+ return MENU_RETURN_NO_LOOP;
+}
+
+static int help_cmd(void)
+{
+ clean_print_color(CLEAN_COLOR_HELP);
+ printf_ln(_(
+ "clean - start cleaning\n"
+ "filter by pattern - exclude items from deletion\n"
+ "select by numbers - select items to be deleted by numbers\n"
+ "ask each - confirm each deletion (like \"rm -i\")\n"
+ "quit - stop cleaning\n"
+ "help - this screen\n"
+ "? - help for prompt selection"
+ ));
+ clean_print_color(CLEAN_COLOR_RESET);
+ return 0;
+}
+
+static void interactive_main_loop(void)
+{
+ while (del_list.nr) {
+ struct menu_opts menu_opts;
+ struct menu_stuff menu_stuff;
+ struct menu_item menus[] = {
+ {'c', "clean", 0, clean_cmd},
+ {'f', "filter by pattern", 0, filter_by_patterns_cmd},
+ {'s', "select by numbers", 0, select_by_numbers_cmd},
+ {'a', "ask each", 0, ask_each_cmd},
+ {'q', "quit", 0, quit_cmd},
+ {'h', "help", 0, help_cmd},
+ };
+ int *chosen;
+
+ menu_opts.header = N_("*** Commands ***");
+ menu_opts.prompt = N_("What now");
+ menu_opts.flags = MENU_OPTS_SINGLETON;
+
+ menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;
+ menu_stuff.stuff = menus;
+ menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);
+
+ clean_print_color(CLEAN_COLOR_HEADER);
+ printf_ln(Q_("Would remove the following item:",
+ "Would remove the following items:",
+ del_list.nr));
+ clean_print_color(CLEAN_COLOR_RESET);
+
+ pretty_print_dels();
+
+ chosen = list_and_choose(&menu_opts, &menu_stuff);
+
+ if (*chosen != EOF) {
+ int ret;
+ ret = menus[*chosen].fn();
+ if (ret != MENU_RETURN_NO_LOOP) {
+ free(chosen);
+ chosen = NULL;
+ if (!del_list.nr) {
+ clean_print_color(CLEAN_COLOR_ERROR);
+ printf_ln(_("No more files to clean, exiting."));
+ clean_print_color(CLEAN_COLOR_RESET);
+ break;
+ }
+ continue;
+ }
+ } else {
+ quit_cmd();
+ }
+
+ free(chosen);
+ chosen = NULL;
+ break;
+ }
+}
+
int cmd_clean(int argc, const char **argv, const char *prefix)
{
int i, res;
int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
- struct strbuf directory = STRBUF_INIT;
+ struct strbuf abs_path = STRBUF_INIT;
struct dir_struct dir;
static const char **pathspec;
struct strbuf buf = STRBUF_INIT;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
struct exclude_list *el;
+ struct string_list_item *item;
const char *qname;
char *seen = NULL;
struct option options[] = {
OPT__QUIET(&quiet, N_("do not print names of files removed")),
OPT__DRY_RUN(&dry_run, N_("dry run")),
OPT__FORCE(&force, N_("force")),
+ OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
OPT_BOOLEAN('d', NULL, &remove_directories,
N_("remove whole directories")),
{ OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
@@ -186,12 +877,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (ignored && ignored_only)
die(_("-x and -X cannot be used together"));
- if (!dry_run && !force) {
+ if (!interactive && !dry_run && !force) {
if (config_set)
- die(_("clean.requireForce set to true and neither -n nor -f given; "
+ die(_("clean.requireForce set to true and neither -i, -n nor -f given; "
"refusing to clean"));
else
- die(_("clean.requireForce defaults to true and neither -n nor -f given; "
+ die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; "
"refusing to clean"));
}
@@ -221,8 +912,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
struct dir_entry *ent = dir.entries[i];
int len, pos;
int matches = 0;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
struct stat st;
+ const char *rel;
/*
* Remove the '/' at the end that directory
@@ -242,13 +934,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
continue; /* Yup, this one exists unmerged */
}
- /*
- * we might have removed this as part of earlier
- * recursive directory removal, so lstat() here could
- * fail with ENOENT.
- */
if (lstat(ent->name, &st))
- continue;
+ die_errno("Cannot lstat '%s'", ent->name);
if (pathspec) {
memset(seen, 0, argc > 0 ? argc : 1);
@@ -257,33 +944,62 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
}
if (S_ISDIR(st.st_mode)) {
- strbuf_addstr(&directory, ent->name);
if (remove_directories || (matches == MATCHED_EXACTLY)) {
- if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))
- errors++;
- if (gone && !quiet) {
- qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
- printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
- }
+ rel = relative_path(ent->name, prefix, &buf);
+ string_list_append(&del_list, rel);
}
- strbuf_reset(&directory);
} else {
if (pathspec && !matches)
continue;
- res = dry_run ? 0 : unlink(ent->name);
+ rel = relative_path(ent->name, prefix, &buf);
+ string_list_append(&del_list, rel);
+ }
+ }
+
+ if (interactive && del_list.nr > 0)
+ interactive_main_loop();
+
+ for_each_string_list_item(item, &del_list) {
+ struct stat st;
+
+ if (prefix)
+ strbuf_addstr(&abs_path, prefix);
+
+ strbuf_addstr(&abs_path, item->string);
+
+ /*
+ * we might have removed this as part of earlier
+ * recursive directory removal, so lstat() here could
+ * fail with ENOENT.
+ */
+ if (lstat(abs_path.buf, &st))
+ continue;
+
+ if (S_ISDIR(st.st_mode)) {
+ if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
+ errors++;
+ if (gone && !quiet) {
+ qname = quote_path_relative(item->string, NULL, &buf);
+ printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
+ }
+ } else {
+ res = dry_run ? 0 : unlink(abs_path.buf);
if (res) {
- qname = quote_path_relative(ent->name, -1, &buf, prefix);
+ qname = quote_path_relative(item->string, NULL, &buf);
warning(_(msg_warn_remove_failed), qname);
errors++;
} else if (!quiet) {
- qname = quote_path_relative(ent->name, -1, &buf, prefix);
+ qname = quote_path_relative(item->string, NULL, &buf);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
}
+ strbuf_reset(&abs_path);
}
free(seen);
- strbuf_release(&directory);
+ strbuf_release(&abs_path);
+ strbuf_release(&buf);
+ string_list_clear(&del_list, 0);
string_list_clear(&exclude_list, 0);
return (errors != 0);
}
diff --git a/builtin/clone.c b/builtin/clone.c
index 66bff5700f..430307b298 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -493,13 +493,16 @@ static void write_remote_refs(const struct ref *local_refs)
{
const struct ref *r;
+ lock_packed_refs(LOCK_DIE_ON_ERROR);
+
for (r = local_refs; r; r = r->next) {
if (!r->peer_ref)
continue;
add_packed_ref(r->peer_ref->name, r->old_sha1);
}
- pack_refs(PACK_REFS_ALL);
+ if (commit_packed_refs())
+ die_errno("unable to overwrite old ref-pack file");
}
static void write_followtags(const struct ref *refs, const char *msg)
@@ -542,17 +545,20 @@ static void update_remote_refs(const struct ref *refs,
const struct ref *remote_head_points_at,
const char *branch_top,
const char *msg,
- struct transport *transport)
+ struct transport *transport,
+ int check_connectivity)
{
const struct ref *rm = mapped_refs;
- if (0 <= option_verbosity)
- printf(_("Checking connectivity... "));
- if (check_everything_connected_with_transport(iterate_ref_map,
- 0, &rm, transport))
- die(_("remote did not send all necessary objects"));
- if (0 <= option_verbosity)
- printf(_("done\n"));
+ if (check_connectivity) {
+ if (0 <= option_verbosity)
+ printf(_("Checking connectivity... "));
+ if (check_everything_connected_with_transport(iterate_ref_map,
+ 0, &rm, transport))
+ die(_("remote did not send all necessary objects"));
+ if (0 <= option_verbosity)
+ printf(_("done\n"));
+ }
if (refs) {
write_remote_refs(mapped_refs);
@@ -698,7 +704,7 @@ static void write_refspec_config(const char* src_ref_prefix,
/*
* otherwise, the next "git fetch" will
* simply fetch from HEAD without updating
- * any remote tracking branch, which is what
+ * any remote-tracking branch, which is what
* we want.
*/
} else {
@@ -960,7 +966,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport_fetch_refs(transport, mapped_refs);
update_remote_refs(refs, mapped_refs, remote_head_points_at,
- branch_top.buf, reflog_msg.buf, transport);
+ branch_top.buf, reflog_msg.buf, transport, !is_local);
update_head(our_head_points_at, remote_head, reflog_msg.buf);
diff --git a/builtin/commit.c b/builtin/commit.c
index 6b693c16d8..65cf2a79b7 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -112,12 +112,14 @@ static int show_ignored_in_status, have_option_m;
static const char *only_include_assumed;
static struct strbuf message = STRBUF_INIT;
-static enum {
+static enum status_format {
STATUS_FORMAT_NONE = 0,
STATUS_FORMAT_LONG,
STATUS_FORMAT_SHORT,
- STATUS_FORMAT_PORCELAIN
-} status_format;
+ STATUS_FORMAT_PORCELAIN,
+
+ STATUS_FORMAT_UNSPECIFIED
+} status_format = STATUS_FORMAT_UNSPECIFIED;
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
{
@@ -205,7 +207,7 @@ static int list_paths(struct string_list *list, const char *with_tree,
}
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
struct string_list_item *item;
if (ce->ce_flags & CE_UPDATE)
@@ -460,6 +462,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
case STATUS_FORMAT_PORCELAIN:
wt_porcelain_print(s);
break;
+ case STATUS_FORMAT_UNSPECIFIED:
+ die("BUG: finalize_deferred_config() should have been called");
+ break;
case STATUS_FORMAT_NONE:
case STATUS_FORMAT_LONG:
wt_status_print(s);
@@ -961,6 +966,42 @@ static const char *read_commit_message(const char *name)
return logmsg_reencode(commit, NULL, out_enc);
}
+/*
+ * Enumerate what needs to be propagated when --porcelain
+ * is not in effect here.
+ */
+static struct status_deferred_config {
+ enum status_format status_format;
+ int show_branch;
+} status_deferred_config = {
+ STATUS_FORMAT_UNSPECIFIED,
+ -1 /* unspecified */
+};
+
+static void finalize_deferred_config(struct wt_status *s)
+{
+ int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN &&
+ !s->null_termination);
+
+ if (s->null_termination) {
+ if (status_format == STATUS_FORMAT_NONE ||
+ status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = STATUS_FORMAT_PORCELAIN;
+ else if (status_format == STATUS_FORMAT_LONG)
+ die(_("--long and -z are incompatible"));
+ }
+
+ if (use_deferred_config && status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = status_deferred_config.status_format;
+ if (status_format == STATUS_FORMAT_UNSPECIFIED)
+ status_format = STATUS_FORMAT_NONE;
+
+ if (use_deferred_config && s->show_branch < 0)
+ s->show_branch = status_deferred_config.show_branch;
+ if (s->show_branch < 0)
+ s->show_branch = 0;
+}
+
static int parse_and_validate_options(int argc, const char *argv[],
const struct option *options,
const char * const usage[],
@@ -971,6 +1012,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
int f = 0;
argc = parse_options(argc, argv, prefix, options, usage, 0);
+ finalize_deferred_config(s);
if (force_author && !strchr(force_author, '>'))
force_author = find_author_by_nickname(force_author);
@@ -1055,12 +1097,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (all && argc > 0)
die(_("Paths with -a does not make sense."));
- if (s->null_termination) {
- if (status_format == STATUS_FORMAT_NONE)
- status_format = STATUS_FORMAT_PORCELAIN;
- else if (status_format == STATUS_FORMAT_LONG)
- die(_("--long and -z are incompatible"));
- }
if (status_format != STATUS_FORMAT_NONE)
dry_run = 1;
@@ -1113,6 +1149,17 @@ static int git_status_config(const char *k, const char *v, void *cb)
s->submodule_summary = -1;
return 0;
}
+ if (!strcmp(k, "status.short")) {
+ if (git_config_bool(k, v))
+ status_deferred_config.status_format = STATUS_FORMAT_SHORT;
+ else
+ status_deferred_config.status_format = STATUS_FORMAT_NONE;
+ return 0;
+ }
+ if (!strcmp(k, "status.branch")) {
+ status_deferred_config.show_branch = git_config_bool(k, v);
+ return 0;
+ }
if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
s->use_color = git_config_colorbool(k, v);
return 0;
@@ -1155,8 +1202,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_SET_INT('s', "short", &status_format,
N_("show status concisely"), STATUS_FORMAT_SHORT),
- OPT_BOOLEAN('b', "branch", &s.show_branch,
- N_("show branch information")),
+ OPT_BOOL('b', "branch", &s.show_branch,
+ N_("show branch information")),
OPT_SET_INT(0, "porcelain", &status_format,
N_("machine-readable output"),
STATUS_FORMAT_PORCELAIN),
@@ -1189,13 +1236,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
builtin_status_options,
builtin_status_usage, 0);
finalize_colopts(&s.colopts, -1);
-
- if (s.null_termination) {
- if (status_format == STATUS_FORMAT_NONE)
- status_format = STATUS_FORMAT_PORCELAIN;
- else if (status_format == STATUS_FORMAT_LONG)
- die(_("--long and -z are incompatible"));
- }
+ finalize_deferred_config(&s);
handle_untracked_files_arg(&s);
if (show_ignored_in_status)
@@ -1224,6 +1265,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
case STATUS_FORMAT_PORCELAIN:
wt_porcelain_print(&s);
break;
+ case STATUS_FORMAT_UNSPECIFIED:
+ die("BUG: finalize_deferred_config() should have been called");
+ break;
case STATUS_FORMAT_NONE:
case STATUS_FORMAT_LONG:
s.verbose = verbose;
@@ -1392,7 +1436,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")),
OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
STATUS_FORMAT_SHORT),
- OPT_BOOLEAN(0, "branch", &s.show_branch, N_("show branch information")),
+ OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
OPT_SET_INT(0, "porcelain", &status_format,
N_("machine-readable output"), STATUS_FORMAT_PORCELAIN),
OPT_SET_INT(0, "long", &status_format,
@@ -1433,6 +1477,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
wt_status_prepare(&s);
gitmodules_config();
git_config(git_commit_config, &s);
+ status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
determine_whence(&s);
s.colopts = 0;
diff --git a/builtin/config.c b/builtin/config.c
index 7759671eb8..4010c4320a 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -21,6 +21,7 @@ static char term = '\n';
static int use_global_config, use_system_config, use_local_config;
static const char *given_config_file;
+static const char *given_config_blob;
static int actions, types;
static const char *get_color_slot, *get_colorbool_slot;
static int end_null;
@@ -53,6 +54,7 @@ static struct option builtin_config_options[] = {
OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")),
OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")),
OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")),
+ OPT_STRING(0, "blob", &given_config_blob, N_("blob-id"), N_("read config from given blob object")),
OPT_GROUP(N_("Action")),
OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL),
@@ -218,7 +220,8 @@ static int get_value(const char *key_, const char *regex_)
}
git_config_with_options(collect_config, &values,
- given_config_file, respect_includes);
+ given_config_file, given_config_blob,
+ respect_includes);
ret = !values.nr;
@@ -302,7 +305,8 @@ static void get_color(const char *def_color)
get_color_found = 0;
parsed_color[0] = '\0';
git_config_with_options(git_get_color_config, NULL,
- given_config_file, respect_includes);
+ given_config_file, given_config_blob,
+ respect_includes);
if (!get_color_found && def_color)
color_parse(def_color, "command line", parsed_color);
@@ -331,7 +335,8 @@ static int get_colorbool(int print)
get_diff_color_found = -1;
get_color_ui_found = -1;
git_config_with_options(git_get_colorbool_config, NULL,
- given_config_file, respect_includes);
+ given_config_file, given_config_blob,
+ respect_includes);
if (get_colorbool_found < 0) {
if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -353,6 +358,12 @@ static int get_colorbool(int print)
return get_colorbool_found ? 0 : 1;
}
+static void check_blob_write(void)
+{
+ if (given_config_blob)
+ die("writing config blobs is not supported");
+}
+
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = !startup_info->have_repository;
@@ -364,7 +375,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
builtin_config_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) {
+ if (use_global_config + use_system_config + use_local_config +
+ !!given_config_file + !!given_config_blob > 1) {
error("only one config file at a time.");
usage_with_options(builtin_config_usage, builtin_config_options);
}
@@ -443,6 +455,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 0, 0);
if (git_config_with_options(show_all_config, NULL,
given_config_file,
+ given_config_blob,
respect_includes) < 0) {
if (given_config_file)
die_errno("unable to read config file '%s'",
@@ -455,6 +468,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 0, 0);
if (!given_config_file && nongit)
die("not in a git directory");
+ if (given_config_blob)
+ die("editing blobs is not supported");
git_config(git_default_config, NULL);
launch_editor(given_config_file ?
given_config_file : git_path("config"),
@@ -462,6 +477,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_SET) {
int ret;
+ check_blob_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
ret = git_config_set_in_file(given_config_file, argv[0], value);
@@ -471,18 +487,21 @@ int cmd_config(int argc, const char **argv, const char *prefix)
return ret;
}
else if (actions == ACTION_SET_ALL) {
+ check_blob_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
return git_config_set_multivar_in_file(given_config_file,
argv[0], value, argv[2], 0);
}
else if (actions == ACTION_ADD) {
+ check_blob_write();
check_argc(argc, 2, 2);
value = normalize_value(argv[0], argv[1]);
return git_config_set_multivar_in_file(given_config_file,
argv[0], value, "^$", 0);
}
else if (actions == ACTION_REPLACE_ALL) {
+ check_blob_write();
check_argc(argc, 2, 3);
value = normalize_value(argv[0], argv[1]);
return git_config_set_multivar_in_file(given_config_file,
@@ -505,6 +524,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
return get_value(argv[0], argv[1]);
}
else if (actions == ACTION_UNSET) {
+ check_blob_write();
check_argc(argc, 1, 2);
if (argc == 2)
return git_config_set_multivar_in_file(given_config_file,
@@ -514,12 +534,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
argv[0], NULL);
}
else if (actions == ACTION_UNSET_ALL) {
+ check_blob_write();
check_argc(argc, 1, 2);
return git_config_set_multivar_in_file(given_config_file,
argv[0], NULL, argv[1], 1);
}
else if (actions == ACTION_RENAME_SECTION) {
int ret;
+ check_blob_write();
check_argc(argc, 2, 2);
ret = git_config_rename_section_in_file(given_config_file,
argv[0], argv[1]);
@@ -530,6 +552,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
else if (actions == ACTION_REMOVE_SECTION) {
int ret;
+ check_blob_write();
check_argc(argc, 1, 1);
ret = git_config_rename_section_in_file(given_config_file,
argv[0], NULL);
diff --git a/builtin/grep.c b/builtin/grep.c
index 159e65d47a..d3b3b1db11 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -286,8 +286,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
struct strbuf pathbuf = STRBUF_INIT;
if (opt->relative && opt->prefix_length) {
- quote_path_relative(filename + tree_name_len, -1, &pathbuf,
- opt->prefix);
+ quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
strbuf_insert(&pathbuf, 0, filename, tree_name_len);
} else {
strbuf_addstr(&pathbuf, filename);
@@ -318,7 +317,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
struct strbuf buf = STRBUF_INIT;
if (opt->relative && opt->prefix_length)
- quote_path_relative(filename, -1, &buf, opt->prefix);
+ quote_path_relative(filename, opt->prefix, &buf);
else
strbuf_addstr(&buf, filename);
@@ -376,7 +375,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
read_cache();
for (nr = 0; nr < active_nr; nr++) {
- struct cache_entry *ce = active_cache[nr];
+ const struct cache_entry *ce = active_cache[nr];
if (!S_ISREG(ce->ce_mode))
continue;
if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
diff --git a/builtin/log.c b/builtin/log.c
index 9e2123295f..2625f9881a 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -237,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
int i = revs->early_output;
int show_header = 1;
- sort_in_topological_order(&list, revs->lifo);
+ sort_in_topological_order(&list, revs->sort_order);
while (list && i) {
struct commit *commit = list->item;
switch (simplify_commit(revs, commit)) {
@@ -1112,6 +1112,21 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int from_callback(const struct option *opt, const char *arg, int unset)
+{
+ char **from = opt->value;
+
+ free(*from);
+
+ if (unset)
+ *from = NULL;
+ else if (arg)
+ *from = xstrdup(arg);
+ else
+ *from = xstrdup(git_committer_info(IDENT_NO_DATE));
+ return 0;
+}
+
int cmd_format_patch(int argc, const char **argv, const char *prefix)
{
struct commit *commit;
@@ -1134,6 +1149,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int quiet = 0;
int reroll_count = -1;
char *branch_name = NULL;
+ char *from = NULL;
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
N_("use [PATCH n/m] even with a single patch"),
@@ -1177,6 +1193,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
0, to_callback },
{ OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
0, cc_callback },
+ { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
+ N_("set From address to <ident> (or committer ident if absent)"),
+ PARSE_OPT_OPTARG, from_callback },
OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
N_("make first mail a reply to <message-id>")),
{ OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
@@ -1264,6 +1283,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.extra_headers = strbuf_detach(&buf, NULL);
+ if (from) {
+ if (split_ident_line(&rev.from_ident, from, strlen(from)))
+ die(_("invalid ident line: %s"), from);
+ }
+
if (start_number < 0)
start_number = 1;
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 87f3b331ca..5cf3e31370 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -46,10 +46,14 @@ static const char *tag_modified = "";
static const char *tag_skip_worktree = "";
static const char *tag_resolve_undo = "";
-static void write_name(const char* name, size_t len)
+static void write_name(const char *name)
{
- write_name_quoted_relative(name, len, prefix, prefix_len, stdout,
- line_terminator);
+ /*
+ * With "--full-name", prefix_len=0; this caller needs to pass
+ * an empty string in that case (a NULL is good for "").
+ */
+ write_name_quoted_relative(name, prefix_len ? prefix : NULL,
+ stdout, line_terminator);
}
static void show_dir_entry(const char *tag, struct dir_entry *ent)
@@ -63,7 +67,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
return;
fputs(tag, stdout);
- write_name(ent->name, ent->len);
+ write_name(ent->name);
}
static void show_other_files(struct dir_struct *dir)
@@ -127,7 +131,7 @@ static void show_killed_files(struct dir_struct *dir)
}
}
-static void show_ce_entry(const char *tag, struct cache_entry *ce)
+static void show_ce_entry(const char *tag, const struct cache_entry *ce)
{
int len = max_prefix_len;
@@ -163,13 +167,15 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
find_unique_abbrev(ce->sha1,abbrev),
ce_stage(ce));
}
- write_name(ce->name, ce_namelen(ce));
+ write_name(ce->name);
if (debug_mode) {
- printf(" ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
- printf(" mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
- printf(" dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
- printf(" uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
- printf(" size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
+ const struct stat_data *sd = &ce->ce_stat_data;
+
+ printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
+ printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
+ printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
+ printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
+ printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
}
@@ -196,12 +202,12 @@ static void show_ru_info(void)
printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
find_unique_abbrev(ui->sha1[i], abbrev),
i + 1);
- write_name(path, len);
+ write_name(path);
}
}
}
-static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce)
+static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce)
{
int dtype = ce_to_dtype(ce);
return is_excluded(dir, ce->name, &dtype);
@@ -221,7 +227,7 @@ static void show_files(struct dir_struct *dir)
}
if (show_cached || show_stage) {
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
if ((dir->flags & DIR_SHOW_IGNORED) &&
!ce_excluded(dir, ce))
continue;
@@ -235,7 +241,7 @@ static void show_files(struct dir_struct *dir)
}
if (show_deleted || show_modified) {
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
struct stat st;
int err;
if ((dir->flags & DIR_SHOW_IGNORED) &&
@@ -271,7 +277,7 @@ static void prune_cache(const char *prefix)
last = active_nr;
while (last > first) {
int next = (last + first) >> 1;
- struct cache_entry *ce = active_cache[next];
+ const struct cache_entry *ce = active_cache[next];
if (!strncmp(ce->name, prefix, max_prefix_len)) {
first = next+1;
continue;
@@ -389,7 +395,7 @@ int report_path_error(const char *ps_matched, const char **pathspec, const char
if (found_dup)
continue;
- name = quote_path_relative(pathspec[num], -1, &sb, prefix);
+ name = quote_path_relative(pathspec[num], prefix, &sb);
error("pathspec '%s' did not match any file(s) known to git.",
name);
errors++;
diff --git a/builtin/merge-index.c b/builtin/merge-index.c
index be5e514324..b416d92849 100644
--- a/builtin/merge-index.c
+++ b/builtin/merge-index.c
@@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path)
die("git merge-index: %s not in the cache", path);
found = 0;
do {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
int stage = ce_stage(ce);
if (strcmp(ce->name, path))
@@ -58,7 +58,7 @@ static void merge_all(void)
{
int i;
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
if (!ce_stage(ce))
continue;
i += merge_entry(i, ce->name)-1;
diff --git a/builtin/merge.c b/builtin/merge.c
index 2ebe732896..34a6166b52 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -47,8 +47,8 @@ static const char * const builtin_merge_usage[] = {
};
static int show_diffstat = 1, shortlog_len = -1, squash;
-static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only, option_edit = -1;
+static int option_commit = 1;
+static int option_edit = -1;
static int allow_trivial = 1, have_message, verify_signatures;
static int overwrite_ignore = 1;
static struct strbuf merge_msg = STRBUF_INIT;
@@ -76,6 +76,14 @@ static struct strategy all_strategy[] = {
static const char *pull_twohead, *pull_octopus;
+enum ff_type {
+ FF_NO,
+ FF_ALLOW,
+ FF_ONLY
+};
+
+static enum ff_type fast_forward = FF_ALLOW;
+
static int option_parse_message(const struct option *opt,
const char *arg, int unset)
{
@@ -178,6 +186,13 @@ static int option_parse_n(const struct option *opt,
return 0;
}
+static int option_parse_ff_only(const struct option *opt,
+ const char *arg, int unset)
+{
+ fast_forward = FF_ONLY;
+ return 0;
+}
+
static struct option builtin_merge_options[] = {
{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
N_("do not show a diffstat at the end of the merge"),
@@ -194,10 +209,10 @@ static struct option builtin_merge_options[] = {
N_("perform a commit if the merge succeeds (default)")),
OPT_BOOL('e', "edit", &option_edit,
N_("edit message before committing")),
- OPT_BOOLEAN(0, "ff", &allow_fast_forward,
- N_("allow fast-forward (default)")),
- OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
- N_("abort if fast-forward is not possible")),
+ OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW),
+ { OPTION_CALLBACK, 0, "ff-only", NULL, NULL,
+ N_("abort if fast-forward is not possible"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, option_parse_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")),
@@ -581,10 +596,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
else if (!strcmp(k, "merge.ff")) {
int boolval = git_config_maybe_bool(k, v);
if (0 <= boolval) {
- allow_fast_forward = boolval;
+ fast_forward = boolval ? FF_ALLOW : FF_NO;
} else if (v && !strcmp(v, "only")) {
- allow_fast_forward = 1;
- fast_forward_only = 1;
+ fast_forward = FF_ONLY;
} /* do not barf on values from future versions of git */
return 0;
} else if (!strcmp(k, "merge.defaulttoupstream")) {
@@ -863,7 +877,7 @@ static int finish_automerge(struct commit *head,
free_commit_list(common);
parents = remoteheads;
- if (!head_subsumed || !allow_fast_forward)
+ if (!head_subsumed || fast_forward == FF_NO)
commit_list_insert(head, &parents);
strbuf_addch(&merge_msg, '\n');
prepare_to_commit(remoteheads);
@@ -889,7 +903,7 @@ static int suggest_conflicts(int renormalizing)
die_errno(_("Could not open '%s' for writing"), filename);
fprintf(fp, "\nConflicts:\n");
for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (ce_stage(ce)) {
fprintf(fp, "\t%s\n", ce->name);
@@ -948,7 +962,7 @@ static int evaluate_result(void)
}
/*
- * Pretend as if the user told us to merge with the tracking
+ * Pretend as if the user told us to merge with the remote-tracking
* branch we have for the upstream of the current branch
*/
static int setup_with_upstream(const char ***argv)
@@ -967,7 +981,7 @@ static int setup_with_upstream(const char ***argv)
args = xcalloc(branch->merge_nr + 1, sizeof(char *));
for (i = 0; i < branch->merge_nr; i++) {
if (!branch->merge[i]->dst)
- die(_("No remote tracking branch for %s from %s"),
+ die(_("No remote-tracking branch for %s from %s"),
branch->merge[i]->src, branch->remote_name);
args[i] = branch->merge[i]->dst;
}
@@ -1008,7 +1022,7 @@ static void write_merge_state(struct commit_list *remoteheads)
if (fd < 0)
die_errno(_("Could not open '%s' for writing"), filename);
strbuf_reset(&buf);
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
strbuf_addf(&buf, "no-ff");
if (write_in_full(fd, buf.buf, buf.len) != buf.len)
die_errno(_("Could not write to '%s'"), filename);
@@ -1157,14 +1171,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
show_diffstat = 0;
if (squash) {
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
die(_("You cannot combine --squash with --no-ff."));
option_commit = 0;
}
- if (!allow_fast_forward && fast_forward_only)
- die(_("You cannot combine --no-ff with --ff-only."));
-
if (!abort_current_merge) {
if (!argc) {
if (default_to_upstream)
@@ -1206,7 +1217,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
"empty head"));
if (squash)
die(_("Squash commit into empty head not supported yet"));
- if (!allow_fast_forward)
+ if (fast_forward == FF_NO)
die(_("Non-fast-forward commit does not make sense into "
"an empty head"));
remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
@@ -1294,11 +1305,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
sha1_to_hex(commit->object.sha1));
setenv(buf.buf, merge_remote_util(commit)->name, 1);
strbuf_reset(&buf);
- if (!fast_forward_only &&
+ if (fast_forward != FF_ONLY &&
merge_remote_util(commit) &&
merge_remote_util(commit)->obj &&
merge_remote_util(commit)->obj->type == OBJ_TAG)
- allow_fast_forward = 0;
+ fast_forward = FF_NO;
}
if (option_edit < 0)
@@ -1315,7 +1326,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
for (i = 0; i < use_strategies_nr; i++) {
if (use_strategies[i]->attr & NO_FAST_FORWARD)
- allow_fast_forward = 0;
+ fast_forward = FF_NO;
if (use_strategies[i]->attr & NO_TRIVIAL)
allow_trivial = 0;
}
@@ -1345,7 +1356,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
*/
finish_up_to_date("Already up-to-date.");
goto done;
- } else if (allow_fast_forward && !remoteheads->next &&
+ } else if (fast_forward != FF_NO && !remoteheads->next &&
!common->next &&
!hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
/* Again the most common case of merging one remote. */
@@ -1392,7 +1403,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* only one common.
*/
refresh_cache(REFRESH_QUIET);
- if (allow_trivial && !fast_forward_only) {
+ if (allow_trivial && fast_forward != FF_ONLY) {
/* See if it is really trivial. */
git_committer_info(IDENT_STRICT);
printf(_("Trying really trivial in-index merge...\n"));
@@ -1433,7 +1444,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
}
- if (fast_forward_only)
+ if (fast_forward == FF_ONLY)
die(_("Not possible to fast-forward, aborting."));
/* We are going to make a new commit. */
diff --git a/builtin/push.c b/builtin/push.c
index 2d84d10720..6d36c24268 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -120,10 +120,11 @@ static const char message_detached_head_die[] =
"\n"
" git push %s HEAD:<name-of-remote-branch>\n");
-static void setup_push_upstream(struct remote *remote, int simple)
+static void setup_push_upstream(struct remote *remote, struct branch *branch,
+ int triangular)
{
struct strbuf refspec = STRBUF_INIT;
- struct branch *branch = branch_get(NULL);
+
if (!branch)
die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
@@ -137,18 +138,29 @@ static void setup_push_upstream(struct remote *remote, int simple)
if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name);
- if (strcmp(branch->remote_name, remote->name))
+ if (triangular)
die(_("You are pushing to remote '%s', which is not the upstream of\n"
"your current branch '%s', without telling me what to push\n"
"to update which remote branch."),
remote->name, branch->name);
- if (simple && strcmp(branch->refname, branch->merge[0]->src))
- die_push_simple(branch, remote);
+
+ if (push_default == PUSH_DEFAULT_SIMPLE) {
+ /* Additional safety */
+ if (strcmp(branch->refname, branch->merge[0]->src))
+ die_push_simple(branch, remote);
+ }
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf);
}
+static void setup_push_current(struct remote *remote, struct branch *branch)
+{
+ if (!branch)
+ die(_(message_detached_head_die), remote->name);
+ add_refspec(branch->name);
+}
+
static char warn_unspecified_push_default_msg[] =
N_("push.default is unset; its implicit value is changing in\n"
"Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
@@ -173,9 +185,16 @@ static void warn_unspecified_push_default_configuration(void)
warning("%s\n", _(warn_unspecified_push_default_msg));
}
+static int is_workflow_triangular(struct remote *remote)
+{
+ struct remote *fetch_remote = remote_get(NULL);
+ return (fetch_remote && fetch_remote != remote);
+}
+
static void setup_default_push_refspecs(struct remote *remote)
{
- struct branch *branch;
+ struct branch *branch = branch_get(NULL);
+ int triangular = is_workflow_triangular(remote);
switch (push_default) {
default:
@@ -188,18 +207,18 @@ static void setup_default_push_refspecs(struct remote *remote)
break;
case PUSH_DEFAULT_SIMPLE:
- setup_push_upstream(remote, 1);
+ if (triangular)
+ setup_push_current(remote, branch);
+ else
+ setup_push_upstream(remote, branch, triangular);
break;
case PUSH_DEFAULT_UPSTREAM:
- setup_push_upstream(remote, 0);
+ setup_push_upstream(remote, branch, triangular);
break;
case PUSH_DEFAULT_CURRENT:
- branch = branch_get(NULL);
- if (!branch)
- die(_(message_detached_head_die), remote->name);
- add_refspec(branch->name);
+ setup_push_current(remote, branch);
break;
case PUSH_DEFAULT_NOTHING:
@@ -211,8 +230,8 @@ static void setup_default_push_refspecs(struct remote *remote)
static const char message_advice_pull_before_push[] =
N_("Updates were rejected because the tip of your current branch is behind\n"
- "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
- "before pushing again.\n"
+ "its remote counterpart. Integrate the remote changes (e.g.\n"
+ "'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_use_upstream[] =
@@ -223,15 +242,15 @@ static const char message_advice_use_upstream[] =
static const char message_advice_checkout_pull_push[] =
N_("Updates were rejected because a pushed branch tip is behind its remote\n"
- "counterpart. Check out this branch and merge the remote changes\n"
- "(e.g. 'git pull') before pushing again.\n"
+ "counterpart. Check out this branch and integrate the remote changes\n"
+ "(e.g. 'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_ref_fetch_first[] =
N_("Updates were rejected because the remote contains work that you do\n"
"not have locally. This is usually caused by another repository pushing\n"
- "to the same ref. You may want to first merge the remote changes (e.g.,\n"
- "'git pull') before pushing again.\n"
+ "to the same ref. You may want to first integrate the remote changes\n"
+ "(e.g., 'git pull ...') before pushing again.\n"
"See the 'Note about fast-forwards' in 'git push --help' for details.");
static const char message_advice_ref_already_exists[] =
diff --git a/builtin/reset.c b/builtin/reset.c
index 6032131a90..afa6e020e8 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -93,10 +93,12 @@ static int reset_index(const unsigned char *sha1, int reset_type, int quiet)
static void print_new_head_line(struct commit *commit)
{
const char *hex, *body;
+ char *msg;
hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
printf(_("HEAD is now at %s"), hex);
- body = strstr(commit->buffer, "\n\n");
+ msg = logmsg_reencode(commit, NULL, get_log_output_encoding());
+ body = strstr(msg, "\n\n");
if (body) {
const char *eol;
size_t len;
@@ -107,6 +109,7 @@ static void print_new_head_line(struct commit *commit)
}
else
printf("\n");
+ logmsg_free(msg, commit);
}
static void update_index_from_diff(struct diff_queue_struct *q,
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 67701be551..a5ec30d74e 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -111,6 +111,7 @@ static void show_commit(struct commit *commit, void *data)
ctx.date_mode = revs->date_mode;
ctx.date_mode_explicit = revs->date_mode_explicit;
ctx.fmt = revs->commit_format;
+ ctx.output_encoding = get_log_output_encoding();
pretty_print_commit(&ctx, commit, &buf);
if (revs->graph) {
if (buf.len) {
diff --git a/builtin/revert.c b/builtin/revert.c
index 0401fdb02c..1d2648b756 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -54,6 +54,7 @@ static int option_parse_x(const struct option *opt,
return 0;
}
+LAST_ARG_MUST_BE_NULL
static void verify_opt_compatible(const char *me, const char *base_opt, ...)
{
const char *this_opt;
@@ -70,6 +71,7 @@ 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);
}
+LAST_ARG_MUST_BE_NULL
static void verify_opt_mutually_compatible(const char *me, ...)
{
const char *opt1, *opt2 = NULL;
diff --git a/builtin/rm.c b/builtin/rm.c
index 06025a2e75..18916e022a 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -67,7 +67,7 @@ static int check_submodules_use_gitfiles(void)
for (i = 0; i < list.nr; i++) {
const char *name = list.entry[i].name;
int pos;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
struct stat st;
pos = cache_name_pos(name, strlen(name));
@@ -120,7 +120,7 @@ static int check_local_mod(unsigned char *head, int index_only)
for (i = 0; i < list.nr; i++) {
struct stat st;
int pos;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
const char *name = list.entry[i].name;
unsigned char sha1[20];
unsigned mode;
@@ -321,7 +321,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
seen = xcalloc(i, 1);
for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
+ const struct cache_entry *ce = active_cache[i];
if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
continue;
ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 1fd6f8ac59..1434f8fee4 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -137,6 +137,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
ctx.subject = "";
ctx.after_subject = "";
ctx.date_mode = DATE_NORMAL;
+ ctx.output_encoding = get_log_output_encoding();
pretty_print_commit(&ctx, commit, &ufbuf);
buffer = ufbuf.buf;
} else if (*buffer) {
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 90fc6b1b9d..9788eb115b 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int num_rev, i, extra = 0;
int all_heads = 0, all_remotes = 0;
int all_mask, all_revs;
- int lifo = 1;
+ enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
char head[128];
const char *head_p;
int head_len;
@@ -665,15 +665,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
N_("show possible merge bases")),
OPT_BOOLEAN(0, "independent", &independent,
N_("show refs unreachable from any other ref")),
- OPT_BOOLEAN(0, "topo-order", &lifo,
- N_("show commits in topological order")),
+ OPT_SET_INT(0, "topo-order", &sort_order,
+ N_("show commits in topological order"),
+ REV_SORT_IN_GRAPH_ORDER),
OPT_BOOLEAN(0, "topics", &topics,
N_("show only commits not on the first branch")),
OPT_SET_INT(0, "sparse", &dense,
N_("show merges reachable from only one tip"), 0),
- OPT_SET_INT(0, "date-order", &lifo,
- N_("show commits where no parent comes before its "
- "children"), 0),
+ OPT_SET_INT(0, "date-order", &sort_order,
+ N_("topologically sort, maintaining date order "
+ "where possible"),
+ REV_SORT_BY_COMMIT_DATE),
{ OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
N_("show <n> most recent ref-log entries starting at "
"base"),
@@ -900,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
exit(0);
/* Sort topologically */
- sort_in_topological_order(&seen, lifo);
+ sort_in_topological_order(&seen, sort_order);
/* Give names to commits */
if (!sha1_name && !no_name)
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 4a0310da37..87806ad5b0 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -31,6 +31,9 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
const char *hex;
unsigned char peeled[20];
+ if (show_head && !strcmp(refname, "HEAD"))
+ goto match;
+
if (tags_only || heads_only) {
int match;
@@ -167,9 +170,10 @@ static const struct option show_ref_options[] = {
OPT_BOOLEAN(0, "verify", &verify, N_("stricter reference checking, "
"requires exact ref path")),
{ OPTION_BOOLEAN, 'h', NULL, &show_head, NULL,
- N_("show the HEAD reference"),
+ N_("show the HEAD reference, even if it would be filtered out"),
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
- OPT_BOOLEAN(0, "head", &show_head, N_("show the HEAD reference")),
+ OPT_BOOLEAN(0, "head", &show_head,
+ N_("show the HEAD reference, even if it would be filtered out")),
OPT_BOOLEAN('d', "dereference", &deref_tags,
N_("dereference tags into object IDs")),
{ OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"),
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 5c7762eef4..c317981516 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -83,7 +83,7 @@ static int process_lstat_error(const char *path, int err)
return error("lstat(\"%s\"): %s", path, strerror(errno));
}
-static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
+static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st)
{
int option, size;
struct cache_entry *ce;
@@ -142,7 +142,7 @@ static int process_directory(const char *path, int len, struct stat *st)
/* Exact match: file or existing gitlink */
if (pos >= 0) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (S_ISGITLINK(ce->ce_mode)) {
/* Do nothing to the index if there is no HEAD! */
@@ -158,7 +158,7 @@ static int process_directory(const char *path, int len, struct stat *st)
/* Inexact match: is there perhaps a subdirectory match? */
pos = -pos-1;
while (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos++];
+ const struct cache_entry *ce = active_cache[pos++];
if (strncmp(ce->name, path, len))
break;
@@ -183,7 +183,7 @@ static int process_path(const char *path)
{
int pos, len;
struct stat st;
- struct cache_entry *ce;
+ const struct cache_entry *ce;
len = strlen(path);
if (has_symlink_leading_path(path, len))
@@ -448,7 +448,7 @@ static int unresolve_one(const char *path)
/* already merged */
pos = unmerge_cache_entry_at(pos);
if (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (ce_stage(ce) &&
ce_namelen(ce) == namelen &&
!memcmp(ce->name, path, namelen))
@@ -462,7 +462,7 @@ static int unresolve_one(const char *path)
*/
pos = -pos-1;
if (pos < active_nr) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
if (ce_namelen(ce) == namelen &&
!memcmp(ce->name, path, namelen)) {
fprintf(stderr,
@@ -558,7 +558,7 @@ static int do_reupdate(int ac, const char **av,
has_head = 0;
redo:
for (pos = 0; pos < active_nr; pos++) {
- struct cache_entry *ce = active_cache[pos];
+ const struct cache_entry *ce = active_cache[pos];
struct cache_entry *old = NULL;
int save_nr;