Re: [PATCH v3 7/8] update-ref: support multiple simultaneous updates

2013-09-02 Thread Brad King
On 09/02/2013 01:48 PM, Brad King wrote:
> + /* Parse the argument: */
> + strbuf_reset(arg);
> + if (*next == '"') {
> + if (unquote_c_style(arg, next, &next))
> + die("badly quoted argument: %s", next);
> + return next;
> + }
> + while (*next && !isspace(*next))
> + strbuf_addch(arg, *next++);
> + return next;

This quoting proposal was written in response to $gmane/233479:

On 08/30/2013 06:51 PM, Junio C Hamano wrote:
> When we need to deal with arbitrary strings (like pathnames), other
> parts of the system usually give the user two interfaces, --stdin
> with and without -z, and the strings are C-quoted when run without
> the -z option, and terminated with NUL when run with the -z option.

1. Do we want to allow arbitrary non-space characters in unquoted
arguments (while loop above) or reserve some syntax for future use?

2. Thinking about how the -z variation might work, I ran:

$ git grep '\[0\] == '"'"'"' -- '*.c'
builtin/check-attr.c:   if (line_termination && buf.buf[0] == '"') {
builtin/check-ignore.c: if (line_termination && buf.buf[0] == '"') {
builtin/checkout-index.c:   if (line_termination && 
buf.buf[0] == '"') {
builtin/hash-object.c:  if (buf.buf[0] == '"') {
builtin/mktree.c:   if (line_termination && path[0] == '"') {
builtin/update-index.c: if (line_termination && path_name[0] == '"') {
builtin/update-index.c: if (line_termination && buf.buf[0] == 
'"') {

All of these support quoting only in the non-z mode (the hash-object.c
line follows a getline using hard-coded '\n').  However, they are
all in cases looking for one value on a line or at the end of a line
so their -z option allows NUL-terminated lines containing LF.

What distinguishes the "update-ref --stdin" case is that we want to
represent multiple arguments on one line, each allowing arbitrary
characters or an empty string.  From a brief search a couple places
I found that do something related are:

* apply: Read multiple paths from a diff header, using unquote_c_style
  for quoted paths and separated by spaces.  There is no -z input mode.

* config: Output keyword=value\n becomes keyword\nvalue\0 in -z mode.
  This works because the first piece (keyword) cannot have a LF
  and there is at most one value so all LFs belong to it.

* quote.c: sq_dequote_to_argv handles single quotes like a shell
  would but allows only one space between arguments.  No -z mode.
  This is similar to my v2 proposal.

If we use unquote_c_style and spaces to divide LF-terminated lines,
how shall we divide arguments on NUL-terminated lines?

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


[PATCH v3 7/8] update-ref: support multiple simultaneous updates

2013-09-02 Thread Brad King
Add a --stdin signature to read update instructions from standard input
and apply multiple ref updates together.  Use an input format that
supports any update that could be specified via the command-line,
including object names like "branch:path with space".

Signed-off-by: Brad King 
---
 Documentation/git-update-ref.txt |   20 +++-
 builtin/update-ref.c |  103 +-
 2 files changed, 121 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 0df13ff..01019f1 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely
 SYNOPSIS
 
 [verse]
-'git update-ref' [-m ] (-d  [] | [--no-deref]  
 [])
+'git update-ref' [-m ] (-d  [] | [--no-deref]  
 [] | --stdin)
 
 DESCRIPTION
 ---
@@ -58,6 +58,24 @@ archive by creating a symlink tree).
 With `-d` flag, it deletes the named  after verifying it
 still contains .
 
+With `--stdin`, update-ref reads instructions from standard input and
+performs all modifications together.  Empty lines are ignored.
+Each non-empty line is parsed as whitespace-separated arguments.
+Quote arguments containing whitespace as if in C source code.
+Specify updates with lines of the form:
+
+   [--no-deref] [--]   []
+
+Lines of any other format or a repeated  produce an error.
+Specify a zero  to delete a ref and/or a zero 
+to make sure that a ref not exist.  Use either 40 "0" or the
+empty string (written as "") to specify a zero value.
+
+If all s can be locked with matching s
+simultaneously, all modifications are performed.  Otherwise, no
+modifications are performed.  Note that while each individual
+ is updated or deleted atomically, a concurrent reader may
+still see a subset of the modifications.
 
 Logging Updates
 ---
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 51d2684..12a3c76 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -2,23 +2,115 @@
 #include "refs.h"
 #include "builtin.h"
 #include "parse-options.h"
+#include "quote.h"
 
 static const char * const git_update_ref_usage[] = {
N_("git update-ref [options] -d  []"),
N_("git update-ref [options]  []"),
+   N_("git update-ref [options] --stdin"),
NULL
 };
 
+static int updates_alloc;
+static int updates_count;
+static const struct ref_update **updates;
+
+static const char* update_refs_stdin_next_arg(const char* next,
+ struct strbuf *arg)
+{
+   /* Skip leading whitespace: */
+   while (isspace(*next))
+   ++next;
+
+   /* Return NULL when no argument is found: */
+   if (!*next)
+   return NULL;
+
+   /* Parse the argument: */
+   strbuf_reset(arg);
+   if (*next == '"') {
+   if (unquote_c_style(arg, next, &next))
+   die("badly quoted argument: %s", next);
+   return next;
+   }
+   while (*next && !isspace(*next))
+   strbuf_addch(arg, *next++);
+   return next;
+}
+
+static void update_refs_stdin(const char *line)
+{
+   int options = 1, flags = 0, argc = 0;
+   char *argv[3] = {0, 0, 0};
+   struct strbuf arg = STRBUF_INIT;
+   struct ref_update *update;
+   const char *next = line;
+
+   /* Skip blank lines: */
+   if (!line[0])
+   return;
+
+   /* Parse arguments on this line: */
+   while ((next = update_refs_stdin_next_arg(next, &arg)) != NULL) {
+   if (options && arg.buf[0] == '-')
+   if (!strcmp(arg.buf, "--no-deref"))
+   flags |= REF_NODEREF;
+   else if (!strcmp(arg.buf, "--"))
+   options = 0;
+   else
+   die("unknown option %s", arg.buf);
+   else if (argc >= 3)
+   die("too many arguments on line: %s", line);
+   else {
+   argv[argc++] = xstrdup(arg.buf);
+   options = 0;
+   }
+   }
+   strbuf_release(&arg);
+
+   /* Allocate and zero-init a struct ref_update: */
+   update = xcalloc(1, sizeof(*update));
+   ALLOC_GROW(updates, updates_count+1, updates_alloc);
+   updates[updates_count++] = update;
+
+   /* Set the update ref_name: */
+   if (!argv[0])
+   die("no ref on line: %s", line);
+   if (check_refname_format(argv[0], REFNAME_ALLOW_ONELEVEL))
+   die("invalid ref format on line: %s", line);
+   update->ref_name = argv[0];
+   argv[0] = 0;
+
+   /* Set the update new_sha1 and, if specified, old_sha1: */
+   if (!argv[1])
+   die("missing new value on line: %s", line);
+   if (*argv[1] && get_sha1(argv[1], update->new_sha1))
+