/*
* "git clean" builtin command
*
* Copyright (C) 2007 Shawn Bohrer
*
* Based on git-clean.sh by Pavel Roskin
*/
#include "builtin.h"
#include "cache.h"
#include "dir.h"
#include "parse-options.h"
#include "refs.h"
#include "string-list.h"
#include "quote.h"
#include "column.h"
#include "color.h"
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] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
NULL
};
static const char *msg_remove = N_("Removing %s\n");
static const char *msg_would_remove = N_("Would remove %s\n");
static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
static const char *msg_warn_remove_failed = N_("failed to remove %s");
static int clean_use_color = -1;
static char clean_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
GIT_COLOR_NORMAL, /* PLAIN */
GIT_COLOR_BOLD_BLUE, /* PROMPT */
GIT_COLOR_BOLD, /* HEADER */
GIT_COLOR_BOLD_RED, /* HELP */
GIT_COLOR_BOLD_RED, /* ERROR */
};
enum color_clean {
CLEAN_COLOR_RESET = 0,
CLEAN_COLOR_PLAIN = 1,
CLEAN_COLOR_PROMPT = 2,
CLEAN_COLOR_HEADER = 3,
CLEAN_COLOR_HELP = 4,
CLEAN_COLOR_ERROR = 5,
};
#define MENU_OPTS_SINGLETON 01
#define MENU_OPTS_IMMEDIATE 02
#define MENU_OPTS_LIST_ONLY 04
struct menu_opts {
const char *header;
const char *prompt;
int flags;
};
#define MENU_RETURN_NO_LOOP 10
struct menu_item {
char hotkey;
const char *title;
int selected;
int (*fn)();
};
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 (!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 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)
{
struct string_list *exclude_list = opt->value;
string_list_append(exclude_list, arg);
return 0;
}
static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
int dry_run, int quiet, int *dir_gone)
{
DIR *dir;
struct strbuf quoted = STRBUF_INIT;
struct dirent *e;
int res = 0, ret = 0, gone = 1, original_len = path->len, len, i;
unsigned char submodule_head[20];
struct string_list dels = STRING_LIST_INIT_DUP;
*dir_gone = 1;
if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
if (!quiet) {
quote_path_relative(path->buf, prefix, "ed);
printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
quoted.buf);
}
*dir_gone = 0;
return 0;
}
dir = opendir(path->buf);
if (!dir) {
/* an empty dir could be removed even if it is unreadble */
res = dry_run ? 0 : rmdir(path->buf);
if (res) {
quote_path_relative(path->buf, prefix, "ed);
warning(_(msg_warn_remove_failed), quoted.buf);
*dir_gone = 0;
}
return res;
}
if (path->buf[original_len - 1] != '/')
|