# Contributing to zig-git This file covers what you need to know to make a change and get it accepted. [README.md](README.md) says what zig-git is and where it is going. ## Where to send changes Report issues and send pull requests to the Tangled mirror: . ## Setting up Everything is pinned in the Nix flake, Zig 0.17.0 and `git` included: ```console $ git clone https://git.jcollie.dev/jeff/zig-git.git $ cd zig-git $ nix develop ``` Without Nix you need Zig 0.17.0 and a `git` on `PATH`. The tests that read a repository make it with `git`; without one they are skipped. ## Before you send a change CI runs these, and a change has to pass all of them: ```console $ reuse lint # every file has a license $ zig fmt --check --exclude zig-pkg . # Zig is formatted $ zig build test --summary all # every test, under SHA-1 and SHA-256 $ zig build check # compiles what nothing else builds $ nix build # the package; runs the tests in the sandbox ``` `nix build` sees only files git knows about. `git add` a new file before running it, or the build fails as if the file were missing. ## Where things are | Path | What | | --- | --- | | `src/root.zig` | The public API: what the `git` module exports | | `src/object.zig` | Object formats, object IDs, kinds, headers, hashing | | `src/Identity.zig` | Author, committer and tagger lines | | `src/commit.zig`, `src/tag.zig`, `src/tree.zig` | Views of commits, tags and trees | | `src/quote.zig` | Paths quoted as git quotes them | | `src/ObjectDatabase.zig` | The object database interface | | `src/loose.zig` | Loose objects | | `src/zlib.zig` | zlib streams, inflated and checked as git checks them | | `src/PackIndex.zig` | Pack indexes (`.idx`), versions 1 and 2 | | `src/pack.zig` | Packs: entries, delta chains, reading objects | | `src/delta.zig` | Applying a delta to its base | | `src/MultiPackIndex.zig` | The multi-pack-index | | `src/alternates.zig` | Reading `objects/info/alternates` and `GIT_ALTERNATE_OBJECT_DIRECTORIES` | | `src/DiskDatabase.zig` | The object database on disk: every objects directory, pack and loose object | | `src/Config.zig` | Configuration: its sources, includes, scopes and lookups | | `src/config/syntax.zig` | The configuration file syntax, and keys spelled as git spells them | | `src/config/value.zig` | Booleans, integers and paths as git reads them | | `src/config/parameters.zig` | `GIT_CONFIG_PARAMETERS` and `GIT_CONFIG_COUNT` | | `src/wildmatch.zig` | git's glob patterns | | `src/discover.zig` | Finding a repository as git does: the walk, `.git` files, ceilings, ownership | | `src/RepositoryFormat.zig` | A repository's format version and `extensions.*` | | `src/Repository.zig` | A repository, opened: where it is, its configuration and its objects | | `src/Diagnostics.zig` | What a failed read was about | | `src/testing/fixture.zig` | Repositories made with `git` for the tests | | `src/testing/packs.zig` | Packs and pack indexes written byte by byte for the tests | | `src/testing/fuzz.zig` | Fuzz test inputs from the corpus or the fuzzer | | `src/fuzz/` | The fuzz corpus: files `git` wrote, and patterns | | `tools/zig_git.zig` | `zig-git`, git's commands answered by the library, for comparing with git | | `tools/config_command.zig` | `zig-git config` | | `tools/discovery.zig` | How every `zig-git` command finds its repository, and git's words when it cannot | | `tools/rev_parse.zig` | `zig-git rev-parse`, for the discovery queries | | `tools/regex.zig` | POSIX extended regular expressions, for `zig-git config --get-regexp` | | `tools/docs_server.zig` | `zig build docs-serve` | | `GLOSSARY.md` | The words for the things in a repository | | `docs/adr/` | Decisions, and why | ## Making changes ### Never from git's source zig-git is MIT licensed and git is GPL-2.0-only, so nothing here may be translated from git's source, nor from libgit2, xdiff or any other implementation whose license this project could not carry (`docs/adr/0004-written-from-documentation-never-gits-source.md`). Don't read them while working on zig-git. What git does comes from its documentation — the manual pages, `gitformat-*`, the protocol documents — and from running it. When the documentation is silent, ask git: make a repository that exercises the case and see what it answers. ### Words Use the words in `GLOSSARY.md`, in code and in comments: an **object ID**, never a SHA or an OID; the **index** is the staging file, and a pack's `.idx` is a **pack index**. A new concept gets an entry there when it gets a name. ### git is the oracle Correct means what `git` does. A test that reads a repository makes it with `git` through `src/testing/fixture.zig` and checks the library's answer against what `git` says about the same repository, rather than against a value written into the test. The fixture runs `git` with no system or global configuration and a fixed clock, so object IDs are the same on every machine. The git the tests compare with is the one the flake pins. `zig build test` runs every test twice, the second time with `ZIG_GIT_TEST_OBJECT_FORMAT=sha256`, so each repository test reads a SHA-1 repository and a SHA-256 one. Nothing may assume an object ID is 20 bytes. Repositories `git` refuses to make — a corrupt object, a malformed tree — are written byte by byte in the test that needs them. A repository someone else owns cannot be made by a test that owns all its files. The tests that need one run `git` as root in a user namespace of its own (`unshare --map-auto --map-current-user --setuid 0 --setgid 0`), where the test's files belong to another user, and tell `zig-git` it is root too. Where no such namespace can be made — no `unshare`, no subordinate user IDs in `/etc/subuid`, or the Nix build sandbox — those tests are skipped; the library's own tests cover the same ground with a stand-in for the filesystem that says who owns what. `zig-git` (`tools/zig_git.zig`) is the same idea from the command line: some of git's commands answered by the library and printing what git prints, so `diff <(zig-git ls-tree -r HEAD) <(git ls-tree -r HEAD)` checks the library against git on any repository. Its tests run each command both ways and compare the output and exit status. ### Layers and I/O Reading a format (an object header, a commit, a tree) is done on bytes the caller already has, with no I/O, so it can be tested and fuzzed alone. Only the storage layer touches files, through a `std.Io` it is given. Nothing reads the environment, the clock or the current directory on its own. ### Errors Anything a repository can get wrong is an error, never a panic, and a function that can fail on the repository's account takes an optional `*Diagnostics` and fills it in. Something this library does not support returns an error saying so rather than an answer that is nearly right; see `docs/adr/0002-unsupported-revision-options-are-errors.md`. ### Documentation Every public declaration has a doc comment, since the published API documentation is made from them. Comments say why rather than what. The README describes the project as it is now, and its references cite the documents the code was written from.