Home / Alt manpages / git-update-ref(1)

  • git-update-ref(1)
  • User command
  • linux

Safely Move Git Refs with git-update-ref

git-update-ref changes a ref's value without you editing files under .git by hand, which is exactly how people corrupt a repository at 2am. This covers creating or moving a ref, requiring an expected old value before it moves, deleting with a guard, and committing several ref changes as one transaction. Allow about fifteen minutes, and work in a disposable test repository before you touch anything valuable.

This guide describes Git 2.43.0 from Ubuntu package git 1:2.43.0-1ubuntu7.3 and its matching git-man package. Check your version first: later Git releases may add options this installed manual does not mention.

1. Prepare a ref and record its current value

git-update-ref is a plumbing command. It changes the name stored in a ref, such as refs/heads/review, but it does not create a commit or touch the index. Reach for it only when you have a specific ref-level operation in mind.

Run these read-only checks inside the repository; none need elevated privileges:

$ git --version
git version 2.43.0
$ git rev-parse --git-dir
.git
$ OID=$(git rev-parse HEAD)
$ printf '%s\n' "$OID"
0123456789abcdef0123456789abcdef01234567

Your object ID will differ. The variable is used below so the examples track the repository you are actually testing against. If HEAD is unborn, use an existing object ID from git rev-parse <known-commit> instead.

Checkpoint

Inspect the ref before you touch it:

$ git show-ref --verify --quiet refs/heads/review
$ printf 'review: %s\n' "$(git rev-parse refs/heads/review 2>/dev/null || printf 'absent')"
review: absent

The first command succeeds only if the ref exists. The second reports its object ID or the word absent. A missing ref and a ref set to all zeroes are not the same thing: the zero value is an input convention that asserts a ref does not exist.

2. Create a ref only if it is absent

Pass the ref name, the new object ID, and an old object ID. Forty zeroes as the old value mean creation is allowed only when the ref does not already exist:

$ git update-ref refs/heads/review "$OID" 0000000000000000000000000000000000000000
$ git rev-parse refs/heads/review
0123456789abcdef0123456789abcdef01234567

A successful command prints nothing. The following rev-parse check should match $OID. If another process created the ref first, the compare-and-swap check fails instead of silently overwriting it. For a normal branch, prefer Git's higher-level commands when they say what you mean; this plumbing command earns its keep in scripts and tools that must update an exact ref against an explicit expected value.

3. Move a ref with a compare-and-swap guard

To move an existing ref safely, read its current ID, pick a new one, then supply the old ID as the final argument. The update only succeeds if the ref still holds that value when Git locks it:

$ OLD=$(git rev-parse refs/heads/review)
$ NEW=$(git rev-parse HEAD~1)
$ git update-ref -m 'move review for test' refs/heads/review "$NEW" "$OLD"
$ test "$(git rev-parse refs/heads/review)" = "$NEW" && echo 'review moved'
review moved

The -m text lands in a reflog entry when logging is on; it is not a commit message and creates no commit. If a concurrent update changes the ref after you read $OLD, Git reports that it cannot lock the ref and leaves it alone.

Warning

Moving a branch ref changes what commands such as git log review and other users of that ref see. It does not rewrite the commits themselves, but it can make commits temporarily hard to find. Record the old ID before the change.

4. Delete a ref with an undo value

Deletion is conditional too. -d removes the ref only when its current value matches the old value you supply:

$ OLD=$(git rev-parse refs/heads/review)
$ git update-ref -d refs/heads/review "$OLD"
$ git show-ref --verify --quiet refs/heads/review; printf 'status: %s\n' "$?"
status: 1

This is a destructive ref change, so stop before running it if you have not saved the old object ID. Recovery is straightforward while that ID is still around:

$ git update-ref refs/heads/review "$OLD" 0000000000000000000000000000000000000000
$ git rev-parse refs/heads/review
0123456789abcdef0123456789abcdef01234567

That restore recreates the ref only if it is still absent. Someone else already recreated it? The zero old value protects their ref, and the command fails.

5. Update several refs as one transaction

For related updates, feed commands to --stdin. This transaction creates two refs, prepares their locks, and commits both together:

$ printf 'start\nupdate refs/heads/review %s\nupdate refs/heads/test-copy %s\nprepare\ncommit\n' "$OID" "$OID" \
    | git update-ref --stdin
start: ok
prepare: ok
commit: ok
$ git show-ref refs/heads/review refs/heads/test-copy
0123456789abcdef0123456789abcdef01234567 refs/heads/review
0123456789abcdef0123456789abcdef01234567 refs/heads/test-copy
  • Each instruction is a line. update can include an old value, while create, delete and verify express stricter intent.
  • A repeated ref in one input is an error. If Git cannot lock one of the refs, or an expected value does not match, the whole transaction is not committed.
  • Each individual ref update is atomic, but a concurrent reader may still see a subset of a multi-ref change. Use start, prepare and commit when the transaction lifecycle and its failure point need to be explicit.
  • A script that exits before commit aborts the transaction.

6. Avoid the common traps

  • Do not write .git/HEAD or files under .git/refs with shell redirection. update-ref follows Git's symbolic-ref rules, locks the ref, checks object names and reports failures; a plain redirect does none of that.
  • Do not omit the old value in a script that must never overwrite a concurrent change. Read it, pass it, and handle a non-zero exit status.
  • Symbolic refs are dereferenced by default. Updating HEAD normally updates its target. Use --no-deref only when you deliberately want to overwrite the named ref itself.
  • Reflogs depend on core.logAllRefUpdates and the type of ref. Use --create-reflog when this operation specifically needs one, and make sure Git has committer identity to write it.
  • Do not use sudo for ref updates. Elevated ownership just makes the repository harder to maintain, and it does not make a mismatched ref safe to overwrite.

Done means

  • You identified the exact ref and recorded its old object ID.
  • Creation, movement or deletion used an expected old value wherever concurrency mattered.
  • Every changed ref was checked with git rev-parse or git show-ref.
  • You kept the old ID so a mistaken change can be restored without guessing.
  • Related changes went through a reviewed --stdin transaction, not scattered edits to .git files.