Remotes, Refspecs, Fetch, Pull, Push, SSH, HTTPS, and Protocol Behavior: Diagnostics, Failure Modes, Security, and Performance
Diagnose remote failures by separating ref divergence, destination selection, pull policy, stale tracking state, SSH/HTTPS trust, authentication, authorization, and guarded force updates.
Learning objectives
- Diagnose a non-fast-forward rejection without reaching for plain force.
- Identify configuration that causes unexpected pull integration or implicit push destinations.
- Distinguish stale remote-tracking refs from live server refs and prune safely.
- Classify SSH and HTTPS failures by transport trust, authentication, and authorization boundary.
- Use explicit force-with-lease preconditions to prevent overwriting unseen remote updates.
1. Diagnostic sequence — do not “fix remote Git” until you know which boundary failed
- Preserve evidence: exact command/output, current branch/OID, intended remote/ref.
- Inspect local state: status, branch/upstream, refs, config, working-tree cleanliness.
-
Inspect remote state without mutation:
ls-remote,remote show, dry-run push where applicable. - Classify the layer: transport/trust, authentication/authorization, refspec/destination, stale observation, or integration graph.
- Choose the narrowest correction and verify both local and remote refs afterward.
2. Intentionally broken example — non-fast-forward push rejection
Two clones start from the same remote commit. Clone B pushes a new commit first. Clone A then creates an independent local commit and tries to push without fetching:
git status --short --branch
git branch -vv
git push origin trunk
Typical output contains a rejected update similar to:
! [rejected] trunk -> trunk (fetch first)
error: failed to push some refs to '...'
hint: Updates were rejected because the remote contains work that you do not have locally.
Line 1: the receiver refused to move
refs/heads/trunk because your proposed tip would not be
a fast-forward from its current tip. Line 2: the
push as requested did not complete. The hint: your
local view is incomplete; inspect/fetch before deciding how to
integrate.
git ls-remote origin refs/heads/trunk
git fetch origin
git log --graph --decorate --oneline --all -12
git rev-list --left-right --count trunk...origin/trunk
git diff trunk...origin/trunk
The repair is usually merge/rebase according to team policy, then a
normal push. It is not “add --force until it works.”
3. Failure mode — pull integrates differently from what you expected
If pull creates an unexpected merge, attempts a rebase, or refuses because only fast-forward is allowed, inspect the policy chain:
git status --short --branch
git branch -vv
git config --show-origin --show-scope --get-regexp '^(pull\.|branch\..*\.(rebase|remote|merge)|merge\.)'
git log --graph --decorate --oneline --all -12
For critical workflows, separate the phases:
git fetch origin, inspect the graph, then run an
explicit merge --ff-only, merge, or rebase. This makes
the integration choice visible before it changes local history.
4. Failure mode — pushing the wrong branch or wrong remote
A successful push can still be operationally wrong. Before release/deployment writes, prove four values: current branch/OID, push remote, destination ref, and proposed update.
git rev-parse --abbrev-ref HEAD
git rev-parse HEAD
git remote -v
git branch -vv
git remote get-url --push origin
git push --dry-run origin HEAD:refs/heads/trunk
Do not assume that origin is the writable production
repository or that the current branch has the same name as the
intended destination. remote.pushDefault,
branch.<name>.pushRemote,
remote.<name>.push, and
push.default can all affect implicit pushes.
5. Failure mode — stale remote-tracking refs look like live server state
If origin/feature remains after somebody deletes the
branch remotely, that does not prove the branch still exists:
git show-ref --verify refs/remotes/origin/feature
git ls-remote origin refs/heads/feature
git remote prune --dry-run origin
git fetch --prune origin
Pruning deletes stale local remote-tracking refs, not the
server branch. The safety concern shifts when tag pruning is
configured; review fetch.pruneTags/remote.<name>.pruneTags
before adopting blanket prune policy.
6. SSH failure classification
| Symptom | Boundary | Safer evidence |
|---|---|---|
Could not resolve hostname / timeout |
DNS/network/routing | Verify URL/host/port and network path |
Host key verification failed |
Server identity/trust | Verify expected host fingerprint with authoritative admin/provider source |
Permission denied (publickey) |
Client authentication/key selection | Inspect SSH agent/key config; use verbose SSH diagnostics carefully |
| Authenticated but repository denied/not found | Repository authorization/path | Verify repo URL and server permissions |
7. HTTPS failure classification
Certificate validation failures are TLS/server-trust problems; repeated username/token prompts are credential-helper or authentication problems; HTTP 403-style failures can be authorization/policy problems even when the credential is valid. Keep them separate.
git remote get-url origin
git config --show-origin --get-all credential.helper
git config --show-origin --get http.sslVerify
http.sslVerify=false.
Repair the trust chain, proxy/CA configuration, hostname, or
corporate certificate setup. Do not paste tokens into commands for
debugging.
8. Failure mode — somebody else updated the branch while you prepared a rewrite
This is the precise situation --force-with-lease is
designed to guard. Record the value you actually inspected, rewrite
only in a disposable/unpublished scenario, then require the remote
still to equal that value:
EXPECTED=$(git rev-parse origin/rewrite-demo)
# ... local rewrite occurs ...
git push --dry-run --force-with-lease=refs/heads/rewrite-demo:$EXPECTED origin HEAD:refs/heads/rewrite-demo
If another clone moved the remote branch, the lease rejects the
push. Plain --force would ignore that expectation and
can remove the other person's commit from the branch's reachable
history.
Current Git documentation also warns that lease forms relying
implicitly on remote-tracking refs can be undermined by background
fetches that update those refs. For high-assurance scripts, the
explicit ref:expected-OID form makes the precondition
visible.
9. Security concerns that are causal in remote operations
- Server identity: verify SSH host keys or HTTPS certificates before sending credentials.
- Credential confidentiality: use helpers/secret stores; do not embed tokens in URLs or publish trace logs without review.
- Least privilege: CI read jobs should not receive write credentials merely because human clones do.
- Authorization is server-side: local Git config cannot bypass a protected branch, receive hook, or repository permission rule.
- Commit identity is separate: a successful authenticated push does not cryptographically validate every commit's author field.
10. Performance belongs to negotiation and transferred state
Fetch transfers refs/objects you do not already have; protocol v2 reduces/structures some negotiation and is the current client default. Very large ref sets, histories, or blobs may need narrower fetches, partial clones, or repository maintenance, but those are later chapters. Here, optimize first by avoiding redundant fetch/pull loops and by diagnosing whether the delay is DNS/SSH/TLS/auth, ref advertisement, or object transfer.
11. Red-zone operations
git push --force to a shared branch, deleting remote
refs to “unstick” pull, destructive reset/clean, reflog expiry,
object pruning, or history rewrite merely to satisfy a push.
Preserve local and remote OIDs first; fetch and inspect the graph.
12. Symptom → first read-only questions
| Symptom | Question | Evidence |
|---|---|---|
| Push rejected | Did remote advance? Is destination correct? | ls-remote, fetch, graph, dry-run |
| Unexpected pull result | Which integration policy was active? | config origins + graph |
| Wrong repository updated | Which push URL/refspec was selected? |
remote -v, get-url --push, dry-run
|
| Remote branch “still exists” locally | Is it merely stale tracking state? | ls-remote, prune dry-run |
| SSH failure | Network, host key, client auth, or authorization? | URL + SSH diagnostics |
| HTTPS failure | TLS trust, credentials, or authorization? | URL/helper/CA + server response |
13. Knowledge check
Question 1. A push is rejected non-fast-forward. What is the safest first network mutation?
git fetch: it updates local remote-tracking
information so you can inspect the divergence without changing the
remote branch or automatically integrating.
Question 2. Why can git pull produce a different
graph on two machines?
Question 3. Does git fetch --prune remove somebody
else's server branch?
Question 4. A lease-protected force push is rejected after another user pushed. Is that a bug?
Question 5. Permission denied (publickey) means
your commit user.email is wrong. True or
false?
14. Summary
Remote failures become manageable when you separate local refs, live remote refs, refspec/upstream selection, transfer/integration, server trust, authentication, and authorization. The safest repair is evidence first, fetch/inspect next, then the narrowest ref or integration change that matches policy.
Authoritative references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.