1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package db
6
7import (
8 "path/filepath"
9 "runtime"
10 "strings"
11
12 "github.com/zeebo/blake3"
13)
14
15// CanonicalizeDir normalizes path according to the repository requirements.
16func CanonicalizeDir(path string) (string, error) {
17 abs, err := filepath.Abs(path)
18 if err != nil {
19 return "", err
20 }
21
22 evaluated, err := filepath.EvalSymlinks(abs)
23 if err != nil {
24 return "", err
25 }
26
27 clean := filepath.Clean(evaluated)
28 slashed := filepath.ToSlash(clean)
29 if runtime.GOOS == "windows" {
30 slashed = strings.ToLower(slashed)
31 }
32 return slashed, nil
33}
34
35// DirHash produces the blake3-256 hash for canonicalPath and renders it in
36// lowercase hex (64 characters).
37func DirHash(canonicalPath string) string {
38 sum := blake3.Sum256([]byte(canonicalPath))
39 return encodeHex(sum[:])
40}
41
42// CanonicalizeAndHash resolves path to its canonical representation and
43// computes its hash.
44func CanonicalizeAndHash(path string) (canonical string, hash string, err error) {
45 canonical, err = CanonicalizeDir(path)
46 if err != nil {
47 return "", "", err
48 }
49 return canonical, DirHash(canonical), nil
50}
51
52// ParentWalk returns canonical parent paths (including the input) ascending to
53// the root. The caller can hash each entry as needed.
54func ParentWalk(canonicalPath string) []string {
55 if canonicalPath == "/" {
56 return []string{canonicalPath}
57 }
58 var parents []string
59 seen := make(map[string]struct{})
60 current := canonicalPath
61 for {
62 if _, exists := seen[current]; exists {
63 break
64 }
65 seen[current] = struct{}{}
66 parents = append(parents, current)
67 next := parentDir(current)
68 if next == current {
69 break
70 }
71 current = next
72 }
73 return parents
74}
75
76func parentDir(path string) string {
77 if path == "/" {
78 return path
79 }
80 idx := strings.LastIndex(path, "/")
81 if idx == -1 {
82 return path
83 }
84 parent := path[:idx]
85 if parent == "" {
86 return "/"
87 }
88 if runtime.GOOS == "windows" {
89 if len(parent) == 2 && parent[1] == ':' {
90 parent += "/"
91 }
92 }
93 return parent
94}