@@ -42,19 +42,38 @@ fn git_ok(repo_dir: &Path, args: &[&str]) -> Option<bool> {
4242 . map ( |o| o. status . success ( ) )
4343}
4444
45- /// Walk up from `start` looking for `.git` (file or directory).
46- /// Returns the directory *containing* `.git`, not `.git` itself.
47- fn find_repo_root ( start : & Path ) -> Option < PathBuf > {
48- let mut dir = start. to_path_buf ( ) ;
49- loop {
50- let candidate = dir. join ( ".git" ) ;
51- if candidate. exists ( ) {
52- return Some ( dir) ;
53- }
54- if !dir. pop ( ) {
55- return None ;
56- }
45+ /// Find the git repo root for the crate being built.
46+ ///
47+ /// Uses `git rev-parse --show-toplevel` from CARGO_MANIFEST_DIR, which
48+ /// lets git itself determine whether we're inside a repo. This avoids
49+ /// the problem of manually walking up the directory tree and accidentally
50+ /// finding an unrelated `.git` in a parent directory (e.g. when
51+ /// `cargo install` extracts into `~/.cargo/registry/src/` and some
52+ /// ancestor has a `.git`).
53+ ///
54+ /// Returns None if git isn't installed, we're not in a repo (crates.io
55+ /// tarball), or the detected repo root isn't an ancestor of our manifest
56+ /// directory (sanity check).
57+ fn find_repo_root ( manifest_dir : & Path ) -> Option < PathBuf > {
58+ let toplevel = git ( manifest_dir, & [ "rev-parse" , "--show-toplevel" ] ) ?;
59+ let root = PathBuf :: from ( & toplevel) ;
60+
61+ // Sanity check: the manifest dir must be inside the detected repo.
62+ // This guards against pathological setups where a completely unrelated
63+ // git repo contains our extraction directory.
64+ let canonical_root = root. canonicalize ( ) . unwrap_or_else ( |_| root. clone ( ) ) ;
65+ let canonical_manifest = manifest_dir
66+ . canonicalize ( )
67+ . unwrap_or_else ( |_| manifest_dir. to_path_buf ( ) ) ;
68+ if !canonical_manifest. starts_with ( & canonical_root) {
69+ println ! (
70+ "cargo:warning=build.rs: git toplevel {:?} is not an ancestor of manifest dir {:?}, ignoring" ,
71+ canonical_root, canonical_manifest
72+ ) ;
73+ return None ;
5774 }
75+
76+ Some ( root)
5877}
5978
6079/// Write `contents` to `path`, but only if the file doesn't already exist
@@ -139,8 +158,11 @@ fn main() {
139158 Some ( ref root) => (
140159 // --tags: match lightweight tags too (not just annotated).
141160 // --dirty: append "-dirty" if the worktree has uncommitted changes.
142- // --always: fall back to abbreviated SHA if no tag is reachable.
143- git ( root, & [ "describe" , "--tags" , "--dirty" , "--always" ] ) ,
161+ // Deliberately no --always: if no tag is reachable (shallow
162+ // clone, tags not fetched), we want this to fail so we fall
163+ // through to the PKG_VERSION+sha format instead of emitting
164+ // a bare SHA as the version string.
165+ git ( root, & [ "describe" , "--tags" , "--dirty" ] ) ,
144166 git ( root, & [ "rev-parse" , "HEAD" ] ) ,
145167 git ( root, & [ "rev-parse" , "--short" , "HEAD" ] ) ,
146168 git ( root, & [ "symbolic-ref" , "--short" , "-q" , "HEAD" ] ) ,
0 commit comments