static gboolean
enumerate_refs_recurse (OstreeRepo *repo,
const char *remote,
+ OstreeRepoListRefsExtFlags flags,
const char *collection_id,
int base_dfd,
GString *base_path,
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;
{
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);
static gboolean
_ostree_repo_list_refs_internal (OstreeRepo *self,
gboolean cut_prefix,
+ OstreeRepoListRefsExtFlags flags,
const char *refspec_prefix,
GHashTable **out_all_refs,
GCancellable *cancellable,
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;
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;
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))
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);
}
/**
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);
}
/**
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;
return glnx_throw_errno_prefix (error, "Opening remotes/ dir %s", remote);
}
- if (rev == NULL)
+ if (rev == NULL && alias == NULL)
{
if (dfd >= 0)
{
}
}
}
- 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;
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;
}
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;
}
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;
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))
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;
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 },
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;
}
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)
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 */
setup_fake_remote_repo1 "archive-z2"
-echo '1..1'
+echo '1..2'
cd ${test_tmpdir}
mkdir repo
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"