lib/repo: Add API to create and list ref aliases

There are multiple use cases where we'd like to alias refs.

First, having a "stable" alias which gets swapped across major
versions: https://pagure.io/atomic-wg/issue/228

Another case is when a ref is obsoleted;
<https://pagure.io/atomic-wg/issue/303>
This second one could be done with endoflife rebase, but I think
this case is better on the server side, as we might later change
our minds and do actual releases there.

I initially just added some test cases for symlinks in the `refs/heads` dir to
ensure this actually works (and it did), but I think it's worth having APIs.

Closes: #1033
Approved by: jlebon
This commit is contained in:
Colin Walters 2017-07-27 21:22:48 -04:00 committed by Atomic Bot
parent d1eb909cd0
commit d5273b34d0
8 changed files with 213 additions and 33 deletions

View File

@ -307,6 +307,7 @@ ostree_repo_abort_transaction
ostree_repo_transaction_set_refspec
ostree_repo_transaction_set_ref
ostree_repo_set_ref_immediate
ostree_repo_set_alias_ref_immediate
ostree_repo_set_cache_dir
ostree_repo_sign_delta
ostree_repo_has_object

View File

@ -19,6 +19,7 @@
/* Add new symbols here. Release commits should copy this section into -released.sym. */
LIBOSTREE_2017.10 {
ostree_repo_set_alias_ref_immediate;
};
/* Stub section for the stable release *after* this development one; don't

View File

@ -1451,7 +1451,31 @@ ostree_repo_set_ref_immediate (OstreeRepo *self,
GError **error)
{
const OstreeCollectionRef _ref = { NULL, (gchar *) ref };
return _ostree_repo_write_ref (self, remote, &_ref, checksum,
return _ostree_repo_write_ref (self, remote, &_ref, checksum, NULL,
cancellable, error);
}
/**
* ostree_repo_set_alias_ref_immediate:
* @self: An #OstreeRepo
* @remote: (allow-none): A remote for the ref
* @ref: The ref to write
* @target: (allow-none): The ref target to point it to, or %NULL to unset
* @cancellable: GCancellable
* @error: GError
*
* Like ostree_repo_set_ref_immediate(), but creates an alias.
*/
gboolean
ostree_repo_set_alias_ref_immediate (OstreeRepo *self,
const char *remote,
const char *ref,
const char *target,
GCancellable *cancellable,
GError **error)
{
const OstreeCollectionRef _ref = { NULL, (gchar *) ref };
return _ostree_repo_write_ref (self, remote, &_ref, NULL, target,
cancellable, error);
}
@ -1483,7 +1507,7 @@ ostree_repo_set_collection_ref_immediate (OstreeRepo *self,
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
return _ostree_repo_write_ref (self, NULL, ref, checksum,
return _ostree_repo_write_ref (self, NULL, ref, checksum, NULL,
cancellable, error);
}

View File

@ -265,6 +265,7 @@ _ostree_repo_write_ref (OstreeRepo *self,
const char *remote,
const OstreeCollectionRef *ref,
const char *rev,
const char *alias,
GCancellable *cancellable,
GError **error);

View File

@ -472,6 +472,7 @@ ostree_repo_resolve_rev_ext (OstreeRepo *self,
static gboolean
enumerate_refs_recurse (OstreeRepo *repo,
const char *remote,
OstreeRepoListRefsExtFlags flags,
const char *collection_id,
int base_dfd,
GString *base_path,
@ -482,6 +483,7 @@ enumerate_refs_recurse (OstreeRepo *repo,
GError **error)
{
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
const gboolean aliases_only = (flags & OSTREE_REPO_LIST_REFS_EXT_ALIASES) > 0;
if (!glnx_dirfd_iterator_init_at (child_dfd, path, FALSE, &dfd_iter, error))
return FALSE;
@ -502,16 +504,30 @@ enumerate_refs_recurse (OstreeRepo *repo,
{
g_string_append_c (base_path, '/');
if (!enumerate_refs_recurse (repo, remote, collection_id, base_dfd, base_path,
if (!enumerate_refs_recurse (repo, remote, flags, collection_id, base_dfd, base_path,
dfd_iter.fd, dent->d_name,
refs, cancellable, error))
return FALSE;
}
else if (dent->d_type == DT_REG)
else
{
if (!add_ref_to_set (remote, collection_id, base_dfd, base_path->str, refs,
cancellable, error))
return FALSE;
if (aliases_only && dent->d_type == DT_LNK)
{
g_autofree char *target = glnx_readlinkat_malloc (base_dfd, base_path->str,
cancellable, error);
const char *resolved_target = target;
if (!target)
return FALSE;
while (g_str_has_prefix (resolved_target, "../"))
resolved_target += 3;
g_hash_table_insert (refs, g_strdup (base_path->str), g_strdup (resolved_target));
}
else if ((!aliases_only && dent->d_type == DT_REG) || dent->d_type == DT_LNK)
{
if (!add_ref_to_set (remote, collection_id, base_dfd, base_path->str, refs,
cancellable, error))
return FALSE;
}
}
g_string_truncate (base_path, len);
@ -523,6 +539,7 @@ enumerate_refs_recurse (OstreeRepo *repo,
static gboolean
_ostree_repo_list_refs_internal (OstreeRepo *self,
gboolean cut_prefix,
OstreeRepoListRefsExtFlags flags,
const char *refspec_prefix,
GHashTable **out_all_refs,
GCancellable *cancellable,
@ -571,7 +588,7 @@ _ostree_repo_list_refs_internal (OstreeRepo *self,
if (!glnx_opendirat (self->repo_dir_fd, cut_prefix ? path : prefix_path, TRUE, &base_fd, error))
return FALSE;
if (!enumerate_refs_recurse (self, remote, NULL, base_fd, base_path,
if (!enumerate_refs_recurse (self, remote, flags, NULL, base_fd, base_path,
base_fd, cut_prefix ? "." : ref_prefix,
ret_all_refs, cancellable, error))
return FALSE;
@ -598,7 +615,7 @@ _ostree_repo_list_refs_internal (OstreeRepo *self,
if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &refs_heads_dfd, error))
return FALSE;
if (!enumerate_refs_recurse (self, NULL, NULL, refs_heads_dfd, base_path,
if (!enumerate_refs_recurse (self, NULL, flags, NULL, refs_heads_dfd, base_path,
refs_heads_dfd, ".",
ret_all_refs, cancellable, error))
return FALSE;
@ -624,7 +641,7 @@ _ostree_repo_list_refs_internal (OstreeRepo *self,
if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error))
return FALSE;
if (!enumerate_refs_recurse (self, dent->d_name, NULL, remote_dfd, base_path,
if (!enumerate_refs_recurse (self, dent->d_name, flags, NULL, remote_dfd, base_path,
remote_dfd, ".",
ret_all_refs,
cancellable, error))
@ -655,7 +672,10 @@ ostree_repo_list_refs (OstreeRepo *self,
GCancellable *cancellable,
GError **error)
{
return _ostree_repo_list_refs_internal (self, TRUE, refspec_prefix, out_all_refs, cancellable, error);
return _ostree_repo_list_refs_internal (self, TRUE,
OSTREE_REPO_LIST_REFS_EXT_NONE,
refspec_prefix, out_all_refs,
cancellable, error);
}
/**
@ -681,7 +701,9 @@ ostree_repo_list_refs_ext (OstreeRepo *self,
GCancellable *cancellable,
GError **error)
{
return _ostree_repo_list_refs_internal (self, FALSE, refspec_prefix, out_all_refs, cancellable, error);
return _ostree_repo_list_refs_internal (self, FALSE, flags,
refspec_prefix, out_all_refs,
cancellable, error);
}
/**
@ -757,17 +779,43 @@ ostree_repo_remote_list_refs (OstreeRepo *self,
return TRUE;
}
static char *
relative_symlink_to (const char *relpath,
const char *target)
{
g_assert (*relpath);
g_assert (*target && *target != '/');
g_autoptr(GString) buf = g_string_new ("");
while (TRUE)
{
const char *slash = strchr (relpath, '/');
if (!slash)
break;
relpath = slash + 1;
g_string_append (buf, "../");
}
g_string_append (buf, target);
return g_string_free (g_steal_pointer (&buf), FALSE);
}
/* May specify @rev or @alias */
gboolean
_ostree_repo_write_ref (OstreeRepo *self,
const char *remote,
const OstreeCollectionRef *ref,
const char *rev,
const char *alias,
GCancellable *cancellable,
GError **error)
{
glnx_fd_close int dfd = -1;
g_return_val_if_fail (remote == NULL || ref->collection_id == NULL, FALSE);
g_return_val_if_fail (!(rev != NULL && alias != NULL), FALSE);
if (remote != NULL && !ostree_validate_remote_name (remote, error))
return FALSE;
@ -832,7 +880,7 @@ _ostree_repo_write_ref (OstreeRepo *self,
return glnx_throw_errno_prefix (error, "Opening remotes/ dir %s", remote);
}
if (rev == NULL)
if (rev == NULL && alias == NULL)
{
if (dfd >= 0)
{
@ -843,11 +891,32 @@ _ostree_repo_write_ref (OstreeRepo *self,
}
}
}
else
else if (rev != NULL)
{
if (!write_checksum_file_at (self, dfd, ref->ref_name, rev, cancellable, error))
return FALSE;
}
else if (alias != NULL)
{
const char *lastslash = strrchr (ref->ref_name, '/');
if (lastslash)
{
char *parent = strdupa (ref->ref_name);
parent[lastslash - ref->ref_name] = '\0';
if (!glnx_shutil_mkdir_p_at (dfd, parent, 0755, cancellable, error))
return FALSE;
}
g_autofree char *reltarget = relative_symlink_to (ref->ref_name, alias);
g_autofree char *tmplink = NULL;
if (!_ostree_make_temporary_symlink_at (self->tmp_dir_fd, reltarget,
&tmplink, cancellable, error))
return FALSE;
if (!glnx_renameat (self->tmp_dir_fd, tmplink, dfd, ref->ref_name, error))
return FALSE;
}
if (!_ostree_repo_update_mtime (self, error))
return FALSE;
@ -876,7 +945,7 @@ _ostree_repo_update_refs (OstreeRepo *self,
return FALSE;
const OstreeCollectionRef ref = { NULL, ref_name };
if (!_ostree_repo_write_ref (self, remote, &ref, rev,
if (!_ostree_repo_write_ref (self, remote, &ref, rev, NULL,
cancellable, error))
return FALSE;
}
@ -899,7 +968,7 @@ _ostree_repo_update_collection_refs (OstreeRepo *self,
const OstreeCollectionRef *ref = key;
const char *rev = value;
if (!_ostree_repo_write_ref (self, NULL, ref, rev,
if (!_ostree_repo_write_ref (self, NULL, ref, rev, NULL,
cancellable, error))
return FALSE;
}
@ -961,7 +1030,8 @@ ostree_repo_list_collection_refs (OstreeRepo *self,
if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &refs_heads_dfd, error))
return FALSE;
if (!enumerate_refs_recurse (self, NULL, main_collection_id, refs_heads_dfd, base_path,
if (!enumerate_refs_recurse (self, NULL, OSTREE_REPO_LIST_REFS_EXT_NONE,
main_collection_id, refs_heads_dfd, base_path,
refs_heads_dfd, ".",
ret_all_refs, cancellable, error))
return FALSE;
@ -993,7 +1063,8 @@ ostree_repo_list_collection_refs (OstreeRepo *self,
if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &collection_dfd, error))
return FALSE;
if (!enumerate_refs_recurse (self, NULL, dent->d_name, collection_dfd, base_path,
if (!enumerate_refs_recurse (self, NULL, OSTREE_REPO_LIST_REFS_EXT_NONE,
dent->d_name, collection_dfd, base_path,
collection_dfd, ".",
ret_all_refs,
cancellable, error))

View File

@ -327,6 +327,14 @@ gboolean ostree_repo_set_ref_immediate (OstreeRepo *self,
GCancellable *cancellable,
GError **error);
_OSTREE_PUBLIC
gboolean ostree_repo_set_alias_ref_immediate (OstreeRepo *self,
const char *remote,
const char *ref,
const char *target,
GCancellable *cancellable,
GError **error);
#ifdef OSTREE_ENABLE_EXPERIMENTAL_API
_OSTREE_PUBLIC
@ -452,9 +460,11 @@ gboolean ostree_repo_list_refs (OstreeRepo *self,
/**
* OstreeRepoListRefsExtFlags:
* @OSTREE_REPO_LIST_REFS_EXT_NONE: No flags.
* @OSTREE_REPO_LIST_REFS_EXT_ALIASES: Only list aliases. Since: 2017.10
*/
typedef enum {
OSTREE_REPO_LIST_REFS_EXT_NONE = 0,
OSTREE_REPO_LIST_REFS_EXT_ALIASES = 1,
} OstreeRepoListRefsExtFlags;
_OSTREE_PUBLIC

View File

@ -28,6 +28,7 @@
static gboolean opt_delete;
static gboolean opt_list;
static gboolean opt_alias;
static char *opt_create;
#ifdef OSTREE_ENABLE_EXPERIMENTAL_API
static gboolean opt_collections;
@ -36,6 +37,7 @@ static gboolean opt_collections;
static GOptionEntry options[] = {
{ "delete", 0, 0, G_OPTION_ARG_NONE, &opt_delete, "Delete refs which match PREFIX, rather than listing them", NULL },
{ "list", 0, 0, G_OPTION_ARG_NONE, &opt_list, "Do not remove the prefix from the refs", NULL },
{ "alias", 'A', 0, G_OPTION_ARG_NONE, &opt_alias, "If used with --create, create an alias, otherwise just list aliases", NULL },
{ "create", 0, 0, G_OPTION_ARG_STRING, &opt_create, "Create a new ref for an existing commit", "NEWREF" },
#ifdef OSTREE_ENABLE_EXPERIMENTAL_API
{ "collections", 'c', 0, G_OPTION_ARG_NONE, &opt_collections, "Enable listing collection IDs for refs", NULL },
@ -132,18 +134,36 @@ do_ref_with_collections (OstreeRepo *repo,
static gboolean do_ref (OstreeRepo *repo, const char *refspec_prefix, GCancellable *cancellable, GError **error)
{
g_autoptr(GHashTable) refs = NULL;
g_autoptr(GHashTable) ref_aliases = NULL;
GHashTableIter hashiter;
gpointer hashkey, hashvalue;
gboolean ret = FALSE;
gboolean is_list;
#ifdef OSTREE_ENABLE_EXPERIMENTAL_API
if (opt_collections)
return do_ref_with_collections (repo, refspec_prefix, cancellable, error);
#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */
if (opt_delete || opt_list)
/* If we're doing aliasing, we need the full list of aliases mostly to allow
* replacing existing aliases.
*/
if (opt_alias)
{
if (!ostree_repo_list_refs_ext (repo, refspec_prefix, &refs, OSTREE_REPO_LIST_REFS_EXT_NONE,
if (!ostree_repo_list_refs_ext (repo, NULL, &ref_aliases,
OSTREE_REPO_LIST_REFS_EXT_ALIASES,
cancellable, error))
goto out;
}
is_list = !(opt_delete || opt_create);
if (opt_delete || opt_list || (!opt_create && opt_alias))
{
OstreeRepoListRefsExtFlags flags = OSTREE_REPO_LIST_REFS_EXT_NONE;
if (opt_alias)
flags |= OSTREE_REPO_LIST_REFS_EXT_ALIASES;
if (!ostree_repo_list_refs_ext (repo, refspec_prefix, &refs, flags,
cancellable, error))
goto out;
}
@ -156,13 +176,14 @@ static gboolean do_ref (OstreeRepo *repo, const char *refspec_prefix, GCancellab
else if (!ostree_repo_list_refs (repo, refspec_prefix, &refs, cancellable, error))
goto out;
if (!opt_delete && !opt_create)
if (is_list)
{
g_hash_table_iter_init (&hashiter, refs);
while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
GLNX_HASH_TABLE_FOREACH_KV (refs, const char *, ref, const char *, value)
{
const char *ref = hashkey;
g_print ("%s\n", ref);
if (opt_alias)
g_print ("%s -> %s\n", ref, value);
else
g_print ("%s\n", ref);
}
}
else if (opt_create)
@ -183,22 +204,33 @@ static gboolean do_ref (OstreeRepo *repo, const char *refspec_prefix, GCancellab
else goto out;
}
if (checksum_existing != NULL)
/* We want to allow replacing an existing alias */
gboolean replacing_alias = opt_alias && g_hash_table_contains (ref_aliases, opt_create);
if (!replacing_alias && checksum_existing != NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"--create specified but ref %s already exists", opt_create);
goto out;
}
if (!ostree_repo_resolve_rev (repo, refspec_prefix, FALSE, &checksum, error))
goto out;
if (!ostree_parse_refspec (opt_create, &remote, &ref, error))
goto out;
if (!ostree_repo_set_ref_immediate (repo, remote, ref, checksum,
cancellable, error))
goto out;
if (opt_alias)
{
if (!ostree_repo_set_alias_ref_immediate (repo, remote, ref, refspec_prefix,
cancellable, error))
goto out;
}
else
{
if (!ostree_repo_resolve_rev (repo, refspec_prefix, FALSE, &checksum, error))
goto out;
if (!ostree_repo_set_ref_immediate (repo, remote, ref, checksum,
cancellable, error))
goto out;
}
}
else
/* delete */

View File

@ -23,7 +23,7 @@ set -euo pipefail
setup_fake_remote_repo1 "archive-z2"
echo '1..1'
echo '1..2'
cd ${test_tmpdir}
mkdir repo
@ -117,3 +117,43 @@ ${CMD_PREFIX} ostree --repo=repo refs | wc -l > refscount.create6
assert_file_has_content refscount.create6 "^11$"
echo "ok refs"
# Test symlinking a ref
${CMD_PREFIX} ostree --repo=repo refs ctest --create=exampleos/x86_64/26/server
${CMD_PREFIX} ostree --repo=repo refs -A exampleos/x86_64/26/server --create=exampleos/x86_64/stable/server
${CMD_PREFIX} ostree --repo=repo summary -u
${CMD_PREFIX} ostree --repo=repo refs > refs.txt
for v in 26 stable; do
assert_file_has_content refs.txt exampleos/x86_64/${v}/server
done
${CMD_PREFIX} ostree --repo=repo refs -A > refs.txt
assert_file_has_content_literal refs.txt 'exampleos/x86_64/stable/server -> exampleos/x86_64/26/server'
assert_not_file_has_content refs.txt '^exampleos/x86_64/26/server'
stable=$(${CMD_PREFIX} ostree --repo=repo rev-parse exampleos/x86_64/stable/server)
current=$(${CMD_PREFIX} ostree --repo=repo rev-parse exampleos/x86_64/26/server)
assert_streq "${stable}" "${current}"
${CMD_PREFIX} ostree --repo=repo commit -b exampleos/x86_64/26/server --tree=dir=tree
${CMD_PREFIX} ostree --repo=repo summary -u
newcurrent=$(${CMD_PREFIX} ostree --repo=repo rev-parse exampleos/x86_64/26/server)
assert_not_streq "${newcurrent}" "${current}"
newstable=$(${CMD_PREFIX} ostree --repo=repo rev-parse exampleos/x86_64/stable/server)
assert_streq "${newcurrent}" "${newstable}"
# Test that we can swap the symlink
${CMD_PREFIX} ostree --repo=repo commit -b exampleos/x86_64/27/server --tree=dir=tree
newcurrent=$(${CMD_PREFIX} ostree --repo=repo rev-parse exampleos/x86_64/27/server)
assert_not_streq "${newcurrent}" "${newstable}"
${CMD_PREFIX} ostree --repo=repo refs -A exampleos/x86_64/27/server --create=exampleos/x86_64/stable/server
newnewstable=$(${CMD_PREFIX} ostree --repo=repo rev-parse exampleos/x86_64/stable/server)
assert_not_streq "${newnewstable}" "${newstable}"
assert_streq "${newnewstable}" "${newcurrent}"
${CMD_PREFIX} ostree --repo=repo refs > refs.txt
for v in 26 27 stable; do
assert_file_has_content refs.txt exampleos/x86_64/${v}/server
done
${CMD_PREFIX} ostree --repo=repo refs -A > refs.txt
assert_file_has_content_literal refs.txt 'exampleos/x86_64/stable/server -> exampleos/x86_64/27/server'
${CMD_PREFIX} ostree --repo=repo summary -u
echo "ok ref symlink"