Thuta Learning
ExercisesDevOps & Toolsintermediate

Exercise: Diagnose a Git Problem

What you'll walk away with

  • Explain the core ideas behind Exercise: Diagnose a Git Problem
  • Read the diagram and trace how state or data flows through the Git/GitHub workflow
  • Decide how this applies to your own project or team

Build the mental model

When Git refuses to do what you asked, the tempting move is to guess a fix and try again. That habit is how small problems turn into bigger ones. The real skill in Git troubleshooting is diagnosis: reading exactly what Git tells you before you touch anything else.

  • `git status` -- what your working directory and staging area look like right now.
  • `git log --oneline --graph --all` -- the real shape of history across every branch and remote-tracking branch you know about.

Git's error and status messages are unusually literal -- a rejected push, a merge conflict marker, a "detached HEAD" warning each name the actual state of the repository, not a vague complaint. Those two commands above answer the two questions you need before doing anything else: where am I, and how did history actually get here.

This lesson works through one concrete case: a `git push` that Git rejects because the remote has commits your local clone has never seen -- the classic symptom of two people working from the same branch without syncing first. You will reproduce the rejection for real, using a local bare repository as a stand-in for GitHub.

Read the hint lines

Git's `hint:` lines after a rejected push are not filler -- they describe the actual cause and a real path forward. Reading them before Googling the error saves real time.

The exact command that resolves a diverged branch, a detached HEAD, or a wrongly staged file differs case by case. The diagnostic habit -- read the message, check status, check history, then act -- is the one skill that transfers to every Git problem you will ever hit.

text
PUSH REJECTED: LOCAL MAIN VS ORIGIN/MAIN
----------------------------------------
origin/main (bare repo)          your local "you" clone
------------------------         ------------------------
24e9b92 Initial commit           24e9b92 Initial commit
3e8f241 Add Contributing         44aff82 Add Installation
  ^ teammate pushed this           ^ you committed this,
    -- you don't have it            origin doesn't have it
       locally yet

                git push origin main
                        |
                        v
        ! [rejected]   main -> main (fetch first)
   "remote contains work that you do not have locally"

AFTER: git fetch reveals the divergence, then merge + push
  origin/main == local main == one new merge commit

Connect it to a real scenario

To make this real, you will build a tiny two-person team using nothing but a local bare repository as a stand-in for a GitHub remote. A bare repo (`git init --bare`) has no working directory -- it exists purely to be pushed to and pulled from, exactly like `origin` on GitHub.

Play yourself

Clone the bare repo and push an initial README commit.

Play the teammate

With a different GIT_AUTHOR_NAME, clone the same bare repo again and push a second commit directly to it.

Back in your own clone, without fetching first, commit a change of your own and run `git push`. Git refuses -- not a bug, but Git protecting shared history from being silently overwritten.

Read the exact rejection text it prints, then use `git status` and `git fetch` to see precisely how your branch and `origin/main` have diverged before deciding what to do next. By the end you will have watched a genuine push rejection happen and resolved it the same way you would on an actual team.

Try the working example

bash
# One-time setup
git config --global init.defaultBranch main

# Create a bare repo to stand in for a GitHub remote
git init --bare team-repo.git

# --- You: clone it and push the first commit ---
git clone team-repo.git you
cd you
export GIT_AUTHOR_NAME="Thuta Learner"
export GIT_AUTHOR_EMAIL="learner@example.com"
export GIT_COMMITTER_NAME="Thuta Learner"
export GIT_COMMITTER_EMAIL="learner@example.com"
export GIT_AUTHOR_DATE="2026-01-01T09:00:00"
export GIT_COMMITTER_DATE="2026-01-01T09:00:00"
echo "# Team Project" > README.md
echo "Initial setup." >> README.md
git add README.md
git commit -m "Initial commit: add README"
git push origin main
cd ..

# --- Teammate: clone the SAME bare repo, push a second commit ---
git clone team-repo.git teammate
cd teammate
export GIT_AUTHOR_NAME="Maung Maung"
export GIT_AUTHOR_EMAIL="maungmaung@example.com"
export GIT_COMMITTER_NAME="Maung Maung"
export GIT_COMMITTER_EMAIL="maungmaung@example.com"
export GIT_AUTHOR_DATE="2026-01-01T10:15:00"
export GIT_COMMITTER_DATE="2026-01-01T10:15:00"
echo "" >> README.md
echo "## Contributing" >> README.md
echo "See CONTRIBUTING.md for guidelines." >> README.md
git add README.md
git commit -m "Add Contributing section to README"
git push origin main
cd ..

# --- You again: commit locally WITHOUT fetching first, then push ---
cd you
export GIT_AUTHOR_DATE="2026-01-01T11:00:00"
export GIT_COMMITTER_DATE="2026-01-01T11:00:00"
echo "" >> README.md
echo "## Installation" >> README.md
echo "Run npm install to get started." >> README.md
git add README.md
git commit -m "Add Installation section to README"
git push origin main

# --- Diagnose: what does Git actually tell us? ---
git status
git log --oneline
git fetch origin
git log --oneline --graph --all
git status
You should see
The final push is rejected:

To .../team-repo.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '.../team-repo.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.

Before fetching, `git status` says:

On branch main
Your branch is ahead of 'origin/main' by 1 commit.

That is technically true but misleading -- it reflects the last fetch, not the remote as it is right now. After `git fetch origin`, `git log --oneline --graph --all` shows two branches that share the "Initial commit" ancestor and then split into separate commits, and `git status` now reports the real picture:

On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.

5-minute try-it

Your teammate merged their change first. You finish your own edit, commit it, and run `git push origin main` -- but instead of the usual `main -> main` success line, Git prints "! [rejected] main -> main (fetch first)" followed by a hint explaining that "the remote contains work that you do not have locally." Meanwhile `git status` still says "Your branch is ahead of 'origin/main' by 1 commit" -- which sounds like everything is fine, even though it is not telling the whole story. What command would show you what `git status` alone can't, and what should you check about the shape of history before deciding how to bring your branch and the remote back in sync?

One important caution

Reaching for `git push --force` to make the rejection go away can silently overwrite a teammate's commits.

Trusting `git status` alone right after a rejected push is misleading -- it only reflects your last fetch, not the remote's current state.

Pro Git -- Working with RemotesGit & GitHub

Easy traps

  • Reaching for `git push --force` to make the rejection go away can silently overwrite a teammate's commits.
  • Trusting `git status` alone right after a rejected push is misleading -- it only reflects your last fetch, not the remote's current state.
  • Always run `git status` before any destructive or history-rewriting command, to know exactly what state you're in.

Exercise

Your teammate merged their change first. You finish your own edit, commit it, and run `git push origin main` -- but instead of the usual `main -> main` success line, Git prints "! [rejected] main -> main (fetch first)" followed by a hint explaining that "the remote contains work that you do not have locally." Meanwhile `git status` still says "Your branch is ahead of 'origin/main' by 1 commit" -- which sounds like everything is fine, even though it is not telling the whole story. What command would show you what `git status` alone can't, and what should you check about the shape of history before deciding how to bring your branch and the remote back in sync?

You'll know it worked when: The final push is rejected: To .../team-repo.git ! [rejected] main -> main (fetch first) error: failed to push some refs to '.../team-repo.git' hint: Updates were rejected because the remote contains work that you do not hint: have locally. This is usually caused by another repository pushing to hint: the same ref. If you want to integrate the remote changes, use hint: 'git pull' before pushing again. Before fetching, `git status` says: On branch main Your branch is ahead of 'origin/main' by 1 commit. That is technically true but misleading -- it reflects the last fetch, not the remote as it is right now. After `git fetch origin`, `git log --oneline --graph --all` shows two branches that share the "Initial commit" ancestor and then split into separate commits, and `git status` now reports the real picture: On branch main Your branch and 'origin/main' have diverged, and have 1 and 1 different commits each, respectively.

Exercise: Diagnose a Git Problem | Thuta Learning