[PATCH] Add a -z option to 'git status' to safely feed shell programs with path names

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add a -z option to 'git status' to get a list of all files
but ignored, starting with modification type or 'untracked'
as prefix and ending with '\0'.

Basically the output is similar to 'git status' but with no
comments and suitable to shell programs that should process
filenames (xargs would help for some of them) in a safe way.

Modifications:

  * Documentation/git-commit.txt,Documentation/git-status.txt:
    add description for -z option.
  * builtin-commit.c: add support for '-z' and complains if
    this option is given to `git-commit`.
  * quote.*: add 'make_path_relative' function which acts like
    the 'quote_path_relative' but do not quote path (only give
    the relative path to the perfix).
  * wt-status.*: add many tests and code to handle '-z'.
---


A file would match the template:

 ([status] [action]|untracked):file_name

Where:

 * `status` is: [ "updated", "changed" ]
 * `action` is: [ "new file", "deleted", "modified",
 	"changetype", "unknown", "unmerged" ]

Both "copied" and "renamed" are no used.

Hope nothing would be broken. This patch was generated against
1fbb58b4153e90eda08c2b022ee32d90729582e6.

Examples:

 * Remove all untracked files:

   git status -z | gawk 'BEGIN{RS="\0"; ORS="\0";}/^untracked/ \
	{sub("^.+:", "");print}' | xargs -0 -r rm

 * List all updated file regardless the modification:

   git status -z | gawk 'BEGIN{RS="\0"; ORS="\0";}/^updated/ \
	{sub("^.+:", "");print}' | xargs -0 -n 1


 Documentation/git-commit.txt |   10 ++++-
 Documentation/git-status.txt |   29 ++++++++++++++
 builtin-commit.c             |    8 +++-
 quote.c                      |   42 ++++++++++++++++++++
 quote.h                      |    2 +
 wt-status.c                  |   87 +++++++++++++++++++++++++++++-------------
 wt-status.h                  |    1 +
 7 files changed, 150 insertions(+), 29 deletions(-)

diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index c3c9f5b..2ffb95b 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -8,7 +8,7 @@ git-commit - Record changes to the repository
 SYNOPSIS
 --------
 [verse]
-'git-commit' [-a | --interactive] [-s] [-v] [-u]
+'git-commit' [-a | --interactive] [-s] [-v] [-u] [-z]
 	   [(-c | -C) <commit> | -F <file> | -m <msg> | --amend]
 	   [--allow-empty] [--no-verify] [-e] [--author <author>]
 	   [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
@@ -166,6 +166,14 @@ but can be used to amend a merge commit.
 -q|--quiet::
 	Suppress commit summary message.
 
+-z::
+	Only if calling linkgit:git-status[1]. Show all file
+	names that are not ignored, prefixed by its modification
+	type (or untracked) and suffixed by '\0'. Useful for
+	processing file with unsafe names.
+	See linkgit:git-status[1] for more information about tag
+	prefix.
+
 \--::
 	Do not interpret any more arguments as options.
 
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
index ea4376a..61deb25 100644
--- a/Documentation/git-status.txt
+++ b/Documentation/git-status.txt
@@ -34,6 +34,21 @@ OUTPUT
 The output from this command is designed to be used as a commit
 template comment, and all the output lines are prefixed with '#'.
 
+One exception is made with the `-z` option. In this case, all files
+are printed prefixed with a modification tag, and suffixed with '\0'.
+In that case all files can be piped to any shell program.
+The output format is the following:
+
+ ([status] [action]|untracked):file_name
+
+Where:
+
+  * `status` is: [ "updated", "changed" ]
+  * `action` is: [ "new file", "deleted", "modified", "changetype",
+    "unknown", "unmerged" ]
+
+Both "copied" and "renamed" are no used.
+
 The paths mentioned in the output, unlike many other git commands, are
 made relative to the current directory if you are working in a
 subdirectory (this is on purpose, to help cutting and pasting). See
@@ -57,6 +72,20 @@ to -1 or an unlimited number), the submodule summary will be enabled and a
 summary of commits for modified submodules will be shown (see --summary-limit
 option of linkgit:git-submodule[1]).
 
+EXAMPLES
+--------
+
+Remove all untracked files:
+
+   git status -z | gawk 'BEGIN{RS="\0"; ORS="\0";}/^untracked/ \
+  	{sub("^.+:", "");print}' | xargs -0 -r rm
+
+
+List all updated file regardless the modification:
+
+  git status -z | gawk 'BEGIN{RS="\0"; ORS="\0";}/^updated/ \
+  	{sub("^.+:", "");print}' | xargs -0 -n 1
+
 See Also
 --------
 linkgit:gitignore[5]
diff --git a/builtin-commit.c b/builtin-commit.c
index 0baec6d..2b37a85 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -50,6 +50,8 @@ static char *edit_message, *use_message;
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, untracked_files, no_verify, allow_empty;
+static int ls_files_zeroed;
+
 /*
  * The default commit message cleanup mode will remove the lines
  * beginning with # (shell comments) and leading and trailing
@@ -106,6 +108,8 @@ static struct option builtin_commit_options[] = {
 	OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
 	OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
 
+	OPT_GROUP("List files (only for status)"),
+	OPT_BOOLEAN('z', 0, &ls_files_zeroed, "\\0 line termination on output"),
 	OPT_END()
 };
 
@@ -348,6 +352,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
 	s.index_file = index_file;
 	s.fp = fp;
 	s.nowarn = nowarn;
+	s.ls_files_zeroed = ls_files_zeroed;
 
 	wt_status_print(&s);
 
@@ -899,7 +904,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	git_config(git_commit_config);
 
 	argc = parse_and_validate_options(argc, argv, builtin_commit_usage);
-
+	if (ls_files_zeroed)
+		die("option -z is nonsense with commit");
 	index_file = prepare_index(argc, argv, prefix);
 
 	/* Set up everything for writing the commit object.  This includes
diff --git a/quote.c b/quote.c
index d5cf9d8..6ab6c32 100644
--- a/quote.c
+++ b/quote.c
@@ -303,6 +303,48 @@ char *quote_path_relative(const char *in, int len,
 }
 
 /*
+ * return path as relative to the given prefix but do not quote it
+ * Useful to build a zero-terminated string of the path.
+ * TODO: Factorize this function with quote_path_relative
+ */
+char *make_path_relative(const char *in, int len,
+			  struct strbuf *out, const char *prefix)
+{
+
+	if (len < 0)
+		len = strlen(in);
+
+	/* "../" prefix itself does not need quoting, but "in" might. */
+	strbuf_setlen(out, 0);
+	strbuf_grow(out, len);
+	
+	if (prefix) {
+		int off = 0;
+		while (prefix[off] && off < len && prefix[off] == in[off])
+			if (prefix[off] == '/') {
+				prefix += off + 1;
+				in += off + 1;
+				len -= off + 1;
+				off = 0;
+			} else
+				off++;
+
+		for (; *prefix; prefix++)
+			if (*prefix == '/')
+				strbuf_addstr(out, "../");
+	}
+
+
+	strbuf_addstr(out, in);
+	/* quote_c_style_counted (in, len, out, NULL, 1); */
+
+	if (!out->len)
+		strbuf_addstr(out, "./");
+
+	return out->buf;
+}
+
+/*
  * C-style name unquoting.
  *
  * Quoted should point at the opening double quote.
diff --git a/quote.h b/quote.h
index c5eea6f..1934c60 100644
--- a/quote.h
+++ b/quote.h
@@ -50,6 +50,8 @@ extern void write_name_quotedpfx(const char *pfx, size_t pfxlen,
 /* quote path as relative to the given prefix */
 char *quote_path_relative(const char *in, int len,
 			  struct strbuf *out, const char *prefix);
+char *make_path_relative(const char *in, int len,
+			  struct strbuf *out, const char *prefix);
 
 /* quoting as a string literal for other languages */
 extern void perl_quote_print(FILE *stream, const char *src);
diff --git a/wt-status.c b/wt-status.c
index a44c543..1a007e1 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -93,42 +93,58 @@ static void wt_status_print_filepair(struct wt_status *s,
 	const char *c = color(t);
 	const char *one, *two;
 	struct strbuf onebuf, twobuf;
+	char *updated_prefix="updated ", *changed_prefix="changed ", *pfx = 0;
 
 	strbuf_init(&onebuf, 0);
 	strbuf_init(&twobuf, 0);
-	one = quote_path(p->one->path, -1, &onebuf, s->prefix);
-	two = quote_path(p->two->path, -1, &twobuf, s->prefix);
 
-	color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+	if (!s->ls_files_zeroed) {
+		one = quote_path(p->one->path, -1, &onebuf, s->prefix);
+		two = quote_path(p->two->path, -1, &twobuf, s->prefix);
+		color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+	} else {
+		one = make_path_relative(p->one->path, -1, &onebuf, s->prefix);
+		two = 0;	/* Maybe in a future usage ? */
+		pfx = t != WT_STATUS_CHANGED ? changed_prefix : updated_prefix;
+	}
 	switch (p->status) {
 	case DIFF_STATUS_ADDED:
+		s->ls_files_zeroed ? printf("%snew file:%s%c", pfx, one, 0) :
 		color_fprintf(s->fp, c, "new file:   %s", one);
 		break;
 	case DIFF_STATUS_COPIED:
-		color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
+		if (!s->ls_files_zeroed)
+			color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
 		break;
 	case DIFF_STATUS_DELETED:
+		s->ls_files_zeroed ? printf("%sdeleted:%s%c", pfx, one, 0) :
 		color_fprintf(s->fp, c, "deleted:    %s", one);
 		break;
 	case DIFF_STATUS_MODIFIED:
+		s->ls_files_zeroed ? printf("%smodified:%s%c", pfx, one, 0) :
 		color_fprintf(s->fp, c, "modified:   %s", one);
 		break;
 	case DIFF_STATUS_RENAMED:
-		color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
+		if (!s->ls_files_zeroed)
+			color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
 		break;
 	case DIFF_STATUS_TYPE_CHANGED:
+		s->ls_files_zeroed ? printf("%schangetype:%s%c", pfx, one, 0) :
 		color_fprintf(s->fp, c, "typechange: %s", one);
 		break;
 	case DIFF_STATUS_UNKNOWN:
+		s->ls_files_zeroed ? printf("%sunknown:%s%c", pfx, one, 0) :
 		color_fprintf(s->fp, c, "unknown:    %s", one);
 		break;
 	case DIFF_STATUS_UNMERGED:
+		s->ls_files_zeroed ? printf("%sunmerged:%s%c", pfx, one, 0) :
 		color_fprintf(s->fp, c, "unmerged:   %s", one);
 		break;
 	default:
 		die("bug: unhandled diff status %c", p->status);
 	}
-	fprintf(s->fp, "\n");
+	if (!s->ls_files_zeroed)
+		fprintf(s->fp, "\n");
 	strbuf_release(&onebuf);
 	strbuf_release(&twobuf);
 }
@@ -143,14 +159,16 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q,
 	for (i = 0; i < q->nr; i++) {
 		if (q->queue[i]->status == 'U')
 			continue;
-		if (!shown_header) {
-			wt_status_print_cached_header(s);
-			s->commitable = 1;
-			shown_header = 1;
+		if (!s->ls_files_zeroed) {
+			if (!shown_header) {
+				wt_status_print_cached_header(s);
+				s->commitable = 1;
+				shown_header = 1;
+			}
 		}
 		wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]);
 	}
-	if (shown_header)
+	if (shown_header && !s->ls_files_zeroed)
 		wt_status_print_trailer(s);
 }
 
@@ -163,16 +181,18 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
 	if (q->nr) {
 		const char *msg = use_add_msg;
 		s->workdir_dirty = 1;
-		for (i = 0; i < q->nr; i++)
-			if (q->queue[i]->status == DIFF_STATUS_DELETED) {
-				msg = use_add_rm_msg;
-				break;
-			}
-		wt_status_print_header(s, "Changed but not updated", msg);
+		if (!s->ls_files_zeroed) {
+			for (i = 0; i < q->nr; i++)
+				if (q->queue[i]->status == DIFF_STATUS_DELETED) {
+					msg = use_add_rm_msg;
+					break;
+				}
+			wt_status_print_header(s, "Changed but not updated", msg);
+		}
 	}
 	for (i = 0; i < q->nr; i++)
 		wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]);
-	if (q->nr)
+	if (q->nr && !s->ls_files_zeroed)
 		wt_status_print_trailer(s);
 }
 
@@ -284,16 +304,21 @@ static void wt_status_print_untracked(struct wt_status *s)
 			    !memcmp(ce->name, ent->name, ent->len))
 				continue;
 		}
-		if (!shown_header) {
-			s->workdir_untracked = 1;
-			wt_status_print_header(s, "Untracked files",
-					       use_add_to_include_msg);
-			shown_header = 1;
+		if (s->ls_files_zeroed) {
+			printf("untracked:%s%c", make_path_relative(ent->name,
+				ent->len, &buf, s->prefix), 0);
+		} else {
+			if (!shown_header) {
+				s->workdir_untracked = 1;
+				wt_status_print_header(s, "Untracked files",
+						       use_add_to_include_msg);
+				shown_header = 1;
+			}
+			color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
+			color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s",
+					quote_path(ent->name, ent->len,
+						&buf, s->prefix));
 		}
-		color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
-		color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s",
-				quote_path(ent->name, ent->len,
-					&buf, s->prefix));
 	}
 	strbuf_release(&buf);
 }
@@ -316,6 +341,14 @@ void wt_status_print(struct wt_status *s)
 	unsigned char sha1[20];
 	s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
 
+	if(s->ls_files_zeroed) {
+		wt_status_print_updated(s);
+		wt_status_print_changed(s);
+		wt_status_print_untracked(s);
+		return;
+	}
+
+
 	if (s->branch) {
 		const char *on_what = "On branch ";
 		const char *branch_name = s->branch;
diff --git a/wt-status.h b/wt-status.h
index 7d61410..7a02472 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -25,6 +25,7 @@ struct wt_status {
 	const char *index_file;
 	FILE *fp;
 	const char *prefix;
+	int ls_files_zeroed;
 };
 
 int git_status_config(const char *var, const char *value);
-- 
1.5.5.1

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux