@@ -1565,6 +1565,11 @@ prepare_build(bool print_fingerprint,
15651565 std::string version;
15661566 };
15671567 std::vector<DepCacheIdentity> dep_cache_identities;
1568+ struct GitLockIdentity {
1569+ std::string source;
1570+ std::string hash;
1571+ };
1572+ std::map<std::string, GitLockIdentity> root_git_lock_identities;
15681573
15691574 struct ResolvedKey {
15701575 std::string ns;
@@ -2615,20 +2620,44 @@ prepare_build(bool print_fingerprint,
26152620 if (dep_root.is_relative ()) dep_root = base / dep_root;
26162621 dep_root = std::filesystem::weakly_canonical (dep_root);
26172622 } else if (spec.isGit ()) {
2618- // Git-based (M4 #5): clone into ~/.mcpp/git/<hash>/<rev>/
2619- // and treat as a path dep from there.
2623+ // Git-based (M4 #5): clone into ~/.mcpp/git/<hash>/ and treat
2624+ // as a path dep from there. Branch refs are floating, so resolve
2625+ // them to a commit before forming the cache key; this lets
2626+ // `mcpp update <dep>` pick up a moved branch without deleting
2627+ // unrelated git caches.
26202628 auto mcppHome = [] {
26212629 if (auto * e = std::getenv (" MCPP_HOME" ); e && *e)
26222630 return std::filesystem::path (e);
26232631 if (auto * e = std::getenv (" HOME" ); e && *e)
26242632 return std::filesystem::path (e) / " .mcpp" ;
26252633 return std::filesystem::current_path () / " .mcpp" ;
26262634 }();
2627- // Cache key: hash(url + refkind + ref). Avoids collisions across
2628- // different revs of the same repo.
2635+ std::string resolvedGitRev = spec.gitRev ;
2636+ if (spec.gitRefKind == " branch" ) {
2637+ auto ref = std::format (" refs/heads/{}" , spec.gitRev );
2638+ auto cmd = std::format (
2639+ " git ls-remote {} {} 2>&1" ,
2640+ mcpp::platform::shell::quote (spec.git ),
2641+ mcpp::platform::shell::quote (ref));
2642+ auto r = mcpp::platform::process::capture (cmd);
2643+ if (r.exit_code != 0 ) {
2644+ return std::unexpected (std::format (
2645+ " git ls-remote of '{}' failed:\n {}" , spec.git , r.output ));
2646+ }
2647+ std::istringstream is (r.output );
2648+ is >> resolvedGitRev;
2649+ if (resolvedGitRev.empty ()) {
2650+ return std::unexpected (std::format (
2651+ " git branch '{}' not found in '{}'" , spec.gitRev , spec.git ));
2652+ }
2653+ }
2654+
2655+ // Cache key: hash(url + refkind + declared ref + resolved commit).
2656+ // For fixed rev/tag deps the declared ref is also the resolved ref.
26292657 std::hash<std::string> H;
26302658 auto urlHash = std::format (" {:016x}" ,
2631- H (spec.git + " |" + spec.gitRefKind + " |" + spec.gitRev ));
2659+ H (spec.git + " |" + spec.gitRefKind + " |" + spec.gitRev
2660+ + " |" + resolvedGitRev));
26322661 auto gitRoot = mcppHome / " git" / urlHash;
26332662 std::error_code ec;
26342663 std::filesystem::create_directories (gitRoot.parent_path (), ec);
@@ -2638,10 +2667,12 @@ prepare_build(bool print_fingerprint,
26382667 std::string cloneCmd;
26392668 if (spec.gitRefKind == " branch" ) {
26402669 cloneCmd = std::format (
2641- " git clone --depth 1 --branch {} {} {} 2>&1" ,
2670+ " git clone --depth 1 --branch {} {} {} && cd {} && git checkout --quiet {} 2>&1" ,
26422671 mcpp::platform::shell::quote (spec.gitRev ),
26432672 mcpp::platform::shell::quote (spec.git ),
2644- mcpp::platform::shell::quote (gitRoot.string ()));
2673+ mcpp::platform::shell::quote (gitRoot.string ()),
2674+ mcpp::platform::shell::quote (gitRoot.string ()),
2675+ mcpp::platform::shell::quote (resolvedGitRev));
26452676 } else {
26462677 // For tag/rev: full clone, then checkout (depth-1 may miss the rev).
26472678 cloneCmd = std::format (
@@ -2663,6 +2694,17 @@ prepare_build(bool print_fingerprint,
26632694 }
26642695 }
26652696 }
2697+ if (item.consumerDepIndex == kMainConsumer ) {
2698+ auto source = std::format (" git+{}#{}={}" ,
2699+ spec.git , spec.gitRefKind , spec.gitRev );
2700+ if (spec.gitRefKind == " branch" ) source += " @" + resolvedGitRev;
2701+ root_git_lock_identities[name] = GitLockIdentity{
2702+ .source = std::move (source),
2703+ .hash = std::format (" fnv1a:{:016x}" , H (spec.git + " |"
2704+ + spec.gitRefKind + " |" + spec.gitRev + " |"
2705+ + resolvedGitRev)),
2706+ };
2707+ }
26662708 dep_root = gitRoot;
26672709 }
26682710 // (version-source: dep_root + manifest are loaded together via
@@ -2985,19 +3027,33 @@ prepare_build(bool print_fingerprint,
29853027 if (spec.isPath ()) continue ;
29863028 mcpp::lockfile::LockedPackage lp;
29873029 lp.name = name;
2988- lp.namespace_ = spec.namespace_ .empty ()
2989- ? std::string (mcpp::pm::kDefaultNamespace )
2990- : spec.namespace_ ;
2991- lp.version = spec.version ;
2992- // Use the namespace and resolved version as the source identifier.
2993- // For custom indices, include the index name for traceability.
2994- lp.source = std::format (" index+{}@{}" , lp.namespace_ , lp.version );
2995- // Use a deterministic hash based on namespace + name + version.
2996- // A future PR can replace this with a real content hash from the
2997- // xpkg.lua's declared sha256 or from the install plan.
2998- std::hash<std::string> hasher;
2999- auto hashInput = std::format (" {}:{}@{}" , lp.namespace_ , name, lp.version );
3000- lp.hash = std::format (" fnv1a:{:016x}" , hasher (hashInput));
3030+ if (spec.isGit ()) {
3031+ auto gitIt = root_git_lock_identities.find (name);
3032+ lp.version = spec.gitRev ;
3033+ if (gitIt == root_git_lock_identities.end ()) {
3034+ lp.source = std::format (" git+{}#{}={}" ,
3035+ spec.git , spec.gitRefKind , spec.gitRev );
3036+ std::hash<std::string> hasher;
3037+ lp.hash = std::format (" fnv1a:{:016x}" , hasher (lp.source ));
3038+ } else {
3039+ lp.source = gitIt->second .source ;
3040+ lp.hash = gitIt->second .hash ;
3041+ }
3042+ } else {
3043+ lp.namespace_ = spec.namespace_ .empty ()
3044+ ? std::string (mcpp::pm::kDefaultNamespace )
3045+ : spec.namespace_ ;
3046+ lp.version = spec.version ;
3047+ // Use the namespace and resolved version as the source identifier.
3048+ // For custom indices, include the index name for traceability.
3049+ lp.source = std::format (" index+{}@{}" , lp.namespace_ , lp.version );
3050+ // Use a deterministic hash based on namespace + name + version.
3051+ // A future PR can replace this with a real content hash from the
3052+ // xpkg.lua's declared sha256 or from the install plan.
3053+ std::hash<std::string> hasher;
3054+ auto hashInput = std::format (" {}:{}@{}" , lp.namespace_ , name, lp.version );
3055+ lp.hash = std::format (" fnv1a:{:016x}" , hasher (hashInput));
3056+ }
30013057 lock.packages .push_back (std::move (lp));
30023058 }
30033059 if (!lock.packages .empty () || !lock.indices .empty ()) {
0 commit comments