update-ref: support multiple simultaneous updates

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 <brad.king@kitware.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brad King committed Sep 9, 2013 at 09:22 UTC c750ba9519f7e25a22072578a80ac0eafb423ed1
2 files changed +304 -2
Documentation/git-update-ref.txt
+53 -1
@@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely
8 SYNOPSIS
9 --------
10 [verse]
11 -'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])
11 +'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
12
13 DESCRIPTION
14 -----------
@@ -58,6 +58,58 @@ archive by creating a symlink tree).
58 With `-d` flag, it deletes the named <ref> after verifying it
59 still contains <oldvalue>.
60
61 +With `--stdin`, update-ref reads instructions from standard input and
62 +performs all modifications together. Specify commands of the form:
63 +
64 + update SP <ref> SP <newvalue> [SP <oldvalue>] LF
65 + create SP <ref> SP <newvalue> LF
66 + delete SP <ref> [SP <oldvalue>] LF
67 + verify SP <ref> [SP <oldvalue>] LF
68 + option SP <opt> LF
69 +
70 +Quote fields containing whitespace as if they were strings in C source
71 +code. Alternatively, use `-z` to specify commands without quoting:
72 +
73 + update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL
74 + create SP <ref> NUL <newvalue> NUL
75 + delete SP <ref> NUL [<oldvalue>] NUL
76 + verify SP <ref> NUL [<oldvalue>] NUL
77 + option SP <opt> NUL
78 +
79 +Lines of any other format or a repeated <ref> produce an error.
80 +Command meanings are:
81 +
82 +update::
83 + Set <ref> to <newvalue> after verifying <oldvalue>, if given.
84 + Specify a zero <newvalue> to ensure the ref does not exist
85 + after the update and/or a zero <oldvalue> to make sure the
86 + ref does not exist before the update.
87 +
88 +create::
89 + Create <ref> with <newvalue> after verifying it does not
90 + exist. The given <newvalue> may not be zero.
91 +
92 +delete::
93 + Delete <ref> after verifying it exists with <oldvalue>, if
94 + given. If given, <oldvalue> may not be zero.
95 +
96 +verify::
97 + Verify <ref> against <oldvalue> but do not change it. If
98 + <oldvalue> zero or missing, the ref must not exist.
99 +
100 +option::
101 + Modify behavior of the next command naming a <ref>.
102 + The only valid option is `no-deref` to avoid dereferencing
103 + a symbolic ref.
104 +
105 +Use 40 "0" or the empty string to specify a zero value, except that
106 +with `-z` an empty <oldvalue> is considered missing.
107 +
108 +If all <ref>s can be locked with matching <oldvalue>s
109 +simultaneously, all modifications are performed. Otherwise, no
110 +modifications are performed. Note that while each individual
111 +<ref> is updated or deleted atomically, a concurrent reader may
112 +still see a subset of the modifications.
113
114 Logging Updates
115 ---------------
builtin/update-ref.c
+251 -1
@@ -2,23 +2,261 @@
2 #include "refs.h"
3 #include "builtin.h"
4 #include "parse-options.h"
5 +#include "quote.h"
6 +#include "argv-array.h"
7
8 static const char * const git_update_ref_usage[] = {
9 N_("git update-ref [options] -d <refname> [<oldval>]"),
10 N_("git update-ref [options] <refname> <newval> [<oldval>]"),
11 + N_("git update-ref [options] --stdin [-z]"),
12 NULL
13 };
14
15 +static int updates_alloc;
16 +static int updates_count;
17 +static const struct ref_update **updates;
18 +
19 +static char line_termination = '\n';
20 +static int update_flags;
21 +
22 +static struct ref_update *update_alloc(void)
23 +{
24 + struct ref_update *update;
25 +
26 + /* Allocate and zero-init a struct ref_update */
27 + update = xcalloc(1, sizeof(*update));
28 + ALLOC_GROW(updates, updates_count + 1, updates_alloc);
29 + updates[updates_count++] = update;
30 +
31 + /* Store and reset accumulated options */
32 + update->flags = update_flags;
33 + update_flags = 0;
34 +
35 + return update;
36 +}
37 +
38 +static void update_store_ref_name(struct ref_update *update,
39 + const char *ref_name)
40 +{
41 + if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
42 + die("invalid ref format: %s", ref_name);
43 + update->ref_name = xstrdup(ref_name);
44 +}
45 +
46 +static void update_store_new_sha1(struct ref_update *update,
47 + const char *newvalue)
48 +{
49 + if (*newvalue && get_sha1(newvalue, update->new_sha1))
50 + die("invalid new value for ref %s: %s",
51 + update->ref_name, newvalue);
52 +}
53 +
54 +static void update_store_old_sha1(struct ref_update *update,
55 + const char *oldvalue)
56 +{
57 + if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
58 + die("invalid old value for ref %s: %s",
59 + update->ref_name, oldvalue);
60 +
61 + /* We have an old value if non-empty, or if empty without -z */
62 + update->have_old = *oldvalue || line_termination;
63 +}
64 +
65 +static const char *parse_arg(const char *next, struct strbuf *arg)
66 +{
67 + /* Parse SP-terminated, possibly C-quoted argument */
68 + if (*next != '"')
69 + while (*next && !isspace(*next))
70 + strbuf_addch(arg, *next++);
71 + else if (unquote_c_style(arg, next, &next))
72 + die("badly quoted argument: %s", next);
73 +
74 + /* Return position after the argument */
75 + return next;
76 +}
77 +
78 +static const char *parse_first_arg(const char *next, struct strbuf *arg)
79 +{
80 + /* Parse argument immediately after "command SP" */
81 + strbuf_reset(arg);
82 + if (line_termination) {
83 + /* Without -z, use the next argument */
84 + next = parse_arg(next, arg);
85 + } else {
86 + /* With -z, use rest of first NUL-terminated line */
87 + strbuf_addstr(arg, next);
88 + next = next + arg->len;
89 + }
90 + return next;
91 +}
92 +
93 +static const char *parse_next_arg(const char *next, struct strbuf *arg)
94 +{
95 + /* Parse next SP-terminated or NUL-terminated argument, if any */
96 + strbuf_reset(arg);
97 + if (line_termination) {
98 + /* Without -z, consume SP and use next argument */
99 + if (!*next)
100 + return NULL;
101 + if (*next != ' ')
102 + die("expected SP but got: %s", next);
103 + next = parse_arg(next + 1, arg);
104 + } else {
105 + /* With -z, read the next NUL-terminated line */
106 + if (*next)
107 + die("expected NUL but got: %s", next);
108 + if (strbuf_getline(arg, stdin, '\0') == EOF)
109 + return NULL;
110 + next = arg->buf + arg->len;
111 + }
112 + return next;
113 +}
114 +
115 +static void parse_cmd_update(const char *next)
116 +{
117 + struct strbuf ref = STRBUF_INIT;
118 + struct strbuf newvalue = STRBUF_INIT;
119 + struct strbuf oldvalue = STRBUF_INIT;
120 + struct ref_update *update;
121 +
122 + update = update_alloc();
123 +
124 + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
125 + update_store_ref_name(update, ref.buf);
126 + else
127 + die("update line missing <ref>");
128 +
129 + if ((next = parse_next_arg(next, &newvalue)) != NULL)
130 + update_store_new_sha1(update, newvalue.buf);
131 + else
132 + die("update %s missing <newvalue>", ref.buf);
133 +
134 + if ((next = parse_next_arg(next, &oldvalue)) != NULL)
135 + update_store_old_sha1(update, oldvalue.buf);
136 + else if(!line_termination)
137 + die("update %s missing [<oldvalue>] NUL", ref.buf);
138 +
139 + if (next && *next)
140 + die("update %s has extra input: %s", ref.buf, next);
141 +}
142 +
143 +static void parse_cmd_create(const char *next)
144 +{
145 + struct strbuf ref = STRBUF_INIT;
146 + struct strbuf newvalue = STRBUF_INIT;
147 + struct ref_update *update;
148 +
149 + update = update_alloc();
150 +
151 + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
152 + update_store_ref_name(update, ref.buf);
153 + else
154 + die("create line missing <ref>");
155 +
156 + if ((next = parse_next_arg(next, &newvalue)) != NULL)
157 + update_store_new_sha1(update, newvalue.buf);
158 + else
159 + die("create %s missing <newvalue>", ref.buf);
160 + if (is_null_sha1(update->new_sha1))
161 + die("create %s given zero new value", ref.buf);
162 +
163 + if (next && *next)
164 + die("create %s has extra input: %s", ref.buf, next);
165 +}
166 +
167 +static void parse_cmd_delete(const char *next)
168 +{
169 + struct strbuf ref = STRBUF_INIT;
170 + struct strbuf oldvalue = STRBUF_INIT;
171 + struct ref_update *update;
172 +
173 + update = update_alloc();
174 +
175 + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
176 + update_store_ref_name(update, ref.buf);
177 + else
178 + die("delete line missing <ref>");
179 +
180 + if ((next = parse_next_arg(next, &oldvalue)) != NULL)
181 + update_store_old_sha1(update, oldvalue.buf);
182 + else if(!line_termination)
183 + die("delete %s missing [<oldvalue>] NUL", ref.buf);
184 + if (update->have_old && is_null_sha1(update->old_sha1))
185 + die("delete %s given zero old value", ref.buf);
186 +
187 + if (next && *next)
188 + die("delete %s has extra input: %s", ref.buf, next);
189 +}
190 +
191 +static void parse_cmd_verify(const char *next)
192 +{
193 + struct strbuf ref = STRBUF_INIT;
194 + struct strbuf value = STRBUF_INIT;
195 + struct ref_update *update;
196 +
197 + update = update_alloc();
198 +
199 + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
200 + update_store_ref_name(update, ref.buf);
201 + else
202 + die("verify line missing <ref>");
203 +
204 + if ((next = parse_next_arg(next, &value)) != NULL) {
205 + update_store_old_sha1(update, value.buf);
206 + update_store_new_sha1(update, value.buf);
207 + } else if(!line_termination)
208 + die("verify %s missing [<oldvalue>] NUL", ref.buf);
209 +
210 + if (next && *next)
211 + die("verify %s has extra input: %s", ref.buf, next);
212 +}
213 +
214 +static void parse_cmd_option(const char *next)
215 +{
216 + if (!strcmp(next, "no-deref"))
217 + update_flags |= REF_NODEREF;
218 + else
219 + die("option unknown: %s", next);
220 +}
221 +
222 +static void update_refs_stdin(void)
223 +{
224 + struct strbuf cmd = STRBUF_INIT;
225 +
226 + /* Read each line dispatch its command */
227 + while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
228 + if (!cmd.buf[0])
229 + die("empty command in input");
230 + else if (isspace(*cmd.buf))
231 + die("whitespace before command: %s", cmd.buf);
232 + else if (!prefixcmp(cmd.buf, "update "))
233 + parse_cmd_update(cmd.buf + 7);
234 + else if (!prefixcmp(cmd.buf, "create "))
235 + parse_cmd_create(cmd.buf + 7);
236 + else if (!prefixcmp(cmd.buf, "delete "))
237 + parse_cmd_delete(cmd.buf + 7);
238 + else if (!prefixcmp(cmd.buf, "verify "))
239 + parse_cmd_verify(cmd.buf + 7);
240 + else if (!prefixcmp(cmd.buf, "option "))
241 + parse_cmd_option(cmd.buf + 7);
242 + else
243 + die("unknown command: %s", cmd.buf);
244 +
245 + strbuf_release(&cmd);
246 +}
247 +
248 int cmd_update_ref(int argc, const char **argv, const char *prefix)
249 {
250 const char *refname, *oldval, *msg = NULL;
251 unsigned char sha1[20], oldsha1[20];
16 - int delete = 0, no_deref = 0, flags = 0;
252 + int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
253 struct option options[] = {
254 OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
255 OPT_BOOLEAN('d', NULL, &delete, N_("delete the reference")),
256 + OPT_BOOLEAN('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
257 OPT_BOOLEAN( 0 , "no-deref", &no_deref,
258 N_("update <refname> not the one it points to")),
259 + OPT_BOOLEAN( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
260 OPT_END(),
261 };
262
@@ -28,6 +266,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
266 if (msg && !*msg)
267 die("Refusing to perform update with empty message.");
268
269 + if (read_stdin) {
270 + if (delete || no_deref || argc > 0)
271 + usage_with_options(git_update_ref_usage, options);
272 + if (end_null)
273 + line_termination = '\0';
274 + update_refs_stdin();
275 + return update_refs(msg, updates, updates_count, DIE_ON_ERR);
276 + }
277 +
278 + if (end_null)
279 + usage_with_options(git_update_ref_usage, options);
280 +
281 if (delete) {
282 if (argc < 1 || argc > 2)
283 usage_with_options(git_update_ref_usage, options);