1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
# cgit — Snapshot System
## Overview
cgit can generate downloadable source archives (snapshots) from any git
reference. Supported formats include tar, compressed tar variants, and zip.
The snapshot system validates requests against a configured format mask and
delegates archive generation to the git archive API.
Source file: `ui-snapshot.c`, `ui-snapshot.h`.
## Snapshot Format Table
All supported formats are defined in `cgit_snapshot_formats[]`:
```c
const struct cgit_snapshot_format cgit_snapshot_formats[] = {
{ ".zip", "application/x-zip", write_zip_archive, 0x01 },
{ ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x02 },
{ ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x04 },
{ ".tar", "application/x-tar", write_tar_archive, 0x08 },
{ ".tar.xz", "application/x-xz", write_tar_xz_archive, 0x10 },
{ ".tar.zst", "application/x-zstd", write_tar_zstd_archive, 0x20 },
{ ".tar.lz", "application/x-lzip", write_tar_lzip_archive, 0x40 },
{ NULL }
};
```
### Format Structure
```c
struct cgit_snapshot_format {
const char *suffix; /* file extension */
const char *mimetype; /* HTTP Content-Type */
write_archive_fn_t fn; /* archive writer function */
int bit; /* bitmask flag */
};
```
### Format Bitmask
Each format has a power-of-two bit value. The `snapshots` configuration
directive sets a bitmask by OR-ing the bits of enabled formats:
| Suffix | Bit | Hex |
|--------|-----|-----|
| `.zip` | 0x01 | 1 |
| `.tar.gz` | 0x02 | 2 |
| `.tar.bz2` | 0x04 | 4 |
| `.tar` | 0x08 | 8 |
| `.tar.xz` | 0x10 | 16 |
| `.tar.zst` | 0x20 | 32 |
| `.tar.lz` | 0x40 | 64 |
| all | 0x7F | 127 |
### Parsing Snapshot Configuration
`cgit_parse_snapshots_mask()` in `shared.c` converts the configuration
string to a bitmask:
```c
int cgit_parse_snapshots_mask(const char *str)
{
int mask = 0;
/* for each word in str */
/* compare against cgit_snapshot_formats[].suffix */
/* if match, mask |= format->bit */
/* "all" enables all formats */
return mask;
}
```
## Snapshot Request Processing
### Entry Point: `cgit_print_snapshot()`
```c
void cgit_print_snapshot(const char *head, const char *hex,
const char *prefix, const char *filename,
int snapshots)
```
Parameters:
- `head` — Branch/tag reference
- `hex` — Commit SHA
- `prefix` — Archive prefix (directory name within archive)
- `filename` — Requested filename (e.g., `myrepo-v1.0.tar.gz`)
- `snapshots` — Enabled format bitmask
### Reference Resolution: `get_ref_from_filename()`
Decomposes the requested filename into a reference and format:
```c
static const struct cgit_snapshot_format *get_ref_from_filename(
const char *filename, char **ref)
{
/* for each format suffix */
/* if filename ends with suffix */
/* extract the part before the suffix as the ref */
/* return the matching format */
/* strip repo prefix if present */
}
```
Example decomposition:
- `myrepo-v1.0.tar.gz` → ref=`v1.0`, format=`.tar.gz`
- `myrepo-main.zip` → ref=`main`, format=`.zip`
- `myrepo-abc1234.tar.xz` → ref=`abc1234`, format=`.tar.xz`
The prefix `myrepo-` is the `snapshot-prefix` (defaults to the repo basename).
### Validation
Before generating an archive, the function validates:
1. **Format enabled**: The format's bit must be set in the snapshot mask
2. **Reference exists**: The ref must resolve to a valid git object
3. **Object type**: Must be a commit, tag, or tree
### Archive Generation: `write_archive_type()`
```c
static int write_archive_type(const char *format, const char *hex,
const char *prefix)
{
struct archiver_args args;
memset(&args, 0, sizeof(args));
args.base = prefix; /* directory prefix in archive */
/* resolve hex to tree object */
/* call write_archive() from libgit */
}
```
The actual archive creation is delegated to Git's `write_archive()` API,
which handles tar and zip generation natively.
### Compression Pipeline
For compressed formats, the archive data is piped through compression:
```c
static int write_tar_gzip_archive(/* ... */)
{
/* pipe tar output through gzip compression */
}
static int write_tar_bzip2_archive(/* ... */)
{
/* pipe tar output through bzip2 compression */
}
static int write_tar_xz_archive(/* ... */)
{
/* pipe tar output through xz compression */
}
static int write_tar_zstd_archive(/* ... */)
{
/* pipe tar output through zstd compression */
}
static int write_tar_lzip_archive(/* ... */)
{
/* pipe tar output through lzip compression */
}
```
## HTTP Response
Snapshot responses include:
```
Content-Type: application/x-gzip
Content-Disposition: inline; filename="myrepo-v1.0.tar.gz"
```
The `Content-Disposition` header triggers a file download in browsers with
the correct filename.
## Snapshot Links
Snapshot links on repository pages are generated by `ui-shared.c`:
```c
void cgit_print_snapshot_links(const char *repo, const char *head,
const char *hex, int snapshots)
{
for (f = cgit_snapshot_formats; f->suffix; f++) {
if (!(snapshots & f->bit))
continue;
/* generate link: repo/snapshot/prefix-ref.suffix */
}
}
```
These links appear on the summary page and optionally in the log view.
## Snapshot Prefix
The archive prefix (directory name inside the archive) is determined by:
1. `repo.snapshot-prefix` if set
2. Otherwise, the repository basename
For a request like `myrepo-v1.0.tar.gz`, the archive contains files under
`myrepo-v1.0/`.
## Signature Detection
cgit can detect and display signature files alongside snapshots. When a
file matching `<snapshot-name>.asc` or `<snapshot-name>.sig` exists in the
repository, a signature link is shown next to the snapshot download.
## Configuration
| Directive | Default | Description |
|-----------|---------|-------------|
| `snapshots` | (none) | Space-separated list of enabled suffixes |
| `repo.snapshots` | (inherited) | Per-repo override |
| `repo.snapshot-prefix` | (basename) | Per-repo archive prefix |
| `cache-snapshot-ttl` | 5 min | Cache TTL for snapshot pages |
### Enabling Snapshots
```ini
# Global: enable tar.gz and zip for all repos
snapshots=tar.gz zip
# Per-repo: enable all formats
repo.url=myrepo
repo.snapshots=all
# Per-repo: disable snapshots
repo.url=internal-tools
repo.snapshots=
```
## Security Considerations
- Snapshots are generated on-the-fly from git objects, so they always reflect
the repository's current state
- Large repositories can produce large archives — consider enabling caching
and setting appropriate `max-blob-size` limits
- Snapshot requests for non-existent refs return a 404 error page
- The snapshot filename is sanitized to prevent path traversal
|