Merge branch 'jk/snprintf-truncation'
authorJunio C Hamano <gitster@pobox.com>
Wed, 30 May 2018 12:51:27 +0000 (21:51 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 30 May 2018 12:51:28 +0000 (21:51 +0900)
Avoid unchecked snprintf() to make future code auditing easier.

* jk/snprintf-truncation:
fmt_with_err: add a comment that truncation is OK
shorten_unambiguous_ref: use xsnprintf
fsmonitor: use internal argv_array of struct child_process
log_write_email_headers: use strbufs
http: use strbufs instead of fixed buffers

1  2 
fsmonitor.c
http.c
http.h
log-tree.c
refs.c
usage.c
diff --combined fsmonitor.c
index ed3d1a074d60309063d16044bfd83beb78ef0e9d,b6fcb0f08fb6d3e34437ba36526d51bae477b0af..665bd2d4254b12edf38e94cb0e8b2200ae3bf34a
@@@ -97,19 -97,13 +97,13 @@@ void write_fsmonitor_extension(struct s
  static int query_fsmonitor(int version, uint64_t last_update, struct strbuf *query_result)
  {
        struct child_process cp = CHILD_PROCESS_INIT;
-       char ver[64];
-       char date[64];
-       const char *argv[4];
  
-       if (!(argv[0] = core_fsmonitor))
+       if (!core_fsmonitor)
                return -1;
  
-       snprintf(ver, sizeof(ver), "%d", version);
-       snprintf(date, sizeof(date), "%" PRIuMAX, (uintmax_t)last_update);
-       argv[1] = ver;
-       argv[2] = date;
-       argv[3] = NULL;
-       cp.argv = argv;
+       argv_array_push(&cp.args, core_fsmonitor);
+       argv_array_pushf(&cp.args, "%d", version);
+       argv_array_pushf(&cp.args, "%" PRIuMAX, (uintmax_t)last_update);
        cp.use_shell = 1;
        cp.dir = get_git_work_tree();
  
@@@ -185,9 -179,6 +179,9 @@@ void refresh_fsmonitor(struct index_sta
                for (i = 0; i < istate->cache_nr; i++)
                        istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
  
 +              /* If we're going to check every file, ensure we save the results */
 +              istate->cache_changed |= FSMONITOR_CHANGED;
 +
                if (istate->untracked)
                        istate->untracked->use_fsmonitor = 0;
        }
diff --combined http.c
index d9155972d68e82c188a41d0a2a02728d9862570b,fc5fff90a776ca7e93dc151836c1e0a4c4e7c179..deea47411a9d263fc87d9ff03f6b3bf6fa0d0fff
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -14,7 -14,6 +14,7 @@@
  #include "packfile.h"
  #include "protocol.h"
  #include "string-list.h"
 +#include "object-store.h"
  
  static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
  static int trace_curl_data = 1;
@@@ -63,9 -62,6 +63,9 @@@ static struct 
        { "tlsv1.1", CURL_SSLVERSION_TLSv1_1 },
        { "tlsv1.2", CURL_SSLVERSION_TLSv1_2 },
  #endif
 +#if LIBCURL_VERSION_NUM >= 0x073400
 +      { "tlsv1.3", CURL_SSLVERSION_TLSv1_3 },
 +#endif
  };
  #if LIBCURL_VERSION_NUM >= 0x070903
  static const char *ssl_key;
@@@ -976,6 -972,21 +976,6 @@@ static void set_from_env(const char **v
                *var = val;
  }
  
 -static void protocol_http_header(void)
 -{
 -      if (get_protocol_version_config() > 0) {
 -              struct strbuf protocol_header = STRBUF_INIT;
 -
 -              strbuf_addf(&protocol_header, GIT_PROTOCOL_HEADER ": version=%d",
 -                          get_protocol_version_config());
 -
 -
 -              extra_http_headers = curl_slist_append(extra_http_headers,
 -                                                     protocol_header.buf);
 -              strbuf_release(&protocol_header);
 -      }
 -}
 -
  void http_init(struct remote *remote, const char *url, int proactive_auth)
  {
        char *low_speed_limit;
        if (remote)
                var_override(&http_proxy_authmethod, remote->http_proxy_authmethod);
  
 -      protocol_http_header();
 -
        pragma_header = curl_slist_append(http_copy_default_headers(),
                "Pragma: no-cache");
        no_pragma_header = curl_slist_append(http_copy_default_headers(),
@@@ -1778,14 -1791,6 +1778,14 @@@ static int http_request(const char *url
  
        headers = curl_slist_append(headers, buf.buf);
  
 +      /* Add additional headers here */
 +      if (options && options->extra_headers) {
 +              const struct string_list_item *item;
 +              for_each_string_list_item(item, options->extra_headers) {
 +                      headers = curl_slist_append(headers, item->string);
 +              }
 +      }
 +
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip");
@@@ -1846,7 -1851,7 +1846,7 @@@ static int update_url_from_redirect(str
                return 0;
  
        if (!skip_prefix(asked, base->buf, &tail))
 -              die("BUG: update_url_from_redirect: %s is not a superset of %s",
 +              BUG("update_url_from_redirect: %s is not a superset of %s",
                    asked, base->buf);
  
        new_len = got->len;
@@@ -1894,7 -1899,7 +1894,7 @@@ static int http_request_reauth(const ch
                        strbuf_reset(result);
                        break;
                default:
 -                      die("BUG: HTTP_KEEP_ERROR is only supported with strbufs");
 +                      BUG("HTTP_KEEP_ERROR is only supported with strbufs");
                }
        }
  
@@@ -2038,8 -2043,7 +2038,8 @@@ int http_get_info_packs(const char *bas
        int ret = 0, i = 0;
        char *url, *data;
        struct strbuf buf = STRBUF_INIT;
 -      unsigned char sha1[20];
 +      unsigned char hash[GIT_MAX_RAWSZ];
 +      const unsigned hexsz = the_hash_algo->hexsz;
  
        end_url_with_slash(&buf, base_url);
        strbuf_addstr(&buf, "objects/info/packs");
                switch (data[i]) {
                case 'P':
                        i++;
 -                      if (i + 52 <= buf.len &&
 +                      if (i + hexsz + 12 <= buf.len &&
                            starts_with(data + i, " pack-") &&
 -                          starts_with(data + i + 46, ".pack\n")) {
 -                              get_sha1_hex(data + i + 6, sha1);
 -                              fetch_and_setup_pack_index(packs_head, sha1,
 +                          starts_with(data + i + hexsz + 6, ".pack\n")) {
 +                              get_sha1_hex(data + i + 6, hash);
 +                              fetch_and_setup_pack_index(packs_head, hash,
                                                      base_url);
 -                              i += 51;
 +                              i += hexsz + 11;
                                break;
                        }
                default:
@@@ -2083,6 -2087,7 +2083,7 @@@ void release_http_pack_request(struct h
                preq->packfile = NULL;
        }
        preq->slot = NULL;
+       strbuf_release(&preq->tmpfile);
        free(preq->url);
        free(preq);
  }
@@@ -2105,19 -2110,19 +2106,19 @@@ int finish_http_pack_request(struct htt
                lst = &((*lst)->next);
        *lst = (*lst)->next;
  
-       if (!strip_suffix(preq->tmpfile, ".pack.temp", &len))
+       if (!strip_suffix(preq->tmpfile.buf, ".pack.temp", &len))
 -              die("BUG: pack tmpfile does not end in .pack.temp?");
 +              BUG("pack tmpfile does not end in .pack.temp?");
-       tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile);
+       tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile.buf);
  
        argv_array_push(&ip.args, "index-pack");
        argv_array_pushl(&ip.args, "-o", tmp_idx, NULL);
-       argv_array_push(&ip.args, preq->tmpfile);
+       argv_array_push(&ip.args, preq->tmpfile.buf);
        ip.git_cmd = 1;
        ip.no_stdin = 1;
        ip.no_stdout = 1;
  
        if (run_command(&ip)) {
-               unlink(preq->tmpfile);
+               unlink(preq->tmpfile.buf);
                unlink(tmp_idx);
                free(tmp_idx);
                return -1;
  
        unlink(sha1_pack_index_name(p->sha1));
  
-       if (finalize_object_file(preq->tmpfile, sha1_pack_name(p->sha1))
+       if (finalize_object_file(preq->tmpfile.buf, sha1_pack_name(p->sha1))
         || finalize_object_file(tmp_idx, sha1_pack_index_name(p->sha1))) {
                free(tmp_idx);
                return -1;
        }
  
 -      install_packed_git(p);
 +      install_packed_git(the_repository, p);
        free(tmp_idx);
        return 0;
  }
@@@ -2144,6 -2149,7 +2145,7 @@@ struct http_pack_request *new_http_pack
        struct http_pack_request *preq;
  
        preq = xcalloc(1, sizeof(*preq));
+       strbuf_init(&preq->tmpfile, 0);
        preq->target = target;
  
        end_url_with_slash(&buf, base_url);
                sha1_to_hex(target->sha1));
        preq->url = strbuf_detach(&buf, NULL);
  
-       snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp",
-               sha1_pack_name(target->sha1));
-       preq->packfile = fopen(preq->tmpfile, "a");
+       strbuf_addf(&preq->tmpfile, "%s.temp", sha1_pack_name(target->sha1));
+       preq->packfile = fopen(preq->tmpfile.buf, "a");
        if (!preq->packfile) {
                error("Unable to open local file %s for pack",
-                     preq->tmpfile);
+                     preq->tmpfile.buf);
                goto abort;
        }
  
        return preq;
  
  abort:
+       strbuf_release(&preq->tmpfile);
        free(preq->url);
        free(preq);
        return NULL;
@@@ -2202,7 -2208,7 +2204,7 @@@ static size_t fwrite_sha1_file(char *pt
                CURLcode c = curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE,
                                                &slot->http_code);
                if (c != CURLE_OK)
 -                      die("BUG: curl_easy_getinfo for HTTP code failed: %s",
 +                      BUG("curl_easy_getinfo for HTTP code failed: %s",
                                curl_easy_strerror(c));
                if (slot->http_code >= 300)
                        return size;
@@@ -2233,7 -2239,7 +2235,7 @@@ struct http_object_request *new_http_ob
  {
        char *hex = sha1_to_hex(sha1);
        struct strbuf filename = STRBUF_INIT;
-       char prevfile[PATH_MAX];
+       struct strbuf prevfile = STRBUF_INIT;
        int prevlocal;
        char prev_buf[PREV_BUF_SIZE];
        ssize_t prev_read = 0;
        struct http_object_request *freq;
  
        freq = xcalloc(1, sizeof(*freq));
+       strbuf_init(&freq->tmpfile, 0);
        hashcpy(freq->sha1, sha1);
        freq->localfile = -1;
  
 -      sha1_file_name(&filename, sha1);
 +      sha1_file_name(the_repository, &filename, sha1);
-       snprintf(freq->tmpfile, sizeof(freq->tmpfile),
-                "%s.temp", filename.buf);
+       strbuf_addf(&freq->tmpfile, "%s.temp", filename.buf);
  
-       snprintf(prevfile, sizeof(prevfile), "%s.prev", filename.buf);
-       unlink_or_warn(prevfile);
-       rename(freq->tmpfile, prevfile);
-       unlink_or_warn(freq->tmpfile);
+       strbuf_addf(&prevfile, "%s.prev", filename.buf);
+       unlink_or_warn(prevfile.buf);
+       rename(freq->tmpfile.buf, prevfile.buf);
+       unlink_or_warn(freq->tmpfile.buf);
        strbuf_release(&filename);
  
        if (freq->localfile != -1)
                error("fd leakage in start: %d", freq->localfile);
-       freq->localfile = open(freq->tmpfile,
+       freq->localfile = open(freq->tmpfile.buf,
                               O_WRONLY | O_CREAT | O_EXCL, 0666);
        /*
         * This could have failed due to the "lazy directory creation";
         * try to mkdir the last path component.
         */
        if (freq->localfile < 0 && errno == ENOENT) {
-               char *dir = strrchr(freq->tmpfile, '/');
+               char *dir = strrchr(freq->tmpfile.buf, '/');
                if (dir) {
                        *dir = 0;
-                       mkdir(freq->tmpfile, 0777);
+                       mkdir(freq->tmpfile.buf, 0777);
                        *dir = '/';
                }
-               freq->localfile = open(freq->tmpfile,
+               freq->localfile = open(freq->tmpfile.buf,
                                       O_WRONLY | O_CREAT | O_EXCL, 0666);
        }
  
        if (freq->localfile < 0) {
-               error_errno("Couldn't create temporary file %s", freq->tmpfile);
+               error_errno("Couldn't create temporary file %s",
+                           freq->tmpfile.buf);
                goto abort;
        }
  
         * If a previous temp file is present, process what was already
         * fetched.
         */
-       prevlocal = open(prevfile, O_RDONLY);
+       prevlocal = open(prevfile.buf, O_RDONLY);
        if (prevlocal != -1) {
                do {
                        prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
                } while (prev_read > 0);
                close(prevlocal);
        }
-       unlink_or_warn(prevfile);
+       unlink_or_warn(prevfile.buf);
+       strbuf_release(&prevfile);
  
        /*
         * Reset inflate/SHA1 if there was an error reading the previous temp
                        lseek(freq->localfile, 0, SEEK_SET);
                        if (ftruncate(freq->localfile, 0) < 0) {
                                error_errno("Couldn't truncate temporary file %s",
-                                           freq->tmpfile);
+                                           freq->tmpfile.buf);
                                goto abort;
                        }
                }
        return freq;
  
  abort:
+       strbuf_release(&prevfile);
        free(freq->url);
        free(freq);
        return NULL;
@@@ -2377,24 -2386,25 +2382,24 @@@ int finish_http_object_request(struct h
        if (freq->http_code == 416) {
                warning("requested range invalid; we may already have all the data.");
        } else if (freq->curl_result != CURLE_OK) {
-               if (stat(freq->tmpfile, &st) == 0)
+               if (stat(freq->tmpfile.buf, &st) == 0)
                        if (st.st_size == 0)
-                               unlink_or_warn(freq->tmpfile);
+                               unlink_or_warn(freq->tmpfile.buf);
                return -1;
        }
  
        git_inflate_end(&freq->stream);
        git_SHA1_Final(freq->real_sha1, &freq->c);
        if (freq->zret != Z_STREAM_END) {
-               unlink_or_warn(freq->tmpfile);
+               unlink_or_warn(freq->tmpfile.buf);
                return -1;
        }
        if (hashcmp(freq->sha1, freq->real_sha1)) {
-               unlink_or_warn(freq->tmpfile);
+               unlink_or_warn(freq->tmpfile.buf);
                return -1;
        }
 -
 -      sha1_file_name(&filename, freq->sha1);
 +      sha1_file_name(the_repository, &filename, freq->sha1);
-       freq->rename = finalize_object_file(freq->tmpfile, filename.buf);
+       freq->rename = finalize_object_file(freq->tmpfile.buf, filename.buf);
        strbuf_release(&filename);
  
        return freq->rename;
  
  void abort_http_object_request(struct http_object_request *freq)
  {
-       unlink_or_warn(freq->tmpfile);
+       unlink_or_warn(freq->tmpfile.buf);
  
        release_http_object_request(freq);
  }
@@@ -2422,4 -2432,5 +2427,5 @@@ void release_http_object_request(struc
                release_active_slot(freq->slot);
                freq->slot = NULL;
        }
+       strbuf_release(&freq->tmpfile);
  }
diff --combined http.h
index 4df4a25e1abc232c27f1b00ba0a97b05a689db2e,d9379f9cb268af704cbf1362ff8bad1956bc8505..d305ca1dc7a3f931a81353c56060e98f4039c692
--- 1/http.h
--- 2/http.h
+++ b/http.h
@@@ -172,13 -172,6 +172,13 @@@ struct http_get_options 
         * for details.
         */
        struct strbuf *base_url;
 +
 +      /*
 +       * If not NULL, contains additional HTTP headers to be sent with the
 +       * request. The strings in the list must not be freed until after the
 +       * request has completed.
 +       */
 +      struct string_list *extra_headers;
  };
  
  /* Return values for http_get_*() */
@@@ -207,7 -200,7 +207,7 @@@ struct http_pack_request 
        struct packed_git *target;
        struct packed_git **lst;
        FILE *packfile;
-       char tmpfile[PATH_MAX];
+       struct strbuf tmpfile;
        struct active_request_slot *slot;
  };
  
@@@ -219,7 -212,7 +219,7 @@@ extern void release_http_pack_request(s
  /* Helpers for fetching object */
  struct http_object_request {
        char *url;
-       char tmpfile[PATH_MAX];
+       struct strbuf tmpfile;
        int localfile;
        CURLcode curl_result;
        char errorstr[CURL_ERROR_SIZE];
diff --combined log-tree.c
index 724bae0de25b5b6e22dfecee233ff999be880dd5,4e83d7125b43b167f77b725229efcef52ab8bb9f..4aef85331e0b696d0373cf1bdb0743a7e6b58c36
@@@ -177,7 -177,7 +177,7 @@@ static void show_parents(struct commit 
        struct commit_list *p;
        for (p = commit->parents; p ; p = p->next) {
                struct commit *parent = p->item;
 -              fprintf(file, " %s", find_unique_abbrev(parent->object.oid.hash, abbrev));
 +              fprintf(file, " %s", find_unique_abbrev(&parent->object.oid, abbrev));
        }
  }
  
@@@ -185,7 -185,7 +185,7 @@@ static void show_children(struct rev_in
  {
        struct commit_list *p = lookup_decoration(&opt->children, &commit->object);
        for ( ; p; p = p->next) {
 -              fprintf(opt->diffopt.file, " %s", find_unique_abbrev(p->item->object.oid.hash, abbrev));
 +              fprintf(opt->diffopt.file, " %s", find_unique_abbrev(&p->item->object.oid, abbrev));
        }
  }
  
@@@ -362,8 -362,7 +362,8 @@@ void fmt_output_email_subject(struct st
  
  void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **extra_headers_p,
 -                           int *need_8bit_cte_p)
 +                           int *need_8bit_cte_p,
 +                           int maybe_multipart)
  {
        const char *extra_headers = opt->extra_headers;
        const char *name = oid_to_hex(opt->zero_commit ?
                               opt->ref_message_ids->items[i].string);
                graph_show_oneline(opt->graph);
        }
 -      if (opt->mime_boundary) {
 +      if (opt->mime_boundary && maybe_multipart) {
-               static char subject_buffer[1024];
-               static char buffer[1024];
+               static struct strbuf subject_buffer = STRBUF_INIT;
+               static struct strbuf buffer = STRBUF_INIT;
                struct strbuf filename =  STRBUF_INIT;
                *need_8bit_cte_p = -1; /* NEVER */
-               snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+               strbuf_reset(&subject_buffer);
+               strbuf_reset(&buffer);
+               strbuf_addf(&subject_buffer,
                         "%s"
                         "MIME-Version: 1.0\n"
                         "Content-Type: multipart/mixed;"
                         extra_headers ? extra_headers : "",
                         mime_boundary_leader, opt->mime_boundary,
                         mime_boundary_leader, opt->mime_boundary);
-               extra_headers = subject_buffer;
+               extra_headers = subject_buffer.buf;
  
                if (opt->numbered_files)
                        strbuf_addf(&filename, "%d", opt->nr);
                else
                        fmt_output_commit(&filename, commit, opt);
-               snprintf(buffer, sizeof(buffer) - 1,
+               strbuf_addf(&buffer,
                         "\n--%s%s\n"
                         "Content-Type: text/x-patch;"
                         " name=\"%s\"\n"
                         filename.buf,
                         opt->no_inline ? "attachment" : "inline",
                         filename.buf);
-               opt->diffopt.stat_sep = buffer;
+               opt->diffopt.stat_sep = buffer.buf;
                strbuf_release(&filename);
        }
        *extra_headers_p = extra_headers;
@@@ -489,9 -492,9 +493,9 @@@ static int is_common_merge(const struc
                && !commit->parents->next->next);
  }
  
 -static void show_one_mergetag(struct commit *commit,
 -                            struct commit_extra_header *extra,
 -                            void *data)
 +static int show_one_mergetag(struct commit *commit,
 +                           struct commit_extra_header *extra,
 +                           void *data)
  {
        struct rev_info *opt = (struct rev_info *)data;
        struct object_id oid;
        hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid);
        tag = lookup_tag(&oid);
        if (!tag)
 -              return; /* error message already given */
 +              return -1; /* error message already given */
  
        strbuf_init(&verify_message, 256);
        if (parse_tag_buffer(tag, extra->value, extra->len))
  
        show_sig_lines(opt, status, verify_message.buf);
        strbuf_release(&verify_message);
 +      return 0;
  }
  
 -static void show_mergetag(struct rev_info *opt, struct commit *commit)
 +static int show_mergetag(struct rev_info *opt, struct commit *commit)
  {
 -      for_each_mergetag(show_one_mergetag, commit, opt);
 +      return for_each_mergetag(show_one_mergetag, commit, opt);
  }
  
  void show_log(struct rev_info *opt)
  
                if (!opt->graph)
                        put_revision_mark(opt, commit);
 -              fputs(find_unique_abbrev(commit->object.oid.hash, abbrev_commit), opt->diffopt.file);
 +              fputs(find_unique_abbrev(&commit->object.oid, abbrev_commit), opt->diffopt.file);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit, opt->diffopt.file);
                if (opt->children.name)
  
        if (cmit_fmt_is_mail(opt->commit_format)) {
                log_write_email_headers(opt, commit, &extra_headers,
 -                                      &ctx.need_8bit_cte);
 +                                      &ctx.need_8bit_cte, 1);
                ctx.rev = opt;
                ctx.print_email_subject = 1;
        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
  
                if (!opt->graph)
                        put_revision_mark(opt, commit);
 -              fputs(find_unique_abbrev(commit->object.oid.hash, abbrev_commit),
 +              fputs(find_unique_abbrev(&commit->object.oid,
 +                                       abbrev_commit),
                      opt->diffopt.file);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit, opt->diffopt.file);
                        show_children(opt, commit, abbrev_commit);
                if (parent)
                        fprintf(opt->diffopt.file, " (from %s)",
 -                             find_unique_abbrev(parent->object.oid.hash,
 -                                                abbrev_commit));
 +                             find_unique_abbrev(&parent->object.oid, abbrev_commit));
                fputs(diff_get_color_opt(&opt->diffopt, DIFF_RESET), opt->diffopt.file);
                show_decorations(opt, commit);
                if (opt->commit_format == CMIT_FMT_ONELINE) {
@@@ -808,7 -810,7 +812,7 @@@ static int log_tree_diff(struct rev_inf
                return 0;
  
        parse_commit_or_die(commit);
 -      oid = &commit->tree->object.oid;
 +      oid = get_commit_tree_oid(commit);
  
        /* Root commit? */
        parents = get_saved_parents(opt, commit);
                         * we merged _in_.
                         */
                        parse_commit_or_die(parents->item);
 -                      diff_tree_oid(&parents->item->tree->object.oid,
 +                      diff_tree_oid(get_commit_tree_oid(parents->item),
                                      oid, "", &opt->diffopt);
                        log_tree_diff_flush(opt);
                        return !opt->loginfo;
                struct commit *parent = parents->item;
  
                parse_commit_or_die(parent);
 -              diff_tree_oid(&parent->tree->object.oid,
 +              diff_tree_oid(get_commit_tree_oid(parent),
                              oid, "", &opt->diffopt);
                log_tree_diff_flush(opt);
  
diff --combined refs.c
index 20fb35d8952611527e7ade5c2e394c4bde79291b,bd7ac72aa71a933d4232407d4517a6a47aaa3a45..0eb379f9312fd9f167fea2e0f148c85c47cd2ff0
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -13,8 -13,6 +13,8 @@@
  #include "tag.h"
  #include "submodule.h"
  #include "worktree.h"
 +#include "argv-array.h"
 +#include "repository.h"
  
  /*
   * List of all available backends
@@@ -208,7 -206,7 +208,7 @@@ char *refs_resolve_refdup(struct ref_st
  char *resolve_refdup(const char *refname, int resolve_flags,
                     struct object_id *oid, int *flags)
  {
 -      return refs_resolve_refdup(get_main_ref_store(),
 +      return refs_resolve_refdup(get_main_ref_store(the_repository),
                                   refname, resolve_flags,
                                   oid, flags);
  }
@@@ -230,7 -228,7 +230,7 @@@ int refs_read_ref_full(struct ref_stor
  
  int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags)
  {
 -      return refs_read_ref_full(get_main_ref_store(), refname,
 +      return refs_read_ref_full(get_main_ref_store(the_repository), refname,
                                  resolve_flags, oid, flags);
  }
  
@@@ -303,7 -301,7 +303,7 @@@ enum peel_status peel_object(const stru
        struct object *o = lookup_unknown_object(name->hash);
  
        if (o->type == OBJ_NONE) {
 -              int type = sha1_object_info(name->hash, NULL);
 +              int type = oid_object_info(the_repository, name, NULL);
                if (type < 0 || !object_as_type(o, type, 0))
                        return PEEL_INVALID;
        }
@@@ -377,7 -375,7 +377,7 @@@ int refs_for_each_tag_ref(struct ref_st
  
  int for_each_tag_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_tag_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_tag_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
  
  int for_each_branch_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_branch_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_branch_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
  
  int for_each_remote_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_remote_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_remote_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
@@@ -503,19 -501,6 +503,19 @@@ int refname_match(const char *abbrev_na
        return 0;
  }
  
 +/*
 + * Given a 'prefix' expand it by the rules in 'ref_rev_parse_rules' and add
 + * the results to 'prefixes'
 + */
 +void expand_ref_prefix(struct argv_array *prefixes, const char *prefix)
 +{
 +      const char **p;
 +      int len = strlen(prefix);
 +
 +      for (p = ref_rev_parse_rules; *p; p++)
 +              argv_array_pushf(prefixes, *p, len, prefix);
 +}
 +
  /*
   * *string and *len will only be substituted, and *string returned (for
   * later free()ing) if the string passed in is a magic short-hand form
@@@ -615,8 -600,7 +615,8 @@@ int dwim_log(const char *str, int len, 
  static int is_per_worktree_ref(const char *refname)
  {
        return !strcmp(refname, "HEAD") ||
 -              starts_with(refname, "refs/bisect/");
 +              starts_with(refname, "refs/bisect/") ||
 +              starts_with(refname, "refs/rewritten/");
  }
  
  static int is_pseudoref_syntax(const char *refname)
@@@ -660,7 -644,7 +660,7 @@@ static int write_pseudoref(const char *
  {
        const char *filename;
        int fd;
 -      static struct lock_file lock;
 +      struct lock_file lock = LOCK_INIT;
        struct strbuf buf = STRBUF_INIT;
        int ret = -1;
  
        strbuf_addf(&buf, "%s\n", oid_to_hex(oid));
  
        filename = git_path("%s", pseudoref);
 -      fd = hold_lock_file_for_update_timeout(&lock, filename,
 -                                             LOCK_DIE_ON_ERROR,
 +      fd = hold_lock_file_for_update_timeout(&lock, filename, 0,
                                               get_files_ref_lock_timeout_ms());
        if (fd < 0) {
                strbuf_addf(err, "could not open '%s' for writing: %s",
        if (old_oid) {
                struct object_id actual_old_oid;
  
 -              if (read_ref(pseudoref, &actual_old_oid))
 -                      die("could not read ref '%s'", pseudoref);
 -              if (oidcmp(&actual_old_oid, old_oid)) {
 -                      strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref);
 +              if (read_ref(pseudoref, &actual_old_oid)) {
 +                      if (!is_null_oid(old_oid)) {
 +                              strbuf_addf(err, "could not read ref '%s'",
 +                                          pseudoref);
 +                              rollback_lock_file(&lock);
 +                              goto done;
 +                      }
 +              } else if (is_null_oid(old_oid)) {
 +                      strbuf_addf(err, "ref '%s' already exists",
 +                                  pseudoref);
 +                      rollback_lock_file(&lock);
 +                      goto done;
 +              } else if (oidcmp(&actual_old_oid, old_oid)) {
 +                      strbuf_addf(err, "unexpected object ID when writing '%s'",
 +                                  pseudoref);
                        rollback_lock_file(&lock);
                        goto done;
                }
@@@ -716,28 -690,24 +716,28 @@@ done
  
  static int delete_pseudoref(const char *pseudoref, const struct object_id *old_oid)
  {
 -      static struct lock_file lock;
        const char *filename;
  
        filename = git_path("%s", pseudoref);
  
        if (old_oid && !is_null_oid(old_oid)) {
 +              struct lock_file lock = LOCK_INIT;
                int fd;
                struct object_id actual_old_oid;
  
                fd = hold_lock_file_for_update_timeout(
 -                              &lock, filename, LOCK_DIE_ON_ERROR,
 +                              &lock, filename, 0,
                                get_files_ref_lock_timeout_ms());
 -              if (fd < 0)
 -                      die_errno(_("Could not open '%s' for writing"), filename);
 +              if (fd < 0) {
 +                      error_errno(_("could not open '%s' for writing"),
 +                                  filename);
 +                      return -1;
 +              }
                if (read_ref(pseudoref, &actual_old_oid))
                        die("could not read ref '%s'", pseudoref);
                if (oidcmp(&actual_old_oid, old_oid)) {
 -                      warning("Unexpected sha1 when deleting %s", pseudoref);
 +                      error("unexpected object ID when deleting '%s'",
 +                            pseudoref);
                        rollback_lock_file(&lock);
                        return -1;
                }
@@@ -760,7 -730,7 +760,7 @@@ int refs_delete_ref(struct ref_store *r
        struct strbuf err = STRBUF_INIT;
  
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 -              assert(refs == get_main_ref_store());
 +              assert(refs == get_main_ref_store(the_repository));
                return delete_pseudoref(refname, old_oid);
        }
  
  int delete_ref(const char *msg, const char *refname,
               const struct object_id *old_oid, unsigned int flags)
  {
 -      return refs_delete_ref(get_main_ref_store(), msg, refname,
 +      return refs_delete_ref(get_main_ref_store(the_repository), msg, refname,
                               old_oid, flags);
  }
  
@@@ -958,7 -928,7 +958,7 @@@ struct ref_transaction *ref_store_trans
  
  struct ref_transaction *ref_transaction_begin(struct strbuf *err)
  {
 -      return ref_store_transaction_begin(get_main_ref_store(), err);
 +      return ref_store_transaction_begin(get_main_ref_store(the_repository), err);
  }
  
  void ref_transaction_free(struct ref_transaction *transaction)
                /* OK */
                break;
        case REF_TRANSACTION_PREPARED:
 -              die("BUG: free called on a prepared reference transaction");
 +              BUG("free called on a prepared reference transaction");
                break;
        default:
 -              die("BUG: unexpected reference transaction state");
 +              BUG("unexpected reference transaction state");
                break;
        }
  
@@@ -999,7 -969,7 +999,7 @@@ struct ref_update *ref_transaction_add_
        struct ref_update *update;
  
        if (transaction->state != REF_TRANSACTION_OPEN)
 -              die("BUG: update called for transaction that is not open");
 +              BUG("update called for transaction that is not open");
  
        FLEX_ALLOC_STR(update, refname, refname);
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
@@@ -1049,7 -1019,7 +1049,7 @@@ int ref_transaction_create(struct ref_t
                           struct strbuf *err)
  {
        if (!new_oid || is_null_oid(new_oid))
 -              die("BUG: create called without valid new_oid");
 +              BUG("create called without valid new_oid");
        return ref_transaction_update(transaction, refname, new_oid,
                                      &null_oid, flags, msg, err);
  }
@@@ -1061,7 -1031,7 +1061,7 @@@ int ref_transaction_delete(struct ref_t
                           struct strbuf *err)
  {
        if (old_oid && is_null_oid(old_oid))
 -              die("BUG: delete called with old_oid set to zeros");
 +              BUG("delete called with old_oid set to zeros");
        return ref_transaction_update(transaction, refname,
                                      &null_oid, old_oid,
                                      flags, msg, err);
@@@ -1074,7 -1044,7 +1074,7 @@@ int ref_transaction_verify(struct ref_t
                           struct strbuf *err)
  {
        if (!old_oid)
 -              die("BUG: verify called with old_oid set to NULL");
 +              BUG("verify called with old_oid set to NULL");
        return ref_transaction_update(transaction, refname,
                                      NULL, old_oid,
                                      flags, NULL, err);
@@@ -1090,7 -1060,7 +1090,7 @@@ int refs_update_ref(struct ref_store *r
        int ret = 0;
  
        if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
 -              assert(refs == get_main_ref_store());
 +              assert(refs == get_main_ref_store(the_repository));
                ret = write_pseudoref(refname, new_oid, old_oid, &err);
        } else {
                t = ref_store_transaction_begin(refs, &err);
@@@ -1129,7 -1099,7 +1129,7 @@@ int update_ref(const char *msg, const c
               const struct object_id *old_oid,
               unsigned int flags, enum action_on_err onerr)
  {
 -      return refs_update_ref(get_main_ref_store(), msg, refname, new_oid,
 +      return refs_update_ref(get_main_ref_store(the_repository), msg, refname, new_oid,
                               old_oid, flags, onerr);
  }
  
@@@ -1162,8 -1132,8 +1162,8 @@@ char *shorten_unambiguous_ref(const cha
                for (i = 0; i < nr_rules; i++) {
                        assert(offset < total_len);
                        scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;
-                       offset += snprintf(scanf_fmts[i], total_len - offset,
-                                          ref_rev_parse_rules[i], 2, "%s") + 1;
+                       offset += xsnprintf(scanf_fmts[i], total_len - offset,
+                                           ref_rev_parse_rules[i], 2, "%s") + 1;
                }
        }
  
@@@ -1350,7 -1320,7 +1350,7 @@@ int refs_head_ref(struct ref_store *ref
  
  int head_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_head_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  struct ref_iterator *refs_ref_iterator_begin(
@@@ -1409,7 -1379,7 +1409,7 @@@ int refs_for_each_ref(struct ref_store 
  
  int for_each_ref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_ref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_ref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
  
  int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_ref_in(get_main_ref_store(), prefix, fn, cb_data);
 +      return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data);
  }
  
  int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
  
        if (broken)
                flag = DO_FOR_EACH_INCLUDE_BROKEN;
 -      return do_for_each_ref(get_main_ref_store(),
 +      return do_for_each_ref(get_main_ref_store(the_repository),
                               prefix, fn, 0, flag, cb_data);
  }
  
@@@ -1444,9 -1414,9 +1444,9 @@@ int refs_for_each_fullref_in(struct ref
        return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
  }
  
 -int for_each_replace_ref(each_ref_fn fn, void *cb_data)
 +int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(get_main_ref_store(),
 +      return do_for_each_ref(get_main_ref_store(r),
                               git_replace_ref_base, fn,
                               strlen(git_replace_ref_base),
                               DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
@@@ -1457,7 -1427,7 +1457,7 @@@ int for_each_namespaced_ref(each_ref_f
        struct strbuf buf = STRBUF_INIT;
        int ret;
        strbuf_addf(&buf, "%srefs/", get_git_namespace());
 -      ret = do_for_each_ref(get_main_ref_store(),
 +      ret = do_for_each_ref(get_main_ref_store(the_repository),
                              buf.buf, fn, 0, 0, cb_data);
        strbuf_release(&buf);
        return ret;
@@@ -1471,7 -1441,7 +1471,7 @@@ int refs_for_each_rawref(struct ref_sto
  
  int for_each_rawref(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_rawref(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_read_raw_ref(struct ref_store *ref_store,
@@@ -1577,7 -1547,7 +1577,7 @@@ const char *refs_resolve_ref_unsafe(str
  /* backend functions */
  int refs_init_db(struct strbuf *err)
  {
 -      struct ref_store *refs = get_main_ref_store();
 +      struct ref_store *refs = get_main_ref_store(the_repository);
  
        return refs->be->init_db(refs, err);
  }
  const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
                               struct object_id *oid, int *flags)
  {
 -      return refs_resolve_ref_unsafe(get_main_ref_store(), refname,
 +      return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname,
                                       resolve_flags, oid, flags);
  }
  
@@@ -1637,6 -1607,9 +1637,6 @@@ static struct ref_store_hash_entry *all
        return entry;
  }
  
 -/* A pointer to the ref_store for the main repository: */
 -static struct ref_store *main_ref_store;
 -
  /* A hashmap of ref_stores, stored by submodule name: */
  static struct hashmap submodule_ref_stores;
  
@@@ -1672,22 -1645,19 +1672,22 @@@ static struct ref_store *ref_store_init
        struct ref_store *refs;
  
        if (!be)
 -              die("BUG: reference backend %s is unknown", be_name);
 +              BUG("reference backend %s is unknown", be_name);
  
        refs = be->init(gitdir, flags);
        return refs;
  }
  
 -struct ref_store *get_main_ref_store(void)
 +struct ref_store *get_main_ref_store(struct repository *r)
  {
 -      if (main_ref_store)
 -              return main_ref_store;
 +      if (r->refs)
 +              return r->refs;
 +
 +      if (!r->gitdir)
 +              BUG("attempting to get main_ref_store outside of repository");
  
 -      main_ref_store = ref_store_init(get_git_dir(), REF_STORE_ALL_CAPS);
 -      return main_ref_store;
 +      r->refs = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
 +      return r->refs;
  }
  
  /*
@@@ -1703,7 -1673,7 +1703,7 @@@ static void register_ref_store_map(stru
                hashmap_init(map, ref_store_hash_cmp, NULL, 0);
  
        if (hashmap_put(map, alloc_ref_store_hash_entry(name, refs)))
 -              die("BUG: %s ref_store '%s' initialized twice", type, name);
 +              BUG("%s ref_store '%s' initialized twice", type, name);
  }
  
  struct ref_store *get_submodule_ref_store(const char *submodule)
@@@ -1756,7 -1726,7 +1756,7 @@@ struct ref_store *get_worktree_ref_stor
        const char *id;
  
        if (wt->is_current)
 -              return get_main_ref_store();
 +              return get_main_ref_store(the_repository);
  
        id = wt->id ? wt->id : "/";
        refs = lookup_ref_store_map(&worktree_ref_stores, id);
@@@ -1812,7 -1782,7 +1812,7 @@@ int refs_peel_ref(struct ref_store *ref
  
  int peel_ref(const char *refname, struct object_id *oid)
  {
 -      return refs_peel_ref(get_main_ref_store(), refname, oid);
 +      return refs_peel_ref(get_main_ref_store(the_repository), refname, oid);
  }
  
  int refs_create_symref(struct ref_store *refs,
  int create_symref(const char *ref_target, const char *refs_heads_master,
                  const char *logmsg)
  {
 -      return refs_create_symref(get_main_ref_store(), ref_target,
 +      return refs_create_symref(get_main_ref_store(the_repository), ref_target,
                                  refs_heads_master, logmsg);
  }
  
@@@ -1849,7 -1819,7 +1849,7 @@@ int ref_update_reject_duplicates(struc
                                    refnames->items[i].string);
                        return 1;
                } else if (cmp > 0) {
 -                      die("BUG: ref_update_reject_duplicates() received unsorted list");
 +                      BUG("ref_update_reject_duplicates() received unsorted list");
                }
        }
        return 0;
@@@ -1865,13 -1835,13 +1865,13 @@@ int ref_transaction_prepare(struct ref_
                /* Good. */
                break;
        case REF_TRANSACTION_PREPARED:
 -              die("BUG: prepare called twice on reference transaction");
 +              BUG("prepare called twice on reference transaction");
                break;
        case REF_TRANSACTION_CLOSED:
 -              die("BUG: prepare called on a closed reference transaction");
 +              BUG("prepare called on a closed reference transaction");
                break;
        default:
 -              die("BUG: unexpected reference transaction state");
 +              BUG("unexpected reference transaction state");
                break;
        }
  
@@@ -1898,10 -1868,10 +1898,10 @@@ int ref_transaction_abort(struct ref_tr
                ret = refs->be->transaction_abort(refs, transaction, err);
                break;
        case REF_TRANSACTION_CLOSED:
 -              die("BUG: abort called on a closed reference transaction");
 +              BUG("abort called on a closed reference transaction");
                break;
        default:
 -              die("BUG: unexpected reference transaction state");
 +              BUG("unexpected reference transaction state");
                break;
        }
  
@@@ -1926,10 -1896,10 +1926,10 @@@ int ref_transaction_commit(struct ref_t
                /* Fall through to finish. */
                break;
        case REF_TRANSACTION_CLOSED:
 -              die("BUG: commit called on a closed reference transaction");
 +              BUG("commit called on a closed reference transaction");
                break;
        default:
 -              die("BUG: unexpected reference transaction state");
 +              BUG("unexpected reference transaction state");
                break;
        }
  
@@@ -2010,7 -1980,7 +2010,7 @@@ int refs_verify_refname_available(struc
        }
  
        if (ok != ITER_DONE)
 -              die("BUG: error while iterating over references");
 +              BUG("error while iterating over references");
  
        extra_refname = find_descendant_ref(dirname.buf, extras, skip);
        if (extra_refname)
@@@ -2036,7 -2006,7 +2036,7 @@@ int refs_for_each_reflog(struct ref_sto
  
  int for_each_reflog(each_ref_fn fn, void *cb_data)
  {
 -      return refs_for_each_reflog(get_main_ref_store(), fn, cb_data);
 +      return refs_for_each_reflog(get_main_ref_store(the_repository), fn, cb_data);
  }
  
  int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
  int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
                                void *cb_data)
  {
 -      return refs_for_each_reflog_ent_reverse(get_main_ref_store(),
 +      return refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository),
                                                refname, fn, cb_data);
  }
  
@@@ -2064,7 -2034,7 +2064,7 @@@ int refs_for_each_reflog_ent(struct ref
  int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,
                        void *cb_data)
  {
 -      return refs_for_each_reflog_ent(get_main_ref_store(), refname,
 +      return refs_for_each_reflog_ent(get_main_ref_store(the_repository), refname,
                                        fn, cb_data);
  }
  
@@@ -2075,7 -2045,7 +2075,7 @@@ int refs_reflog_exists(struct ref_stor
  
  int reflog_exists(const char *refname)
  {
 -      return refs_reflog_exists(get_main_ref_store(), refname);
 +      return refs_reflog_exists(get_main_ref_store(the_repository), refname);
  }
  
  int refs_create_reflog(struct ref_store *refs, const char *refname,
  int safe_create_reflog(const char *refname, int force_create,
                       struct strbuf *err)
  {
 -      return refs_create_reflog(get_main_ref_store(), refname,
 +      return refs_create_reflog(get_main_ref_store(the_repository), refname,
                                  force_create, err);
  }
  
@@@ -2098,7 -2068,7 +2098,7 @@@ int refs_delete_reflog(struct ref_stor
  
  int delete_reflog(const char *refname)
  {
 -      return refs_delete_reflog(get_main_ref_store(), refname);
 +      return refs_delete_reflog(get_main_ref_store(the_repository), refname);
  }
  
  int refs_reflog_expire(struct ref_store *refs,
@@@ -2121,7 -2091,7 +2121,7 @@@ int reflog_expire(const char *refname, 
                  reflog_expiry_cleanup_fn cleanup_fn,
                  void *policy_cb_data)
  {
 -      return refs_reflog_expire(get_main_ref_store(),
 +      return refs_reflog_expire(get_main_ref_store(the_repository),
                                  refname, oid, flags,
                                  prepare_fn, should_prune_fn,
                                  cleanup_fn, policy_cb_data);
@@@ -2144,7 -2114,7 +2144,7 @@@ int refs_delete_refs(struct ref_store *
  int delete_refs(const char *msg, struct string_list *refnames,
                unsigned int flags)
  {
 -      return refs_delete_refs(get_main_ref_store(), msg, refnames, flags);
 +      return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, flags);
  }
  
  int refs_rename_ref(struct ref_store *refs, const char *oldref,
  
  int rename_ref(const char *oldref, const char *newref, const char *logmsg)
  {
 -      return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg);
 +      return refs_rename_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
  }
  
  int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
  
  int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
  {
 -      return refs_copy_existing_ref(get_main_ref_store(), oldref, newref, logmsg);
 +      return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
  }
diff --combined usage.c
index 9c84dccfa9719d925b23eb63247d79d5dfe4b817,b3c78931ad9f68f45f78253c64abe9416b3971b3..cc803336bd5e6761b7fd50c33dba5aa9f734c4ba
+++ b/usage.c
@@@ -148,6 -148,7 +148,7 @@@ static const char *fmt_with_err(char *b
                }
        }
        str_error[j] = 0;
+       /* Truncation is acceptable here */
        snprintf(buf, n, "%s: %s", fmt, str_error);
        return buf;
  }
@@@ -210,9 -211,6 +211,9 @@@ void warning(const char *warn, ...
        va_end(params);
  }
  
 +/* Only set this, ever, from t/helper/, when verifying that bugs are caught. */
 +int BUG_exit_code;
 +
  static NORETURN void BUG_vfl(const char *file, int line, const char *fmt, va_list params)
  {
        char prefix[256];
                snprintf(prefix, sizeof(prefix), "BUG: ");
  
        vreportf(prefix, fmt, params);
 +      if (BUG_exit_code)
 +              exit(BUG_exit_code);
        abort();
  }