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[]:
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
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:
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()
void cgit_print_snapshot(const char *head, const char *hex,
const char *prefix, const char *filename,
int snapshots)
Parameters:
head— Branch/tag referencehex— Commit SHAprefix— 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:
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.gzmyrepo-main.zip→ ref=main, format=.zipmyrepo-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:
- Format enabled: The format's bit must be set in the snapshot mask
- Reference exists: The ref must resolve to a valid git object
- Object type: Must be a commit, tag, or tree
Archive Generation: write_archive_type()
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:
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:
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:
repo.snapshot-prefixif set- 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
# 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-sizelimits - Snapshot requests for non-existent refs return a 404 error page
- The snapshot filename is sanitized to prevent path traversal