Skip to main content

dev_prune/adapters/
go.rs

1// Copyright 2026 VKrishna04
2// SPDX-License-Identifier: Apache-2.0
3
4// Go package manager adapter.
5
6use super::{BloatDir, EnforcePolicy, PackageManager, dir_size, enforce_two_tier, run_command};
7use anyhow::Result;
8use std::path::Path;
9
10/// Adapter for Go modules.
11pub struct Go;
12
13impl PackageManager for Go {
14    fn name(&self) -> &'static str {
15        "go"
16    }
17
18    fn detect(&self, path: &Path) -> bool {
19        path.join("go.mod").exists()
20    }
21
22    fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
23        let mut dirs = Vec::new();
24        let vendor_path = path.join("vendor");
25        if vendor_path.exists() {
26            dirs.push(BloatDir {
27                name: "vendor".to_string(),
28                path: vendor_path.clone(),
29                size_bytes: dir_size(&vendor_path),
30            });
31        }
32        dirs
33    }
34
35    /// `go mod tidy` reconciles `go.mod` and `go.sum` against the real imports, and can
36    /// *remove* a requirement nothing imports any more — which is exactly why it is not
37    /// the default. With `go.sum` present the module cache is verified instead, which
38    /// never touches tracked files. `tidy` is reached only when there is no `go.sum` to
39    /// bootstrap from, or when the user opted in.
40    fn enforce_lockfile(&self, path: &Path, policy: EnforcePolicy) -> Result<()> {
41        enforce_two_tier(
42            &path.join("go.sum"),
43            "go",
44            &["mod", "download"],
45            &["mod", "tidy"],
46            path,
47            policy,
48        )
49    }
50
51    fn restore(&self, path: &Path) -> Result<()> {
52        if path.join("vendor").exists() {
53            run_command("go", &["mod", "vendor"], path)
54        } else {
55            run_command("go", &["mod", "download"], path)
56        }
57    }
58
59    fn lockfiles(&self) -> &'static [&'static str] {
60        &["go.sum"]
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use std::fs;
68    use std::fs::File;
69    use tempfile::tempdir;
70
71    #[test]
72    fn test_name() {
73        let adapter = Go;
74        assert_eq!(adapter.name(), "go");
75    }
76
77    #[test]
78    fn test_detect_positive() {
79        let dir = tempdir().unwrap();
80        File::create(dir.path().join("go.mod")).unwrap();
81
82        let adapter = Go;
83        assert!(adapter.detect(dir.path()));
84    }
85
86    #[test]
87    fn test_detect_negative() {
88        let dir = tempdir().unwrap();
89
90        let adapter = Go;
91        assert!(!adapter.detect(dir.path()));
92    }
93
94    #[test]
95    fn test_bloat_dirs_present() {
96        let dir = tempdir().unwrap();
97        fs::create_dir(dir.path().join("vendor")).unwrap();
98
99        let adapter = Go;
100        let dirs = adapter.bloat_dirs(dir.path());
101        assert_eq!(dirs.len(), 1);
102        assert_eq!(dirs[0].name, "vendor");
103    }
104
105    #[test]
106    fn test_bloat_dirs_absent() {
107        let dir = tempdir().unwrap();
108
109        let adapter = Go;
110        let dirs = adapter.bloat_dirs(dir.path());
111        assert!(dirs.is_empty());
112    }
113}