Chapter 01Lesson 02~100 minutes

Version Control Foundations and Distributed Collaboration: Guided Hands-On Workflow and Core Operations

Build a disposable Git repository step by step, observe working-tree and history state before and after each command, then exchange commits through a local bare repository and two independent clones.

Hands-oninit/add/commitClone/fetch/pushDisposable lab

Learning objectives

  • Verify the Git client and use the help system before relying on memorized syntax.
  • Initialize a repository with an explicit initial branch, configure safe local author metadata, and read status before changing state.
  • Explain what git add, git commit, git diff, and git log change or inspect.
  • Create a local bare repository and understand why it is suitable as a collaboration endpoint.
  • Clone, fetch, fast-forward, commit, and push while observing local branches and remote-tracking refs.
  • Use inspection-before-mutation as the default workflow and choose a command from state rather than from habit.

1. Lab contract: disposable, explicit, observable

This lesson changes repository state repeatedly, so it begins with a rule: use a new disposable directory. Do not point these commands at a work project. Every important mutation is bracketed by inspection so you can see cause and effect.

Course convention: when the lesson writes <oid>, your Git output will show an object ID appropriate to your repository's object format. Do not build automation that assumes a fixed display length.

2. Verify the client and ask Git for help

git --version
git help -a
git help init
git help status

git --version establishes which client you are running. git help -a lists available commands. git help <command> opens the command manual using your configured help viewer. In restricted environments where the viewer is inconvenient, git <command> -h prints a concise usage summary in the terminal.

3. Create the first repository with an explicit branch name

Use --initial-branch=trunk so the exercise behaves the same regardless of your global init.defaultBranch setting.

mkdir git-foundations-work
cd git-foundations-work
git init --initial-branch=trunk

git rev-parse --show-toplevel
git branch --show-current
git status --short --branch

Representative compact status:

## No commits yet on trunk

git init created repository metadata. It did not create a commit. git status is therefore describing an unborn branch: the branch name exists as the intended current branch, but there is not yet a commit for it to point at.

4. Configure author identity locally, not globally, for this lab

git config --local user.name "Learner Example"
git config --local user.email "learner@example.invalid"
git config --local --get user.name
git config --local --get user.email

These values become commit metadata. They are not passwords and they do not authenticate you to a remote. Using --local keeps the exercise from changing your global identity.

5. Working tree → staged snapshot → commit

Create a small file using your shell.

Git Bash, Bash, or zsh

printf "%s\n" "# Delivery Runbook" "" "Owner: platform-team" > runbook.md

PowerShell

@("# Delivery Runbook", "", "Owner: platform-team") | Set-Content -Encoding utf8 runbook.md

Now inspect before staging:

git status --short --branch
git diff -- runbook.md

Because the file is untracked, ordinary git diff does not show it as a tracked-file patch. git status is the important signal. Stage it, then inspect the staged comparison:

git add runbook.md
git status --short --branch
git diff --staged -- runbook.md

git add updates the index—the proposed content for the next commit. It does not create a commit and it does not upload anything. Now record the staged snapshot:

git commit -m "docs: add delivery runbook"
git status --short --branch
git log --oneline --decorate --graph -5

After the commit, the working tree and index match the recorded commit, so status should be clean.

6. Modify, compare, stage, and commit again

Git Bash, Bash, or zsh

printf "%s\n" "Preflight: verify staging health" >> runbook.md

PowerShell

"Preflight: verify staging health" | Add-Content -Encoding utf8 runbook.md

Before changing Git state, ask what changed:

git status --short
git diff -- runbook.md

The diff compares the working-tree version with the staged/index version. After git add, ordinary git diff becomes empty because the working tree and index agree; git diff --staged then shows what the next commit would contain.

git add runbook.md
git diff -- runbook.md
git diff --staged -- runbook.md
git commit -m "docs: add deployment preflight"
git log --oneline --decorate --graph -5

7. Map each command to the layer it reads or changes

Command Reads Changes
git status HEAD, index, working tree Nothing substantive
git diff Compared states Nothing
git add Working-tree content/pathspec Index
git commit Index + metadata Object database and current branch ref
git log Commit graph/refs Nothing

This table is intentionally simplified. Later chapters refine the index, objects, refs, and revision traversal. For now it gives you enough structure to predict the ordinary file lifecycle.

8. A bare repository: a collaboration endpoint without a normal working tree

Return to the parent directory and create a new bare repository. We keep the standalone repository above only as the first-workflow exercise; the collaboration exercise uses new repositories so the topology is obvious.

cd ..
git init --bare --initial-branch=trunk origin.git
git --git-dir=origin.git rev-parse --is-bare-repository
true

A bare repository stores Git repository data directly in the directory and does not have a normal checked-out working tree. Shared Git servers commonly use bare repositories because nobody should be editing a server-side working copy as part of normal collaboration.

9. Clone the empty origin, create history in Alice's clone, then push it

git clone origin.git alice
cd alice
git config user.name "Alice Example"
git config user.email "alice@example.invalid"
git status --short --branch

Cloning an empty repository may emit a warning that the repository is empty. The clone still has a configured origin. Create the first commit in Alice's clone:

Git Bash, Bash, or zsh

printf "%s\n" "# Shared Delivery Notes" "Owner: platform-team" > README.md

PowerShell

@("# Shared Delivery Notes", "Owner: platform-team") | Set-Content -Encoding utf8 README.md
git status --short --branch
git add README.md
git diff --staged -- README.md
git commit -m "docs: add shared delivery notes"
git log --oneline --decorate --graph --all -5
git push -u origin trunk

-u establishes upstream tracking for the local branch, allowing later git push or status output to use that relationship. Push transfers the necessary objects and asks the bare repository to update its trunk ref.

10. Bob clones history, then Alice advances the shared repository

cd ..
git clone origin.git bob
cd bob
git config user.name "Bob Example"
git config user.email "bob@example.invalid"
git log --oneline --decorate --graph --all -5
cd ../alice

Alice adds another commit

Git Bash, Bash, or zsh

printf "%s\n" "Preflight: verify staging before deploy" >> README.md

PowerShell

"Preflight: verify staging before deploy" | Add-Content -Encoding utf8 README.md
git status --short
git diff -- README.md
git add README.md
git commit -m "docs: add staging verification note"
git push

At this point Bob's repository does not automatically know that Alice pushed a commit. That is the distributed model in action.

11. Fetch first, inspect remote-tracking state, then fast-forward deliberately

cd ../bob
git status --short --branch
git log --oneline --decorate --graph --all -5
git fetch origin
git log --oneline --decorate --graph --all -5

Before fetch, Bob's local trunk and his local origin/trunk both reflect the old state. Fetch contacts the other repository, downloads new objects if required, and updates Bob's remote-tracking refs. It does not automatically move Bob's local trunk.

Because Bob has made no local commit since cloning, the local branch can move forward without creating a merge commit:

git merge --ff-only origin/trunk
git status --short --branch

--ff-only refuses to invent a merge commit if a simple pointer move is not possible. Full merge behavior belongs in Chapter 6; here we use the safest form needed to align Bob's branch with the fetched shared history.

12. Bob commits, pushes, and Alice proves her clone is independent

Git Bash, Bash, or zsh

printf "%s\n" "Rollback owner: platform-team" > rollback.md

PowerShell

"Rollback owner: platform-team" | Set-Content -Encoding utf8 rollback.md
git status --short
git add rollback.md
git commit -m "docs: add rollback owner"
git push

cd ../alice
git log --oneline --decorate --graph --all -6
git fetch origin
git log --oneline --decorate --graph --all -6
git merge --ff-only origin/trunk

Before Alice fetches, her clone cannot see Bob's new commit. After fetch, her origin/trunk advances while her local trunk remains where it was until she integrates. That sequence is the concrete proof that each clone maintains its own repository state.

13. Mini challenge: choose from state, not from memory

You are in Bob's clone. A teammate says, “I pushed documentation updates.” You have uncommitted local edits. Which first command should you run?

A strong answer begins with git status, because your local state determines what is safe. If the working state is understood and fetching will not overwrite it, git fetch origin is the next communication step. Do not jump directly to a command that integrates or rewrites history merely because “pull” sounds convenient.

14. Cleanup the disposable exercise

First verify you are about to remove only the disposable parent directory. Leave every repository, inspect its path, then remove the parent with your shell.

Git Bash, Bash, or zsh

cd ..
printf "Current directory: %s\n" "$PWD"
# Verify the disposable names before deletion:
ls
cd ..
rm -rf git-foundations-work origin.git alice bob

PowerShell

Set-Location ..
Get-Location
Get-ChildItem
Set-Location ..
Remove-Item -Recurse -Force git-foundations-work, origin.git, alice, bob
Cleanup warning: recursive deletion is an operating-system action, not a Git recovery feature. Run it only after confirming that these paths are the disposable lab directories you created.

15. Knowledge check

Question 1. What changes when you run <code>git add runbook.md</code>?

Question 2. Why does <code>git fetch</code> not make Bob’s local branch automatically identical to <code>origin/trunk</code>?

Question 3. What does a successful <code>git merge --ff-only origin/trunk</code> mean in this lab?

Question 4. After Bob pushes, why can Alice’s <code>git log</code> remain unchanged until she fetches?

Question 5. Which command should normally come before a state-changing command when you are unsure of local edits?

16. Summary

You have now seen the core loop rather than merely reading command definitions: inspect, modify a working file, stage the intended snapshot, inspect again, commit locally, and exchange commits explicitly through another repository. Clone created an independent repository. Fetch updated knowledge of another repository without silently moving the local branch. Push updated a remote ref only after a local commit existed.

Next

Make Git behavior predictable across machines

Lesson 3 explains repository boundaries, configuration scope and precedence, initial-branch naming, author identity versus authentication, ignore policy, commit quality, and why Git history is not a complete backup strategy.

Authoritative references

 git-init
 git-status
 git-add
 git-commit
 git-diff
 git-log
 git-clone
 git-fetch
 git-push

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.