repository.go RawHistoryFolder

1package snapshot
2
3import (
4	"log/slog"
5	"sync"
6
7	"github.com/bnema/vev/internal/ports"
8)
9
10const (
11	repositorySessionsDir = "sessions"
12	repositoryObjectsDir  = "objects"
13	repositoryGenerations = "generations"
14	repositoryHead        = "HEAD"
15	generationWidth       = 20
16	maxRepositoryRead     = maxSnapshotFileSize
17)
18
19// Repository is the crash-safe, content-addressed session snapshot store.
20type Repository struct {
21	dir string
22	log *slog.Logger
23
24	// sessionStateMu owns locks. A caller retains a reference before it waits
25	// on a session mutex, so an idle entry can never be removed while a waiter
26	// still holds its old mutex.
27	sessionStateMu sync.Mutex
28	locks          map[string]*sessionMutex
29}
30
31var _ ports.SnapshotRepository = (*Repository)(nil)
32
33// NewRepository creates a repository rooted at dir with the default logger. It
34// does not create files until the first publication, so construction is side-effect free.
35func NewRepository(dir string) *Repository {
36	return NewRepositoryWithLogger(dir, slog.Default())
37}
38
39// NewRepositoryWithLogger creates a repository rooted at dir with log.
40func NewRepositoryWithLogger(dir string, log *slog.Logger) *Repository {
41	if log == nil {
42		log = slog.Default()
43	}
44	return &Repository{
45		dir:   dir,
46		log:   log,
47		locks: make(map[string]*sessionMutex),
48	}
49}
50
51// sessionMutex is retained by each waiter. That reference makes eviction
52// safe: a new caller cannot lock a replacement mutex until all users of the
53// prior mutex have left it.
54type sessionMutex struct {
55	key        string
56	mu         sync.Mutex
57	references int
58}
59
60func (r *Repository) lockSession(key string) *sessionMutex {
61	r.sessionStateMu.Lock()
62	lock := r.locks[key]
63	if lock == nil {
64		lock = &sessionMutex{key: key}
65		r.locks[key] = lock
66	}
67	lock.references++
68	r.sessionStateMu.Unlock()
69	lock.mu.Lock()
70	return lock
71}
72
73func (r *Repository) unlockSession(lock *sessionMutex) {
74	lock.mu.Unlock()
75	r.releaseSessionReference(lock)
76}
77
78func (r *Repository) releaseSessionReference(lock *sessionMutex) {
79	r.sessionStateMu.Lock()
80	lock.references--
81	if lock.references < 0 {
82		r.sessionStateMu.Unlock()
83		panic("snapshot: release of unretained session lock")
84	}
85	if lock.references == 0 {
86		if r.locks[lock.key] != lock {
87			r.sessionStateMu.Unlock()
88			panic("snapshot: session lock registry mismatch")
89		}
90		delete(r.locks, lock.key)
91	}
92	r.sessionStateMu.Unlock()
93}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close