#define USE_THE_REPOSITORY_VARIABLE #include "../git-compat-util.h" #include "../abspath.h" #include "../chdir-notify.h" #include "../config.h" #include "../dir.h" #include "../environment.h" #include "../gettext.h" #include "../hash.h" #include "../hex.h" #include "../iterator.h" #include "../ident.h" #include "../lockfile.h" #include "../object.h" #include "../path.h" #include "../refs.h" #include "../reftable/reftable-basics.h" #include "../reftable/reftable-stack.h" #include "../reftable/reftable-record.h" #include "../reftable/reftable-error.h" #include "../reftable/reftable-iterator.h" #include "../repo-settings.h" #include "../setup.h" #include "../strmap.h" #include "../trace2.h" #include "../write-or-die.h" #include "parse.h" #include "refs-internal.h" /* * Used as a flag in ref_update::flags when the ref_update was via an * update to HEAD. */ #define REF_UPDATE_VIA_HEAD (1 << 8) struct reftable_backend { struct reftable_stack *stack; struct reftable_iterator it; }; static void reftable_backend_on_reload(void *payload) { struct reftable_backend *be = payload; reftable_iterator_destroy(&be->it); } static int reftable_backend_init(struct reftable_backend *be, const char *path, const struct reftable_write_options *_opts) { struct reftable_write_options opts = *_opts; opts.on_reload = reftable_backend_on_reload; opts.on_reload_payload = be; return reftable_new_stack(&be->stack, path, &opts); } static void reftable_backend_release(struct reftable_backend *be) { reftable_stack_destroy(be->stack); be->stack = NULL; reftable_iterator_destroy(&be->it); } static int reftable_backend_read_ref(struct reftable_backend *be, const char *refname, struct object_id *oid, struct strbuf *referent, unsigned int *type) { struct reftable_ref_record ref = {0}; int ret; if (!be->it.ops) { ret = reftable_stack_init_ref_iterator(be->stack, &be->it); if (ret) goto done; } ret = reftable_iterator_seek_ref(&be->it, refname); if (ret) goto done; ret = reftable_iterator_next_ref(&be->it, &ref); if (ret) goto done; if (strcmp(ref.refname, refname)) { ret = 1; goto done; } if (ref.value_type == REFTABLE_REF_SYMREF) { strbuf_reset(referent); strbuf_addstr(referent, ref.value.symref); *type |= REF_ISSYMREF; } else if (reftable_ref_record_val1(&ref)) { unsigned int hash_id; switch (reftable_stack_hash_id(be->stack)) { case REFTABLE_HASH_SHA1: hash_id = GIT_HASH_SHA1; break; case REFTABLE_HASH_SHA256: hash_id = GIT_HASH_SHA256; break; default: BUG("unhandled hash ID %d", reftable_stack_hash_id(be->stack)); } oidread(oid, reftable_ref_record_val1(&ref), &hash_algos[hash_id]); } else { /* We got a tombstone, which should not happen. */ BUG("unhandled reference value type %d", ref.value_type); } done: assert(ret != REFTABLE_API_ERROR); reftable_ref_record_release(&ref); return ret; } struct reftable_ref_store { struct ref_store base; /* * The main backend refers to the common dir and thus contains common * refs as well as refs of the main repository. */ struct reftable_backend main_backend; /* * The worktree backend refers to the gitdir in case the refdb is opened * via a worktree. It thus contains the per-worktree refs. */ struct reftable_backend worktree_backend; /* * Map of worktree backends by their respective worktree names. The map * is populated lazily when we try to resolve `worktrees/$worktree` refs. */ struct strmap worktree_backends; struct reftable_write_options write_options; unsigned int store_flags; enum log_refs_config log_all_ref_updates; int err; }; /* * Downcast ref_store to reftable_ref_store. Die if ref_store is not a * reftable_ref_store. required_flags is compared with ref_store's store_flags * to ensure the ref_store has all required capabilities. "caller" is used in * any necessary error messages. */ static struct reftable_ref_store *reftable_be_downcast(struct ref_store *ref_store, unsigned int required_flags, const char *caller) { struct reftable_ref_store *refs; if (ref_store->be != &refs_be_reftable) BUG("ref_store is type \"%s\" not \"reftables\" in %s", ref_store->be->name, caller); refs = (struct reftable_ref_store *)ref_store; if ((refs->store_flags & required_flags) != required_flags) BUG("operation %s requires abilities 0x%x, but only have 0x%x", caller, required_flags, refs->store_flags); return refs; } /* * Some refs are global to the repository (refs/heads/{*}), while others are * local to the worktree (eg. HEAD, refs/bisect/{*}). We solve this by having * multiple separate databases (ie. multiple reftable/ directories), one for * the shared refs, one for the current worktree refs, and one for each * additional worktree. For reading, we merge the view of both the shared and * the current worktree's refs, when necessary. * * This function also optionally assigns the rewritten reference name that is * local to the stack. This translation is required when using worktree refs * like `worktrees/$worktree/refs/heads/foo` as worktree stacks will store * those references in their normalized form. */ static int backend_for(struct reftable_backend **out, struct reftable_ref_store *store, const char *refname, const char **rewritten_ref, int reload) { struct reftable_backend *be; const char *wtname; int wtname_len; if (!refname) { be = &store->main_backend; goto out; } switch (parse_worktree_ref(refname, &wtname, &wtname_len, rewritten_ref)) { case REF_WORKTREE_OTHER: { static struct strbuf wtname_buf = STRBUF_INIT; struct strbuf wt_dir = STRBUF_INIT; /* * We're using a static buffer here so that we don't need to * allocate the worktree name whenever we look up a reference. * This could be avoided if the strmap interface knew how to * handle keys with a length. */ strbuf_reset(&wtname_buf); strbuf_add(&wtname_buf, wtname, wtname_len); /* * There is an edge case here: when the worktree references the * current worktree, then we set up the stack once via * `worktree_backends` and once via `worktree_backend`. This is * wasteful, but in the reading case it shouldn't matter. And * in the writing case we would notice that the stack is locked * already and error out when trying to write a reference via * both stacks. */ be = strmap_get(&store->worktree_backends, wtname_buf.buf); if (!be) { strbuf_addf(&wt_dir, "%s/worktrees/%s/reftable", store->base.repo->commondir, wtname_buf.buf); CALLOC_ARRAY(be, 1); store->err = reftable_backend_init(be, wt_dir.buf, &store->write_options); assert(store->err != REFTABLE_API_ERROR); strmap_put(&store->worktree_backends, wtname_buf.buf, be); } strbuf_release(&wt_dir); goto out; } case REF_WORKTREE_CURRENT: /* * If there is no worktree stack then we're currently in the * main worktree. We thus return the main stack in that case. */ if (!store->worktree_backend.stack) be = &store->main_backend; else be = &store->worktree_backend; goto out; case REF_WORKTREE_MAIN: case REF_WORKTREE_SHARED: be = &store->main_backend; goto out; default: BUG("unhandled worktree reference type"); } out: if (reload) { int ret = reftable_stack_reload(be->stack); if (ret) return ret; } *out = be; return 0; } static int should_write_log(struct reftable_ref_store *refs, const char *refname) { enum log_refs_config log_refs_cfg = refs->log_all_ref_updates; if (log_refs_cfg == LOG_REFS_UNSET) log_refs_cfg = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; switch (log_refs_cfg) { case LOG_REFS_NONE: return refs_reflog_exists(&refs->base, refname); case LOG_REFS_ALWAYS: return 1; case LOG_REFS_NORMAL: if (should_autocreate_reflog(log_refs_cfg, refname)) return 1; return refs_reflog_exists(&refs->base, refname); default: BUG("unhandled core.logAllRefUpdates value %d", log_refs_cfg); } } static void fill_reftable_log_record(struct reftable_log_record *log, const struct ident_split *split) { const char *tz_begin; int sign = 1; reftable_log_record_release(log); log->value_type = REFTABLE_LOG_UPDATE; log->value.update.name = xstrndup(split->name_begin, split->name_end - split->name_begin); log->value.update.email = xstrndup(split->mail_begin, split->mail_end - split->mail_begin); log->value.update.time = atol(split->date_begin); tz_begin = split->tz_begin; if (*tz_begin == '-') { sign = -1; tz_begin++; } if (*tz_begin == '+') { sign = 1; tz_begin++; } log->value.update.tz_offset = sign * atoi(tz_begin); } static int reftable_be_config(const char *var, const char *value, const struct config_context *ctx, void *_opts) { struct reftable_write_options *opts = _opts; if (!strcmp(var, "reftable.blocksize")) { unsigned long block_size = git_config_ulong(var, value, ctx->kvi); if (block_size > 16777215) die("reftable block size cannot exceed 16MB"); opts->block_size = block_size; } else if (!strcmp(var, "reftable.restartinterval")) { unsigned long restart_interval = git_config_ulong(var, value, ctx->kvi); if (restart_interval > UINT16_MAX) die("reftable block size cannot exceed %u", (unsigned)UINT16_MAX); opts->restart_interval = restart_interval; } else if (!strcmp(var, "reftable.indexobjects")) { opts->skip_index_objects = !git_config_bool(var, value); } else if (!strcmp(var, "reftable.geometricfactor")) { unsigned long factor = git_config_ulong(var, value, ctx->kvi); if (factor > UINT8_MAX) die("reftable geometric factor cannot exceed %u", (unsigned)UINT8_MAX); opts->auto_compaction_factor = factor; } else if (!strcmp(var, "reftable.locktimeout")) { int64_t lock_timeout = git_config_int64(var, value, ctx->kvi); if (lock_timeout > LONG_MAX) die("reftable lock timeout cannot exceed %"PRIdMAX, (intmax_t)LONG_MAX); if (lock_timeout < 0 && lock_timeout != -1) die("reftable lock timeout does not support negative values other than -1"); opts->lock_timeout_ms = lock_timeout; } return 0; } static int reftable_be_fsync(int fd) { return fsync_component(FSYNC_COMPONENT_REFERENCE, fd); } static struct ref_store *reftable_be_init(struct repository *repo, const char *gitdir, unsigned int store_flags) { struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs)); struct strbuf path = STRBUF_INIT; int is_worktree; mode_t mask; mask = umask(0); umask(mask); base_ref_store_init(&refs->base, repo, gitdir, &refs_be_reftable); strmap_init(&refs->worktree_backends); refs->store_flags = store_flags; refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo); switch (repo->hash_algo->format_id) { case GIT_SHA1_FORMAT_ID: refs->write_options.hash_id = REFTABLE_HASH_SHA1; break; case GIT_SHA256_FORMAT_ID: refs->write_options.hash_id = REFTABLE_HASH_SHA256; break; default: BUG("unknown hash algorithm %d", repo->hash_algo->format_id); } refs->write_options.default_permissions = calc_shared_perm(the_repository, 0666 & ~mask); refs->write_options.disable_auto_compact = !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); refs->write_options.lock_timeout_ms = 100; refs->write_options.fsync = reftable_be_fsync; git_config(reftable_be_config, &refs->write_options); /* * It is somewhat unfortunate that we have to mirror the default block * size of the reftable library here. But given that the write options * wouldn't be updated by the library here, and given that we require * the proper block size to trim reflog message so that they fit, we * must set up a proper value here. */ if (!refs->write_options.block_size) refs->write_options.block_size = 4096; /* * Set up the main reftable stack that is hosted in GIT_COMMON_DIR. * This stack contains both the shared and the main worktree refs. * * Note that we don't try to resolve the path in case we have a * worktree because `get_common_dir_noenv()` already does it for us. */ is_worktree = get_common_dir_noenv(&path, gitdir); if (!is_worktree) { strbuf_reset(&path); strbuf_realpath(&path, gitdir, 0); } strbuf_addstr(&path, "/reftable"); refs->err = reftable_backend_init(&refs->main_backend, path.buf, &refs->write_options); if (refs->err) goto done; /* * If we're in a worktree we also need to set up the worktree reftable * stack that is contained in the per-worktree GIT_DIR. * * Ideally, we would also add the stack to our worktree stack map. But * we have no way to figure out the worktree name here and thus can't * do it efficiently. */ if (is_worktree) { strbuf_reset(&path); strbuf_addf(&path, "%s/reftable", gitdir); refs->err = reftable_backend_init(&refs->worktree_backend, path.buf, &refs->write_options); if (refs->err) goto done; } chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); done: assert(refs->err != REFTABLE_API_ERROR); strbuf_release(&path); return &refs->base; } static void reftable_be_release(struct ref_store *ref_store) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, 0, "release"); struct strmap_entry *entry; struct hashmap_iter iter; if (refs->main_backend.stack) reftable_backend_release(&refs->main_backend); if (refs->worktree_backend.stack) reftable_backend_release(&refs->worktree_backend); strmap_for_each_entry(&refs->worktree_backends, &iter, entry) { struct reftable_backend *be = entry->value; reftable_backend_release(be); free(be); } strmap_clear(&refs->worktree_backends, 0); } static int reftable_be_create_on_disk(struct ref_store *ref_store, int flags UNUSED, struct strbuf *err UNUSED) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE, "create"); struct strbuf sb = STRBUF_INIT; strbuf_addf(&sb, "%s/reftable", refs->base.gitdir); safe_create_dir(the_repository, sb.buf, 1); strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir); write_file(sb.buf, "ref: refs/heads/.invalid"); adjust_shared_perm(the_repository, sb.buf); strbuf_reset(&sb); strbuf_addf(&sb, "%s/refs", refs->base.gitdir); safe_create_dir(the_repository, sb.buf, 1); strbuf_reset(&sb); strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir); write_file(sb.buf, "this repository uses the reftable format"); adjust_shared_perm(the_repository, sb.buf); strbuf_release(&sb); return 0; } static int reftable_be_remove_on_disk(struct ref_store *ref_store, struct strbuf *err) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE, "remove"); struct strbuf sb = STRBUF_INIT; int ret = 0; /* * Release the ref store such that all stacks are closed. This is * required so that the "tables.list" file is not open anymore, which * would otherwise make it impossible to remove the file on Windows. */ reftable_be_release(ref_store); strbuf_addf(&sb, "%s/reftable", refs->base.gitdir); if (remove_dir_recursively(&sb, 0) < 0) { strbuf_addf(err, "could not delete reftables: %s", strerror(errno)); ret = -1; } strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir); if (unlink(sb.buf) < 0) { strbuf_addf(err, "could not delete stub HEAD: %s", strerror(errno)); ret = -1; } strbuf_reset(&sb); strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir); if (unlink(sb.buf) < 0) { strbuf_addf(err, "could not delete stub heads: %s", strerror(errno)); ret = -1; } strbuf_reset(&sb); strbuf_addf(&sb, "%s/refs", refs->base.gitdir); if (rmdir(sb.buf) < 0) { strbuf_addf(err, "could not delete refs directory: %s", strerror(errno)); ret = -1; } strbuf_release(&sb); return ret; } struct reftable_ref_iterator { struct ref_iterator base; struct reftable_ref_store *refs; struct reftable_iterator iter; struct reftable_ref_record ref; struct object_id oid; char *prefix; size_t prefix_len; char **exclude_patterns; size_t exclude_patterns_index; size_t exclude_patterns_strlen; unsigned int flags; int err; }; /* * Handle exclude patterns. Returns either `1`, which tells the caller that the * current reference shall not be shown. Or `0`, which indicates that it should * be shown. */ static int should_exclude_current_ref(struct reftable_ref_iterator *iter) { while (iter->exclude_patterns[iter->exclude_patterns_index]) { const char *pattern = iter->exclude_patterns[iter->exclude_patterns_index]; char *ref_after_pattern; int cmp; /* * Lazily cache the pattern length so that we don't have to * recompute it every time this function is called. */ if (!iter->exclude_patterns_strlen) iter->exclude_patterns_strlen = strlen(pattern); /* * When the reference name is lexicographically bigger than the * current exclude pattern we know that it won't ever match any * of the following references, either. We thus advance to the * next pattern and re-check whether it matches. * * Otherwise, if it's smaller, then we do not have a match and * thus want to show the current reference. */ cmp = strncmp(iter->ref.refname, pattern, iter->exclude_patterns_strlen); if (cmp > 0) { iter->exclude_patterns_index++; iter->exclude_patterns_strlen = 0; continue; } if (cmp < 0) return 0; /* * The reference shares a prefix with the exclude pattern and * shall thus be omitted. We skip all references that match the * pattern by seeking to the first reference after the block of * matches. * * This is done by appending the highest possible character to * the pattern. Consequently, all references that have the * pattern as prefix and whose suffix starts with anything in * the range [0x00, 0xfe] are skipped. And given that 0xff is a * non-printable character that shouldn't ever be in a ref name, * we'd not yield any such record, either. * * Note that the seeked-to reference may also be excluded. This * is not handled here though, but the caller is expected to * loop and re-verify the next reference for us. */ ref_after_pattern = xstrfmt("%s%c", pattern, 0xff); iter->err = reftable_iterator_seek_ref(&iter->iter, ref_after_pattern); iter->exclude_patterns_index++; iter->exclude_patterns_strlen = 0; trace2_counter_add(TRACE2_COUNTER_ID_REFTABLE_RESEEKS, 1); free(ref_after_pattern); return 1; } return 0; } static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator) { struct reftable_ref_iterator *iter = (struct reftable_ref_iterator *)ref_iterator; struct reftable_ref_store *refs = iter->refs; const char *referent = NULL; while (!iter->err) { int flags = 0; iter->err = reftable_iterator_next_ref(&iter->iter, &iter->ref); if (iter->err) break; /* * The files backend only lists references contained in "refs/" unless * the root refs are to be included. We emulate the same behaviour here. */ if (!starts_with(iter->ref.refname, "refs/") && !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS && is_root_ref(iter->ref.refname))) { continue; } if (iter->prefix_len && strncmp(iter->prefix, iter->ref.refname, iter->prefix_len)) { iter->err = 1; break; } if (iter->exclude_patterns && should_exclude_current_ref(iter)) continue; if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) != REF_WORKTREE_CURRENT) continue; switch (iter->ref.value_type) { case REFTABLE_REF_VAL1: oidread(&iter->oid, iter->ref.value.val1, refs->base.repo->hash_algo); break; case REFTABLE_REF_VAL2: oidread(&iter->oid, iter->ref.value.val2.value, refs->base.repo->hash_algo); break; case REFTABLE_REF_SYMREF: referent = refs_resolve_ref_unsafe(&iter->refs->base, iter->ref.refname, RESOLVE_REF_READING, &iter->oid, &flags); if (!referent) oidclr(&iter->oid, refs->base.repo->hash_algo); break; default: BUG("unhandled reference value type %d", iter->ref.value_type); } if (is_null_oid(&iter->oid)) flags |= REF_ISBROKEN; if (check_refname_format(iter->ref.refname, REFNAME_ALLOW_ONELEVEL)) { if (!refname_is_safe(iter->ref.refname)) die(_("refname is dangerous: %s"), iter->ref.refname); oidclr(&iter->oid, refs->base.repo->hash_algo); flags |= REF_BAD_NAME | REF_ISBROKEN; } if (iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS && flags & REF_ISSYMREF && flags & REF_ISBROKEN) continue; if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && !ref_resolves_to_object(iter->ref.refname, refs->base.repo, &iter->oid, flags)) continue; iter->base.refname = iter->ref.refname; iter->base.referent = referent; iter->base.oid = &iter->oid; iter->base.flags = flags; break; } if (iter->err > 0) return ITER_DONE; if (iter->err < 0) return ITER_ERROR; return ITER_OK; } static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator, const char *prefix) { struct reftable_ref_iterator *iter = (struct reftable_ref_iterator *)ref_iterator; free(iter->prefix); iter->prefix = xstrdup_or_null(prefix); iter->prefix_len = prefix ? strlen(prefix) : 0; iter->err = reftable_iterator_seek_ref(&iter->iter, prefix); return iter->err; } static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { struct reftable_ref_iterator *iter = (struct reftable_ref_iterator *)ref_iterator; if (iter->ref.value_type == REFTABLE_REF_VAL2) { oidread(peeled, iter->ref.value.val2.target_value, iter->refs->base.repo->hash_algo); return 0; } return -1; } static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator) { struct reftable_ref_iterator *iter = (struct reftable_ref_iterator *)ref_iterator; reftable_ref_record_release(&iter->ref); reftable_iterator_destroy(&iter->iter); if (iter->exclude_patterns) { for (size_t i = 0; iter->exclude_patterns[i]; i++) free(iter->exclude_patterns[i]); free(iter->exclude_patterns); } free(iter->prefix); } static struct ref_iterator_vtable reftable_ref_iterator_vtable = { .advance = reftable_ref_iterator_advance, .seek = reftable_ref_iterator_seek, .peel = reftable_ref_iterator_peel, .release = reftable_ref_iterator_release, }; static int qsort_strcmp(const void *va, const void *vb) { const char *a = *(const char **)va; const char *b = *(const char **)vb; return strcmp(a, b); } static char **filter_exclude_patterns(const char **exclude_patterns) { size_t filtered_size = 0, filtered_alloc = 0; char **filtered = NULL; if (!exclude_patterns) return NULL; for (size_t i = 0; ; i++) { const char *exclude_pattern = exclude_patterns[i]; int has_glob = 0; if (!exclude_pattern) break; for (const char *p = exclude_pattern; *p; p++) { has_glob = is_glob_special(*p); if (has_glob) break; } if (has_glob) continue; ALLOC_GROW(filtered, filtered_size + 1, filtered_alloc); filtered[filtered_size++] = xstrdup(exclude_pattern); } if (filtered_size) { QSORT(filtered, filtered_size, qsort_strcmp); ALLOC_GROW(filtered, filtered_size + 1, filtered_alloc); filtered[filtered_size++] = NULL; } return filtered; } static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_store *refs, struct reftable_stack *stack, const char *prefix, const char **exclude_patterns, int flags) { struct reftable_ref_iterator *iter; int ret; iter = xcalloc(1, sizeof(*iter)); base_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable); iter->base.oid = &iter->oid; iter->flags = flags; iter->refs = refs; iter->exclude_patterns = filter_exclude_patterns(exclude_patterns); ret = refs->err; if (ret) goto done; ret = reftable_stack_reload(stack); if (ret) goto done; ret = reftable_stack_init_ref_iterator(stack, &iter->iter); if (ret) goto done; ret = reftable_ref_iterator_seek(&iter->base, prefix); if (ret) goto done; done: iter->err = ret; return iter; } static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_store, const char *prefix, const char **exclude_patterns, unsigned int flags) { struct reftable_ref_iterator *main_iter, *worktree_iter; struct reftable_ref_store *refs; unsigned int required_flags = REF_STORE_READ; if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) required_flags |= REF_STORE_ODB; refs = reftable_be_downcast(ref_store, required_flags, "ref_iterator_begin"); main_iter = ref_iterator_for_stack(refs, refs->main_backend.stack, prefix, exclude_patterns, flags); /* * The worktree stack is only set when we're in an actual worktree * right now. If we aren't, then we return the common reftable * iterator, only. */ if (!refs->worktree_backend.stack) return &main_iter->base; /* * Otherwise we merge both the common and the per-worktree refs into a * single iterator. */ worktree_iter = ref_iterator_for_stack(refs, refs->worktree_backend.stack, prefix, exclude_patterns, flags); return merge_ref_iterator_begin(&worktree_iter->base, &main_iter->base, ref_iterator_select, NULL); } static int reftable_be_read_raw_ref(struct ref_store *ref_store, const char *refname, struct object_id *oid, struct strbuf *referent, unsigned int *type, int *failure_errno) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "read_raw_ref"); struct reftable_backend *be; int ret; if (refs->err < 0) return refs->err; ret = backend_for(&be, refs, refname, &refname, 1); if (ret) return ret; ret = reftable_backend_read_ref(be, refname, oid, referent, type); if (ret < 0) return ret; if (ret > 0) { *failure_errno = ENOENT; return -1; } return 0; } static int reftable_be_read_symbolic_ref(struct ref_store *ref_store, const char *refname, struct strbuf *referent) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "read_symbolic_ref"); struct reftable_backend *be; struct object_id oid; unsigned int type = 0; int ret; ret = backend_for(&be, refs, refname, &refname, 1); if (ret) return ret; ret = reftable_backend_read_ref(be, refname, &oid, referent, &type); if (ret) ret = -1; else if (type == REF_ISSYMREF) ; /* happy */ else ret = NOT_A_SYMREF; return ret; } struct reftable_transaction_update { struct ref_update *update; struct object_id current_oid; }; struct write_transaction_table_arg { struct reftable_ref_store *refs; struct reftable_backend *be; struct reftable_addition *addition; struct reftable_transaction_update *updates; size_t updates_nr; size_t updates_alloc; size_t updates_expected; uint64_t max_index; }; struct reftable_transaction_data { struct write_transaction_table_arg *args; size_t args_nr, args_alloc; }; static void free_transaction_data(struct reftable_transaction_data *tx_data) { if (!tx_data) return; for (size_t i = 0; i < tx_data->args_nr; i++) { reftable_addition_destroy(tx_data->args[i].addition); free(tx_data->args[i].updates); } free(tx_data->args); free(tx_data); } /* * Prepare transaction update for the given reference update. This will cause * us to lock the corresponding reftable stack for concurrent modification. */ static int prepare_transaction_update(struct write_transaction_table_arg **out, struct reftable_ref_store *refs, struct reftable_transaction_data *tx_data, struct ref_update *update, struct strbuf *err) { struct write_transaction_table_arg *arg = NULL; struct reftable_backend *be; size_t i; int ret; /* * This function gets called in a loop, and we don't want to repeatedly * reload the stack for every single ref update. Instead, we manually * reload further down in the case where we haven't yet prepared the * specific `reftable_backend`. */ ret = backend_for(&be, refs, update->refname, NULL, 0); if (ret) return ret; /* * Search for a preexisting stack update. If there is one then we add * the update to it, otherwise we set up a new stack update. */ for (i = 0; !arg && i < tx_data->args_nr; i++) if (tx_data->args[i].be == be) arg = &tx_data->args[i]; if (!arg) { struct reftable_addition *addition; ret = reftable_stack_reload(be->stack); if (ret) return ret; ret = reftable_stack_new_addition(&addition, be->stack, REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret) { if (ret == REFTABLE_LOCK_ERROR) strbuf_addstr(err, "cannot lock references"); return ret; } ALLOC_GROW(tx_data->args, tx_data->args_nr + 1, tx_data->args_alloc); arg = &tx_data->args[tx_data->args_nr++]; arg->refs = refs; arg->be = be; arg->addition = addition; arg->updates = NULL; arg->updates_nr = 0; arg->updates_alloc = 0; arg->updates_expected = 0; arg->max_index = 0; } arg->updates_expected++; if (out) *out = arg; return 0; } /* * Queue a reference update for the correct stack. We potentially need to * handle multiple stack updates in a single transaction when it spans across * multiple worktrees. */ static int queue_transaction_update(struct reftable_ref_store *refs, struct reftable_transaction_data *tx_data, struct ref_update *update, struct object_id *current_oid, struct strbuf *err) { struct write_transaction_table_arg *arg = NULL; int ret; if (update->backend_data) BUG("reference update queued more than once"); ret = prepare_transaction_update(&arg, refs, tx_data, update, err); if (ret < 0) return ret; ALLOC_GROW(arg->updates, arg->updates_nr + 1, arg->updates_alloc); arg->updates[arg->updates_nr].update = update; oidcpy(&arg->updates[arg->updates_nr].current_oid, current_oid); update->backend_data = &arg->updates[arg->updates_nr++]; return 0; } static enum ref_transaction_error prepare_single_update(struct reftable_ref_store *refs, struct reftable_transaction_data *tx_data, struct ref_transaction *transaction, struct reftable_backend *be, struct ref_update *u, size_t update_idx, struct string_list *refnames_to_check, unsigned int head_type, struct strbuf *head_referent, struct strbuf *referent, struct strbuf *err) { enum ref_transaction_error ret = 0; struct object_id current_oid = {0}; const char *rewritten_ref; /* * There is no need to reload the respective backends here as * we have already reloaded them when preparing the transaction * update. And given that the stacks have been locked there * shouldn't have been any concurrent modifications of the * stack. */ ret = backend_for(&be, refs, u->refname, &rewritten_ref, 0); if (ret) return REF_TRANSACTION_ERROR_GENERIC; /* Verify that the new object ID is valid. */ if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) && !(u->flags & REF_SKIP_OID_VERIFICATION) && !(u->flags & REF_LOG_ONLY)) { struct object *o = parse_object(refs->base.repo, &u->new_oid); if (!o) { strbuf_addf(err, _("trying to write ref '%s' with nonexistent object %s"), u->refname, oid_to_hex(&u->new_oid)); return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; } if (o->type != OBJ_COMMIT && is_branch(u->refname)) { strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"), oid_to_hex(&u->new_oid), u->refname); return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE; } } /* * When we update the reference that HEAD points to we enqueue * a second log-only update for HEAD so that its reflog is * updated accordingly. */ if (head_type == REF_ISSYMREF && !(u->flags & REF_LOG_ONLY) && !(u->flags & REF_UPDATE_VIA_HEAD) && !strcmp(rewritten_ref, head_referent->buf)) { /* * First make sure that HEAD is not already in the * transaction. This check is O(lg N) in the transaction * size, but it happens at most once per transaction. */ if (string_list_has_string(&transaction->refnames, "HEAD")) { /* An entry already existed */ strbuf_addf(err, _("multiple updates for 'HEAD' (including one " "via its referent '%s') are not allowed"), u->refname); return REF_TRANSACTION_ERROR_NAME_CONFLICT; } ref_transaction_add_update( transaction, "HEAD", u->flags | REF_LOG_ONLY | REF_NO_DEREF, &u->new_oid, &u->old_oid, NULL, NULL, NULL, u->msg); } ret = reftable_backend_read_ref(be, rewritten_ref, ¤t_oid, referent, &u->type); if (ret < 0) return REF_TRANSACTION_ERROR_GENERIC; if (ret > 0 && !ref_update_expects_existing_old_ref(u)) { struct string_list_item *item; /* * The reference does not exist, and we either have no * old object ID or expect the reference to not exist. * We can thus skip below safety checks as well as the * symref splitting. But we do want to verify that * there is no conflicting reference here so that we * can output a proper error message instead of failing * at a later point. */ item = string_list_append(refnames_to_check, u->refname); item->util = xmalloc(sizeof(update_idx)); memcpy(item->util, &update_idx, sizeof(update_idx)); /* * There is no need to write the reference deletion * when the reference in question doesn't exist. */ if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) { ret = queue_transaction_update(refs, tx_data, u, ¤t_oid, err); if (ret) return REF_TRANSACTION_ERROR_GENERIC; } return 0; } if (ret > 0) { /* The reference does not exist, but we expected it to. */ strbuf_addf(err, _("cannot lock ref '%s': " "unable to resolve reference '%s'"), ref_update_original_update_refname(u), u->refname); return REF_TRANSACTION_ERROR_NONEXISTENT_REF; } if (u->type & REF_ISSYMREF) { /* * The reftable stack is locked at this point already, * so it is safe to call `refs_resolve_ref_unsafe()` * here without causing races. */ const char *resolved = refs_resolve_ref_unsafe(&refs->base, u->refname, 0, ¤t_oid, NULL); if (u->flags & REF_NO_DEREF) { if (u->flags & REF_HAVE_OLD && !resolved) { strbuf_addf(err, _("cannot lock ref '%s': " "error reading reference"), u->refname); return REF_TRANSACTION_ERROR_GENERIC; } } else { struct ref_update *new_update; int new_flags; new_flags = u->flags; if (!strcmp(rewritten_ref, "HEAD")) new_flags |= REF_UPDATE_VIA_HEAD; if (string_list_has_string(&transaction->refnames, referent->buf)) { strbuf_addf(err, _("multiple updates for '%s' (including one " "via symref '%s') are not allowed"), referent->buf, u->refname); return REF_TRANSACTION_ERROR_NAME_CONFLICT; } /* * If we are updating a symref (eg. HEAD), we should also * update the branch that the symref points to. * * This is generic functionality, and would be better * done in refs.c, but the current implementation is * intertwined with the locking in files-backend.c. */ new_update = ref_transaction_add_update( transaction, referent->buf, new_flags, u->new_target ? NULL : &u->new_oid, u->old_target ? NULL : &u->old_oid, u->new_target, u->old_target, u->committer_info, u->msg); new_update->parent_update = u; /* * Change the symbolic ref update to log only. Also, it * doesn't need to check its old OID value, as that will be * done when new_update is processed. */ u->flags |= REF_LOG_ONLY | REF_NO_DEREF; u->flags &= ~REF_HAVE_OLD; } } /* * Verify that the old object matches our expectations. Note * that the error messages here do not make a lot of sense in * the context of the reftable backend as we never lock * individual refs. But the error messages match what the files * backend returns, which keeps our tests happy. */ if (u->old_target) { if (!(u->type & REF_ISSYMREF)) { strbuf_addf(err, _("cannot lock ref '%s': " "expected symref with target '%s': " "but is a regular ref"), ref_update_original_update_refname(u), u->old_target); return REF_TRANSACTION_ERROR_EXPECTED_SYMREF; } ret = ref_update_check_old_target(referent->buf, u, err); if (ret) return ret; } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) { if (is_null_oid(&u->old_oid)) { strbuf_addf(err, _("cannot lock ref '%s': " "reference already exists"), ref_update_original_update_refname(u)); return REF_TRANSACTION_ERROR_CREATE_EXISTS; } else if (is_null_oid(¤t_oid)) { strbuf_addf(err, _("cannot lock ref '%s': " "reference is missing but expected %s"), ref_update_original_update_refname(u), oid_to_hex(&u->old_oid)); return REF_TRANSACTION_ERROR_NONEXISTENT_REF; } else { strbuf_addf(err, _("cannot lock ref '%s': " "is at %s but expected %s"), ref_update_original_update_refname(u), oid_to_hex(¤t_oid), oid_to_hex(&u->old_oid)); return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE; } } /* * If all of the following conditions are true: * * - We're not about to write a symref. * - We're not about to write a log-only entry. * - Old and new object ID are different. * * Then we're essentially doing a no-op update that can be * skipped. This is not only for the sake of efficiency, but * also skips writing unneeded reflog entries. */ if ((u->type & REF_ISSYMREF) || (u->flags & REF_LOG_ONLY) || (u->flags & REF_HAVE_NEW && !oideq(¤t_oid, &u->new_oid))) if (queue_transaction_update(refs, tx_data, u, ¤t_oid, err)) return REF_TRANSACTION_ERROR_GENERIC; return 0; } static int reftable_be_transaction_prepare(struct ref_store *ref_store, struct ref_transaction *transaction, struct strbuf *err) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE|REF_STORE_MAIN, "ref_transaction_prepare"); struct strbuf referent = STRBUF_INIT, head_referent = STRBUF_INIT; struct string_list refnames_to_check = STRING_LIST_INIT_NODUP; struct reftable_transaction_data *tx_data = NULL; struct reftable_backend *be; struct object_id head_oid; unsigned int head_type = 0; size_t i; int ret; ret = refs->err; if (ret < 0) goto done; tx_data = xcalloc(1, sizeof(*tx_data)); /* * Preprocess all updates. For one we check that there are no duplicate * reference updates in this transaction. Second, we lock all stacks * that will be modified during the transaction. */ for (i = 0; i < transaction->nr; i++) { ret = prepare_transaction_update(NULL, refs, tx_data, transaction->updates[i], err); if (ret) goto done; } /* * Now that we have counted updates per stack we can preallocate their * arrays. This avoids having to reallocate many times. */ for (i = 0; i < tx_data->args_nr; i++) { CALLOC_ARRAY(tx_data->args[i].updates, tx_data->args[i].updates_expected); tx_data->args[i].updates_alloc = tx_data->args[i].updates_expected; } /* * TODO: it's dubious whether we should reload the stack that "HEAD" * belongs to or not. In theory, it may happen that we only modify * stacks which are _not_ part of the "HEAD" stack. In that case we * wouldn't have prepared any transaction for its stack and would not * have reloaded it, which may mean that it is stale. * * On the other hand, reloading that stack without locking it feels * wrong, too, as the value of "HEAD" could be modified concurrently at * any point in time. */ ret = backend_for(&be, refs, "HEAD", NULL, 0); if (ret) goto done; ret = reftable_backend_read_ref(be, "HEAD", &head_oid, &head_referent, &head_type); if (ret < 0) goto done; ret = 0; for (i = 0; i < transaction->nr; i++) { ret = prepare_single_update(refs, tx_data, transaction, be, transaction->updates[i], i, &refnames_to_check, head_type, &head_referent, &referent, err); if (ret) { if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { strbuf_reset(err); ret = 0; continue; } goto done; } } ret = refs_verify_refnames_available(ref_store, &refnames_to_check, &transaction->refnames, NULL, transaction, transaction->flags & REF_TRANSACTION_FLAG_INITIAL, err); if (ret < 0) goto done; transaction->backend_data = tx_data; transaction->state = REF_TRANSACTION_PREPARED; done: if (ret < 0) { free_transaction_data(tx_data); transaction->state = REF_TRANSACTION_CLOSED; if (!err->len) strbuf_addf(err, _("reftable: transaction prepare: %s"), reftable_error_str(ret)); } strbuf_release(&referent); strbuf_release(&head_referent); string_list_clear(&refnames_to_check, 1); return ret; } static int reftable_be_transaction_abort(struct ref_store *ref_store UNUSED, struct ref_transaction *transaction, struct strbuf *err UNUSED) { struct reftable_transaction_data *tx_data = transaction->backend_data; free_transaction_data(tx_data); transaction->state = REF_TRANSACTION_CLOSED; return 0; } static int transaction_update_cmp(const void *a, const void *b) { struct reftable_transaction_update *update_a = (struct reftable_transaction_update *)a; struct reftable_transaction_update *update_b = (struct reftable_transaction_update *)b; /* * If there is an index set, it should take preference (default is 0). * This ensures that updates with indexes are sorted amongst themselves. */ if (update_a->update->index || update_b->update->index) return update_a->update->index - update_b->update->index; return strcmp(update_a->update->refname, update_b->update->refname); } static int write_transaction_table(struct reftable_writer *writer, void *cb_data) { struct write_transaction_table_arg *arg = cb_data; uint64_t ts = reftable_stack_next_update_index(arg->be->stack); struct reftable_log_record *logs = NULL; struct ident_split committer_ident = {0}; size_t logs_nr = 0, logs_alloc = 0, i; const char *committer_info; int ret = 0; committer_info = git_committer_info(0); if (split_ident_line(&committer_ident, committer_info, strlen(committer_info))) BUG("failed splitting committer info"); QSORT(arg->updates, arg->updates_nr, transaction_update_cmp); /* * During reflog migration, we add indexes for a single reflog with * multiple entries. Each entry will contain a different update_index, * so set the limits accordingly. */ ret = reftable_writer_set_limits(writer, ts, ts + arg->max_index); if (ret < 0) goto done; for (i = 0; i < arg->updates_nr; i++) { struct reftable_transaction_update *tx_update = &arg->updates[i]; struct ref_update *u = tx_update->update; if (u->rejection_err) continue; /* * Write a reflog entry when updating a ref to point to * something new in either of the following cases: * * - The reference is about to be deleted. We always want to * delete the reflog in that case. * - REF_FORCE_CREATE_REFLOG is set, asking us to always create * the reflog entry. * - `core.logAllRefUpdates` tells us to create the reflog for * the given ref. */ if ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) { struct reftable_log_record log = {0}; struct reftable_iterator it = {0}; ret = reftable_stack_init_log_iterator(arg->be->stack, &it); if (ret < 0) goto done; /* * When deleting refs we also delete all reflog entries * with them. While it is not strictly required to * delete reflogs together with their refs, this * matches the behaviour of the files backend. * * Unfortunately, we have no better way than to delete * all reflog entries one by one. */ ret = reftable_iterator_seek_log(&it, u->refname); while (ret == 0) { struct reftable_log_record *tombstone; ret = reftable_iterator_next_log(&it, &log); if (ret < 0) break; if (ret > 0 || strcmp(log.refname, u->refname)) { ret = 0; break; } ALLOC_GROW(logs, logs_nr + 1, logs_alloc); tombstone = &logs[logs_nr++]; tombstone->refname = xstrdup(u->refname); tombstone->value_type = REFTABLE_LOG_DELETION; tombstone->update_index = log.update_index; } reftable_log_record_release(&log); reftable_iterator_destroy(&it); if (ret) goto done; } else if (!(u->flags & REF_SKIP_CREATE_REFLOG) && (u->flags & REF_HAVE_NEW) && (u->flags & REF_FORCE_CREATE_REFLOG || should_write_log(arg->refs, u->refname))) { struct reftable_log_record *log; int create_reflog = 1; if (u->new_target) { if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target, RESOLVE_REF_READING, &u->new_oid, NULL)) { /* * TODO: currently we skip creating reflogs for dangling * symref updates. It would be nice to capture this as * zero oid updates however. */ create_reflog = 0; } } if (create_reflog) { struct ident_split c; ALLOC_GROW(logs, logs_nr + 1, logs_alloc); log = &logs[logs_nr++]; memset(log, 0, sizeof(*log)); if (u->committer_info) { if (split_ident_line(&c, u->committer_info, strlen(u->committer_info))) BUG("failed splitting committer info"); } else { c = committer_ident; } fill_reftable_log_record(log, &c); /* * Updates are sorted by the writer. So updates for the same * refname need to contain different update indices. */ log->update_index = ts + u->index; log->refname = xstrdup(u->refname); memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ); memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ); log->value.update.message = xstrndup(u->msg, arg->refs->write_options.block_size / 2); } } if (u->flags & REF_LOG_ONLY) continue; if (u->new_target) { struct reftable_ref_record ref = { .refname = (char *)u->refname, .value_type = REFTABLE_REF_SYMREF, .value.symref = (char *)u->new_target, .update_index = ts, }; ret = reftable_writer_add_ref(writer, &ref); if (ret < 0) goto done; } else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) { struct reftable_ref_record ref = { .refname = (char *)u->refname, .update_index = ts, .value_type = REFTABLE_REF_DELETION, }; ret = reftable_writer_add_ref(writer, &ref); if (ret < 0) goto done; } else if (u->flags & REF_HAVE_NEW) { struct reftable_ref_record ref = {0}; struct object_id peeled; int peel_error; ref.refname = (char *)u->refname; ref.update_index = ts; peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled); if (!peel_error) { ref.value_type = REFTABLE_REF_VAL2; memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ); memcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ); } else if (!is_null_oid(&u->new_oid)) { ref.value_type = REFTABLE_REF_VAL1; memcpy(ref.value.val1, u->new_oid.hash, GIT_MAX_RAWSZ); } ret = reftable_writer_add_ref(writer, &ref); if (ret < 0) goto done; } } /* * Logs are written at the end so that we do not have intermixed ref * and log blocks. */ if (logs) { ret = reftable_writer_add_logs(writer, logs, logs_nr); if (ret < 0) goto done; } done: assert(ret != REFTABLE_API_ERROR); for (i = 0; i < logs_nr; i++) reftable_log_record_release(&logs[i]); free(logs); return ret; } static int reftable_be_transaction_finish(struct ref_store *ref_store UNUSED, struct ref_transaction *transaction, struct strbuf *err) { struct reftable_transaction_data *tx_data = transaction->backend_data; int ret = 0; for (size_t i = 0; i < tx_data->args_nr; i++) { tx_data->args[i].max_index = transaction->max_index; ret = reftable_addition_add(tx_data->args[i].addition, write_transaction_table, &tx_data->args[i]); if (ret < 0) goto done; ret = reftable_addition_commit(tx_data->args[i].addition); if (ret < 0) goto done; } done: assert(ret != REFTABLE_API_ERROR); free_transaction_data(tx_data); transaction->state = REF_TRANSACTION_CLOSED; if (ret) { strbuf_addf(err, _("reftable: transaction failure: %s"), reftable_error_str(ret)); return -1; } return ret; } static int reftable_be_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *opts) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB, "pack_refs"); struct reftable_stack *stack; int ret; if (refs->err) return refs->err; stack = refs->worktree_backend.stack; if (!stack) stack = refs->main_backend.stack; if (opts->flags & PACK_REFS_AUTO) ret = reftable_stack_auto_compact(stack); else ret = reftable_stack_compact_all(stack, NULL); if (ret < 0) { ret = error(_("unable to compact stack: %s"), reftable_error_str(ret)); goto out; } ret = reftable_stack_clean(stack); if (ret) goto out; out: return ret; } struct write_create_symref_arg { struct reftable_ref_store *refs; struct reftable_stack *stack; struct strbuf *err; const char *refname; const char *target; const char *logmsg; }; struct write_copy_arg { struct reftable_ref_store *refs; struct reftable_backend *be; const char *oldname; const char *newname; const char *logmsg; int delete_old; }; static int write_copy_table(struct reftable_writer *writer, void *cb_data) { struct write_copy_arg *arg = cb_data; uint64_t deletion_ts, creation_ts; struct reftable_ref_record old_ref = {0}, refs[2] = {0}; struct reftable_log_record old_log = {0}, *logs = NULL; struct reftable_iterator it = {0}; struct string_list skip = STRING_LIST_INIT_NODUP; struct ident_split committer_ident = {0}; struct strbuf errbuf = STRBUF_INIT; size_t logs_nr = 0, logs_alloc = 0, i; const char *committer_info; int ret; committer_info = git_committer_info(0); if (split_ident_line(&committer_ident, committer_info, strlen(committer_info))) BUG("failed splitting committer info"); if (reftable_stack_read_ref(arg->be->stack, arg->oldname, &old_ref)) { ret = error(_("refname %s not found"), arg->oldname); goto done; } if (old_ref.value_type == REFTABLE_REF_SYMREF) { ret = error(_("refname %s is a symbolic ref, copying it is not supported"), arg->oldname); goto done; } /* * There's nothing to do in case the old and new name are the same, so * we exit early in that case. */ if (!strcmp(arg->oldname, arg->newname)) { ret = 0; goto done; } /* * Verify that the new refname is available. */ if (arg->delete_old) string_list_insert(&skip, arg->oldname); ret = refs_verify_refname_available(&arg->refs->base, arg->newname, NULL, &skip, 0, &errbuf); if (ret < 0) { error("%s", errbuf.buf); goto done; } /* * When deleting the old reference we have to use two update indices: * once to delete the old ref and its reflog, and once to create the * new ref and its reflog. They need to be staged with two separate * indices because the new reflog needs to encode both the deletion of * the old branch and the creation of the new branch, and we cannot do * two changes to a reflog in a single update. */ deletion_ts = creation_ts = reftable_stack_next_update_index(arg->be->stack); if (arg->delete_old) creation_ts++; ret = reftable_writer_set_limits(writer, deletion_ts, creation_ts); if (ret < 0) goto done; /* * Add the new reference. If this is a rename then we also delete the * old reference. */ refs[0] = old_ref; refs[0].refname = xstrdup(arg->newname); refs[0].update_index = creation_ts; if (arg->delete_old) { refs[1].refname = xstrdup(arg->oldname); refs[1].value_type = REFTABLE_REF_DELETION; refs[1].update_index = deletion_ts; } ret = reftable_writer_add_refs(writer, refs, arg->delete_old ? 2 : 1); if (ret < 0) goto done; /* * When deleting the old branch we need to create a reflog entry on the * new branch name that indicates that the old branch has been deleted * and then recreated. This is a tad weird, but matches what the files * backend does. */ if (arg->delete_old) { struct strbuf head_referent = STRBUF_INIT; struct object_id head_oid; int append_head_reflog; unsigned head_type = 0; ALLOC_GROW(logs, logs_nr + 1, logs_alloc); memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); fill_reftable_log_record(&logs[logs_nr], &committer_ident); logs[logs_nr].refname = xstrdup(arg->newname); logs[logs_nr].update_index = deletion_ts; logs[logs_nr].value.update.message = xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); memcpy(logs[logs_nr].value.update.old_hash, old_ref.value.val1, GIT_MAX_RAWSZ); logs_nr++; ret = reftable_backend_read_ref(arg->be, "HEAD", &head_oid, &head_referent, &head_type); if (ret < 0) goto done; append_head_reflog = (head_type & REF_ISSYMREF) && !strcmp(head_referent.buf, arg->oldname); strbuf_release(&head_referent); /* * The files backend uses `refs_delete_ref()` to delete the old * branch name, which will append a reflog entry for HEAD in * case it points to the old branch. */ if (append_head_reflog) { ALLOC_GROW(logs, logs_nr + 1, logs_alloc); logs[logs_nr] = logs[logs_nr - 1]; logs[logs_nr].refname = xstrdup("HEAD"); logs[logs_nr].value.update.name = xstrdup(logs[logs_nr].value.update.name); logs[logs_nr].value.update.email = xstrdup(logs[logs_nr].value.update.email); logs[logs_nr].value.update.message = xstrdup(logs[logs_nr].value.update.message); logs_nr++; } } /* * Create the reflog entry for the newly created branch. */ ALLOC_GROW(logs, logs_nr + 1, logs_alloc); memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); fill_reftable_log_record(&logs[logs_nr], &committer_ident); logs[logs_nr].refname = xstrdup(arg->newname); logs[logs_nr].update_index = creation_ts; logs[logs_nr].value.update.message = xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); memcpy(logs[logs_nr].value.update.new_hash, old_ref.value.val1, GIT_MAX_RAWSZ); logs_nr++; /* * In addition to writing the reflog entry for the new branch, we also * copy over all log entries from the old reflog. Last but not least, * when renaming we also have to delete all the old reflog entries. */ ret = reftable_stack_init_log_iterator(arg->be->stack, &it); if (ret < 0) goto done; ret = reftable_iterator_seek_log(&it, arg->oldname); if (ret < 0) goto done; while (1) { ret = reftable_iterator_next_log(&it, &old_log); if (ret < 0) goto done; if (ret > 0 || strcmp(old_log.refname, arg->oldname)) { ret = 0; break; } free(old_log.refname); /* * Copy over the old reflog entry with the new refname. */ ALLOC_GROW(logs, logs_nr + 1, logs_alloc); logs[logs_nr] = old_log; logs[logs_nr].refname = xstrdup(arg->newname); logs_nr++; /* * Delete the old reflog entry in case we are renaming. */ if (arg->delete_old) { ALLOC_GROW(logs, logs_nr + 1, logs_alloc); memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); logs[logs_nr].refname = xstrdup(arg->oldname); logs[logs_nr].value_type = REFTABLE_LOG_DELETION; logs[logs_nr].update_index = old_log.update_index; logs_nr++; } /* * Transfer ownership of the log record we're iterating over to * the array of log records. Otherwise, the pointers would get * free'd or reallocated by the iterator. */ memset(&old_log, 0, sizeof(old_log)); } ret = reftable_writer_add_logs(writer, logs, logs_nr); if (ret < 0) goto done; done: assert(ret != REFTABLE_API_ERROR); reftable_iterator_destroy(&it); string_list_clear(&skip, 0); strbuf_release(&errbuf); for (i = 0; i < logs_nr; i++) reftable_log_record_release(&logs[i]); free(logs); for (i = 0; i < ARRAY_SIZE(refs); i++) reftable_ref_record_release(&refs[i]); reftable_ref_record_release(&old_ref); reftable_log_record_release(&old_log); return ret; } static int reftable_be_rename_ref(struct ref_store *ref_store, const char *oldrefname, const char *newrefname, const char *logmsg) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE, "rename_ref"); struct write_copy_arg arg = { .refs = refs, .oldname = oldrefname, .newname = newrefname, .logmsg = logmsg, .delete_old = 1, }; int ret; ret = refs->err; if (ret < 0) goto done; ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1); if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg); done: assert(ret != REFTABLE_API_ERROR); return ret; } static int reftable_be_copy_ref(struct ref_store *ref_store, const char *oldrefname, const char *newrefname, const char *logmsg) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE, "copy_ref"); struct write_copy_arg arg = { .refs = refs, .oldname = oldrefname, .newname = newrefname, .logmsg = logmsg, }; int ret; ret = refs->err; if (ret < 0) goto done; ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1); if (ret) goto done; ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg); done: assert(ret != REFTABLE_API_ERROR); return ret; } struct reftable_reflog_iterator { struct ref_iterator base; struct reftable_ref_store *refs; struct reftable_iterator iter; struct reftable_log_record log; struct strbuf last_name; int err; }; static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator) { struct reftable_reflog_iterator *iter = (struct reftable_reflog_iterator *)ref_iterator; while (!iter->err) { iter->err = reftable_iterator_next_log(&iter->iter, &iter->log); if (iter->err) break; /* * We want the refnames that we have reflogs for, so we skip if * we've already produced this name. This could be faster by * seeking directly to reflog@update_index==0. */ if (!strcmp(iter->log.refname, iter->last_name.buf)) continue; if (check_refname_format(iter->log.refname, REFNAME_ALLOW_ONELEVEL)) continue; strbuf_reset(&iter->last_name); strbuf_addstr(&iter->last_name, iter->log.refname); iter->base.refname = iter->log.refname; break; } if (iter->err > 0) return ITER_DONE; if (iter->err < 0) return ITER_ERROR; return ITER_OK; } static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED, const char *prefix UNUSED) { BUG("reftable reflog iterator cannot be seeked"); return -1; } static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED, struct object_id *peeled UNUSED) { BUG("reftable reflog iterator cannot be peeled"); return -1; } static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator) { struct reftable_reflog_iterator *iter = (struct reftable_reflog_iterator *)ref_iterator; reftable_log_record_release(&iter->log); reftable_iterator_destroy(&iter->iter); strbuf_release(&iter->last_name); } static struct ref_iterator_vtable reftable_reflog_iterator_vtable = { .advance = reftable_reflog_iterator_advance, .seek = reftable_reflog_iterator_seek, .peel = reftable_reflog_iterator_peel, .release = reftable_reflog_iterator_release, }; static struct reftable_reflog_iterator *reflog_iterator_for_stack(struct reftable_ref_store *refs, struct reftable_stack *stack) { struct reftable_reflog_iterator *iter; int ret; iter = xcalloc(1, sizeof(*iter)); base_ref_iterator_init(&iter->base, &reftable_reflog_iterator_vtable); strbuf_init(&iter->last_name, 0); iter->refs = refs; ret = refs->err; if (ret) goto done; ret = reftable_stack_reload(stack); if (ret < 0) goto done; ret = reftable_stack_init_log_iterator(stack, &iter->iter); if (ret < 0) goto done; ret = reftable_iterator_seek_log(&iter->iter, ""); if (ret < 0) goto done; done: iter->err = ret; return iter; } static struct ref_iterator *reftable_be_reflog_iterator_begin(struct ref_store *ref_store) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "reflog_iterator_begin"); struct reftable_reflog_iterator *main_iter, *worktree_iter; main_iter = reflog_iterator_for_stack(refs, refs->main_backend.stack); if (!refs->worktree_backend.stack) return &main_iter->base; worktree_iter = reflog_iterator_for_stack(refs, refs->worktree_backend.stack); return merge_ref_iterator_begin(&worktree_iter->base, &main_iter->base, ref_iterator_select, NULL); } static int yield_log_record(struct reftable_ref_store *refs, struct reftable_log_record *log, each_reflog_ent_fn fn, void *cb_data) { struct object_id old_oid, new_oid; const char *full_committer; oidread(&old_oid, log->value.update.old_hash, refs->base.repo->hash_algo); oidread(&new_oid, log->value.update.new_hash, refs->base.repo->hash_algo); /* * When both the old object ID and the new object ID are null * then this is the reflog existence marker. The caller must * not be aware of it. */ if (is_null_oid(&old_oid) && is_null_oid(&new_oid)) return 0; full_committer = fmt_ident(log->value.update.name, log->value.update.email, WANT_COMMITTER_IDENT, NULL, IDENT_NO_DATE); return fn(&old_oid, &new_oid, full_committer, log->value.update.time, log->value.update.tz_offset, log->value.update.message, cb_data); } static int reftable_be_for_each_reflog_ent_reverse(struct ref_store *ref_store, const char *refname, each_reflog_ent_fn fn, void *cb_data) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "for_each_reflog_ent_reverse"); struct reftable_log_record log = {0}; struct reftable_iterator it = {0}; struct reftable_backend *be; int ret; if (refs->err < 0) return refs->err; /* * TODO: we should adapt this callsite to reload the stack. There is no * obvious reason why we shouldn't. */ ret = backend_for(&be, refs, refname, &refname, 0); if (ret) goto done; ret = reftable_stack_init_log_iterator(be->stack, &it); if (ret < 0) goto done; ret = reftable_iterator_seek_log(&it, refname); while (!ret) { ret = reftable_iterator_next_log(&it, &log); if (ret < 0) break; if (ret > 0 || strcmp(log.refname, refname)) { ret = 0; break; } ret = yield_log_record(refs, &log, fn, cb_data); if (ret) break; } done: reftable_log_record_release(&log); reftable_iterator_destroy(&it); return ret; } static int reftable_be_for_each_reflog_ent(struct ref_store *ref_store, const char *refname, each_reflog_ent_fn fn, void *cb_data) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "for_each_reflog_ent"); struct reftable_log_record *logs = NULL; struct reftable_iterator it = {0}; struct reftable_backend *be; size_t logs_alloc = 0, logs_nr = 0, i; int ret; if (refs->err < 0) return refs->err; /* * TODO: we should adapt this callsite to reload the stack. There is no * obvious reason why we shouldn't. */ ret = backend_for(&be, refs, refname, &refname, 0); if (ret) goto done; ret = reftable_stack_init_log_iterator(be->stack, &it); if (ret < 0) goto done; ret = reftable_iterator_seek_log(&it, refname); while (!ret) { struct reftable_log_record log = {0}; ret = reftable_iterator_next_log(&it, &log); if (ret < 0) goto done; if (ret > 0 || strcmp(log.refname, refname)) { reftable_log_record_release(&log); ret = 0; break; } ALLOC_GROW(logs, logs_nr + 1, logs_alloc); logs[logs_nr++] = log; } for (i = logs_nr; i--;) { ret = yield_log_record(refs, &logs[i], fn, cb_data); if (ret) goto done; } done: reftable_iterator_destroy(&it); for (i = 0; i < logs_nr; i++) reftable_log_record_release(&logs[i]); free(logs); return ret; } static int reftable_be_reflog_exists(struct ref_store *ref_store, const char *refname) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, "reflog_exists"); struct reftable_log_record log = {0}; struct reftable_iterator it = {0}; struct reftable_backend *be; int ret; ret = refs->err; if (ret < 0) goto done; ret = backend_for(&be, refs, refname, &refname, 1); if (ret < 0) goto done; ret = reftable_stack_init_log_iterator(be->stack, &it); if (ret < 0) goto done; ret = reftable_iterator_seek_log(&it, refname); if (ret < 0) goto done; /* * Check whether we get at least one log record for the given ref name. * If so, the reflog exists, otherwise it doesn't. */ ret = reftable_iterator_next_log(&it, &log); if (ret < 0) goto done; if (ret > 0) { ret = 0; goto done; } ret = strcmp(log.refname, refname) == 0; done: reftable_iterator_destroy(&it); reftable_log_record_release(&log); if (ret < 0) ret = 0; return ret; } struct write_reflog_existence_arg { struct reftable_ref_store *refs; const char *refname; struct reftable_stack *stack; }; static int write_reflog_existence_table(struct reftable_writer *writer, void *cb_data) { struct write_reflog_existence_arg *arg = cb_data; uint64_t ts = reftable_stack_next_update_index(arg->stack); struct reftable_log_record log = {0}; int ret; ret = reftable_stack_read_log(arg->stack, arg->refname, &log); if (ret <= 0) goto done; ret = reftable_writer_set_limits(writer, ts, ts); if (ret < 0) goto done; /* * The existence entry has both old and new object ID set to the * null object ID. Our iterators are aware of this and will not present * them to their callers. */ log.refname = xstrdup(arg->refname); log.update_index = ts; log.value_type = REFTABLE_LOG_UPDATE; ret = reftable_writer_add_log(writer, &log); done: assert(ret != REFTABLE_API_ERROR); reftable_log_record_release(&log); return ret; } static int reftable_be_create_reflog(struct ref_store *ref_store, const char *refname, struct strbuf *errmsg UNUSED) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_reflog"); struct reftable_backend *be; struct write_reflog_existence_arg arg = { .refs = refs, .refname = refname, }; int ret; ret = refs->err; if (ret < 0) goto done; ret = backend_for(&be, refs, refname, &refname, 1); if (ret) goto done; arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg); done: return ret; } struct write_reflog_delete_arg { struct reftable_stack *stack; const char *refname; }; static int write_reflog_delete_table(struct reftable_writer *writer, void *cb_data) { struct write_reflog_delete_arg *arg = cb_data; struct reftable_log_record log = {0}, tombstone = {0}; struct reftable_iterator it = {0}; uint64_t ts = reftable_stack_next_update_index(arg->stack); int ret; ret = reftable_writer_set_limits(writer, ts, ts); if (ret < 0) goto out; ret = reftable_stack_init_log_iterator(arg->stack, &it); if (ret < 0) goto out; /* * In order to delete a table we need to delete all reflog entries one * by one. This is inefficient, but the reftable format does not have a * better marker right now. */ ret = reftable_iterator_seek_log(&it, arg->refname); while (ret == 0) { ret = reftable_iterator_next_log(&it, &log); if (ret < 0) break; if (ret > 0 || strcmp(log.refname, arg->refname)) { ret = 0; break; } tombstone.refname = (char *)arg->refname; tombstone.value_type = REFTABLE_LOG_DELETION; tombstone.update_index = log.update_index; ret = reftable_writer_add_log(writer, &tombstone); } out: reftable_log_record_release(&log); reftable_iterator_destroy(&it); return ret; } static int reftable_be_delete_reflog(struct ref_store *ref_store, const char *refname) { struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE, "delete_reflog"); struct reftable_backend *be; struct write_reflog_delete_arg arg = { .refname = refname, }; int ret; ret = backend_for(&be, refs, refname, &refname, 1); if (ret) return ret; arg.stack = be->stack; ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg); assert(ret != REFTABLE_API_ERROR); return ret; } struct reflog_expiry_arg { struct reftable_ref_store *refs; struct reftable_stack *stack; struct reftable_log_record *records; struct object_id update_oid; const char *refname; size_t len; }; static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_data) { struct reflog_expiry_arg *arg = cb_data; uint64_t ts = reftable_stack_next_update_index(arg->stack); uint64_t live_records = 0; size_t i; int ret; for (i = 0; i < arg->len; i++) if (arg->records[i].value_type == REFTABLE_LOG_UPDATE) live_records++; ret = reftable_writer_set_limits(writer, ts, ts); if (ret < 0) return ret; if (!is_null_oid(&arg->update_oid)) { struct reftable_ref_record ref = {0}; struct object_id peeled; ref.refname = (char *)arg->refname; ref.update_index = ts; if (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled)) { ref.value_type = REFTABLE_REF_VAL2; memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ); memcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ); } else { ref.value_type = REFTABLE_REF_VAL1; memcpy(ref.value.val1, arg->update_oid.hash, GIT_MAX_RAWSZ); } ret = reftable_writer_add_ref(writer, &ref); if (ret < 0) return ret; } /* * When there are no more entries left in the reflog we empty it * completely, but write a placeholder reflog entry that indicates that * the reflog still exists. */ if (!live_records) { struct reftable_log_record log = { .refname = (char *)arg->refname, .value_type = REFTABLE_LOG_UPDATE, .update_index = ts, }; ret = reftable_writer_add_log(writer, &log); if (ret) return ret; } for (i = 0; i < arg->len; i++) { ret = reftable_writer_add_log(writer, &arg->records[i]); if (ret) return ret; } return 0; } static int reftable_be_reflog_expire(struct ref_store *ref_store, const char *refname, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, reflog_expiry_cleanup_fn cleanup_fn, void *policy_cb_data) { /* * For log expiry, we write tombstones for every single reflog entry * that is to be expired. This means that the entries are still * retrievable by delving into the stack, and expiring entries * paradoxically takes extra memory. This memory is only reclaimed when * compacting the reftable stack. * * It would be better if the refs backend supported an API that sets a * criterion for all refs, passing the criterion to pack_refs(). * * On the plus side, because we do the expiration per ref, we can easily * insert the reflog existence dummies. */ struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_WRITE, "reflog_expire"); struct reftable_log_record *logs = NULL; struct reftable_log_record *rewritten = NULL; struct reftable_iterator it = {0}; struct reftable_addition *add = NULL; struct reflog_expiry_arg arg = {0}; struct reftable_backend *be; struct object_id oid = {0}; struct strbuf referent = STRBUF_INIT; uint8_t *last_hash = NULL; size_t logs_nr = 0, logs_alloc = 0, i; unsigned int type = 0; int ret; if (refs->err < 0) return refs->err; ret = backend_for(&be, refs, refname, &refname, 1); if (ret < 0) goto done; ret = reftable_stack_init_log_iterator(be->stack, &it); if (ret < 0) goto done; ret = reftable_iterator_seek_log(&it, refname); if (ret < 0) goto done; ret = reftable_stack_new_addition(&add, be->stack, 0); if (ret < 0) goto done; ret = reftable_backend_read_ref(be, refname, &oid, &referent, &type); if (ret < 0) goto done; prepare_fn(refname, &oid, policy_cb_data); while (1) { struct reftable_log_record log = {0}; struct object_id old_oid, new_oid; ret = reftable_iterator_next_log(&it, &log); if (ret < 0) goto done; if (ret > 0 || strcmp(log.refname, refname)) { reftable_log_record_release(&log); break; } oidread(&old_oid, log.value.update.old_hash, ref_store->repo->hash_algo); oidread(&new_oid, log.value.update.new_hash, ref_store->repo->hash_algo); /* * Skip over the reflog existence marker. We will add it back * in when there are no live reflog records. */ if (is_null_oid(&old_oid) && is_null_oid(&new_oid)) { reftable_log_record_release(&log); continue; } ALLOC_GROW(logs, logs_nr + 1, logs_alloc); logs[logs_nr++] = log; } /* * We need to rewrite all reflog entries according to the pruning * callback function: * * - If a reflog entry shall be pruned we mark the record for * deletion. * * - Otherwise we may have to rewrite the chain of reflog entries so * that gaps created by just-deleted records get backfilled. */ CALLOC_ARRAY(rewritten, logs_nr); for (i = logs_nr; i--;) { struct reftable_log_record *dest = &rewritten[i]; struct object_id old_oid, new_oid; *dest = logs[i]; oidread(&old_oid, logs[i].value.update.old_hash, ref_store->repo->hash_algo); oidread(&new_oid, logs[i].value.update.new_hash, ref_store->repo->hash_algo); if (should_prune_fn(&old_oid, &new_oid, logs[i].value.update.email, (timestamp_t)logs[i].value.update.time, logs[i].value.update.tz_offset, logs[i].value.update.message, policy_cb_data)) { dest->value_type = REFTABLE_LOG_DELETION; } else { if ((flags & EXPIRE_REFLOGS_REWRITE) && last_hash) memcpy(dest->value.update.old_hash, last_hash, GIT_MAX_RAWSZ); last_hash = logs[i].value.update.new_hash; } } if (flags & EXPIRE_REFLOGS_UPDATE_REF && last_hash && !is_null_oid(&oid)) oidread(&arg.update_oid, last_hash, ref_store->repo->hash_algo); arg.refs = refs; arg.records = rewritten; arg.len = logs_nr; arg.stack = be->stack; arg.refname = refname; ret = reftable_addition_add(add, &write_reflog_expiry_table, &arg); if (ret < 0) goto done; /* * Future improvement: we could skip writing records that were * not changed. */ if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) ret = reftable_addition_commit(add); done: if (add) cleanup_fn(policy_cb_data); assert(ret != REFTABLE_API_ERROR); reftable_iterator_destroy(&it); reftable_addition_destroy(add); for (i = 0; i < logs_nr; i++) reftable_log_record_release(&logs[i]); strbuf_release(&referent); free(logs); free(rewritten); return ret; } static int reftable_be_fsck(struct ref_store *ref_store UNUSED, struct fsck_options *o UNUSED, struct worktree *wt UNUSED) { return 0; } struct ref_storage_be refs_be_reftable = { .name = "reftable", .init = reftable_be_init, .release = reftable_be_release, .create_on_disk = reftable_be_create_on_disk, .remove_on_disk = reftable_be_remove_on_disk, .transaction_prepare = reftable_be_transaction_prepare, .transaction_finish = reftable_be_transaction_finish, .transaction_abort = reftable_be_transaction_abort, .pack_refs = reftable_be_pack_refs, .rename_ref = reftable_be_rename_ref, .copy_ref = reftable_be_copy_ref, .iterator_begin = reftable_be_iterator_begin, .read_raw_ref = reftable_be_read_raw_ref, .read_symbolic_ref = reftable_be_read_symbolic_ref, .reflog_iterator_begin = reftable_be_reflog_iterator_begin, .for_each_reflog_ent = reftable_be_for_each_reflog_ent, .for_each_reflog_ent_reverse = reftable_be_for_each_reflog_ent_reverse, .reflog_exists = reftable_be_reflog_exists, .create_reflog = reftable_be_create_reflog, .delete_reflog = reftable_be_delete_reflog, .reflog_expire = reftable_be_reflog_expire, .fsck = reftable_be_fsck, };