• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyberDuck79 / duckfile / 17023221994

17 Aug 2025 04:14PM UTC coverage: 70.232% (+4.5%) from 65.767%
17023221994

Pull #43

github

CyberDuck79
feat: implement commit hash tracking and validation

Add commit hash tracking to detect template changes and ensure reproducible builds.

Key features:
- CLI flags: --track-commit-hash/--no-track-commit-hash, --auto-update-on-change
- Environment variables: DUCK_TRACK_COMMIT_HASH, DUCK_AUTO_UPDATE_ON_CHANGE
- Template and global configuration support
- Network-resilient validation with auto-update capability
- Comprehensive test coverage and documentation updates
- Backward compatible with existing configurations

Fixes security config bug where default behavior incorrectly restricted hosts.
Pull Request #43: feat: implement commit hash tracking and validation

262 of 295 new or added lines in 6 files covered. (88.81%)

4 existing lines in 1 file now uncovered.

998 of 1421 relevant lines covered (70.23%)

9.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

38.71
/internal/git/git.go
1
package git
2

3
import (
4
        "fmt"
5
        "os/exec"
6
        "path/filepath"
7
        "regexp"
8
        "strings"
9
)
10

11
// CloneInto clones/fetches repo@ref into cacheDir/repo and checks out the ref in the workdir.
12
// Returns the workdir path with the working tree set to the requested ref (detached HEAD).
13
func CloneInto(repo, ref, cacheDir string) (string, error) {
×
14
        workdir := filepath.Join(cacheDir, "repo") // 1-repo MVP, improve later
×
15

×
16
        // Already cloned?
×
17
        if _, err := exec.Command("test", "-d", filepath.Join(workdir, ".git")).CombinedOutput(); err == nil {
×
18
                // Fetch the desired ref and checkout FETCH_HEAD (detached)
×
19
                if out, err := exec.Command("git", "-C", workdir, "fetch", "--depth", "1", "origin", ref).CombinedOutput(); err != nil {
×
20
                        return "", fmt.Errorf("git fetch failed: %v: %s", err, string(out))
×
21
                }
×
22
                if out, err := exec.Command("git", "-C", workdir, "checkout", "--force", "--detach", "FETCH_HEAD").CombinedOutput(); err != nil {
×
23
                        return "", fmt.Errorf("git checkout failed: %v: %s", err, string(out))
×
24
                }
×
25
        } else {
×
26
                // Fresh clone, then force checkout the ref (supports branch, tag, or commit)
×
27
                if out, err := exec.Command("git", "clone", "--depth", "1", repo, workdir).CombinedOutput(); err != nil {
×
28
                        return "", fmt.Errorf("git clone failed: %v: %s", err, string(out))
×
29
                }
×
30
                // Ensure we have the ref and check it out detached
31
                if out, err := exec.Command("git", "-C", workdir, "fetch", "--depth", "1", "origin", ref).CombinedOutput(); err != nil {
×
32
                        return "", fmt.Errorf("git fetch failed: %v: %s", err, string(out))
×
33
                }
×
34
                if out, err := exec.Command("git", "-C", workdir, "checkout", "--force", "--detach", "FETCH_HEAD").CombinedOutput(); err != nil {
×
35
                        return "", fmt.Errorf("git checkout failed: %v: %s", err, string(out))
×
36
                }
×
37
        }
38
        return workdir, nil
×
39
}
40

41
// GetCurrentCommitHash returns the commit hash of the currently checked out ref in the given directory.
42
// Returns the full 40-character SHA-1 hash.
43
func GetCurrentCommitHash(workdir string) (string, error) {
2✔
44
        out, err := exec.Command("git", "-C", workdir, "rev-parse", "HEAD").CombinedOutput()
2✔
45
        if err != nil {
4✔
46
                return "", fmt.Errorf("git rev-parse HEAD failed: %v: %s", err, string(out))
2✔
47
        }
2✔
NEW
48
        hash := strings.TrimSpace(string(out))
×
NEW
49
        if len(hash) != 40 {
×
NEW
50
                return "", fmt.Errorf("invalid commit hash length: got %d characters, expected 40", len(hash))
×
NEW
51
        }
×
NEW
52
        return hash, nil
×
53
}
54

55
// GetRemoteCommitHash fetches the remote ref and returns its commit hash without checking it out.
56
// This function is used to check if the remote has changed since the last cache.
57
// If network fails, returns an error that can be handled gracefully by the caller.
58
func GetRemoteCommitHash(repo, ref string) (string, error) {
3✔
59
        // Use ls-remote to get the commit hash without cloning/fetching
3✔
60
        out, err := exec.Command("git", "ls-remote", repo, ref).CombinedOutput()
3✔
61
        if err != nil {
5✔
62
                return "", fmt.Errorf("git ls-remote failed (network or repository error): %v: %s", err, string(out))
2✔
63
        }
2✔
64

65
        output := strings.TrimSpace(string(out))
1✔
66
        if output == "" {
2✔
67
                return "", fmt.Errorf("ref %q not found in repository %q", ref, repo)
1✔
68
        }
1✔
69

70
        // Parse output: "commit_hash\trefs/heads/branch" or "commit_hash\tHEAD"
NEW
71
        lines := strings.Split(output, "\n")
×
NEW
72
        for _, line := range lines {
×
NEW
73
                parts := strings.Split(line, "\t")
×
NEW
74
                if len(parts) >= 2 {
×
NEW
75
                        hash := strings.TrimSpace(parts[0])
×
NEW
76
                        if len(hash) == 40 && isValidCommitHash(hash) {
×
NEW
77
                                return hash, nil
×
NEW
78
                        }
×
79
                }
80
        }
81

NEW
82
        return "", fmt.Errorf("could not parse commit hash from ls-remote output: %s", output)
×
83
}
84

85
// IsCommitHash checks if the given ref is already a commit hash (40-character hex string).
86
// This is used to validate configuration - if ref is already a commit hash,
87
// commit hash tracking doesn't make sense since commit hashes don't change.
88
func IsCommitHash(ref string) bool {
17✔
89
        return len(ref) == 40 && isValidCommitHash(ref)
17✔
90
}
17✔
91

92
// isValidCommitHash checks if a string is a valid 40-character hexadecimal hash
93
func isValidCommitHash(hash string) bool {
13✔
94
        if len(hash) != 40 {
14✔
95
                return false
1✔
96
        }
1✔
97
        matched, _ := regexp.MatchString("^[a-fA-F0-9]{40}$", hash)
12✔
98
        return matched
12✔
99
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc