A push affects a specific remote reference, and a fetch records remote history under specific local references. Refspecs make that mapping explicit. They become especially important when a team needs to replace a remote branch deliberately: a force-with-lease expectation can reject the update if the branch no longer has the value you reviewed. This check prevents one kind of unseen overwrite, but it does not decide whether replacing known work is appropriate. The local lab demonstrates a rejected stale lease and then preserves both contributions through a normal merge.
Read the source and destination names
A refspec generally uses source:destination. For a push, HEAD:refs/heads/main means take the current local commit and request an update to the remote main branch. For a fetch, refs/heads/main:refs/remotes/origin/main means read the remote branch and record it in a local remote-tracking reference. The words before and after the colon describe different repositories depending on the command, so read them in the context of fetch or push.
Remote-tracking references are local observations of remote state. origin/main is not a live connection to the server, and it is not automatically the same object as your local main branch. A fetch updates observations; merging or rebasing is a separate decision about integrating history. The default fetch mapping often covers many remote branches, but you can fetch a precise ref when a workflow needs a narrow, inspectable update. Prefer fully qualified destination names in automation where ambiguity would be costly.
Understand fast-forward checks and force
A normal branch push rejects a non-fast-forward update because the proposed tip would replace history rather than extend the current remote tip. That rejection is a coordination signal. Fetch and inspect the difference before deciding what to do. In ordinary collaboration, integrating the remote work and pushing a fast-forward result is often the correct outcome. Do not reach for force merely because a push failed.
A leading plus in a refspec or a force option can relax update checks, but a blind forced update can discard a collaborator's reachable work. A lease adds an expected old value to the requested ref update. The server must still see that expected value before the update can proceed. Other server rules and permissions may also reject the push. A successful lease check says the expected ref has not changed; it does not say that the new history preserves every contribution or satisfies the team's review policy.
Use an explicit expected object
The form --force-with-lease=refs/heads/main:<expected-object> records exactly which remote value you reviewed. Capture that identifier after an intentional fetch and inspection, and retain it for the update decision. If another person pushes afterward, the lease rejects the replacement. An explicit value is easier to reason about than a command that silently derives its expectation from a remote-tracking ref whose value may change in the background.
The shorthand form without an explicit object often uses remote-tracking information as its expectation. Background fetches by an editor or helper can update that information, weakening the assumption that the observed tip is the one you personally reviewed. An explicit expected identifier avoids that particular moving-input problem. It still requires good judgment: knowingly supplying a newer expected value without reviewing the newer work defeats the purpose of the human review boundary.
Preserve work when replacement is unnecessary
After a rejected lease, inspect what changed rather than retrying with unconditional force. Use history comparisons to understand the remote-only and local-only commits. If both changes can coexist, merge or rebase according to the repository's policy and push normally. If the team has explicitly agreed to replace history, preserve recovery references and state the expected old and new objects in the review. A lease belongs to that concrete decision, not to a generic recipe for making every push succeed.
Atomicity also has a scope. A lease checks the named reference for the requested update; it is not a lock on the whole repository or every external process. For multi-reference operations, consult the server's supported atomic push behavior and define each update's expectations. Avoid assuming a tag, deployment or issue state changed merely because a branch push succeeded. Keep the ref update and any subsequent release actions independently observable.
Sources and prerequisites
Read remotes, branches and workflows first. The official fetch reference explains mapping remote refs locally; the push reference explains leases, forced updates and server checks.
Git Refspecs and Explicit Force-With-Lease Checks Summary
- Read refspecs as source-to-destination mappings for the command in use.
- An explicit lease compares a remote ref with a stable expected object.
- After rejection, review and integrate remote work when replacement is unnecessary.
Git Refspecs and Explicit Force-With-Lease Checks Examples
Each example shows the code, the output it produces and what each part does.
Reject a stale explicit lease in a local remote
Run the full Bash lab in a fresh directory. Alice and Bob clone the same local bare repository. Bob captures the original tip, Alice pushes new work and Bob's attempted replacement is rejected because its explicit expectation is stale. No hosted repository is touched.
Push progress includes a stale-information rejection. The remote notes remain Alice's update. Bob's local commit is still available, so the rejection does not lose his work. It prevents replacing a remote tip that differs from the recorded expectation.
mkdir lease-lab
cd lease-lab
git init --bare hub.git
git init -b main alice
git -C alice config user.name Alice
git -C alice config user.email alice@example.test
printf 'Initial notes\n' > alice/notes.txt
git -C alice add notes.txt
git -C alice commit -m 'Initial notes'
git -C alice remote add origin ../hub.git
git -C alice push -u origin main
git clone -b main hub.git bob
cd bob
git config user.name Bob
git config user.email bob@example.test
expected=$(git rev-parse refs/remotes/origin/main)
printf 'Alice update\n' > ../alice/notes.txt
git -C ../alice commit -am 'Alice update'
git -C ../alice push origin main
printf 'Bob work\n' > bob.txt
git add bob.txt
git commit -m 'Bob work'
if git push --force-with-lease="refs/heads/main:$expected" origin HEAD:refs/heads/main; then
echo 'unexpected replacement'
else
echo 'lease rejected'
fi
git --git-dir=../hub.git show refs/heads/main:notes.txtlease rejected
Alice updateGit Refspecs and Explicit Force-With-Lease Checks in Real Projects
Fetch into an explicit ref and push a merge
Continue inside bob. Fetch the remote main into the explicit tracking destination, merge its contribution and push the combined history normally. The fixture changes different files, so the merge is conflict-free. Inspect both files in the bare repository after the push.
git fetch origin refs/heads/main:refs/remotes/origin/main
git merge --no-edit refs/remotes/origin/main
git push origin HEAD:refs/heads/main
git --git-dir=../hub.git show refs/heads/main:notes.txt
git --git-dir=../hub.git show refs/heads/main:bob.txtAlice update
Bob workThe remote now contains both contributions. A normal push was sufficient after integration, so no history replacement was necessary. In a real conflict, review and test the resolution before pushing rather than assuming this clean fixture represents every collaboration scenario.
Git Refspecs and Explicit Force-With-Lease Checks FAQ
Is origin/main always the latest server state?
No. It is a local remote-tracking reference updated by fetch operations. The server may have changed since your last fetch, and a background tool may update the local observation without a deliberate review. Distinguish the server's actual ref, your tracking ref and your local branch. Fetch intentionally, inspect the relevant commits and capture an explicit expected object when a replacement decision needs a stable reviewed value.
Does force-with-lease guarantee that no work is discarded?
It checks that the remote reference still has the expected value before allowing the requested update. You can still propose a replacement that omits known commits even when the expectation matches. Review what the new history removes, obtain the team's agreement when required and preserve recovery information. A lease protects against a particular unseen change; it does not determine whether your intended replacement is correct.
Why can a background fetch affect shorthand lease behavior?
Some lease forms derive expectations from local remote-tracking references. A background fetch can advance those references to a remote tip you have not inspected, so the expectation no longer necessarily represents your own review. Use the explicit ref-and-object form when that distinction matters. Capture the identifier deliberately and do not refresh it merely to bypass a rejection without reviewing the intervening remote work.
What should I do after a stale lease is rejected?
Fetch and inspect the new remote history, then decide whether the contributions should be integrated or an agreed replacement is still necessary. The lab merges the two independent changes and pushes normally. Avoid blindly retrying with --force or a freshly copied expectation. Keep your local work available, explain the intervening commits and perform relevant checks before requesting another remote update.
Git Refspecs and Explicit Force-With-Lease Checks Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Why does Bob's first push fail even though he uses force-with-lease?
Git Refspecs and Explicit Force-With-Lease Checks Quiz
2 questions to check what you learned.
In a fetch refspec, what does refs/remotes/origin/main name?
- The remote server's working directory
- The local branch currently checked out
- The local destination for the observed remote branch
Which response best preserves the two contributions in the fixture?
- Fetch, merge the compatible changes and push normally
- Replace the remote using unconditional force
- Delete Bob's local repository immediately