git/builtin/clean.c
Adam Spiers c04318e46a dir.c: keep track of where patterns came from
For exclude patterns read in from files, the filename is stored in the
exclude list, and the originating line number is stored in the
individual exclude (counting starting at 1).

For exclude patterns provided on the command line, a string describing
the source of the patterns is stored in the exclude list, and the
sequence number assigned to each exclude pattern is negative, with
counting starting at -1.  So for example the 2nd pattern provided via
--exclude would be numbered -2.  This allows any future consumers of
that data to easily distinguish between exclude patterns from files
vs. from the CLI.

Signed-off-by: Adam Spiers <git@adamspiers.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-06 14:26:37 -08:00

194 lines
4.9 KiB
C

/*
* "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 "string-list.h"
#include "quote.h"
static int force = -1; /* unset */
static const char *const builtin_clean_usage[] = {
"git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>...",
NULL
};
static int git_clean_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "clean.requireforce"))
force = !git_config_bool(var, value);
return git_default_config(var, value, cb);
}
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;
}
int cmd_clean(int argc, const char **argv, const char *prefix)
{
int i;
int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
int ignored_only = 0, config_set = 0, errors = 0;
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
struct strbuf directory = STRBUF_INIT;
struct dir_struct dir;
static const char **pathspec;
struct strbuf buf = STRBUF_INIT;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
const char *qname;
char *seen = NULL;
struct option options[] = {
OPT__QUIET(&quiet, "do not print names of files removed"),
OPT__DRY_RUN(&show_only, "dry run"),
OPT__FORCE(&force, "force"),
OPT_BOOLEAN('d', NULL, &remove_directories,
"remove whole directories"),
{ OPTION_CALLBACK, 'e', "exclude", &exclude_list, "pattern",
"add <pattern> to ignore rules", PARSE_OPT_NONEG, exclude_cb },
OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"),
OPT_BOOLEAN('X', NULL, &ignored_only,
"remove only ignored files"),
OPT_END()
};
git_config(git_clean_config, NULL);
if (force < 0)
force = 0;
else
config_set = 1;
argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
0);
memset(&dir, 0, sizeof(dir));
if (ignored_only)
dir.flags |= DIR_SHOW_IGNORED;
if (ignored && ignored_only)
die(_("-x and -X cannot be used together"));
if (!show_only && !force) {
if (config_set)
die(_("clean.requireForce set to true and neither -n nor -f given; "
"refusing to clean"));
else
die(_("clean.requireForce defaults to true and neither -n nor -f given; "
"refusing to clean"));
}
if (force > 1)
rm_flags = 0;
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
if (read_cache() < 0)
die(_("index file corrupt"));
if (!ignored)
setup_standard_excludes(&dir);
add_exclude_list(&dir, EXC_CMDL, "--exclude option");
for (i = 0; i < exclude_list.nr; i++)
add_exclude(exclude_list.items[i].string, "", 0,
&dir.exclude_list_group[EXC_CMDL].el[0], -(i+1));
pathspec = get_pathspec(prefix, argv);
fill_directory(&dir, pathspec);
if (pathspec)
seen = xmalloc(argc > 0 ? argc : 1);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
int len, pos;
int matches = 0;
struct cache_entry *ce;
struct stat st;
/*
* Remove the '/' at the end that directory
* walking adds for directory entries.
*/
len = ent->len;
if (len && ent->name[len-1] == '/')
len--;
pos = cache_name_pos(ent->name, len);
if (0 <= pos)
continue; /* exact match */
pos = -pos - 1;
if (pos < active_nr) {
ce = active_cache[pos];
if (ce_namelen(ce) == len &&
!memcmp(ce->name, ent->name, len))
continue; /* Yup, this one exists unmerged */
}
/*
* 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;
if (pathspec) {
memset(seen, 0, argc > 0 ? argc : 1);
matches = match_pathspec(pathspec, ent->name, len,
0, seen);
}
if (S_ISDIR(st.st_mode)) {
strbuf_addstr(&directory, ent->name);
qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
if (show_only && (remove_directories ||
(matches == MATCHED_EXACTLY))) {
printf(_("Would remove %s\n"), qname);
} else if (remove_directories ||
(matches == MATCHED_EXACTLY)) {
if (!quiet)
printf(_("Removing %s\n"), qname);
if (remove_dir_recursively(&directory,
rm_flags) != 0) {
warning(_("failed to remove %s"), qname);
errors++;
}
} else if (show_only) {
printf(_("Would not remove %s\n"), qname);
} else {
printf(_("Not removing %s\n"), qname);
}
strbuf_reset(&directory);
} else {
if (pathspec && !matches)
continue;
qname = quote_path_relative(ent->name, -1, &buf, prefix);
if (show_only) {
printf(_("Would remove %s\n"), qname);
continue;
} else if (!quiet) {
printf(_("Removing %s\n"), qname);
}
if (unlink(ent->name) != 0) {
warning(_("failed to remove %s"), qname);
errors++;
}
}
}
free(seen);
strbuf_release(&directory);
string_list_clear(&exclude_list, 0);
return (errors != 0);
}