Files
ostree/src/libostree/ostree-repo-refs.c
T
Yu Qi Zhang 02a2b689dd refs: resolve conflict between local/remote repos
Add the functionality to use the same name for refs in local and remote
repos. This helps users keep track of local refs of remote origin, much
like local and remote git branches.

Previously, when a local ref is specified, resolve_refspec would fall
back to searching through remote repos if the ref is not found locally.
This function now takes an extra flag to specify whether it should
search through remote repos. Additionally, ostree_repo_resove_rev_ext
was added to call resolve_refspec with fallback_remote being false, so
refs --create would no longer complain when trying to create a local
ref of the same name as a remote one.

Fix remote repo parsing not being handled correctly on refs --create.

Closes: #363
Approved by: jlebon
2016-06-23 19:52:26 +00:00

919 lines
27 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "ostree-repo-private.h"
#include "otutil.h"
#include "ot-fs-utils.h"
static gboolean
add_ref_to_set (const char *remote,
int base_fd,
const char *path,
GHashTable *refs,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
char *contents;
gsize len;
GString *refname;
contents = glnx_file_get_contents_utf8_at (base_fd, path, &len, cancellable, error);
if (!contents)
goto out;
g_strchomp (contents);
refname = g_string_new ("");
if (remote)
{
g_string_append (refname, remote);
g_string_append_c (refname, ':');
}
g_string_append (refname, path);
g_hash_table_insert (refs, g_string_free (refname, FALSE), contents);
ret = TRUE;
out:
return ret;
}
static gboolean
write_checksum_file_at (OstreeRepo *self,
int dfd,
const char *name,
const char *sha256,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
const char *lastslash;
if (!ostree_validate_checksum_string (sha256, error))
goto out;
if (ostree_validate_checksum_string (name, NULL))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Rev name '%s' looks like a checksum", name);
goto out;
}
if (!*name)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid empty ref name");
goto out;
}
lastslash = strrchr (name, '/');
if (lastslash)
{
char *parent = strdupa (name);
parent[lastslash - name] = '\0';
if (!glnx_shutil_mkdir_p_at (dfd, parent, 0777, cancellable, error))
goto out;
}
{
size_t l = strlen (sha256);
char *bufnl = alloca (l + 2);
g_autoptr(GError) temp_error = NULL;
memcpy (bufnl, sha256, l);
bufnl[l] = '\n';
bufnl[l+1] = '\0';
if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1,
cancellable, &temp_error))
{
if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY))
{
g_autoptr(GHashTable) refs = NULL;
GHashTableIter hashiter;
gpointer hashkey, hashvalue;
g_clear_error (&temp_error);
if (!ostree_repo_list_refs (self, name, &refs, cancellable, error))
goto out;
g_hash_table_iter_init (&hashiter, refs);
while ((g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)))
{
if (strcmp (name, (char *)hashkey) != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Conflict: %s exists under %s when attempting write", (char*)hashkey, name);
goto out;
}
}
if (!glnx_shutil_rm_rf_at (dfd, name, cancellable, error))
goto out;
if (!_ostree_repo_file_replace_contents (self, dfd, name, (guint8*)bufnl, l + 1,
cancellable, error))
goto out;
}
else
{
g_propagate_error (error, g_steal_pointer (&temp_error));
goto out;
}
}
}
ret = TRUE;
out:
return ret;
}
static gboolean
find_ref_in_remotes (OstreeRepo *self,
const char *rev,
int *out_fd,
GError **error)
{
gboolean ret = FALSE;
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
glnx_fd_close int ret_fd = -1;
if (!glnx_dirfd_iterator_init_at (self->repo_dir_fd, "refs/remotes", TRUE, &dfd_iter, error))
goto out;
while (TRUE)
{
struct dirent *dent = NULL;
glnx_fd_close int remote_dfd = -1;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, error))
goto out;
if (dent == NULL)
break;
if (dent->d_type != DT_DIR)
continue;
if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error))
goto out;
if (!ot_openat_ignore_enoent (remote_dfd, rev, &ret_fd, error))
goto out;
if (ret_fd != -1)
break;
}
ret = TRUE;
*out_fd = ret_fd; ret_fd = -1;
out:
return ret;
}
static gboolean
resolve_refspec (OstreeRepo *self,
const char *remote,
const char *ref,
gboolean allow_noent,
gboolean fallback_remote,
char **out_rev,
GError **error);
static gboolean
resolve_refspec_fallback (OstreeRepo *self,
const char *remote,
const char *ref,
gboolean allow_noent,
gboolean fallback_remote,
char **out_rev,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autofree char *ret_rev = NULL;
if (self->parent_repo)
{
if (!resolve_refspec (self->parent_repo, remote, ref, allow_noent,
fallback_remote, &ret_rev, error))
goto out;
}
else if (!allow_noent)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Refspec '%s%s%s' not found",
remote ? remote : "",
remote ? ":" : "",
ref);
goto out;
}
ret = TRUE;
ot_transfer_out_value (out_rev, &ret_rev);
out:
return ret;
}
static gboolean
resolve_refspec (OstreeRepo *self,
const char *remote,
const char *ref,
gboolean allow_noent,
gboolean fallback_remote,
char **out_rev,
GError **error)
{
gboolean ret = FALSE;
__attribute__((unused)) GCancellable *cancellable = NULL;
g_autofree char *ret_rev = NULL;
glnx_fd_close int target_fd = -1;
g_return_val_if_fail (ref != NULL, FALSE);
/* We intentionally don't allow a ref that looks like a checksum */
if (ostree_validate_checksum_string (ref, NULL))
{
ret_rev = g_strdup (ref);
}
else if (remote != NULL)
{
const char *remote_ref = glnx_strjoina ("refs/remotes/", remote, "/", ref);
if (!ot_openat_ignore_enoent (self->repo_dir_fd, remote_ref, &target_fd, error))
goto out;
}
else
{
const char *local_ref = glnx_strjoina ("refs/heads/", ref);
if (!ot_openat_ignore_enoent (self->repo_dir_fd, local_ref, &target_fd, error))
goto out;
if (target_fd == -1 && fallback_remote)
{
local_ref = glnx_strjoina ("refs/remotes/", ref);
if (!ot_openat_ignore_enoent (self->repo_dir_fd, local_ref, &target_fd, error))
goto out;
if (target_fd == -1)
{
if (!find_ref_in_remotes (self, ref, &target_fd, error))
goto out;
}
}
}
if (target_fd != -1)
{
ret_rev = glnx_fd_readall_utf8 (target_fd, NULL, NULL, error);
if (!ret_rev)
{
g_prefix_error (error, "Couldn't open ref '%s': ", ref);
goto out;
}
g_strchomp (ret_rev);
if (!ostree_validate_checksum_string (ret_rev, error))
goto out;
}
else
{
if (!resolve_refspec_fallback (self, remote, ref, allow_noent, fallback_remote,
&ret_rev, cancellable, error))
goto out;
}
ot_transfer_out_value (out_rev, &ret_rev);
ret = TRUE;
out:
return ret;
}
/**
* ostree_repo_resolve_partial_checksum:
* @self: Repo
* @refspec: A refspec
* @full_checksum (out) (transfer full): A full checksum corresponding to the truncated ref given
* @error: Error
*
* Look up the existing refspec checksums. If the given ref is a unique truncated beginning
* of a valid checksum it will return that checksum in the parameter @full_checksum
*/
static gboolean
ostree_repo_resolve_partial_checksum (OstreeRepo *self,
const char *refspec,
char **full_checksum,
GError **error)
{
gboolean ret = FALSE;
static const char hexchars[] = "0123456789abcdef";
gsize off;
g_autoptr(GHashTable) ref_list = NULL;
g_autofree char *ret_rev = NULL;
guint length;
const char *checksum = NULL;
OstreeObjectType objtype;
GHashTableIter hashiter;
gpointer key, value;
GVariant *first_commit;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* If the input is longer than OSTREE_SHA256_STRING_LEN chars or contains non-hex chars,
don't bother looking for it as an object */
off = strspn (refspec, hexchars);
if (off > OSTREE_SHA256_STRING_LEN || refspec[off] != '\0')
return TRUE;
/* this looks through all objects and adds them to the ref_list if:
a) they are a commit object AND
b) the obj checksum starts with the partual checksum defined by "refspec" */
if (!ostree_repo_list_commit_objects_starting_with (self, refspec, &ref_list, NULL, error))
goto out;
length = g_hash_table_size (ref_list);
g_hash_table_iter_init (&hashiter, ref_list);
if (g_hash_table_iter_next (&hashiter, &key, &value))
first_commit = (GVariant*) key;
else
first_commit = NULL;
if (first_commit)
ostree_object_name_deserialize (first_commit, &checksum, &objtype);
/* length more than one - multiple commits match partial refspec: is not unique */
if (length > 1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Refspec %s not unique", refspec);
goto out;
}
/* length is 1 - a single matching commit gives us our revision */
else if (length == 1)
{
ret_rev = g_strdup (checksum);
}
/* Note: if length is 0, then code will return TRUE
because there is no error, but it will return full_checksum = NULL
to signal to continue parsing */
ret = TRUE;
ot_transfer_out_value (full_checksum, &ret_rev);
out:
return ret;
}
static gboolean
_ostree_repo_resolve_rev_internal (OstreeRepo *self,
const char *refspec,
gboolean allow_noent,
gboolean fallback_remote,
char **out_rev,
GError **error)
{
gboolean ret = FALSE;
g_autofree char *ret_rev = NULL;
g_return_val_if_fail (refspec != NULL, FALSE);
if (ostree_validate_checksum_string (refspec, NULL))
{
ret_rev = g_strdup (refspec);
}
else if (!ostree_repo_resolve_partial_checksum (self, refspec, &ret_rev, error))
goto out;
if (!ret_rev)
{
if (error != NULL && *error != NULL)
goto out;
if (g_str_has_suffix (refspec, "^"))
{
g_autofree char *parent_refspec = NULL;
g_autofree char *parent_rev = NULL;
g_autoptr(GVariant) commit = NULL;
parent_refspec = g_strdup (refspec);
parent_refspec[strlen(parent_refspec) - 1] = '\0';
if (!ostree_repo_resolve_rev (self, parent_refspec, allow_noent, &parent_rev, error))
goto out;
if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, parent_rev,
&commit, error))
goto out;
if (!(ret_rev = ostree_commit_get_parent (commit)))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Commit %s has no parent", parent_rev);
goto out;
}
}
else
{
g_autofree char *remote = NULL;
g_autofree char *ref = NULL;
if (!ostree_parse_refspec (refspec, &remote, &ref, error))
goto out;
if (!resolve_refspec (self, remote, ref, allow_noent,
fallback_remote, &ret_rev, error))
goto out;
}
}
ret = TRUE;
ot_transfer_out_value (out_rev, &ret_rev);
out:
return ret;
}
/**
* ostree_repo_resolve_rev:
* @self: Repo
* @refspec: A refspec
* @allow_noent: Do not throw an error if refspec does not exist
* @out_rev: (out) (transfer full): A checksum,or %NULL if @allow_noent is true and it does not exist
* @error: Error
*
* Look up the given refspec, returning the checksum it references in
* the parameter @out_rev. Will fall back on remote directory if cannot
* find the given refspec in local.
*/
gboolean
ostree_repo_resolve_rev (OstreeRepo *self,
const char *refspec,
gboolean allow_noent,
char **out_rev,
GError **error)
{
return _ostree_repo_resolve_rev_internal (self, refspec, allow_noent, TRUE, out_rev, error);
}
/**
* ostree_repo_resolve_rev_ext:
* @self: Repo
* @refspec: A refspec
* @allow_noent: Do not throw an error if refspec does not exist
* @flags: Options controlling behavior
* @out_rev: (out) (transfer full): A checksum,or %NULL if @allow_noent is true and it does not exist
* @error: Error
*
* Look up the given refspec, returning the checksum it references in
* the parameter @out_rev. Differently from ostree_repo_resolve_rev(),
* this will not fall back to searching through remote repos if a
* local ref is specified but not found.
*/
gboolean
ostree_repo_resolve_rev_ext (OstreeRepo *self,
const char *refspec,
gboolean allow_noent,
OstreeRepoResolveRevExtFlags flags,
char **out_rev,
GError **error)
{
return _ostree_repo_resolve_rev_internal (self, refspec, allow_noent, FALSE, out_rev, error);
}
static gboolean
enumerate_refs_recurse (OstreeRepo *repo,
const char *remote,
int base_dfd,
GString *base_path,
int child_dfd,
const char *path,
GHashTable *refs,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
if (!glnx_dirfd_iterator_init_at (child_dfd, path, FALSE, &dfd_iter, error))
goto out;
while (TRUE)
{
guint len = base_path->len;
struct dirent *dent = NULL;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
goto out;
if (dent == NULL)
break;
g_string_append (base_path, dent->d_name);
if (dent->d_type == DT_DIR)
{
g_string_append_c (base_path, '/');
if (!enumerate_refs_recurse (repo, remote, base_dfd, base_path,
dfd_iter.fd, dent->d_name,
refs, cancellable, error))
goto out;
}
else if (dent->d_type == DT_REG)
{
if (!add_ref_to_set (remote, base_dfd, base_path->str, refs,
cancellable, error))
goto out;
}
g_string_truncate (base_path, len);
}
ret = TRUE;
out:
return ret;
}
static gboolean
_ostree_repo_list_refs_internal (OstreeRepo *self,
gboolean cut_prefix,
const char *refspec_prefix,
GHashTable **out_all_refs,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autoptr(GHashTable) ret_all_refs = NULL;
g_autofree char *remote = NULL;
g_autofree char *ref_prefix = NULL;
ret_all_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
if (refspec_prefix)
{
struct stat stbuf;
const char *prefix_path;
const char *path;
if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error))
goto out;
if (remote)
{
prefix_path = glnx_strjoina ("refs/remotes/", remote, "/");
path = glnx_strjoina (prefix_path, ref_prefix);
}
else
{
prefix_path = "refs/heads/";
path = glnx_strjoina (prefix_path, ref_prefix);
}
if (fstatat (self->repo_dir_fd, path, &stbuf, 0) < 0)
{
if (errno != ENOENT)
{
glnx_set_error_from_errno (error);
goto out;
}
}
else
{
if (S_ISDIR (stbuf.st_mode))
{
glnx_fd_close int base_fd = -1;
g_autoptr(GString) base_path = g_string_new ("");
if (!cut_prefix)
g_string_printf (base_path, "%s/", ref_prefix);
if (!glnx_opendirat (self->repo_dir_fd, cut_prefix ? path : prefix_path, TRUE, &base_fd, error))
goto out;
if (!enumerate_refs_recurse (self, remote, base_fd, base_path,
base_fd, cut_prefix ? "." : ref_prefix,
ret_all_refs, cancellable, error))
goto out;
}
else
{
glnx_fd_close int prefix_dfd = -1;
if (!glnx_opendirat (self->repo_dir_fd, prefix_path, TRUE, &prefix_dfd, error))
goto out;
if (!add_ref_to_set (remote, prefix_dfd, ref_prefix, ret_all_refs,
cancellable, error))
goto out;
}
}
}
else
{
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
g_autoptr(GString) base_path = g_string_new ("");
glnx_fd_close int refs_heads_dfd = -1;
if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &refs_heads_dfd, error))
goto out;
if (!enumerate_refs_recurse (self, NULL, refs_heads_dfd, base_path,
refs_heads_dfd, ".",
ret_all_refs, cancellable, error))
goto out;
g_string_truncate (base_path, 0);
if (!glnx_dirfd_iterator_init_at (self->repo_dir_fd, "refs/remotes", TRUE, &dfd_iter, error))
goto out;
while (TRUE)
{
struct dirent *dent;
glnx_fd_close int remote_dfd = -1;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
goto out;
if (!dent)
break;
if (dent->d_type != DT_DIR)
continue;
if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error))
goto out;
if (!enumerate_refs_recurse (self, dent->d_name, remote_dfd, base_path,
remote_dfd, ".",
ret_all_refs,
cancellable, error))
goto out;
}
}
ret = TRUE;
ot_transfer_out_value (out_all_refs, &ret_all_refs);
out:
return ret;
}
/**
* ostree_repo_list_refs:
* @self: Repo
* @refspec_prefix: (allow-none): Only list refs which match this prefix
* @out_all_refs: (out) (element-type utf8 utf8): Mapping from ref to checksum
* @cancellable: Cancellable
* @error: Error
*
* If @refspec_prefix is %NULL, list all local and remote refspecs,
* with their current values in @out_all_refs. Otherwise, only list
* refspecs which have @refspec_prefix as a prefix.
*/
gboolean
ostree_repo_list_refs (OstreeRepo *self,
const char *refspec_prefix,
GHashTable **out_all_refs,
GCancellable *cancellable,
GError **error)
{
return _ostree_repo_list_refs_internal (self, TRUE, refspec_prefix, out_all_refs, cancellable, error);
}
/**
* ostree_repo_list_refs_ext:
* @self: Repo
* @refspec_prefix: (allow-none): Only list refs which match this prefix
* @out_all_refs: (out) (element-type utf8 utf8): Mapping from ref to checksum
* @flags: Options controlling listing behavior
* @cancellable: Cancellable
* @error: Error
*
* If @refspec_prefix is %NULL, list all local and remote refspecs,
* with their current values in @out_all_refs. Otherwise, only list
* refspecs which have @refspec_prefix as a prefix. Differently from
* ostree_repo_list_refs(), the prefix will not be removed from the ref
* name.
*/
gboolean
ostree_repo_list_refs_ext (OstreeRepo *self,
const char *refspec_prefix,
GHashTable **out_all_refs,
OstreeRepoListRefsExtFlags flags,
GCancellable *cancellable,
GError **error)
{
return _ostree_repo_list_refs_internal (self, FALSE, refspec_prefix, out_all_refs, cancellable, error);
}
/**
* ostree_repo_remote_list_refs:
* @self: Repo
* @remote_name: Name of the remote.
* @out_all_refs: (out) (element-type utf8 utf8): Mapping from ref to checksum
* @cancellable: Cancellable
* @error: Error
*
*/
gboolean
ostree_repo_remote_list_refs (OstreeRepo *self,
const char *remote_name,
GHashTable **out_all_refs,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GBytes) summary_bytes = NULL;
gboolean ret = FALSE;
g_autoptr(GHashTable) ret_all_refs = NULL;
if (!ostree_repo_remote_fetch_summary (self, remote_name,
&summary_bytes, NULL,
cancellable, error))
goto out;
if (summary_bytes == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Remote refs not available; server has no summary file\n");
goto out;
}
else
{
g_autoptr(GVariant) summary = NULL;
g_autoptr(GVariant) ref_map = NULL;
GVariantIter iter;
GVariant *child;
ret_all_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
summary_bytes, FALSE);
ref_map = g_variant_get_child_value (summary, 0);
g_variant_iter_init (&iter, ref_map);
while ((child = g_variant_iter_next_value (&iter)) != NULL)
{
const char *ref_name = NULL;
g_autoptr(GVariant) csum_v = NULL;
char tmp_checksum[OSTREE_SHA256_STRING_LEN+1];
g_variant_get_child (child, 0, "&s", &ref_name);
if (ref_name != NULL)
{
const guchar *csum_bytes;
g_variant_get_child (child, 1, "(t@aya{sv})", NULL, &csum_v, NULL);
csum_bytes = ostree_checksum_bytes_peek_validate (csum_v, error);
if (csum_bytes == NULL)
goto out;
ostree_checksum_inplace_from_bytes (csum_bytes, tmp_checksum);
g_hash_table_insert (ret_all_refs,
g_strdup (ref_name),
g_strdup (tmp_checksum));
}
g_variant_unref (child);
}
}
ret = TRUE;
ot_transfer_out_value (out_all_refs, &ret_all_refs);
out:
return ret;
}
gboolean
_ostree_repo_write_ref (OstreeRepo *self,
const char *remote,
const char *ref,
const char *rev,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
glnx_fd_close int dfd = -1;
if (remote == NULL)
{
if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE,
&dfd, error))
{
g_prefix_error (error, "Opening %s: ", "refs/heads");
goto out;
}
}
else
{
glnx_fd_close int refs_remotes_dfd = -1;
if (!glnx_opendirat (self->repo_dir_fd, "refs/remotes", TRUE,
&refs_remotes_dfd, error))
{
g_prefix_error (error, "Opening %s: ", "refs/remotes");
goto out;
}
if (rev != NULL)
{
/* Ensure we have a dir for the remote */
if (!glnx_shutil_mkdir_p_at (refs_remotes_dfd, remote, 0777, cancellable, error))
goto out;
}
dfd = glnx_opendirat_with_errno (refs_remotes_dfd, remote, TRUE);
if (dfd < 0 && (errno != ENOENT || rev != NULL))
{
glnx_set_error_from_errno (error);
g_prefix_error (error, "Opening remotes/ dir %s: ", remote);
goto out;
}
}
if (rev == NULL)
{
if (dfd >= 0)
{
if (unlinkat (dfd, ref, 0) != 0)
{
if (errno != ENOENT)
{
glnx_set_error_from_errno (error);
goto out;
}
}
}
}
else
{
if (!write_checksum_file_at (self, dfd, ref, rev, cancellable, error))
goto out;
}
if (!_ostree_repo_update_mtime (self, error))
goto out;
ret = TRUE;
out:
return ret;
}
gboolean
_ostree_repo_update_refs (OstreeRepo *self,
GHashTable *refs,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
GHashTableIter hash_iter;
gpointer key, value;
g_hash_table_iter_init (&hash_iter, refs);
while (g_hash_table_iter_next (&hash_iter, &key, &value))
{
const char *refspec = key;
const char *rev = value;
g_autofree char *remote = NULL;
g_autofree char *ref = NULL;
if (!ostree_parse_refspec (refspec, &remote, &ref, error))
goto out;
if (!_ostree_repo_write_ref (self, remote, ref, rev,
cancellable, error))
goto out;
}
ret = TRUE;
out:
return ret;
}