lib/pull: Allow specific commits in P2P updates
authorMatthew Leeds <matthew.leeds@endlessm.com>
Thu, 18 Jan 2018 08:32:05 +0000 (00:32 -0800)
committerAtomic Bot <atomic-devel@projectatomic.io>
Wed, 24 Jan 2018 14:15:53 +0000 (14:15 +0000)
Currently users of the find_remotes_async()/pull_from_remotes_async()
functions have no way to specify a commit hash to use instead of the
latest one available. This commit implements an "override-commit-ids"
option analogous to the one used by ostree_repo_pull_with_options().
It's accomplished by returning OstreeRepoFinderResult objects pointing
to the given commit checksum(s) regardless of which ones were available
from the remotes, but in the future this implementation could be
improved to take into account the commits advertised by the remotes.

One effect of this is that flatpak will have the ability to downgrade
apps that use collection IDs
(https://github.com/flatpak/flatpak/issues/1309).

Closes: #1425
Approved by: pwithnall

src/libostree/ostree-repo-pull.c

index 41d7ae2c8e277026dc5e55a5f6da5260171f342e..464d7e66a0c8954c8f8a5347b385bfb40a6bbed4 100644 (file)
@@ -4611,7 +4611,11 @@ static void find_remotes_cb (GObject      *obj,
  * Pass the results to ostree_repo_pull_from_remotes_async() to pull the given @refs
  * from those remotes.
  *
- * No @options are currently supported.
+ * The following @options are currently defined:
+ *
+ *   * `override-commit-ids` (`as`): Array of specific commit IDs to fetch. The nth
+ *   commit ID applies to the nth ref, so this must be the same length as @refs, if
+ *   provided.
  *
  * @finders must be a non-empty %NULL-terminated array of the #OstreeRepoFinder
  * instances to use, or %NULL to use the system default set of finders, which
@@ -4640,6 +4644,7 @@ ostree_repo_find_remotes_async (OstreeRepo                     *self,
   g_autoptr(OstreeRepoFinder) finder_config = NULL;
   g_autoptr(OstreeRepoFinder) finder_mount = NULL;
   g_autoptr(OstreeRepoFinder) finder_avahi = NULL;
+  g_autofree char **override_commit_ids = NULL;
 
   g_return_if_fail (OSTREE_IS_REPO (self));
   g_return_if_fail (is_valid_collection_ref_array (refs));
@@ -4649,6 +4654,12 @@ ostree_repo_find_remotes_async (OstreeRepo                     *self,
   g_return_if_fail (progress == NULL || OSTREE_IS_ASYNC_PROGRESS (progress));
   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
+  if (options)
+    {
+      (void) g_variant_lookup (options, "override-commit-ids", "^a&s", &override_commit_ids);
+      g_return_if_fail (override_commit_ids == NULL || g_strv_length ((gchar **) refs) == g_strv_length (override_commit_ids));
+    }
+
   /* Set up a task for the whole operation. */
   task = g_task_new (self, cancellable, callback, user_data);
   g_task_set_source_tag (task, ostree_repo_find_remotes_async);
@@ -4856,6 +4867,7 @@ find_remotes_cb (GObject      *obj,
   g_autoptr(OstreeFetcher) fetcher = NULL;
   g_autofree const gchar **ref_to_latest_commit = NULL;  /* indexed as @refs; (element-type commit-checksum) */
   gsize n_refs;
+  g_autofree char **override_commit_ids = NULL;
   g_autoptr(GPtrArray) remotes_to_remove = NULL;  /* (element-type OstreeRemote) */
   g_autoptr(GPtrArray) final_results = NULL;  /* (element-type OstreeRepoFinderResult) */
 
@@ -4888,13 +4900,10 @@ find_remotes_cb (GObject      *obj,
    * to be %NULL-safe. */
   g_ptr_array_set_free_func (results, (GDestroyNotify) repo_finder_result_free0);
 
-  /* FIXME: Add support for options:
-   *  - override-commit-ids (allow downgrades)
-   *
-   * Use case: multiple pulls of separate subdirs; want them to use the same
-   * configuration.
-   * Use case: downgrading a flatpak app.
-   */
+  if (data->options)
+    {
+      (void) g_variant_lookup (data->options, "override-commit-ids", "^a&s", &override_commit_ids);
+    }
 
   /* FIXME: In future, we also want to pull static delta superblocks in this
    * phase, so that we have all the metadata we need for accurate size
@@ -5144,10 +5153,11 @@ find_remotes_cb (GObject      *obj,
    * differences between remotes: two remotes could both contain ref R, but one
    * remote could be outdated compared to the other, and point to an older
    * commit. For each ref, we want to find the most recent commit any remote
-   * points to for it.
+   * points to for it (unless override-commit-ids was used).
    *
    * @ref_to_latest_commit is indexed by @ref_index, and its values are the
-   * latest checksum for each ref. */
+   * latest checksum for each ref. If override-commit-ids was used,
+   * @ref_to_latest_commit won't be initialized or used.*/
   ref_to_latest_commit = g_new0 (const gchar *, n_refs);
 
   for (i = 0; i < n_refs; i++)
@@ -5157,6 +5167,13 @@ find_remotes_cb (GObject      *obj,
       const CommitMetadata *latest_commit_metadata = NULL;
       g_autofree gchar *latest_commit_timestamp_str = NULL;
 
+      if (override_commit_ids)
+        {
+          g_debug ("%s: Using specified commit ‘%s’ for ref (%s, %s).",
+                   G_STRFUNC, override_commit_ids[i], refs[i]->collection_id, refs[i]->ref_name);
+          continue;
+        }
+
       for (j = 0; j < results->len; j++)
         {
           const CommitMetadata *candidate_commit_metadata;
@@ -5217,26 +5234,37 @@ find_remotes_cb (GObject      *obj,
                                                          ostree_collection_ref_equal,
                                                          (GDestroyNotify) ostree_collection_ref_free,
                                                          g_free);
-      n_latest_refs = 0;
 
-      for (j = 0; refs[j] != NULL; j++)
+      if (override_commit_ids)
         {
-          const gchar *latest_commit_for_ref = ref_to_latest_commit[j];
+          for (j = 0; refs[j] != NULL; j++)
+            g_hash_table_insert (validated_ref_to_checksum, ostree_collection_ref_dup (refs[j]),
+                                 g_strdup (override_commit_ids[j]));
+        }
+      else
+        {
+          n_latest_refs = 0;
 
-          if (pointer_table_get (refs_and_remotes_table, j, i) != latest_commit_for_ref)
-            latest_commit_for_ref = NULL;
-          if (latest_commit_for_ref != NULL)
-            n_latest_refs++;
+          for (j = 0; refs[j] != NULL; j++)
+            {
+              const gchar *latest_commit_for_ref = ref_to_latest_commit[j];
 
-          g_hash_table_insert (validated_ref_to_checksum, ostree_collection_ref_dup (refs[j]), g_strdup (latest_commit_for_ref));
-        }
+              if (pointer_table_get (refs_and_remotes_table, j, i) != latest_commit_for_ref)
+                latest_commit_for_ref = NULL;
+              if (latest_commit_for_ref != NULL)
+                n_latest_refs++;
 
-      if (n_latest_refs == 0)
-        {
-          g_debug ("%s: Omitting remote ‘%s’ from results as none of its refs are new enough.",
-                   G_STRFUNC, result->remote->name);
-          ostree_repo_finder_result_free (g_steal_pointer (&g_ptr_array_index (results, i)));
-          continue;
+              g_hash_table_insert (validated_ref_to_checksum, ostree_collection_ref_dup (refs[j]),
+                                   g_strdup (latest_commit_for_ref));
+            }
+
+          if (n_latest_refs == 0)
+            {
+              g_debug ("%s: Omitting remote ‘%s’ from results as none of its refs are new enough.",
+                       G_STRFUNC, result->remote->name);
+              ostree_repo_finder_result_free (g_steal_pointer (&g_ptr_array_index (results, i)));
+              continue;
+            }
         }
 
       result->ref_to_checksum = g_steal_pointer (&validated_ref_to_checksum);