]> git.ipfire.org Git - thirdparty/git.git/blobdiff - refs.c
Merge branch 'jc/test-i18ngrep'
[thirdparty/git.git] / refs.c
diff --git a/refs.c b/refs.c
index fcae5dddc6050627668048e966384b7f9fa70171..55d2e0b2cb9e959443e98eb329fdf97eff9073a9 100644 (file)
--- a/refs.c
+++ b/refs.c
 /*
  * List of all available backends
  */
-static struct ref_storage_be *refs_backends = &refs_be_files;
+static const struct ref_storage_be *refs_backends[] = {
+       [REF_STORAGE_FORMAT_FILES] = &refs_be_files,
+       [REF_STORAGE_FORMAT_REFTABLE] = &refs_be_reftable,
+};
 
-static struct ref_storage_be *find_ref_storage_backend(const char *name)
+static const struct ref_storage_be *find_ref_storage_backend(unsigned int ref_storage_format)
 {
-       struct ref_storage_be *be;
-       for (be = refs_backends; be; be = be->next)
-               if (!strcmp(be->name, name))
-                       return be;
+       if (ref_storage_format < ARRAY_SIZE(refs_backends))
+               return refs_backends[ref_storage_format];
        return NULL;
 }
 
+unsigned int ref_storage_format_by_name(const char *name)
+{
+       for (unsigned int i = 0; i < ARRAY_SIZE(refs_backends); i++)
+               if (refs_backends[i] && !strcmp(refs_backends[i]->name, name))
+                       return i;
+       return REF_STORAGE_FORMAT_UNKNOWN;
+}
+
+const char *ref_storage_format_to_name(unsigned int ref_storage_format)
+{
+       const struct ref_storage_be *be = find_ref_storage_backend(ref_storage_format);
+       if (!be)
+               return "unknown";
+       return be->name;
+}
+
 /*
  * How to handle various characters in refnames:
  * 0: An acceptable character for refs
@@ -843,6 +860,47 @@ static int is_pseudoref_syntax(const char *refname)
        return 1;
 }
 
+int is_pseudoref(struct ref_store *refs, const char *refname)
+{
+       static const char *const irregular_pseudorefs[] = {
+               "AUTO_MERGE",
+               "BISECT_EXPECTED_REV",
+               "NOTES_MERGE_PARTIAL",
+               "NOTES_MERGE_REF",
+               "MERGE_AUTOSTASH",
+       };
+       struct object_id oid;
+       size_t i;
+
+       if (!is_pseudoref_syntax(refname))
+               return 0;
+
+       if (ends_with(refname, "_HEAD")) {
+               refs_resolve_ref_unsafe(refs, refname,
+                                       RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+                                       &oid, NULL);
+               return !is_null_oid(&oid);
+       }
+
+       for (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)
+               if (!strcmp(refname, irregular_pseudorefs[i])) {
+                       refs_resolve_ref_unsafe(refs, refname,
+                                               RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+                                               &oid, NULL);
+                       return !is_null_oid(&oid);
+               }
+
+       return 0;
+}
+
+int is_headref(struct ref_store *refs, const char *refname)
+{
+       if (!strcmp(refname, "HEAD"))
+               return refs_ref_exists(refs, refname);
+
+       return 0;
+}
+
 static int is_current_worktree_ref(const char *ref) {
        return is_pseudoref_syntax(ref) || is_per_worktree_ref(ref);
 }
@@ -1022,55 +1080,40 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
                           const char *message, void *cb_data)
 {
        struct read_ref_at_cb *cb = cb_data;
-       int reached_count;
 
        cb->tz = tz;
        cb->date = timestamp;
 
-       /*
-        * It is not possible for cb->cnt == 0 on the first iteration because
-        * that special case is handled in read_ref_at().
-        */
-       if (cb->cnt > 0)
-               cb->cnt--;
-       reached_count = cb->cnt == 0 && !is_null_oid(ooid);
-       if (timestamp <= cb->at_time || reached_count) {
+       if (timestamp <= cb->at_time || cb->cnt == 0) {
                set_read_ref_cutoffs(cb, timestamp, tz, message);
                /*
                 * we have not yet updated cb->[n|o]oid so they still
                 * hold the values for the previous record.
                 */
-               if (!is_null_oid(&cb->ooid) && !oideq(&cb->ooid, noid))
-                       warning(_("log for ref %s has gap after %s"),
+               if (!is_null_oid(&cb->ooid)) {
+                       oidcpy(cb->oid, noid);
+                       if (!oideq(&cb->ooid, noid))
+                               warning(_("log for ref %s has gap after %s"),
                                        cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
-               if (reached_count)
-                       oidcpy(cb->oid, ooid);
-               else if (!is_null_oid(&cb->ooid) || cb->date == cb->at_time)
+               }
+               else if (cb->date == cb->at_time)
                        oidcpy(cb->oid, noid);
                else if (!oideq(noid, cb->oid))
                        warning(_("log for ref %s unexpectedly ended on %s"),
                                cb->refname, show_date(cb->date, cb->tz,
                                                       DATE_MODE(RFC2822)));
+               cb->reccnt++;
+               oidcpy(&cb->ooid, ooid);
+               oidcpy(&cb->noid, noid);
                cb->found_it = 1;
+               return 1;
        }
        cb->reccnt++;
        oidcpy(&cb->ooid, ooid);
        oidcpy(&cb->noid, noid);
-       return cb->found_it;
-}
-
-static int read_ref_at_ent_newest(struct object_id *ooid UNUSED,
-                                 struct object_id *noid,
-                                 const char *email UNUSED,
-                                 timestamp_t timestamp, int tz,
-                                 const char *message, void *cb_data)
-{
-       struct read_ref_at_cb *cb = cb_data;
-
-       set_read_ref_cutoffs(cb, timestamp, tz, message);
-       oidcpy(cb->oid, noid);
-       /* We just want the first entry */
-       return 1;
+       if (cb->cnt > 0)
+               cb->cnt--;
+       return 0;
 }
 
 static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid,
@@ -1082,7 +1125,7 @@ static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid
 
        set_read_ref_cutoffs(cb, timestamp, tz, message);
        oidcpy(cb->oid, ooid);
-       if (is_null_oid(cb->oid))
+       if (cb->at_time && is_null_oid(cb->oid))
                oidcpy(cb->oid, noid);
        /* We just want the first entry */
        return 1;
@@ -1105,14 +1148,24 @@ int read_ref_at(struct ref_store *refs, const char *refname,
        cb.cutoff_cnt = cutoff_cnt;
        cb.oid = oid;
 
-       if (cb.cnt == 0) {
-               refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent_newest, &cb);
-               return 0;
-       }
-
        refs_for_each_reflog_ent_reverse(refs, refname, read_ref_at_ent, &cb);
 
        if (!cb.reccnt) {
+               if (cnt == 0) {
+                       /*
+                        * The caller asked for ref@{0}, and we had no entries.
+                        * It's a bit subtle, but in practice all callers have
+                        * prepped the "oid" field with the current value of
+                        * the ref, which is the most reasonable fallback.
+                        *
+                        * We'll put dummy values into the out-parameters (so
+                        * they're not just uninitialized garbage), and the
+                        * caller can take our return value as a hint that
+                        * we did not find any such reflog.
+                        */
+                       set_read_ref_cutoffs(&cb, 0, 0, "empty reflog");
+                       return 1;
+               }
                if (flags & GET_OID_QUIETLY)
                        exit(128);
                else
@@ -1577,10 +1630,6 @@ struct ref_iterator *refs_ref_iterator_begin(
        if (trim)
                iter = prefix_ref_iterator_begin(iter, "", trim);
 
-       /* Sanity check for subclasses: */
-       if (!iter->ordered)
-               BUG("reference iterator is not ordered");
-
        return iter;
 }
 
@@ -1707,6 +1756,13 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
        return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data);
 }
 
+int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
+                                   void *cb_data)
+{
+       return do_for_each_ref(refs, "", NULL, fn, 0,
+                              DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data);
+}
+
 static int qsort_strcmp(const void *va, const void *vb)
 {
        const char *a = *(const char **)va;
@@ -1806,8 +1862,10 @@ static int refs_read_special_head(struct ref_store *ref_store,
        int result = -1;
        strbuf_addf(&full_path, "%s/%s", ref_store->gitdir, refname);
 
-       if (strbuf_read_file(&content, full_path.buf, 0) < 0)
+       if (strbuf_read_file(&content, full_path.buf, 0) < 0) {
+               *failure_errno = errno;
                goto done;
+       }
 
        result = parse_loose_ref_contents(content.buf, oid, referent, type,
                                          failure_errno);
@@ -1818,15 +1876,45 @@ done:
        return result;
 }
 
+static int is_special_ref(const char *refname)
+{
+       /*
+        * Special references are refs that have different semantics compared
+        * to "normal" refs. These refs can thus not be stored in the ref
+        * backend, but must always be accessed via the filesystem. The
+        * following refs are special:
+        *
+        * - FETCH_HEAD may contain multiple object IDs, and each one of them
+        *   carries additional metadata like where it came from.
+        *
+        * - MERGE_HEAD may contain multiple object IDs when merging multiple
+        *   heads.
+        *
+        * Reading, writing or deleting references must consistently go either
+        * through the filesystem (special refs) or through the reference
+        * backend (normal ones).
+        */
+       static const char * const special_refs[] = {
+               "FETCH_HEAD",
+               "MERGE_HEAD",
+       };
+       size_t i;
+
+       for (i = 0; i < ARRAY_SIZE(special_refs); i++)
+               if (!strcmp(refname, special_refs[i]))
+                       return 1;
+
+       return 0;
+}
+
 int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
                      struct object_id *oid, struct strbuf *referent,
                      unsigned int *type, int *failure_errno)
 {
        assert(failure_errno);
-       if (!strcmp(refname, "FETCH_HEAD") || !strcmp(refname, "MERGE_HEAD")) {
+       if (is_special_ref(refname))
                return refs_read_special_head(ref_store, refname, oid, referent,
                                              type, failure_errno);
-       }
 
        return ref_store->be->read_raw_ref(ref_store, refname, oid, referent,
                                           type, failure_errno);
@@ -1928,11 +2016,9 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
 }
 
 /* backend functions */
-int refs_init_db(struct strbuf *err)
+int refs_init_db(struct ref_store *refs, int flags, struct strbuf *err)
 {
-       struct ref_store *refs = get_main_ref_store(the_repository);
-
-       return refs->be->init_db(refs, err);
+       return refs->be->init_db(refs, flags, err);
 }
 
 const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
@@ -2029,12 +2115,12 @@ static struct ref_store *ref_store_init(struct repository *repo,
                                        const char *gitdir,
                                        unsigned int flags)
 {
-       const char *be_name = "files";
-       struct ref_storage_be *be = find_ref_storage_backend(be_name);
+       const struct ref_storage_be *be;
        struct ref_store *refs;
 
+       be = find_ref_storage_backend(repo->ref_storage_format);
        if (!be)
-               BUG("reference backend %s is unknown", be_name);
+               BUG("reference backend is unknown");
 
        refs = be->init(repo, gitdir, flags);
        return refs;
@@ -2469,18 +2555,33 @@ cleanup:
        return ret;
 }
 
-int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+struct do_for_each_reflog_help {
+       each_reflog_fn *fn;
+       void *cb_data;
+};
+
+static int do_for_each_reflog_helper(struct repository *r UNUSED,
+                                    const char *refname,
+                                    const struct object_id *oid UNUSED,
+                                    int flags,
+                                    void *cb_data)
+{
+       struct do_for_each_reflog_help *hp = cb_data;
+       return hp->fn(refname, hp->cb_data);
+}
+
+int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)
 {
        struct ref_iterator *iter;
-       struct do_for_each_ref_help hp = { fn, cb_data };
+       struct do_for_each_reflog_help hp = { fn, cb_data };
 
        iter = refs->be->reflog_iterator_begin(refs);
 
        return do_for_each_repo_ref_iterator(the_repository, iter,
-                                            do_for_each_ref_helper, &hp);
+                                            do_for_each_reflog_helper, &hp);
 }
 
-int for_each_reflog(each_ref_fn fn, void *cb_data)
+int for_each_reflog(each_reflog_fn fn, void *cb_data)
 {
        return refs_for_each_reflog(get_main_ref_store(the_repository), fn, cb_data);
 }
@@ -2599,13 +2700,55 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
 int refs_delete_refs(struct ref_store *refs, const char *logmsg,
                     struct string_list *refnames, unsigned int flags)
 {
+       struct ref_transaction *transaction;
+       struct strbuf err = STRBUF_INIT;
+       struct string_list_item *item;
+       int ret = 0, failures = 0;
        char *msg;
-       int retval;
+
+       if (!refnames->nr)
+               return 0;
 
        msg = normalize_reflog_message(logmsg);
-       retval = refs->be->delete_refs(refs, msg, refnames, flags);
+
+       /*
+        * Since we don't check the references' old_oids, the
+        * individual updates can't fail, so we can pack all of the
+        * updates into a single transaction.
+        */
+       transaction = ref_store_transaction_begin(refs, &err);
+       if (!transaction) {
+               ret = error("%s", err.buf);
+               goto out;
+       }
+
+       for_each_string_list_item(item, refnames) {
+               ret = ref_transaction_delete(transaction, item->string,
+                                            NULL, flags, msg, &err);
+               if (ret) {
+                       warning(_("could not delete reference %s: %s"),
+                               item->string, err.buf);
+                       strbuf_reset(&err);
+                       failures = 1;
+               }
+       }
+
+       ret = ref_transaction_commit(transaction, &err);
+       if (ret) {
+               if (refnames->nr == 1)
+                       error(_("could not delete reference %s: %s"),
+                             refnames->items[0].string, err.buf);
+               else
+                       error(_("could not delete references: %s"), err.buf);
+       }
+
+out:
+       if (!ret && failures)
+               ret = -1;
+       ref_transaction_free(transaction);
+       strbuf_release(&err);
        free(msg);
-       return retval;
+       return ret;
 }
 
 int delete_refs(const char *msg, struct string_list *refnames,