Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 28m ago 32290114
history blame raw 3.3 KiB
1package git
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "strings"
8)
9
10// Writers of one ref are serialized inside one repository.
11//
12// CommitFiles reads the branch, decides, and moves the ref with a compare-and-swap.
13// That is correct on its own, but under load every writer reads a tree it is about
14// to lose and pays for the read again: measured on a 2,000-file branch, a write
15// needed one attempt alone and 21 with 32 writers, and the retries were then most
16// of the work. Holding this lock across the read-modify-write turns that race into
17// a queue, so the compare-and-swap is left for writers that went around the lock —
18// an admin push, a hand-run `git update-ref`, or a second host.
19//
20// The lock is a file in the git directory held with flock(2), so it covers the
21// server, the post-receive hook (which is its own process), and a CLI on the same
22// host. The kernel releases it when the process exits, so a writer that is killed
23// mid-commit does not leave the branch waiting behind a lock nobody holds. It is
24// advisory and per-ref: nothing else in the repository reads it, and a writer that
25// takes the lock still competes correctly with one that does not.
26func (r *Repo) lockWrites(ref string) (func(), error) {
27 path := filepath.Join(r.gitDir(), "vole-write-"+lockName(ref)+".lock")
28 f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o644)
29 if err != nil {
30 return nil, fmt.Errorf("lock %s: %w", ref, err)
31 }
32 release, err := lockFile(f)
33 if err != nil {
34 f.Close()
35 return nil, fmt.Errorf("lock %s: %w", ref, err)
36 }
37 return func() {
38 release()
39 _ = f.Close()
40 }, nil
41}
42
43// lockName turns a ref into one file name: refs/heads/vole becomes
44// refs-heads-vole. Anything that is not plainly a name becomes a dash, so a ref can
45// never be read as a path.
46func lockName(ref string) string {
47 var b strings.Builder
48 for _, c := range ref {
49 switch {
50 case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9', c == '-', c == '_':
51 b.WriteRune(c)
52 default:
53 b.WriteByte('-')
54 }
55 }
56 return b.String()
57}
58
59// gitDir is the directory git keeps this repository's own files in: the directory
60// itself for a bare repository, .git for a clone with a worktree. RefCommit takes
61// the same view of Dir, and the lock file belongs beside the refs it guards.
62//
63// It answers from the filesystem because a write already starts several git
64// processes and the lock has to be taken before the first of them. The process is
65// a fallback for the layouts a directory check cannot recognise.
66func (r *Repo) gitDir() string {
67 if isGitDir(r.Dir) {
68 return r.Dir
69 }
70 dot := filepath.Join(r.Dir, ".git")
71 if isGitDir(dot) {
72 return dot
73 }
74 // A linked worktree or a submodule keeps a .git file that names the real
75 // directory instead of being one.
76 if data, err := os.ReadFile(dot); err == nil {
77 if rest, ok := strings.CutPrefix(strings.TrimSpace(string(data)), "gitdir:"); ok {
78 dir := strings.TrimSpace(rest)
79 if !filepath.IsAbs(dir) {
80 dir = filepath.Join(r.Dir, dir)
81 }
82 if isGitDir(dir) {
83 return dir
84 }
85 }
86 }
87 if out, _, err := r.run(nil, "rev-parse", "--absolute-git-dir"); err == nil {
88 if dir := strings.TrimSpace(string(out)); dir != "" {
89 return dir
90 }
91 }
92 return r.Dir
93}
94
95func isGitDir(dir string) bool {
96 if _, err := os.Stat(filepath.Join(dir, "HEAD")); err != nil {
97 return false
98 }
99 _, err := os.Stat(filepath.Join(dir, "objects"))
100 return err == nil
101}
102