aboutsummaryrefslogtreecommitdiff
path: root/bpkg/fetch-git.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'bpkg/fetch-git.cxx')
-rw-r--r--bpkg/fetch-git.cxx212
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.
//