aboutsummaryrefslogtreecommitdiff
path: root/refs.c
diff options
context:
space:
mode:
Diffstat (limited to 'refs.c')
-rw-r--r--refs.c712
1 files changed, 458 insertions, 254 deletions
diff --git a/refs.c b/refs.c
index 965381367e..bfcb9c7ac3 100644
--- a/refs.c
+++ b/refs.c
@@ -5,6 +5,7 @@
#define USE_THE_REPOSITORY_VARIABLE
#include "git-compat-util.h"
+#include "abspath.h"
#include "advice.h"
#include "config.h"
#include "environment.h"
@@ -15,7 +16,6 @@
#include "iterator.h"
#include "refs.h"
#include "refs/refs-internal.h"
-#include "run-command.h"
#include "hook.h"
#include "object-name.h"
#include "odb.h"
@@ -26,7 +26,6 @@
#include "strvec.h"
#include "repo-settings.h"
#include "setup.h"
-#include "sigchain.h"
#include "date.h"
#include "commit.h"
#include "wildmatch.h"
@@ -65,6 +64,9 @@ const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_forma
return be->name;
}
+static const char *abort_by_ref_transaction_hook =
+ N_("in '%s' phase, update aborted by the reference-transaction hook");
+
/*
* How to handle various characters in refnames:
* 0: An acceptable character for refs
@@ -320,6 +322,49 @@ int check_refname_format(const char *refname, int flags)
return check_or_sanitize_refname(refname, flags, NULL);
}
+int refs_fsck_ref(struct ref_store *refs UNUSED, struct fsck_options *o,
+ struct fsck_ref_report *report,
+ const char *refname UNUSED, const struct object_id *oid)
+{
+ if (is_null_oid(oid))
+ return fsck_report_ref(o, report, FSCK_MSG_BAD_REF_OID,
+ "points to invalid object ID '%s'",
+ oid_to_hex(oid));
+
+ return 0;
+}
+
+int refs_fsck_symref(struct ref_store *refs UNUSED, struct fsck_options *o,
+ struct fsck_ref_report *report,
+ const char *refname, const char *target)
+{
+ const char *stripped_refname;
+
+ parse_worktree_ref(refname, NULL, NULL, &stripped_refname);
+
+ if (!strcmp(stripped_refname, "HEAD") &&
+ !starts_with(target, "refs/heads/") &&
+ fsck_report_ref(o, report, FSCK_MSG_BAD_HEAD_TARGET,
+ "HEAD points to non-branch '%s'", target))
+ return -1;
+
+ if (is_root_ref(target))
+ return 0;
+
+ if (check_refname_format(target, 0) &&
+ fsck_report_ref(o, report, FSCK_MSG_BAD_REFERENT_NAME,
+ "points to invalid refname '%s'", target))
+ return -1;
+
+ if (!starts_with(target, "refs/") &&
+ !starts_with(target, "worktrees/") &&
+ fsck_report_ref(o, report, FSCK_MSG_SYMREF_TARGET_IS_NOT_A_REF,
+ "points to non-ref target '%s'", target))
+ return -1;
+
+ return 0;
+}
+
int refs_fsck(struct ref_store *refs, struct fsck_options *o,
struct worktree *wt)
{
@@ -380,7 +425,7 @@ int ref_resolves_to_object(const char *refname,
if (flags & REF_ISBROKEN)
return 0;
if (!odb_has_object(repo->objects, oid,
- HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) {
+ ODB_HAS_OBJECT_RECHECK_PACKED | ODB_HAS_OBJECT_FETCH_PROMISOR)) {
error(_("%s does not point to a valid object!"), refname);
return 0;
}
@@ -401,8 +446,8 @@ char *refs_resolve_refdup(struct ref_store *refs,
/* The argument to for_each_filter_refs */
struct for_each_ref_filter {
const char *pattern;
- const char *prefix;
- each_ref_fn *fn;
+ size_t trim_prefix;
+ refs_for_each_cb *fn;
void *cb_data;
};
@@ -426,17 +471,21 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)
NULL, NULL);
}
-static int for_each_filter_refs(const char *refname, const char *referent,
- const struct object_id *oid,
- int flags, void *data)
+static int for_each_filter_refs(const struct reference *ref, void *data)
{
struct for_each_ref_filter *filter = data;
- if (wildmatch(filter->pattern, refname, 0))
+ if (wildmatch(filter->pattern, ref->name, 0))
return 0;
- if (filter->prefix)
- skip_prefix(refname, filter->prefix, &refname);
- return filter->fn(refname, referent, oid, flags, filter->cb_data);
+ if (filter->trim_prefix) {
+ struct reference skipped = *ref;
+ if (strlen(skipped.name) <= filter->trim_prefix)
+ BUG("attempt to trim too many characters");
+ skipped.name += filter->trim_prefix;
+ return filter->fn(&skipped, filter->cb_data);
+ } else {
+ return filter->fn(ref, filter->cb_data);
+ }
}
struct warn_if_dangling_data {
@@ -447,17 +496,15 @@ struct warn_if_dangling_data {
int dry_run;
};
-static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,
- const struct object_id *oid UNUSED,
- int flags, void *cb_data)
+static int warn_if_dangling_symref(const struct reference *ref, void *cb_data)
{
struct warn_if_dangling_data *d = cb_data;
const char *resolves_to, *msg;
- if (!(flags & REF_ISSYMREF))
+ if (!(ref->flags & REF_ISSYMREF))
return 0;
- resolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);
+ resolves_to = refs_resolve_ref_unsafe(d->refs, ref->name, 0, NULL, NULL);
if (!resolves_to
|| !string_list_has_string(d->refnames, resolves_to)) {
return 0;
@@ -466,7 +513,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU
msg = d->dry_run
? _("%s%s will become dangling after %s is deleted\n")
: _("%s%s has become dangling after %s was deleted\n");
- fprintf(d->fp, msg, d->indent, refname, resolves_to);
+ fprintf(d->fp, msg, d->indent, ref->name, resolves_to);
return 0;
}
@@ -481,25 +528,40 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,
.indent = indent,
.dry_run = dry_run,
};
- refs_for_each_rawref(refs, warn_if_dangling_symref, &data);
+ struct refs_for_each_ref_options opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+ refs_for_each_ref_ext(refs, warn_if_dangling_symref, &data, &opts);
}
-int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/tags/",
+ .trim_prefix = strlen("refs/tags/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/heads/",
+ .trim_prefix = strlen("refs/heads/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
+ struct refs_for_each_ref_options opts = {
+ .prefix = "refs/remotes/",
+ .trim_prefix = strlen("refs/remotes/"),
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
struct strbuf buf = STRBUF_INIT;
int ret = 0;
@@ -507,8 +569,15 @@ int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_da
int flag;
strbuf_addf(&buf, "%sHEAD", get_git_namespace());
- if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))
- ret = fn(buf.buf, NULL, &oid, flag, cb_data);
+ if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) {
+ struct reference ref = {
+ .name = buf.buf,
+ .oid = &oid,
+ .flags = flag,
+ };
+
+ ret = fn(&ref, cb_data);
+ }
strbuf_release(&buf);
return ret;
@@ -540,42 +609,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
- const char *pattern, const char *prefix, void *cb_data)
-{
- struct strbuf real_pattern = STRBUF_INIT;
- struct for_each_ref_filter filter;
- int ret;
-
- if (!prefix && !starts_with(pattern, "refs/"))
- strbuf_addstr(&real_pattern, "refs/");
- else if (prefix)
- strbuf_addstr(&real_pattern, prefix);
- strbuf_addstr(&real_pattern, pattern);
-
- if (!has_glob_specials(pattern)) {
- /* Append implied '/' '*' if not present. */
- strbuf_complete(&real_pattern, '/');
- /* No need to check for '*', there is none. */
- strbuf_addch(&real_pattern, '*');
- }
-
- filter.pattern = real_pattern.buf;
- filter.prefix = prefix;
- filter.fn = fn;
- filter.cb_data = cb_data;
- ret = refs_for_each_ref(refs, for_each_filter_refs, &filter);
-
- strbuf_release(&real_pattern);
- return ret;
-}
-
-int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
- const char *pattern, void *cb_data)
-{
- return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
-}
-
const char *prettify_refname(const char *name)
{
if (skip_prefix(name, "refs/heads/", &name) ||
@@ -710,7 +743,8 @@ static char *substitute_branch_name(struct repository *r,
return NULL;
}
-void copy_branchname(struct strbuf *sb, const char *name, unsigned allowed)
+void copy_branchname(struct strbuf *sb, const char *name,
+ enum interpret_branch_kind allowed)
{
int len = strlen(name);
struct interpret_branch_name_options options = {
@@ -1217,6 +1251,7 @@ void ref_transaction_free(struct ref_transaction *transaction)
free(transaction->updates[i]->committer_info);
free((char *)transaction->updates[i]->new_target);
free((char *)transaction->updates[i]->old_target);
+ free((char *)transaction->updates[i]->rejection_details);
free(transaction->updates[i]);
}
@@ -1231,7 +1266,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
size_t update_idx,
- enum ref_transaction_error err)
+ enum ref_transaction_error err,
+ struct strbuf *details)
{
if (update_idx >= transaction->nr)
BUG("trying to set rejection on invalid update index");
@@ -1257,6 +1293,7 @@ int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
transaction->updates[update_idx]->refname, 0);
transaction->updates[update_idx]->rejection_err = err;
+ transaction->updates[update_idx]->rejection_details = strbuf_detach(details, NULL);
ALLOC_GROW(transaction->rejections->update_indices,
transaction->rejections->nr + 1,
transaction->rejections->alloc);
@@ -1735,14 +1772,21 @@ const char *find_descendant_ref(const char *dirname,
return NULL;
}
-int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_head_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data)
{
struct object_id oid;
int flag;
if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING,
- &oid, &flag))
- return fn("HEAD", NULL, &oid, flag, cb_data);
+ &oid, &flag)) {
+ struct reference ref = {
+ .name = "HEAD",
+ .oid = &oid,
+ .flags = flag,
+ };
+
+ return fn(&ref, cb_data);
+ }
return 0;
}
@@ -1752,7 +1796,7 @@ struct ref_iterator *refs_ref_iterator_begin(
const char *prefix,
const char **exclude_patterns,
int trim,
- enum do_for_each_ref_flags flags)
+ enum refs_for_each_flag flags)
{
struct ref_iterator *iter;
struct strvec normalized_exclude_patterns = STRVEC_INIT;
@@ -1774,14 +1818,14 @@ struct ref_iterator *refs_ref_iterator_begin(
exclude_patterns = normalized_exclude_patterns.v;
}
- if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
+ if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) {
static int ref_paranoia = -1;
if (ref_paranoia < 0)
ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1);
if (ref_paranoia) {
- flags |= DO_FOR_EACH_INCLUDE_BROKEN;
- flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS;
+ flags |= REFS_FOR_EACH_INCLUDE_BROKEN;
+ flags |= REFS_FOR_EACH_OMIT_DANGLING_SYMREFS;
}
}
@@ -1798,85 +1842,105 @@ struct ref_iterator *refs_ref_iterator_begin(
return iter;
}
-static int do_for_each_ref(struct ref_store *refs, const char *prefix,
- const char **exclude_patterns,
- each_ref_fn fn, int trim,
- enum do_for_each_ref_flags flags, void *cb_data)
+int refs_for_each_ref_ext(struct ref_store *refs,
+ refs_for_each_cb cb, void *cb_data,
+ const struct refs_for_each_ref_options *opts)
{
+ struct strvec namespaced_exclude_patterns = STRVEC_INIT;
+ struct strbuf namespaced_prefix = STRBUF_INIT;
+ struct strbuf real_pattern = STRBUF_INIT;
+ struct for_each_ref_filter filter;
struct ref_iterator *iter;
+ size_t trim_prefix = opts->trim_prefix;
+ const char **exclude_patterns;
+ const char *prefix;
+ int ret;
if (!refs)
- return 0;
+ BUG("no ref store passed");
- iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim,
- flags);
+ if (opts->trim_prefix) {
+ size_t prefix_len;
- return do_for_each_ref_iterator(iter, fn, cb_data);
-}
+ if (!opts->prefix)
+ BUG("trimming only allowed with a prefix");
-int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
-{
- return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
-}
+ prefix_len = strlen(opts->prefix);
+ if (prefix_len == opts->trim_prefix && opts->prefix[prefix_len - 1] != '/')
+ BUG("ref pattern must end in a trailing slash when trimming");
+ }
-int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data)
-{
- return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
-}
+ if (opts->pattern) {
+ if (!opts->prefix && !starts_with(opts->pattern, "refs/"))
+ strbuf_addstr(&real_pattern, "refs/");
+ else if (opts->prefix)
+ strbuf_addstr(&real_pattern, opts->prefix);
+ strbuf_addstr(&real_pattern, opts->pattern);
-int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
- const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
-{
- return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data);
-}
+ if (!has_glob_specials(opts->pattern)) {
+ /* Append implied '/' '*' if not present. */
+ strbuf_complete(&real_pattern, '/');
+ /* No need to check for '*', there is none. */
+ strbuf_addch(&real_pattern, '*');
+ }
-int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
-{
- const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
- return do_for_each_ref(refs, git_replace_ref_base, NULL, fn,
- strlen(git_replace_ref_base),
- DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
-}
+ filter.pattern = real_pattern.buf;
+ filter.trim_prefix = opts->trim_prefix;
+ filter.fn = cb;
+ filter.cb_data = cb_data;
-int refs_for_each_namespaced_ref(struct ref_store *refs,
- const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
-{
- struct strvec namespaced_exclude_patterns = STRVEC_INIT;
- struct strbuf prefix = STRBUF_INIT;
- int ret;
+ /*
+ * We need to trim the prefix in the callback function as the
+ * pattern is expected to match on the full refname.
+ */
+ trim_prefix = 0;
- exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
- get_git_namespace(),
- &namespaced_exclude_patterns);
+ cb = for_each_filter_refs;
+ cb_data = &filter;
+ }
- strbuf_addf(&prefix, "%srefs/", get_git_namespace());
- ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);
+ if (opts->namespace) {
+ strbuf_addstr(&namespaced_prefix, opts->namespace);
+ if (opts->prefix)
+ strbuf_addstr(&namespaced_prefix, opts->prefix);
+ else
+ strbuf_addstr(&namespaced_prefix, "refs/");
+
+ prefix = namespaced_prefix.buf;
+ exclude_patterns = get_namespaced_exclude_patterns(opts->exclude_patterns,
+ opts->namespace,
+ &namespaced_exclude_patterns);
+ } else {
+ prefix = opts->prefix ? opts->prefix : "";
+ exclude_patterns = opts->exclude_patterns;
+ }
+
+ iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns,
+ trim_prefix, opts->flags);
+
+ ret = do_for_each_ref_iterator(iter, cb, cb_data);
strvec_clear(&namespaced_exclude_patterns);
- strbuf_release(&prefix);
+ strbuf_release(&namespaced_prefix);
+ strbuf_release(&real_pattern);
return ret;
}
-int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
+int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return refs_for_each_rawref_in(refs, "", fn, cb_data);
+ struct refs_for_each_ref_options opts = { 0 };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
-int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix,
- each_ref_fn fn, void *cb_data)
+int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data)
{
- return do_for_each_ref(refs, prefix, NULL, fn, 0,
- DO_FOR_EACH_INCLUDE_BROKEN, 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);
+ const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
+ struct refs_for_each_ref_options opts = {
+ .prefix = git_replace_ref_base,
+ .trim_prefix = strlen(git_replace_ref_base),
+ .flags = REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
+ return refs_for_each_ref_ext(refs, cb, cb_data, &opts);
}
static int qsort_strcmp(const void *va, const void *vb)
@@ -1937,40 +2001,31 @@ static void find_longest_prefixes(struct string_list *out,
strbuf_release(&prefix);
}
-int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
- const char *namespace,
- const char **patterns,
- const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
+int refs_for_each_ref_in_prefixes(struct ref_store *ref_store,
+ const char **prefixes,
+ const struct refs_for_each_ref_options *opts,
+ refs_for_each_cb cb, void *cb_data)
{
- struct strvec namespaced_exclude_patterns = STRVEC_INIT;
- struct string_list prefixes = STRING_LIST_INIT_DUP;
+ struct string_list longest_prefixes = STRING_LIST_INIT_DUP;
struct string_list_item *prefix;
- struct strbuf buf = STRBUF_INIT;
- int ret = 0, namespace_len;
+ int ret = 0;
- find_longest_prefixes(&prefixes, patterns);
+ if (opts->prefix)
+ BUG("refs_for_each_ref_in_prefixes called with specific prefix");
- if (namespace)
- strbuf_addstr(&buf, namespace);
- namespace_len = buf.len;
+ find_longest_prefixes(&longest_prefixes, prefixes);
- exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
- namespace,
- &namespaced_exclude_patterns);
+ for_each_string_list_item(prefix, &longest_prefixes) {
+ struct refs_for_each_ref_options prefix_opts = *opts;
+ prefix_opts.prefix = prefix->string;
- for_each_string_list_item(prefix, &prefixes) {
- strbuf_addstr(&buf, prefix->string);
- ret = refs_for_each_fullref_in(ref_store, buf.buf,
- exclude_patterns, fn, cb_data);
+ ret = refs_for_each_ref_ext(ref_store, cb, cb_data,
+ &prefix_opts);
if (ret)
break;
- strbuf_setlen(&buf, namespace_len);
}
- strvec_clear(&namespaced_exclude_patterns);
- string_list_clear(&prefixes, 0);
- strbuf_release(&buf);
+ string_list_clear(&longest_prefixes, 0);
return ret;
}
@@ -2106,15 +2161,93 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
return NULL;
}
+void refs_create_refdir_stubs(struct repository *repo, const char *refdir,
+ const char *refs_heads_content)
+{
+ struct strbuf path = STRBUF_INIT;
+
+ strbuf_addf(&path, "%s/HEAD", refdir);
+ write_file(path.buf, "ref: refs/heads/.invalid");
+ adjust_shared_perm(repo, path.buf);
+
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/refs", refdir);
+ safe_create_dir(repo, path.buf, 1);
+
+ if (refs_heads_content) {
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/refs/heads", refdir);
+ write_file(path.buf, "%s", refs_heads_content);
+ adjust_shared_perm(repo, path.buf);
+ }
+
+ strbuf_release(&path);
+}
+
/* backend functions */
int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)
{
- return refs->be->create_on_disk(refs, flags, err);
+ int ret = refs->be->create_on_disk(refs, flags, err);
+
+ if (!ret) {
+ /* Creation of stubs for linked worktrees are handled in the worktree code. */
+ if (!(flags & REF_STORE_CREATE_ON_DISK_IS_WORKTREE) && refs->repo->ref_storage_payload) {
+ refs_create_refdir_stubs(refs->repo, refs->repo->gitdir,
+ "repository uses alternate refs storage");
+ } else if (ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {
+ struct strbuf msg = STRBUF_INIT;
+ strbuf_addf(&msg, "this repository uses the %s format", refs->be->name);
+ refs_create_refdir_stubs(refs->repo, refs->gitdir, msg.buf);
+ strbuf_release(&msg);
+ }
+ }
+
+ return ret;
}
int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)
{
- return refs->be->remove_on_disk(refs, err);
+ int ret = refs->be->remove_on_disk(refs, err);
+
+ if (!ret) {
+ enum ref_storage_format format = ref_storage_format_by_name(refs->be->name);
+ struct strbuf sb = STRBUF_INIT;
+
+ /* Backends apart from the files backend create stubs. */
+ if (format == REF_STORAGE_FORMAT_FILES)
+ return ret;
+
+ /* Alternate refs backend require stubs in the gitdir. */
+ if (refs->repo->ref_storage_payload)
+ return ret;
+
+ strbuf_addf(&sb, "%s/HEAD", refs->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->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->gitdir);
+ if (rmdir(sb.buf) < 0) {
+ strbuf_addf(err, "could not delete refs directory: %s",
+ strerror(errno));
+ ret = -1;
+ }
+
+ strbuf_release(&sb);
+ }
+
+ return ret;
}
int repo_resolve_gitlink_ref(struct repository *r,
@@ -2167,7 +2300,11 @@ static struct ref_store *ref_store_init(struct repository *repo,
if (!be)
BUG("reference backend is unknown");
- refs = be->init(repo, gitdir, flags);
+ /*
+ * TODO Send in a 'struct worktree' instead of a 'gitdir', and
+ * allow the backend to handle how it wants to deal with worktrees.
+ */
+ refs = be->init(repo, repo->ref_storage_payload, gitdir, flags);
return refs;
}
@@ -2299,25 +2436,28 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
refs->gitdir = xstrdup(path);
}
-/* backend functions */
-int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)
+int refs_optimize(struct ref_store *refs, struct refs_optimize_opts *opts)
{
- return refs->be->pack_refs(refs, opts);
+ return refs->be->optimize(refs, opts);
}
-int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)
+int refs_optimize_required(struct ref_store *refs,
+ struct refs_optimize_opts *opts,
+ bool *required)
{
- return refs->be->optimize(refs, opts);
+ return refs->be->optimize_required(refs, opts, required);
}
-int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)
+int reference_get_peeled_oid(struct repository *repo,
+ const struct reference *ref,
+ struct object_id *peeled_oid)
{
- if (current_ref_iter &&
- (current_ref_iter->oid == base ||
- oideq(current_ref_iter->oid, base)))
- return ref_iterator_peel(current_ref_iter, peeled);
+ if (ref->peeled_oid) {
+ oidcpy(peeled_oid, ref->peeled_oid);
+ return 0;
+ }
- return peel_object(r, base, peeled) ? -1 : 0;
+ return peel_object(repo, ref->oid, peeled_oid, 0) ? -1 : 0;
}
int refs_update_symref(struct ref_store *refs, const char *ref,
@@ -2405,68 +2545,87 @@ static int ref_update_reject_duplicates(struct string_list *refnames,
return 0;
}
-static int run_transaction_hook(struct ref_transaction *transaction,
- const char *state)
+struct transaction_feed_cb_data {
+ size_t index;
+ struct strbuf buf;
+};
+
+static int transaction_hook_feed_stdin(int hook_stdin_fd, void *pp_cb, void *pp_task_cb)
{
- struct child_process proc = CHILD_PROCESS_INIT;
- struct strbuf buf = STRBUF_INIT;
- const char *hook;
- int ret = 0;
+ struct hook_cb_data *hook_cb = pp_cb;
+ struct ref_transaction *transaction = hook_cb->options->feed_pipe_ctx;
+ struct transaction_feed_cb_data *feed_cb_data = pp_task_cb;
+ struct strbuf *buf = &feed_cb_data->buf;
+ struct ref_update *update;
+ size_t i = feed_cb_data->index++;
+ int ret;
- hook = find_hook(transaction->ref_store->repo, "reference-transaction");
- if (!hook)
- return ret;
+ if (i >= transaction->nr)
+ return 1; /* No more refs to process */
- strvec_pushl(&proc.args, hook, state, NULL);
- proc.in = -1;
- proc.stdout_to_stderr = 1;
- proc.trace2_hook_name = "reference-transaction";
+ update = transaction->updates[i];
- ret = start_command(&proc);
- if (ret)
- return ret;
+ if (update->flags & REF_LOG_ONLY)
+ return 0;
- sigchain_push(SIGPIPE, SIG_IGN);
+ strbuf_reset(buf);
- for (size_t i = 0; i < transaction->nr; i++) {
- struct ref_update *update = transaction->updates[i];
+ if (!(update->flags & REF_HAVE_OLD))
+ strbuf_addf(buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
+ else if (update->old_target)
+ strbuf_addf(buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(buf, "%s ", oid_to_hex(&update->old_oid));
- if (update->flags & REF_LOG_ONLY)
- continue;
+ if (!(update->flags & REF_HAVE_NEW))
+ strbuf_addf(buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
+ else if (update->new_target)
+ strbuf_addf(buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(buf, "%s ", oid_to_hex(&update->new_oid));
- strbuf_reset(&buf);
+ strbuf_addf(buf, "%s\n", update->refname);
- if (!(update->flags & REF_HAVE_OLD))
- strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
- else if (update->old_target)
- strbuf_addf(&buf, "ref:%s ", update->old_target);
- else
- strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
+ ret = write_in_full(hook_stdin_fd, buf->buf, buf->len);
+ if (ret < 0 && errno != EPIPE)
+ return ret;
- if (!(update->flags & REF_HAVE_NEW))
- strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
- else if (update->new_target)
- strbuf_addf(&buf, "ref:%s ", update->new_target);
- else
- strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+ return 0; /* no more input to feed */
+}
- strbuf_addf(&buf, "%s\n", update->refname);
+static void *transaction_feed_cb_data_alloc(void *feed_pipe_ctx UNUSED)
+{
+ struct transaction_feed_cb_data *data;
+ CALLOC_ARRAY(data, 1);
+ strbuf_init(&data->buf, 0);
+ data->index = 0;
+ return data;
+}
- if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
- if (errno != EPIPE) {
- /* Don't leak errno outside this API */
- errno = 0;
- ret = -1;
- }
- break;
- }
- }
+static void transaction_feed_cb_data_free(void *data)
+{
+ struct transaction_feed_cb_data *d = data;
+ if (!d)
+ return;
+ strbuf_release(&d->buf);
+ free(d);
+}
- close(proc.in);
- sigchain_pop(SIGPIPE);
- strbuf_release(&buf);
+static int run_transaction_hook(struct ref_transaction *transaction,
+ const char *state)
+{
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+ int ret = 0;
+
+ strvec_push(&opt.args, state);
+
+ opt.feed_pipe = transaction_hook_feed_stdin;
+ opt.feed_pipe_ctx = transaction;
+ opt.feed_pipe_cb_data_alloc = transaction_feed_cb_data_alloc;
+ opt.feed_pipe_cb_data_free = transaction_feed_cb_data_free;
+
+ ret = run_hooks_opt(transaction->ref_store->repo, "reference-transaction", &opt);
- ret |= finish_command(&proc);
return ret;
}
@@ -2491,7 +2650,7 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
break;
}
- if (refs->repo->objects->sources->disable_ref_updates) {
+ if (refs->repo->disable_ref_updates) {
strbuf_addstr(err,
_("ref updates forbidden inside quarantine environment"));
return -1;
@@ -2501,6 +2660,13 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
if (ref_update_reject_duplicates(&transaction->refnames, err))
return REF_TRANSACTION_ERROR_GENERIC;
+ /* Preparing checks before locking references */
+ ret = run_transaction_hook(transaction, "preparing");
+ if (ret) {
+ ref_transaction_abort(transaction, err);
+ die(_(abort_by_ref_transaction_hook), "preparing");
+ }
+
ret = refs->be->transaction_prepare(refs, transaction, err);
if (ret)
return ret;
@@ -2508,7 +2674,7 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
ret = run_transaction_hook(transaction, "prepared");
if (ret) {
ref_transaction_abort(transaction, err);
- die(_("ref updates aborted by hook"));
+ die(_(abort_by_ref_transaction_hook), "prepared");
}
return 0;
@@ -2638,30 +2804,33 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
if (!initial_transaction &&
(strset_contains(&conflicting_dirnames, dirname.buf) ||
!refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
- &type, &ignore_errno))) {
+ &type, &ignore_errno))) {
+
+ strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
+ dirname.buf, refname);
+
if (transaction && ref_transaction_maybe_set_rejected(
transaction, *update_idx,
- REF_TRANSACTION_ERROR_NAME_CONFLICT)) {
+ REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) {
strset_remove(&dirnames, dirname.buf);
strset_add(&conflicting_dirnames, dirname.buf);
- continue;
+ goto next_ref;
}
- strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
- dirname.buf, refname);
goto cleanup;
}
if (extras && string_list_has_string(extras, dirname.buf)) {
+ strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
+ refname, dirname.buf);
+
if (transaction && ref_transaction_maybe_set_rejected(
transaction, *update_idx,
- REF_TRANSACTION_ERROR_NAME_CONFLICT)) {
+ REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) {
strset_remove(&dirnames, dirname.buf);
- continue;
+ goto next_ref;
}
- strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
- refname, dirname.buf);
goto cleanup;
}
}
@@ -2682,23 +2851,23 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
if (!iter)
iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
- DO_FOR_EACH_INCLUDE_BROKEN);
+ REFS_FOR_EACH_INCLUDE_BROKEN);
else if (ref_iterator_seek(iter, dirname.buf,
REF_ITERATOR_SEEK_SET_PREFIX) < 0)
goto cleanup;
while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
if (skip &&
- string_list_has_string(skip, iter->refname))
+ string_list_has_string(skip, iter->ref.name))
continue;
+ strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
+ iter->ref.name, refname);
if (transaction && ref_transaction_maybe_set_rejected(
transaction, *update_idx,
- REF_TRANSACTION_ERROR_NAME_CONFLICT))
- continue;
+ REF_TRANSACTION_ERROR_NAME_CONFLICT, err))
+ goto next_ref;
- strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
- iter->refname, refname);
goto cleanup;
}
@@ -2708,15 +2877,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
extra_refname = find_descendant_ref(dirname.buf, extras, skip);
if (extra_refname) {
+ strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
+ refname, extra_refname);
+
if (transaction && ref_transaction_maybe_set_rejected(
transaction, *update_idx,
- REF_TRANSACTION_ERROR_NAME_CONFLICT))
- continue;
+ REF_TRANSACTION_ERROR_NAME_CONFLICT, err))
+ goto next_ref;
- strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
- refname, extra_refname);
goto cleanup;
}
+next_ref:;
}
ret = 0;
@@ -2753,14 +2924,10 @@ struct do_for_each_reflog_help {
void *cb_data;
};
-static int do_for_each_reflog_helper(const char *refname,
- const char *referent UNUSED,
- const struct object_id *oid UNUSED,
- int flags UNUSED,
- void *cb_data)
+static int do_for_each_reflog_helper(const struct reference *ref, void *cb_data)
{
struct do_for_each_reflog_help *hp = cb_data;
- return hp->fn(refname, hp->cb_data);
+ return hp->fn(ref->name, hp->cb_data);
}
int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)
@@ -2849,7 +3016,7 @@ void ref_transaction_for_each_rejected_update(struct ref_transaction *transactio
(update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL,
(update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL,
update->old_target, update->new_target,
- update->rejection_err, cb_data);
+ update->rejection_err, update->rejection_details, cb_data);
}
}
@@ -2976,25 +3143,24 @@ struct migration_data {
uint64_t index;
};
-static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,
- int flags, void *cb_data)
+static int migrate_one_ref(const struct reference *ref, void *cb_data)
{
struct migration_data *data = cb_data;
struct strbuf symref_target = STRBUF_INIT;
int ret;
- if (flags & REF_ISSYMREF) {
- ret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target);
+ if (ref->flags & REF_ISSYMREF) {
+ ret = refs_read_symbolic_ref(data->old_refs, ref->name, &symref_target);
if (ret < 0)
goto done;
- ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo),
+ ret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(the_hash_algo),
symref_target.buf, NULL,
REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);
if (ret < 0)
goto done;
} else {
- ret = ref_transaction_create(data->transaction, refname, oid, NULL,
+ ret = ref_transaction_create(data->transaction, ref->name, ref->oid, NULL,
REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION,
NULL, data->errbuf);
if (ret < 0)
@@ -3131,6 +3297,9 @@ int repo_migrate_ref_storage_format(struct repository *repo,
struct strbuf *errbuf)
{
struct ref_store *old_refs = NULL, *new_refs = NULL;
+ struct refs_for_each_ref_options for_each_ref_opts = {
+ .flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN,
+ };
struct ref_transaction *transaction = NULL;
struct strbuf new_gitdir = STRBUF_INIT;
struct migration_data data = {
@@ -3214,7 +3383,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
data.errbuf = errbuf;
/*
- * We need to use the internal `do_for_each_ref()` here so that we can
+ * We need to use `refs_for_each_ref_ext()` here so that we can
* also include broken refs and symrefs. These would otherwise be
* skipped silently.
*
@@ -3224,9 +3393,7 @@ int repo_migrate_ref_storage_format(struct repository *repo,
* allow for a central lock due to its design. It's thus on the user to
* ensure that there are no concurrent writes.
*/
- ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0,
- DO_FOR_EACH_INCLUDE_ROOT_REFS | DO_FOR_EACH_INCLUDE_BROKEN,
- &data);
+ ret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts);
if (ret < 0)
goto done;
@@ -3347,3 +3514,40 @@ const char *ref_transaction_error_msg(enum ref_transaction_error err)
return "unknown failure";
}
}
+
+void refs_compute_filesystem_location(const char *gitdir, const char *payload,
+ bool *is_worktree, struct strbuf *refdir,
+ struct strbuf *ref_common_dir)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ *is_worktree = get_common_dir_noenv(ref_common_dir, gitdir);
+
+ if (!payload) {
+ /*
+ * We can use the 'gitdir' as the 'refdir' without appending the
+ * worktree path, as the 'gitdir' here is already the worktree
+ * path and is different from 'commondir' denoted by 'ref_common_dir'.
+ */
+ strbuf_addstr(refdir, gitdir);
+ return;
+ }
+
+ if (!is_absolute_path(payload)) {
+ strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload);
+ strbuf_realpath(ref_common_dir, sb.buf, 1);
+ } else {
+ strbuf_realpath(ref_common_dir, payload, 1);
+ }
+
+ strbuf_addbuf(refdir, ref_common_dir);
+
+ if (*is_worktree) {
+ const char *wt_id = strrchr(gitdir, '/');
+ if (!wt_id)
+ BUG("worktree path does not contain slash");
+ strbuf_addf(refdir, "/worktrees/%s", wt_id + 1);
+ }
+
+ strbuf_release(&sb);
+}