diff options
Diffstat (limited to 'docs/handbook/cgit/snapshot-system.md')
| -rw-r--r-- | docs/handbook/cgit/snapshot-system.md | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/docs/handbook/cgit/snapshot-system.md b/docs/handbook/cgit/snapshot-system.md new file mode 100644 index 0000000000..bb39047f48 --- /dev/null +++ b/docs/handbook/cgit/snapshot-system.md @@ -0,0 +1,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 |
