From: Jiang Xin <worldhello.net@gmail.com>
To: Junio C Hamano <gitster@pobox.com>,
Matthieu Moy <Matthieu.Moy@imag.fr>,
Eric Sunshine <sunshine@sunshineco.com>,
Thomas Rast <trast@inf.ethz.ch>
Cc: Git List <git@vger.kernel.org>, Jiang Xin <worldhello.net@gmail.com>
Subject: [PATCH v6 1/7] Add support for -i/--interactive to git-clean
Date: Tue, 7 May 2013 03:18:50 +0800 [thread overview]
Message-ID: <4a32eb7cc4e277fcbf4b46c13524874334bea2a3.1367867498.git.worldhello.net@gmail.com> (raw)
In-Reply-To: <cover.1367867498.git.worldhello.net@gmail.com>
In-Reply-To: <cover.1367867498.git.worldhello.net@gmail.com>
Show what would be done and the user must confirm before actually
cleaning. In the confirmation dialog, the user has three choices:
* y/yes: Start to do cleaning.
* n/no: Nothing will be deleted.
* e/edit: Exclude items from deletion using ignore patterns.
When the user chooses the edit mode, the user can input space-
separated patterns (the same syntax as gitignore), and each clean
candidate that matches with one of the patterns will be excluded
from cleaning. When the user feels it's OK, presses ENTER and back
to the confirmation dialog.
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Suggested-by: Junio C Hamano <gitster@pobox.com>
Spelling-checked-by: Eric Sunshine <sunshine@sunshineco.com>
Comments-by: Matthieu Moy <Matthieu.Moy@imag.fr>
Suggested-by: Eric Sunshine <sunshine@sunshineco.com>
---
Documentation/git-clean.txt | 15 +++-
builtin/clean.c | 195 ++++++++++++++++++++++++++++++++++++++++----
2 files changed, 191 insertions(+), 19 deletions(-)
diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt
index bdc3a..f5572 100644
--- a/Documentation/git-clean.txt
+++ b/Documentation/git-clean.txt
@@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree
SYNOPSIS
--------
[verse]
-'git clean' [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...
+'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...
DESCRIPTION
-----------
@@ -34,7 +34,18 @@ OPTIONS
-f::
--force::
If the Git configuration variable clean.requireForce is not set
- to false, 'git clean' will refuse to run unless given -f or -n.
+ to false, 'git clean' will refuse to run unless given -f, -n or
+ -i.
+
+-i::
+--interactive::
+ Show what would be done and the user must confirm before actually
+ cleaning. In the confirmation dialog, the user can choose to abort
+ the cleaning, or enter into an edit mode. In the edit mode, the
+ user can input space-separated patterns (the same syntax as
+ gitignore), and each clean candidate that matches with one of the
+ patterns will be excluded from cleaning. When the user feels it's
+ OK, presses ENTER and back to the confirmation dialog.
-n::
--dry-run::
diff --git a/builtin/clean.c b/builtin/clean.c
index 04e39..29fbf 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -15,9 +15,12 @@
#include "quote.h"
static int force = -1; /* unset */
+static int interactive;
+static struct string_list del_list = STRING_LIST_INIT_DUP;
+static const char **the_prefix;
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
};
@@ -142,6 +145,139 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
return ret;
}
+void edit_by_patterns_cmd()
+{
+ struct dir_struct dir;
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf **ignore_list;
+ struct string_list_item *item;
+ struct exclude_list *el;
+ const char *qname;
+ int changed = -1, i;
+
+ while (1) {
+ /* dels list may become empty when we run string_list_remove_empty_items later */
+ if (!del_list.nr) {
+ printf_ln(_("No more files to clean, exiting."));
+ break;
+ }
+
+ if (changed) {
+ putchar('\n');
+
+ /* Display dels in "Would remove ..." format */
+ for_each_string_list_item(item, &del_list) {
+ qname = quote_path_relative(item->string, -1, &buf, *the_prefix);
+ printf(_(msg_would_remove), qname);
+ }
+ putchar('\n');
+ }
+
+ printf(_("Input ignore patterns>> "));
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+ strbuf_trim(&confirm);
+ } else {
+ putchar('\n');
+ break;
+ }
+
+ /* Quit edit mode */
+ 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;
+ const char *qname;
+
+ qname = quote_path_relative(item->string, -1, &buf, *the_prefix);
+
+ if (is_excluded(&dir, qname, &dtype)) {
+ *item->string = '\0';
+ changed++;
+ }
+ }
+
+ if (changed) {
+ string_list_remove_empty_items(&del_list, 0);
+ } else {
+ printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
+ }
+
+ strbuf_list_free(ignore_list);
+ clear_directory(&dir);
+ }
+
+ strbuf_release(&buf);
+ strbuf_release(&confirm);
+}
+
+void interactive_main_loop()
+{
+ struct strbuf confirm = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list_item *item;
+ const char *qname;
+
+ /* dels list may become empty after return back from edit mode */
+ while (del_list.nr) {
+ printf_ln(Q_("Would remove the following item:",
+ "Would remove the following items:",
+ del_list.nr));
+ putchar('\n');
+
+ /* Display dels in "Would remove ..." format */
+ for_each_string_list_item(item, &del_list) {
+ qname = quote_path_relative(item->string, -1, &buf, *the_prefix);
+ printf(_(msg_would_remove), qname);
+ }
+ putchar('\n');
+
+ /* Confirmation dialog */
+ printf(_("Remove ([y]es/[n]o/[e]dit) ? "));
+ if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+ strbuf_trim(&confirm);
+ } else {
+ /* Ctrl-D is the same as "quit" */
+ string_list_clear(&del_list, 0);
+ putchar('\n');
+ printf_ln("Bye.");
+ break;
+ }
+
+ if (confirm.len) {
+ if (!strncasecmp(confirm.buf, "yes", confirm.len)) {
+ break;
+ } else if (!strncasecmp(confirm.buf, "no", confirm.len) ||
+ !strncasecmp(confirm.buf, "quit", confirm.len)) {
+ string_list_clear(&del_list, 0);
+ printf_ln("Bye.");
+ break;
+ } else if (!strncasecmp(confirm.buf, "edit", confirm.len)) {
+ edit_by_patterns_cmd();
+ } else {
+ continue;
+ }
+ }
+ }
+
+ strbuf_release(&buf);
+ strbuf_release(&confirm);
+}
+
int cmd_clean(int argc, const char **argv, const char *prefix)
{
int i, res;
@@ -154,12 +290,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
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"),
@@ -176,7 +314,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
else
config_set = 1;
- argc = parse_options(argc, argv, prefix, options, builtin_clean_usage,
+ the_prefix = &prefix;
+
+ argc = parse_options(argc, argv, *the_prefix, options, builtin_clean_usage,
0);
memset(&dir, 0, sizeof(dir));
@@ -186,12 +326,16 @@ 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) {
+ if (!isatty(0) || !isatty(1))
+ die(_("interactive clean can not run without a valid tty; "
+ "refusing to clean"));
+ } else if (!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"));
}
@@ -210,7 +354,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
for (i = 0; i < exclude_list.nr; i++)
add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
- pathspec = get_pathspec(prefix, argv);
+ pathspec = get_pathspec(*the_prefix, argv);
fill_directory(&dir, pathspec);
@@ -257,26 +401,42 @@ 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);
- }
+ string_list_append(&del_list, ent->name);
}
- strbuf_reset(&directory);
} else {
if (pathspec && !matches)
continue;
- res = dry_run ? 0 : unlink(ent->name);
+ string_list_append(&del_list, ent->name);
+ }
+ }
+
+ if (interactive && del_list.nr > 0 && !dry_run && isatty(0) && isatty(1))
+ interactive_main_loop();
+
+ for_each_string_list_item(item, &del_list) {
+ struct stat st;
+
+ if (lstat(item->string, &st))
+ continue;
+
+ if (S_ISDIR(st.st_mode)) {
+ strbuf_addstr(&directory, item->string);
+ if (remove_dirs(&directory, *the_prefix, rm_flags, dry_run, quiet, &gone))
+ errors++;
+ if (gone && !quiet) {
+ qname = quote_path_relative(directory.buf, directory.len, &buf, *the_prefix);
+ printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
+ }
+ strbuf_reset(&directory);
+ } else {
+ res = dry_run ? 0 : unlink(item->string);
if (res) {
- qname = quote_path_relative(ent->name, -1, &buf, prefix);
+ qname = quote_path_relative(item->string, -1, &buf, *the_prefix);
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, -1, &buf, *the_prefix);
printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
}
}
@@ -285,5 +445,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
strbuf_release(&directory);
string_list_clear(&exclude_list, 0);
+ string_list_clear(&del_list, 0);
return (errors != 0);
}
--
1.8.3.rc1.338.gb35aa5d
next prev parent reply other threads:[~2013-05-06 19:19 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-05-03 3:49 [PATCH v5 0/3] interactive git clean Jiang Xin
2013-05-03 3:49 ` [PATCH v5 1/3] Add support for -i/--interactive to git-clean Jiang Xin
2013-05-03 3:49 ` [PATCH v5 2/3] Show items of interactive git-clean in columns Jiang Xin
2013-05-03 3:49 ` [PATCH v5 3/3] Add colors to interactive git-clean Jiang Xin
2013-05-03 10:37 ` [PATCH v5 0/3] interactive git clean Eric Sunshine
2013-05-04 1:06 ` Jiang Xin
2013-05-05 12:35 ` Eric Sunshine
2013-05-06 7:58 ` Matthieu Moy
2013-05-06 9:40 ` Eric Sunshine
2013-05-06 19:18 ` [PATCH v6 0/7] " Jiang Xin
2013-05-06 19:18 ` Jiang Xin [this message]
2013-05-06 19:18 ` [PATCH v6 2/7] Show items of interactive git-clean in columns Jiang Xin
2013-05-06 19:18 ` [PATCH v6 3/7] Add colors to interactive git-clean Jiang Xin
2013-05-06 19:18 ` [PATCH v6 4/7] git-clean: use a git-add-interactive compatible UI Jiang Xin
2013-05-07 4:16 ` Jiang Xin
2013-05-07 15:20 ` Junio C Hamano
2013-05-08 0:28 ` Jiang Xin
2013-05-06 19:18 ` [PATCH v6 5/7] git-clean: interactive cleaning by select numbers Jiang Xin
2013-05-06 19:18 ` [PATCH v6 6/7] git-clean: rm -i style interactive cleaning Jiang Xin
2013-05-06 19:18 ` [PATCH v6 7/7] git-clean: update document for interactive git-clean Jiang Xin
2013-05-07 4:20 ` Jiang Xin
2013-05-03 16:07 ` [PATCH v5 0/3] interactive git clean Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4a32eb7cc4e277fcbf4b46c13524874334bea2a3.1367867498.git.worldhello.net@gmail.com \
--to=worldhello.net@gmail.com \
--cc=Matthieu.Moy@imag.fr \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=sunshine@sunshineco.com \
--cc=trast@inf.ethz.ch \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).