git fast-import writes commits straight into a repository's object database from a stream on standard input, without touching a worktree at all. You will finish with a small Git repository created from that stream, a main branch, and one checked-in file. The example uses git-fast-import from Git 2.43.0, supplied here by Debian package git-man 1:2.43.0-1ubuntu7.3.
Allow about fifteen minutes. You need Git and a shell. No elevated privileges are needed when you own the destination directory. This guide changes a repository's object database and branch refs, so use a new temporary repository first. Do not point the example at a valuable repository until you have a backup and a recovery plan.
Confirm the binary and package version before relying on syntax. These are read-only checks:
$ git --version
git version 2.43.0
$ dpkg-query -W -f='${Package} ${Version}\n' git-man
git-man 1:2.43.0-1ubuntu7.3
fast-import does not read or update the working tree. It uses the repository selected by Git, including GIT_DIR when that variable is set, which makes it useful for importers with their own extraction directory. It also means a successful command can alter refs without changing any visible files in the worktree.
Checkpoint: decide the exact destination before sending any data. If it is an existing repository, record its current refs with git show-ref and make a backup. For this walkthrough, create an empty repository under /tmp:
$ DEST=$(mktemp -d /tmp/fast-import-demo.XXXXXX)
$ git init "$DEST"
Initialized empty Git repository in /tmp/fast-import-demo.XXXXXX/.git/
The random directory suffix will differ. The git init command is ordinary and unprivileged. Keep the value of DEST in the same shell for the remaining steps.
Run this in the empty repository. The stream creates refs/heads/main, marks the new commit as :1, adds README.txt, and then ends with done:
$ git -C "$DEST" fast-import --done <<'EOF'
commit refs/heads/main
mark :1
author Guide Example <[email protected]> 1704067200 +0000
committer Guide Example <[email protected]> 1704067200 +0000
data 22
Initial imported file
M 100644 inline README.txt
data 24
Imported by fast-import
progress import-finished
done
EOF
progress import-finished
fast-import statistics: ...
Do not copy the abbreviated statistics line as an expected transcript: its object counts and memory figures vary. The meaningful success signals are the progress line and a zero exit status. If your output instead reports fatal: stream ends early, check the byte counts in the two data commands. They include the terminating newline: the first message is 22 bytes and the second is 24.
The stream format is deliberately exact. A command line uses single spaces, raw file data follows a byte count, and an unexpected blank line can become part of that data. Keep the quoted heredoc delimiter so the shell does not expand text inside the stream. The progress line is optional and is echoed to standard output; --done makes a missing final done an error.
Inspect the result from Git rather than trusting a successful-looking progress message:
$ git -C "$DEST" show-ref
1bac51cae12defbfd8bed472da4e2f181072c2d5 refs/heads/main
$ git -C "$DEST" log --oneline --decorate --all
1bac51c (HEAD -> main) Initial imported file
$ git -C "$DEST" show main:README.txt
Imported by fast-import
Your object ID will differ if you change the identity, timestamp or message. Check the ref name, commit subject and file content instead. A fast-import stream writes objects directly, so there is no index update and no checkout to perform.
For an automated check, test the branch and exact content separately:
git -C "$DEST" show-ref --verify --quiet refs/heads/main
test "$(git -C "$DEST" show main:README.txt)" = 'Imported by fast-import'
printf '%s\n' 'import verified'
Expected output:
import verified
A mark is an importer-side name for an object. mark :1 assigns the commit a temporary reference that later commands can use as from :1 or merge :1. Marks are not branch names and are not a replacement for the final ref. In a larger stream, mark each commit that a later branch or merge needs.
For repeated imports, export marks to a file and load them on the next run:
$ git -C "$DEST" fast-import --export-marks="$DEST/marks.txt" < stream.fi
$ git -C "$DEST" fast-import --import-marks="$DEST/marks.txt" < next-stream.fi
The input marks file must exist and use the format produced by export. Use --import-marks-if-exists=FILE when the first run is allowed to have no marks file. By default, mark paths are taken as supplied; with --relative-marks, they are relative to .git/info/fast-import in this form of the command. Keep marks files private if your importer treats object history or source paths as sensitive.
Warning: these options are file operations performed by fast-import. On Git 2.43.0 they are classified as unsafe stream features and are rejected from the stream unless you explicitly pass --allow-unsafe-features. Do not add that option to make an untrusted importer work: it allows filesystem access features, and the manual specifically limits it to streams generated by a trusted program. Review the importer code and every input stream first.
On an existing branch, fast-import checks whether the new commit contains the old tip. If it is not a fast-forward update, it leaves that ref unchanged and warns, while continuing to try other branch updates. It does not lock a branch for the whole import. Coordinate with other writers and inspect the refs after completion.
Destructive action: --force permits modified existing branches even when commits would be lost because the new history does not contain the old one. This is destructive history rewriting. Take a full backup or clone, record the old ref, stop competing writers, and review the generated stream before using it:
$ git -C /path/to/repository show-ref refs/heads/main
$ git -C /path/to/repository fast-import --force < reviewed-stream.fi
There is no general undo command for a forced ref update. Recovery is possible only if you retained the old commit ID or another backup, for example by restoring the recorded ref with a reviewed git update-ref command. Do not use --force for an initial import into an empty repository.
First preserve the exact stream and the command's error output. Common failures are structural:
data byte count makes the next bytes look like a malformed command. Recount the data, including its newline.SP.git show-ref; do not assume every branch updated because the process continued.When an import aborts, do not immediately retry against the same valuable repository. Check which refs changed, retain the crash report if Git writes one, and compare the repository with your backup. A fresh disposable repository is the safest place to repair a stream. No part of this workflow requires sudo; elevated privileges can hide ownership mistakes and make recovery harder.
done command are understood.--allow-unsafe-features.