Chapter 07Lesson 04~95 minutes

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.

DiagnosticsNon-fast-forwardPruneAuthentication

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

  1. Preserve evidence: exact command/output, current branch/OID, intended remote/ref.
  2. Inspect local state: status, branch/upstream, refs, config, working-tree cleanliness.
  3. Inspect remote state without mutation: ls-remote, remote show, dry-run push where applicable.
  4. Classify the layer: transport/trust, authentication/authorization, refspec/destination, stale observation, or integration graph.
  5. 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
Never normalize disabling host-key checks. That converts a trust failure into silent acceptance of an unverified server.

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
Do not fix TLS errors with a global 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

Do not use these as first-response fixes: plain 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?

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.

Next

Checkpoint the full two-clone synchronization lifecycle

Lesson 5 intentionally triggers a non-fast-forward rejection, integrates safely, performs one successful explicit-lease rewrite, then proves the same lease blocks an overwrite after a second clone advances the remote.

Authoritative references

 git-push
 git-fetch
 git-pull
 git-remote
 gitcredentials

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.