diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 47f351d2..43e267f6 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -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 diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index c93e388a..d4ee86bf 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -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 diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index ab348311..37b14f26 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -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); } diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 5dd0dea7..1596c0db 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -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); diff --git a/src/libostree/ostree-repo-refs.c b/src/libostree/ostree-repo-refs.c index 491f22bd..a180e40b 100644 --- a/src/libostree/ostree-repo-refs.c +++ b/src/libostree/ostree-repo-refs.c @@ -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)) diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index f1e964b7..decf9a4e 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -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 diff --git a/src/ostree/ot-builtin-refs.c b/src/ostree/ot-builtin-refs.c index f2274802..0f850069 100644 --- a/src/ostree/ot-builtin-refs.c +++ b/src/ostree/ot-builtin-refs.c @@ -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 */ diff --git a/tests/test-refs.sh b/tests/test-refs.sh index d4db0013..e48784aa 100755 --- a/tests/test-refs.sh +++ b/tests/test-refs.sh @@ -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"