@@ -46,6 +46,7 @@ import mcpp.pm.index_refresh;
4646import mcpp.pm.mangle;
4747import mcpp.pm.compat;
4848import mcpp.pm.dep_spec;
49+ import mcpp.pm.lock_io;
4950import mcpp.version_req;
5051import mcpp.ui;
5152import mcpp.log;
@@ -838,6 +839,26 @@ prepare_build(bool print_fingerprint,
838839 mcpp::diag::warning (" manifest/schema" , w);
839840 }
840841
842+ // Load mcpp.lock once. Git-based deps can use it as an offline anchor:
843+ // if a branch is already resolved to a commit and the local cache matches,
844+ // no network round-trip is needed.
845+ std::map<std::string, mcpp::pm::LockedGitSource> gitLockAnchors;
846+ {
847+ auto lockPath = *root / " mcpp.lock" ;
848+ if (std::filesystem::exists (lockPath)) {
849+ if (auto lock = mcpp::lockfile::load (lockPath); lock) {
850+ for (auto const & p : lock->packages ) {
851+ if (auto parsed = mcpp::pm::parse_git_source (p.source ); parsed) {
852+ gitLockAnchors.emplace (p.name , std::move (*parsed));
853+ }
854+ }
855+ } else {
856+ mcpp::diag::warning (" lockfile" ,
857+ std::format (" ignoring mcpp.lock: {}" , lock.error ().message ));
858+ }
859+ }
860+ }
861+
841862 // Global-cache mode: --cache > MCPP_BUILD_CACHE > [build] cache > global.
842863 // An unparseable value is a warning (error under --strict) and falls
843864 // through to the next source rather than silently meaning "global" — a typo
@@ -2987,24 +3008,102 @@ prepare_build(bool print_fingerprint,
29873008 // them to a commit before forming the cache key; this lets
29883009 // `mcpp update <dep>` pick up a moved branch without deleting
29893010 // unrelated git caches.
3011+ //
3012+ // mcpp.lock acts as an offline anchor: if a branch is already
3013+ // resolved to a commit and the local cache still matches it,
3014+ // we skip the network round-trip entirely.
29903015 auto mcppHome = mcpp::home::root (); // single resolver (#311)
29913016 std::string resolvedGitRev = spec.gitRev ;
3017+ bool skipLsRemote = false ;
3018+
3019+ // Look up an offline anchor for this git dep.
3020+ std::optional<std::string> lockedCommit;
3021+ if (auto it = gitLockAnchors.find (name); it != gitLockAnchors.end ()) {
3022+ auto const & anchor = it->second ;
3023+ if (anchor.refKind == spec.gitRefKind && anchor.ref == spec.gitRev ) {
3024+ lockedCommit = anchor.resolvedCommit ;
3025+ }
3026+ }
3027+
3028+ auto computeGitRoot = [&](const std::string& rev) {
3029+ std::hash<std::string> H;
3030+ auto urlHash = std::format (" {:016x}" ,
3031+ H (spec.git + " |" + spec.gitRefKind + " |" + spec.gitRev
3032+ + " |" + rev));
3033+ return mcppHome / " git" / urlHash;
3034+ };
3035+
3036+ auto readCacheHead = [&](const std::filesystem::path& gitRoot)
3037+ -> std::string
3038+ {
3039+ auto cmd = std::format (" git -C {} rev-parse HEAD 2>&1" ,
3040+ mcpp::platform::shell::quote (gitRoot.string ()));
3041+ auto r = mcpp::platform::process::capture (cmd);
3042+ if (r.exit_code != 0 ) return {};
3043+ std::string head = r.output ;
3044+ head.erase (head.find_last_not_of (" \r\n\t " ) + 1 );
3045+ return head;
3046+ };
3047+
29923048 if (spec.gitRefKind == " branch" ) {
29933049 auto ref = std::format (" refs/heads/{}" , spec.gitRev );
2994- auto cmd = std::format (
2995- " git ls-remote {} {} 2>&1" ,
2996- mcpp::platform::shell::quote (spec.git ),
2997- mcpp::platform::shell::quote (ref));
2998- auto r = mcpp::platform::process::capture (cmd);
2999- if (r.exit_code != 0 ) {
3000- return std::unexpected (std::format (
3001- " git ls-remote of '{}' failed:\n {}" , spec.git , r.output ));
3050+ if (lockedCommit && !lockedCommit->empty ()) {
3051+ resolvedGitRev = *lockedCommit;
3052+ auto gitRoot = computeGitRoot (resolvedGitRev);
3053+ if (std::filesystem::exists (gitRoot / " .git" ) &&
3054+ readCacheHead (gitRoot) == resolvedGitRev) {
3055+ skipLsRemote = true ;
3056+ mcpp::ui::info (" Resolved" ,
3057+ std::format (" {} ({} = {}) from lock" ,
3058+ spec.git , spec.gitRefKind , spec.gitRev ));
3059+ }
3060+ }
3061+ if (!skipLsRemote) {
3062+ if (mcpp::platform::env::offline_mode ()) {
3063+ if (lockedCommit) {
3064+ return std::unexpected (std::format (
3065+ " git dep '{}' locked to commit {} but its local cache is missing or stale; "
3066+ " run without --offline to refresh, or `mcpp update {}` to re-resolve" ,
3067+ name, *lockedCommit, name));
3068+ } else {
3069+ return std::unexpected (std::format (
3070+ " git dep '{}' uses branch '{}' and mcpp.lock has no commit; "
3071+ " cannot resolve offline. Run `mcpp update {}` or build without --offline." ,
3072+ name, spec.gitRev , name));
3073+ }
3074+ }
3075+ auto cmd = std::format (
3076+ " git ls-remote {} {} 2>&1" ,
3077+ mcpp::platform::shell::quote (spec.git ),
3078+ mcpp::platform::shell::quote (ref));
3079+ auto r = mcpp::platform::process::capture (cmd);
3080+ if (r.exit_code != 0 ) {
3081+ return std::unexpected (std::format (
3082+ " git ls-remote of '{}' failed:\n {}" , spec.git , r.output ));
3083+ }
3084+ std::istringstream is (r.output );
3085+ is >> resolvedGitRev;
3086+ if (resolvedGitRev.empty ()) {
3087+ return std::unexpected (std::format (
3088+ " git branch '{}' not found in '{}'" , spec.gitRev , spec.git ));
3089+ }
3090+ }
3091+ } else {
3092+ // tag/rev: the declared ref is already a stable identity.
3093+ // If the lock already recorded this dep and the clone is present,
3094+ // we still use it; otherwise fall through to clone.
3095+ auto gitRoot = computeGitRoot (resolvedGitRev);
3096+ if (std::filesystem::exists (gitRoot / " .git" )) {
3097+ mcpp::ui::info (" Resolved" ,
3098+ std::format (" {} ({} = {}) from cache" ,
3099+ spec.git , spec.gitRefKind , spec.gitRev ));
30023100 }
3003- std::istringstream is (r.output );
3004- is >> resolvedGitRev;
3005- if (resolvedGitRev.empty ()) {
3101+ if (!std::filesystem::exists (gitRoot / " .git" ) &&
3102+ mcpp::platform::env::offline_mode ()) {
30063103 return std::unexpected (std::format (
3007- " git branch '{}' not found in '{}'" , spec.gitRev , spec.git ));
3104+ " git dep '{}' is locked but its local cache is missing; "
3105+ " run without --offline to clone, or `mcpp update {}` to re-resolve." ,
3106+ name, name));
30083107 }
30093108 }
30103109
0 commit comments