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);