v0.1.0 7deb448

snapshot.go RawHistoryFolder

1package vt
2
3import (
4	"fmt"
5	"math"
6)
7
8// DecodeStats describes resources declared by one canonical VT blob.
9type DecodeStats struct {
10	Chunks uint64
11	Rows   uint64
12	Cells  uint64
13	Styles uint64
14	Bytes  uint64
15}
16
17// Add adds another preflight result with overflow checking.
18func (s *DecodeStats) Add(other DecodeStats) bool {
19	for _, totals := range [][2]*uint64{{&s.Chunks, &other.Chunks}, {&s.Rows, &other.Rows}, {&s.Cells, &other.Cells}, {&s.Styles, &other.Styles}, {&s.Bytes, &other.Bytes}} {
20		if math.MaxUint64-*totals[0] < *totals[1] {
21			return false
22		}
23		*totals[0] += *totals[1]
24	}
25	return true
26}
27
28// PreflightHistoryBlob validates one self-contained history blob without
29// allocating decoded rows.
30func PreflightHistoryBlob(data []byte) (DecodeStats, error) {
31	stats, ok := preflightHistory(data)
32	if !ok {
33		return DecodeStats{}, fmt.Errorf("preflight history: %w", errInvalidHistory)
34	}
35	return DecodeStats{Chunks: stats.chunks, Rows: stats.rows, Cells: stats.cells, Styles: stats.styles, Bytes: stats.bytes}, nil
36}
37
38// MarshalHistoryChunk encodes one immutable chunk as a self-contained blob.
39func MarshalHistoryChunk(chunk *HistoryChunk) ([]byte, error) {
40	if chunk == nil {
41		return nil, fmt.Errorf("marshal history chunk: %w", errInvalidHistory)
42	}
43	return MarshalHistory(HistoryView{chunks: []*HistoryChunk{chunk}, rows: len(chunk.rows)})
44}
45
46// MarshalEmptyHistoryTail returns the mandatory canonical empty tail blob.
47func MarshalEmptyHistoryTail() ([]byte, error) { return MarshalHistory(HistoryView{}) }
48
49// MarshalHistoryTail encodes the copied mutable tail of a snapshot view as one
50// canonical history blob. It does not seal or otherwise mutate live history.
51func MarshalHistoryTail(view HistorySnapshotView) ([]byte, error) {
52	return MarshalHistory(view.Tail())
53}
54
55// MarshalSealedHistory serializes a SealAndView result as oldest-first,
56// self-contained sealed blobs plus a mandatory empty canonical tail blob.
57func MarshalSealedHistory(view HistoryView) ([][]byte, []byte, error) {
58	if view.rows != historyViewRowCount(view) {
59		return nil, nil, fmt.Errorf("marshal sealed history: %w", errInvalidHistory)
60	}
61	sealed := make([][]byte, len(view.chunks))
62	for i, chunk := range view.chunks {
63		blob, err := MarshalHistoryChunk(chunk)
64		if err != nil {
65			return nil, nil, err
66		}
67		sealed[i] = blob
68	}
69	tail, err := MarshalEmptyHistoryTail()
70	if err != nil {
71		return nil, nil, err
72	}
73	return sealed, tail, nil
74}
75
76// HistoryFromBlobs restores history directly from sealed, oldest-first blobs
77// and the mandatory tail blob. It never feeds decoded rows through Append.
78func HistoryFromBlobs(config HistoryConfig, sealed [][]byte, tail []byte) (*History, error) {
79	h := NewHistory(config)
80	for _, blob := range sealed {
81		view, err := UnmarshalHistory(blob)
82		if err != nil || len(view.chunks) != 1 || len(view.chunks[0].rows) == 0 {
83			return nil, fmt.Errorf("restore sealed history: %w", errInvalidHistory)
84		}
85		h.evictUntil(len(view.chunks[0].rows), view.Cells())
86		h.chunks = append(h.chunks, view.chunks[0])
87		h.rows += len(view.chunks[0].rows)
88		h.cells += view.Cells()
89	}
90	view, err := UnmarshalHistory(tail)
91	if err != nil || len(view.chunks) > 1 {
92		return nil, fmt.Errorf("restore history tail: %w", errInvalidHistory)
93	}
94	if len(view.chunks) == 1 {
95		h.evictUntil(len(view.chunks[0].rows), view.Cells())
96		h.tail = view.chunks[0].rows
97		h.tailBounds = growBounds(view.chunks[0].bounds, len(h.tail))
98		h.rows += len(h.tail)
99		h.cells += view.Cells()
100	}
101	h.evict()
102	h.normalizeTail()
103	return h, nil
104}
105
106// NewScreenWithRecoveryTranscript constructs a fresh blank screen whose
107// history contains the restored bounded history followed by the recovery
108// transcript. The transcript is decoded in full before history is restored.
109func NewScreenWithRecoveryTranscript(width, height int, config HistoryConfig, sealed [][]byte, tail, transcript []byte) (*Screen, error) {
110	transcriptView, err := UnmarshalHistory(transcript)
111	if err != nil {
112		return nil, fmt.Errorf("restore recovery transcript: %w", err)
113	}
114
115	history, err := HistoryFromBlobs(config, sealed, tail)
116	if err != nil {
117		return nil, err
118	}
119	remainingRows := transcriptView.rows
120	for _, chunk := range transcriptView.chunks {
121		for i, row := range chunk.rows {
122			remainingRows--
123			bound := chunk.bounds[i]
124			if remainingRows == 0 {
125				bound.Soft = false
126			}
127			if err := history.Append(row, bound); err != nil {
128				return nil, fmt.Errorf("restore recovery transcript: %w", err)
129			}
130		}
131	}
132
133	screen := NewScreenWithHistory(width, height, config)
134	screen.history = history
135	return screen, nil
136}

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