main
 1package git
 2
 3import (
 4	"fmt"
 5	"strconv"
 6	"strings"
 7)
 8
 9// ParseFileMode converts a git-style file mode (e.g. "100644")
10// into a human-readable string like "rw-r--r--".
11func ParseFileMode(modeStr string) (string, error) {
12	// Git modes are typically 6 digits. The last 3 represent permissions.
13	// e.g. 100644 → 644
14	if len(modeStr) < 3 {
15		return "", fmt.Errorf("invalid mode: %s", modeStr)
16	}
17
18	permStr := modeStr[len(modeStr)-3:]
19	permVal, err := strconv.Atoi(permStr)
20	if err != nil {
21		return "", err
22	}
23
24	return numericPermToLetters(permVal), nil
25}
26
27func numericPermToLetters(perm int) string {
28	// Map each octal digit to rwx letters
29	lookup := map[int]string{
30		0: "---",
31		1: "--x",
32		2: "-w-",
33		3: "-wx",
34		4: "r--",
35		5: "r-x",
36		6: "rw-",
37		7: "rwx",
38	}
39
40	u := perm / 100       // user
41	g := (perm / 10) % 10 // group
42	o := perm % 10        // others
43
44	return lookup[u] + lookup[g] + lookup[o]
45}
46
47// IsBinary performs a heuristic check to determine if data is binary.
48// Rules:
49//   - Any NUL byte => binary
50//   - Consider only a sample (up to 8 KiB). If >30% of bytes are control characters
51//     outside the common whitespace/newline range, treat as binary.
52func IsBinary(b []byte) bool {
53	n := len(b)
54	if n == 0 {
55		return false
56	}
57	if n > 8192 {
58		n = 8192
59	}
60	sample := b[:n]
61	bad := 0
62	for _, c := range sample {
63		if c == 0x00 {
64			return true
65		}
66		// Allow common whitespace and control: tab(9), LF(10), CR(13)
67		if c == 9 || c == 10 || c == 13 {
68			continue
69		}
70		// Count other control chars and DEL as non-text
71		if c < 32 || c == 127 {
72			bad++
73		}
74	}
75	// If more than 30% of sampled bytes are non-text, consider binary
76	return bad*100 > n*30
77}
78
79func RefToFileName(ref string) string {
80	var result strings.Builder
81	for _, c := range ref {
82		if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' {
83			result.WriteByte(byte(c))
84		} else if c >= 'A' && c <= 'Z' {
85			result.WriteByte(byte(c - 'A' + 'a'))
86		} else {
87			result.WriteByte('-')
88		}
89	}
90	return result.String()
91}