From 2be778a8ac557931c270fb09fa6b4b3c4ec0729c Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 30 Aug 2013 14:11:59 -0400 Subject: reset: rename update_refs to reset_refs The function resets refs rather than doing arbitrary updates. Rename it to allow a future general-purpose update_refs function to be added. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- builtin/reset.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'builtin') diff --git a/builtin/reset.c b/builtin/reset.c index afa6e020e8..789ee489b7 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -219,7 +219,7 @@ static const char **parse_args(const char **argv, const char *prefix, const char return argv[0] ? get_pathspec(prefix, argv) : NULL; } -static int update_refs(const char *rev, const unsigned char *sha1) +static int reset_refs(const char *rev, const unsigned char *sha1) { int update_ref_status; struct strbuf msg = STRBUF_INIT; @@ -350,7 +350,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (!pathspec && !unborn) { /* Any resets without paths update HEAD to the head being * switched to, saving the previous head in ORIG_HEAD before. */ - update_ref_status = update_refs(rev, sha1); + update_ref_status = reset_refs(rev, sha1); if (reset_type == HARD && !update_ref_status && !quiet) print_new_head_line(lookup_commit_reference(sha1)); -- cgit v1.3-5-g9baa From 9bbb0fa1fdc6c413b1f35c26e090515e5d0096aa Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 30 Aug 2013 14:12:00 -0400 Subject: refs: report ref type from lock_any_ref_for_update Expose lock_ref_sha1_basic's type_p argument to callers of lock_any_ref_for_update. Update all call sites to ignore it by passing NULL for now. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- branch.c | 2 +- builtin/commit.c | 2 +- builtin/fetch.c | 3 ++- builtin/receive-pack.c | 3 ++- builtin/reflog.c | 2 +- builtin/replace.c | 2 +- builtin/tag.c | 2 +- fast-import.c | 2 +- refs.c | 7 ++++--- refs.h | 2 +- sequencer.c | 3 ++- 11 files changed, 17 insertions(+), 13 deletions(-) (limited to 'builtin') diff --git a/branch.c b/branch.c index c5c6984cb5..f2d383fa77 100644 --- a/branch.c +++ b/branch.c @@ -291,7 +291,7 @@ void create_branch(const char *head, hashcpy(sha1, commit->object.sha1); if (!dont_change_ref) { - lock = lock_any_ref_for_update(ref.buf, NULL, 0); + lock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL); if (!lock) die_errno(_("Failed to lock ref for update")); } diff --git a/builtin/commit.c b/builtin/commit.c index 10acc53f80..be08f4153e 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1618,7 +1618,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) !current_head ? NULL : current_head->object.sha1, - 0); + 0, NULL); nl = strchr(sb.buf, '\n'); if (nl) diff --git a/builtin/fetch.c b/builtin/fetch.c index d784b2e694..28e40255be 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -246,7 +246,8 @@ static int s_update_ref(const char *action, rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); lock = lock_any_ref_for_update(ref->name, - check_old ? ref->old_sha1 : NULL, 0); + check_old ? ref->old_sha1 : NULL, + 0, NULL); if (!lock) return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : STORE_REF_ERROR_OTHER; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e3eb5fc058..a32307038e 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -524,7 +524,8 @@ static const char *update(struct command *cmd) return NULL; /* good */ } else { - lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0); + lock = lock_any_ref_for_update(namespaced_name, old_sha1, + 0, NULL); if (!lock) { rp_error("failed to lock %s", name); return "failed to lock"; diff --git a/builtin/reflog.c b/builtin/reflog.c index 54184b3d13..28d756a418 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -366,7 +366,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, * we take the lock for the ref itself to prevent it from * getting updated. */ - lock = lock_any_ref_for_update(ref, sha1, 0); + lock = lock_any_ref_for_update(ref, sha1, 0, NULL); if (!lock) return error("cannot lock ref '%s'", ref); log_file = git_pathdup("logs/%s", ref); diff --git a/builtin/replace.c b/builtin/replace.c index 59d31152d0..1ecae8d076 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -105,7 +105,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, else if (!force) die("replace ref '%s' already exists", ref); - lock = lock_any_ref_for_update(ref, prev, 0); + lock = lock_any_ref_for_update(ref, prev, 0, NULL); if (!lock) die("%s: cannot lock the ref", ref); if (write_ref_sha1(lock, repl, NULL) < 0) diff --git a/builtin/tag.c b/builtin/tag.c index af3af3f649..2c867d2c09 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -577,7 +577,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (annotate) create_tag(object, tag, &buf, &opt, prev, object); - lock = lock_any_ref_for_update(ref.buf, prev, 0); + lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL); if (!lock) die(_("%s: cannot lock the ref"), ref.buf); if (write_ref_sha1(lock, object, NULL) < 0) diff --git a/fast-import.c b/fast-import.c index 23f625f561..5c329f6009 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1678,7 +1678,7 @@ static int update_branch(struct branch *b) return 0; if (read_ref(b->name, old_sha1)) hashclr(old_sha1); - lock = lock_any_ref_for_update(b->name, old_sha1, 0); + lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL); if (!lock) return error("Unable to lock %s", b->name); if (!force_update && !is_null_sha1(old_sha1)) { diff --git a/refs.c b/refs.c index 7922261580..c69fd68021 100644 --- a/refs.c +++ b/refs.c @@ -2121,11 +2121,12 @@ struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha } struct ref_lock *lock_any_ref_for_update(const char *refname, - const unsigned char *old_sha1, int flags) + const unsigned char *old_sha1, + int flags, int *type_p) { if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) return NULL; - return lock_ref_sha1_basic(refname, old_sha1, flags, NULL); + return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); } /* @@ -3174,7 +3175,7 @@ int update_ref(const char *action, const char *refname, int flags, enum action_on_err onerr) { static struct ref_lock *lock; - lock = lock_any_ref_for_update(refname, oldval, flags); + lock = lock_any_ref_for_update(refname, oldval, flags, NULL); if (!lock) { const char *str = "Cannot lock the ref '%s'."; switch (onerr) { diff --git a/refs.h b/refs.h index 9e5db3ae26..2cd307af25 100644 --- a/refs.h +++ b/refs.h @@ -137,7 +137,7 @@ extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char * #define REF_NODEREF 0x01 extern struct ref_lock *lock_any_ref_for_update(const char *refname, const unsigned char *old_sha1, - int flags); + int flags, int *type_p); /** Close the file descriptor owned by a lock and return the status */ extern int close_ref(struct ref_lock *lock); diff --git a/sequencer.c b/sequencer.c index 351548f57d..06e52b4c83 100644 --- a/sequencer.c +++ b/sequencer.c @@ -279,7 +279,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, read_cache(); if (checkout_fast_forward(from, to, 1)) exit(1); /* the callee should have complained already */ - ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0); + ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, + 0, NULL); strbuf_addf(&sb, "%s: fast-forward", action_name(opts)); ret = write_ref_sha1(ref_lock, to, sb.buf); strbuf_release(&sb); -- cgit v1.3-5-g9baa From c750ba9519f7e25a22072578a80ac0eafb423ed1 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 9 Sep 2013 09:22:32 -0400 Subject: 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 Signed-off-by: Junio C Hamano --- Documentation/git-update-ref.txt | 54 ++++++++- builtin/update-ref.c | 252 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 304 insertions(+), 2 deletions(-) (limited to 'builtin') diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index 0df13ff6f4..0a0a5512b3 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 [-z]) DESCRIPTION ----------- @@ -58,6 +58,58 @@ 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. Specify commands of the form: + + update SP SP [SP ] LF + create SP SP LF + delete SP [SP ] LF + verify SP [SP ] LF + option SP LF + +Quote fields containing whitespace as if they were strings in C source +code. Alternatively, use `-z` to specify commands without quoting: + + update SP NUL NUL [] NUL + create SP NUL NUL + delete SP NUL [] NUL + verify SP NUL [] NUL + option SP NUL + +Lines of any other format or a repeated produce an error. +Command meanings are: + +update:: + Set to after verifying , if given. + Specify a zero to ensure the ref does not exist + after the update and/or a zero to make sure the + ref does not exist before the update. + +create:: + Create with after verifying it does not + exist. The given may not be zero. + +delete:: + Delete after verifying it exists with , if + given. If given, may not be zero. + +verify:: + Verify against but do not change it. If + zero or missing, the ref must not exist. + +option:: + Modify behavior of the next command naming a . + The only valid option is `no-deref` to avoid dereferencing + a symbolic ref. + +Use 40 "0" or the empty string to specify a zero value, except that +with `-z` an empty is considered missing. + +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 51d2684859..894f16bc59 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -2,23 +2,261 @@ #include "refs.h" #include "builtin.h" #include "parse-options.h" +#include "quote.h" +#include "argv-array.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 [-z]"), NULL }; +static int updates_alloc; +static int updates_count; +static const struct ref_update **updates; + +static char line_termination = '\n'; +static int update_flags; + +static struct ref_update *update_alloc(void) +{ + struct ref_update *update; + + /* 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; + + /* Store and reset accumulated options */ + update->flags = update_flags; + update_flags = 0; + + return update; +} + +static void update_store_ref_name(struct ref_update *update, + const char *ref_name) +{ + if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL)) + die("invalid ref format: %s", ref_name); + update->ref_name = xstrdup(ref_name); +} + +static void update_store_new_sha1(struct ref_update *update, + const char *newvalue) +{ + if (*newvalue && get_sha1(newvalue, update->new_sha1)) + die("invalid new value for ref %s: %s", + update->ref_name, newvalue); +} + +static void update_store_old_sha1(struct ref_update *update, + const char *oldvalue) +{ + if (*oldvalue && get_sha1(oldvalue, update->old_sha1)) + die("invalid old value for ref %s: %s", + update->ref_name, oldvalue); + + /* We have an old value if non-empty, or if empty without -z */ + update->have_old = *oldvalue || line_termination; +} + +static const char *parse_arg(const char *next, struct strbuf *arg) +{ + /* Parse SP-terminated, possibly C-quoted argument */ + if (*next != '"') + while (*next && !isspace(*next)) + strbuf_addch(arg, *next++); + else if (unquote_c_style(arg, next, &next)) + die("badly quoted argument: %s", next); + + /* Return position after the argument */ + return next; +} + +static const char *parse_first_arg(const char *next, struct strbuf *arg) +{ + /* Parse argument immediately after "command SP" */ + strbuf_reset(arg); + if (line_termination) { + /* Without -z, use the next argument */ + next = parse_arg(next, arg); + } else { + /* With -z, use rest of first NUL-terminated line */ + strbuf_addstr(arg, next); + next = next + arg->len; + } + return next; +} + +static const char *parse_next_arg(const char *next, struct strbuf *arg) +{ + /* Parse next SP-terminated or NUL-terminated argument, if any */ + strbuf_reset(arg); + if (line_termination) { + /* Without -z, consume SP and use next argument */ + if (!*next) + return NULL; + if (*next != ' ') + die("expected SP but got: %s", next); + next = parse_arg(next + 1, arg); + } else { + /* With -z, read the next NUL-terminated line */ + if (*next) + die("expected NUL but got: %s", next); + if (strbuf_getline(arg, stdin, '\0') == EOF) + return NULL; + next = arg->buf + arg->len; + } + return next; +} + +static void parse_cmd_update(const char *next) +{ + struct strbuf ref = STRBUF_INIT; + struct strbuf newvalue = STRBUF_INIT; + struct strbuf oldvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("update line missing "); + + if ((next = parse_next_arg(next, &newvalue)) != NULL) + update_store_new_sha1(update, newvalue.buf); + else + die("update %s missing ", ref.buf); + + if ((next = parse_next_arg(next, &oldvalue)) != NULL) + update_store_old_sha1(update, oldvalue.buf); + else if(!line_termination) + die("update %s missing [] NUL", ref.buf); + + if (next && *next) + die("update %s has extra input: %s", ref.buf, next); +} + +static void parse_cmd_create(const char *next) +{ + struct strbuf ref = STRBUF_INIT; + struct strbuf newvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("create line missing "); + + if ((next = parse_next_arg(next, &newvalue)) != NULL) + update_store_new_sha1(update, newvalue.buf); + else + die("create %s missing ", ref.buf); + if (is_null_sha1(update->new_sha1)) + die("create %s given zero new value", ref.buf); + + if (next && *next) + die("create %s has extra input: %s", ref.buf, next); +} + +static void parse_cmd_delete(const char *next) +{ + struct strbuf ref = STRBUF_INIT; + struct strbuf oldvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("delete line missing "); + + if ((next = parse_next_arg(next, &oldvalue)) != NULL) + update_store_old_sha1(update, oldvalue.buf); + else if(!line_termination) + die("delete %s missing [] NUL", ref.buf); + if (update->have_old && is_null_sha1(update->old_sha1)) + die("delete %s given zero old value", ref.buf); + + if (next && *next) + die("delete %s has extra input: %s", ref.buf, next); +} + +static void parse_cmd_verify(const char *next) +{ + struct strbuf ref = STRBUF_INIT; + struct strbuf value = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("verify line missing "); + + if ((next = parse_next_arg(next, &value)) != NULL) { + update_store_old_sha1(update, value.buf); + update_store_new_sha1(update, value.buf); + } else if(!line_termination) + die("verify %s missing [] NUL", ref.buf); + + if (next && *next) + die("verify %s has extra input: %s", ref.buf, next); +} + +static void parse_cmd_option(const char *next) +{ + if (!strcmp(next, "no-deref")) + update_flags |= REF_NODEREF; + else + die("option unknown: %s", next); +} + +static void update_refs_stdin(void) +{ + struct strbuf cmd = STRBUF_INIT; + + /* Read each line dispatch its command */ + while (strbuf_getline(&cmd, stdin, line_termination) != EOF) + if (!cmd.buf[0]) + die("empty command in input"); + else if (isspace(*cmd.buf)) + die("whitespace before command: %s", cmd.buf); + else if (!prefixcmp(cmd.buf, "update ")) + parse_cmd_update(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "create ")) + parse_cmd_create(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "delete ")) + parse_cmd_delete(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "verify ")) + parse_cmd_verify(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "option ")) + parse_cmd_option(cmd.buf + 7); + else + die("unknown command: %s", cmd.buf); + + strbuf_release(&cmd); +} + int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname, *oldval, *msg = NULL; unsigned char sha1[20], oldsha1[20]; - int delete = 0, no_deref = 0, flags = 0; + int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_BOOLEAN('d', NULL, &delete, N_("delete the reference")), + OPT_BOOLEAN('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), OPT_BOOLEAN( 0 , "no-deref", &no_deref, N_("update not the one it points to")), + OPT_BOOLEAN( 0 , "stdin", &read_stdin, N_("read updates from stdin")), OPT_END(), }; @@ -28,6 +266,18 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (msg && !*msg) die("Refusing to perform update with empty message."); + if (read_stdin) { + if (delete || no_deref || argc > 0) + usage_with_options(git_update_ref_usage, options); + if (end_null) + line_termination = '\0'; + update_refs_stdin(); + return update_refs(msg, updates, updates_count, DIE_ON_ERR); + } + + if (end_null) + usage_with_options(git_update_ref_usage, options); + if (delete) { if (argc < 1 || argc > 2) usage_with_options(git_update_ref_usage, options); -- cgit v1.3-5-g9baa