main de06cbd

inflate.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6	"bufio"
7	"bytes"
8	"compress/flate"
9	"encoding/binary"
10	"errors"
11	"hash"
12	"hash/adler32"
13	"io"
14	"math"
15	"sync"
16)
17
18// buffer is a pooled byte slice. Pooling the pointer keeps Put free of
19// allocations.
20type buffer struct{ b []byte }
21
22// maxPooledBuffer is the largest buffer that goes back to the pool, so a
23// single big blob does not pin memory.
24const maxPooledBuffer = 4 << 20
25
26var bufPool = sync.Pool{New: func() any { return &buffer{b: make([]byte, 0, 4096)} }}
27
28func getBuf() *buffer { return bufPool.Get().(*buffer) }
29
30func putBuf(b *buffer) {
31	if b == nil || cap(b.b) > maxPooledBuffer {
32		return
33	}
34	b.b = b.b[:0]
35	bufPool.Put(b)
36}
37
38// grow returns b with length n, reusing its capacity when possible. The
39// content is not preserved.
40func grow(b []byte, n int) []byte {
41	if cap(b) >= n {
42		return b[:n]
43	}
44	// Spare room for the next, slightly bigger object; n+n/4 may overflow
45	// an int on 32-bit systems, where the size is capped at MaxInt.
46	c := n
47	if n <= math.MaxInt-n/4 {
48		c = n + n/4
49	}
50	return make([]byte, n, c)
51}
52
53// Errors of the zlib wrapper. Callers wrap them in ErrCorrupt.
54var (
55	errZlibHeader   = errors.New("zlib: invalid header")
56	errZlibDict     = errors.New("zlib: preset dictionary")
57	errZlibChecksum = errors.New("zlib: invalid checksum")
58)
59
60// zlibReader inflates a zlib stream (RFC 1950). It is compress/zlib's reader
61// without its allocation per Reset: Go's zlib.Reader.Reset calls
62// adler32.New() each time, while here the checksum state and the flate
63// decompressor are both created once and reset. The adler32 trailer is
64// verified when the deflate stream ends; it is the only check of the
65// inflated content, since neither the pack trailer nor the idx CRCs are
66// verified.
67//
68// A zlibReader is not safe for concurrent use.
69type zlibReader struct {
70	src  flate.Reader
71	fl   io.ReadCloser // *flate decompressor, created at the first Reset
72	rs   flate.Resetter
73	sum  hash.Hash32
74	err  error // sticky; io.EOF once the stream and its checksum are verified
75	tail [4]byte
76}
77
78// Reset starts reading the zlib stream at the current position of src. It
79// reads the 2-byte header: deflate with a window of at most 32 KiB, a valid
80// check value, no preset dictionary (Git never writes one).
81func (z *zlibReader) Reset(src flate.Reader) error {
82	z.src, z.err = src, nil
83	cmf, err := src.ReadByte()
84	if err == nil {
85		var flg byte
86		if flg, err = src.ReadByte(); err == nil {
87			switch {
88			case cmf&0x0f != 8 || cmf>>4 > 7 || (uint(cmf)<<8|uint(flg))%31 != 0:
89				err = errZlibHeader
90			case flg&0x20 != 0:
91				err = errZlibDict
92			}
93		}
94	}
95	if err != nil {
96		if errors.Is(err, io.EOF) {
97			err = io.ErrUnexpectedEOF
98		}
99		z.err = err
100		return err
101	}
102	if z.fl == nil {
103		z.fl = flate.NewReader(src)
104		z.rs = z.fl.(flate.Resetter)
105		z.sum = adler32.New()
106	} else {
107		_ = z.rs.Reset(src, nil) // never fails without a dictionary
108		z.sum.Reset()
109	}
110	return nil
111}
112
113func (z *zlibReader) Read(p []byte) (int, error) {
114	if z.err != nil {
115		return 0, z.err
116	}
117	n, err := z.fl.Read(p)
118	_, _ = z.sum.Write(p[:n]) // never fails
119	if err != io.EOF {
120		z.err = err
121		return n, err
122	}
123	// End of the deflate stream: the big-endian adler32 of the content.
124	if _, err := io.ReadFull(z.src, z.tail[:]); err != nil {
125		if errors.Is(err, io.EOF) {
126			err = io.ErrUnexpectedEOF
127		}
128		z.err = err
129		return n, err
130	}
131	if binary.BigEndian.Uint32(z.tail[:]) != z.sum.Sum32() {
132		z.err = errZlibChecksum
133		return n, z.err
134	}
135	z.err = io.EOF
136	return n, io.EOF
137}
138
139// memInflater inflates a zlib stream held in memory.
140type memInflater struct {
141	zr      zlibReader
142	br      bytes.Reader
143	scratch [maxLooseHeader]byte // for reads through the io.Reader interface, which escape
144}
145
146var memPool = sync.Pool{New: func() any { return new(memInflater) }}
147
148// getMem returns an inflater positioned at the start of src.
149func getMem(src []byte) (*memInflater, error) {
150	m := memPool.Get().(*memInflater)
151	m.br.Reset(src)
152	if err := m.zr.Reset(&m.br); err != nil {
153		putMem(m)
154		return nil, corrupt("zlib header: %v", err)
155	}
156	return m, nil
157}
158
159func putMem(m *memInflater) {
160	m.br.Reset(nil)
161	memPool.Put(m)
162}
163
164// fileInflater inflates a zlib stream read from a file.
165type fileInflater struct {
166	zr      zlibReader
167	br      *bufio.Reader
168	scratch [maxLooseHeader]byte
169}
170
171var filePool = sync.Pool{New: func() any { return &fileInflater{br: bufio.NewReaderSize(nil, 8192)} }}
172
173func getFile(r io.Reader) (*fileInflater, error) {
174	f := filePool.Get().(*fileInflater)
175	f.br.Reset(r)
176	if err := f.zr.Reset(f.br); err != nil {
177		putFile(f)
178		return nil, corrupt("zlib header: %v", err)
179	}
180	return f, nil
181}
182
183func putFile(f *fileInflater) {
184	f.br.Reset(nil)
185	filePool.Put(f)
186}
187
188// inflateExact fills dst from zr and checks that the stream ends right
189// after, so a stream shorter or longer than declared is corrupt.
190func inflateExact(zr io.Reader, dst []byte, probe []byte) error {
191	if _, err := io.ReadFull(zr, dst); err != nil {
192		return corrupt("inflate: %v", err)
193	}
194	n, err := zr.Read(probe[:1])
195	if n != 0 {
196		return corrupt("inflate: object longer than its declared size")
197	}
198	if !errors.Is(err, io.EOF) {
199		return corrupt("inflate: %v", err)
200	}
201	return nil
202}

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 1

Tags 2

↑↓ moveenter openesc close