flatpak_dir_undeploy (FlatpakDir        *self,
                      FlatpakDecomposed *ref,
                      const char        *active_id,
                      gboolean           is_update,
                      gboolean           force_remove,
                      GCancellable      *cancellable,
                      GError           **error)
{
  g_autoptr(GFile) deploy_base = NULL;
  g_autoptr(GFile) checkoutdir = NULL;
  g_autoptr(GFile) removed_subdir = NULL;
  g_autoptr(GFile) removed_dir = NULL;
  g_autofree char *id = NULL;
  g_autofree char *dirname = NULL;
  g_autofree char *current_active = NULL;
  g_autoptr(GFile) change_file = NULL;
  g_autoptr(GError) child_error = NULL;
  int i, retry;

  g_assert (ref != NULL);
  g_assert (active_id != NULL);

  deploy_base = flatpak_dir_get_deploy_dir (self, ref);

  checkoutdir = g_file_get_child (deploy_base, active_id);
  if (!g_file_query_exists (checkoutdir, cancellable))
    {
      g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
                   _("%s commit %s not installed"),
                   flatpak_decomposed_get_ref (ref), active_id);
      return FALSE;
    }

  if (!flatpak_dir_ensure_repo (self, cancellable, error))
    return FALSE;

  current_active = flatpak_dir_read_active (self, ref, cancellable);
  if (current_active != NULL && strcmp (current_active, active_id) == 0)
    {
      g_auto(GStrv) deployed_ids = NULL;
      const char *some_deployment;

      /* We're removing the active deployment, start by repointing that
         to another deployment if one exists */

      if (!flatpak_dir_list_deployed (self, ref,
                                      &deployed_ids,
                                      cancellable, error))
        return FALSE;

      some_deployment = NULL;
      for (i = 0; deployed_ids[i] != NULL; i++)
        {
          if (strcmp (deployed_ids[i], active_id) == 0)
            continue;

          some_deployment = deployed_ids[i];
          break;
        }

      if (!flatpak_dir_set_active (self, ref, some_deployment, cancellable, error))
        return FALSE;
    }

  removed_dir = flatpak_dir_get_removed_dir (self);
  if (!flatpak_mkdir_p (removed_dir, cancellable, error))
    return FALSE;

  id = flatpak_decomposed_dup_id (ref);
  dirname = g_strdup_printf ("%s-%s", id, active_id);

  removed_subdir = g_file_get_child (removed_dir, dirname);

  retry = 0;
  while (TRUE)
    {
      g_autoptr(GError) local_error = NULL;
      g_autoptr(GFile) tmpdir = NULL;
      g_autofree char *tmpname = NULL;

      if (flatpak_file_rename (checkoutdir,
                               removed_subdir,
                               cancellable, &local_error))
        break;

      if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS) || retry >= 10)
        {
          g_propagate_error (error, g_steal_pointer (&local_error));
          return FALSE;
        }

      retry++;

      /* Destination already existed, move that aside, as we want to use the exact
       * removed dirname for the latest undeployed version */

      tmpname = g_strdup_printf ("%s-XXXXXX", dirname);
      glnx_gen_temp_name (tmpname);
      tmpdir = g_file_get_child (removed_dir, tmpname);

      if (!flatpak_file_rename (removed_subdir,
                                tmpdir,
                                cancellable, error))
        return FALSE;
    }


  if (is_update)
    change_file = g_file_resolve_relative_path (removed_subdir, "files/.updated");
  else
    change_file = g_file_resolve_relative_path (removed_subdir, "files/.removed");

  if (!g_file_replace_contents (change_file, "", 0, NULL, FALSE,
                                G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, &child_error))
    {
      g_autofree gchar *path = g_file_get_path (change_file);
      g_warning ("Unable to clear %s: %s", path, child_error->message);
      g_clear_error (&child_error);
    }

  if (force_remove || !dir_is_locked (removed_subdir))
    {
      g_autoptr(GError) tmp_error = NULL;

      if (!flatpak_rm_rf (removed_subdir, cancellable, &tmp_error))
        g_warning ("Unable to remove old checkout: %s", tmp_error->message);
    }

  return TRUE;
}