From db4a9915b25ab682762eb73d65aab44e6bddcc1f Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 11 Jan 2023 21:39:15 +0300 Subject: Add --git-capabilities common option --- bpkg/common.cli | 18 +++++++++++ bpkg/fetch-git.cxx | 27 +++++++++++----- bpkg/options-types.hxx | 13 ++++++++ bpkg/repository-types.cli | 4 ++- bpkg/types-parsers.cxx | 79 +++++++++++++++++++++++++++++++++++++++++++++++ bpkg/types-parsers.hxx | 23 ++++++++++++++ 6 files changed, 156 insertions(+), 8 deletions(-) diff --git a/bpkg/common.cli b/bpkg/common.cli index d870a8d..0d97631 100644 --- a/bpkg/common.cli +++ b/bpkg/common.cli @@ -381,6 +381,24 @@ namespace bpkg "Assume the answer to all authentication prompts is \cb{no}." } + git_capabilities_map --git-capabilities + { + "=", + "Protocol capabilities () for a \cb{git} repository URL prefix + (). Valid values for the capabilities are \cb{dumb} (no shallow + clone support), \cb{smart} (support for shallow clone, but not for + fetching unadvertised commits), \cb{unadv} (support for shallow clone + and for fetching unadvertised commits). For example: + + \ + bpkg build https://example.org/foo.git#master \ + --git-capabilities https://example.org=smart + \ + + See \l{bpkg-repository-types(1)} for details on the \cb{git} protocol + capabilities." + } + string --pager // String to allow empty value. { "", diff --git a/bpkg/fetch-git.cxx b/bpkg/fetch-git.cxx index dc1e16a..4b1a242 100644 --- a/bpkg/fetch-git.cxx +++ b/bpkg/fetch-git.cxx @@ -556,12 +556,7 @@ namespace bpkg // URLs, if possible. That's why the function requires the git version // parameter. // - enum class capabilities - { - dumb, // No shallow clone support. - smart, // Support for shallow clone, but not for unadvertised refs fetch. - unadv // Support for shallow clone and for unadvertised refs fetch. - }; + using capabilities = git_protocol_capabilities; static capabilities sense_capabilities (const common_options& co, @@ -1132,7 +1127,25 @@ namespace bpkg // the first call, and so git version get assigned (and checked). // if (!cap) - cap = sense_capabilities (co, url (), git_ver); + { + const repository_url& u (url ()); + + // Check if the protocol capabilities are overridden for this + // repository. + // + const git_capabilities_map& gcs (co.git_capabilities ()); + + if (!gcs.empty () && u.scheme != repository_protocol::file) + { + auto i (gcs.find_sup (u.string ())); + + if (i != gcs.end ()) + cap = i->second; + } + + if (!cap) + cap = sense_capabilities (co, u, git_ver); + } return *cap; }; diff --git a/bpkg/options-types.hxx b/bpkg/options-types.hxx index 6576060..30d52a0 100644 --- a/bpkg/options-types.hxx +++ b/bpkg/options-types.hxx @@ -8,6 +8,8 @@ #include #include // move() +#include + #include namespace bpkg @@ -27,6 +29,17 @@ namespace bpkg json }; + enum class git_protocol_capabilities + { + dumb, // No shallow clone support. + smart, // Support for shallow clone, but not for unadvertised refs fetch. + unadv // Support for shallow clone and for unadvertised refs fetch. + }; + + using git_capabilities_map = butl::prefix_map; + // Qualified options. // // An option that uses this type can have its values qualified using the diff --git a/bpkg/repository-types.cli b/bpkg/repository-types.cli index 21ddaf9..1692a13 100644 --- a/bpkg/repository-types.cli +++ b/bpkg/repository-types.cli @@ -189,7 +189,9 @@ that they support fetching minimal history for tags and branches and may or may not support this for commit ids depending on the server configuration. Note, however, that unlike for \cb{http(s)://}, for these protocols \cb{bpkg} does not try to sense if fetching unadvertised commits is allowed and always -assumes that it is not. +assumes that it is not. Also note that the sensed or assumed protocol +capabilities can be overridden for a \cb{git} repository URL prefix using the +\cb{--git-capabilities} option (\l{bpkg-common-options(1)}). Based on this information, to achieve optimal results the recommended protocol for remote repositories is smart \cb{https://}. Additionally, if you are diff --git a/bpkg/types-parsers.cxx b/bpkg/types-parsers.cxx index e27f050..f23751d 100644 --- a/bpkg/types-parsers.cxx +++ b/bpkg/types-parsers.cxx @@ -3,6 +3,8 @@ #include +#include + namespace bpkg { namespace cli @@ -141,6 +143,83 @@ namespace bpkg throw invalid_value (o, v); } + void parser:: + parse (git_protocol_capabilities& x, bool& xs, scanner& s) + { + xs = true; + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + const string v (s.next ()); + if (v == "dumb") + x = git_protocol_capabilities::dumb; + else if (v == "smart") + x = git_protocol_capabilities::smart; + else if (v == "unadv") + x = git_protocol_capabilities::unadv; + else + throw invalid_value (o, v); + } + + void parser:: + parse (git_capabilities_map& x, bool& xs, scanner& s) + { + xs = true; + const char* o (s.next ()); + + if (!s.more ()) + throw missing_value (o); + + string v (s.next ()); + size_t p (v.rfind ('=')); + + if (p == string::npos) + throw invalid_value (o, v); + + string k (v, 0, p); + + // Verify that the key is a valid remote git repository URL prefix. + // + try + { + repository_url u (k); + + if (u.scheme == repository_protocol::file) + throw invalid_value (o, k, "local repository location"); + } + catch (const invalid_argument& e) + { + throw invalid_value (o, k, e.what ()); + } + + // Parse the protocol capabilities value. + // + int ac (2); + char* av[] = {const_cast (o), + const_cast (v.c_str () + p + 1)}; + + argv_scanner vs (0, ac, av); + + bool dummy; + parser::parse (x[k], dummy, vs); + } + + void parser:: + merge (git_capabilities_map& b, const git_capabilities_map& a) + { + for (const auto& o: a) + { + auto i (b.find (o.first)); + + if (i != b.end ()) + i->second = o.second; + else + b.emplace (o.first, o.second); + } + } + void parser:: parse (stdout_format& x, bool& xs, scanner& s) { diff --git a/bpkg/types-parsers.hxx b/bpkg/types-parsers.hxx index dba459a..7bbb414 100644 --- a/bpkg/types-parsers.hxx +++ b/bpkg/types-parsers.hxx @@ -84,6 +84,29 @@ namespace bpkg }; template <> + struct parser + { + static void + parse (git_protocol_capabilities&, bool&, scanner&); + + static void + merge (git_protocol_capabilities& b, const git_protocol_capabilities& a) + { + b = a; + } + }; + + template <> + struct parser + { + static void + parse (git_capabilities_map&, bool&, scanner&); + + static void + merge (git_capabilities_map&, const git_capabilities_map&); + }; + + template <> struct parser { static void -- cgit v1.1