diff options
Diffstat (limited to 'bpkg/fetch-git.cxx')
-rw-r--r-- | bpkg/fetch-git.cxx | 212 |
1 files changed, 139 insertions, 73 deletions
diff --git a/bpkg/fetch-git.cxx b/bpkg/fetch-git.cxx index d2c30a1..47b65da 100644 --- a/bpkg/fetch-git.cxx +++ b/bpkg/fetch-git.cxx @@ -6,7 +6,7 @@ #include <map> #include <libbutl/git.hxx> -#include <libbutl/filesystem.hxx> // path_entry +#include <libbutl/filesystem.hxx> // path_entry(), try_rmsymlink() #include <libbutl/path-pattern.hxx> #include <libbutl/semantic-version.hxx> #include <libbutl/standard-version.hxx> // parse_standard_version() @@ -927,11 +927,19 @@ namespace bpkg static repository_refs_map repository_refs; - // It is assumed that sense_capabilities() function was already called for - // the URL. + // If the advertized refs/commits are already cached for the specified URL, + // then return them from the cache. Otherwise, query them and cache. In the + // latter case, optionally, probe the URL first, calling the specified probe + // function. Otherwise (the probe function is not specified), it is assumed + // that the URL has already been probed (sense_capabilities() function was + // already called for this URL, etc). // + using probe_function = void (); + static const refs& - load_refs (const common_options& co, const repository_url& url) + load_refs (const common_options& co, + const repository_url& url, + const function<probe_function>& probe = nullptr) { tracer trace ("load_refs"); @@ -944,6 +952,9 @@ namespace bpkg if ((verb && !co.no_progress ()) || co.progress ()) text << "querying " << url; + if (probe) + probe (); + refs rs; for (;;) // Breakout loop. @@ -1175,31 +1186,29 @@ namespace bpkg return *cap; }; - auto references = [&co, &url, &caps] (const string& refname, - bool abbr_commit) + function<probe_function> probe ([&caps] () {caps ();}); + + auto references = [&co, &url, &probe] (const string& refname, + bool abbr_commit) -> refs::search_result { // Make sure the URL is probed before running git-ls-remote (see // load_refs() for details). // - caps (); - - return load_refs (co, url ()).search_names (refname, abbr_commit); + return load_refs (co, url (), probe).search_names (refname, abbr_commit); }; // Return the default reference set (see repository-types(1) for details). // - auto default_references = [&co, &url, &caps] () -> refs::search_result + auto default_references = [&co, &url, &probe] () -> refs::search_result { // Make sure the URL is probed before running git-ls-remote (see // load_refs() for details). // - caps (); - refs::search_result r; vector<standard_version> vs; // Parallel to search_result. - for (const ref& rf: load_refs (co, url ())) + for (const ref& rf: load_refs (co, url (), probe)) { if (!rf.peeled && rf.name.compare (0, 11, "refs/tags/v") == 0) { @@ -1342,6 +1351,9 @@ namespace bpkg { // Reduce the reference to the commit id. // + // Note that it is assumed that the URL has already been probed by + // the above default_references() or references() call. + // const string& c (load_refs (co, url ()).peel (r).commit); if (!rf.exclusion) @@ -1358,18 +1370,23 @@ namespace bpkg remove_spec (c); } } + // // Check if this is a commit exclusion and remove the corresponding // fetch spec if that's the case. // else if (rf.exclusion) + { remove_spec (*rf.commit); - + } + // // Check if the commit is already fetched and, if that's the case, save // it, indicating that no fetch is required. // else if (commit_fetched (co, dir, *rf.commit)) + { add_spec (*rf.commit); - + } + // // If the shallow fetch is possible for the commit, then we fetch it. // else if (shallow ()) @@ -1378,6 +1395,7 @@ namespace bpkg add_spec (*rf.commit, strings ({*rf.commit}), true /* shallow */); } + // // If the shallow fetch is not possible for the commit but the refname // containing the commit is specified, then we fetch the whole history // of references the refname translates to. @@ -1402,6 +1420,7 @@ namespace bpkg add_spec (*rf.commit, move (specs)); // Fetch deep. } + // // Otherwise, if the refname is not specified and the commit is not // advertised, we have to fetch the whole repository history. // @@ -2412,64 +2431,6 @@ namespace bpkg } void - git_checkout (const common_options& co, - const dir_path& dir, - const string& commit) - { - // For some (probably valid) reason the hard reset command doesn't remove - // a submodule directory that is not plugged into the repository anymore. - // It also prints the non-suppressible warning like this: - // - // warning: unable to rmdir libbar: Directory not empty - // - // That's why we run the clean command afterwards. It may also be helpful - // if we produce any untracked files in the tree between checkouts down - // the road. - // - if (!run_git (co, - co.git_option (), - "-C", dir, - "reset", - "--hard", - verb < 2 ? "-q" : nullptr, - commit)) - fail << "unable to reset to " << commit << endg; - - if (!run_git (co, - co.git_option (), - "-C", dir, - "clean", - "-d", - "-x", - "-ff", - verb < 2 ? "-q" : nullptr)) - fail << "unable to clean " << dir << endg; - - // Iterate over the registered submodules and "deinitialize" those whose - // tip commit has changed. - // - // Note that not doing so will make git treat the repository worktree as - // modified (new commits in submodule). Also the caller may proceed with - // an inconsistent repository, having no indication that they need to - // re-run git_checkout_submodules(). - // - for (const submodule& sm: - find_submodules (co, dir, dir_path () /* prefix */)) - { - dir_path sd (dir / sm.path); // Submodule full directory path. - - optional<string> commit (submodule_commit (co, sd)); - - // Note that we may re-initialize the submodule later due to the empty - // directory (see checkout_submodules() for details). Seems that git - // has no problem with such a re-initialization. - // - if (commit && *commit != sm.commit) - rm_r (sd, false /* dir_itself */); - } - } - - void git_checkout_submodules (const common_options& co, const repository_location& rl, const dir_path& dir) @@ -2593,6 +2554,111 @@ namespace bpkg submodule_failure ("unable to list repository files", prefix); } + static void + git_checkout (const common_options& co, + const dir_path& dir, + const string& commit, +#ifdef _WIN32 + const dir_path& prefix) + { + // Note that on Windows git may incorrectly deduce the type of a symlink + // it needs to create. Thus, it is recommended to specify the link type + // for directory symlinks in the project's .gitattributes file (see the + // "Using Symlinks in build2 Projects" article for background). However, + // it turns out that if, for example, such a type has not been specified + // for some early package version and this have been fixed in some later + // version, then it may still cause problems even when this later package + // version is being built. That happens because during the git repository + // fetch, to produce the available packages list, bpkg sequentially checks + // out multiple package versions. Git, on the other hand, does not bother + // re-creating an existing symlink on check out (or git-reset which we + // use) even though .gitattributes indicates that its type has changed. + // Thus, on Windows, let's just remove all the existing symlinks prior to + // running git-reset. + // + for (const auto& l: find_symlinks (co, dir, prefix)) + { + // Note that the symlinks may be filesystem-agnostic (see + // fixup_worktree() for details) and thus we check the types of the + // filesystem entries prior to their removal. Also note that the + // try_rmsymlink() implementation doesn't actually distinguish between + // the directory and file symlinks and thus we always remove them as the + // file symlinks. + // + path p (dir / l.first); + + pair<bool, entry_stat> e ( + path_entry (p, false /* follow_symlink */, true /* ignore_error */)); + + if (e.first && e.second.type == entry_type::symlink) + try_rmsymlink (p, false /* dir */, true /* ignore_error */); + } +#else + const dir_path&) + { +#endif + + // For some (probably valid) reason the hard reset command doesn't remove + // a submodule directory that is not plugged into the repository anymore. + // It also prints the non-suppressible warning like this: + // + // warning: unable to rmdir libbar: Directory not empty + // + // That's why we run the clean command afterwards. It may also be helpful + // if we produce any untracked files in the tree between checkouts down + // the road. + // + if (!run_git (co, + co.git_option (), + "-C", dir, + "reset", + "--hard", + verb < 2 ? "-q" : nullptr, + commit)) + fail << "unable to reset to " << commit << endg; + + if (!run_git (co, + co.git_option (), + "-C", dir, + "clean", + "-d", + "-x", + "-ff", + verb < 2 ? "-q" : nullptr)) + fail << "unable to clean " << dir << endg; + + // Iterate over the registered submodules and "deinitialize" those whose + // tip commit has changed. + // + // Note that not doing so will make git treat the repository worktree as + // modified (new commits in submodule). Also the caller may proceed with + // an inconsistent repository, having no indication that they need to + // re-run git_checkout_submodules(). + // + for (const submodule& sm: + find_submodules (co, dir, dir_path () /* prefix */)) + { + dir_path sd (dir / sm.path); // Submodule full directory path. + + optional<string> commit (submodule_commit (co, sd)); + + // Note that we may re-initialize the submodule later due to the empty + // directory (see checkout_submodules() for details). Seems that git + // has no problem with such a re-initialization. + // + if (commit && *commit != sm.commit) + rm_r (sd, false /* dir_itself */); + } + } + + void + git_checkout (const common_options& co, + const dir_path& dir, + const string& commit) + { + git_checkout (co, dir, commit, dir_path () /* prefix */); + } + // Verify symlinks in a working tree of a top repository or submodule, // recursively. // |