diff options
Diffstat (limited to 'bpkg/fetch-git.cxx')
-rw-r--r-- | bpkg/fetch-git.cxx | 725 |
1 files changed, 608 insertions, 117 deletions
diff --git a/bpkg/fetch-git.cxx b/bpkg/fetch-git.cxx index 0c21af6..d2c30a1 100644 --- a/bpkg/fetch-git.cxx +++ b/bpkg/fetch-git.cxx @@ -5,12 +5,11 @@ #include <map> -#include <libbutl/git.mxx> -#include <libbutl/utility.mxx> // digit(), xdigit() -#include <libbutl/filesystem.mxx> // path_entry -#include <libbutl/path-pattern.mxx> -#include <libbutl/semantic-version.mxx> -#include <libbutl/standard-version.mxx> // parse_standard_version() +#include <libbutl/git.hxx> +#include <libbutl/filesystem.hxx> // path_entry +#include <libbutl/path-pattern.hxx> +#include <libbutl/semantic-version.hxx> +#include <libbutl/standard-version.hxx> // parse_standard_version() #include <bpkg/diagnostics.hxx> @@ -286,20 +285,7 @@ namespace bpkg try { - ifdstream is (move (pipe.in), fdstream_mode::skip, ifdstream::badbit); - - // We could probably write something like this, instead: - // - // *diag_stream << is.rdbuf () << flush; - // - // However, it would never throw and we could potentially miss the - // reading failure, unless we decide to additionally mess with the - // diagnostics stream exception mask. - // - for (string l; !eof (getline (is, l)); ) - *diag_stream << l << endl; - - is.close (); + dump_stderr (move (pipe.in)); // Fall through. } @@ -550,7 +536,11 @@ namespace bpkg // For HTTP(S) sense the protocol type by sending the first HTTP request of // the fetch operation handshake and analyzing the first line of the // response. Fail if connecting to the server failed, the response code - // differs from 200, or reading the response body failed. + // differs from 200 and 401, or reading the response body failed. If the + // response code is 401 (requires authentication), then consider protocol as + // smart. The thinking here is that a git repository with support for + // authentication is likely one of the hosting places (like git{hub,lab}) + // and is unlikely to be dumb. // // Note that, as a side-effect, this function checks the HTTP(S) server // availability and so must be called prior to any git command that involves @@ -566,21 +556,16 @@ 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, - repository_url url, + const repository_url& repo_url, const semantic_version& git_ver) { - assert (url.path); + assert (repo_url.path); - switch (url.scheme) + switch (repo_url.scheme) { case repository_protocol::git: case repository_protocol::ssh: @@ -589,6 +574,9 @@ namespace bpkg case repository_protocol::https: break; // Ask the server (see below). } + // Craft the URL for sensing the capabilities. + // + repository_url url (repo_url); path& up (*url.path); if (!up.to_directory ()) @@ -602,19 +590,94 @@ namespace bpkg url.query = "service=git-upload-pack"; string u (url.string ()); - process pr (start_fetch (co, - u, - path () /* out */, - "git/" + git_ver.string ())); + + // Start fetching, also trying to retrieve the HTTP status code. + // + // We unset failbit to properly handle an empty response (no refs) from + // the dumb server. + // + ifdstream is (ifdstream::badbit); + + pair<process, uint16_t> ps ( + start_fetch_http (co, + u, + is /* out */, + fdstream_mode::skip | fdstream_mode::binary, + stderr_mode::redirect_quiet, + "git/" + git_ver.string ())); + + process& pr (ps.first); + + // If the fetch program stderr is redirected, then read it out and pass + // through. + // + auto dump_stderr = [&pr] () + { + if (pr.in_efd != nullfd) + try + { + bpkg::dump_stderr (move (pr.in_efd)); + } + catch (const io_error&) + { + // Not much we can do here. + } + }; try { - // We unset failbit to properly handle an empty response (no refs) from - // the dumb server. + // If authentication is required (HTTP status code is 401), then + // consider the protocol as smart. Drop the diagnostics if that's the + // case and dump it otherwise. // - ifdstream is (move (pr.in_ofd), - fdstream_mode::skip | fdstream_mode::binary, - ifdstream::badbit); + if (ps.second == 401) + { + if (verb >= 2) + { + info << "smart git protocol assumed for repository " << repo_url + << " due to authentication requirement" << + info << "use --git-capabilities to override or suppress this " + << "diagnostics"; + } + + // Note that we don't care about the process exit code here and just + // silently wait for the process completion in the process object + // destructor. We, however, close the stream (reading out the + // content), so that the process won't get blocked writing to it. + // + // Also note that we drop the potentially redirected process stderr + // stream content. We even don't read it out, since we assume it fully + // fits into the pipe buffer. + // + is.close (); + + return capabilities::smart; + } + + // Fail on any other HTTP error (e.g., 404). In the case of a success + // code other than 200 (e.g. 204 (No Content)) just let the capabilities + // detection to take its course. + // + if (ps.second != 0 && (ps.second < 200 || ps.second >= 300)) + { + // Note that we don't care about the process exit code here (see above + // for the reasoning). + // + is.close (); + + // Dump the potentially redirected process stderr stream content since + // it may be helpful to the user. + // + // Note, however, that we don't know if it really contains the error + // description since the fetch program may even exit successfully (see + // start_fetch_http() for details). Thus, we additionally print the + // HTTP status code in the diagnostics. + // + dump_stderr (); + + fail << "unable to fetch " << url << + info << "HTTP status code " << ps.second << endg; + } string l; getline (is, l); // Is empty if no refs returned by the dumb server. @@ -640,7 +703,7 @@ namespace bpkg ? capabilities::smart : capabilities::dumb); - // If the transport is smart let's see it the server also supports + // If the transport is smart let's see if the server also supports // unadvertised refs fetch. // if (r == capabilities::smart && !is.eof ()) @@ -668,6 +731,8 @@ namespace bpkg is.close (); + dump_stderr (); + if (pr.wait ()) return r; @@ -675,6 +740,8 @@ namespace bpkg } catch (const io_error&) { + dump_stderr (); + if (pr.wait ()) fail << "unable to read fetched " << url << endg; @@ -874,28 +941,29 @@ namespace bpkg if (i != repository_refs.end ()) return i->second; - if (verb && !co.no_progress ()) + if ((verb && !co.no_progress ()) || co.progress ()) text << "querying " << url; refs rs; - fdpipe pipe (open_pipe ()); - - // Note: ls-remote doesn't print anything to stderr, so no progress - // suppression is required. - // - process pr (start_git (co, - pipe, 2 /* stderr */, - timeout_opts (co, url.scheme), - co.git_option (), - "ls-remote", - to_git_url (url))); - - // Shouldn't throw, unless something is severely damaged. - // - pipe.out.close (); for (;;) // Breakout loop. { + fdpipe pipe (open_pipe ()); + + // Note: ls-remote doesn't print anything to stderr, so no progress + // suppression is required. + // + process pr (start_git (co, + pipe, 2 /* stderr */, + timeout_opts (co, url.scheme), + co.git_option (), + "ls-remote", + to_git_url (url))); + + // Shouldn't throw, unless something is severely damaged. + // + pipe.out.close (); + try { ifdstream is (move (pipe.in), fdstream_mode::skip, ifdstream::badbit); @@ -1084,7 +1152,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; }; @@ -1402,7 +1488,7 @@ namespace bpkg catch (const logic_error&) { fail << "'" << s << "' doesn't appear to contain a git commit " - "timestamp" << endg; + << "timestamp" << endg; } } @@ -1505,7 +1591,7 @@ namespace bpkg if (progress) { - if (verb == 1 && stderr_term) + if ((verb == 1 && stderr_term) || co.progress ()) v.push_back ("--progress"); } else @@ -1557,7 +1643,7 @@ namespace bpkg // Print progress. // - if (verb && !co.no_progress ()) + if ((verb && !co.no_progress ()) || co.progress ()) { // Note that the clone command prints the following line prior to the // progress lines: @@ -1581,7 +1667,7 @@ namespace bpkg dr << "from " << url (); if (verb >= 2) - dr << " in '" << dir.posix_string () << "'"; // Is used by tests. + dr << " in '" << dir.string () << "'"; // Used by tests. } // Print information messages prior to the deep fetching. @@ -1702,6 +1788,255 @@ namespace bpkg submodule_failure (d, prefix, e); }; + // Use git-config to obtain the submodules names/paths and then + // git-ls-files to obtain their commits. + // + // Note that previously we used git-submodule--helper-list subcommand to + // obtain the submodules commits/paths and then git-submodule--helper-name + // to obtain their names. However, git 2.38 has removed these subcommands. + + // Obtain the submodules names/paths. + // + for (;;) // Breakout loop. + { + fdpipe pipe (open_pipe ()); + + process pr (start_git (co, + pipe, 2 /* stderr */, + co.git_option (), + "-C", dir, + "config", + "--list", + "--file", gitmodules_file, + "-z")); + + // Shouldn't throw, unless something is severely damaged. + // + pipe.out.close (); + + try + { + ifdstream is (move (pipe.in), fdstream_mode::skip, ifdstream::badbit); + + for (string l; !eof (getline (is, l, '\0')); ) + { + auto bad = [&l] () + { + throw runtime_error ("invalid submodule option '" + l + '\''); + }; + + // The submodule configuration option line is NULL-terminated and + // has the following form: + // + // submodule.<submodule-name>.<option-name><NEWLINE><value> + // + // For example: + // + // submodule.style.path + // doc/style + // + l4 ([&]{trace << "submodule option: " << l;}); + + // If this is a submodule path option, then extract its name and + // path and add the entry to the resulting list. + // + size_t n (l.find ('\n')); + + if (n != string::npos && + n >= 15 && + l.compare (0, 10, "submodule.") == 0 && + l.compare (n - 5, 5, ".path") == 0) + { + string nm (l, 10, n - 15); + dir_path p (l, n + 1, l.size () - n - 1); + + // For good measure verify that the name and path are not empty. + // + if (nm.empty () || p.empty ()) + bad (); + + r.push_back (submodule {move (p), move (nm), empty_string}); + } + } + + is.close (); + + if (pr.wait ()) + break; + + // Fall through. + } + catch (const invalid_path& e) + { + if (pr.wait ()) + failure ("invalid submodule directory path '" + e.path + '\''); + + // Fall through. + } + catch (const io_error& e) + { + if (pr.wait ()) + failure ("unable to read submodule options", &e); + + // Fall through. + } + // Note that the io_error class inherits from the runtime_error class, + // so this catch-clause must go last. + // + catch (const runtime_error& e) + { + if (pr.wait ()) + failure (e.what ()); + + // Fall through. + } + + // We should only get here if the child exited with an error status. + // + assert (!pr.wait ()); + + failure ("unable to list submodule options"); + } + + // Note that we could potentially bail out here if the submodules list is + // empty. Let's however continue and verify that via git-ls-files, for + // good measure. + + // Complete the resulting submodules information with their commits. + // + for (;;) // Breakout loop. + { + fdpipe pipe (open_pipe ()); + + process pr (start_git (co, + pipe, 2 /* stderr */, + co.git_option (), + "-C", dir, + "ls-files", + "--stage", + "-z")); + + // Shouldn't throw, unless something is severely damaged. + // + pipe.out.close (); + + try + { + ifdstream is (move (pipe.in), fdstream_mode::skip, ifdstream::badbit); + + for (string l; !eof (getline (is, l, '\0')); ) + { + auto bad = [&l] () + { + throw runtime_error ("invalid file description '" + l + '\''); + }; + + // The line describing a file is NULL-terminated and has the + // following form: + // + // <mode><SPACE><object><SPACE><stage><TAB><path> + // + // The mode is a 6-digit octal representation of the file type and + // permission bits mask. For a submodule directory it is 160000 (see + // git index format documentation for gitlink object type). For + // example: + // + // 160000 59dcc1bea3509e37b65905ac472f86f4c55eb510 0 doc/style + // + if (!(l.size () > 50 && l[48] == '0' && l[49] == '\t')) + bad (); + + // For submodules permission bits are always zero, so we can match + // the mode as a string. + // + if (l.compare (0, 6, "160000") == 0) + { + l4 ([&]{trace << "submodule: " << l;}); + + dir_path d (l, 50, l.size () - 50); + + auto i (find_if (r.begin (), r.end (), + [&d] (const submodule& sm) {return sm.path == d;})); + + if (i == r.end ()) + bad (); + + i->commit = string (l, 7, 40); + } + } + + is.close (); + + if (pr.wait ()) + break; + + // Fall through. + } + catch (const invalid_path& e) + { + if (pr.wait ()) + failure ("invalid submodule directory path '" + e.path + '\''); + + // Fall through. + } + catch (const io_error& e) + { + if (pr.wait ()) + failure ("unable to read repository file list", &e); + + // Fall through. + } + // Note that the io_error class inherits from the runtime_error class, + // so this catch-clause must go last. + // + catch (const runtime_error& e) + { + if (pr.wait ()) + failure (e.what ()); + + // Fall through. + } + + // We should only get here if the child exited with an error status. + // + assert (!pr.wait ()); + + failure ("unable to list repository files"); + } + + // Make sure that we have deduced commits for all the submodules. + // + for (const submodule& sm: r) + { + if (sm.commit.empty ()) + failure ("unable to deduce commit for submodule " + sm.name); + } + + return r; + } + + // @@ TMP Old, submodule--helper-{list,name} subcommands-based, + // implementation of find_submodules(). + // +#if 0 + static submodules + find_submodules (const common_options& co, + const dir_path& dir, + const dir_path& prefix, + bool gitmodules = true) + { + tracer trace ("find_submodules"); + + submodules r; + + if (gitmodules && !exists (dir / gitmodules_file)) + return r; + + auto failure = [&prefix] (const string& d, const exception* e = nullptr) + { + submodule_failure (d, prefix, e); + }; + fdpipe pipe (open_pipe ()); process pr (start_git (co, @@ -1731,7 +2066,7 @@ namespace bpkg l4 ([&]{trace << "submodule: " << l;}); if (!(l.size () > 50 && l[48] == '0' && l[49] == '\t')) - throw runtime_error ("invalid submodule description '" + l + "'"); + throw runtime_error ("invalid submodule description '" + l + '\''); dir_path d (string (l, 50)); @@ -1765,7 +2100,7 @@ namespace bpkg catch (const invalid_path& e) { if (pr.wait ()) - failure ("invalid submodule path '" + e.path + "'"); + failure ("invalid submodule path '" + e.path + '\''); // Fall through. } @@ -1793,6 +2128,7 @@ namespace bpkg submodule_failure ("unable to list submodules", prefix); } +#endif // Return commit id for the submodule directory or nullopt if the submodule // is not initialized (directory doesn't exist, doesn't contain .git entry, @@ -1840,13 +2176,15 @@ namespace bpkg co.git_option (), "-C", dir, - // Note that older git versions don't recognize the --super-prefix - // option but seem to behave correctly without any additional - // efforts when it is omitted. + // Note that git versions outside the [2.14.0 2.38.0) range don't + // recognize the --super-prefix option but seem to behave correctly + // without any additional efforts when it is omitted. // - !prefix.empty () && git_ver >= semantic_version {2, 14, 0} - ? strings ({"--super-prefix", prefix.posix_representation ()}) - : strings (), + (!prefix.empty () && + git_ver >= semantic_version {2, 14, 0} && + git_ver < semantic_version {2, 38, 0} + ? strings ({"--super-prefix", prefix.posix_representation ()}) + : strings ()), "submodule--helper", "init", verb < 2 ? "-q" : nullptr)) @@ -1884,7 +2222,7 @@ namespace bpkg if (u && *u == "none") { - if (verb >= 2 && !co.no_progress ()) + if ((verb >= 2 && !co.no_progress ()) || co.progress ()) text << "skipping submodule '" << psd << "'"; // Note that the submodule can be enabled for some other snapshot we @@ -1959,7 +2297,7 @@ namespace bpkg catch (const invalid_path& e) { failure ("invalid submodule '" + sm.name + "' repository path '" + - e.path + "'"); + e.path + '\''); } catch (const invalid_argument& e) { @@ -1994,7 +2332,7 @@ namespace bpkg // Let's make the message match the git-submodule script output (again, // except for capitalization). // - if (verb && !co.no_progress ()) + if ((verb && !co.no_progress ()) || co.progress ()) text << "submodule path '" << psd << "': checked out '" << sm.commit << "'"; @@ -2149,21 +2487,11 @@ namespace bpkg dir_path () /* prefix */); } -#ifndef _WIN32 - - // Noop on POSIX. - // - bool - git_fixup_worktree (const common_options&, const dir_path&, bool) - { - return false; - } - -#else - - // Find symlinks in the repository (non-recursive submodule-wise). + // Find symlinks in a working tree of a top repository or submodule + // (non-recursive submodule-wise) and return their relative paths together + // with the respective git object ids. // - static paths + static vector<pair<path, string>> find_symlinks (const common_options& co, const dir_path& dir, const dir_path& prefix) @@ -2194,12 +2522,12 @@ namespace bpkg try { - paths r; + vector<pair<path, string>> r; ifdstream is (move (pipe.in), fdstream_mode::skip, ifdstream::badbit); for (string l; !eof (getline (is, l, '\0')); ) { - // The line describing a file is NUL-terminated and has the following + // The line describing a file is NULL-terminated and has the following // form: // // <mode><SPACE><object><SPACE><stage><TAB><path> @@ -2209,16 +2537,18 @@ namespace bpkg // // 100644 165b42ec7a10fb6dd4a60b756fa1966c1065ef85 0 README // - l4 ([&]{trace << "file: " << l;}); - if (!(l.size () > 50 && l[48] == '0' && l[49] == '\t')) - throw runtime_error ("invalid file description '" + l + "'"); + throw runtime_error ("invalid file description '" + l + '\''); // For symlinks permission bits are always zero, so we can match the // mode as a string. // if (l.compare (0, 6, "120000") == 0) - r.push_back (path (string (l, 50))); + { + l4 ([&]{trace << "symlink: " << l;}); + + r.push_back (make_pair (path (string (l, 50)), string (l, 7, 40))); + } } is.close (); @@ -2231,7 +2561,7 @@ namespace bpkg catch (const invalid_path& e) { if (pr.wait ()) - failure ("invalid repository symlink path '" + e.path + "'"); + failure ("invalid repository symlink path '" + e.path + '\''); // Fall through. } @@ -2263,6 +2593,127 @@ namespace bpkg submodule_failure ("unable to list repository files", prefix); } + // Verify symlinks in a working tree of a top repository or submodule, + // recursively. + // + // Specifically, fail if the symlink target is not a valid relative path or + // refers outside the top repository directory. + // + static void + verify_symlinks (const common_options& co, + const dir_path& dir, + const dir_path& prefix) + { + auto failure = [&prefix] (const string& d, const exception* e = nullptr) + { + submodule_failure (d, prefix, e); + }; + + for (const auto& l: find_symlinks (co, dir, prefix)) + { + const path& lp (l.first); + + // Obtain the symlink target path. + // + path tp; + + fdpipe pipe (open_pipe ()); + process pr (start_git (co, + pipe, 2 /* stderr */, + co.git_option (), + "-C", dir, + "cat-file", + "-p", + l.second + "^{object}")); + + // Shouldn't throw, unless something is severely damaged. + // + pipe.out.close (); + + try + { + ifdstream is (move (pipe.in), fdstream_mode::skip); + string s (is.read_text ()); // Note: is not newline-terminated. + is.close (); + + if (pr.wait () && !s.empty ()) + try + { + tp = path (move (s)); + } + catch (const invalid_path& e) + { + failure ("invalid target path '" + e.path + "' for symlink '" + + lp.string () + '\'', + &e); + } + + // Fall through. + } + catch (const io_error&) + { + // Fall through. + } + + if (tp.empty ()) + failure ("unable to read target path for symlink '" + lp.string () + + "'"); + + // Verify that the symlink target path is relative. + // + if (tp.absolute ()) + failure ("absolute target path '" + tp.string () + "' for symlink '" + + lp.string () + '\''); + + // Verify that the symlink target path refers inside the top repository + // directory. + // + path rtp (prefix / lp.directory () / tp); // Relative to top directory. + rtp.normalize (); // Note: can't throw since the path is relative. + + // Normalizing non-empty path can't end up with an empty path. + // + assert (!rtp.empty ()); + + // Make sure that the relative to the top repository directory target + // path doesn't start with '..'. + // + if (dir_path::traits_type::parent (*rtp.begin ())) + failure ("target path '" + tp.string () + "' for symlink '" + + lp.string () + "' refers outside repository"); + } + + // Verify symlinks for submodules. + // + for (const submodule& sm: find_submodules (co, dir, prefix)) + verify_symlinks (co, dir / sm.path, prefix / sm.path); + } + + void + git_verify_symlinks (const common_options& co, const dir_path& dir) + { + if ((verb && !co.no_progress ()) || co.progress ()) + text << "verifying symlinks..."; + + verify_symlinks (co, dir, dir_path () /* prefix */); + } + +#ifndef _WIN32 + + // Noop on POSIX. + // + optional<bool> + git_fixup_worktree (const common_options&, + const dir_path&, + bool revert, + bool) + { + assert (!revert); + return false; + } + +#else + // Fix up or revert the previously made fixes in a working tree of a top // repository or submodule (see git_fixup_worktree() description for // details). Return nullopt if no changes are required (because real symlink @@ -2304,7 +2755,7 @@ namespace bpkg // if (r) failure ("unexpected real symlink in submodule '" + - sm.path.string () + "'"); + sm.path.string () + '\''); return nullopt; } @@ -2324,13 +2775,35 @@ namespace bpkg // skipping those with not-yet-existing target, unless no links were // created at the previous run, in which case we fail. // - paths ls (find_symlinks (co, dir, prefix)); + vector<pair<path, string>> ls (find_symlinks (co, dir, prefix)); vector<pair<path, path>> links; // List of the link/target path pairs. + // Mark the being replaced in the working tree links as unchanged, + // running git-update-index(1) for multiple links per run. + // + strings unchanged_links; // Links to mark as unchanged. + + auto mark_unchanged = [&unchanged_links, &co, &dir, &failure] () + { + if (!unchanged_links.empty ()) + { + if (!run_git (co, + co.git_option (), + "-C", dir, + "update-index", + "--assume-unchanged", + unchanged_links)) + failure ("unable to mark symlinks as unchanged"); + + unchanged_links.clear (); + } + }; + // Cache/remove filesystem-agnostic symlinks. // - for (auto& l: ls) + for (auto& li: ls) { + path& l (li.first); path lp (dir / l); // Absolute or relative to the current directory. // Check the symlink type to see if we need to replace it or can bail @@ -2340,7 +2813,7 @@ namespace bpkg // "elevated console mode": // // - file symlinks are currently not supported (see - // libbutl/filesystem.mxx for details). + // libbutl/filesystem.hxx for details). // // - git creates symlinks to directories, rather than junctions. This // makes things to fall apart as Windows API seems to be unable to @@ -2356,14 +2829,14 @@ namespace bpkg if (e.second.type == entry_type::symlink) { if (r) - failure ("unexpected real symlink '" + l.string () + "'"); + failure ("unexpected real symlink '" + l.string () + '\''); return nullopt; } } catch (const system_error& e) { - failure ("unable to stat symlink '" + l.string () + "'", &e); + failure ("unable to stat symlink '" + l.string () + '\'', &e); } // Read the symlink target path. @@ -2378,7 +2851,7 @@ namespace bpkg catch (const invalid_path& e) { failure ("invalid target path '" + e.path + "' for symlink '" + - l.string () + "'", + l.string () + '\'', &e); } catch (const io_error& e) @@ -2390,14 +2863,14 @@ namespace bpkg // Mark the symlink as unchanged and remove it. // - if (!run_git (co, - co.git_option (), - "-C", dir, - "update-index", - "--assume-unchanged", - l)) - failure ("unable to mark symlink '" + l.string () + - "' as unchanged"); + // Note that we restrict the batch to 100 symlinks not to exceed the + // Windows command line max size, which is about 32K, and assuming + // that _MAX_PATH is 256 characters. + // + unchanged_links.push_back (l.string ()); + + if (unchanged_links.size () == 100) + mark_unchanged (); links.emplace_back (move (l), move (t)); @@ -2405,6 +2878,8 @@ namespace bpkg r = true; } + mark_unchanged (); // Mark the rest. + // Create real links (hardlinks, symlinks, and junctions). // while (!links.empty ()) @@ -2440,7 +2915,7 @@ namespace bpkg catch (const system_error& e) { failure ("unable to stat target '" + t.string () + - "' for symlink '" + l.string () + "'", + "' for symlink '" + l.string () + '\'', &e); } @@ -2458,7 +2933,7 @@ namespace bpkg { failure (string ("unable to create ") + (dir_target ? "junction" : "hardlink") + " '" + - l.string () + "' with target '" + t.string () + "'", + l.string () + "' with target '" + t.string () + '\'', &e); } @@ -2486,8 +2961,10 @@ namespace bpkg // filesystem entry. To prevent this, we remove all links ourselves // first. // - for (const path& l: find_symlinks (co, dir, prefix)) + for (const auto& li: find_symlinks (co, dir, prefix)) { + const path& l (li.first); + try { try_rmfile (dir / l); @@ -2495,7 +2972,7 @@ namespace bpkg catch (const system_error& e) { failure ("unable to remove hardlink, symlink, or junction '" + - l.string () + "'", + l.string () + '\'', &e); } } @@ -2523,15 +3000,29 @@ namespace bpkg return r; } - bool + optional<bool> git_fixup_worktree (const common_options& co, const dir_path& dir, - bool revert) + bool revert, + bool ie) { - optional<bool> r ( - fixup_worktree (co, dir, revert, dir_path () /* prefix */)); + if (!revert && ((verb && !co.no_progress ()) || co.progress ())) + text << "fixing up symlinks..."; + + try + { + optional<bool> r ( + fixup_worktree (co, dir, revert, dir_path () /* prefix */)); - return r ? *r : false; + return r ? *r : false; + } + catch (const failed&) + { + if (ie) + return nullopt; + + throw; + } } #endif |