Push a Git Branch Without Losing Anyone's Work
A rejected git push is Git protecting you, not an obstacle to force past. This guide checks the branch and destination, previews the exact update, and shows how to recover from the two common surprises: an upstream mismatch and a non-fast-forward rejection. The examples use Git 2.43.0, as installed with the local git-man package.
The route
Jump straight to the step you need, or tick off Done means at the end.
- You need: a Git repository with at least one commit, and permission to write to its remote.
- No elevated privileges. Do not reach for
sudoto solve an authentication or remote-branch problem, it can change which credentials and configuration Git uses. - Time: about 10 minutes if the remote is already configured.
1. Check the branch and its destination
Start in the repository that holds the commit you want to publish. Check the current branch, working tree and configured remotes:
$ git branch --show-current
$ git status --short --branch
$ git remote -v
- First command should print a branch name such as
feature/reporting. - Status output should show the branch and, ideally, no unexpected file changes.
- Remote list should show the repository you expect, commonly named
origin.
Check the upstream relationship rather than relying on memory:
$ git branch -vv
* feature/reporting 1a2b3c4 [origin/feature/reporting: ahead 2] Add report export
The bracketed part names the remote-tracking branch used for comparison. No brackets means no upstream connected yet. A plain git push normally consults the current branch's remote configuration, then defaults the remote to origin if none is set. With no explicit refspec, Git's default push mode is normally simple: push the current branch to a same-named upstream, and refuse when the names do not match.
2. Preview the exact update
Run a dry run before the real push. It contacts the remote and checks the proposed update, but sends no ref update:
$ git push --dry-run origin HEAD:feature/reporting
To ssh://[email protected]/team/reports.git
9f8e7d6..1a2b3c4 HEAD -> feature/reporting
HEAD:feature/reporting means the commit checked out right now becomes the remote branch named feature/reporting. Naming both sides avoids a branch-name guess. A dry run is a check, not a reservation, another writer can update the remote before your real command lands.
If the preview names an unexpected host or branch, stop. Fix the remote or refspec and preview again:
$ git remote get-url origin
$ git ls-remote --heads origin feature/reporting
The second command prints the remote object ID and full ref when the branch exists, making the destination visible without touching either repository.
3. Push the branch and set its upstream when needed
- Existing, same-named upstream: the short command is usually enough.
$ git push
- New local branch: choose the remote branch explicitly and record the relationship.
$ git push --set-upstream origin feature/reporting
branch 'feature/reporting' set up to track 'origin/feature/reporting'.
After that, plain git push and git pull both know the destination for this branch. The upstream is local configuration, setting it does not create a second copy of your work.
- Local name differs from the destination, or you're scripting it: keep using an explicit refspec.
$ git push origin HEAD:refs/heads/release-candidate
The destination must name an actual remote ref; the source, such as HEAD, can be an object expression, but the destination cannot. A successful push reports a ref status like feature/reporting -> feature/reporting: a leading * means a new remote ref, a blank flag means a plain fast-forward, = means already up to date.
4. Resolve a non-fast-forward rejection without deleting history
A push is normally accepted only when the remote tip is an ancestor of the commit you are sending. If somebody else pushed first, expect:
! [rejected] feature/reporting -> feature/reporting (non-fast-forward)
error: failed to push some refs
Fetch the remote history, then look at the divergence:
$ git fetch origin
$ git log --oneline --graph --decorate --all -20
- Keep both lines of history: merge, then push.
$ git merge origin/feature/reporting
$ git push
- Project permits rebasing: rebase your unpublished commits onto the remote tip instead.
$ git rebase origin/feature/reporting
$ git push
Resolve any conflicts, then continue or abort as Git instructs: git merge --abort returns to the pre-merge state where possible, and git rebase --abort does the equivalent for a rebase. Warning: do not run git push --force just to silence this rejection, a forced update can make commits unreachable from the branch.
5. Rewrite an existing branch only with a lease
Rewriting a branch after an amend or rebase is sometimes intentional, but it changes published history. Warn collaborators first, make sure you have a recovery point, and prefer a lease over unrestricted force:
$ git fetch origin
$ git push --force-with-lease origin HEAD:feature/reporting
The lease updates the remote only if its current value matches the remote-tracking value you have; if the remote moved since your fetch, the command fails instead of overwriting that work. For the strongest, most explicit check, record the expected object ID and supply it:
$ git rev-parse origin/feature/reporting
9f8e7d6c5b4a32100112233445566778899aabb
$ git push --force-with-lease=feature/reporting:9f8e7d6c5b4a32100112233445566778899aabb \
origin HEAD:feature/reporting
Warning
A lease is not a lock. Background fetches can update your remote-tracking refs and change what the lease means. Never use --mirror or --prune casually, they can update or remove multiple remote refs at once. Deleting a single remote branch is also destructive:
$ git push origin --delete feature/obsolete
Only run that after checking the exact branch name and agreeing its remote ref may disappear. Recovery depends on the commit still being available locally or in another clone, so record its object ID before deletion if the history matters.
6. Verify the remote result
After a successful push, compare the remote ref with your intended commit:
$ git rev-parse HEAD
1a2b3c4d5e6f77889900aabbccddeeff00112233
$ git ls-remote --heads origin feature/reporting
1a2b3c4d5e6f77889900aabbccddeeff00112233 refs/heads/feature/reporting
The IDs should match. A remote rejected response means the server rejected a hook, deletion or non-fast-forward update; a transport or remote failure means check connectivity and the server before repeating a command that could have partially completed elsewhere.
Done means
- Checked before pushing. Current branch, remote URL and upstream destination confirmed.
- Previewed first.
git push --dry-runshowed the intended ref update. - Pushed and verified. The expected branch landed, and its remote object ID matches.
- Integrated, not forced. Remote work was merged or rebased in rather than overwritten.
- Rewrites reviewed. Any intentional rewrite used a fresh
--force-with-leaseand warned collaborators first.