Skip to content

Combining and exchanging work

The previous pages cover reading and recording history. This page covers combining it, transforming files on the way in and out, and attaching extra meaning to commits. girt doesn’t cover most of these yet.

A merge combines two lines of work. Git finds their merge base, diffs each side against it, and keeps both sets of changes. A file changed on only one side takes that side’s version. Where both sides changed overlapping regions differently, Git records a conflict. Adjacent changes can also be grouped into one conflict. The index holds the available base, ours and theirs versions in stages 1–3; a missing version has no entry at its stage. Text conflicts put markers in the working file:

<<<<<<< HEAD
let limit = 10;
=======
let limit = 20;
>>>>>>> feature

Binary conflicts keep our version in the working file without text markers; the index retains the available original sides for resolution.

Whitespace options change which lines compare equal without rewriting accepted payloads. Attributes can select text, binary, union or custom merge drivers for a path. Marker presentation is separate from the original versions retained in the index.

When there are several merge bases, Git’s default strategy first merges the bases together into a virtual base. Cherry-pick, revert, rebase and stash are all merges underneath: cherry-picking C is a merge whose base is C’s parent. Without merging, a tool can read history but can’t combine work.

girtPartial

Content and whitespace policies, rename-aware tree/commit merging with clean recursive bases, original conflict stages, per-path attribute/custom drivers and guarded conflict checkout. Conflicted virtual bases, directory-stage expansion and ambiguous rename layouts remain limited. Remaining work is tracked in #493.

API docs

Cherry-pick applies a commit’s change to a new destination. The selected parent supplies the base, the destination supplies ours, and the picked commit supplies theirs. Revert swaps the commit and its parent to apply the inverse change. A merge commit needs a mainline parent so Git knows which change to replay. Rebase repeats cherry-picking onto each newly created destination commit.

Stash records working files and the index as commits, with an optional extra parent for untracked files. Applying a stash merges those saved changes into the current checkout. These operations can stop on the same conflicts as a merge; the provisional tree alone does not resolve them.

girtPartial

Object-only cherry-pick, revert and ordered rebase sequences with mainline selection, commit metadata, explicit empty policies and first-conflict progress. Linear todo execution and explicit Git sequencer recovery metadata are supported. Stash snapshots, apply/index plans and files-backend stash history are supported through explicit publication; merge-topology replay, repository stash capture/cleanup and reftable stash history editing remain partial. Remaining work is tracked in #608.

API docs

Filters transform file contents between the repository and the working tree:

  • Line endings. With core.autocrlf or the text and eol attributes, Git stores LF in the repository and writes CRLF on Windows checkouts.
  • ident expands $Id$ to $Id: <blob id> $ on checkout and collapses it again when adding.
  • Clean and smudge drivers (filter.<name>.clean and .smudge) run an external program: clean when adding a file, smudge when checking it out. Git’s long-running filter process protocol keeps one program running for many files.

Git LFS works this way: the repository stores a small pointer file, and the smudge filter replaces it with the real file on checkout. Without filters, checkout writes the pointer instead of the file, and status reports converted files as changed.

girtPartialLine endings, ident, clean/smudge commands and the long-running filter process (Git LFS works) in girt::filter, which runs commands only when the caller allows it. working-tree-encoding for UTF-16 and UTF-32 only (#593); no delay capability. Status and checkout apply them through Repository::status_with_filters and Repository::checkout_tree_with_filters, and clone, Repo and the CLI apply them by default.API docs

Applying a patch takes a unified diff and changes files to match: git apply edits the working tree or index, checking that each hunk’s context lines still match. git format-patch turns commits into mail-ready patch files, and git am parses a mailbox and recreates the commits with their authors and messages. Patch-based review, moving changes between repositories, and tools that edit code by generating diffs all depend on applying them.

girtPartialBounded unified text parsing, exact-position byte-content application and in-memory tree composition with explicit object installation in girt::apply. Mode changes, new/deleted files, renames/copies and Git binary literal/delta patches are supported, including reverse application. Index drafts are computed for explicit publication through the index lock API. Combined diffs, working-file publication and mail workflows remain unsupported.issue #506

A submodule embeds another repository at a fixed commit. The parent’s tree has a gitlink entry (mode 160000) holding a commit ID from the other repository, and .gitmodules says where to fetch it:

[submodule "vendor/lib"]
path = vendor/lib
url = https://example.com/lib.git

git submodule update --init clones the submodule and checks out the recorded commit. Status reports whether a submodule’s checkout matches the recorded commit.

girtPartialRead and lexically validate .gitmodules from explicit worktree/index/commit sources and resolve relative URLs with girt::submodule. Inspect gitlink registration, HEAD and nested dirtiness with ignore precedence; configured parent status includes visible changes. Compute initialization assignments and recorded-commit update plans without side effects. Init/update execution and clone recursion remain unsupported.issue #507

Notes attach text to a commit after it was made, without changing its ID. They live in an ordinary commit history under refs/notes/commits, whose trees map commit IDs to note blobs, split into fanout directories (ab/cdef…) once there are many. Review sign-offs, CI results and links to review pages are stored this way.

girtPartialRead arbitrary fanout; compose and store flat notes histories. Fanout writes, merges and automatic rewrite propagation remain.issue #508

.mailmap (or the file in mailmap.file, or the blob in mailmap.blob) maps old or inconsistent names and emails to one canonical identity:

git log, git blame and git shortlog use it, so one person doesn’t appear as several.

girtPartialIdentity mapping and configured sources; home-prefixed paths require caller expansion.issue #509

Commits and tags can carry a signature proving who made them, stored as a gpgsig header in the commit or appended to the tag message. Signing calls gpg, or ssh-keygen -Y sign when gpg.format=ssh. Verifying (git verify-commit, git log --show-signature) checks the signature against trusted keys: the GPG keyring, or for SSH the allowed signers file in gpg.ssh.allowedSignersFile. Forges and security-conscious tools need verification.

girtSupportedSigning and verifying commits and tags with gpg, gpgsm and ssh-keygen, configured as Git configures them.issue #510

git describe names a commit by the nearest earlier tag: v1.2.0-14-gabc1234 means 14 commits after v1.2.0, at commit abc1234. Build tools use it for version strings.

girtPartialRepository::describe with tag kinds, patterns, candidates and first-parent; counts are exact where Git estimates. No --dirty, --broken or --contains yet.issue #511

A pathspec is the mini-language commands use to choose paths: src/*.rs, ':(exclude)tests', ':(icase)readme', and ':/' for the repository root from anywhere inside it. Status, diff, log, add and checkout all take pathspecs, so they need one shared implementation that matches identically everywhere.

A spec without wildcards names a path and everything under it on component boundaries, so src selects src/a.rs but sr selects nothing. Without :(glob) magic, * matches / too: *.rs selects src/sub/b.rs. With it, * stops at / and ** spans directories.

git log -- <pathspec> shows the commits that change a selected path. A merge that left those paths exactly as one of its parents had them is skipped, and the walk follows only that parent, so side branches that never touched the paths are not listed.

girtPartialParsing and matching are in girt::pathspec; status, tree diff and log take pathspecs but refuse attr: magic, and log has only Git’s default merge simplification. Remaining work is tracked in #495.API docs

Before committing, Git cleans up the message according to commit.cleanup. By default, when the message was edited in an editor, it strips lines starting with #, trailing whitespace, and leading and trailing blank lines, and collapses runs of blank lines; a message given with -m keeps # lines. Trailers are the Key: value lines at the end of a message, such as Signed-off-by: and Co-authored-by:; git interpret-trailers parses and adds them. Both are pure text functions that commit tools need.

girtPartialCleanup and trailers are in girt::message; trailer.<name>.command is refused.issue #512

“3 ahead, 5 behind origin/main” counts the commits each side has that the other doesn’t, measured from their merge base (git rev-list --left-right --count main...origin/main). Every status bar and branch list shows it. Counted naively, it walks both histories; a commit-graph lets the walk stop early.

girtSupportedObjects::ahead_behind stops early with a commit-graph; without one it reads both full histories.issue #513

git reset moves the current branch, at one of three depths: --soft moves the branch only; --mixed (the default) also resets the index; --hard also resets the working files. git reset -- path resets one path’s index entry, which is how a file gets unstaged. Each is a combination of ref updates, index writes and checkout, done in a safe order.

girtPartialSoft and mixed resets and path resets in girt::reset, with ORIG_HEAD and reflog messages as Git writes them; hard, keep and merge resets are not yet supported.issue #514

Git itself assumes objects live in .git/objects/ and refs in .git/refs/ or reftable. Libraries can instead let callers supply storage: an in-memory repository for tests, a database or object store on a server, or another tool’s own storage. Every read path goes through that interface, so it is a design decision before it is code.

girtNot yetissue #515

Applications often need to customise remote access: handle a custom URL scheme, inspect a TLS certificate or SSH host key and decide whether to accept it, route through a proxy, or supply credentials their own way. Git does this with remote-<scheme> helper programs, http.proxy, http.sslCAInfo and SSH configuration; a library needs callbacks for the same decisions. Enterprise networks, custom authentication and tests depend on them.

girtPartialHTTP proxies, client certificates, http.extraHeader and http.userAgent are configurable; no custom URL schemes or certificate and host-key callbacks.issue #516