Skip to content
Chapter 4Git2.5x

A full cycle, from init to merged pull request

Work through one complete feature the way it happens on a real team — branch, commit, rebase, push, review, merge — with the graph state shown at each step.

4 min read

Type this out. It is fifteen minutes and it covers the loop you will repeat for the rest of your career.

Start a repository

mkdir git-practice && cd git-practice
git init
echo "# Git practice" > README.md
git status

README.md shows as untracked — Git can see it but is not managing it.

git add README.md
git status

Now it is staged: in the index, ready for the next commit.

git commit -m "Add README"
git log --oneline

One commit. Inspect it with the model from chapter one:

git cat-file -p HEAD

You will see the tree hash, the author, and no parent line — this is the root commit.

Make a few commits

mkdir src
cat > src/app.js <<'EOF'
export function greet(name) {
  return `Hello, ${name}`;
}
EOF

git add src/app.js
git commit -m "Add greet function"

cat > src/app.js <<'EOF'
export function greet(name) {
  if (!name) throw new Error('name is required');
  return `Hello, ${name}`;
}
EOF

git add src/app.js
git commit -m "Validate the name argument"
git log --oneline --graph
* 3c4d5e6 Validate the name argument
* 1a2b3c4 Add greet function
* 9f8e7d6 Add README

Branch for a feature

git switch -c feature/farewell

switch -c creates the branch and moves onto it. (git checkout -b does the same; switch is the newer, single-purpose command from chapter one’s point about checkout being overloaded.)

cat >> src/app.js <<'EOF'

export function farewell(name) {
  return `Goodbye, ${name}`;
}
EOF

git add src/app.js
git commit -m "Add farewell function"

# a second, messier commit
echo "console.log('debug');" >> src/app.js
git commit -am "wip"

Simulate a colleague moving main

git switch main
echo "node_modules" > .gitignore
git add .gitignore
git commit -m "Add gitignore"

The branches have now diverged:

git log --oneline --graph --all
* 7a8b9c0 (main) Add gitignore
| * 5e6f7a8 (feature/farewell) wip
| * 4d5e6f7 Add farewell function
|/
* 3c4d5e6 Validate the name argument

Clean up your branch before sharing it

That wip commit and its debug line should not reach a reviewer.

git switch feature/farewell
git rebase -i main

Your editor opens:

pick 4d5e6f7 Add farewell function
pick 5e6f7a8 wip

Change the second line to edit, save and close. Git stops there:

# remove the debug line
sed -i '' '/console.log/d' src/app.js     # macOS; use sed -i on Linux
git add src/app.js
git commit --amend --no-edit
git rebase --continue

Now squash what remains into one clean commit:

git rebase -i main
# change the second `pick` to `fixup`, save
git log --oneline --graph --all
* 2b3c4d5 (feature/farewell) Add farewell function
* 7a8b9c0 (main) Add gitignore
* 3c4d5e6 Validate the name argument

Linear, and one commit that says what it does.

Push and open a pull request

git remote add origin git@github.com:you/git-practice.git
git push -u origin main
git push -u origin feature/farewell

-u sets the upstream so later git push and git pull need no arguments. (The push.autoSetupRemote setting from chapter two does this automatically.)

Open the PR on GitHub, get review, and merge it there.

Come back and sync

git switch main
git pull                          # fetch + rebase, per your config
git branch -d feature/farewell    # delete the local branch
git fetch --prune                 # clean up origin/* refs that no longer exist

The loop, condensed

git switch main && git pull
git switch -c feature/thing
# ... work, commit often ...
git rebase -i main            # clean up
git push -u origin feature/thing
# ... review, merge ...
git switch main && git pull
git branch -d feature/thing

That is the cycle. Next: the commands that make up the daily grind.