Merge branch 'rs/read-ref-at'
authorJunio C Hamano <gitster@pobox.com>
Mon, 16 Jun 2014 19:18:48 +0000 (12:18 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 16 Jun 2014 19:18:48 +0000 (12:18 -0700)
* rs/read-ref-at:
refs.c: change read_ref_at to use the reflog iterators

1  2 
refs.c
t/t1400-update-ref.sh
diff --combined refs.c
index ceeeec645c12a0e35e33d2263dca38829668e444,f1afec5a46eaf1f778365e5b59b5927dea80186c..ca620f07c1a756a5d5549a40810f845cf89b947b
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -1611,7 -1611,6 +1611,7 @@@ int peel_ref(const char *refname, unsig
  struct warn_if_dangling_data {
        FILE *fp;
        const char *refname;
 +      const struct string_list *refnames;
        const char *msg_fmt;
  };
  
@@@ -1626,12 -1625,8 +1626,12 @@@ static int warn_if_dangling_symref(cons
                return 0;
  
        resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
 -      if (!resolves_to || strcmp(resolves_to, d->refname))
 +      if (!resolves_to
 +          || (d->refname
 +              ? strcmp(resolves_to, d->refname)
 +              : !string_list_has_string(d->refnames, resolves_to))) {
                return 0;
 +      }
  
        fprintf(d->fp, d->msg_fmt, refname);
        fputc('\n', d->fp);
@@@ -1644,18 -1639,6 +1644,18 @@@ void warn_dangling_symref(FILE *fp, con
  
        data.fp = fp;
        data.refname = refname;
 +      data.refnames = NULL;
 +      data.msg_fmt = msg_fmt;
 +      for_each_rawref(warn_if_dangling_symref, &data);
 +}
 +
 +void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)
 +{
 +      struct warn_if_dangling_data data;
 +
 +      data.fp = fp;
 +      data.refname = NULL;
 +      data.refnames = refnames;
        data.msg_fmt = msg_fmt;
        for_each_rawref(warn_if_dangling_symref, &data);
  }
@@@ -2016,6 -1999,7 +2016,6 @@@ int dwim_log(const char *str, int len, 
  
        *log = NULL;
        for (p = ref_rev_parse_rules; *p; p++) {
 -              struct stat st;
                unsigned char hash[20];
                char path[PATH_MAX];
                const char *ref, *it;
                ref = resolve_ref_unsafe(path, hash, 1, NULL);
                if (!ref)
                        continue;
 -              if (!stat(git_path("logs/%s", path), &st) &&
 -                  S_ISREG(st.st_mode))
 +              if (reflog_exists(path))
                        it = path;
 -              else if (strcmp(ref, path) &&
 -                       !stat(git_path("logs/%s", ref), &st) &&
 -                       S_ISREG(st.st_mode))
 +              else if (strcmp(ref, path) && reflog_exists(ref))
                        it = ref;
                else
                        continue;
@@@ -2444,7 -2431,7 +2444,7 @@@ static int curate_packed_ref_fn(struct 
        return 0;
  }
  
 -static int repack_without_refs(const char **refnames, int n)
 +int repack_without_refs(const char **refnames, int n)
  {
        struct ref_dir *packed;
        struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
@@@ -2943,135 -2930,120 +2943,133 @@@ int create_symref(const char *ref_targe
        return 0;
  }
  
- static char *ref_msg(const char *line, const char *endp)
- {
-       const char *ep;
-       line += 82;
-       ep = memchr(line, '\n', endp - line);
-       if (!ep)
-               ep = endp;
-       return xmemdupz(line, ep - line);
+ struct read_ref_at_cb {
+       const char *refname;
+       unsigned long at_time;
+       int cnt;
+       int reccnt;
+       unsigned char *sha1;
+       int found_it;
+       unsigned char osha1[20];
+       unsigned char nsha1[20];
+       int tz;
+       unsigned long date;
+       char **msg;
+       unsigned long *cutoff_time;
+       int *cutoff_tz;
+       int *cutoff_cnt;
+ };
+ static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+ {
+       struct read_ref_at_cb *cb = cb_data;
+       cb->reccnt++;
+       cb->tz = tz;
+       cb->date = timestamp;
+       if (timestamp <= cb->at_time || cb->cnt == 0) {
+               if (cb->msg)
+                       *cb->msg = xstrdup(message);
+               if (cb->cutoff_time)
+                       *cb->cutoff_time = timestamp;
+               if (cb->cutoff_tz)
+                       *cb->cutoff_tz = tz;
+               if (cb->cutoff_cnt)
+                       *cb->cutoff_cnt = cb->reccnt - 1;
+               /*
+                * we have not yet updated cb->[n|o]sha1 so they still
+                * hold the values for the previous record.
+                */
+               if (!is_null_sha1(cb->osha1)) {
+                       hashcpy(cb->sha1, nsha1);
+                       if (hashcmp(cb->osha1, nsha1))
+                               warning("Log for ref %s has gap after %s.",
+                                       cb->refname, show_date(cb->date, cb->tz, DATE_RFC2822));
+               }
+               else if (cb->date == cb->at_time)
+                       hashcpy(cb->sha1, nsha1);
+               else if (hashcmp(nsha1, cb->sha1))
+                       warning("Log for ref %s unexpectedly ended on %s.",
+                               cb->refname, show_date(cb->date, cb->tz,
+                                                  DATE_RFC2822));
+               hashcpy(cb->osha1, osha1);
+               hashcpy(cb->nsha1, nsha1);
+               cb->found_it = 1;
+               return 1;
+       }
+       hashcpy(cb->osha1, osha1);
+       hashcpy(cb->nsha1, nsha1);
+       if (cb->cnt > 0)
+               cb->cnt--;
+       return 0;
+ }
+ static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,
+                                 const char *email, unsigned long timestamp,
+                                 int tz, const char *message, void *cb_data)
+ {
+       struct read_ref_at_cb *cb = cb_data;
+       if (cb->msg)
+               *cb->msg = xstrdup(message);
+       if (cb->cutoff_time)
+               *cb->cutoff_time = timestamp;
+       if (cb->cutoff_tz)
+               *cb->cutoff_tz = tz;
+       if (cb->cutoff_cnt)
+               *cb->cutoff_cnt = cb->reccnt;
+       hashcpy(cb->sha1, osha1);
+       if (is_null_sha1(cb->sha1))
+               hashcpy(cb->sha1, nsha1);
+       /* We just want the first entry */
+       return 1;
  }
  
  int read_ref_at(const char *refname, unsigned long at_time, int cnt,
                unsigned char *sha1, char **msg,
                unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
  {
-       const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
-       char *tz_c;
-       int logfd, tz, reccnt = 0;
-       struct stat st;
-       unsigned long date;
-       unsigned char logged_sha1[20];
-       void *log_mapped;
-       size_t mapsz;
+       struct read_ref_at_cb cb;
  
-       logfile = git_path("logs/%s", refname);
-       logfd = open(logfile, O_RDONLY, 0);
-       if (logfd < 0)
-               die_errno("Unable to read log '%s'", logfile);
-       fstat(logfd, &st);
-       if (!st.st_size)
-               die("Log %s is empty.", logfile);
-       mapsz = xsize_t(st.st_size);
-       log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
-       logdata = log_mapped;
-       close(logfd);
+       memset(&cb, 0, sizeof(cb));
+       cb.refname = refname;
+       cb.at_time = at_time;
+       cb.cnt = cnt;
+       cb.msg = msg;
+       cb.cutoff_time = cutoff_time;
+       cb.cutoff_tz = cutoff_tz;
+       cb.cutoff_cnt = cutoff_cnt;
+       cb.sha1 = sha1;
+       for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
+       if (!cb.reccnt)
+               die("Log for %s is empty.", refname);
+       if (cb.found_it)
+               return 0;
+       for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);
  
-       lastrec = NULL;
-       rec = logend = logdata + st.st_size;
-       while (logdata < rec) {
-               reccnt++;
-               if (logdata < rec && *(rec-1) == '\n')
-                       rec--;
-               lastgt = NULL;
-               while (logdata < rec && *(rec-1) != '\n') {
-                       rec--;
-                       if (*rec == '>')
-                               lastgt = rec;
-               }
-               if (!lastgt)
-                       die("Log %s is corrupt.", logfile);
-               date = strtoul(lastgt + 1, &tz_c, 10);
-               if (date <= at_time || cnt == 0) {
-                       tz = strtoul(tz_c, NULL, 10);
-                       if (msg)
-                               *msg = ref_msg(rec, logend);
-                       if (cutoff_time)
-                               *cutoff_time = date;
-                       if (cutoff_tz)
-                               *cutoff_tz = tz;
-                       if (cutoff_cnt)
-                               *cutoff_cnt = reccnt - 1;
-                       if (lastrec) {
-                               if (get_sha1_hex(lastrec, logged_sha1))
-                                       die("Log %s is corrupt.", logfile);
-                               if (get_sha1_hex(rec + 41, sha1))
-                                       die("Log %s is corrupt.", logfile);
-                               if (hashcmp(logged_sha1, sha1)) {
-                                       warning("Log %s has gap after %s.",
-                                               logfile, show_date(date, tz, DATE_RFC2822));
-                               }
-                       }
-                       else if (date == at_time) {
-                               if (get_sha1_hex(rec + 41, sha1))
-                                       die("Log %s is corrupt.", logfile);
-                       }
-                       else {
-                               if (get_sha1_hex(rec + 41, logged_sha1))
-                                       die("Log %s is corrupt.", logfile);
-                               if (hashcmp(logged_sha1, sha1)) {
-                                       warning("Log %s unexpectedly ended on %s.",
-                                               logfile, show_date(date, tz, DATE_RFC2822));
-                               }
-                       }
-                       munmap(log_mapped, mapsz);
-                       return 0;
-               }
-               lastrec = rec;
-               if (cnt > 0)
-                       cnt--;
-       }
-       rec = logdata;
-       while (rec < logend && *rec != '>' && *rec != '\n')
-               rec++;
-       if (rec == logend || *rec == '\n')
-               die("Log %s is corrupt.", logfile);
-       date = strtoul(rec + 1, &tz_c, 10);
-       tz = strtoul(tz_c, NULL, 10);
-       if (get_sha1_hex(logdata, sha1))
-               die("Log %s is corrupt.", logfile);
-       if (is_null_sha1(sha1)) {
-               if (get_sha1_hex(logdata + 41, sha1))
-                       die("Log %s is corrupt.", logfile);
-       }
-       if (msg)
-               *msg = ref_msg(logdata, logend);
-       munmap(log_mapped, mapsz);
-       if (cutoff_time)
-               *cutoff_time = date;
-       if (cutoff_tz)
-               *cutoff_tz = tz;
-       if (cutoff_cnt)
-               *cutoff_cnt = reccnt;
        return 1;
  }
  
 +int reflog_exists(const char *refname)
 +{
 +      struct stat st;
 +
 +      return !lstat(git_path("logs/%s", refname), &st) &&
 +              S_ISREG(st.st_mode);
 +}
 +
 +int delete_reflog(const char *refname)
 +{
 +      return remove_path(git_path("logs/%s", refname));
 +}
 +
  static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
  {
        unsigned char osha1[20], nsha1[20];
@@@ -3269,9 -3241,9 +3267,9 @@@ static struct ref_lock *update_ref_lock
        if (!lock) {
                const char *str = "Cannot lock the ref '%s'.";
                switch (onerr) {
 -              case MSG_ON_ERR: error(str, refname); break;
 -              case DIE_ON_ERR: die(str, refname); break;
 -              case QUIET_ON_ERR: break;
 +              case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
 +              case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
 +              case UPDATE_REFS_QUIET_ON_ERR: break;
                }
        }
        return lock;
@@@ -3284,118 -3256,15 +3282,118 @@@ static int update_ref_write(const char 
        if (write_ref_sha1(lock, sha1, action) < 0) {
                const char *str = "Cannot update the ref '%s'.";
                switch (onerr) {
 -              case MSG_ON_ERR: error(str, refname); break;
 -              case DIE_ON_ERR: die(str, refname); break;
 -              case QUIET_ON_ERR: break;
 +              case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
 +              case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
 +              case UPDATE_REFS_QUIET_ON_ERR: break;
                }
                return 1;
        }
        return 0;
  }
  
 +/**
 + * Information needed for a single ref update.  Set new_sha1 to the
 + * new value or to zero to delete the ref.  To check the old value
 + * while locking the ref, set have_old to 1 and set old_sha1 to the
 + * value or to zero to ensure the ref does not exist before update.
 + */
 +struct ref_update {
 +      unsigned char new_sha1[20];
 +      unsigned char old_sha1[20];
 +      int flags; /* REF_NODEREF? */
 +      int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
 +      struct ref_lock *lock;
 +      int type;
 +      const char refname[FLEX_ARRAY];
 +};
 +
 +/*
 + * Data structure for holding a reference transaction, which can
 + * consist of checks and updates to multiple references, carried out
 + * as atomically as possible.  This structure is opaque to callers.
 + */
 +struct ref_transaction {
 +      struct ref_update **updates;
 +      size_t alloc;
 +      size_t nr;
 +};
 +
 +struct ref_transaction *ref_transaction_begin(void)
 +{
 +      return xcalloc(1, sizeof(struct ref_transaction));
 +}
 +
 +static void ref_transaction_free(struct ref_transaction *transaction)
 +{
 +      int i;
 +
 +      for (i = 0; i < transaction->nr; i++)
 +              free(transaction->updates[i]);
 +
 +      free(transaction->updates);
 +      free(transaction);
 +}
 +
 +void ref_transaction_rollback(struct ref_transaction *transaction)
 +{
 +      ref_transaction_free(transaction);
 +}
 +
 +static struct ref_update *add_update(struct ref_transaction *transaction,
 +                                   const char *refname)
 +{
 +      size_t len = strlen(refname);
 +      struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
 +
 +      strcpy((char *)update->refname, refname);
 +      ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
 +      transaction->updates[transaction->nr++] = update;
 +      return update;
 +}
 +
 +void ref_transaction_update(struct ref_transaction *transaction,
 +                          const char *refname,
 +                          unsigned char *new_sha1, unsigned char *old_sha1,
 +                          int flags, int have_old)
 +{
 +      struct ref_update *update = add_update(transaction, refname);
 +
 +      hashcpy(update->new_sha1, new_sha1);
 +      update->flags = flags;
 +      update->have_old = have_old;
 +      if (have_old)
 +              hashcpy(update->old_sha1, old_sha1);
 +}
 +
 +void ref_transaction_create(struct ref_transaction *transaction,
 +                          const char *refname,
 +                          unsigned char *new_sha1,
 +                          int flags)
 +{
 +      struct ref_update *update = add_update(transaction, refname);
 +
 +      assert(!is_null_sha1(new_sha1));
 +      hashcpy(update->new_sha1, new_sha1);
 +      hashclr(update->old_sha1);
 +      update->flags = flags;
 +      update->have_old = 1;
 +}
 +
 +void ref_transaction_delete(struct ref_transaction *transaction,
 +                          const char *refname,
 +                          unsigned char *old_sha1,
 +                          int flags, int have_old)
 +{
 +      struct ref_update *update = add_update(transaction, refname);
 +
 +      update->flags = flags;
 +      update->have_old = have_old;
 +      if (have_old) {
 +              assert(!is_null_sha1(old_sha1));
 +              hashcpy(update->old_sha1, old_sha1);
 +      }
 +}
 +
  int update_ref(const char *action, const char *refname,
               const unsigned char *sha1, const unsigned char *oldval,
               int flags, enum action_on_err onerr)
@@@ -3411,7 -3280,7 +3409,7 @@@ static int ref_update_compare(const voi
  {
        const struct ref_update * const *u1 = r1;
        const struct ref_update * const *u2 = r2;
 -      return strcmp((*u1)->ref_name, (*u2)->ref_name);
 +      return strcmp((*u1)->refname, (*u2)->refname);
  }
  
  static int ref_update_reject_duplicates(struct ref_update **updates, int n,
  {
        int i;
        for (i = 1; i < n; i++)
 -              if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
 +              if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
                        const char *str =
                                "Multiple updates for ref '%s' not allowed.";
                        switch (onerr) {
 -                      case MSG_ON_ERR:
 -                              error(str, updates[i]->ref_name); break;
 -                      case DIE_ON_ERR:
 -                              die(str, updates[i]->ref_name); break;
 -                      case QUIET_ON_ERR:
 +                      case UPDATE_REFS_MSG_ON_ERR:
 +                              error(str, updates[i]->refname); break;
 +                      case UPDATE_REFS_DIE_ON_ERR:
 +                              die(str, updates[i]->refname); break;
 +                      case UPDATE_REFS_QUIET_ON_ERR:
                                break;
                        }
                        return 1;
        return 0;
  }
  
 -int update_refs(const char *action, const struct ref_update **updates_orig,
 -              int n, enum action_on_err onerr)
 +int ref_transaction_commit(struct ref_transaction *transaction,
 +                         const char *msg, enum action_on_err onerr)
  {
        int ret = 0, delnum = 0, i;
 -      struct ref_update **updates;
 -      int *types;
 -      struct ref_lock **locks;
        const char **delnames;
 +      int n = transaction->nr;
 +      struct ref_update **updates = transaction->updates;
  
 -      if (!updates_orig || !n)
 +      if (!n)
                return 0;
  
        /* Allocate work space */
 -      updates = xmalloc(sizeof(*updates) * n);
 -      types = xmalloc(sizeof(*types) * n);
 -      locks = xcalloc(n, sizeof(*locks));
        delnames = xmalloc(sizeof(*delnames) * n);
  
        /* Copy, sort, and reject duplicate refs */
 -      memcpy(updates, updates_orig, sizeof(*updates) * n);
        qsort(updates, n, sizeof(*updates), ref_update_compare);
        ret = ref_update_reject_duplicates(updates, n, onerr);
        if (ret)
  
        /* Acquire all locks while verifying old values */
        for (i = 0; i < n; i++) {
 -              locks[i] = update_ref_lock(updates[i]->ref_name,
 -                                         (updates[i]->have_old ?
 -                                          updates[i]->old_sha1 : NULL),
 -                                         updates[i]->flags,
 -                                         &types[i], onerr);
 -              if (!locks[i]) {
 +              struct ref_update *update = updates[i];
 +
 +              update->lock = update_ref_lock(update->refname,
 +                                             (update->have_old ?
 +                                              update->old_sha1 : NULL),
 +                                             update->flags,
 +                                             &update->type, onerr);
 +              if (!update->lock) {
                        ret = 1;
                        goto cleanup;
                }
        }
  
        /* Perform updates first so live commits remain referenced */
 -      for (i = 0; i < n; i++)
 -              if (!is_null_sha1(updates[i]->new_sha1)) {
 -                      ret = update_ref_write(action,
 -                                             updates[i]->ref_name,
 -                                             updates[i]->new_sha1,
 -                                             locks[i], onerr);
 -                      locks[i] = NULL; /* freed by update_ref_write */
 +      for (i = 0; i < n; i++) {
 +              struct ref_update *update = updates[i];
 +
 +              if (!is_null_sha1(update->new_sha1)) {
 +                      ret = update_ref_write(msg,
 +                                             update->refname,
 +                                             update->new_sha1,
 +                                             update->lock, onerr);
 +                      update->lock = NULL; /* freed by update_ref_write */
                        if (ret)
                                goto cleanup;
                }
 +      }
  
        /* Perform deletes now that updates are safely completed */
 -      for (i = 0; i < n; i++)
 -              if (locks[i]) {
 -                      delnames[delnum++] = locks[i]->ref_name;
 -                      ret |= delete_ref_loose(locks[i], types[i]);
 +      for (i = 0; i < n; i++) {
 +              struct ref_update *update = updates[i];
 +
 +              if (update->lock) {
 +                      delnames[delnum++] = update->lock->ref_name;
 +                      ret |= delete_ref_loose(update->lock, update->type);
                }
 +      }
 +
        ret |= repack_without_refs(delnames, delnum);
        for (i = 0; i < delnum; i++)
                unlink_or_warn(git_path("logs/%s", delnames[i]));
  
  cleanup:
        for (i = 0; i < n; i++)
 -              if (locks[i])
 -                      unlock_ref(locks[i]);
 -      free(updates);
 -      free(types);
 -      free(locks);
 +              if (updates[i]->lock)
 +                      unlock_ref(updates[i]->lock);
        free(delnames);
 +      ref_transaction_free(transaction);
        return ret;
  }
  
diff --combined t/t1400-update-ref.sh
index 4e2459afc549d5d2985024e8c23bf5fa117d570a,953dacc8e964959fb94a28a85f0bea264c3556b4..0218e96366ddd8659d5a78b42ffcfd415013114c
@@@ -235,7 -235,7 +235,7 @@@ test_expect_success 
        'rm -f o e &&
         git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
         test '"$B"' = $(cat o) &&
-        test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
+        test "warning: Log for ref '"$m has gap after $gd"'." = "$(cat e)"'
  test_expect_success \
        'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
        'rm -f o e &&
@@@ -253,7 -253,7 +253,7 @@@ test_expect_success 
        'rm -f o e &&
         git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
         test '"$D"' = $(cat o) &&
-        test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
+        test "warning: Log for ref '"$m unexpectedly ended on $ld"'." = "$(cat e)"'
  
  
  rm -f .git/$m .git/logs/$m expect
@@@ -350,28 -350,22 +350,28 @@@ test_expect_success 'stdin fails on unk
        grep "fatal: unknown command: unknown $a" err
  '
  
 -test_expect_success 'stdin fails on badly quoted input' '
 +test_expect_success 'stdin fails on unbalanced quotes' '
        echo "create $a \"master" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
        grep "fatal: badly quoted argument: \\\"master" err
  '
  
 -test_expect_success 'stdin fails on arguments not separated by space' '
 +test_expect_success 'stdin fails on invalid escape' '
 +      echo "create $a \"ma\zter\"" >stdin &&
 +      test_must_fail git update-ref --stdin <stdin 2>err &&
 +      grep "fatal: badly quoted argument: \\\"ma\\\\zter\\\"" err
 +'
 +
 +test_expect_success 'stdin fails on junk after quoted argument' '
        echo "create \"$a\"master" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: expected SP but got: master" err
 +      grep "fatal: unexpected character after quoted argument: \\\"$a\\\"master" err
  '
  
  test_expect_success 'stdin fails create with no ref' '
        echo "create " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: create line missing <ref>" err
 +      grep "fatal: create: missing <ref>" err
  '
  
  test_expect_success 'stdin fails create with bad ref name' '
  test_expect_success 'stdin fails create with no new value' '
        echo "create $a" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: create $a missing <newvalue>" err
 +      grep "fatal: create $a: missing <newvalue>" err
  '
  
  test_expect_success 'stdin fails create with too many arguments' '
        echo "create $a $m $m" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: create $a has extra input:  $m" err
 +      grep "fatal: create $a: extra input:  $m" err
  '
  
  test_expect_success 'stdin fails update with no ref' '
        echo "update " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: update line missing <ref>" err
 +      grep "fatal: update: missing <ref>" err
  '
  
  test_expect_success 'stdin fails update with bad ref name' '
  test_expect_success 'stdin fails update with no new value' '
        echo "update $a" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: update $a missing <newvalue>" err
 +      grep "fatal: update $a: missing <newvalue>" err
  '
  
  test_expect_success 'stdin fails update with too many arguments' '
        echo "update $a $m $m $m" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: update $a has extra input:  $m" err
 +      grep "fatal: update $a: extra input:  $m" err
  '
  
  test_expect_success 'stdin fails delete with no ref' '
        echo "delete " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: delete line missing <ref>" err
 +      grep "fatal: delete: missing <ref>" err
  '
  
  test_expect_success 'stdin fails delete with bad ref name' '
  test_expect_success 'stdin fails delete with too many arguments' '
        echo "delete $a $m $m" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: delete $a has extra input:  $m" err
 +      grep "fatal: delete $a: extra input:  $m" err
  '
  
  test_expect_success 'stdin fails verify with too many arguments' '
        echo "verify $a $m $m" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: verify $a has extra input:  $m" err
 +      grep "fatal: verify $a: extra input:  $m" err
  '
  
  test_expect_success 'stdin fails option with unknown name' '
@@@ -464,24 -458,6 +464,24 @@@ test_expect_success 'stdin create ref w
        test_cmp expect actual
  '
  
 +test_expect_success 'stdin succeeds with quoted argument' '
 +      git update-ref -d $a &&
 +      echo "create $a \"$m\"" >stdin &&
 +      git update-ref --stdin <stdin &&
 +      git rev-parse $m >expect &&
 +      git rev-parse $a >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'stdin succeeds with escaped character' '
 +      git update-ref -d $a &&
 +      echo "create $a \"ma\\163ter\"" >stdin &&
 +      git update-ref --stdin <stdin &&
 +      git rev-parse $m >expect &&
 +      git rev-parse $a >actual &&
 +      test_cmp expect actual
 +'
 +
  test_expect_success 'stdin update ref creates with zero old value' '
        echo "update $b $m $Z" >stdin &&
        git update-ref --stdin <stdin &&
@@@ -518,21 -494,21 +518,21 @@@ test_expect_success 'stdin update ref f
  test_expect_success 'stdin update ref fails with bad old value' '
        echo "update $c $m does-not-exist" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: invalid old value for ref $c: does-not-exist" err &&
 +      grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
        test_must_fail git rev-parse --verify -q $c
  '
  
  test_expect_success 'stdin create ref fails with bad new value' '
        echo "create $c does-not-exist" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: invalid new value for ref $c: does-not-exist" err &&
 +      grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
        test_must_fail git rev-parse --verify -q $c
  '
  
  test_expect_success 'stdin create ref fails with zero new value' '
        echo "create $c " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: create $c given zero new value" err &&
 +      grep "fatal: create $c: zero <newvalue>" err &&
        test_must_fail git rev-parse --verify -q $c
  '
  
@@@ -556,7 -532,7 +556,7 @@@ test_expect_success 'stdin delete ref f
  test_expect_success 'stdin delete ref fails with zero old value' '
        echo "delete $a " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
 -      grep "fatal: delete $a given zero old value" err &&
 +      grep "fatal: delete $a: zero <oldvalue>" err &&
        git rev-parse $m >expect &&
        git rev-parse $a >actual &&
        test_cmp expect actual
@@@ -697,7 -673,7 +697,7 @@@ test_expect_success 'stdin -z fails on 
  test_expect_success 'stdin -z fails create with no ref' '
        printf $F "create " >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: create line missing <ref>" err
 +      grep "fatal: create: missing <ref>" err
  '
  
  test_expect_success 'stdin -z fails create with bad ref name' '
  test_expect_success 'stdin -z fails create with no new value' '
        printf $F "create $a" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: create $a missing <newvalue>" err
 +      grep "fatal: create $a: unexpected end of input when reading <newvalue>" err
  '
  
  test_expect_success 'stdin -z fails create with too many arguments' '
  test_expect_success 'stdin -z fails update with no ref' '
        printf $F "update " >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: update line missing <ref>" err
 +      grep "fatal: update: missing <ref>" err
 +'
 +
 +test_expect_success 'stdin -z fails update with too few args' '
 +      printf $F "update $a" "$m" >stdin &&
 +      test_must_fail git update-ref -z --stdin <stdin 2>err &&
 +      grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
  '
  
  test_expect_success 'stdin -z fails update with bad ref name' '
 -      printf $F "update ~a" "$m" >stdin &&
 +      printf $F "update ~a" "$m" "" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
        grep "fatal: invalid ref format: ~a" err
  '
  
 +test_expect_success 'stdin -z emits warning with empty new value' '
 +      git update-ref $a $m &&
 +      printf $F "update $a" "" "" >stdin &&
 +      git update-ref -z --stdin <stdin 2>err &&
 +      grep "warning: update $a: missing <newvalue>, treating as zero" err &&
 +      test_must_fail git rev-parse --verify -q $a
 +'
 +
  test_expect_success 'stdin -z fails update with no new value' '
        printf $F "update $a" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: update $a missing <newvalue>" err
 +      grep "fatal: update $a: unexpected end of input when reading <newvalue>" err
  '
  
  test_expect_success 'stdin -z fails update with no old value' '
        printf $F "update $a" "$m" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: update $a missing \\[<oldvalue>\\] NUL" err
 +      grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
  '
  
  test_expect_success 'stdin -z fails update with too many arguments' '
  test_expect_success 'stdin -z fails delete with no ref' '
        printf $F "delete " >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: delete line missing <ref>" err
 +      grep "fatal: delete: missing <ref>" err
  '
  
  test_expect_success 'stdin -z fails delete with bad ref name' '
  test_expect_success 'stdin -z fails delete with no old value' '
        printf $F "delete $a" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: delete $a missing \\[<oldvalue>\\] NUL" err
 +      grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err
  '
  
  test_expect_success 'stdin -z fails delete with too many arguments' '
@@@ -795,7 -757,7 +795,7 @@@ test_expect_success 'stdin -z fails ver
  test_expect_success 'stdin -z fails verify with no old value' '
        printf $F "verify $a" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: verify $a missing \\[<oldvalue>\\] NUL" err
 +      grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err
  '
  
  test_expect_success 'stdin -z fails option with unknown name' '
@@@ -854,7 -816,7 +854,7 @@@ test_expect_success 'stdin -z update re
  test_expect_success 'stdin -z update ref fails with bad old value' '
        printf $F "update $c" "$m" "does-not-exist" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: invalid old value for ref $c: does-not-exist" err &&
 +      grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
        test_must_fail git rev-parse --verify -q $c
  '
  
@@@ -872,14 -834,14 +872,14 @@@ test_expect_success 'stdin -z create re
        git update-ref -d "$c" &&
        printf $F "create $c" "does-not-exist" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: invalid new value for ref $c: does-not-exist" err &&
 +      grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
        test_must_fail git rev-parse --verify -q $c
  '
  
 -test_expect_success 'stdin -z create ref fails with zero new value' '
 +test_expect_success 'stdin -z create ref fails with empty new value' '
        printf $F "create $c" "" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: create $c given zero new value" err &&
 +      grep "fatal: create $c: missing <newvalue>" err &&
        test_must_fail git rev-parse --verify -q $c
  '
  
@@@ -903,7 -865,7 +903,7 @@@ test_expect_success 'stdin -z delete re
  test_expect_success 'stdin -z delete ref fails with zero old value' '
        printf $F "delete $a" "$Z" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
 -      grep "fatal: delete $a given zero old value" err &&
 +      grep "fatal: delete $a: zero <oldvalue>" err &&
        git rev-parse $m >expect &&
        git rev-parse $a >actual &&
        test_cmp expect actual
@@@ -961,7 -923,7 +961,7 @@@ test_expect_success 'stdin -z update re
  
  test_expect_success 'stdin -z update refs fails with wrong old value' '
        git update-ref $c $m &&
 -      printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "" "$Z" >stdin &&
 +      printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
        grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
        git rev-parse $m >expect &&