Merge branch 'ds/bundle-uri-5'
The bundle-URI subsystem adds support for creation-token heuristics to help incremental fetches. * ds/bundle-uri-5: bundle-uri: test missing bundles with heuristic bundle-uri: store fetch.bundleCreationToken fetch: fetch from an external bundle URI bundle-uri: drop bundle.flag from design doc clone: set fetch.bundleURI if appropriate bundle-uri: download in creationToken order bundle-uri: parse bundle.<id>.creationToken values bundle-uri: parse bundle.heuristic=creationToken t5558: add tests for creationToken heuristic bundle: verify using check_connected() bundle: test unbundling with incomplete history
This commit is contained in:
commit
4f59836451
@ -15,6 +15,13 @@ bundle.mode::
|
|||||||
complete understanding of the bundled information (`all`) or if any one
|
complete understanding of the bundled information (`all`) or if any one
|
||||||
of the listed bundle URIs is sufficient (`any`).
|
of the listed bundle URIs is sufficient (`any`).
|
||||||
|
|
||||||
|
bundle.heuristic::
|
||||||
|
If this string-valued key exists, then the bundle list is designed to
|
||||||
|
work well with incremental `git fetch` commands. The heuristic signals
|
||||||
|
that there are additional keys available for each bundle that help
|
||||||
|
determine which subset of bundles the client should download. The
|
||||||
|
only value currently understood is `creationToken`.
|
||||||
|
|
||||||
bundle.<id>.*::
|
bundle.<id>.*::
|
||||||
The `bundle.<id>.*` keys are used to describe a single item in the
|
The `bundle.<id>.*` keys are used to describe a single item in the
|
||||||
bundle list, grouped under `<id>` for identification purposes.
|
bundle list, grouped under `<id>` for identification purposes.
|
||||||
|
@ -96,3 +96,27 @@ fetch.writeCommitGraph::
|
|||||||
merge and the write may take longer. Having an updated commit-graph
|
merge and the write may take longer. Having an updated commit-graph
|
||||||
file helps performance of many Git commands, including `git merge-base`,
|
file helps performance of many Git commands, including `git merge-base`,
|
||||||
`git push -f`, and `git log --graph`. Defaults to false.
|
`git push -f`, and `git log --graph`. Defaults to false.
|
||||||
|
|
||||||
|
fetch.bundleURI::
|
||||||
|
This value stores a URI for downloading Git object data from a bundle
|
||||||
|
URI before performing an incremental fetch from the origin Git server.
|
||||||
|
This is similar to how the `--bundle-uri` option behaves in
|
||||||
|
linkgit:git-clone[1]. `git clone --bundle-uri` will set the
|
||||||
|
`fetch.bundleURI` value if the supplied bundle URI contains a bundle
|
||||||
|
list that is organized for incremental fetches.
|
||||||
|
+
|
||||||
|
If you modify this value and your repository has a `fetch.bundleCreationToken`
|
||||||
|
value, then remove that `fetch.bundleCreationToken` value before fetching from
|
||||||
|
the new bundle URI.
|
||||||
|
|
||||||
|
fetch.bundleCreationToken::
|
||||||
|
When using `fetch.bundleURI` to fetch incrementally from a bundle
|
||||||
|
list that uses the "creationToken" heuristic, this config value
|
||||||
|
stores the maximum `creationToken` value of the downloaded bundles.
|
||||||
|
This value is used to prevent downloading bundles in the future
|
||||||
|
if the advertised `creationToken` is not strictly larger than this
|
||||||
|
value.
|
||||||
|
+
|
||||||
|
The creation token values are chosen by the provider serving the specific
|
||||||
|
bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to
|
||||||
|
remove the value for the `fetch.bundleCreationToken` value before fetching.
|
||||||
|
@ -479,14 +479,14 @@ outline for submitting these features:
|
|||||||
(This choice is an opt-in via a config option and a command-line
|
(This choice is an opt-in via a config option and a command-line
|
||||||
option.)
|
option.)
|
||||||
|
|
||||||
4. Allow the client to understand the `bundle.flag=forFetch` configuration
|
4. Allow the client to understand the `bundle.heuristic` configuration key
|
||||||
and the `bundle.<id>.creationToken` heuristic. When `git clone`
|
and the `bundle.<id>.creationToken` heuristic. When `git clone`
|
||||||
discovers a bundle URI with `bundle.flag=forFetch`, it configures the
|
discovers a bundle URI with `bundle.heuristic`, it configures the client
|
||||||
client repository to check that bundle URI during later `git fetch <remote>`
|
repository to check that bundle URI during later `git fetch <remote>`
|
||||||
commands.
|
commands.
|
||||||
|
|
||||||
5. Allow clients to discover bundle URIs during `git fetch` and configure
|
5. Allow clients to discover bundle URIs during `git fetch` and configure
|
||||||
a bundle URI for later fetches if `bundle.flag=forFetch`.
|
a bundle URI for later fetches if `bundle.heuristic` is set.
|
||||||
|
|
||||||
6. Implement the "inspect headers" heuristic to reduce data downloads when
|
6. Implement the "inspect headers" heuristic to reduce data downloads when
|
||||||
the `bundle.<id>.creationToken` heuristic is not available.
|
the `bundle.<id>.creationToken` heuristic is not available.
|
||||||
|
@ -1248,12 +1248,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||||||
* data from the --bundle-uri option.
|
* data from the --bundle-uri option.
|
||||||
*/
|
*/
|
||||||
if (bundle_uri) {
|
if (bundle_uri) {
|
||||||
|
int has_heuristic = 0;
|
||||||
|
|
||||||
/* At this point, we need the_repository to match the cloned repo. */
|
/* At this point, we need the_repository to match the cloned repo. */
|
||||||
if (repo_init(the_repository, git_dir, work_tree))
|
if (repo_init(the_repository, git_dir, work_tree))
|
||||||
warning(_("failed to initialize the repo, skipping bundle URI"));
|
warning(_("failed to initialize the repo, skipping bundle URI"));
|
||||||
else if (fetch_bundle_uri(the_repository, bundle_uri))
|
else if (fetch_bundle_uri(the_repository, bundle_uri, &has_heuristic))
|
||||||
warning(_("failed to fetch objects from bundle URI '%s'"),
|
warning(_("failed to fetch objects from bundle URI '%s'"),
|
||||||
bundle_uri);
|
bundle_uri);
|
||||||
|
else if (has_heuristic)
|
||||||
|
git_config_set_gently("fetch.bundleuri", bundle_uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
|
strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD");
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "commit-graph.h"
|
#include "commit-graph.h"
|
||||||
#include "shallow.h"
|
#include "shallow.h"
|
||||||
#include "worktree.h"
|
#include "worktree.h"
|
||||||
|
#include "bundle-uri.h"
|
||||||
|
|
||||||
#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
|
#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
|
||||||
|
|
||||||
@ -2109,6 +2110,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
|
|||||||
int cmd_fetch(int argc, const char **argv, const char *prefix)
|
int cmd_fetch(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
const char *bundle_uri;
|
||||||
struct string_list list = STRING_LIST_INIT_DUP;
|
struct string_list list = STRING_LIST_INIT_DUP;
|
||||||
struct remote *remote = NULL;
|
struct remote *remote = NULL;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
@ -2194,6 +2196,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
|
|||||||
if (dry_run)
|
if (dry_run)
|
||||||
write_fetch_head = 0;
|
write_fetch_head = 0;
|
||||||
|
|
||||||
|
if (!git_config_get_string_tmp("fetch.bundleuri", &bundle_uri) &&
|
||||||
|
fetch_bundle_uri(the_repository, bundle_uri, NULL))
|
||||||
|
warning(_("failed to fetch bundles from '%s'"), bundle_uri);
|
||||||
|
|
||||||
if (all) {
|
if (all) {
|
||||||
if (argc == 1)
|
if (argc == 1)
|
||||||
die(_("fetch --all does not take a repository argument"));
|
die(_("fetch --all does not take a repository argument"));
|
||||||
|
249
bundle-uri.c
249
bundle-uri.c
@ -9,6 +9,14 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "remote.h"
|
#include "remote.h"
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
enum bundle_list_heuristic heuristic;
|
||||||
|
const char *name;
|
||||||
|
} heuristics[BUNDLE_HEURISTIC__COUNT] = {
|
||||||
|
{ BUNDLE_HEURISTIC_NONE, ""},
|
||||||
|
{ BUNDLE_HEURISTIC_CREATIONTOKEN, "creationToken" },
|
||||||
|
};
|
||||||
|
|
||||||
static int compare_bundles(const void *hashmap_cmp_fn_data,
|
static int compare_bundles(const void *hashmap_cmp_fn_data,
|
||||||
const struct hashmap_entry *he1,
|
const struct hashmap_entry *he1,
|
||||||
const struct hashmap_entry *he2,
|
const struct hashmap_entry *he2,
|
||||||
@ -75,6 +83,9 @@ static int summarize_bundle(struct remote_bundle_info *info, void *data)
|
|||||||
FILE *fp = data;
|
FILE *fp = data;
|
||||||
fprintf(fp, "[bundle \"%s\"]\n", info->id);
|
fprintf(fp, "[bundle \"%s\"]\n", info->id);
|
||||||
fprintf(fp, "\turi = %s\n", info->uri);
|
fprintf(fp, "\turi = %s\n", info->uri);
|
||||||
|
|
||||||
|
if (info->creationToken)
|
||||||
|
fprintf(fp, "\tcreationToken = %"PRIu64"\n", info->creationToken);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +111,17 @@ void print_bundle_list(FILE *fp, struct bundle_list *list)
|
|||||||
fprintf(fp, "\tversion = %d\n", list->version);
|
fprintf(fp, "\tversion = %d\n", list->version);
|
||||||
fprintf(fp, "\tmode = %s\n", mode);
|
fprintf(fp, "\tmode = %s\n", mode);
|
||||||
|
|
||||||
|
if (list->heuristic) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < BUNDLE_HEURISTIC__COUNT; i++) {
|
||||||
|
if (heuristics[i].heuristic == list->heuristic) {
|
||||||
|
printf("\theuristic = %s\n",
|
||||||
|
heuristics[list->heuristic].name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for_all_bundles_in_list(list, summarize_bundle, fp);
|
for_all_bundles_in_list(list, summarize_bundle, fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +164,21 @@ static int bundle_list_update(const char *key, const char *value,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strcmp(subkey, "heuristic")) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < BUNDLE_HEURISTIC__COUNT; i++) {
|
||||||
|
if (heuristics[i].heuristic &&
|
||||||
|
heuristics[i].name &&
|
||||||
|
!strcmp(value, heuristics[i].name)) {
|
||||||
|
list->heuristic = heuristics[i].heuristic;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore unknown heuristics. */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ignore other unknown global keys. */
|
/* Ignore other unknown global keys. */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -169,6 +206,13 @@ static int bundle_list_update(const char *key, const char *value,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strcmp(subkey, "creationtoken")) {
|
||||||
|
if (sscanf(value, "%"PRIu64, &bundle->creationToken) != 1)
|
||||||
|
warning(_("could not parse bundle list key %s with value '%s'"),
|
||||||
|
"creationToken", value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* At this point, we ignore any information that we don't
|
* At this point, we ignore any information that we don't
|
||||||
* understand, assuming it to be hints for a heuristic the client
|
* understand, assuming it to be hints for a heuristic the client
|
||||||
@ -403,6 +447,183 @@ static int download_bundle_to_file(struct remote_bundle_info *bundle, void *data
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct bundles_for_sorting {
|
||||||
|
struct remote_bundle_info **items;
|
||||||
|
size_t alloc;
|
||||||
|
size_t nr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int append_bundle(struct remote_bundle_info *bundle, void *data)
|
||||||
|
{
|
||||||
|
struct bundles_for_sorting *list = data;
|
||||||
|
list->items[list->nr++] = bundle;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For use in QSORT() to get a list sorted by creationToken
|
||||||
|
* in decreasing order.
|
||||||
|
*/
|
||||||
|
static int compare_creation_token_decreasing(const void *va, const void *vb)
|
||||||
|
{
|
||||||
|
const struct remote_bundle_info * const *a = va;
|
||||||
|
const struct remote_bundle_info * const *b = vb;
|
||||||
|
|
||||||
|
if ((*a)->creationToken > (*b)->creationToken)
|
||||||
|
return -1;
|
||||||
|
if ((*a)->creationToken < (*b)->creationToken)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fetch_bundles_by_token(struct repository *r,
|
||||||
|
struct bundle_list *list)
|
||||||
|
{
|
||||||
|
int cur;
|
||||||
|
int move_direction = 0;
|
||||||
|
const char *creationTokenStr;
|
||||||
|
uint64_t maxCreationToken = 0, newMaxCreationToken = 0;
|
||||||
|
struct bundle_list_context ctx = {
|
||||||
|
.r = r,
|
||||||
|
.list = list,
|
||||||
|
.mode = list->mode,
|
||||||
|
};
|
||||||
|
struct bundles_for_sorting bundles = {
|
||||||
|
.alloc = hashmap_get_size(&list->bundles),
|
||||||
|
};
|
||||||
|
|
||||||
|
ALLOC_ARRAY(bundles.items, bundles.alloc);
|
||||||
|
|
||||||
|
for_all_bundles_in_list(list, append_bundle, &bundles);
|
||||||
|
|
||||||
|
if (!bundles.nr) {
|
||||||
|
free(bundles.items);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSORT(bundles.items, bundles.nr, compare_creation_token_decreasing);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If fetch.bundleCreationToken exists, parses to a uint64t, and
|
||||||
|
* is not strictly smaller than the maximum creation token in the
|
||||||
|
* bundle list, then do not download any bundles.
|
||||||
|
*/
|
||||||
|
if (!repo_config_get_value(r,
|
||||||
|
"fetch.bundlecreationtoken",
|
||||||
|
&creationTokenStr) &&
|
||||||
|
sscanf(creationTokenStr, "%"PRIu64, &maxCreationToken) == 1 &&
|
||||||
|
bundles.items[0]->creationToken <= maxCreationToken) {
|
||||||
|
free(bundles.items);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attempt to download and unbundle the minimum number of bundles by
|
||||||
|
* creationToken in decreasing order. If we fail to unbundle (after
|
||||||
|
* a successful download) then move to the next non-downloaded bundle
|
||||||
|
* and attempt downloading. Once we succeed in applying a bundle,
|
||||||
|
* move to the previous unapplied bundle and attempt to unbundle it
|
||||||
|
* again.
|
||||||
|
*
|
||||||
|
* In the case of a fresh clone, we will likely download all of the
|
||||||
|
* bundles before successfully unbundling the oldest one, then the
|
||||||
|
* rest of the bundles unbundle successfully in increasing order
|
||||||
|
* of creationToken.
|
||||||
|
*
|
||||||
|
* If there are existing objects, then this process may terminate
|
||||||
|
* early when all required commits from "new" bundles exist in the
|
||||||
|
* repo's object store.
|
||||||
|
*/
|
||||||
|
cur = 0;
|
||||||
|
while (cur >= 0 && cur < bundles.nr) {
|
||||||
|
struct remote_bundle_info *bundle = bundles.items[cur];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we need to dig into bundles below the previous
|
||||||
|
* creation token value, then likely we are in an erroneous
|
||||||
|
* state due to missing or invalid bundles. Halt the process
|
||||||
|
* instead of continuing to download extra data.
|
||||||
|
*/
|
||||||
|
if (bundle->creationToken <= maxCreationToken)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!bundle->file) {
|
||||||
|
/*
|
||||||
|
* Not downloaded yet. Try downloading.
|
||||||
|
*
|
||||||
|
* Note that bundle->file is non-NULL if a download
|
||||||
|
* was attempted, even if it failed to download.
|
||||||
|
*/
|
||||||
|
if (fetch_bundle_uri_internal(ctx.r, bundle, ctx.depth + 1, ctx.list)) {
|
||||||
|
/* Mark as unbundled so we do not retry. */
|
||||||
|
bundle->unbundled = 1;
|
||||||
|
|
||||||
|
/* Try looking deeper in the list. */
|
||||||
|
move_direction = 1;
|
||||||
|
goto move;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We expect bundles when using creationTokens. */
|
||||||
|
if (!is_bundle(bundle->file, 1)) {
|
||||||
|
warning(_("file downloaded from '%s' is not a bundle"),
|
||||||
|
bundle->uri);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bundle->file && !bundle->unbundled) {
|
||||||
|
/*
|
||||||
|
* This was downloaded, but not successfully
|
||||||
|
* unbundled. Try unbundling again.
|
||||||
|
*/
|
||||||
|
if (unbundle_from_file(ctx.r, bundle->file)) {
|
||||||
|
/* Try looking deeper in the list. */
|
||||||
|
move_direction = 1;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Succeeded in unbundle. Retry bundles
|
||||||
|
* that previously failed to unbundle.
|
||||||
|
*/
|
||||||
|
move_direction = -1;
|
||||||
|
bundle->unbundled = 1;
|
||||||
|
|
||||||
|
if (bundle->creationToken > newMaxCreationToken)
|
||||||
|
newMaxCreationToken = bundle->creationToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Else case: downloaded and unbundled successfully.
|
||||||
|
* Skip this by moving in the same direction as the
|
||||||
|
* previous step.
|
||||||
|
*/
|
||||||
|
|
||||||
|
move:
|
||||||
|
/* Move in the specified direction and repeat. */
|
||||||
|
cur += move_direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We succeed if the loop terminates because 'cur' drops below
|
||||||
|
* zero. The other case is that we terminate because 'cur'
|
||||||
|
* reaches the end of the list, so we have a failure no matter
|
||||||
|
* which bundles we apply from the list.
|
||||||
|
*/
|
||||||
|
if (cur < 0) {
|
||||||
|
struct strbuf value = STRBUF_INIT;
|
||||||
|
strbuf_addf(&value, "%"PRIu64"", newMaxCreationToken);
|
||||||
|
if (repo_config_set_multivar_gently(ctx.r,
|
||||||
|
"fetch.bundleCreationToken",
|
||||||
|
value.buf, NULL, 0))
|
||||||
|
warning(_("failed to store maximum creation token"));
|
||||||
|
|
||||||
|
strbuf_release(&value);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(bundles.items);
|
||||||
|
return cur >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int download_bundle_list(struct repository *r,
|
static int download_bundle_list(struct repository *r,
|
||||||
struct bundle_list *local_list,
|
struct bundle_list *local_list,
|
||||||
struct bundle_list *global_list,
|
struct bundle_list *global_list,
|
||||||
@ -440,7 +661,15 @@ static int fetch_bundle_list_in_config_format(struct repository *r,
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((result = download_bundle_list(r, &list_from_bundle,
|
/*
|
||||||
|
* If this list uses the creationToken heuristic, then the URIs
|
||||||
|
* it advertises are expected to be bundles, not nested lists.
|
||||||
|
* We can drop 'global_list' and 'depth'.
|
||||||
|
*/
|
||||||
|
if (list_from_bundle.heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN) {
|
||||||
|
result = fetch_bundles_by_token(r, &list_from_bundle);
|
||||||
|
global_list->heuristic = BUNDLE_HEURISTIC_CREATIONTOKEN;
|
||||||
|
} else if ((result = download_bundle_list(r, &list_from_bundle,
|
||||||
global_list, depth)))
|
global_list, depth)))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
@ -551,7 +780,8 @@ static int unlink_bundle(struct remote_bundle_info *info, void *data)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fetch_bundle_uri(struct repository *r, const char *uri)
|
int fetch_bundle_uri(struct repository *r, const char *uri,
|
||||||
|
int *has_heuristic)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
struct bundle_list list;
|
struct bundle_list list;
|
||||||
@ -571,6 +801,8 @@ int fetch_bundle_uri(struct repository *r, const char *uri)
|
|||||||
result = unbundle_all_bundles(r, &list);
|
result = unbundle_all_bundles(r, &list);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
if (has_heuristic)
|
||||||
|
*has_heuristic = (list.heuristic != BUNDLE_HEURISTIC_NONE);
|
||||||
for_all_bundles_in_list(&list, unlink_bundle, NULL);
|
for_all_bundles_in_list(&list, unlink_bundle, NULL);
|
||||||
clear_bundle_list(&list);
|
clear_bundle_list(&list);
|
||||||
clear_remote_bundle_info(&bundle, NULL);
|
clear_remote_bundle_info(&bundle, NULL);
|
||||||
@ -582,6 +814,14 @@ int fetch_bundle_list(struct repository *r, struct bundle_list *list)
|
|||||||
int result;
|
int result;
|
||||||
struct bundle_list global_list;
|
struct bundle_list global_list;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the creationToken heuristic is used, then the URIs
|
||||||
|
* advertised by 'list' are not nested lists and instead
|
||||||
|
* direct bundles. We do not need to use global_list.
|
||||||
|
*/
|
||||||
|
if (list->heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN)
|
||||||
|
return fetch_bundles_by_token(r, list);
|
||||||
|
|
||||||
init_bundle_list(&global_list);
|
init_bundle_list(&global_list);
|
||||||
|
|
||||||
/* If a bundle is added to this global list, then it is required. */
|
/* If a bundle is added to this global list, then it is required. */
|
||||||
@ -590,7 +830,10 @@ int fetch_bundle_list(struct repository *r, struct bundle_list *list)
|
|||||||
if ((result = download_bundle_list(r, list, &global_list, 0)))
|
if ((result = download_bundle_list(r, list, &global_list, 0)))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
result = unbundle_all_bundles(r, &global_list);
|
if (list->heuristic == BUNDLE_HEURISTIC_CREATIONTOKEN)
|
||||||
|
result = fetch_bundles_by_token(r, list);
|
||||||
|
else
|
||||||
|
result = unbundle_all_bundles(r, &global_list);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
for_all_bundles_in_list(&global_list, unlink_bundle, NULL);
|
for_all_bundles_in_list(&global_list, unlink_bundle, NULL);
|
||||||
|
28
bundle-uri.h
28
bundle-uri.h
@ -42,6 +42,12 @@ struct remote_bundle_info {
|
|||||||
* this boolean is true.
|
* this boolean is true.
|
||||||
*/
|
*/
|
||||||
unsigned unbundled:1;
|
unsigned unbundled:1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the bundle is part of a list with the creationToken
|
||||||
|
* heuristic, then we use this member for sorting the bundles.
|
||||||
|
*/
|
||||||
|
uint64_t creationToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define REMOTE_BUNDLE_INFO_INIT { 0 }
|
#define REMOTE_BUNDLE_INFO_INIT { 0 }
|
||||||
@ -52,6 +58,14 @@ enum bundle_list_mode {
|
|||||||
BUNDLE_MODE_ANY
|
BUNDLE_MODE_ANY
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum bundle_list_heuristic {
|
||||||
|
BUNDLE_HEURISTIC_NONE = 0,
|
||||||
|
BUNDLE_HEURISTIC_CREATIONTOKEN,
|
||||||
|
|
||||||
|
/* Must be last. */
|
||||||
|
BUNDLE_HEURISTIC__COUNT
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A bundle_list contains an unordered set of remote_bundle_info structs,
|
* A bundle_list contains an unordered set of remote_bundle_info structs,
|
||||||
* as well as information about the bundle listing, such as version and
|
* as well as information about the bundle listing, such as version and
|
||||||
@ -75,6 +89,12 @@ struct bundle_list {
|
|||||||
* advertised by the bundle list at that location.
|
* advertised by the bundle list at that location.
|
||||||
*/
|
*/
|
||||||
char *baseURI;
|
char *baseURI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list can have a heuristic, which helps reduce the number of
|
||||||
|
* downloaded bundles.
|
||||||
|
*/
|
||||||
|
enum bundle_list_heuristic heuristic;
|
||||||
};
|
};
|
||||||
|
|
||||||
void init_bundle_list(struct bundle_list *list);
|
void init_bundle_list(struct bundle_list *list);
|
||||||
@ -104,8 +124,14 @@ int bundle_uri_parse_config_format(const char *uri,
|
|||||||
* based on that information.
|
* based on that information.
|
||||||
*
|
*
|
||||||
* Returns non-zero if no bundle information is found at the given 'uri'.
|
* Returns non-zero if no bundle information is found at the given 'uri'.
|
||||||
|
*
|
||||||
|
* If the pointer 'has_heuristic' is non-NULL, then the value it points to
|
||||||
|
* will be set to be non-zero if and only if the fetched list has a
|
||||||
|
* heuristic value. Such a value indicates that the list was designed for
|
||||||
|
* incremental fetches.
|
||||||
*/
|
*/
|
||||||
int fetch_bundle_uri(struct repository *r, const char *uri);
|
int fetch_bundle_uri(struct repository *r, const char *uri,
|
||||||
|
int *has_heuristic);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a bundle list that was already advertised (likely by the
|
* Given a bundle list that was already advertised (likely by the
|
||||||
|
75
bundle.c
75
bundle.c
@ -12,6 +12,7 @@
|
|||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "strvec.h"
|
#include "strvec.h"
|
||||||
#include "list-objects-filter-options.h"
|
#include "list-objects-filter-options.h"
|
||||||
|
#include "connected.h"
|
||||||
|
|
||||||
static const char v2_bundle_signature[] = "# v2 git bundle\n";
|
static const char v2_bundle_signature[] = "# v2 git bundle\n";
|
||||||
static const char v3_bundle_signature[] = "# v3 git bundle\n";
|
static const char v3_bundle_signature[] = "# v3 git bundle\n";
|
||||||
@ -187,6 +188,21 @@ static int list_refs(struct string_list *r, int argc, const char **argv)
|
|||||||
/* Remember to update object flag allocation in object.h */
|
/* Remember to update object flag allocation in object.h */
|
||||||
#define PREREQ_MARK (1u<<16)
|
#define PREREQ_MARK (1u<<16)
|
||||||
|
|
||||||
|
struct string_list_iterator {
|
||||||
|
struct string_list *list;
|
||||||
|
size_t cur;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct object_id *iterate_ref_map(void *cb_data)
|
||||||
|
{
|
||||||
|
struct string_list_iterator *iter = cb_data;
|
||||||
|
|
||||||
|
if (iter->cur >= iter->list->nr)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return iter->list->items[iter->cur++].util;
|
||||||
|
}
|
||||||
|
|
||||||
int verify_bundle(struct repository *r,
|
int verify_bundle(struct repository *r,
|
||||||
struct bundle_header *header,
|
struct bundle_header *header,
|
||||||
enum verify_bundle_flags flags)
|
enum verify_bundle_flags flags)
|
||||||
@ -196,26 +212,25 @@ int verify_bundle(struct repository *r,
|
|||||||
* to be verbose about the errors
|
* to be verbose about the errors
|
||||||
*/
|
*/
|
||||||
struct string_list *p = &header->prerequisites;
|
struct string_list *p = &header->prerequisites;
|
||||||
struct rev_info revs = REV_INFO_INIT;
|
int i, ret = 0;
|
||||||
const char *argv[] = {NULL, "--all", NULL};
|
|
||||||
struct commit *commit;
|
|
||||||
int i, ret = 0, req_nr;
|
|
||||||
const char *message = _("Repository lacks these prerequisite commits:");
|
const char *message = _("Repository lacks these prerequisite commits:");
|
||||||
|
struct string_list_iterator iter = {
|
||||||
|
.list = p,
|
||||||
|
};
|
||||||
|
struct check_connected_options opts = {
|
||||||
|
.quiet = 1,
|
||||||
|
};
|
||||||
|
|
||||||
if (!r || !r->objects || !r->objects->odb)
|
if (!r || !r->objects || !r->objects->odb)
|
||||||
return error(_("need a repository to verify a bundle"));
|
return error(_("need a repository to verify a bundle"));
|
||||||
|
|
||||||
repo_init_revisions(r, &revs, NULL);
|
|
||||||
for (i = 0; i < p->nr; i++) {
|
for (i = 0; i < p->nr; i++) {
|
||||||
struct string_list_item *e = p->items + i;
|
struct string_list_item *e = p->items + i;
|
||||||
const char *name = e->string;
|
const char *name = e->string;
|
||||||
struct object_id *oid = e->util;
|
struct object_id *oid = e->util;
|
||||||
struct object *o = parse_object(r, oid);
|
struct object *o = parse_object(r, oid);
|
||||||
if (o) {
|
if (o)
|
||||||
o->flags |= PREREQ_MARK;
|
|
||||||
add_pending_object(&revs, o, name);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
ret++;
|
ret++;
|
||||||
if (flags & VERIFY_BUNDLE_QUIET)
|
if (flags & VERIFY_BUNDLE_QUIET)
|
||||||
continue;
|
continue;
|
||||||
@ -223,37 +238,14 @@ int verify_bundle(struct repository *r,
|
|||||||
error("%s", message);
|
error("%s", message);
|
||||||
error("%s %s", oid_to_hex(oid), name);
|
error("%s %s", oid_to_hex(oid), name);
|
||||||
}
|
}
|
||||||
if (revs.pending.nr != p->nr)
|
if (ret)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
req_nr = revs.pending.nr;
|
|
||||||
setup_revisions(2, argv, &revs, NULL);
|
|
||||||
|
|
||||||
list_objects_filter_copy(&revs.filter, &header->filter);
|
if ((ret = check_connected(iterate_ref_map, &iter, &opts)))
|
||||||
|
error(_("some prerequisite commits exist in the object store, "
|
||||||
if (prepare_revision_walk(&revs))
|
"but are not connected to the repository's history"));
|
||||||
die(_("revision walk setup failed"));
|
|
||||||
|
|
||||||
i = req_nr;
|
|
||||||
while (i && (commit = get_revision(&revs)))
|
|
||||||
if (commit->object.flags & PREREQ_MARK)
|
|
||||||
i--;
|
|
||||||
|
|
||||||
for (i = 0; i < p->nr; i++) {
|
|
||||||
struct string_list_item *e = p->items + i;
|
|
||||||
const char *name = e->string;
|
|
||||||
const struct object_id *oid = e->util;
|
|
||||||
struct object *o = parse_object(r, oid);
|
|
||||||
assert(o); /* otherwise we'd have returned early */
|
|
||||||
if (o->flags & SHOWN)
|
|
||||||
continue;
|
|
||||||
ret++;
|
|
||||||
if (flags & VERIFY_BUNDLE_QUIET)
|
|
||||||
continue;
|
|
||||||
if (ret == 1)
|
|
||||||
error("%s", message);
|
|
||||||
error("%s %s", oid_to_hex(oid), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* TODO: preserve this verbose language. */
|
||||||
if (flags & VERIFY_BUNDLE_VERBOSE) {
|
if (flags & VERIFY_BUNDLE_VERBOSE) {
|
||||||
struct string_list *r;
|
struct string_list *r;
|
||||||
|
|
||||||
@ -282,15 +274,6 @@ int verify_bundle(struct repository *r,
|
|||||||
list_objects_filter_spec(&header->filter));
|
list_objects_filter_spec(&header->filter));
|
||||||
}
|
}
|
||||||
cleanup:
|
cleanup:
|
||||||
/* Clean up objects used, as they will be reused. */
|
|
||||||
for (i = 0; i < p->nr; i++) {
|
|
||||||
struct string_list_item *e = p->items + i;
|
|
||||||
struct object_id *oid = e->util;
|
|
||||||
commit = lookup_commit_reference_gently(r, oid, 1);
|
|
||||||
if (commit)
|
|
||||||
clear_commit_marks(commit, ALL_REV_FLAGS | PREREQ_MARK);
|
|
||||||
}
|
|
||||||
release_revisions(&revs);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,6 +285,8 @@ test_expect_success 'clone HTTP bundle' '
|
|||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'clone bundle list (HTTP, no heuristic)' '
|
test_expect_success 'clone bundle list (HTTP, no heuristic)' '
|
||||||
|
test_when_finished rm -f trace*.txt &&
|
||||||
|
|
||||||
cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" &&
|
cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" &&
|
||||||
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
[bundle]
|
[bundle]
|
||||||
@ -304,12 +306,26 @@ test_expect_success 'clone bundle list (HTTP, no heuristic)' '
|
|||||||
uri = $HTTPD_URL/bundle-4.bundle
|
uri = $HTTPD_URL/bundle-4.bundle
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
git clone --bundle-uri="$HTTPD_URL/bundle-list" \
|
GIT_TRACE2_EVENT="$(pwd)/trace-clone.txt" \
|
||||||
|
git clone --bundle-uri="$HTTPD_URL/bundle-list" \
|
||||||
clone-from clone-list-http 2>err &&
|
clone-from clone-list-http 2>err &&
|
||||||
! grep "Repository lacks these prerequisite commits" err &&
|
! grep "Repository lacks these prerequisite commits" err &&
|
||||||
|
|
||||||
git -C clone-from for-each-ref --format="%(objectname)" >oids &&
|
git -C clone-from for-each-ref --format="%(objectname)" >oids &&
|
||||||
git -C clone-list-http cat-file --batch-check <oids
|
git -C clone-list-http cat-file --batch-check <oids &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-1.bundle
|
||||||
|
$HTTPD_URL/bundle-2.bundle
|
||||||
|
$HTTPD_URL/bundle-3.bundle
|
||||||
|
$HTTPD_URL/bundle-4.bundle
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Sort the list, since the order is not well-defined
|
||||||
|
# without a heuristic.
|
||||||
|
test_remote_https_urls <trace-clone.txt | sort >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'clone bundle list (HTTP, any mode)' '
|
test_expect_success 'clone bundle list (HTTP, any mode)' '
|
||||||
@ -350,6 +366,658 @@ test_expect_success 'clone bundle list (HTTP, any mode)' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clone bundle list (http, creationToken)' '
|
||||||
|
test_when_finished rm -f trace*.txt &&
|
||||||
|
|
||||||
|
cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" &&
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = bundle-4.bundle
|
||||||
|
creationToken = 4
|
||||||
|
EOF
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-clone.txt" git \
|
||||||
|
clone --bundle-uri="$HTTPD_URL/bundle-list" \
|
||||||
|
"$HTTPD_URL/smart/fetch.git" clone-list-http-2 &&
|
||||||
|
|
||||||
|
git -C clone-from for-each-ref --format="%(objectname)" >oids &&
|
||||||
|
git -C clone-list-http-2 cat-file --batch-check <oids &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-4.bundle
|
||||||
|
$HTTPD_URL/bundle-3.bundle
|
||||||
|
$HTTPD_URL/bundle-2.bundle
|
||||||
|
$HTTPD_URL/bundle-1.bundle
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_remote_https_urls <trace-clone.txt >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clone incomplete bundle list (http, creationToken)' '
|
||||||
|
test_when_finished rm -f trace*.txt &&
|
||||||
|
|
||||||
|
cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" &&
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT=$(pwd)/trace-clone.txt \
|
||||||
|
git clone --bundle-uri="$HTTPD_URL/bundle-list" \
|
||||||
|
--single-branch --branch=base --no-tags \
|
||||||
|
"$HTTPD_URL/smart/fetch.git" clone-token-http &&
|
||||||
|
|
||||||
|
test_cmp_config -C clone-token-http "$HTTPD_URL/bundle-list" fetch.bundleuri &&
|
||||||
|
test_cmp_config -C clone-token-http 1 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-1.bundle
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_remote_https_urls <trace-clone.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# We now have only one bundle ref.
|
||||||
|
git -C clone-token-http for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs &&
|
||||||
|
|
||||||
|
# Add remaining bundles, exercising the "deepening" strategy
|
||||||
|
# for downloading via the creationToken heurisitc.
|
||||||
|
cat >>"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = bundle-4.bundle
|
||||||
|
creationToken = 4
|
||||||
|
EOF
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace1.txt" \
|
||||||
|
git -C clone-token-http fetch origin --no-tags \
|
||||||
|
refs/heads/merge:refs/heads/merge &&
|
||||||
|
test_cmp_config -C clone-token-http 4 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-4.bundle
|
||||||
|
$HTTPD_URL/bundle-3.bundle
|
||||||
|
$HTTPD_URL/bundle-2.bundle
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_remote_https_urls <trace1.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# We now have all bundle refs.
|
||||||
|
git -C clone-token-http for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
refs/bundles/left
|
||||||
|
refs/bundles/merge
|
||||||
|
refs/bundles/right
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'http clone with bundle.heuristic creates fetch.bundleURI' '
|
||||||
|
test_when_finished rm -rf fetch-http-4 trace*.txt &&
|
||||||
|
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-clone.txt" \
|
||||||
|
git clone --single-branch --branch=base \
|
||||||
|
--bundle-uri="$HTTPD_URL/bundle-list" \
|
||||||
|
"$HTTPD_URL/smart/fetch.git" fetch-http-4 &&
|
||||||
|
|
||||||
|
test_cmp_config -C fetch-http-4 "$HTTPD_URL/bundle-list" fetch.bundleuri &&
|
||||||
|
test_cmp_config -C fetch-http-4 1 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-1.bundle
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_remote_https_urls <trace-clone.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# only received base ref from bundle-1
|
||||||
|
git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs &&
|
||||||
|
|
||||||
|
cat >>"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Fetch the objects for bundle-2 _and_ bundle-3.
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace1.txt" \
|
||||||
|
git -C fetch-http-4 fetch origin --no-tags \
|
||||||
|
refs/heads/left:refs/heads/left \
|
||||||
|
refs/heads/right:refs/heads/right &&
|
||||||
|
test_cmp_config -C fetch-http-4 2 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-2.bundle
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_remote_https_urls <trace1.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# received left from bundle-2
|
||||||
|
git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
refs/bundles/left
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs &&
|
||||||
|
|
||||||
|
# No-op fetch
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace1b.txt" \
|
||||||
|
git -C fetch-http-4 fetch origin --no-tags \
|
||||||
|
refs/heads/left:refs/heads/left \
|
||||||
|
refs/heads/right:refs/heads/right &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
EOF
|
||||||
|
test_remote_https_urls <trace1b.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
cat >>"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = bundle-4.bundle
|
||||||
|
creationToken = 4
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# This fetch should skip bundle-3.bundle, since its objects are
|
||||||
|
# already local (we have the requisite commits for bundle-4.bundle).
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \
|
||||||
|
git -C fetch-http-4 fetch origin --no-tags \
|
||||||
|
refs/heads/merge:refs/heads/merge &&
|
||||||
|
test_cmp_config -C fetch-http-4 4 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-4.bundle
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_remote_https_urls <trace2.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# received merge ref from bundle-4, but right is missing
|
||||||
|
# because we did not download bundle-3.
|
||||||
|
git -C fetch-http-4 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
refs/bundles/left
|
||||||
|
refs/bundles/merge
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs &&
|
||||||
|
|
||||||
|
# No-op fetch
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace2b.txt" \
|
||||||
|
git -C fetch-http-4 fetch origin &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
EOF
|
||||||
|
test_remote_https_urls <trace2b.txt >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'creationToken heuristic with failed downloads (clone)' '
|
||||||
|
test_when_finished rm -rf download-* trace*.txt &&
|
||||||
|
|
||||||
|
# Case 1: base bundle does not exist, nothing can unbundle
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = fake.bundle
|
||||||
|
creationToken = 1
|
||||||
|
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = bundle-4.bundle
|
||||||
|
creationToken = 4
|
||||||
|
EOF
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-clone-1.txt" \
|
||||||
|
git clone --single-branch --branch=base \
|
||||||
|
--bundle-uri="$HTTPD_URL/bundle-list" \
|
||||||
|
"$HTTPD_URL/smart/fetch.git" download-1 &&
|
||||||
|
|
||||||
|
# Bundle failure does not set these configs.
|
||||||
|
test_must_fail git -C download-1 config fetch.bundleuri &&
|
||||||
|
test_must_fail git -C download-1 config fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-4.bundle
|
||||||
|
$HTTPD_URL/bundle-3.bundle
|
||||||
|
$HTTPD_URL/bundle-2.bundle
|
||||||
|
$HTTPD_URL/fake.bundle
|
||||||
|
EOF
|
||||||
|
test_remote_https_urls <trace-clone-1.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# All bundles failed to unbundle
|
||||||
|
git -C download-1 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
test_must_be_empty refs &&
|
||||||
|
|
||||||
|
# Case 2: middle bundle does not exist, only two bundles can unbundle
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = fake.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = bundle-4.bundle
|
||||||
|
creationToken = 4
|
||||||
|
EOF
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-clone-2.txt" \
|
||||||
|
git clone --single-branch --branch=base \
|
||||||
|
--bundle-uri="$HTTPD_URL/bundle-list" \
|
||||||
|
"$HTTPD_URL/smart/fetch.git" download-2 &&
|
||||||
|
|
||||||
|
# Bundle failure does not set these configs.
|
||||||
|
test_must_fail git -C download-2 config fetch.bundleuri &&
|
||||||
|
test_must_fail git -C download-2 config fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-4.bundle
|
||||||
|
$HTTPD_URL/bundle-3.bundle
|
||||||
|
$HTTPD_URL/fake.bundle
|
||||||
|
$HTTPD_URL/bundle-1.bundle
|
||||||
|
EOF
|
||||||
|
test_remote_https_urls <trace-clone-2.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# bundle-1 and bundle-3 could unbundle, but bundle-4 could not
|
||||||
|
git -C download-2 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
refs/bundles/right
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs &&
|
||||||
|
|
||||||
|
# Case 3: top bundle does not exist, rest unbundle fine.
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = fake.bundle
|
||||||
|
creationToken = 4
|
||||||
|
EOF
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-clone-3.txt" \
|
||||||
|
git clone --single-branch --branch=base \
|
||||||
|
--bundle-uri="$HTTPD_URL/bundle-list" \
|
||||||
|
"$HTTPD_URL/smart/fetch.git" download-3 &&
|
||||||
|
|
||||||
|
# As long as we have continguous successful downloads,
|
||||||
|
# we _do_ set these configs.
|
||||||
|
test_cmp_config -C download-3 "$HTTPD_URL/bundle-list" fetch.bundleuri &&
|
||||||
|
test_cmp_config -C download-3 3 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/fake.bundle
|
||||||
|
$HTTPD_URL/bundle-3.bundle
|
||||||
|
$HTTPD_URL/bundle-2.bundle
|
||||||
|
$HTTPD_URL/bundle-1.bundle
|
||||||
|
EOF
|
||||||
|
test_remote_https_urls <trace-clone-3.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# fake.bundle did not unbundle, but the others did.
|
||||||
|
git -C download-3 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
refs/bundles/left
|
||||||
|
refs/bundles/right
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs
|
||||||
|
'
|
||||||
|
|
||||||
|
# Expand the bundle list to include other interesting shapes, specifically
|
||||||
|
# interesting for use when fetching from a previous state.
|
||||||
|
#
|
||||||
|
# ---------------- bundle-7
|
||||||
|
# 7
|
||||||
|
# _/|\_
|
||||||
|
# ---/--|--\------ bundle-6
|
||||||
|
# 5 | 6
|
||||||
|
# --|---|---|----- bundle-4
|
||||||
|
# | 4 |
|
||||||
|
# | / \ /
|
||||||
|
# --|-|---|/------ bundle-3 (the client will be caught up to this point.)
|
||||||
|
# \ | 3
|
||||||
|
# ---\|---|------- bundle-2
|
||||||
|
# 2 |
|
||||||
|
# ----|---|------- bundle-1
|
||||||
|
# \ /
|
||||||
|
# 1
|
||||||
|
# |
|
||||||
|
# (previous commits)
|
||||||
|
test_expect_success 'expand incremental bundle list' '
|
||||||
|
(
|
||||||
|
cd clone-from &&
|
||||||
|
git checkout -b lefter left &&
|
||||||
|
test_commit 5 &&
|
||||||
|
git checkout -b righter right &&
|
||||||
|
test_commit 6 &&
|
||||||
|
git checkout -b top lefter &&
|
||||||
|
git merge -m "7" merge righter &&
|
||||||
|
|
||||||
|
git bundle create bundle-6.bundle lefter righter --not left right &&
|
||||||
|
git bundle create bundle-7.bundle top --not lefter merge righter &&
|
||||||
|
|
||||||
|
cp bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/"
|
||||||
|
) &&
|
||||||
|
git -C "$HTTPD_DOCUMENT_ROOT_PATH/fetch.git" fetch origin +refs/heads/*:refs/heads/*
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'creationToken heuristic with failed downloads (fetch)' '
|
||||||
|
test_when_finished rm -rf download-* trace*.txt &&
|
||||||
|
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git clone --single-branch --branch=left \
|
||||||
|
--bundle-uri="$HTTPD_URL/bundle-list" \
|
||||||
|
"$HTTPD_URL/smart/fetch.git" fetch-base &&
|
||||||
|
test_cmp_config -C fetch-base "$HTTPD_URL/bundle-list" fetch.bundleURI &&
|
||||||
|
test_cmp_config -C fetch-base 3 fetch.bundleCreationToken &&
|
||||||
|
|
||||||
|
# Case 1: all bundles exist: successful unbundling of all bundles
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = bundle-4.bundle
|
||||||
|
creationToken = 4
|
||||||
|
|
||||||
|
[bundle "bundle-6"]
|
||||||
|
uri = bundle-6.bundle
|
||||||
|
creationToken = 6
|
||||||
|
|
||||||
|
[bundle "bundle-7"]
|
||||||
|
uri = bundle-7.bundle
|
||||||
|
creationToken = 7
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp -r fetch-base fetch-1 &&
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-fetch-1.txt" \
|
||||||
|
git -C fetch-1 fetch origin &&
|
||||||
|
test_cmp_config -C fetch-1 7 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-7.bundle
|
||||||
|
$HTTPD_URL/bundle-6.bundle
|
||||||
|
$HTTPD_URL/bundle-4.bundle
|
||||||
|
EOF
|
||||||
|
test_remote_https_urls <trace-fetch-1.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# Check which bundles have unbundled by refs
|
||||||
|
git -C fetch-1 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
refs/bundles/left
|
||||||
|
refs/bundles/lefter
|
||||||
|
refs/bundles/merge
|
||||||
|
refs/bundles/right
|
||||||
|
refs/bundles/righter
|
||||||
|
refs/bundles/top
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs &&
|
||||||
|
|
||||||
|
# Case 2: middle bundle does not exist, only bundle-4 can unbundle
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = bundle-4.bundle
|
||||||
|
creationToken = 4
|
||||||
|
|
||||||
|
[bundle "bundle-6"]
|
||||||
|
uri = fake.bundle
|
||||||
|
creationToken = 6
|
||||||
|
|
||||||
|
[bundle "bundle-7"]
|
||||||
|
uri = bundle-7.bundle
|
||||||
|
creationToken = 7
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp -r fetch-base fetch-2 &&
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-fetch-2.txt" \
|
||||||
|
git -C fetch-2 fetch origin &&
|
||||||
|
|
||||||
|
# Since bundle-7 fails to unbundle, do not update creation token.
|
||||||
|
test_cmp_config -C fetch-2 3 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/bundle-7.bundle
|
||||||
|
$HTTPD_URL/fake.bundle
|
||||||
|
$HTTPD_URL/bundle-4.bundle
|
||||||
|
EOF
|
||||||
|
test_remote_https_urls <trace-fetch-2.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# Check which bundles have unbundled by refs
|
||||||
|
git -C fetch-2 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
refs/bundles/left
|
||||||
|
refs/bundles/merge
|
||||||
|
refs/bundles/right
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs &&
|
||||||
|
|
||||||
|
# Case 3: top bundle does not exist, rest unbundle fine.
|
||||||
|
cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "bundle-1"]
|
||||||
|
uri = bundle-1.bundle
|
||||||
|
creationToken = 1
|
||||||
|
|
||||||
|
[bundle "bundle-2"]
|
||||||
|
uri = bundle-2.bundle
|
||||||
|
creationToken = 2
|
||||||
|
|
||||||
|
[bundle "bundle-3"]
|
||||||
|
uri = bundle-3.bundle
|
||||||
|
creationToken = 3
|
||||||
|
|
||||||
|
[bundle "bundle-4"]
|
||||||
|
uri = bundle-4.bundle
|
||||||
|
creationToken = 4
|
||||||
|
|
||||||
|
[bundle "bundle-6"]
|
||||||
|
uri = bundle-6.bundle
|
||||||
|
creationToken = 6
|
||||||
|
|
||||||
|
[bundle "bundle-7"]
|
||||||
|
uri = fake.bundle
|
||||||
|
creationToken = 7
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp -r fetch-base fetch-3 &&
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-fetch-3.txt" \
|
||||||
|
git -C fetch-3 fetch origin &&
|
||||||
|
|
||||||
|
# As long as we have continguous successful downloads,
|
||||||
|
# we _do_ set the maximum creation token.
|
||||||
|
test_cmp_config -C fetch-3 6 fetch.bundlecreationtoken &&
|
||||||
|
|
||||||
|
# NOTE: the fetch skips bundle-4 since bundle-6 successfully
|
||||||
|
# unbundles itself and bundle-7 failed to download.
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/bundle-list
|
||||||
|
$HTTPD_URL/fake.bundle
|
||||||
|
$HTTPD_URL/bundle-6.bundle
|
||||||
|
EOF
|
||||||
|
test_remote_https_urls <trace-fetch-3.txt >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
|
# Check which bundles have unbundled by refs
|
||||||
|
git -C fetch-3 for-each-ref --format="%(refname)" "refs/bundles/*" >refs &&
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
refs/bundles/base
|
||||||
|
refs/bundles/left
|
||||||
|
refs/bundles/lefter
|
||||||
|
refs/bundles/right
|
||||||
|
refs/bundles/righter
|
||||||
|
EOF
|
||||||
|
test_cmp expect refs
|
||||||
|
'
|
||||||
|
|
||||||
# Do not add tests here unless they use the HTTP server, as they will
|
# Do not add tests here unless they use the HTTP server, as they will
|
||||||
# not run unless the HTTP dependencies exist.
|
# not run unless the HTTP dependencies exist.
|
||||||
|
|
||||||
|
@ -831,6 +831,52 @@ test_expect_success 'auto-discover multiple bundles from HTTP clone' '
|
|||||||
grep -f pattern trace.txt
|
grep -f pattern trace.txt
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'auto-discover multiple bundles from HTTP clone: creationToken heuristic' '
|
||||||
|
test_when_finished rm -rf "$HTTPD_DOCUMENT_ROOT_PATH/repo4.git" &&
|
||||||
|
test_when_finished rm -rf clone-heuristic trace*.txt &&
|
||||||
|
|
||||||
|
test_commit -C src newest &&
|
||||||
|
git -C src bundle create "$HTTPD_DOCUMENT_ROOT_PATH/newest.bundle" HEAD~1..HEAD &&
|
||||||
|
git clone --bare --no-local src "$HTTPD_DOCUMENT_ROOT_PATH/repo4.git" &&
|
||||||
|
|
||||||
|
cat >>"$HTTPD_DOCUMENT_ROOT_PATH/repo4.git/config" <<-EOF &&
|
||||||
|
[uploadPack]
|
||||||
|
advertiseBundleURIs = true
|
||||||
|
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
|
||||||
|
[bundle "everything"]
|
||||||
|
uri = $HTTPD_URL/everything.bundle
|
||||||
|
creationtoken = 1
|
||||||
|
|
||||||
|
[bundle "new"]
|
||||||
|
uri = $HTTPD_URL/new.bundle
|
||||||
|
creationtoken = 2
|
||||||
|
|
||||||
|
[bundle "newest"]
|
||||||
|
uri = $HTTPD_URL/newest.bundle
|
||||||
|
creationtoken = 3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/trace-clone.txt" \
|
||||||
|
git -c protocol.version=2 \
|
||||||
|
-c transfer.bundleURI=true clone \
|
||||||
|
"$HTTPD_URL/smart/repo4.git" clone-heuristic &&
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
$HTTPD_URL/newest.bundle
|
||||||
|
$HTTPD_URL/new.bundle
|
||||||
|
$HTTPD_URL/everything.bundle
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# We should fetch all bundles in the expected order.
|
||||||
|
test_remote_https_urls <trace-clone.txt >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
# DO NOT add non-httpd-specific tests here, because the last part of this
|
# DO NOT add non-httpd-specific tests here, because the last part of this
|
||||||
# test script is only executed when httpd is available and enabled.
|
# test script is only executed when httpd is available and enabled.
|
||||||
|
|
||||||
|
@ -250,4 +250,41 @@ test_expect_success 'parse config format edge cases: empty key or value' '
|
|||||||
test_cmp_config_output expect actual
|
test_cmp_config_output expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'parse config format: creationToken heuristic' '
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
[bundle "one"]
|
||||||
|
uri = http://example.com/bundle.bdl
|
||||||
|
creationToken = 123456
|
||||||
|
[bundle "two"]
|
||||||
|
uri = https://example.com/bundle.bdl
|
||||||
|
creationToken = 12345678901234567890
|
||||||
|
[bundle "three"]
|
||||||
|
uri = file:///usr/share/git/bundle.bdl
|
||||||
|
creationToken = 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test-tool bundle-uri parse-config expect >actual 2>err &&
|
||||||
|
test_must_be_empty err &&
|
||||||
|
test_cmp_config_output expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'parse config format edge cases: creationToken heuristic' '
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
[bundle]
|
||||||
|
version = 1
|
||||||
|
mode = all
|
||||||
|
heuristic = creationToken
|
||||||
|
[bundle "one"]
|
||||||
|
uri = http://example.com/bundle.bdl
|
||||||
|
creationToken = bogus
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test-tool bundle-uri parse-config expect >actual 2>err &&
|
||||||
|
grep "could not parse bundle list key creationToken with value '\''bogus'\''" err
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
@ -566,4 +566,44 @@ test_expect_success 'cloning from filtered bundle has useful error' '
|
|||||||
grep "cannot clone from filtered bundle" err
|
grep "cannot clone from filtered bundle" err
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'verify catches unreachable, broken prerequisites' '
|
||||||
|
test_when_finished rm -rf clone-from clone-to &&
|
||||||
|
git init clone-from &&
|
||||||
|
(
|
||||||
|
cd clone-from &&
|
||||||
|
git checkout -b base &&
|
||||||
|
test_commit A &&
|
||||||
|
git checkout -b tip &&
|
||||||
|
git commit --allow-empty -m "will drop by shallow" &&
|
||||||
|
git commit --allow-empty -m "will keep by shallow" &&
|
||||||
|
git commit --allow-empty -m "for bundle, not clone" &&
|
||||||
|
git bundle create tip.bundle tip~1..tip &&
|
||||||
|
git reset --hard HEAD~1 &&
|
||||||
|
git checkout base
|
||||||
|
) &&
|
||||||
|
BAD_OID=$(git -C clone-from rev-parse tip~1) &&
|
||||||
|
TIP_OID=$(git -C clone-from rev-parse tip) &&
|
||||||
|
git clone --depth=1 --no-single-branch \
|
||||||
|
"file://$(pwd)/clone-from" clone-to &&
|
||||||
|
(
|
||||||
|
cd clone-to &&
|
||||||
|
|
||||||
|
# Set up broken history by removing shallow markers
|
||||||
|
git update-ref -d refs/remotes/origin/tip &&
|
||||||
|
rm .git/shallow &&
|
||||||
|
|
||||||
|
# Verify should fail
|
||||||
|
test_must_fail git bundle verify \
|
||||||
|
../clone-from/tip.bundle 2>err &&
|
||||||
|
grep "some prerequisite commits .* are not connected" err &&
|
||||||
|
test_line_count = 1 err &&
|
||||||
|
|
||||||
|
# Unbundling should fail
|
||||||
|
test_must_fail git bundle unbundle \
|
||||||
|
../clone-from/tip.bundle 2>err &&
|
||||||
|
grep "some prerequisite commits .* are not connected" err &&
|
||||||
|
test_line_count = 1 err
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
@ -1767,6 +1767,14 @@ test_region () {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Given a GIT_TRACE2_EVENT log over stdin, writes to stdout a list of URLs
|
||||||
|
# sent to git-remote-https child processes.
|
||||||
|
test_remote_https_urls() {
|
||||||
|
grep -e '"event":"child_start".*"argv":\["git-remote-https",".*"\]' |
|
||||||
|
sed -e 's/{"event":"child_start".*"argv":\["git-remote-https","//g' \
|
||||||
|
-e 's/"\]}//g'
|
||||||
|
}
|
||||||
|
|
||||||
# Print the destination of symlink(s) provided as arguments. Basically
|
# Print the destination of symlink(s) provided as arguments. Basically
|
||||||
# the same as the readlink command, but it's not available everywhere.
|
# the same as the readlink command, but it's not available everywhere.
|
||||||
test_readlink () {
|
test_readlink () {
|
||||||
|
Loading…
Reference in New Issue
Block a user