lib/pull: Avoid error if current with --require-static-deltas

A tricky thing here that caused this to go past a lot of our tests
is that the code was mostly OK if there was an available delta from
an older commit.  But this case broke if we e.g. had a new OS
deployment and did a `--require-static-deltas` pull, i.e. the initial
state.

I cleaned up our "find static delta state" function to return an enumeration,
and extended it with an "already have the commit" state.  A problem
I then hit is that we've historically fetched detached metadata for
non-delta pulls, even if the commit hasn't changed.  I decided not to
do that for `--require-static-deltas` pulls for now; otherwise the
code gets notably more complex.

Closes: https://github.com/ostreedev/ostree/issues/1321

Closes: #1323
Approved by: jlebon
This commit is contained in:
Colin Walters 2017-11-03 18:43:00 +00:00 committed by Atomic Bot
parent 7296bf3dcc
commit 015513b8f9
2 changed files with 102 additions and 23 deletions

View File

@ -2325,19 +2325,39 @@ process_one_static_delta (OtPullData *pull_data,
return ret; return ret;
} }
/* Loop over the static delta data we got from the summary, /*
* and find the newest commit for @out_from_revision that * DELTA_SEARCH_RESULT_UNCHANGED:
* goes to @to_revision. * We already have the commit.
* *
* Additionally, @out_have_scratch_delta will be set to %TRUE * DELTA_SEARCH_RESULT_NO_MATCH:
* if there is a %NULL @to_revision delta, also known as * No deltas were found.
*
* DELTA_SEARCH_RESULT_FROM:
* A regular delta was found, and the "from" revision will be
* set in `from_revision`.
*
* DELTA_SEARCH_RESULT_SCRATCH:
* There is a %NULL @to_revision delta, also known as
* a "from scratch" delta. * a "from scratch" delta.
*/ */
typedef struct {
enum {
DELTA_SEARCH_RESULT_UNCHANGED,
DELTA_SEARCH_RESULT_NO_MATCH,
DELTA_SEARCH_RESULT_FROM,
DELTA_SEARCH_RESULT_SCRATCH,
} result;
char from_revision[OSTREE_SHA256_STRING_LEN+1];
} DeltaSearchResult;
/* Loop over the static delta data we got from the summary,
* and find the a delta path (if available) that goes to @to_revision.
* See the enum in `DeltaSearchResult` for available result types.
*/
static gboolean static gboolean
get_best_static_delta_start_for (OtPullData *pull_data, get_best_static_delta_start_for (OtPullData *pull_data,
const char *to_revision, const char *to_revision,
gboolean *out_have_scratch_delta, DeltaSearchResult *out_result,
char **out_from_revision,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
{ {
@ -2348,7 +2368,28 @@ get_best_static_delta_start_for (OtPullData *pull_data,
g_assert (pull_data->summary_deltas_checksums != NULL); g_assert (pull_data->summary_deltas_checksums != NULL);
*out_have_scratch_delta = FALSE; out_result->result = DELTA_SEARCH_RESULT_NO_MATCH;
out_result->from_revision[0] = '\0';
/* First, do we already have this commit completely downloaded? */
gboolean have_to_rev;
if (!ostree_repo_has_object (pull_data->repo, OSTREE_OBJECT_TYPE_COMMIT,
to_revision, &have_to_rev,
cancellable, error))
return FALSE;
if (have_to_rev)
{
OstreeRepoCommitState to_rev_state;
if (!ostree_repo_load_commit (pull_data->repo, to_revision,
NULL, &to_rev_state, error))
return FALSE;
if (!(to_rev_state & OSTREE_REPO_COMMIT_STATE_PARTIAL))
{
/* We already have this commit, we're done! */
out_result->result = DELTA_SEARCH_RESULT_UNCHANGED;
return TRUE; /* Early return */
}
}
/* Loop over all deltas known from the summary file, /* Loop over all deltas known from the summary file,
* finding ones which go to to_revision */ * finding ones which go to to_revision */
@ -2366,9 +2407,17 @@ get_best_static_delta_start_for (OtPullData *pull_data,
continue; continue;
if (cur_from_rev) if (cur_from_rev)
g_ptr_array_add (candidates, g_steal_pointer (&cur_from_rev)); {
g_ptr_array_add (candidates, g_steal_pointer (&cur_from_rev));
}
else else
*out_have_scratch_delta = TRUE; {
/* We note that we have a _SCRATCH delta here, but we'll prefer using
* "from" deltas (obviously, they'll be smaller) where possible if we
* find one below.
*/
out_result->result = DELTA_SEARCH_RESULT_SCRATCH;
}
} }
/* Loop over our candidates, find the newest one */ /* Loop over our candidates, find the newest one */
@ -2407,7 +2456,11 @@ get_best_static_delta_start_for (OtPullData *pull_data,
} }
} }
*out_from_revision = g_strdup (newest_candidate); if (newest_candidate)
{
out_result->result = DELTA_SEARCH_RESULT_FROM;
memcpy (out_result->from_revision, newest_candidate, OSTREE_SHA256_STRING_LEN+1);
}
return TRUE; return TRUE;
} }
@ -3082,25 +3135,45 @@ initiate_request (OtPullData *pull_data,
/* If we have a summary, we can use the newer logic */ /* If we have a summary, we can use the newer logic */
if (pull_data->summary) if (pull_data->summary)
{ {
gboolean have_scratch_delta = FALSE; DeltaSearchResult deltares;
/* Look for a delta to @to_revision in the summary data */ /* Look for a delta to @to_revision in the summary data */
if (!get_best_static_delta_start_for (pull_data, to_revision, if (!get_best_static_delta_start_for (pull_data, to_revision, &deltares,
&have_scratch_delta, &delta_from_revision,
pull_data->cancellable, error)) pull_data->cancellable, error))
return FALSE; return FALSE;
if (delta_from_revision) /* Did we find a delta FROM commit? */ switch (deltares.result)
initiate_delta_request (pull_data, delta_from_revision, to_revision, ref);
else if (have_scratch_delta) /* No delta FROM, do we have a scratch? */
initiate_delta_request (pull_data, NULL, to_revision, ref);
else if (pull_data->require_static_deltas) /* No deltas found; are they required? */
{ {
set_required_deltas_error (error, (ref != NULL) ? ref->ref_name : "", to_revision); case DELTA_SEARCH_RESULT_NO_MATCH:
return FALSE; {
if (pull_data->require_static_deltas) /* No deltas found; are they required? */
{
set_required_deltas_error (error, (ref != NULL) ? ref->ref_name : "", to_revision);
return FALSE;
}
else /* No deltas, fall back to object fetches. */
queue_scan_one_metadata_object (pull_data, to_revision, OSTREE_OBJECT_TYPE_COMMIT, NULL, 0, ref);
}
break;
case DELTA_SEARCH_RESULT_FROM:
initiate_delta_request (pull_data, deltares.from_revision, to_revision, ref);
break;
case DELTA_SEARCH_RESULT_SCRATCH:
initiate_delta_request (pull_data, NULL, to_revision, ref);
break;
case DELTA_SEARCH_RESULT_UNCHANGED:
{
/* If we already have the commit, here things get a little special; we've historically
* fetched detached metadata, so let's keep doing that. But in the --require-static-deltas
* path, we don't, under the assumption the user wants as little network traffic as
* possible.
*/
if (pull_data->require_static_deltas)
break;
else
queue_scan_one_metadata_object (pull_data, to_revision, OSTREE_OBJECT_TYPE_COMMIT, NULL, 0, ref);
}
} }
else /* No deltas, fall back to object fetches. */
queue_scan_one_metadata_object (pull_data, to_revision, OSTREE_OBJECT_TYPE_COMMIT, NULL, 0, ref);
} }
else if (ref != NULL) else if (ref != NULL)
{ {

View File

@ -381,6 +381,12 @@ fi
${CMD_PREFIX} ostree --repo=repo fsck ${CMD_PREFIX} ostree --repo=repo fsck
done done
# Test no-op with deltas: https://github.com/ostreedev/ostree/issues/1321
cd ${test_tmpdir}
repo_init --no-gpg-verify
${CMD_PREFIX} ostree --repo=repo pull origin main
${CMD_PREFIX} ostree --repo=repo pull --require-static-deltas origin main
cd ${test_tmpdir} cd ${test_tmpdir}
repo_init --no-gpg-verify repo_init --no-gpg-verify
${CMD_PREFIX} ostree --repo=repo pull origin main@${prev_rev} ${CMD_PREFIX} ostree --repo=repo pull origin main@${prev_rev}