Files
holocron/plugins/git/.apm/skills/git-remotes/references/push.md
Defame1297 38eb0745b7 refactor(git-remotes): retrofit to the ADR-0020 context contract
Description 582 -> 237 chars, body 1217 -> 290 words. The single remotes.md
splits into config, fetch, push, and pull flow files.

Restores the confirm: true token to the force-push gate -- it is the git
plugin's cross-skill contract, gated on by git-orchestrate and git-branches.
Moves push.md's worked example off main, which the skill's own Step 1
refuses, and restores the never-bare---force directive.
2026-08-30 13:10:53 +00:00

3.5 KiB

topic, source_keys
topic source_keys
push
git-scm-push-docs
context7-git-htmldocs

Pushing

Default: safe push to the same-named branch on the remote.

  • Force-push: never bare --force. Use git push --force-with-lease --force-if-includes <remote> <branch>, after the SKILL.md Step 1 gate.
  • Basic: git push <remote> <branch>
  • Set upstream: git push -u <remote> <branch> — push and configure tracking
  • Multi-remote: push sequentially (git push origin develop, git push staging develop), or add a second push URL with git remote set-url --add <name> <url> to reach both in one command
  • Delete a remote branch: git push <remote> --delete <branch> — clearer than the :<branch> form
  • Bulk: git push --all (all local branches), git push --tags (all tags), git push origin <tag> (one tag)
  • Delete remote branches with no local counterpart: git push --prune origin 'refs/heads/*:refs/heads/*'
  • Force only part of a multi-ref push: prefix the one refspec that needs it with + — git push origin +release develop forces release while safe-pushing develop. A + prefix is a force-push and passes the SKILL.md Step 1 gate like any other.

Refspec syntax — [+]<src>[:<dst>]

Pattern Meaning
<branch> Push to same-named remote branch
<src>:<dst> Push <src> local ref to <dst> remote ref
+<src>:<dst> Force this refspec (non-fast-forward allowed) — a force-push; passes the SKILL.md Step 1 gate
:<branch> Delete remote <branch>
refs/heads/*:refs/heads/* Glob: push all matching branches
^refs/heads/dev-* Negative: exclude matching refs
tag <name> Sugar for refs/tags/<name>:refs/tags/<name>

Force-push safety — full detail

--force-with-lease rejects the push if the remote ref moved since your last fetch. Three forms:

Form What it protects
--force-with-lease (bare) All refs being pushed, checked against your remote-tracking branch
--force-with-lease=<refname> Named ref only
--force-with-lease=<refname>:<sha> Named ref must be at exact SHA — most stable

Caveat with the bare form: any background process that runs git fetch (IDE plugin, cron job, editor auto-fetch) updates your remote-tracking branch, which can make the lease check pass even though someone else pushed in between. The protection is silently defeated.

Two mitigations:

# Option 1 — dedicated push-only remote: background tools fetch `origin`, you push
# through a separate remote that nothing else touches, so its tracking ref can't be
# poisoned by an unrelated fetch.
git remote add origin-push $(git config remote.origin.url)
git push --force-with-lease origin-push

# Option 2 — explicit SHA via a local tag, unaffected by tracking-branch state
git fetch
git tag base master
git rebase -i master
git push --force-with-lease=master:base master:master

--force-if-includes adds a second check on top of bare --force-with-lease: it verifies the remote-tracking tip actually appears in your local branch's reflog, i.e. you genuinely integrated it before rewriting. It is a no-op without --force-with-lease, and has no effect with the --force-with-lease=<ref>:<sha> form, which already pins an exact SHA.

Safest combination: git push --force-with-lease --force-if-includes origin.

Server-side policy

receive.denyDeletes, receive.denyDeleteCurrent and receive.denyNonFastForwards are enforced on the remote regardless of any local flag — a hardened server rejects the push even with --force.