diff --git a/src/libostree/ostree-repo-refs.c b/src/libostree/ostree-repo-refs.c index 2b05e6f6..a7909fb7 100644 --- a/src/libostree/ostree-repo-refs.c +++ b/src/libostree/ostree-repo-refs.c @@ -347,6 +347,75 @@ resolve_refspec (OstreeRepo *self, 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; + s_unref_hashtable GHashTable *ref_list = NULL; + gs_free 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); + + /* 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; +} + /** * ostree_repo_resolve_rev: * @self: Repo @@ -374,8 +443,15 @@ ostree_repo_resolve_rev (OstreeRepo *self, { ret_rev = g_strdup (refspec); } - else + + 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, "^")) { gs_free char *parent_refspec = NULL; diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 56766649..70e1e9a9 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -990,12 +990,14 @@ list_loose_objects_at (OstreeRepo *self, GHashTable *inout_objects, const char *prefix, int dfd, + const char *commit_starting_with, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; DIR *d = NULL; struct dirent *dent; + GVariant *key, *value; d = fdopendir (dfd); if (!d) @@ -1030,21 +1032,33 @@ list_loose_objects_at (OstreeRepo *self, else continue; - if ((dot - name) == 62) + if ((dot - name) != 62) + continue; + + memcpy (buf, prefix, 2); + memcpy (buf + 2, name, 62); + buf[sizeof(buf)-1] = '\0'; + + /* if we passed in a "starting with" argument, then + we only want to return .commit objects with a checksum + that matches the commit_starting_with argument */ + if (commit_starting_with) { - GVariant *key, *value; + /* object is not a commit, do not add to array */ + if (objtype != OSTREE_OBJECT_TYPE_COMMIT) + continue; - memcpy (buf, prefix, 2); - memcpy (buf + 2, name, 62); - buf[sizeof(buf)-1] = '\0'; - - key = ostree_object_name_serialize (buf, objtype); - value = g_variant_new ("(b@as)", - TRUE, g_variant_new_strv (NULL, 0)); - /* transfer ownership */ - g_hash_table_replace (inout_objects, key, - g_variant_ref_sink (value)); + /* commit checksum does not match "starting with", do not add to array */ + if (!g_str_has_prefix (buf, commit_starting_with)) + continue; } + + key = ostree_object_name_serialize (buf, objtype); + value = g_variant_new ("(b@as)", + TRUE, g_variant_new_strv (NULL, 0)); + /* transfer ownership */ + g_hash_table_replace (inout_objects, key, + g_variant_ref_sink (value)); } ret = TRUE; @@ -1057,6 +1071,7 @@ list_loose_objects_at (OstreeRepo *self, static gboolean list_loose_objects (OstreeRepo *self, GHashTable *inout_objects, + const char *commit_starting_with, GCancellable *cancellable, GError **error) { @@ -1084,7 +1099,8 @@ list_loose_objects (OstreeRepo *self, } /* Takes ownership of dfd */ if (!list_loose_objects_at (self, inout_objects, buf, dfd, - cancellable, error)) + commit_starting_with, + cancellable, error)) goto out; } @@ -1702,11 +1718,11 @@ ostree_repo_list_objects (OstreeRepo *self, if (flags & OSTREE_REPO_LIST_OBJECTS_LOOSE) { - if (!list_loose_objects (self, ret_objects, cancellable, error)) + if (!list_loose_objects (self, ret_objects, NULL, cancellable, error)) goto out; if (self->parent_repo) { - if (!list_loose_objects (self->parent_repo, ret_objects, cancellable, error)) + if (!list_loose_objects (self->parent_repo, ret_objects, NULL, cancellable, error)) goto out; } } @@ -1722,6 +1738,53 @@ ostree_repo_list_objects (OstreeRepo *self, return ret; } +/** + * ostree_repo_list_commit_objects_starting_with: + * @self: Repo + * @start: List commits starting with this checksum + * @out_commits: Array of GVariants + * @cancellable: Cancellable + * @error: Error + * + * This function synchronously enumerates all commit objects starting + * with @start, returning data in @out_commits. + * + * Returns: %TRUE on success, %FALSE on error, and @error will be set + */ +gboolean +ostree_repo_list_commit_objects_starting_with (OstreeRepo *self, + const char *start, + GHashTable **out_commits, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gs_unref_hashtable GHashTable *ret_commits = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (self->inited, FALSE); + + ret_commits = g_hash_table_new_full (ostree_hash_object_name, g_variant_equal, + (GDestroyNotify) g_variant_unref, + (GDestroyNotify) g_variant_unref); + + if (!list_loose_objects (self, ret_commits, start, cancellable, error)) + goto out; + + + if (self->parent_repo) + { + if (!list_loose_objects (self->parent_repo, ret_commits, start, + cancellable, error)) + goto out; + } + + ret = TRUE; + ot_transfer_out_value (out_commits, &ret_commits); + out: + return ret; +} + /** * ostree_repo_read_commit: * @self: Repo diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 7a57d335..26fd6cc1 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -446,6 +446,12 @@ gboolean ostree_repo_list_objects (OstreeRepo *self, GCancellable *cancellable, GError **error); +gboolean ostree_repo_list_commit_objects_starting_with ( OstreeRepo *self, + const char *start, + GHashTable **out_commits, + GCancellable *cancellable, + GError **error); + gboolean ostree_repo_list_static_delta_names (OstreeRepo *self, GPtrArray **out_deltas, GCancellable *cancellable, diff --git a/tests/test-basic.sh b/tests/test-basic.sh index e0e71329..dfad2551 100755 --- a/tests/test-basic.sh +++ b/tests/test-basic.sh @@ -34,6 +34,15 @@ $OSTREE rev-parse 'test2^' $OSTREE rev-parse 'test2^^' 2>/dev/null && (echo 1>&2 "rev-parse test2^^ unexpectedly succeeded!"; exit 1) echo "ok rev-parse" +checksum=$($OSTREE rev-parse test2) +partial=${checksum:0:6} +echo "partial:" $partial +echo "corresponds to:" $checksum +$OSTREE rev-parse test2 > checksum +$OSTREE rev-parse $partial > partial-results +assert_file_has_content checksum $(cat partial-results) +echo "ok shortened checksum" + (cd repo && ostree rev-parse test2) echo "ok repo-in-cwd"