'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
'git branch' --unset-upstream [<branchname>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
+ 'git branch' (-c | -C) [<oldbranch>] <newbranch>
'git branch' (-d | -D) [-r] <branchname>...
'git branch' --edit-description [<branchname>]
renaming. If <newbranch> exists, -M must be used to force the rename
to happen.
+ The `-c` and `-C` options have the exact same semantics as `-m` and
+ `-M`, except instead of the branch being renamed it along with its
+ config and reflog will be copied to a new name.
+
With a `-d` or `-D` option, `<branchname>` will be deleted. You may
specify more than one branch for deletion. If the branch currently
has a reflog then the reflog will also be deleted.
all changes made to the branch ref, enabling use of date
based sha1 expressions such as "<branchname>@\{yesterday}".
Note that in non-bare repositories, reflogs are usually
- enabled by default by the `core.logallrefupdates` config option.
+ enabled by default by the `core.logAllRefUpdates` config option.
The negated form `--no-create-reflog` only overrides an earlier
`--create-reflog`, but currently does not negate the setting of
- `core.logallrefupdates`.
+ `core.logAllRefUpdates`.
-f::
--force::
In combination with `-d` (or `--delete`), allow deleting the
branch irrespective of its merged status. In combination with
`-m` (or `--move`), allow renaming the branch even if the new
- branch name already exists.
+ branch name already exists, the same applies for `-c` (or `--copy`).
-m::
--move::
-M::
Shortcut for `--move --force`.
+ -c::
+ --copy::
+ Copy a branch and the corresponding reflog.
+
+ -C::
+ Shortcut for `--copy --force`.
+
--color[=<when>]::
Color branches to highlight current, local, and
remote-tracking branches.
branch.autoSetupMerge configuration variable is true.
--set-upstream::
- If specified branch does not exist yet or if `--force` has been
- given, acts exactly like `--track`. Otherwise sets up configuration
- like `--track` would when creating the branch, except that where
- branch points to is not changed.
+ As this option had confusing syntax, it is no longer supported.
+ Please use `--track` or `--set-upstream-to` instead.
-u <upstream>::
--set-upstream-to=<upstream>::
Only list branches of the given object.
--format <format>::
- A string that interpolates `%(fieldname)` from the object
- pointed at by a ref being shown. The format is the same as
+ A string that interpolates `%(fieldname)` from a branch ref being shown
+ and the object it points at. The format is the same as
that of linkgit:git-for-each-ref[1].
Examples
*/
#include "cache.h"
+#include "config.h"
#include "color.h"
#include "refs.h"
#include "commit.h"
N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
+ N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
N_("git branch [<options>] [-r | -a] [--points-at]"),
N_("git branch [<options>] [-r | -a] [--format]"),
NULL
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
- return git_color_default_config(var, value, cb);
+ return git_default_config(var, value, cb);
}
static const char *branch_get_color(enum color_branch ix)
struct strbuf local = STRBUF_INIT;
struct strbuf remote = STRBUF_INIT;
- strbuf_addf(&fmt, "%%(if)%%(HEAD)%%(then)* %s%%(else) %%(end)",
- branch_get_color(BRANCH_COLOR_CURRENT));
+ strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else) %s%%(end)",
+ branch_get_color(BRANCH_COLOR_CURRENT),
+ branch_get_color(BRANCH_COLOR_LOCAL));
+ strbuf_addf(&remote, " %s",
+ branch_get_color(BRANCH_COLOR_REMOTE));
if (filter->verbose) {
struct strbuf obname = STRBUF_INIT;
else
strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
- strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s"
+ strbuf_addf(&remote, "%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s"
"%%(if)%%(symref)%%(then) -> %%(symref:short)"
"%%(else) %s %%(contents:subject)%%(end)",
- branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix),
+ maxwidth, quote_literal_for_format(remote_prefix),
branch_get_color(BRANCH_COLOR_RESET), obname.buf);
strbuf_release(&obname);
} else {
strbuf_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
branch_get_color(BRANCH_COLOR_RESET));
- strbuf_addf(&remote, "%s%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
- branch_get_color(BRANCH_COLOR_REMOTE), quote_literal_for_format(remote_prefix),
+ strbuf_addf(&remote, "%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
+ quote_literal_for_format(remote_prefix),
branch_get_color(BRANCH_COLOR_RESET));
}
return strbuf_detach(&fmt, NULL);
}
-static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
+static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format)
{
int i;
struct ref_array array;
if (filter->verbose)
maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
- if (!format)
- format = to_free = build_format(filter, maxwidth, remote_prefix);
- verify_ref_format(format);
+ if (!format->format)
+ format->format = to_free = build_format(filter, maxwidth, remote_prefix);
+ format->use_color = branch_use_color;
+
+ if (verify_ref_format(format))
+ die(_("unable to parse format string"));
ref_array_sort(sorting, &array);
for (i = 0; i < array.nr; i++) {
- format_ref_array_item(array.items[i], format, 0, &out);
+ format_ref_array_item(array.items[i], format, &out);
if (column_active(colopts)) {
assert(!filter->verbose && "--column and --verbose are incompatible");
/* format to a string_list to let print_columns() do its job */
free_worktrees(worktrees);
}
- static void rename_branch(const char *oldname, const char *newname, int force)
+ static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force)
{
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
int recovery = 0;
int clobber_head_ok;
- if (!oldname)
- die(_("cannot rename the current branch while not on any."));
+ if (!oldname) {
+ if (copy)
+ die(_("cannot copy the current branch while not on any."));
+ else
+ die(_("cannot rename the current branch while not on any."));
+ }
if (strbuf_check_branch_ref(&oldref, oldname)) {
/*
reject_rebase_or_bisect_branch(oldref.buf);
- strbuf_addf(&logmsg, "Branch: renamed %s to %s",
- oldref.buf, newref.buf);
+ if (copy)
+ strbuf_addf(&logmsg, "Branch: copied %s to %s",
+ oldref.buf, newref.buf);
+ else
+ strbuf_addf(&logmsg, "Branch: renamed %s to %s",
+ oldref.buf, newref.buf);
- if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
+ if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch rename failed"));
+ if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
+ die(_("Branch copy failed"));
- if (recovery)
- warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
+ if (recovery) {
+ if (copy)
+ warning(_("Copied a misnamed branch '%s' away"),
+ oldref.buf + 11);
+ else
+ warning(_("Renamed a misnamed branch '%s' away"),
+ oldref.buf + 11);
+ }
- if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
+ if (!copy &&
+ replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
strbuf_release(&logmsg);
strbuf_release(&oldref);
strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
strbuf_release(&newref);
- if (git_config_rename_section(oldsection.buf, newsection.buf) < 0)
+ if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
die(_("Branch is renamed, but update of config-file failed"));
+ if (copy && strcmp(oldname, newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0)
+ die(_("Branch is copied, but update of config-file failed"));
strbuf_release(&oldsection);
strbuf_release(&newsection);
}
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, rename = 0, force = 0, list = 0;
+ int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
int reflog = 0, edit_description = 0;
int quiet = 0, unset_upstream = 0;
const char *new_upstream = NULL;
struct ref_filter filter;
int icase = 0;
static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
- const char *format = NULL;
+ struct ref_format format = REF_FORMAT_INIT;
struct option options[] = {
OPT_GROUP(N_("Generic options")),
OPT__QUIET(&quiet, N_("suppress informational messages")),
OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"),
BRANCH_TRACK_EXPLICIT),
- OPT_SET_INT( 0, "set-upstream", &track, N_("change upstream info"),
- BRANCH_TRACK_OVERRIDE),
+ { OPTION_SET_INT, 0, "set-upstream", &track, NULL, N_("do not use"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, BRANCH_TRACK_OVERRIDE },
OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")),
OPT__COLOR(&branch_use_color, N_("use colored output")),
OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2),
OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1),
OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2),
+ OPT_BIT('c', "copy", ©, N_("copy a branch and its reflog"), 1),
+ OPT_BIT('C', NULL, ©, N_("copy a branch, even if target exists"), 2),
OPT_BOOL(0, "list", &list, N_("list branch names")),
OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")),
OPT_BOOL(0, "edit-description", &edit_description,
N_("print only branches of the object"), 0, parse_opt_object_name
},
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
- OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")),
+ OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
- if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0)
+ if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0)
list = 1;
if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr ||
filter.no_commit)
list = 1;
- if (!!delete + !!rename + !!new_upstream +
+ if (!!delete + !!rename + !!copy + !!new_upstream +
list + unset_upstream > 1)
usage_with_options(builtin_branch_usage, options);
if (force) {
delete *= 2;
rename *= 2;
+ copy *= 2;
}
if (delete) {
if (!sorting)
sorting = ref_default_sorting();
sorting->ignore_case = icase;
- print_ref_list(&filter, sorting, format);
+ print_ref_list(&filter, sorting, &format);
print_columns(&output, colopts, NULL);
string_list_clear(&output, 0);
return 0;
if (edit_branch_description(branch_name))
return 1;
+ } else if (copy) {
+ if (!argc)
+ die(_("branch name required"));
+ else if (argc == 1)
+ copy_or_rename_branch(head, argv[0], 1, copy > 1);
+ else if (argc == 2)
+ copy_or_rename_branch(argv[0], argv[1], 1, copy > 1);
+ else
+ die(_("too many branches for a copy operation"));
} else if (rename) {
if (!argc)
die(_("branch name required"));
else if (argc == 1)
- rename_branch(head, argv[0], rename > 1);
+ copy_or_rename_branch(head, argv[0], 0, rename > 1);
else if (argc == 2)
- rename_branch(argv[0], argv[1], rename > 1);
+ copy_or_rename_branch(argv[0], argv[1], 0, rename > 1);
else
die(_("too many branches for a rename operation"));
} else if (new_upstream) {
strbuf_release(&buf);
} else if (argc > 0 && argc <= 2) {
struct branch *branch = branch_get(argv[0]);
- int branch_existed = 0, remote_tracking = 0;
- struct strbuf buf = STRBUF_INIT;
if (!strcmp(argv[0], "HEAD"))
die(_("it does not make sense to create 'HEAD' manually"));
die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
if (track == BRANCH_TRACK_OVERRIDE)
- fprintf(stderr, _("The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to\n"));
-
- strbuf_addf(&buf, "refs/remotes/%s", branch->name);
- remote_tracking = ref_exists(buf.buf);
- strbuf_release(&buf);
+ die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
- branch_existed = ref_exists(branch->refname);
create_branch(argv[0], (argc == 2) ? argv[1] : head,
force, reflog, 0, quiet, track);
- /*
- * We only show the instructions if the user gave us
- * one branch which doesn't exist locally, but is the
- * name of a remote-tracking branch.
- */
- if (argc == 1 && track == BRANCH_TRACK_OVERRIDE &&
- !branch_existed && remote_tracking) {
- fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name);
- fprintf(stderr, " git branch -d %s\n", branch->name);
- fprintf(stderr, " git branch --set-upstream-to %s\n", branch->name);
- }
-
} else
usage_with_options(builtin_branch_usage, options);
*
*/
#include "cache.h"
+#include "config.h"
+#include "repository.h"
#include "lockfile.h"
#include "exec_cmd.h"
#include "strbuf.h"
#include "string-list.h"
#include "utf8.h"
#include "dir.h"
+#include "color.h"
struct config_source {
struct config_source *prev;
static int pack_compression_seen;
static int zlib_compression_seen;
-/*
- * Default config_set that contains key-value pairs from the usual set of config
- * config files (i.e repo specific .git/config, user wide ~/.gitconfig, XDG
- * config file and the global /etc/gitconfig)
- */
-static struct config_set the_config_set;
-
static int config_file_fgetc(struct config_source *conf)
{
return getc_unlocked(conf->u.file);
if (opts->git_dir)
git_dir = opts->git_dir;
- else if (have_git_dir())
- git_dir = get_git_dir();
else
goto done;
}
ret = !wildmatch(pattern.buf + prefix, text.buf + prefix,
- icase ? WM_CASEFOLD : 0, NULL);
+ icase ? WM_CASEFOLD : 0);
if (!ret && !already_tried_absolute) {
/*
out_free_ret_1:
if (store_key) {
- free(*store_key);
- *store_key = NULL;
+ FREE_AND_NULL(*store_key);
}
return -CONFIG_INVALID_KEY;
}
*/
cf->linenr--;
ret = fn(name->buf, value, data);
- cf->linenr++;
+ if (ret >= 0)
+ cf->linenr++;
return ret;
}
return ret;
}
-int git_parse_maybe_bool(const char *value)
+static int git_parse_maybe_bool_text(const char *value)
{
if (!value)
return 1;
return -1;
}
-int git_config_maybe_bool(const char *name, const char *value)
+int git_parse_maybe_bool(const char *value)
{
- int v = git_parse_maybe_bool(value);
+ int v = git_parse_maybe_bool_text(value);
if (0 <= v)
return v;
if (git_parse_int(value, &v))
int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
{
- int v = git_parse_maybe_bool(value);
+ int v = git_parse_maybe_bool_text(value);
if (0 <= v) {
*is_bool = 1;
return v;
if (starts_with(var, "advice."))
return git_default_advice_config(var, value);
+ if (git_color_config(var, value, dummy) < 0)
+ return -1;
+
if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
pager_use_color = git_config_bool(var,value);
return 0;
int ret = -1;
FILE *f;
- f = fopen(filename, "r");
+ f = fopen_or_warn(filename, "r");
if (f) {
flockfile(f);
ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename, filename, f, data);
return do_config_from(&top, fn, data);
}
-int git_config_from_blob_sha1(config_fn_t fn,
+int git_config_from_blob_oid(config_fn_t fn,
const char *name,
- const unsigned char *sha1,
+ const struct object_id *oid,
void *data)
{
enum object_type type;
unsigned long size;
int ret;
- buf = read_sha1_file(sha1, &type, &size);
+ buf = read_sha1_file(oid->hash, &type, &size);
if (!buf)
return error("unable to load config blob object '%s'", name);
if (type != OBJ_BLOB) {
const char *name,
void *data)
{
- unsigned char sha1[20];
+ struct object_id oid;
- if (get_sha1(name, sha1) < 0)
+ if (get_oid(name, &oid) < 0)
return error("unable to resolve config blob '%s'", name);
- return git_config_from_blob_sha1(fn, name, sha1, data);
+ return git_config_from_blob_oid(fn, name, &oid, data);
}
const char *git_etc_gitconfig(void)
char *user_config = expand_user_path("~/.gitconfig", 0);
char *repo_config;
- if (opts->git_dir)
- repo_config = mkpathdup("%s/config", opts->git_dir);
- else if (have_git_dir())
- repo_config = git_pathdup("config");
+ if (opts->commondir)
+ repo_config = mkpathdup("%s/config", opts->commondir);
else
repo_config = NULL;
return ret;
}
-int git_config_with_options(config_fn_t fn, void *data,
- struct git_config_source *config_source,
- const struct config_options *opts)
+int config_with_options(config_fn_t fn, void *data,
+ struct git_config_source *config_source,
+ const struct config_options *opts)
{
struct config_include_data inc = CONFIG_INCLUDE_INIT;
return do_git_config_sequence(opts, fn, data);
}
-static void git_config_raw(config_fn_t fn, void *data)
-{
- struct config_options opts = {0};
-
- opts.respect_includes = 1;
- if (git_config_with_options(fn, data, NULL, &opts) < 0)
- /*
- * git_config_with_options() normally returns only
- * zero, as most errors are fatal, and
- * non-fatal potential errors are guarded by "if"
- * statements that are entered only when no error is
- * possible.
- *
- * If we ever encounter a non-fatal error, it means
- * something went really wrong and we should stop
- * immediately.
- */
- die(_("unknown error occurred while reading the configuration files"));
-}
-
static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
{
int i, value_index;
void read_early_config(config_fn_t cb, void *data)
{
struct config_options opts = {0};
- struct strbuf buf = STRBUF_INIT;
+ struct strbuf commondir = STRBUF_INIT;
+ struct strbuf gitdir = STRBUF_INIT;
opts.respect_includes = 1;
- if (have_git_dir())
+ if (have_git_dir()) {
+ opts.commondir = get_git_common_dir();
opts.git_dir = get_git_dir();
/*
* When setup_git_directory() was not yet asked to discover the
* notably, the current working directory is still the same after the
* call).
*/
- else if (discover_git_directory(&buf))
- opts.git_dir = buf.buf;
-
- git_config_with_options(cb, data, NULL, &opts);
-
- strbuf_release(&buf);
-}
+ } else if (!discover_git_directory(&commondir, &gitdir)) {
+ opts.commondir = commondir.buf;
+ opts.git_dir = gitdir.buf;
+ }
-static void git_config_check_init(void);
+ config_with_options(cb, data, NULL, &opts);
-void git_config(config_fn_t fn, void *data)
-{
- git_config_check_init();
- configset_iter(&the_config_set, fn, data);
+ strbuf_release(&commondir);
+ strbuf_release(&gitdir);
}
static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
return 0;
}
-static int config_set_element_cmp(const struct config_set_element *e1,
- const struct config_set_element *e2, const void *unused)
+static int config_set_element_cmp(const void *unused_cmp_data,
+ const void *entry,
+ const void *entry_or_key,
+ const void *unused_keydata)
{
+ const struct config_set_element *e1 = entry;
+ const struct config_set_element *e2 = entry_or_key;
+
return strcmp(e1->key, e2->key);
}
void git_configset_init(struct config_set *cs)
{
- hashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp, 0);
+ hashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);
cs->hash_initialized = 1;
cs->list.nr = 0;
cs->list.alloc = 0;
{
const char *value;
if (!git_configset_get_value(cs, key, &value)) {
- *dest = git_config_maybe_bool(key, value);
+ *dest = git_parse_maybe_bool(value);
if (*dest == -1)
return -1;
return 0;
return 1;
}
-static void git_config_check_init(void)
+/* Functions use to read configuration from a repository */
+static void repo_read_config(struct repository *repo)
{
- if (the_config_set.hash_initialized)
+ struct config_options opts;
+
+ opts.respect_includes = 1;
+ opts.commondir = repo->commondir;
+ opts.git_dir = repo->gitdir;
+
+ if (!repo->config)
+ repo->config = xcalloc(1, sizeof(struct config_set));
+ else
+ git_configset_clear(repo->config);
+
+ git_configset_init(repo->config);
+
+ if (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)
+ /*
+ * config_with_options() normally returns only
+ * zero, as most errors are fatal, and
+ * non-fatal potential errors are guarded by "if"
+ * statements that are entered only when no error is
+ * possible.
+ *
+ * If we ever encounter a non-fatal error, it means
+ * something went really wrong and we should stop
+ * immediately.
+ */
+ die(_("unknown error occurred while reading the configuration files"));
+}
+
+static void git_config_check_init(struct repository *repo)
+{
+ if (repo->config && repo->config->hash_initialized)
return;
- git_configset_init(&the_config_set);
- git_config_raw(config_set_callback, &the_config_set);
+ repo_read_config(repo);
}
-void git_config_clear(void)
+static void repo_config_clear(struct repository *repo)
{
- if (!the_config_set.hash_initialized)
+ if (!repo->config || !repo->config->hash_initialized)
return;
- git_configset_clear(&the_config_set);
+ git_configset_clear(repo->config);
}
-int git_config_get_value(const char *key, const char **value)
+void repo_config(struct repository *repo, config_fn_t fn, void *data)
{
- git_config_check_init();
- return git_configset_get_value(&the_config_set, key, value);
+ git_config_check_init(repo);
+ configset_iter(repo->config, fn, data);
}
-const struct string_list *git_config_get_value_multi(const char *key)
+int repo_config_get_value(struct repository *repo,
+ const char *key, const char **value)
{
- git_config_check_init();
- return git_configset_get_value_multi(&the_config_set, key);
+ git_config_check_init(repo);
+ return git_configset_get_value(repo->config, key, value);
}
-int git_config_get_string_const(const char *key, const char **dest)
+const struct string_list *repo_config_get_value_multi(struct repository *repo,
+ const char *key)
+{
+ git_config_check_init(repo);
+ return git_configset_get_value_multi(repo->config, key);
+}
+
+int repo_config_get_string_const(struct repository *repo,
+ const char *key, const char **dest)
{
int ret;
- git_config_check_init();
- ret = git_configset_get_string_const(&the_config_set, key, dest);
+ git_config_check_init(repo);
+ ret = git_configset_get_string_const(repo->config, key, dest);
if (ret < 0)
git_die_config(key, NULL);
return ret;
}
+int repo_config_get_string(struct repository *repo,
+ const char *key, char **dest)
+{
+ git_config_check_init(repo);
+ return repo_config_get_string_const(repo, key, (const char **)dest);
+}
+
+int repo_config_get_int(struct repository *repo,
+ const char *key, int *dest)
+{
+ git_config_check_init(repo);
+ return git_configset_get_int(repo->config, key, dest);
+}
+
+int repo_config_get_ulong(struct repository *repo,
+ const char *key, unsigned long *dest)
+{
+ git_config_check_init(repo);
+ return git_configset_get_ulong(repo->config, key, dest);
+}
+
+int repo_config_get_bool(struct repository *repo,
+ const char *key, int *dest)
+{
+ git_config_check_init(repo);
+ return git_configset_get_bool(repo->config, key, dest);
+}
+
+int repo_config_get_bool_or_int(struct repository *repo,
+ const char *key, int *is_bool, int *dest)
+{
+ git_config_check_init(repo);
+ return git_configset_get_bool_or_int(repo->config, key, is_bool, dest);
+}
+
+int repo_config_get_maybe_bool(struct repository *repo,
+ const char *key, int *dest)
+{
+ git_config_check_init(repo);
+ return git_configset_get_maybe_bool(repo->config, key, dest);
+}
+
+int repo_config_get_pathname(struct repository *repo,
+ const char *key, const char **dest)
+{
+ int ret;
+ git_config_check_init(repo);
+ ret = git_configset_get_pathname(repo->config, key, dest);
+ if (ret < 0)
+ git_die_config(key, NULL);
+ return ret;
+}
+
+/* Functions used historically to read configuration from 'the_repository' */
+void git_config(config_fn_t fn, void *data)
+{
+ repo_config(the_repository, fn, data);
+}
+
+void git_config_clear(void)
+{
+ repo_config_clear(the_repository);
+}
+
+int git_config_get_value(const char *key, const char **value)
+{
+ return repo_config_get_value(the_repository, key, value);
+}
+
+const struct string_list *git_config_get_value_multi(const char *key)
+{
+ return repo_config_get_value_multi(the_repository, key);
+}
+
+int git_config_get_string_const(const char *key, const char **dest)
+{
+ return repo_config_get_string_const(the_repository, key, dest);
+}
+
int git_config_get_string(const char *key, char **dest)
{
- git_config_check_init();
- return git_config_get_string_const(key, (const char **)dest);
+ return repo_config_get_string(the_repository, key, dest);
}
int git_config_get_int(const char *key, int *dest)
{
- git_config_check_init();
- return git_configset_get_int(&the_config_set, key, dest);
+ return repo_config_get_int(the_repository, key, dest);
}
int git_config_get_ulong(const char *key, unsigned long *dest)
{
- git_config_check_init();
- return git_configset_get_ulong(&the_config_set, key, dest);
+ return repo_config_get_ulong(the_repository, key, dest);
}
int git_config_get_bool(const char *key, int *dest)
{
- git_config_check_init();
- return git_configset_get_bool(&the_config_set, key, dest);
+ return repo_config_get_bool(the_repository, key, dest);
}
int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)
{
- git_config_check_init();
- return git_configset_get_bool_or_int(&the_config_set, key, is_bool, dest);
+ return repo_config_get_bool_or_int(the_repository, key, is_bool, dest);
}
int git_config_get_maybe_bool(const char *key, int *dest)
{
- git_config_check_init();
- return git_configset_get_maybe_bool(&the_config_set, key, dest);
+ return repo_config_get_maybe_bool(the_repository, key, dest);
}
int git_config_get_pathname(const char *key, const char **dest)
{
- int ret;
- git_config_check_init();
- ret = git_configset_get_pathname(&the_config_set, key, dest);
- if (ret < 0)
- git_die_config(key, NULL);
- return ret;
+ return repo_config_get_pathname(the_repository, key, dest);
+}
+
+/*
+ * Note: This function exists solely to maintain backward compatibility with
+ * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should
+ * NOT be used anywhere else.
+ *
+ * Runs the provided config function on the '.gitmodules' file found in the
+ * working directory.
+ */
+void config_from_gitmodules(config_fn_t fn, void *data)
+{
+ if (the_repository->worktree) {
+ char *file = repo_worktree_path(the_repository, GITMODULES_FILE);
+ git_config_from_file(fn, file, data);
+ free(file);
+ }
}
int git_config_get_expiry(const char *key, const char **output)
return ret;
}
+int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now)
+{
+ char *expiry_string;
+ intmax_t days;
+ timestamp_t when;
+
+ if (git_config_get_string(key, &expiry_string))
+ return 1; /* no such thing */
+
+ if (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {
+ const int scale = 86400;
+ *expiry = now - days * scale;
+ return 0;
+ }
+
+ if (!parse_expiry_date(expiry_string, &when)) {
+ *expiry = when;
+ return 0;
+ }
+ return -1; /* thing exists but cannot be parsed */
+}
+
int git_config_get_untracked_cache(void)
{
int val = -1;
size_t *offset;
unsigned int offset_alloc;
enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
- int seen;
+ unsigned int seen;
} store;
static int matches(const char *key, const char *value)
return 4;
}
- static ssize_t write_section(int fd, const char *key)
+ static struct strbuf store_create_section(const char *key)
{
const char *dot;
int i;
- ssize_t ret;
struct strbuf sb = STRBUF_INIT;
dot = memchr(key, '.', store.baselen);
strbuf_addf(&sb, "[%.*s]\n", store.baselen, key);
}
- ret = write_in_full(fd, sb.buf, sb.len);
+ return sb;
+ }
+
-static int store_write_section(int fd, const char *key)
++static ssize_t write_section(int fd, const char *key)
+ {
- int success;
-
+ struct strbuf sb = store_create_section(key);
++ ssize_t ret;
+
- success = write_in_full(fd, sb.buf, sb.len) == sb.len;
++ ret = write_in_full(fd, sb.buf, sb.len) == sb.len;
strbuf_release(&sb);
- return success;
+ return ret;
}
-static int store_write_pair(int fd, const char *key, const char *value)
+static ssize_t write_pair(int fd, const char *key, const char *value)
{
- int i, success;
+ int i;
+ ssize_t ret;
int length = strlen(key + store.baselen + 1);
const char *quote = "";
struct strbuf sb = STRBUF_INIT;
case '"':
case '\\':
strbuf_addch(&sb, '\\');
+ /* fallthrough */
default:
strbuf_addch(&sb, value[i]);
break;
}
strbuf_addf(&sb, "%s\n", quote);
- success = write_in_full(fd, sb.buf, sb.len) == sb.len;
+ ret = write_in_full(fd, sb.buf, sb.len);
strbuf_release(&sb);
- return success;
+ return ret;
}
static ssize_t find_beginning_of_line(const char *contents, size_t size,
{
int fd = -1, in_fd = -1;
int ret;
- struct lock_file *lock = NULL;
+ struct lock_file lock = LOCK_INIT;
char *filename_buf = NULL;
char *contents = NULL;
size_t contents_sz;
* The lock serves a purpose in addition to locking: the new
* contents of .git/config will be written into it.
*/
- lock = xcalloc(1, sizeof(struct lock_file));
- fd = hold_lock_file_for_update(lock, config_filename, 0);
+ fd = hold_lock_file_for_update(&lock, config_filename, 0);
if (fd < 0) {
error_errno("could not lock config file %s", config_filename);
free(store.key);
}
store.key = (char *)key;
- if (!store_write_section(fd, key) ||
- !store_write_pair(fd, key, value))
+ if (write_section(fd, key) < 0 ||
+ write_pair(fd, key, value) < 0)
goto write_err_out;
} else {
struct stat st;
close(in_fd);
in_fd = -1;
- if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
- error_errno("chmod on %s failed", get_lock_file_path(lock));
+ if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) {
+ error_errno("chmod on %s failed", get_lock_file_path(&lock));
ret = CONFIG_NO_WRITE;
goto out_free;
}
/* write the first part of the config */
if (copy_end > copy_begin) {
if (write_in_full(fd, contents + copy_begin,
- copy_end - copy_begin) <
- copy_end - copy_begin)
+ copy_end - copy_begin) < 0)
goto write_err_out;
if (new_line &&
- write_str_in_full(fd, "\n") != 1)
+ write_str_in_full(fd, "\n") < 0)
goto write_err_out;
}
copy_begin = store.offset[i];
/* write the pair (value == NULL means unset) */
if (value != NULL) {
if (store.state == START) {
- if (!store_write_section(fd, key))
+ if (write_section(fd, key) < 0)
goto write_err_out;
}
- if (!store_write_pair(fd, key, value))
+ if (write_pair(fd, key, value) < 0)
goto write_err_out;
}
/* write the rest of the config */
if (copy_begin < contents_sz)
if (write_in_full(fd, contents + copy_begin,
- contents_sz - copy_begin) <
- contents_sz - copy_begin)
+ contents_sz - copy_begin) < 0)
goto write_err_out;
munmap(contents, contents_sz);
contents = NULL;
}
- if (commit_lock_file(lock) < 0) {
+ if (commit_lock_file(&lock) < 0) {
error_errno("could not write config file %s", config_filename);
ret = CONFIG_NO_WRITE;
- lock = NULL;
goto out_free;
}
- /*
- * lock is committed, so don't try to roll it back below.
- * NOTE: Since lockfile.c keeps a linked list of all created
- * lock_file structures, it isn't safe to free(lock). It's
- * better to just leave it hanging around.
- */
- lock = NULL;
ret = 0;
/* Invalidate the config cache */
git_config_clear();
out_free:
- if (lock)
- rollback_lock_file(lock);
+ rollback_lock_file(&lock);
free(filename_buf);
if (contents)
munmap(contents, contents_sz);
return ret;
write_err_out:
- ret = write_error(get_lock_file_path(lock));
+ ret = write_error(get_lock_file_path(&lock));
goto out_free;
}
}
/* if new_name == NULL, the section is removed instead */
- int git_config_rename_section_in_file(const char *config_filename,
- const char *old_name, const char *new_name)
+ static int git_config_copy_or_rename_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name, int copy)
{
int ret = 0, remove = 0;
char *filename_buf = NULL;
char buf[1024];
FILE *config_file = NULL;
struct stat st;
+ struct strbuf copystr = STRBUF_INIT;
if (new_name && !section_name_is_ok(new_name)) {
ret = error("invalid section name: %s", new_name);
}
if (!(config_file = fopen(config_filename, "rb"))) {
+ ret = warn_on_fopen_errors(config_filename);
+ if (ret)
+ goto out;
/* no config file means nothing to rename, no error */
goto commit_and_out;
}
while (fgets(buf, sizeof(buf), config_file)) {
int i;
int length;
+ int is_section = 0;
char *output = buf;
for (i = 0; buf[i] && isspace(buf[i]); i++)
; /* do nothing */
if (buf[i] == '[') {
/* it's a section */
- int offset = section_name_match(&buf[i], old_name);
+ int offset;
+ is_section = 1;
+
+ /*
+ * When encountering a new section under -c we
+ * need to flush out any section we're already
+ * coping and begin anew. There might be
+ * multiple [branch "$name"] sections.
+ */
+ if (copystr.len > 0) {
+ if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
+ strbuf_reset(©str);
+ }
+
+ offset = section_name_match(&buf[i], old_name);
if (offset > 0) {
ret++;
if (new_name == NULL) {
continue;
}
store.baselen = strlen(new_name);
- if (write_section(out_fd, new_name) < 0) {
- ret = write_error(get_lock_file_path(lock));
- goto out;
- }
- /*
- * We wrote out the new section, with
- * a newline, now skip the old
- * section's length
- */
- output += offset + i;
- if (strlen(output) > 0) {
+ if (!copy) {
- if (!store_write_section(out_fd, new_name)) {
++ if (write_section(out_fd, new_name) < 0) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
-
/*
- * More content means there's
- * a declaration to put on the
- * next line; indent with a
- * tab
+ * We wrote out the new section, with
+ * a newline, now skip the old
+ * section's length
*/
- output -= 1;
- output[0] = '\t';
+ output += offset + i;
+ if (strlen(output) > 0) {
+ /*
+ * More content means there's
+ * a declaration to put on the
+ * next line; indent with a
+ * tab
+ */
+ output -= 1;
+ output[0] = '\t';
+ }
+ } else {
+ copystr = store_create_section(new_name);
}
}
remove = 0;
if (remove)
continue;
length = strlen(output);
- if (write_in_full(out_fd, output, length) != length) {
+
+ if (!is_section && copystr.len > 0) {
+ strbuf_add(©str, output, length);
+ }
+
+ if (write_in_full(out_fd, output, length) < 0) {
ret = write_error(get_lock_file_path(lock));
goto out;
}
}
+
+ /*
+ * Copy a trailing section at the end of the config, won't be
+ * flushed by the usual "flush because we have a new section
+ * logic in the loop above.
+ */
+ if (copystr.len > 0) {
+ if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
+ ret = write_error(get_lock_file_path(lock));
+ goto out;
+ }
+ strbuf_reset(©str);
+ }
+
fclose(config_file);
config_file = NULL;
commit_and_out:
return ret;
}
+ int git_config_rename_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name)
+ {
+ return git_config_copy_or_rename_section_in_file(config_filename,
+ old_name, new_name, 0);
+ }
+
int git_config_rename_section(const char *old_name, const char *new_name)
{
return git_config_rename_section_in_file(NULL, old_name, new_name);
}
+ int git_config_copy_section_in_file(const char *config_filename,
+ const char *old_name, const char *new_name)
+ {
+ return git_config_copy_or_rename_section_in_file(config_filename,
+ old_name, new_name, 1);
+ }
+
+ int git_config_copy_section(const char *old_name, const char *new_name)
+ {
+ return git_config_copy_section_in_file(NULL, old_name, new_name);
+ }
+
/*
* Call this to report error for your variable that should not
* get a boolean value (i.e. "[my] var" means "true").
--- /dev/null
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/* git_config_parse_key() returns these negated: */
+#define CONFIG_INVALID_KEY 1
+#define CONFIG_NO_SECTION_OR_NAME 2
+/* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */
+#define CONFIG_NO_LOCK -1
+#define CONFIG_INVALID_FILE 3
+#define CONFIG_NO_WRITE 4
+#define CONFIG_NOTHING_SET 5
+#define CONFIG_INVALID_PATTERN 6
+#define CONFIG_GENERIC_ERROR 7
+
+#define CONFIG_REGEX_NONE ((void *)1)
+
+struct git_config_source {
+ unsigned int use_stdin:1;
+ const char *file;
+ const char *blob;
+};
+
+enum config_origin_type {
+ CONFIG_ORIGIN_BLOB,
+ CONFIG_ORIGIN_FILE,
+ CONFIG_ORIGIN_STDIN,
+ CONFIG_ORIGIN_SUBMODULE_BLOB,
+ CONFIG_ORIGIN_CMDLINE
+};
+
+struct config_options {
+ unsigned int respect_includes : 1;
+ const char *commondir;
+ const char *git_dir;
+};
+
+typedef int (*config_fn_t)(const char *, const char *, void *);
+extern int git_default_config(const char *, const char *, void *);
+extern int git_config_from_file(config_fn_t fn, const char *, void *);
+extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
+ const char *name, const char *buf, size_t len, void *data);
+extern int git_config_from_blob_oid(config_fn_t fn, const char *name,
+ const struct object_id *oid, void *data);
+extern void git_config_push_parameter(const char *text);
+extern int git_config_from_parameters(config_fn_t fn, void *data);
+extern void read_early_config(config_fn_t cb, void *data);
+extern void git_config(config_fn_t fn, void *);
+extern int config_with_options(config_fn_t fn, void *,
+ struct git_config_source *config_source,
+ const struct config_options *opts);
+extern int git_parse_ulong(const char *, unsigned long *);
+extern int git_parse_maybe_bool(const char *);
+extern int git_config_int(const char *, const char *);
+extern int64_t git_config_int64(const char *, const char *);
+extern unsigned long git_config_ulong(const char *, const char *);
+extern ssize_t git_config_ssize_t(const char *, const char *);
+extern int git_config_bool_or_int(const char *, const char *, int *);
+extern int git_config_bool(const char *, const char *);
+extern int git_config_string(const char **, const char *, const char *);
+extern int git_config_pathname(const char **, const char *, const char *);
+extern int git_config_set_in_file_gently(const char *, const char *, const char *);
+extern void git_config_set_in_file(const char *, const char *, const char *);
+extern int git_config_set_gently(const char *, const char *);
+extern void git_config_set(const char *, const char *);
+extern int git_config_parse_key(const char *, char **, int *);
+extern int git_config_key_is_valid(const char *key);
+extern int git_config_set_multivar_gently(const char *, const char *, const char *, int);
+extern void git_config_set_multivar(const char *, const char *, const char *, int);
+extern int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, int);
+extern void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
+extern int git_config_rename_section(const char *, const char *);
+extern int git_config_rename_section_in_file(const char *, const char *, const char *);
++extern int git_config_copy_section(const char *, const char *);
++extern int git_config_copy_section_in_file(const char *, const char *, const char *);
+extern const char *git_etc_gitconfig(void);
+extern int git_env_bool(const char *, int);
+extern unsigned long git_env_ulong(const char *, unsigned long);
+extern int git_config_system(void);
+extern int config_error_nonbool(const char *);
+#if defined(__GNUC__)
+#define config_error_nonbool(s) (config_error_nonbool(s), const_error())
+#endif
+
+extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
+
+enum config_scope {
+ CONFIG_SCOPE_UNKNOWN = 0,
+ CONFIG_SCOPE_SYSTEM,
+ CONFIG_SCOPE_GLOBAL,
+ CONFIG_SCOPE_REPO,
+ CONFIG_SCOPE_CMDLINE,
+};
+
+extern enum config_scope current_config_scope(void);
+extern const char *current_config_origin_type(void);
+extern const char *current_config_name(void);
+
+struct config_include_data {
+ int depth;
+ config_fn_t fn;
+ void *data;
+ const struct config_options *opts;
+};
+#define CONFIG_INCLUDE_INIT { 0 }
+extern int git_config_include(const char *name, const char *value, void *data);
+
+/*
+ * Match and parse a config key of the form:
+ *
+ * section.(subsection.)?key
+ *
+ * (i.e., what gets handed to a config_fn_t). The caller provides the section;
+ * we return -1 if it does not match, 0 otherwise. The subsection and key
+ * out-parameters are filled by the function (and *subsection is NULL if it is
+ * missing).
+ *
+ * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if
+ * there is no subsection at all.
+ */
+extern int parse_config_key(const char *var,
+ const char *section,
+ const char **subsection, int *subsection_len,
+ const char **key);
+
+struct config_set_element {
+ struct hashmap_entry ent;
+ char *key;
+ struct string_list value_list;
+};
+
+struct configset_list_item {
+ struct config_set_element *e;
+ int value_index;
+};
+
+/*
+ * the contents of the list are ordered according to their
+ * position in the config files and order of parsing the files.
+ * (i.e. key-value pair at the last position of .git/config will
+ * be at the last item of the list)
+ */
+struct configset_list {
+ struct configset_list_item *items;
+ unsigned int nr, alloc;
+};
+
+struct config_set {
+ struct hashmap config_hash;
+ int hash_initialized;
+ struct configset_list list;
+};
+
+extern void git_configset_init(struct config_set *cs);
+extern int git_configset_add_file(struct config_set *cs, const char *filename);
+extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value);
+extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key);
+extern void git_configset_clear(struct config_set *cs);
+extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest);
+extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest);
+extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest);
+extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest);
+extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest);
+extern int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest);
+extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest);
+extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest);
+
+/* Functions for reading a repository's config */
+struct repository;
+extern void repo_config(struct repository *repo, config_fn_t fn, void *data);
+extern int repo_config_get_value(struct repository *repo,
+ const char *key, const char **value);
+extern const struct string_list *repo_config_get_value_multi(struct repository *repo,
+ const char *key);
+extern int repo_config_get_string_const(struct repository *repo,
+ const char *key, const char **dest);
+extern int repo_config_get_string(struct repository *repo,
+ const char *key, char **dest);
+extern int repo_config_get_int(struct repository *repo,
+ const char *key, int *dest);
+extern int repo_config_get_ulong(struct repository *repo,
+ const char *key, unsigned long *dest);
+extern int repo_config_get_bool(struct repository *repo,
+ const char *key, int *dest);
+extern int repo_config_get_bool_or_int(struct repository *repo,
+ const char *key, int *is_bool, int *dest);
+extern int repo_config_get_maybe_bool(struct repository *repo,
+ const char *key, int *dest);
+extern int repo_config_get_pathname(struct repository *repo,
+ const char *key, const char **dest);
+
+/*
+ * Note: This function exists solely to maintain backward compatibility with
+ * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should
+ * NOT be used anywhere else.
+ *
+ * Runs the provided config function on the '.gitmodules' file found in the
+ * working directory.
+ */
+extern void config_from_gitmodules(config_fn_t fn, void *data);
+
+extern int git_config_get_value(const char *key, const char **value);
+extern const struct string_list *git_config_get_value_multi(const char *key);
+extern void git_config_clear(void);
+extern int git_config_get_string_const(const char *key, const char **dest);
+extern int git_config_get_string(const char *key, char **dest);
+extern int git_config_get_int(const char *key, int *dest);
+extern int git_config_get_ulong(const char *key, unsigned long *dest);
+extern int git_config_get_bool(const char *key, int *dest);
+extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest);
+extern int git_config_get_maybe_bool(const char *key, int *dest);
+extern int git_config_get_pathname(const char *key, const char **dest);
+extern int git_config_get_untracked_cache(void);
+extern int git_config_get_split_index(void);
+extern int git_config_get_max_percent_split_change(void);
+
+/* This dies if the configured or default date is in the future */
+extern int git_config_get_expiry(const char *key, const char **output);
+
+/* parse either "this many days" integer, or "5.days.ago" approxidate */
+extern int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);
+
+struct key_value_info {
+ const char *filename;
+ int linenr;
+ enum config_origin_type origin_type;
+ enum config_scope scope;
+};
+
+extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));
+extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr);
+
+#endif /* CONFIG_H */
*/
#include "cache.h"
+#include "config.h"
#include "hashmap.h"
#include "lockfile.h"
#include "iterator.h"
return 1;
}
+/*
+ * Return true if refname, which has the specified oid and flags, can
+ * be resolved to an object in the database. If the referred-to object
+ * does not exist, emit a warning and return false.
+ */
+int ref_resolves_to_object(const char *refname,
+ const struct object_id *oid,
+ unsigned int flags)
+{
+ if (flags & REF_ISBROKEN)
+ return 0;
+ if (!has_sha1_file(oid->hash)) {
+ error("%s does not point to a valid object!", refname);
+ return 0;
+ }
+ return 1;
+}
+
char *refs_resolve_refdup(struct ref_store *refs,
const char *refname, int resolve_flags,
unsigned char *sha1, int *flags)
int ref_exists(const char *refname)
{
- unsigned char sha1[20];
- return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
+ return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, NULL);
}
static int filter_refs(const char *refname, const struct object_id *oid,
{
struct ref_filter *filter = (struct ref_filter *)data;
- if (wildmatch(filter->pattern, refname, 0, NULL))
+ if (wildmatch(filter->pattern, refname, 0))
return 0;
return filter->fn(refname, oid, flags, filter->cb_data);
}
{
struct warn_if_dangling_data *d = cb_data;
const char *resolves_to;
- struct object_id junk;
if (!(flags & REF_ISSYMREF))
return 0;
- resolves_to = resolve_ref_unsafe(refname, 0, junk.hash, NULL);
+ resolves_to = resolve_ref_unsafe(refname, 0, NULL, NULL);
if (!resolves_to
|| (d->refname
? strcmp(resolves_to, d->refname)
return refs_for_each_tag_ref(get_main_ref_store(), fn, cb_data);
}
-int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_tag_ref(get_submodule_ref_store(submodule),
- fn, cb_data);
-}
-
int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
return refs_for_each_branch_ref(get_main_ref_store(), fn, cb_data);
}
-int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_branch_ref(get_submodule_ref_store(submodule),
- fn, cb_data);
-}
-
int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
return refs_for_each_remote_ref(get_main_ref_store(), fn, cb_data);
}
-int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_remote_ref(get_submodule_ref_store(submodule),
- fn, cb_data);
-}
-
int head_ref_namespaced(each_ref_fn fn, void *cb_data)
{
struct strbuf buf = STRBUF_INIT;
return REF_TYPE_NORMAL;
}
+long get_files_ref_lock_timeout_ms(void)
+{
+ static int configured = 0;
+
+ /* The default timeout is 100 ms: */
+ static int timeout_ms = 100;
+
+ if (!configured) {
+ git_config_get_int("core.filesreflocktimeout", &timeout_ms);
+ configured = 1;
+ }
+
+ return timeout_ms;
+}
+
static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
const unsigned char *old_sha1, struct strbuf *err)
{
strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
filename = git_path("%s", pseudoref);
- fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
+ fd = hold_lock_file_for_update_timeout(&lock, filename,
+ LOCK_DIE_ON_ERROR,
+ get_files_ref_lock_timeout_ms());
if (fd < 0) {
strbuf_addf(err, "could not open '%s' for writing: %s",
filename, strerror(errno));
- return -1;
+ goto done;
}
if (old_sha1) {
}
}
- if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
+ if (write_in_full(fd, buf.buf, buf.len) < 0) {
strbuf_addf(err, "could not write to '%s'", filename);
rollback_lock_file(&lock);
goto done;
int fd;
unsigned char actual_old_sha1[20];
- fd = hold_lock_file_for_update(&lock, filename,
- LOCK_DIE_ON_ERROR);
+ fd = hold_lock_file_for_update_timeout(
+ &lock, filename, LOCK_DIE_ON_ERROR,
+ get_files_ref_lock_timeout_ms());
if (fd < 0)
die_errno(_("Could not open '%s' for writing"), filename);
if (read_ref(pseudoref, actual_old_sha1))
for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
if (!cb.reccnt) {
- if (flags & GET_SHA1_QUIETLY)
+ if (flags & GET_OID_QUIETLY)
exit(128);
else
die("Log for %s is empty.", refname);
return -1;
}
+ flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
+
flags |= (new_sha1 ? REF_HAVE_NEW : 0) | (old_sha1 ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
const char *match = hide_refs->items[i].string;
const char *subject;
int neg = 0;
- int len;
+ const char *p;
if (*match == '!') {
neg = 1;
}
/* refname can be NULL when namespaces are used. */
- if (!subject || !starts_with(subject, match))
- continue;
- len = strlen(match);
- if (!subject[len] || subject[len] == '/')
+ if (subject &&
+ skip_prefix(subject, match, &p) &&
+ (!*p || *p == '/'))
return !neg;
}
return 0;
return ok;
}
-int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
struct object_id oid;
int flag;
- if (submodule) {
- if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0)
- return fn("HEAD", &oid, 0, cb_data);
-
- return 0;
- }
-
- if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag))
+ if (!refs_read_ref_full(refs, "HEAD", RESOLVE_REF_READING,
+ oid.hash, &flag))
return fn("HEAD", &oid, flag, cb_data);
return 0;
int head_ref(each_ref_fn fn, void *cb_data)
{
- return head_ref_submodule(NULL, fn, cb_data);
+ return refs_head_ref(get_main_ref_store(), fn, cb_data);
}
struct ref_iterator *refs_ref_iterator_begin(
return refs_for_each_ref(get_main_ref_store(), fn, cb_data);
}
-int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_ref(get_submodule_ref_store(submodule), fn, cb_data);
-}
-
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
each_ref_fn fn, void *cb_data)
{
prefix, fn, 0, flag, cb_data);
}
-int for_each_ref_in_submodule(const char *submodule, const char *prefix,
- each_ref_fn fn, void *cb_data)
+int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
+ each_ref_fn fn, void *cb_data,
+ unsigned int broken)
{
- return refs_for_each_ref_in(get_submodule_ref_store(submodule),
- prefix, fn, cb_data);
+ unsigned int flag = 0;
+
+ if (broken)
+ flag = DO_FOR_EACH_INCLUDE_BROKEN;
+ return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
}
int for_each_replace_ref(each_ref_fn fn, void *cb_data)
return do_for_each_ref(get_main_ref_store(),
git_replace_ref_base, fn,
strlen(git_replace_ref_base),
- 0, cb_data);
+ DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
unsigned char *sha1, int *flags)
{
static struct strbuf sb_refname = STRBUF_INIT;
+ struct object_id unused_oid;
int unused_flags;
int symref_count;
+ if (!sha1)
+ sha1 = unused_oid.hash;
if (!flags)
flags = &unused_flags;
int resolve_gitlink_ref(const char *submodule, const char *refname,
unsigned char *sha1)
{
- size_t len = strlen(submodule);
struct ref_store *refs;
int flags;
- while (len && submodule[len - 1] == '/')
- len--;
-
- if (!len)
- return -1;
-
- if (submodule[len]) {
- /* We need to strip off one or more trailing slashes */
- char *stripped = xmemdupz(submodule, len);
-
- refs = get_submodule_ref_store(stripped);
- free(stripped);
- } else {
- refs = get_submodule_ref_store(submodule);
- }
+ refs = get_submodule_ref_store(submodule);
if (!refs)
return -1;
char name[FLEX_ARRAY];
};
-static int ref_store_hash_cmp(const void *entry, const void *entry_or_key,
+static int ref_store_hash_cmp(const void *unused_cmp_data,
+ const void *entry, const void *entry_or_key,
const void *keydata)
{
const struct ref_store_hash_entry *e1 = entry, *e2 = entry_or_key;
const char *name)
{
if (!map->tablesize)
- hashmap_init(map, ref_store_hash_cmp, 0);
+ hashmap_init(map, ref_store_hash_cmp, NULL, 0);
if (hashmap_put(map, alloc_ref_store_hash_entry(name, refs)))
die("BUG: %s ref_store '%s' initialized twice", type, name);
{
struct strbuf submodule_sb = STRBUF_INIT;
struct ref_store *refs;
- int ret;
+ char *to_free = NULL;
+ size_t len;
- if (!submodule || !*submodule) {
- /*
- * FIXME: This case is ideally not allowed. But that
- * can't happen until we clean up all the callers.
- */
- return get_main_ref_store();
- }
+ if (!submodule)
+ return NULL;
+
+ len = strlen(submodule);
+ while (len && is_dir_sep(submodule[len - 1]))
+ len--;
+ if (!len)
+ return NULL;
+
+ if (submodule[len])
+ /* We need to strip off one or more trailing slashes */
+ submodule = to_free = xmemdupz(submodule, len);
refs = lookup_ref_store_map(&submodule_ref_stores, submodule);
if (refs)
- return refs;
+ goto done;
strbuf_addstr(&submodule_sb, submodule);
- ret = is_nonbare_repository_dir(&submodule_sb);
- strbuf_release(&submodule_sb);
- if (!ret)
- return NULL;
+ if (!is_nonbare_repository_dir(&submodule_sb))
+ goto done;
- ret = submodule_to_gitdir(&submodule_sb, submodule);
- if (ret) {
- strbuf_release(&submodule_sb);
- return NULL;
- }
+ if (submodule_to_gitdir(&submodule_sb, submodule))
+ goto done;
/* assume that add_submodule_odb() has been called */
refs = ref_store_init(submodule_sb.buf,
register_ref_store_map(&submodule_ref_stores, "submodule",
refs, submodule);
+done:
strbuf_release(&submodule_sb);
+ free(to_free);
+
return refs;
}
{
return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg);
}
+
+ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
+ const char *newref, const char *logmsg)
+ {
+ return refs->be->copy_ref(refs, oldref, newref, logmsg);
+ }
+
+ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
+ {
+ return refs_copy_existing_ref(get_main_ref_store(), oldref, newref, logmsg);
+ }
/*
* Resolve a reference, recursively following symbolic refererences.
*
- * Store the referred-to object's name in sha1 and return the name of
- * the non-symbolic reference that ultimately pointed at it. The
- * return value, if not NULL, is a pointer into either a static buffer
- * or the input ref.
+ * Return the name of the non-symbolic reference that ultimately pointed
+ * at the resolved object name. The return value, if not NULL, is a
+ * pointer into either a static buffer or the input ref.
+ *
+ * If sha1 is non-NULL, store the referred-to object's name in it.
*
* If the reference cannot be resolved to an object, the behavior
* depends on the RESOLVE_REF_READING flag:
* modifies the reference also returns a nonzero value to immediately
* stop the iteration. Returned references are sorted.
*/
+int refs_head_ref(struct ref_store *refs,
+ each_ref_fn fn, void *cb_data);
int refs_for_each_ref(struct ref_store *refs,
each_ref_fn fn, void *cb_data);
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
int head_ref(each_ref_fn fn, void *cb_data);
int for_each_ref(each_ref_fn fn, void *cb_data);
int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
+int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
+ each_ref_fn fn, void *cb_data,
+ unsigned int broken);
int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
unsigned int broken);
int for_each_tag_ref(each_ref_fn fn, void *cb_data);
int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
const char *prefix, void *cb_data);
-int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
-int for_each_ref_submodule(const char *submodule,
- each_ref_fn fn, void *cb_data);
-int for_each_ref_in_submodule(const char *submodule, const char *prefix,
- each_ref_fn fn, void *cb_data);
-int for_each_tag_ref_submodule(const char *submodule,
- each_ref_fn fn, void *cb_data);
-int for_each_branch_ref_submodule(const char *submodule,
- each_ref_fn fn, void *cb_data);
-int for_each_remote_ref_submodule(const char *submodule,
- each_ref_fn fn, void *cb_data);
-
int head_ref_namespaced(each_ref_fn fn, void *cb_data);
int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
#define REF_NODEREF 0x01
#define REF_FORCE_CREATE_REFLOG 0x40
+/*
+ * Flags that can be passed in to ref_transaction_update
+ */
+#define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \
+ REF_ISPRUNING | \
+ REF_FORCE_CREATE_REFLOG | \
+ REF_NODEREF
+
/*
* Setup reflog before using. Fill in err and return -1 on failure.
*/
/** rename ref, return 0 on success **/
int refs_rename_ref(struct ref_store *refs, const char *oldref,
const char *newref, const char *logmsg);
- int rename_ref(const char *oldref, const char *newref, const char *logmsg);
+ int rename_ref(const char *oldref, const char *newref,
+ const char *logmsg);
+
+ /** copy ref, return 0 on success **/
+ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
+ const char *newref, const char *logmsg);
+ int copy_existing_ref(const char *oldref, const char *newref,
+ const char *logmsg);
int refs_create_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
#define TRANSACTION_GENERIC_ERROR -2
/*
- * Perform the preparatory stages of commiting `transaction`. Acquire
+ * Perform the preparatory stages of committing `transaction`. Acquire
* any needed locks, check preconditions, etc.; basically, do as much
* as possible to ensure that the transaction will be able to go
* through, stopping just short of making any irrevocable or
* On failure, abort the transaction, write an error message to `err`,
* and return one of the `TRANSACTION_*` constants.
*
- * Callers who don't need such fine-grained control over commiting
+ * Callers who don't need such fine-grained control over committing
* reference transactions should just call `ref_transaction_commit()`.
*/
int ref_transaction_prepare(struct ref_transaction *transaction,
#include "../cache.h"
+#include "../config.h"
#include "../refs.h"
#include "refs-internal.h"
#include "ref-cache.h"
+#include "packed-backend.h"
#include "../iterator.h"
#include "../dir-iterator.h"
#include "../lockfile.h"
struct ref_lock {
char *ref_name;
- struct lock_file *lk;
+ struct lock_file lk;
struct object_id old_oid;
};
-/*
- * Return true if refname, which has the specified oid and flags, can
- * be resolved to an object in the database. If the referred-to object
- * does not exist, emit a warning and return false.
- */
-static int ref_resolves_to_object(const char *refname,
- const struct object_id *oid,
- unsigned int flags)
-{
- if (flags & REF_ISBROKEN)
- return 0;
- if (!has_sha1_file(oid->hash)) {
- error("%s does not point to a valid object!", refname);
- return 0;
- }
- return 1;
-}
-
-struct packed_ref_cache {
- struct ref_cache *cache;
-
- /*
- * Count of references to the data structure in this instance,
- * including the pointer from files_ref_store::packed if any.
- * The data will not be freed as long as the reference count
- * is nonzero.
- */
- unsigned int referrers;
-
- /* The metadata from when this packed-refs cache was read */
- struct stat_validity validity;
-};
-
/*
* Future: need to be in "struct repository"
* when doing a full libification.
char *gitdir;
char *gitcommondir;
- char *packed_refs_path;
struct ref_cache *loose;
- struct packed_ref_cache *packed;
- /*
- * Lock used for the "packed-refs" file. Note that this (and
- * thus the enclosing `files_ref_store`) must not be freed.
- */
- struct lock_file packed_refs_lock;
+ struct ref_store *packed_ref_store;
};
-/*
- * Increment the reference count of *packed_refs.
- */
-static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
-{
- packed_refs->referrers++;
-}
-
-/*
- * Decrease the reference count of *packed_refs. If it goes to zero,
- * free *packed_refs and return true; otherwise return false.
- */
-static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
-{
- if (!--packed_refs->referrers) {
- free_ref_cache(packed_refs->cache);
- stat_validity_clear(&packed_refs->validity);
- free(packed_refs);
- return 1;
- } else {
- return 0;
- }
-}
-
-static void clear_packed_ref_cache(struct files_ref_store *refs)
-{
- if (refs->packed) {
- struct packed_ref_cache *packed_refs = refs->packed;
-
- if (is_lock_file_locked(&refs->packed_refs_lock))
- die("BUG: packed-ref cache cleared while locked");
- refs->packed = NULL;
- release_packed_ref_cache(packed_refs);
- }
-}
-
static void clear_loose_ref_cache(struct files_ref_store *refs)
{
if (refs->loose) {
get_common_dir_noenv(&sb, gitdir);
refs->gitcommondir = strbuf_detach(&sb, NULL);
strbuf_addf(&sb, "%s/packed-refs", refs->gitcommondir);
- refs->packed_refs_path = strbuf_detach(&sb, NULL);
+ refs->packed_ref_store = packed_ref_store_create(sb.buf, flags);
+ strbuf_release(&sb);
return ref_store;
}
return refs;
}
-/* The length of a peeled reference line in packed-refs, including EOL: */
-#define PEELED_LINE_LENGTH 42
-
-/*
- * The packed-refs header line that we write out. Perhaps other
- * traits will be added later. The trailing space is required.
- */
-static const char PACKED_REFS_HEADER[] =
- "# pack-refs with: peeled fully-peeled \n";
-
-/*
- * Parse one line from a packed-refs file. Write the SHA1 to sha1.
- * Return a pointer to the refname within the line (null-terminated),
- * or NULL if there was a problem.
- */
-static const char *parse_ref_line(struct strbuf *line, struct object_id *oid)
-{
- const char *ref;
-
- if (parse_oid_hex(line->buf, oid, &ref) < 0)
- return NULL;
- if (!isspace(*ref++))
- return NULL;
-
- if (isspace(*ref))
- return NULL;
-
- if (line->buf[line->len - 1] != '\n')
- return NULL;
- line->buf[--line->len] = 0;
-
- return ref;
-}
-
-/*
- * Read from `packed_refs_file` into a newly-allocated
- * `packed_ref_cache` and return it. The return value will already
- * have its reference count incremented.
- *
- * A comment line of the form "# pack-refs with: " may contain zero or
- * more traits. We interpret the traits as follows:
- *
- * No traits:
- *
- * Probably no references are peeled. But if the file contains a
- * peeled value for a reference, we will use it.
- *
- * peeled:
- *
- * References under "refs/tags/", if they *can* be peeled, *are*
- * peeled in this file. References outside of "refs/tags/" are
- * probably not peeled even if they could have been, but if we find
- * a peeled value for such a reference we will use it.
- *
- * fully-peeled:
- *
- * All references in the file that can be peeled are peeled.
- * Inversely (and this is more important), any references in the
- * file for which no peeled value is recorded is not peelable. This
- * trait should typically be written alongside "peeled" for
- * compatibility with older clients, but we do not require it
- * (i.e., "peeled" is a no-op if "fully-peeled" is set).
- */
-static struct packed_ref_cache *read_packed_refs(const char *packed_refs_file)
-{
- FILE *f;
- struct packed_ref_cache *packed_refs = xcalloc(1, sizeof(*packed_refs));
- struct ref_entry *last = NULL;
- struct strbuf line = STRBUF_INIT;
- enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
- struct ref_dir *dir;
-
- acquire_packed_ref_cache(packed_refs);
- packed_refs->cache = create_ref_cache(NULL, NULL);
- packed_refs->cache->root->flag &= ~REF_INCOMPLETE;
-
- f = fopen(packed_refs_file, "r");
- if (!f) {
- if (errno == ENOENT) {
- /*
- * This is OK; it just means that no
- * "packed-refs" file has been written yet,
- * which is equivalent to it being empty.
- */
- return packed_refs;
- } else {
- die_errno("couldn't read %s", packed_refs_file);
- }
- }
-
- stat_validity_update(&packed_refs->validity, fileno(f));
-
- dir = get_ref_dir(packed_refs->cache->root);
- while (strbuf_getwholeline(&line, f, '\n') != EOF) {
- struct object_id oid;
- const char *refname;
- const char *traits;
-
- if (skip_prefix(line.buf, "# pack-refs with:", &traits)) {
- if (strstr(traits, " fully-peeled "))
- peeled = PEELED_FULLY;
- else if (strstr(traits, " peeled "))
- peeled = PEELED_TAGS;
- /* perhaps other traits later as well */
- continue;
- }
-
- refname = parse_ref_line(&line, &oid);
- if (refname) {
- int flag = REF_ISPACKED;
-
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
- if (!refname_is_safe(refname))
- die("packed refname is dangerous: %s", refname);
- oidclr(&oid);
- flag |= REF_BAD_NAME | REF_ISBROKEN;
- }
- last = create_ref_entry(refname, &oid, flag);
- if (peeled == PEELED_FULLY ||
- (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
- last->flag |= REF_KNOWS_PEELED;
- add_ref_entry(dir, last);
- continue;
- }
- if (last &&
- line.buf[0] == '^' &&
- line.len == PEELED_LINE_LENGTH &&
- line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
- !get_oid_hex(line.buf + 1, &oid)) {
- oidcpy(&last->u.value.peeled, &oid);
- /*
- * Regardless of what the file header said,
- * we definitely know the value of *this*
- * reference:
- */
- last->flag |= REF_KNOWS_PEELED;
- }
- }
-
- fclose(f);
- strbuf_release(&line);
-
- return packed_refs;
-}
-
-static const char *files_packed_refs_path(struct files_ref_store *refs)
-{
- return refs->packed_refs_path;
-}
-
static void files_reflog_path(struct files_ref_store *refs,
struct strbuf *sb,
const char *refname)
{
- if (!refname) {
- /*
- * FIXME: of course this is wrong in multi worktree
- * setting. To be fixed real soon.
- */
- strbuf_addf(sb, "%s/logs", refs->gitcommondir);
- return;
- }
-
switch (ref_type(refname)) {
case REF_TYPE_PER_WORKTREE:
case REF_TYPE_PSEUDOREF:
}
}
-/*
- * Get the packed_ref_cache for the specified files_ref_store,
- * creating and populating it if it hasn't been read before or if the
- * file has been changed (according to its `validity` field) since it
- * was last read. On the other hand, if we hold the lock, then assume
- * that the file hasn't been changed out from under us, so skip the
- * extra `stat()` call in `stat_validity_check()`.
- */
-static struct packed_ref_cache *get_packed_ref_cache(struct files_ref_store *refs)
-{
- const char *packed_refs_file = files_packed_refs_path(refs);
-
- if (refs->packed &&
- !is_lock_file_locked(&refs->packed_refs_lock) &&
- !stat_validity_check(&refs->packed->validity, packed_refs_file))
- clear_packed_ref_cache(refs);
-
- if (!refs->packed)
- refs->packed = read_packed_refs(packed_refs_file);
-
- return refs->packed;
-}
-
-static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
-{
- return get_ref_dir(packed_ref_cache->cache->root);
-}
-
-static struct ref_dir *get_packed_refs(struct files_ref_store *refs)
-{
- return get_packed_ref_dir(get_packed_ref_cache(refs));
-}
-
-/*
- * Add a reference to the in-memory packed reference cache. This may
- * only be called while the packed-refs file is locked (see
- * lock_packed_refs()). To actually write the packed-refs file, call
- * commit_packed_refs().
- */
-static void add_packed_ref(struct files_ref_store *refs,
- const char *refname, const struct object_id *oid)
-{
- struct packed_ref_cache *packed_ref_cache = get_packed_ref_cache(refs);
-
- if (!is_lock_file_locked(&refs->packed_refs_lock))
- die("BUG: packed refs not locked");
-
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
- die("Reference has invalid format: '%s'", refname);
-
- add_ref_entry(get_packed_ref_dir(packed_ref_cache),
- create_ref_entry(refname, oid, REF_ISPACKED));
-}
-
/*
* Read the loose references from the namespace dirname into dir
* (without recursing). dirname must end with '/'. dir must be the
return refs->loose;
}
-/*
- * Return the ref_entry for the given refname from the packed
- * references. If it does not exist, return NULL.
- */
-static struct ref_entry *get_packed_ref(struct files_ref_store *refs,
- const char *refname)
-{
- return find_ref_entry(get_packed_refs(refs), refname);
-}
-
-/*
- * A loose ref file doesn't exist; check for a packed ref.
- */
-static int resolve_packed_ref(struct files_ref_store *refs,
- const char *refname,
- unsigned char *sha1, unsigned int *flags)
-{
- struct ref_entry *entry;
-
- /*
- * The loose reference file does not exist; check for a packed
- * reference.
- */
- entry = get_packed_ref(refs, refname);
- if (entry) {
- hashcpy(sha1, entry->u.value.oid.hash);
- *flags |= REF_ISPACKED;
- return 0;
- }
- /* refname is not a packed reference. */
- return -1;
-}
-
static int files_read_raw_ref(struct ref_store *ref_store,
const char *refname, unsigned char *sha1,
struct strbuf *referent, unsigned int *type)
if (lstat(path, &st) < 0) {
if (errno != ENOENT)
goto out;
- if (resolve_packed_ref(refs, refname, sha1, type)) {
+ if (refs_read_raw_ref(refs->packed_ref_store, refname,
+ sha1, referent, type)) {
errno = ENOENT;
goto out;
}
* ref is supposed to be, there could still be a
* packed ref:
*/
- if (resolve_packed_ref(refs, refname, sha1, type)) {
+ if (refs_read_raw_ref(refs->packed_ref_store, refname,
+ sha1, referent, type)) {
errno = EISDIR;
goto out;
}
static void unlock_ref(struct ref_lock *lock)
{
- /* Do not free lock->lk -- atexit() still looks at them */
- if (lock->lk)
- rollback_lock_file(lock->lk);
+ rollback_lock_file(&lock->lk);
free(lock->ref_name);
free(lock);
}
goto error_return;
}
- if (!lock->lk)
- lock->lk = xcalloc(1, sizeof(struct lock_file));
-
- if (hold_lock_file_for_update(lock->lk, ref_file.buf, LOCK_NO_DEREF) < 0) {
+ if (hold_lock_file_for_update_timeout(
+ &lock->lk, ref_file.buf, LOCK_NO_DEREF,
+ get_files_ref_lock_timeout_ms()) < 0) {
if (errno == ENOENT && --attempts_remaining > 0) {
/*
* Maybe somebody just deleted one of the
/*
* If the ref did not exist and we are creating it,
- * make sure there is no existing ref that conflicts
- * with refname:
+ * make sure there is no existing packed ref that
+ * conflicts with refname:
*/
if (refs_verify_refname_available(
- &refs->base, refname,
+ refs->packed_ref_store, refname,
extras, skip, err))
goto error_return;
}
* be expensive and (b) loose references anyway usually do not
* have REF_KNOWS_PEELED.
*/
- if (flag & REF_ISPACKED) {
- struct ref_entry *r = get_packed_ref(refs, refname);
- if (r) {
- if (peel_entry(r, 0))
- return -1;
- hashcpy(sha1, r->u.value.peeled.hash);
- return 0;
- }
- }
+ if (flag & REF_ISPACKED &&
+ !refs_peel_ref(refs->packed_ref_store, refname, sha1))
+ return 0;
return peel_object(base, sha1);
}
struct files_ref_iterator {
struct ref_iterator base;
- struct packed_ref_cache *packed_ref_cache;
struct ref_iterator *iter0;
unsigned int flags;
};
if (iter->iter0)
ok = ref_iterator_abort(iter->iter0);
- release_packed_ref_cache(iter->packed_ref_cache);
base_ref_iterator_free(ref_iterator);
return ok;
}
* (If they've already been read, that's OK; we only need to
* guarantee that they're read before the packed refs, not
* *how much* before.) After that, we call
- * get_packed_ref_cache(), which internally checks whether the
- * packed-ref cache is up to date with what is on disk, and
- * re-reads it if not.
+ * packed_ref_iterator_begin(), which internally checks
+ * whether the packed-ref cache is up to date with what is on
+ * disk, and re-reads it if not.
*/
loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs),
prefix, 1);
- iter->packed_ref_cache = get_packed_ref_cache(refs);
- acquire_packed_ref_cache(iter->packed_ref_cache);
- packed_iter = cache_ref_iterator_begin(iter->packed_ref_cache->cache,
- prefix, 0);
+ /*
+ * The packed-refs file might contain broken references, for
+ * example an old version of a reference that points at an
+ * object that has since been garbage-collected. This is OK as
+ * long as there is a corresponding loose reference that
+ * overrides it, and we don't want to emit an error message in
+ * this case. So ask the packed_ref_store for all of its
+ * references, and (if needed) do our own check for broken
+ * ones in files_ref_iterator_advance(), after we have merged
+ * the packed and loose references.
+ */
+ packed_iter = refs_ref_iterator_begin(
+ refs->packed_ref_store, prefix, 0,
+ DO_FOR_EACH_INCLUDE_BROKEN);
iter->iter0 = overlay_ref_iterator_begin(loose_iter, packed_iter);
iter->flags = flags;
{
struct lock_file *lk = cb;
- return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0;
+ return hold_lock_file_for_update_timeout(
+ lk, path, LOCK_NO_DEREF,
+ get_files_ref_lock_timeout_ms()) < 0 ? -1 : 0;
}
/*
* our refname.
*/
if (is_null_oid(&lock->old_oid) &&
- refs_verify_refname_available(&refs->base, refname,
+ refs_verify_refname_available(refs->packed_ref_store, refname,
extras, skip, err)) {
last_errno = ENOTDIR;
goto error_return;
}
- lock->lk = xcalloc(1, sizeof(struct lock_file));
-
lock->ref_name = xstrdup(refname);
- if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) {
+ if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) {
last_errno = errno;
unable_to_lock_message(ref_file.buf, errno, err);
goto error_return;
return lock;
}
-/*
- * Write an entry to the packed-refs file for the specified refname.
- * If peeled is non-NULL, write it as the entry's peeled value.
- */
-static void write_packed_entry(FILE *fh, const char *refname,
- const unsigned char *sha1,
- const unsigned char *peeled)
-{
- fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname);
- if (peeled)
- fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled));
-}
-
-/*
- * Lock the packed-refs file for writing. Flags is passed to
- * hold_lock_file_for_update(). Return 0 on success. On errors, set
- * errno appropriately and return a nonzero value.
- */
-static int lock_packed_refs(struct files_ref_store *refs, int flags)
-{
- static int timeout_configured = 0;
- static int timeout_value = 1000;
- struct packed_ref_cache *packed_ref_cache;
-
- files_assert_main_repository(refs, "lock_packed_refs");
-
- if (!timeout_configured) {
- git_config_get_int("core.packedrefstimeout", &timeout_value);
- timeout_configured = 1;
- }
-
- if (hold_lock_file_for_update_timeout(
- &refs->packed_refs_lock, files_packed_refs_path(refs),
- flags, timeout_value) < 0)
- return -1;
- /*
- * Get the current packed-refs while holding the lock. It is
- * important that we call `get_packed_ref_cache()` before
- * setting `packed_ref_cache->lock`, because otherwise the
- * former will see that the file is locked and assume that the
- * cache can't be stale.
- */
- packed_ref_cache = get_packed_ref_cache(refs);
- /* Increment the reference count to prevent it from being freed: */
- acquire_packed_ref_cache(packed_ref_cache);
- return 0;
-}
-
-/*
- * Write the current version of the packed refs cache from memory to
- * disk. The packed-refs file must already be locked for writing (see
- * lock_packed_refs()). Return zero on success. On errors, set errno
- * and return a nonzero value
- */
-static int commit_packed_refs(struct files_ref_store *refs)
-{
- struct packed_ref_cache *packed_ref_cache =
- get_packed_ref_cache(refs);
- int ok, error = 0;
- int save_errno = 0;
- FILE *out;
- struct ref_iterator *iter;
-
- files_assert_main_repository(refs, "commit_packed_refs");
-
- if (!is_lock_file_locked(&refs->packed_refs_lock))
- die("BUG: packed-refs not locked");
-
- out = fdopen_lock_file(&refs->packed_refs_lock, "w");
- if (!out)
- die_errno("unable to fdopen packed-refs descriptor");
-
- fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
-
- iter = cache_ref_iterator_begin(packed_ref_cache->cache, NULL, 0);
- while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
- struct object_id peeled;
- int peel_error = ref_iterator_peel(iter, &peeled);
-
- write_packed_entry(out, iter->refname, iter->oid->hash,
- peel_error ? NULL : peeled.hash);
- }
-
- if (ok != ITER_DONE)
- die("error while iterating over references");
-
- if (commit_lock_file(&refs->packed_refs_lock)) {
- save_errno = errno;
- error = -1;
- }
- release_packed_ref_cache(packed_ref_cache);
- errno = save_errno;
- return error;
-}
-
-/*
- * Rollback the lockfile for the packed-refs file, and discard the
- * in-memory packed reference cache. (The packed-refs file will be
- * read anew if it is needed again after this function is called.)
- */
-static void rollback_packed_refs(struct files_ref_store *refs)
-{
- struct packed_ref_cache *packed_ref_cache =
- get_packed_ref_cache(refs);
-
- files_assert_main_repository(refs, "rollback_packed_refs");
-
- if (!is_lock_file_locked(&refs->packed_refs_lock))
- die("BUG: packed-refs not locked");
- rollback_lock_file(&refs->packed_refs_lock);
- release_packed_ref_cache(packed_ref_cache);
- clear_packed_ref_cache(refs);
-}
-
struct ref_to_prune {
struct ref_to_prune *next;
unsigned char sha1[20];
strbuf_release(&err);
}
-static void prune_refs(struct files_ref_store *refs, struct ref_to_prune *r)
+/*
+ * Prune the loose versions of the references in the linked list
+ * `*refs_to_prune`, freeing the entries in the list as we go.
+ */
+static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_to_prune)
{
- while (r) {
+ while (*refs_to_prune) {
+ struct ref_to_prune *r = *refs_to_prune;
+ *refs_to_prune = r->next;
prune_ref(refs, r);
- r = r->next;
+ free(r);
}
}
files_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB,
"pack_refs");
struct ref_iterator *iter;
- struct ref_dir *packed_refs;
int ok;
struct ref_to_prune *refs_to_prune = NULL;
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction;
+
+ transaction = ref_store_transaction_begin(refs->packed_ref_store, &err);
+ if (!transaction)
+ return -1;
- lock_packed_refs(refs, LOCK_DIE_ON_ERROR);
- packed_refs = get_packed_refs(refs);
+ packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err);
iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL, 0);
while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
* in the packed ref cache. If the reference should be
* pruned, also add it to refs_to_prune.
*/
- struct ref_entry *packed_entry;
-
if (!should_pack_ref(iter->refname, iter->oid, iter->flags,
flags))
continue;
/*
- * Create an entry in the packed-refs cache equivalent
- * to the one from the loose ref cache, except that
- * we don't copy the peeled status, because we want it
- * to be re-peeled.
+ * Add a reference creation for this reference to the
+ * packed-refs transaction:
*/
- packed_entry = find_ref_entry(packed_refs, iter->refname);
- if (packed_entry) {
- /* Overwrite existing packed entry with info from loose entry */
- packed_entry->flag = REF_ISPACKED;
- oidcpy(&packed_entry->u.value.oid, iter->oid);
- } else {
- packed_entry = create_ref_entry(iter->refname, iter->oid,
- REF_ISPACKED);
- add_ref_entry(packed_refs, packed_entry);
- }
- oidclr(&packed_entry->u.value.peeled);
+ if (ref_transaction_update(transaction, iter->refname,
+ iter->oid->hash, NULL,
+ REF_NODEREF, NULL, &err))
+ die("failure preparing to create packed reference %s: %s",
+ iter->refname, err.buf);
/* Schedule the loose reference for pruning if requested. */
if ((flags & PACK_REFS_PRUNE)) {
if (ok != ITER_DONE)
die("error while iterating over references");
- if (commit_packed_refs(refs))
- die_errno("unable to overwrite old ref-pack file");
-
- prune_refs(refs, refs_to_prune);
- return 0;
-}
-
-/*
- * Rewrite the packed-refs file, omitting any refs listed in
- * 'refnames'. On error, leave packed-refs unchanged, write an error
- * message to 'err', and return a nonzero value.
- *
- * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
- */
-static int repack_without_refs(struct files_ref_store *refs,
- struct string_list *refnames, struct strbuf *err)
-{
- struct ref_dir *packed;
- struct string_list_item *refname;
- int ret, needs_repacking = 0, removed = 0;
-
- files_assert_main_repository(refs, "repack_without_refs");
- assert(err);
-
- /* Look for a packed ref */
- for_each_string_list_item(refname, refnames) {
- if (get_packed_ref(refs, refname->string)) {
- needs_repacking = 1;
- break;
- }
- }
-
- /* Avoid locking if we have nothing to do */
- if (!needs_repacking)
- return 0; /* no refname exists in packed refs */
+ if (ref_transaction_commit(transaction, &err))
+ die("unable to write new packed-refs: %s", err.buf);
- if (lock_packed_refs(refs, 0)) {
- unable_to_lock_message(files_packed_refs_path(refs), errno, err);
- return -1;
- }
- packed = get_packed_refs(refs);
+ ref_transaction_free(transaction);
- /* Remove refnames from the cache */
- for_each_string_list_item(refname, refnames)
- if (remove_entry_from_dir(packed, refname->string) != -1)
- removed = 1;
- if (!removed) {
- /*
- * All packed entries disappeared while we were
- * acquiring the lock.
- */
- rollback_packed_refs(refs);
- return 0;
- }
+ packed_refs_unlock(refs->packed_ref_store);
- /* Write what remains */
- ret = commit_packed_refs(refs);
- if (ret)
- strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
- strerror(errno));
- return ret;
+ prune_refs(refs, &refs_to_prune);
+ strbuf_release(&err);
+ return 0;
}
static int files_delete_refs(struct ref_store *ref_store, const char *msg,
if (!refnames->nr)
return 0;
- result = repack_without_refs(refs, refnames, &err);
- if (result) {
- /*
- * If we failed to rewrite the packed-refs file, then
- * it is unsafe to try to remove loose refs, because
- * doing so might expose an obsolete packed value for
- * a reference that might even point at an object that
- * has been garbage collected.
- */
- if (refnames->nr == 1)
- error(_("could not delete reference %s: %s"),
- refnames->items[0].string, err.buf);
- else
- error(_("could not delete references: %s"), err.buf);
+ if (packed_refs_lock(refs->packed_ref_store, 0, &err))
+ goto error;
- goto out;
+ if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) {
+ packed_refs_unlock(refs->packed_ref_store);
+ goto error;
}
+ packed_refs_unlock(refs->packed_ref_store);
+
for (i = 0; i < refnames->nr; i++) {
const char *refname = refnames->items[i].string;
result |= error(_("could not remove reference %s"), refname);
}
-out:
strbuf_release(&err);
return result;
+
+error:
+ /*
+ * If we failed to rewrite the packed-refs file, then it is
+ * unsafe to try to remove loose refs, because doing so might
+ * expose an obsolete packed value for a reference that might
+ * even point at an object that has been garbage collected.
+ */
+ if (refnames->nr == 1)
+ error(_("could not delete reference %s: %s"),
+ refnames->items[0].string, err.buf);
+ else
+ error(_("could not delete references: %s"), err.buf);
+
+ strbuf_release(&err);
+ return -1;
}
/*
const struct object_id *oid, const char *logmsg,
struct strbuf *err);
- static int files_rename_ref(struct ref_store *ref_store,
+ static int files_copy_or_rename_ref(struct ref_store *ref_store,
const char *oldrefname, const char *newrefname,
- const char *logmsg)
+ const char *logmsg, int copy)
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "rename_ref");
}
if (flag & REF_ISSYMREF) {
- ret = error("refname %s is a symbolic ref, renaming it is not supported",
- oldrefname);
+ if (copy)
+ ret = error("refname %s is a symbolic ref, copying it is not supported",
+ oldrefname);
+ else
+ ret = error("refname %s is a symbolic ref, renaming it is not supported",
+ oldrefname);
goto out;
}
if (!refs_rename_ref_available(&refs->base, oldrefname, newrefname)) {
goto out;
}
- if (log && rename(sb_oldref.buf, tmp_renamed_log.buf)) {
+ if (!copy && log && rename(sb_oldref.buf, tmp_renamed_log.buf)) {
ret = error("unable to move logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
oldrefname, strerror(errno));
goto out;
}
- if (refs_delete_ref(&refs->base, logmsg, oldrefname,
+ if (copy && log && copy_file(tmp_renamed_log.buf, sb_oldref.buf, 0644)) {
+ ret = error("unable to copy logfile logs/%s to logs/"TMP_RENAMED_LOG": %s",
+ oldrefname, strerror(errno));
+ goto out;
+ }
+
+ if (!copy && refs_delete_ref(&refs->base, logmsg, oldrefname,
orig_oid.hash, REF_NODEREF)) {
error("unable to delete old %s", oldrefname);
goto rollback;
* the safety anyway; we want to delete the reference whatever
* its current value.
*/
- if (!refs_read_ref_full(&refs->base, newrefname,
+ if (!copy && !refs_read_ref_full(&refs->base, newrefname,
RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
oid.hash, NULL) &&
refs_delete_ref(&refs->base, NULL, newrefname,
lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL,
REF_NODEREF, NULL, &err);
if (!lock) {
- error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
+ if (copy)
+ error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
+ else
+ error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
strbuf_release(&err);
goto rollback;
}
return ret;
}
-static int close_ref(struct ref_lock *lock)
+ static int files_rename_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
+ {
+ return files_copy_or_rename_ref(ref_store, oldrefname,
+ newrefname, logmsg, 0);
+ }
+
+ static int files_copy_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
+ {
+ return files_copy_or_rename_ref(ref_store, oldrefname,
+ newrefname, logmsg, 1);
+ }
+
+static int close_ref_gently(struct ref_lock *lock)
{
- if (close_lock_file(lock->lk))
+ if (close_lock_file_gently(&lock->lk))
return -1;
return 0;
}
static int commit_ref(struct ref_lock *lock)
{
- char *path = get_locked_file_path(lock->lk);
+ char *path = get_locked_file_path(&lock->lk);
struct stat st;
if (!lstat(path, &st) && S_ISDIR(st.st_mode)) {
free(path);
}
- if (commit_lock_file(lock->lk))
+ if (commit_lock_file(&lock->lk))
return -1;
return 0;
}
written = len <= maxlen ? write_in_full(fd, logrec, len) : -1;
free(logrec);
- if (written != len)
+ if (written < 0)
return -1;
return 0;
unlock_ref(lock);
return -1;
}
- fd = get_lock_file_fd(lock->lk);
- if (write_in_full(fd, oid_to_hex(oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ ||
- write_in_full(fd, &term, 1) != 1 ||
- close_ref(lock) < 0) {
+ fd = get_lock_file_fd(&lock->lk);
+ if (write_in_full(fd, oid_to_hex(oid), GIT_SHA1_HEXSZ) < 0 ||
+ write_in_full(fd, &term, 1) < 0 ||
+ close_ref_gently(lock) < 0) {
strbuf_addf(err,
- "couldn't write '%s'", get_lock_file_path(lock->lk));
+ "couldn't write '%s'", get_lock_file_path(&lock->lk));
unlock_ref(lock);
return -1;
}
* check with HEAD only which should cover 99% of all usage
* scenarios (even 100% of the default ones).
*/
- struct object_id head_oid;
int head_flag;
const char *head_ref;
head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD",
RESOLVE_REF_READING,
- head_oid.hash, &head_flag);
+ NULL, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name)) {
struct strbuf log_err = STRBUF_INIT;
{
int ret = -1;
#ifndef NO_SYMLINK_HEAD
- char *ref_path = get_locked_file_path(lock->lk);
+ char *ref_path = get_locked_file_path(&lock->lk);
unlink(ref_path);
ret = symlink(target, ref_path);
free(ref_path);
return 0;
}
- if (!fdopen_lock_file(lock->lk, "w"))
+ if (!fdopen_lock_file(&lock->lk, "w"))
return error("unable to fdopen %s: %s",
- lock->lk->tempfile.filename.buf, strerror(errno));
+ lock->lk.tempfile->filename.buf, strerror(errno));
update_symref_reflog(refs, lock, refname, target, logmsg);
/* no error check; commit_ref will check ferror */
- fprintf(lock->lk->tempfile.fp, "ref: %s\n", target);
+ fprintf(lock->lk.tempfile->fp, "ref: %s\n", target);
if (commit_ref(lock) < 0)
return error("unable to write symref for %s: %s", refname,
strerror(errno));
files_reflog_iterator_abort
};
-static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store)
+static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store,
+ const char *gitdir)
{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_READ,
- "reflog_iterator_begin");
struct files_reflog_iterator *iter = xcalloc(1, sizeof(*iter));
struct ref_iterator *ref_iterator = &iter->base;
struct strbuf sb = STRBUF_INIT;
base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable);
- files_reflog_path(refs, &sb, NULL);
+ strbuf_addf(&sb, "%s/logs", gitdir);
iter->dir_iterator = dir_iterator_begin(sb.buf);
iter->ref_store = ref_store;
strbuf_release(&sb);
+
return ref_iterator;
}
+static enum iterator_selection reflog_iterator_select(
+ struct ref_iterator *iter_worktree,
+ struct ref_iterator *iter_common,
+ void *cb_data)
+{
+ if (iter_worktree) {
+ /*
+ * We're a bit loose here. We probably should ignore
+ * common refs if they are accidentally added as
+ * per-worktree refs.
+ */
+ return ITER_SELECT_0;
+ } else if (iter_common) {
+ if (ref_type(iter_common->refname) == REF_TYPE_NORMAL)
+ return ITER_SELECT_1;
+
+ /*
+ * The main ref store may contain main worktree's
+ * per-worktree refs, which should be ignored
+ */
+ return ITER_SKIP_1;
+ } else
+ return ITER_DONE;
+}
+
+static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store)
+{
+ struct files_ref_store *refs =
+ files_downcast(ref_store, REF_STORE_READ,
+ "reflog_iterator_begin");
+
+ if (!strcmp(refs->gitdir, refs->gitcommondir)) {
+ return reflog_iterator_begin(ref_store, refs->gitcommondir);
+ } else {
+ return merge_ref_iterator_begin(
+ reflog_iterator_begin(ref_store, refs->gitdir),
+ reflog_iterator_begin(ref_store, refs->gitcommondir),
+ reflog_iterator_select, refs);
+ }
+}
+
/*
* If update is a direct update of head_ref (the reference pointed to
* by HEAD), then add an extra REF_LOG_ONLY update for HEAD.
/*
* First make sure that HEAD is not already in the
- * transaction. This insertion is O(N) in the transaction
+ * transaction. This check is O(lg N) in the transaction
* size, but it happens at most once per transaction.
*/
- item = string_list_insert(affected_refnames, "HEAD");
- if (item->util) {
+ if (string_list_has_string(affected_refnames, "HEAD")) {
/* An entry already existed */
strbuf_addf(err,
"multiple updates for 'HEAD' (including one "
update->new_oid.hash, update->old_oid.hash,
update->msg);
+ /*
+ * Add "HEAD". This insertion is O(N) in the transaction
+ * size, but it happens at most once per transaction.
+ * Add new_update->refname instead of a literal "HEAD".
+ */
+ if (strcmp(new_update->refname, "HEAD"))
+ BUG("%s unexpectedly not 'HEAD'", new_update->refname);
+ item = string_list_insert(affected_refnames, new_update->refname);
item->util = new_update;
return 0;
/*
* First make sure that referent is not already in the
- * transaction. This insertion is O(N) in the transaction
+ * transaction. This check is O(lg N) in the transaction
* size, but it happens at most once per symref in a
* transaction.
*/
- item = string_list_insert(affected_refnames, referent);
- if (item->util) {
- /* An entry already existed */
+ if (string_list_has_string(affected_refnames, referent)) {
+ /* An entry already exists */
strbuf_addf(err,
"multiple updates for '%s' (including one "
"via symref '%s') are not allowed",
update->flags |= REF_LOG_ONLY | REF_NODEREF;
update->flags &= ~REF_HAVE_OLD;
+ /*
+ * Add the referent. This insertion is O(N) in the transaction
+ * size, but it happens at most once per symref in a
+ * transaction. Make sure to add new_update->refname, which will
+ * be valid as long as affected_refnames is in use, and NOT
+ * referent, which might soon be freed by our caller.
+ */
+ item = string_list_insert(affected_refnames, new_update->refname);
+ if (item->util)
+ BUG("%s unexpectedly found in affected_refnames",
+ new_update->refname);
item->util = new_update;
return 0;
struct strbuf referent = STRBUF_INIT;
int mustexist = (update->flags & REF_HAVE_OLD) &&
!is_null_oid(&update->old_oid);
- int ret;
+ int ret = 0;
struct ref_lock *lock;
files_assert_main_repository(refs, "lock_ref_for_update");
ret = split_head_update(update, transaction, head_ref,
affected_refnames, err);
if (ret)
- return ret;
+ goto out;
}
ret = lock_raw_ref(refs, update->refname, mustexist,
strbuf_addf(err, "cannot lock ref '%s': %s",
original_update_refname(update), reason);
free(reason);
- return ret;
+ goto out;
}
update->backend_data = lock;
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
original_update_refname(update));
- return -1;
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
}
} else if (check_old_oid(update, &lock->old_oid, err)) {
- return TRANSACTION_GENERIC_ERROR;
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
}
} else {
/*
referent.buf, transaction,
affected_refnames, err);
if (ret)
- return ret;
+ goto out;
}
} else {
struct ref_update *parent_update;
- if (check_old_oid(update, &lock->old_oid, err))
- return TRANSACTION_GENERIC_ERROR;
+ if (check_old_oid(update, &lock->old_oid, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
/*
* If this update is happening indirectly because of a
"cannot update ref '%s': %s",
update->refname, write_err);
free(write_err);
- return TRANSACTION_GENERIC_ERROR;
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
} else {
update->flags |= REF_NEEDS_COMMIT;
}
* the lockfile is still open. Close it to
* free up the file descriptor:
*/
- if (close_ref(lock)) {
+ if (close_ref_gently(lock)) {
strbuf_addf(err, "couldn't close '%s.lock'",
update->refname);
- return TRANSACTION_GENERIC_ERROR;
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
}
}
- return 0;
+
+out:
+ strbuf_release(&referent);
+ return ret;
}
+struct files_transaction_backend_data {
+ struct ref_transaction *packed_transaction;
+ int packed_refs_locked;
+};
+
/*
* Unlock any references in `transaction` that are still locked, and
* mark the transaction closed.
*/
-static void files_transaction_cleanup(struct ref_transaction *transaction)
+static void files_transaction_cleanup(struct files_ref_store *refs,
+ struct ref_transaction *transaction)
{
size_t i;
+ struct files_transaction_backend_data *backend_data =
+ transaction->backend_data;
+ struct strbuf err = STRBUF_INIT;
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
}
}
+ if (backend_data->packed_transaction &&
+ ref_transaction_abort(backend_data->packed_transaction, &err)) {
+ error("error aborting transaction: %s", err.buf);
+ strbuf_release(&err);
+ }
+
+ if (backend_data->packed_refs_locked)
+ packed_refs_unlock(refs->packed_ref_store);
+
+ free(backend_data);
+
transaction->state = REF_TRANSACTION_CLOSED;
}
char *head_ref = NULL;
int head_type;
struct object_id head_oid;
+ struct files_transaction_backend_data *backend_data;
+ struct ref_transaction *packed_transaction = NULL;
assert(err);
if (!transaction->nr)
goto cleanup;
+ backend_data = xcalloc(1, sizeof(*backend_data));
+ transaction->backend_data = backend_data;
+
/*
* Fail if a refname appears more than once in the
* transaction. (If we end up splitting up any updates using
head_oid.hash, &head_type);
if (head_ref && !(head_type & REF_ISSYMREF)) {
- free(head_ref);
- head_ref = NULL;
+ FREE_AND_NULL(head_ref);
}
/*
head_ref, &affected_refnames, err);
if (ret)
break;
+
+ if (update->flags & REF_DELETING &&
+ !(update->flags & REF_LOG_ONLY) &&
+ !(update->flags & REF_ISPRUNING)) {
+ /*
+ * This reference has to be deleted from
+ * packed-refs if it exists there.
+ */
+ if (!packed_transaction) {
+ packed_transaction = ref_store_transaction_begin(
+ refs->packed_ref_store, err);
+ if (!packed_transaction) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+
+ backend_data->packed_transaction =
+ packed_transaction;
+ }
+
+ ref_transaction_add_update(
+ packed_transaction, update->refname,
+ update->flags & ~REF_HAVE_OLD,
+ update->new_oid.hash, update->old_oid.hash,
+ NULL);
+ }
+ }
+
+ if (packed_transaction) {
+ if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+ backend_data->packed_refs_locked = 1;
+ ret = ref_transaction_prepare(packed_transaction, err);
}
cleanup:
string_list_clear(&affected_refnames, 0);
if (ret)
- files_transaction_cleanup(transaction);
+ files_transaction_cleanup(refs, transaction);
else
transaction->state = REF_TRANSACTION_PREPARED;
files_downcast(ref_store, 0, "ref_transaction_finish");
size_t i;
int ret = 0;
- struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
- struct string_list_item *ref_to_delete;
struct strbuf sb = STRBUF_INIT;
+ struct files_transaction_backend_data *backend_data;
+ struct ref_transaction *packed_transaction;
+
assert(err);
return 0;
}
+ backend_data = transaction->backend_data;
+ packed_transaction = backend_data->packed_transaction;
+
/* Perform updates first so live commits remain referenced */
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
}
}
}
- /* Perform deletes now that updates are safely completed */
+
+ /*
+ * Now that updates are safely completed, we can perform
+ * deletes. First delete the reflogs of any references that
+ * will be deleted, since (in the unexpected event of an
+ * error) leaving a reference without a reflog is less bad
+ * than leaving a reflog without a reference (the latter is a
+ * mildly invalid repository state):
+ */
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
+ if (update->flags & REF_DELETING &&
+ !(update->flags & REF_LOG_ONLY) &&
+ !(update->flags & REF_ISPRUNING)) {
+ strbuf_reset(&sb);
+ files_reflog_path(refs, &sb, update->refname);
+ if (!unlink_or_warn(sb.buf))
+ try_remove_empty_parents(refs, update->refname,
+ REMOVE_EMPTY_PARENTS_REFLOG);
+ }
+ }
+
+ /*
+ * Perform deletes now that updates are safely completed.
+ *
+ * First delete any packed versions of the references, while
+ * retaining the packed-refs lock:
+ */
+ if (packed_transaction) {
+ ret = ref_transaction_commit(packed_transaction, err);
+ ref_transaction_free(packed_transaction);
+ packed_transaction = NULL;
+ backend_data->packed_transaction = NULL;
+ if (ret)
+ goto cleanup;
+ }
+
+ /* Now delete the loose versions of the references: */
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
struct ref_lock *lock = update->backend_data;
}
update->flags |= REF_DELETED_LOOSE;
}
-
- if (!(update->flags & REF_ISPRUNING))
- string_list_append(&refs_to_delete,
- lock->ref_name);
}
}
- if (repack_without_refs(refs, &refs_to_delete, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto cleanup;
- }
-
- /* Delete the reflogs of any references that were deleted: */
- for_each_string_list_item(ref_to_delete, &refs_to_delete) {
- strbuf_reset(&sb);
- files_reflog_path(refs, &sb, ref_to_delete->string);
- if (!unlink_or_warn(sb.buf))
- try_remove_empty_parents(refs, ref_to_delete->string,
- REMOVE_EMPTY_PARENTS_REFLOG);
- }
-
clear_loose_ref_cache(refs);
cleanup:
- files_transaction_cleanup(transaction);
+ files_transaction_cleanup(refs, transaction);
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
}
strbuf_release(&sb);
- string_list_clear(&refs_to_delete, 0);
return ret;
}
struct ref_transaction *transaction,
struct strbuf *err)
{
- files_transaction_cleanup(transaction);
+ struct files_ref_store *refs =
+ files_downcast(ref_store, 0, "ref_transaction_abort");
+
+ files_transaction_cleanup(refs, transaction);
return 0;
}
size_t i;
int ret = 0;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
+ struct ref_transaction *packed_transaction = NULL;
assert(err);
&affected_refnames))
die("BUG: initial ref transaction called with existing refs");
+ packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err);
+ if (!packed_transaction) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
ret = TRANSACTION_NAME_CONFLICT;
goto cleanup;
}
+
+ /*
+ * Add a reference creation for this reference to the
+ * packed-refs transaction:
+ */
+ ref_transaction_add_update(packed_transaction, update->refname,
+ update->flags & ~REF_HAVE_OLD,
+ update->new_oid.hash, update->old_oid.hash,
+ NULL);
}
- if (lock_packed_refs(refs, 0)) {
- strbuf_addf(err, "unable to lock packed-refs file: %s",
- strerror(errno));
+ if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
- for (i = 0; i < transaction->nr; i++) {
- struct ref_update *update = transaction->updates[i];
-
- if ((update->flags & REF_HAVE_NEW) &&
- !is_null_oid(&update->new_oid))
- add_packed_ref(refs, update->refname,
- &update->new_oid);
- }
-
- if (commit_packed_refs(refs)) {
- strbuf_addf(err, "unable to commit packed-refs file: %s",
- strerror(errno));
+ if (initial_ref_transaction_commit(packed_transaction, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
cleanup:
+ if (packed_transaction)
+ ref_transaction_free(packed_transaction);
+ packed_refs_unlock(refs->packed_ref_store);
transaction->state = REF_TRANSACTION_CLOSED;
string_list_clear(&affected_refnames, 0);
return ret;
!(type & REF_ISSYMREF) &&
!is_null_oid(&cb.last_kept_oid);
- if (close_lock_file(&reflog_lock)) {
+ if (close_lock_file_gently(&reflog_lock)) {
status |= error("couldn't write %s: %s", log_file,
strerror(errno));
+ rollback_lock_file(&reflog_lock);
} else if (update &&
- (write_in_full(get_lock_file_fd(lock->lk),
- oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ ||
- write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 ||
- close_ref(lock) < 0)) {
+ (write_in_full(get_lock_file_fd(&lock->lk),
+ oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) < 0 ||
+ write_str_in_full(get_lock_file_fd(&lock->lk), "\n") < 1 ||
+ close_ref_gently(lock) < 0)) {
status |= error("couldn't write %s",
- get_lock_file_path(lock->lk));
+ get_lock_file_path(&lock->lk));
rollback_lock_file(&reflog_lock);
} else if (commit_lock_file(&reflog_lock)) {
status |= error("unable to write reflog '%s' (%s)",
files_create_symref,
files_delete_refs,
files_rename_ref,
+ files_copy_ref,
files_ref_iterator_begin,
files_read_raw_ref,
--- /dev/null
+#include "../cache.h"
+#include "../config.h"
+#include "../refs.h"
+#include "refs-internal.h"
+#include "ref-cache.h"
+#include "packed-backend.h"
+#include "../iterator.h"
+#include "../lockfile.h"
+
+struct packed_ref_cache {
+ struct ref_cache *cache;
+
+ /*
+ * Count of references to the data structure in this instance,
+ * including the pointer from files_ref_store::packed if any.
+ * The data will not be freed as long as the reference count
+ * is nonzero.
+ */
+ unsigned int referrers;
+
+ /* The metadata from when this packed-refs cache was read */
+ struct stat_validity validity;
+};
+
+/*
+ * Increment the reference count of *packed_refs.
+ */
+static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs)
+{
+ packed_refs->referrers++;
+}
+
+/*
+ * Decrease the reference count of *packed_refs. If it goes to zero,
+ * free *packed_refs and return true; otherwise return false.
+ */
+static int release_packed_ref_cache(struct packed_ref_cache *packed_refs)
+{
+ if (!--packed_refs->referrers) {
+ free_ref_cache(packed_refs->cache);
+ stat_validity_clear(&packed_refs->validity);
+ free(packed_refs);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*
+ * A container for `packed-refs`-related data. It is not (yet) a
+ * `ref_store`.
+ */
+struct packed_ref_store {
+ struct ref_store base;
+
+ unsigned int store_flags;
+
+ /* The path of the "packed-refs" file: */
+ char *path;
+
+ /*
+ * A cache of the values read from the `packed-refs` file, if
+ * it might still be current; otherwise, NULL.
+ */
+ struct packed_ref_cache *cache;
+
+ /*
+ * Lock used for the "packed-refs" file. Note that this (and
+ * thus the enclosing `packed_ref_store`) must not be freed.
+ */
+ struct lock_file lock;
+
+ /*
+ * Temporary file used when rewriting new contents to the
+ * "packed-refs" file. Note that this (and thus the enclosing
+ * `packed_ref_store`) must not be freed.
+ */
+ struct tempfile *tempfile;
+};
+
+struct ref_store *packed_ref_store_create(const char *path,
+ unsigned int store_flags)
+{
+ struct packed_ref_store *refs = xcalloc(1, sizeof(*refs));
+ struct ref_store *ref_store = (struct ref_store *)refs;
+
+ base_ref_store_init(ref_store, &refs_be_packed);
+ refs->store_flags = store_flags;
+
+ refs->path = xstrdup(path);
+ return ref_store;
+}
+
+/*
+ * Downcast `ref_store` to `packed_ref_store`. Die if `ref_store` is
+ * not a `packed_ref_store`. Also die if `packed_ref_store` doesn't
+ * support at least the flags specified in `required_flags`. `caller`
+ * is used in any necessary error messages.
+ */
+static struct packed_ref_store *packed_downcast(struct ref_store *ref_store,
+ unsigned int required_flags,
+ const char *caller)
+{
+ struct packed_ref_store *refs;
+
+ if (ref_store->be != &refs_be_packed)
+ die("BUG: ref_store is type \"%s\" not \"packed\" in %s",
+ ref_store->be->name, caller);
+
+ refs = (struct packed_ref_store *)ref_store;
+
+ if ((refs->store_flags & required_flags) != required_flags)
+ die("BUG: unallowed operation (%s), requires %x, has %x\n",
+ caller, required_flags, refs->store_flags);
+
+ return refs;
+}
+
+static void clear_packed_ref_cache(struct packed_ref_store *refs)
+{
+ if (refs->cache) {
+ struct packed_ref_cache *cache = refs->cache;
+
+ refs->cache = NULL;
+ release_packed_ref_cache(cache);
+ }
+}
+
+/* The length of a peeled reference line in packed-refs, including EOL: */
+#define PEELED_LINE_LENGTH 42
+
+/*
+ * Parse one line from a packed-refs file. Write the SHA1 to sha1.
+ * Return a pointer to the refname within the line (null-terminated),
+ * or NULL if there was a problem.
+ */
+static const char *parse_ref_line(struct strbuf *line, struct object_id *oid)
+{
+ const char *ref;
+
+ if (parse_oid_hex(line->buf, oid, &ref) < 0)
+ return NULL;
+ if (!isspace(*ref++))
+ return NULL;
+
+ if (isspace(*ref))
+ return NULL;
+
+ if (line->buf[line->len - 1] != '\n')
+ return NULL;
+ line->buf[--line->len] = 0;
+
+ return ref;
+}
+
+/*
+ * Read from `packed_refs_file` into a newly-allocated
+ * `packed_ref_cache` and return it. The return value will already
+ * have its reference count incremented.
+ *
+ * A comment line of the form "# pack-refs with: " may contain zero or
+ * more traits. We interpret the traits as follows:
+ *
+ * No traits:
+ *
+ * Probably no references are peeled. But if the file contains a
+ * peeled value for a reference, we will use it.
+ *
+ * peeled:
+ *
+ * References under "refs/tags/", if they *can* be peeled, *are*
+ * peeled in this file. References outside of "refs/tags/" are
+ * probably not peeled even if they could have been, but if we find
+ * a peeled value for such a reference we will use it.
+ *
+ * fully-peeled:
+ *
+ * All references in the file that can be peeled are peeled.
+ * Inversely (and this is more important), any references in the
+ * file for which no peeled value is recorded is not peelable. This
+ * trait should typically be written alongside "peeled" for
+ * compatibility with older clients, but we do not require it
+ * (i.e., "peeled" is a no-op if "fully-peeled" is set).
+ */
+static struct packed_ref_cache *read_packed_refs(const char *packed_refs_file)
+{
+ FILE *f;
+ struct packed_ref_cache *packed_refs = xcalloc(1, sizeof(*packed_refs));
+ struct ref_entry *last = NULL;
+ struct strbuf line = STRBUF_INIT;
+ enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
+ struct ref_dir *dir;
+
+ acquire_packed_ref_cache(packed_refs);
+ packed_refs->cache = create_ref_cache(NULL, NULL);
+ packed_refs->cache->root->flag &= ~REF_INCOMPLETE;
+
+ f = fopen(packed_refs_file, "r");
+ if (!f) {
+ if (errno == ENOENT) {
+ /*
+ * This is OK; it just means that no
+ * "packed-refs" file has been written yet,
+ * which is equivalent to it being empty.
+ */
+ return packed_refs;
+ } else {
+ die_errno("couldn't read %s", packed_refs_file);
+ }
+ }
+
+ stat_validity_update(&packed_refs->validity, fileno(f));
+
+ dir = get_ref_dir(packed_refs->cache->root);
+ while (strbuf_getwholeline(&line, f, '\n') != EOF) {
+ struct object_id oid;
+ const char *refname;
+ const char *traits;
+
+ if (!line.len || line.buf[line.len - 1] != '\n')
+ die("unterminated line in %s: %s", packed_refs_file, line.buf);
+
+ if (skip_prefix(line.buf, "# pack-refs with:", &traits)) {
+ if (strstr(traits, " fully-peeled "))
+ peeled = PEELED_FULLY;
+ else if (strstr(traits, " peeled "))
+ peeled = PEELED_TAGS;
+ /* perhaps other traits later as well */
+ continue;
+ }
+
+ refname = parse_ref_line(&line, &oid);
+ if (refname) {
+ int flag = REF_ISPACKED;
+
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ if (!refname_is_safe(refname))
+ die("packed refname is dangerous: %s", refname);
+ oidclr(&oid);
+ flag |= REF_BAD_NAME | REF_ISBROKEN;
+ }
+ last = create_ref_entry(refname, &oid, flag);
+ if (peeled == PEELED_FULLY ||
+ (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
+ last->flag |= REF_KNOWS_PEELED;
+ add_ref_entry(dir, last);
+ } else if (last &&
+ line.buf[0] == '^' &&
+ line.len == PEELED_LINE_LENGTH &&
+ line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
+ !get_oid_hex(line.buf + 1, &oid)) {
+ oidcpy(&last->u.value.peeled, &oid);
+ /*
+ * Regardless of what the file header said,
+ * we definitely know the value of *this*
+ * reference:
+ */
+ last->flag |= REF_KNOWS_PEELED;
+ } else {
+ strbuf_setlen(&line, line.len - 1);
+ die("unexpected line in %s: %s", packed_refs_file, line.buf);
+ }
+ }
+
+ fclose(f);
+ strbuf_release(&line);
+
+ return packed_refs;
+}
+
+/*
+ * Check that the packed refs cache (if any) still reflects the
+ * contents of the file. If not, clear the cache.
+ */
+static void validate_packed_ref_cache(struct packed_ref_store *refs)
+{
+ if (refs->cache &&
+ !stat_validity_check(&refs->cache->validity, refs->path))
+ clear_packed_ref_cache(refs);
+}
+
+/*
+ * Get the packed_ref_cache for the specified packed_ref_store,
+ * creating and populating it if it hasn't been read before or if the
+ * file has been changed (according to its `validity` field) since it
+ * was last read. On the other hand, if we hold the lock, then assume
+ * that the file hasn't been changed out from under us, so skip the
+ * extra `stat()` call in `stat_validity_check()`.
+ */
+static struct packed_ref_cache *get_packed_ref_cache(struct packed_ref_store *refs)
+{
+ if (!is_lock_file_locked(&refs->lock))
+ validate_packed_ref_cache(refs);
+
+ if (!refs->cache)
+ refs->cache = read_packed_refs(refs->path);
+
+ return refs->cache;
+}
+
+static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache)
+{
+ return get_ref_dir(packed_ref_cache->cache->root);
+}
+
+static struct ref_dir *get_packed_refs(struct packed_ref_store *refs)
+{
+ return get_packed_ref_dir(get_packed_ref_cache(refs));
+}
+
+/*
+ * Return the ref_entry for the given refname from the packed
+ * references. If it does not exist, return NULL.
+ */
+static struct ref_entry *get_packed_ref(struct packed_ref_store *refs,
+ const char *refname)
+{
+ return find_ref_entry(get_packed_refs(refs), refname);
+}
+
+static int packed_read_raw_ref(struct ref_store *ref_store,
+ const char *refname, unsigned char *sha1,
+ struct strbuf *referent, unsigned int *type)
+{
+ struct packed_ref_store *refs =
+ packed_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
+
+ struct ref_entry *entry;
+
+ *type = 0;
+
+ entry = get_packed_ref(refs, refname);
+ if (!entry) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ hashcpy(sha1, entry->u.value.oid.hash);
+ *type = REF_ISPACKED;
+ return 0;
+}
+
+static int packed_peel_ref(struct ref_store *ref_store,
+ const char *refname, unsigned char *sha1)
+{
+ struct packed_ref_store *refs =
+ packed_downcast(ref_store, REF_STORE_READ | REF_STORE_ODB,
+ "peel_ref");
+ struct ref_entry *r = get_packed_ref(refs, refname);
+
+ if (!r || peel_entry(r, 0))
+ return -1;
+
+ hashcpy(sha1, r->u.value.peeled.hash);
+ return 0;
+}
+
+struct packed_ref_iterator {
+ struct ref_iterator base;
+
+ struct packed_ref_cache *cache;
+ struct ref_iterator *iter0;
+ unsigned int flags;
+};
+
+static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
+{
+ struct packed_ref_iterator *iter =
+ (struct packed_ref_iterator *)ref_iterator;
+ int ok;
+
+ while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
+ if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
+ ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE)
+ continue;
+
+ if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
+ !ref_resolves_to_object(iter->iter0->refname,
+ iter->iter0->oid,
+ iter->iter0->flags))
+ continue;
+
+ iter->base.refname = iter->iter0->refname;
+ iter->base.oid = iter->iter0->oid;
+ iter->base.flags = iter->iter0->flags;
+ return ITER_OK;
+ }
+
+ iter->iter0 = NULL;
+ if (ref_iterator_abort(ref_iterator) != ITER_DONE)
+ ok = ITER_ERROR;
+
+ return ok;
+}
+
+static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
+ struct object_id *peeled)
+{
+ struct packed_ref_iterator *iter =
+ (struct packed_ref_iterator *)ref_iterator;
+
+ return ref_iterator_peel(iter->iter0, peeled);
+}
+
+static int packed_ref_iterator_abort(struct ref_iterator *ref_iterator)
+{
+ struct packed_ref_iterator *iter =
+ (struct packed_ref_iterator *)ref_iterator;
+ int ok = ITER_DONE;
+
+ if (iter->iter0)
+ ok = ref_iterator_abort(iter->iter0);
+
+ release_packed_ref_cache(iter->cache);
+ base_ref_iterator_free(ref_iterator);
+ return ok;
+}
+
+static struct ref_iterator_vtable packed_ref_iterator_vtable = {
+ packed_ref_iterator_advance,
+ packed_ref_iterator_peel,
+ packed_ref_iterator_abort
+};
+
+static struct ref_iterator *packed_ref_iterator_begin(
+ struct ref_store *ref_store,
+ const char *prefix, unsigned int flags)
+{
+ struct packed_ref_store *refs;
+ struct packed_ref_iterator *iter;
+ struct ref_iterator *ref_iterator;
+ unsigned int required_flags = REF_STORE_READ;
+
+ if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN))
+ required_flags |= REF_STORE_ODB;
+ refs = packed_downcast(ref_store, required_flags, "ref_iterator_begin");
+
+ iter = xcalloc(1, sizeof(*iter));
+ ref_iterator = &iter->base;
+ base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable);
+
+ /*
+ * Note that get_packed_ref_cache() internally checks whether
+ * the packed-ref cache is up to date with what is on disk,
+ * and re-reads it if not.
+ */
+
+ iter->cache = get_packed_ref_cache(refs);
+ acquire_packed_ref_cache(iter->cache);
+ iter->iter0 = cache_ref_iterator_begin(iter->cache->cache, prefix, 0);
+
+ iter->flags = flags;
+
+ return ref_iterator;
+}
+
+/*
+ * Write an entry to the packed-refs file for the specified refname.
+ * If peeled is non-NULL, write it as the entry's peeled value. On
+ * error, return a nonzero value and leave errno set at the value left
+ * by the failing call to `fprintf()`.
+ */
+static int write_packed_entry(FILE *fh, const char *refname,
+ const unsigned char *sha1,
+ const unsigned char *peeled)
+{
+ if (fprintf(fh, "%s %s\n", sha1_to_hex(sha1), refname) < 0 ||
+ (peeled && fprintf(fh, "^%s\n", sha1_to_hex(peeled)) < 0))
+ return -1;
+
+ return 0;
+}
+
+int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
+{
+ struct packed_ref_store *refs =
+ packed_downcast(ref_store, REF_STORE_WRITE | REF_STORE_MAIN,
+ "packed_refs_lock");
+ static int timeout_configured = 0;
+ static int timeout_value = 1000;
+
+ if (!timeout_configured) {
+ git_config_get_int("core.packedrefstimeout", &timeout_value);
+ timeout_configured = 1;
+ }
+
+ /*
+ * Note that we close the lockfile immediately because we
+ * don't write new content to it, but rather to a separate
+ * tempfile.
+ */
+ if (hold_lock_file_for_update_timeout(
+ &refs->lock,
+ refs->path,
+ flags, timeout_value) < 0) {
+ unable_to_lock_message(refs->path, errno, err);
+ return -1;
+ }
+
+ if (close_lock_file_gently(&refs->lock)) {
+ strbuf_addf(err, "unable to close %s: %s", refs->path, strerror(errno));
+ rollback_lock_file(&refs->lock);
+ return -1;
+ }
+
+ /*
+ * Now that we hold the `packed-refs` lock, make sure that our
+ * cache matches the current version of the file. Normally
+ * `get_packed_ref_cache()` does that for us, but that
+ * function assumes that when the file is locked, any existing
+ * cache is still valid. We've just locked the file, but it
+ * might have changed the moment *before* we locked it.
+ */
+ validate_packed_ref_cache(refs);
+
+ /*
+ * Now make sure that the packed-refs file as it exists in the
+ * locked state is loaded into the cache:
+ */
+ get_packed_ref_cache(refs);
+ return 0;
+}
+
+void packed_refs_unlock(struct ref_store *ref_store)
+{
+ struct packed_ref_store *refs = packed_downcast(
+ ref_store,
+ REF_STORE_READ | REF_STORE_WRITE,
+ "packed_refs_unlock");
+
+ if (!is_lock_file_locked(&refs->lock))
+ die("BUG: packed_refs_unlock() called when not locked");
+ rollback_lock_file(&refs->lock);
+}
+
+int packed_refs_is_locked(struct ref_store *ref_store)
+{
+ struct packed_ref_store *refs = packed_downcast(
+ ref_store,
+ REF_STORE_READ | REF_STORE_WRITE,
+ "packed_refs_is_locked");
+
+ return is_lock_file_locked(&refs->lock);
+}
+
+/*
+ * The packed-refs header line that we write out. Perhaps other
+ * traits will be added later. The trailing space is required.
+ */
+static const char PACKED_REFS_HEADER[] =
+ "# pack-refs with: peeled fully-peeled \n";
+
+static int packed_init_db(struct ref_store *ref_store, struct strbuf *err)
+{
+ /* Nothing to do. */
+ return 0;
+}
+
+/*
+ * Write the packed-refs from the cache to the packed-refs tempfile,
+ * incorporating any changes from `updates`. `updates` must be a
+ * sorted string list whose keys are the refnames and whose util
+ * values are `struct ref_update *`. On error, rollback the tempfile,
+ * write an error message to `err`, and return a nonzero value.
+ *
+ * The packfile must be locked before calling this function and will
+ * remain locked when it is done.
+ */
+static int write_with_updates(struct packed_ref_store *refs,
+ struct string_list *updates,
+ struct strbuf *err)
+{
+ struct ref_iterator *iter = NULL;
+ size_t i;
+ int ok;
+ FILE *out;
+ struct strbuf sb = STRBUF_INIT;
+ char *packed_refs_path;
+
+ if (!is_lock_file_locked(&refs->lock))
+ die("BUG: write_with_updates() called while unlocked");
+
+ /*
+ * If packed-refs is a symlink, we want to overwrite the
+ * symlinked-to file, not the symlink itself. Also, put the
+ * staging file next to it:
+ */
+ packed_refs_path = get_locked_file_path(&refs->lock);
+ strbuf_addf(&sb, "%s.new", packed_refs_path);
+ free(packed_refs_path);
+ refs->tempfile = create_tempfile(sb.buf);
+ if (!refs->tempfile) {
+ strbuf_addf(err, "unable to create file %s: %s",
+ sb.buf, strerror(errno));
+ strbuf_release(&sb);
+ return -1;
+ }
+ strbuf_release(&sb);
+
+ out = fdopen_tempfile(refs->tempfile, "w");
+ if (!out) {
+ strbuf_addf(err, "unable to fdopen packed-refs tempfile: %s",
+ strerror(errno));
+ goto error;
+ }
+
+ if (fprintf(out, "%s", PACKED_REFS_HEADER) < 0)
+ goto write_error;
+
+ /*
+ * We iterate in parallel through the current list of refs and
+ * the list of updates, processing an entry from at least one
+ * of the lists each time through the loop. When the current
+ * list of refs is exhausted, set iter to NULL. When the list
+ * of updates is exhausted, leave i set to updates->nr.
+ */
+ iter = packed_ref_iterator_begin(&refs->base, "",
+ DO_FOR_EACH_INCLUDE_BROKEN);
+ if ((ok = ref_iterator_advance(iter)) != ITER_OK)
+ iter = NULL;
+
+ i = 0;
+
+ while (iter || i < updates->nr) {
+ struct ref_update *update = NULL;
+ int cmp;
+
+ if (i >= updates->nr) {
+ cmp = -1;
+ } else {
+ update = updates->items[i].util;
+
+ if (!iter)
+ cmp = +1;
+ else
+ cmp = strcmp(iter->refname, update->refname);
+ }
+
+ if (!cmp) {
+ /*
+ * There is both an old value and an update
+ * for this reference. Check the old value if
+ * necessary:
+ */
+ if ((update->flags & REF_HAVE_OLD)) {
+ if (is_null_oid(&update->old_oid)) {
+ strbuf_addf(err, "cannot update ref '%s': "
+ "reference already exists",
+ update->refname);
+ goto error;
+ } else if (oidcmp(&update->old_oid, iter->oid)) {
+ strbuf_addf(err, "cannot update ref '%s': "
+ "is at %s but expected %s",
+ update->refname,
+ oid_to_hex(iter->oid),
+ oid_to_hex(&update->old_oid));
+ goto error;
+ }
+ }
+
+ /* Now figure out what to use for the new value: */
+ if ((update->flags & REF_HAVE_NEW)) {
+ /*
+ * The update takes precedence. Skip
+ * the iterator over the unneeded
+ * value.
+ */
+ if ((ok = ref_iterator_advance(iter)) != ITER_OK)
+ iter = NULL;
+ cmp = +1;
+ } else {
+ /*
+ * The update doesn't actually want to
+ * change anything. We're done with it.
+ */
+ i++;
+ cmp = -1;
+ }
+ } else if (cmp > 0) {
+ /*
+ * There is no old value but there is an
+ * update for this reference. Make sure that
+ * the update didn't expect an existing value:
+ */
+ if ((update->flags & REF_HAVE_OLD) &&
+ !is_null_oid(&update->old_oid)) {
+ strbuf_addf(err, "cannot update ref '%s': "
+ "reference is missing but expected %s",
+ update->refname,
+ oid_to_hex(&update->old_oid));
+ goto error;
+ }
+ }
+
+ if (cmp < 0) {
+ /* Pass the old reference through. */
+
+ struct object_id peeled;
+ int peel_error = ref_iterator_peel(iter, &peeled);
+
+ if (write_packed_entry(out, iter->refname,
+ iter->oid->hash,
+ peel_error ? NULL : peeled.hash))
+ goto write_error;
+
+ if ((ok = ref_iterator_advance(iter)) != ITER_OK)
+ iter = NULL;
+ } else if (is_null_oid(&update->new_oid)) {
+ /*
+ * The update wants to delete the reference,
+ * and the reference either didn't exist or we
+ * have already skipped it. So we're done with
+ * the update (and don't have to write
+ * anything).
+ */
+ i++;
+ } else {
+ struct object_id peeled;
+ int peel_error = peel_object(update->new_oid.hash,
+ peeled.hash);
+
+ if (write_packed_entry(out, update->refname,
+ update->new_oid.hash,
+ peel_error ? NULL : peeled.hash))
+ goto write_error;
+
+ i++;
+ }
+ }
+
+ if (ok != ITER_DONE) {
+ strbuf_addf(err, "unable to write packed-refs file: "
+ "error iterating over old contents");
+ goto error;
+ }
+
+ if (close_tempfile_gently(refs->tempfile)) {
+ strbuf_addf(err, "error closing file %s: %s",
+ get_tempfile_path(refs->tempfile),
+ strerror(errno));
+ strbuf_release(&sb);
+ delete_tempfile(&refs->tempfile);
+ return -1;
+ }
+
+ return 0;
+
+write_error:
+ strbuf_addf(err, "error writing to %s: %s",
+ get_tempfile_path(refs->tempfile), strerror(errno));
+
+error:
+ if (iter)
+ ref_iterator_abort(iter);
+
+ delete_tempfile(&refs->tempfile);
+ return -1;
+}
+
+struct packed_transaction_backend_data {
+ /* True iff the transaction owns the packed-refs lock. */
+ int own_lock;
+
+ struct string_list updates;
+};
+
+static void packed_transaction_cleanup(struct packed_ref_store *refs,
+ struct ref_transaction *transaction)
+{
+ struct packed_transaction_backend_data *data = transaction->backend_data;
+
+ if (data) {
+ string_list_clear(&data->updates, 0);
+
+ if (is_tempfile_active(refs->tempfile))
+ delete_tempfile(&refs->tempfile);
+
+ if (data->own_lock && is_lock_file_locked(&refs->lock)) {
+ packed_refs_unlock(&refs->base);
+ data->own_lock = 0;
+ }
+
+ free(data);
+ transaction->backend_data = NULL;
+ }
+
+ transaction->state = REF_TRANSACTION_CLOSED;
+}
+
+static int packed_transaction_prepare(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ struct packed_ref_store *refs = packed_downcast(
+ ref_store,
+ REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
+ "ref_transaction_prepare");
+ struct packed_transaction_backend_data *data;
+ size_t i;
+ int ret = TRANSACTION_GENERIC_ERROR;
+
+ /*
+ * Note that we *don't* skip transactions with zero updates,
+ * because such a transaction might be executed for the side
+ * effect of ensuring that all of the references are peeled.
+ * If the caller wants to optimize away empty transactions, it
+ * should do so itself.
+ */
+
+ data = xcalloc(1, sizeof(*data));
+ string_list_init(&data->updates, 0);
+
+ transaction->backend_data = data;
+
+ /*
+ * Stick the updates in a string list by refname so that we
+ * can sort them:
+ */
+ for (i = 0; i < transaction->nr; i++) {
+ struct ref_update *update = transaction->updates[i];
+ struct string_list_item *item =
+ string_list_append(&data->updates, update->refname);
+
+ /* Store a pointer to update in item->util: */
+ item->util = update;
+ }
+ string_list_sort(&data->updates);
+
+ if (ref_update_reject_duplicates(&data->updates, err))
+ goto failure;
+
+ if (!is_lock_file_locked(&refs->lock)) {
+ if (packed_refs_lock(ref_store, 0, err))
+ goto failure;
+ data->own_lock = 1;
+ }
+
+ if (write_with_updates(refs, &data->updates, err))
+ goto failure;
+
+ transaction->state = REF_TRANSACTION_PREPARED;
+ return 0;
+
+failure:
+ packed_transaction_cleanup(refs, transaction);
+ return ret;
+}
+
+static int packed_transaction_abort(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ struct packed_ref_store *refs = packed_downcast(
+ ref_store,
+ REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
+ "ref_transaction_abort");
+
+ packed_transaction_cleanup(refs, transaction);
+ return 0;
+}
+
+static int packed_transaction_finish(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ struct packed_ref_store *refs = packed_downcast(
+ ref_store,
+ REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
+ "ref_transaction_finish");
+ int ret = TRANSACTION_GENERIC_ERROR;
+ char *packed_refs_path;
+
+ packed_refs_path = get_locked_file_path(&refs->lock);
+ if (rename_tempfile(&refs->tempfile, packed_refs_path)) {
+ strbuf_addf(err, "error replacing %s: %s",
+ refs->path, strerror(errno));
+ goto cleanup;
+ }
+
+ clear_packed_ref_cache(refs);
+ ret = 0;
+
+cleanup:
+ free(packed_refs_path);
+ packed_transaction_cleanup(refs, transaction);
+ return ret;
+}
+
+static int packed_initial_transaction_commit(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ return ref_transaction_commit(transaction, err);
+}
+
+static int packed_delete_refs(struct ref_store *ref_store, const char *msg,
+ struct string_list *refnames, unsigned int flags)
+{
+ struct packed_ref_store *refs =
+ packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs");
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction;
+ struct string_list_item *item;
+ int ret;
+
+ (void)refs; /* We need the check above, but don't use the variable */
+
+ if (!refnames->nr)
+ return 0;
+
+ /*
+ * Since we don't check the references' old_oids, the
+ * individual updates can't fail, so we can pack all of the
+ * updates into a single transaction.
+ */
+
+ transaction = ref_store_transaction_begin(ref_store, &err);
+ if (!transaction)
+ return -1;
+
+ for_each_string_list_item(item, refnames) {
+ if (ref_transaction_delete(transaction, item->string, NULL,
+ flags, msg, &err)) {
+ warning(_("could not delete reference %s: %s"),
+ item->string, err.buf);
+ strbuf_reset(&err);
+ }
+ }
+
+ ret = ref_transaction_commit(transaction, &err);
+
+ if (ret) {
+ if (refnames->nr == 1)
+ error(_("could not delete reference %s: %s"),
+ refnames->items[0].string, err.buf);
+ else
+ error(_("could not delete references: %s"), err.buf);
+ }
+
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ return ret;
+}
+
+static int packed_pack_refs(struct ref_store *ref_store, unsigned int flags)
+{
+ /*
+ * Packed refs are already packed. It might be that loose refs
+ * are packed *into* a packed refs store, but that is done by
+ * updating the packed references via a transaction.
+ */
+ return 0;
+}
+
+static int packed_create_symref(struct ref_store *ref_store,
+ const char *refname, const char *target,
+ const char *logmsg)
+{
+ die("BUG: packed reference store does not support symrefs");
+}
+
+static int packed_rename_ref(struct ref_store *ref_store,
+ const char *oldrefname, const char *newrefname,
+ const char *logmsg)
+{
+ die("BUG: packed reference store does not support renaming references");
+}
+
++static int packed_copy_ref(struct ref_store *ref_store,
++ const char *oldrefname, const char *newrefname,
++ const char *logmsg)
++{
++ die("BUG: packed reference store does not support copying references");
++}
++
+static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store)
+{
+ return empty_ref_iterator_begin();
+}
+
+static int packed_for_each_reflog_ent(struct ref_store *ref_store,
+ const char *refname,
+ each_reflog_ent_fn fn, void *cb_data)
+{
+ return 0;
+}
+
+static int packed_for_each_reflog_ent_reverse(struct ref_store *ref_store,
+ const char *refname,
+ each_reflog_ent_fn fn,
+ void *cb_data)
+{
+ return 0;
+}
+
+static int packed_reflog_exists(struct ref_store *ref_store,
+ const char *refname)
+{
+ return 0;
+}
+
+static int packed_create_reflog(struct ref_store *ref_store,
+ const char *refname, int force_create,
+ struct strbuf *err)
+{
+ die("BUG: packed reference store does not support reflogs");
+}
+
+static int packed_delete_reflog(struct ref_store *ref_store,
+ const char *refname)
+{
+ return 0;
+}
+
+static int packed_reflog_expire(struct ref_store *ref_store,
+ const char *refname, const unsigned char *sha1,
+ 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)
+{
+ return 0;
+}
+
+struct ref_storage_be refs_be_packed = {
+ NULL,
+ "packed",
+ packed_ref_store_create,
+ packed_init_db,
+ packed_transaction_prepare,
+ packed_transaction_finish,
+ packed_transaction_abort,
+ packed_initial_transaction_commit,
+
+ packed_pack_refs,
+ packed_peel_ref,
+ packed_create_symref,
+ packed_delete_refs,
+ packed_rename_ref,
++ packed_copy_ref,
+
+ packed_ref_iterator_begin,
+ packed_read_raw_ref,
+
+ packed_reflog_iterator_begin,
+ packed_for_each_reflog_ent,
+ packed_for_each_reflog_ent_reverse,
+ packed_reflog_exists,
+ packed_create_reflog,
+ packed_delete_reflog,
+ packed_reflog_expire
+};
*/
#define REF_DELETED_LOOSE 0x200
+/*
+ * Return the length of time to retry acquiring a loose reference lock
+ * before giving up, in milliseconds:
+ */
+long get_files_ref_lock_timeout_ms(void);
+
/*
* Return true iff refname is minimally safe. "Safe" here means that
* deleting a loose reference by this name will not do any damage, for
*/
int refname_is_safe(const char *refname);
+/*
+ * Helper function: return true if refname, which has the specified
+ * oid and flags, can be resolved to an object in the database. If the
+ * referred-to object does not exist, emit a warning and return false.
+ */
+int ref_resolves_to_object(const char *refname,
+ const struct object_id *oid,
+ unsigned int flags);
+
enum peel_status {
/* object was peeled successfully: */
PEEL_PEELED = 0,
size_t alloc;
size_t nr;
enum ref_transaction_state state;
+ void *backend_data;
};
/*
typedef int rename_ref_fn(struct ref_store *ref_store,
const char *oldref, const char *newref,
const char *logmsg);
+ typedef int copy_ref_fn(struct ref_store *ref_store,
+ const char *oldref, const char *newref,
+ const char *logmsg);
/*
* Iterate over the references in `ref_store` whose names start with
create_symref_fn *create_symref;
delete_refs_fn *delete_refs;
rename_ref_fn *rename_ref;
+ copy_ref_fn *copy_ref;
ref_iterator_begin_fn *iterator_begin;
read_raw_ref_fn *read_raw_ref;
};
extern struct ref_storage_be refs_be_files;
+extern struct ref_storage_be refs_be_packed;
/*
* A representation of the reference store for the main repository or
git reflog exists refs/heads/n
'
+# The topmost entry in reflog for branch bbb is about branch creation.
+# Hence, we compare bbb@{1} (instead of bbb@{0}) with aaa@{0}.
+
+test_expect_success 'git branch -m bbb should rename checked out branch' '
+ test_when_finished git branch -D bbb &&
+ test_when_finished git checkout master &&
+ git checkout -b aaa &&
+ git commit --allow-empty -m "a new commit" &&
+ git rev-parse aaa@{0} >expect &&
+ git branch -m bbb &&
+ git rev-parse bbb@{1} >actual &&
+ test_cmp expect actual &&
+ git symbolic-ref HEAD >actual &&
+ echo refs/heads/bbb >expect &&
+ test_cmp expect actual
+'
+
test_expect_success 'git branch -m o/o o should fail when o/p exists' '
git branch o/o &&
git branch o/p &&
grep "^0\{40\}.*$msg$" .git/logs/HEAD
'
+test_expect_success 'git branch -M should leave orphaned HEAD alone' '
+ git init orphan &&
+ (
+ cd orphan &&
+ test_commit initial &&
+ git checkout --orphan lonely &&
+ grep lonely .git/HEAD &&
+ test_path_is_missing .git/refs/head/lonely &&
+ git branch -M master mistress &&
+ grep lonely .git/HEAD
+ )
+'
+
+test_expect_success 'resulting reflog can be shown by log -g' '
+ oid=$(git rev-parse HEAD) &&
+ cat >expect <<-EOF &&
+ HEAD@{0} $oid $msg
+ HEAD@{2} $oid checkout: moving from foo to baz
+ EOF
+ git log -g --format="%gd %H %gs" -2 HEAD >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' '
git checkout master &&
git worktree add -b baz bazdir &&
test_must_fail git config branch.s/s.dummy
'
+ test_expect_success 'git branch -m correctly renames multiple config sections' '
+ test_when_finished "git checkout master" &&
+ git checkout -b source master &&
+
+ # Assert that a config file with multiple config sections has
+ # those sections preserved...
+ cat >expect <<-\EOF &&
+ branch.dest.key1=value1
+ some.gar.b=age
+ branch.dest.key2=value2
+ EOF
+ cat >config.branch <<\EOF &&
+ ;; Note the lack of -\EOF above & mixed indenting here. This is
+ ;; intentional, we are also testing that the formatting of copied
+ ;; sections is preserved.
+
+ ;; Comment for source. Tabs
+ [branch "source"]
+ ;; Comment for the source value
+ key1 = value1
+ ;; Comment for some.gar. Spaces
+ [some "gar"]
+ ;; Comment for the some.gar value
+ b = age
+ ;; Comment for source, again. Mixed tabs/spaces.
+ [branch "source"]
+ ;; Comment for the source value, again
+ key2 = value2
+ EOF
+ cat config.branch >>.git/config &&
+ git branch -m source dest &&
+ git config -f .git/config -l | grep -F -e source -e dest -e some.gar >actual &&
+ test_cmp expect actual &&
+
+ # ...and that the comments for those sections are also
+ # preserved.
+ cat config.branch | sed "s/\"source\"/\"dest\"/" >expect &&
+ sed -n -e "/Note the lack/,\$p" .git/config >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success 'git branch -c dumps usage' '
+ test_expect_code 128 git branch -c 2>err &&
+ test_i18ngrep "branch name required" err
+ '
+
+ test_expect_success 'git branch --copy dumps usage' '
+ test_expect_code 128 git branch --copy 2>err &&
+ test_i18ngrep "branch name required" err
+ '
+
+ test_expect_success 'git branch -c d e should work' '
+ git branch -l d &&
+ git reflog exists refs/heads/d &&
+ git config branch.d.dummy Hello &&
+ git branch -c d e &&
+ git reflog exists refs/heads/d &&
+ git reflog exists refs/heads/e &&
+ echo Hello >expect &&
+ git config branch.e.dummy >actual &&
+ test_cmp expect actual &&
+ echo Hello >expect &&
+ git config branch.d.dummy >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success 'git branch --copy is a synonym for -c' '
+ git branch -l copy &&
+ git reflog exists refs/heads/copy &&
+ git config branch.copy.dummy Hello &&
+ git branch --copy copy copy-to &&
+ git reflog exists refs/heads/copy &&
+ git reflog exists refs/heads/copy-to &&
+ echo Hello >expect &&
+ git config branch.copy.dummy >actual &&
+ test_cmp expect actual &&
+ echo Hello >expect &&
+ git config branch.copy-to.dummy >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success 'git branch -c ee ef should copy ee to create branch ef' '
+ git checkout -b ee &&
+ git reflog exists refs/heads/ee &&
+ git config branch.ee.dummy Hello &&
+ git branch -c ee ef &&
+ git reflog exists refs/heads/ee &&
+ git reflog exists refs/heads/ef &&
+ test $(git config branch.ee.dummy) = Hello &&
+ test $(git config branch.ef.dummy) = Hello &&
+ test $(git rev-parse --abbrev-ref HEAD) = ee
+ '
+
+ test_expect_success 'git branch -c f/f g/g should work' '
+ git branch -l f/f &&
+ git reflog exists refs/heads/f/f &&
+ git config branch.f/f.dummy Hello &&
+ git branch -c f/f g/g &&
+ git reflog exists refs/heads/f/f &&
+ git reflog exists refs/heads/g/g &&
+ test $(git config branch.f/f.dummy) = Hello &&
+ test $(git config branch.g/g.dummy) = Hello
+ '
+
+ test_expect_success 'git branch -c m2 m2 should work' '
+ git branch -l m2 &&
+ git reflog exists refs/heads/m2 &&
+ git config branch.m2.dummy Hello &&
+ git branch -c m2 m2 &&
+ git reflog exists refs/heads/m2 &&
+ test $(git config branch.m2.dummy) = Hello
+ '
+
+ test_expect_success 'git branch -c zz zz/zz should fail' '
+ git branch -l zz &&
+ git reflog exists refs/heads/zz &&
+ test_must_fail git branch -c zz zz/zz
+ '
+
+ test_expect_success 'git branch -c b/b b should fail' '
+ git branch -l b/b &&
+ test_must_fail git branch -c b/b b
+ '
+
+ test_expect_success 'git branch -C o/q o/p should work when o/p exists' '
+ git branch -l o/q &&
+ git reflog exists refs/heads/o/q &&
+ git reflog exists refs/heads/o/p &&
+ git branch -C o/q o/p
+ '
+
+ test_expect_success 'git branch -c -f o/q o/p should work when o/p exists' '
+ git reflog exists refs/heads/o/q &&
+ git reflog exists refs/heads/o/p &&
+ git branch -c -f o/q o/p
+ '
+
+ test_expect_success 'git branch -c qq rr/qq should fail when r exists' '
+ git branch qq &&
+ git branch rr &&
+ test_must_fail git branch -c qq rr/qq
+ '
+
+ test_expect_success 'git branch -C b1 b2 should fail when b2 is checked out' '
+ git branch b1 &&
+ git checkout -b b2 &&
+ test_must_fail git branch -C b1 b2
+ '
+
+ test_expect_success 'git branch -C c1 c2 should succeed when c1 is checked out' '
+ git checkout -b c1 &&
+ git branch c2 &&
+ git branch -C c1 c2 &&
+ test $(git rev-parse --abbrev-ref HEAD) = c1
+ '
+
+ test_expect_success 'git branch -C c1 c2 should never touch HEAD' '
+ msg="Branch: copied refs/heads/c1 to refs/heads/c2" &&
+ ! grep "$msg$" .git/logs/HEAD
+ '
+
+ test_expect_success 'git branch -C master should work when master is checked out' '
+ git checkout master &&
+ git branch -C master
+ '
+
+ test_expect_success 'git branch -C master master should work when master is checked out' '
+ git checkout master &&
+ git branch -C master master
+ '
+
+ test_expect_success 'git branch -C master5 master5 should work when master is checked out' '
+ git checkout master &&
+ git branch master5 &&
+ git branch -C master5 master5
+ '
+
+ test_expect_success 'git branch -C ab cd should overwrite existing config for cd' '
+ git branch -l cd &&
+ git reflog exists refs/heads/cd &&
+ git config branch.cd.dummy CD &&
+ git branch -l ab &&
+ git reflog exists refs/heads/ab &&
+ git config branch.ab.dummy AB &&
+ git branch -C ab cd &&
+ git reflog exists refs/heads/ab &&
+ git reflog exists refs/heads/cd &&
+ test $(git config branch.ab.dummy) = AB &&
+ test $(git config branch.cd.dummy) = AB
+ '
+
+ test_expect_success 'git branch -c correctly copies multiple config sections' '
+ FOO=1 &&
+ export FOO &&
+ test_when_finished "git checkout master" &&
+ git checkout -b source2 master &&
+
+ # Assert that a config file with multiple config sections has
+ # those sections preserved...
+ cat >expect <<-\EOF &&
+ branch.source2.key1=value1
+ branch.dest2.key1=value1
+ more.gar.b=age
+ branch.source2.key2=value2
+ branch.dest2.key2=value2
+ EOF
+ cat >config.branch <<\EOF &&
+ ;; Note the lack of -\EOF above & mixed indenting here. This is
+ ;; intentional, we are also testing that the formatting of copied
+ ;; sections is preserved.
+
+ ;; Comment for source2. Tabs
+ [branch "source2"]
+ ;; Comment for the source2 value
+ key1 = value1
+ ;; Comment for more.gar. Spaces
+ [more "gar"]
+ ;; Comment for the more.gar value
+ b = age
+ ;; Comment for source2, again. Mixed tabs/spaces.
+ [branch "source2"]
+ ;; Comment for the source2 value, again
+ key2 = value2
+ EOF
+ cat config.branch >>.git/config &&
+ git branch -c source2 dest2 &&
+ git config -f .git/config -l | grep -F -e source2 -e dest2 -e more.gar >actual &&
+ test_cmp expect actual &&
+
+ # ...and that the comments and formatting for those sections
+ # is also preserved.
+ cat >expect <<\EOF &&
+ ;; Comment for source2. Tabs
+ [branch "source2"]
+ ;; Comment for the source2 value
+ key1 = value1
+ ;; Comment for more.gar. Spaces
+ [branch "dest2"]
+ ;; Comment for the source2 value
+ key1 = value1
+ ;; Comment for more.gar. Spaces
+ [more "gar"]
+ ;; Comment for the more.gar value
+ b = age
+ ;; Comment for source2, again. Mixed tabs/spaces.
+ [branch "source2"]
+ ;; Comment for the source2 value, again
+ key2 = value2
+ [branch "dest2"]
+ ;; Comment for the source2 value, again
+ key2 = value2
+ EOF
+ sed -n -e "/Comment for source2/,\$p" .git/config >actual &&
+ test_cmp expect actual
+ '
+
test_expect_success 'deleting a symref' '
git branch target &&
git symbolic-ref refs/heads/symref refs/heads/target &&
test_expect_success 'use --set-upstream-to modify a particular branch' '
git branch my13 &&
git branch --set-upstream-to master my13 &&
+ test_when_finished "git branch --unset-upstream my13" &&
test "$(git config branch.my13.remote)" = "." &&
test "$(git config branch.my13.merge)" = "refs/heads/master"
'
test_must_fail git config branch.my14.merge
'
-test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' '
- git update-ref refs/remotes/origin/master HEAD &&
- git branch --set-upstream origin/master 2>actual &&
- test_when_finished git update-ref -d refs/remotes/origin/master &&
- test_when_finished git branch -d origin/master &&
- cat >expected <<EOF &&
-The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
-
-If you wanted to make '"'master'"' track '"'origin/master'"', do this:
-
- git branch -d origin/master
- git branch --set-upstream-to origin/master
-EOF
- test_i18ncmp expected actual
-'
-
-test_expect_success '--set-upstream with two args only shows the deprecation message' '
- git branch --set-upstream master my13 2>actual &&
- test_when_finished git branch --unset-upstream master &&
- cat >expected <<EOF &&
-The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
-EOF
- test_i18ncmp expected actual
-'
-
-test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' '
- git branch --set-upstream my13 2>actual &&
- test_when_finished git branch --unset-upstream my13 &&
- cat >expected <<EOF &&
-The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
-EOF
- test_i18ncmp expected actual
+test_expect_success '--set-upstream fails' '
+ test_must_fail git branch --set-upstream origin/master
'
test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
test_must_fail git branch -d my10
'
-test_expect_success 'use set-upstream on the current branch' '
- git checkout master &&
- git --bare init myupstream.git &&
- git push myupstream.git master:refs/heads/frotz &&
- git remote add origin myupstream.git &&
- git fetch &&
- git branch --set-upstream master origin/frotz &&
-
- test "z$(git config branch.master.remote)" = "zorigin" &&
- test "z$(git config branch.master.merge)" = "zrefs/heads/frotz"
-
-'
-
test_expect_success 'use --edit-description' '
write_script editor <<-\EOF &&
echo "New contents" >"$1"