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

CyberDuck79 / duckfile / 17051823733

18 Aug 2025 08:32PM UTC coverage: 72.833% (+0.8%) from 72.058%
17051823733

Pull #52

github

CyberDuck79
test: fix linting
Pull Request #52: feat: Add .env file support and improve logging system

163 of 194 new or added lines in 9 files covered. (84.02%)

1 existing line in 1 file now uncovered.

1185 of 1627 relevant lines covered (72.83%)

9.18 hits per line

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

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

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

10
        "github.com/CyberDuck79/duckfile/internal/log"
11
)
12

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

×
18
        // Already cloned?
×
19
        if _, err := exec.Command("test", "-d", filepath.Join(workdir, ".git")).CombinedOutput(); err == nil {
×
NEW
20
                log.Infof("Repository already exists at %s, updating...", workdir)
×
21
                // Fetch the desired ref and checkout FETCH_HEAD (detached)
×
NEW
22
                log.Debugf("Fetching ref %s from %s", ref, repo)
×
23
                if out, err := exec.Command("git", "-C", workdir, "fetch", "--depth", "1", "origin", ref).CombinedOutput(); err != nil {
×
24
                        return "", fmt.Errorf("git fetch failed: %v: %s", err, string(out))
×
25
                }
×
NEW
26
                log.Debugf("Checking out ref %s", ref)
×
27
                if out, err := exec.Command("git", "-C", workdir, "checkout", "--force", "--detach", "FETCH_HEAD").CombinedOutput(); err != nil {
×
28
                        return "", fmt.Errorf("git checkout failed: %v: %s", err, string(out))
×
29
                }
×
NEW
30
                log.Infof("Successfully updated repository to %s", ref)
×
31
        } else {
×
NEW
32
                log.Infof("Cloning repository %s to %s", repo, workdir)
×
33
                // Fresh clone, then force checkout the ref (supports branch, tag, or commit)
×
34
                if out, err := exec.Command("git", "clone", "--depth", "1", repo, workdir).CombinedOutput(); err != nil {
×
35
                        return "", fmt.Errorf("git clone failed: %v: %s", err, string(out))
×
36
                }
×
37
                // Ensure we have the ref and check it out detached
NEW
38
                log.Debugf("Fetching ref %s", ref)
×
39
                if out, err := exec.Command("git", "-C", workdir, "fetch", "--depth", "1", "origin", ref).CombinedOutput(); err != nil {
×
40
                        return "", fmt.Errorf("git fetch failed: %v: %s", err, string(out))
×
41
                }
×
NEW
42
                log.Debugf("Checking out ref %s", ref)
×
43
                if out, err := exec.Command("git", "-C", workdir, "checkout", "--force", "--detach", "FETCH_HEAD").CombinedOutput(); err != nil {
×
44
                        return "", fmt.Errorf("git checkout failed: %v: %s", err, string(out))
×
45
                }
×
NEW
46
                log.Infof("Successfully cloned and checked out %s", ref)
×
47
        }
48
        return workdir, nil
×
49
}
50

51
// GetCurrentCommitHash returns the commit hash of the currently checked out ref in the given directory.
52
// Returns the full 40-character SHA-1 hash.
53
func GetCurrentCommitHash(workdir string) (string, error) {
2✔
54
        out, err := exec.Command("git", "-C", workdir, "rev-parse", "HEAD").CombinedOutput()
2✔
55
        if err != nil {
4✔
56
                return "", fmt.Errorf("git rev-parse HEAD failed: %v: %s", err, string(out))
2✔
57
        }
2✔
58
        hash := strings.TrimSpace(string(out))
×
59
        if len(hash) != 40 {
×
60
                return "", fmt.Errorf("invalid commit hash length: got %d characters, expected 40", len(hash))
×
61
        }
×
62
        return hash, nil
×
63
}
64

65
// GetRemoteCommitHash fetches the remote ref and returns its commit hash without checking it out.
66
// This function is used to check if the remote has changed since the last cache.
67
// If network fails, returns an error that can be handled gracefully by the caller.
68
func GetRemoteCommitHash(repo, ref string) (string, error) {
3✔
69
        log.Debugf("Checking remote commit hash for %s@%s", repo, ref)
3✔
70
        // Use ls-remote to get the commit hash without cloning/fetching
3✔
71
        out, err := exec.Command("git", "ls-remote", repo, ref).CombinedOutput()
3✔
72
        if err != nil {
5✔
73
                return "", fmt.Errorf("git ls-remote failed (network or repository error): %v: %s", err, string(out))
2✔
74
        }
2✔
75

76
        output := strings.TrimSpace(string(out))
1✔
77
        if output == "" {
2✔
78
                return "", fmt.Errorf("ref %q not found in repository %q", ref, repo)
1✔
79
        }
1✔
80

81
        // Parse output: "commit_hash\trefs/heads/branch" or "commit_hash\tHEAD"
82
        lines := strings.Split(output, "\n")
×
83
        for _, line := range lines {
×
84
                parts := strings.Split(line, "\t")
×
85
                if len(parts) >= 2 {
×
86
                        hash := strings.TrimSpace(parts[0])
×
87
                        if len(hash) == 40 && isValidCommitHash(hash) {
×
NEW
88
                                log.Debugf("Remote commit hash for %s@%s: %s", repo, ref, hash[:8])
×
89
                                return hash, nil
×
90
                        }
×
91
                }
92
        }
93

94
        return "", fmt.Errorf("could not parse commit hash from ls-remote output: %s", output)
×
95
}
96

97
// IsCommitHash checks if the given ref is already a commit hash (40-character hex string).
98
// This is used to validate configuration - if ref is already a commit hash,
99
// commit hash tracking doesn't make sense since commit hashes don't change.
100
func IsCommitHash(ref string) bool {
17✔
101
        return len(ref) == 40 && isValidCommitHash(ref)
17✔
102
}
17✔
103

104
// isValidCommitHash checks if a string is a valid 40-character hexadecimal hash
105
func isValidCommitHash(hash string) bool {
13✔
106
        if len(hash) != 40 {
14✔
107
                return false
1✔
108
        }
1✔
109
        matched, _ := regexp.MatchString("^[a-fA-F0-9]{40}$", hash)
12✔
110
        return matched
12✔
111
}
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