logo
HomeArticlesThoughtsProjects

My git and github notes

27 days ago

Reference:

  • YouTube Video

Phase 0: The Mental Model

Git moves your work through four places. Almost every command is about moving between two of them.

Place

What it is

Working tree (Disk)

The actual files you edit in VS Code

Staging area (Index)

What you've marked to go into the next commit

Local repository

Your committed history, stored in .git

Remote (origin)

The shared repository on GitHub

git add moves Disk → Staging. git commit moves Staging → Local. git push moves Local → Remote.

In addition, we assume that one git branch is owned and worked by only one person.


Phase 1: Initial Setup

Clone the repository:

git clone <repository_url>
cd <repository_name>

This downloads the full history into your local repository and writes the current files onto your disk.


Phase 2: Feature Development

1. Start from an up-to-date main

Never work directly on main. Always branch — but branch from the latest code, or you'll create unnecessary conflicts later.

git checkout main
git pull
git checkout -b <feature_branch_name>

Branch naming: use a prefix and a short description, e.g. feat/user-login, fix/null-pointer-on-save.

2. Make your changes

3. Review before you commit

Always look at your own changes first.

git status # which files changed
git diff # unstaged changes (Disk vs Staging)
git diff --staged # staged changes (Staging vs last commit)
git diff HEAD # everything not yet committed

Note: once you run git add, plain git diff shows nothing. That's expected — use git diff --staged to see what you staged.

4. Stage and commit

git add <file_name>
git commit -m "Short description of what changed"

Stage files by name rather than using git add ., so you don't accidentally commit debug code or unrelated files.

Commit messages: imperative mood, short subject line. Add password reset endpoint, not added stuff.

5. Push to GitHub

First push on a new branch:

git push -u origin <feature_branch_name>

The -u links your local branch to the remote one. After that, plain git push and git pull work with no arguments.


Phase 3: Syncing With main (Rebase)

If main moves forward while you're working, bring those changes into your branch before opening a PR.

1. Update your local main

git checkout main
git pull

2. Rebase your branch onto it

git checkout <feature_branch_name>
git rebase main

Git temporarily sets your commits aside, brings in the latest main, then replays your commits on top.

3. Handle conflicts

A conflict means Git found the same lines changed in two places and needs you to decide. This is normal, not an error.

  1. Run git status to see which files are conflicted.

  2. Open each file. You'll see markers like <<<<<<<, =======, >>>>>>>. Edit the file so it has the code you want, and delete all the markers.

  3. Run git add <file> for each file you fixed.

  4. Run git rebase --continue.

  5. Git may stop again with another conflict. Repeat steps 1–4 until it finishes.

If you get stuck or panic: run git rebase --abort. This puts everything back exactly as it was. Nothing is lost. Then ask for help.

Before continuing, check: no <<<<<<< markers left in any file, and the tests still pass. The merged result is a combination that has never been tested before.

4. Force push after rebasing

Rebasing rewrites your branch's history, so a normal push will be rejected.

git push --force-with-lease origin <feature_branch_name>

Use --force-with-lease, not -f. It does the same thing, but refuses if the remote has commits you don't have locally — for example, a suggestion a reviewer accepted through the GitHub UI.

Never force-push main, and never force-push a branch you don't own.


Phase 4: Merge and Cleanup

1. Open a Pull Request

On GitHub, open a PR from your feature branch into main and request a review.

Before requesting review, read through your own diff on GitHub. It catches leftover debug code and stray files.

2. Squash and merge

Once approved, merge using the "Squash and merge" option.

This combines every commit on your branch into one clean commit on main. Messy work-in-progress commits stay out of the main history.

The PR title becomes the commit message on main, so give the PR a clear, descriptive title.

3. Delete the remote branch

Click "Delete branch" on the merged PR page.

4. Clean up locally

git checkout main
git pull
git fetch --prune
git branch -D <feature_branch_name>

git fetch --prune removes references to branches that were deleted on GitHub.


Quick Reference

Start work

git checkout main && git pull
git checkout -b feat/my-feature
​

Save work

git status
git diff
git add <file>
git commit -m "message"
git push -u origin feat/my-feature
​

Sync with main

git checkout main && git pull
git checkout feat/my-feature
git rebase main
git push --force-with-lease origin feat/my-feature
​

After merge

git checkout main && git pull
git fetch --prune
git branch -D feat/my-feature


Rules

  1. Never commit directly to main.

  2. One person per branch.

  3. Review your own diff before requesting review.

  4. Never force-push main.

  5. Never commit secrets, .env files, keys, or credentials. If you do, rotate the credential immediately — assume it's compromised.

  6. When in doubt, git rebase --abort and ask.



Appendix A: Contributing Without Write Access (Fork Workflow)

When this applies: contributing to an open-source project, or any repository where you can't push branches directly.

You make your own copy of the repository on GitHub (a fork), push branches there, and open a PR from your fork into the original project. Maintainers never give you write access — they just merge your PR.

Every Git command is the same as the main workflow. The only difference is that you have two remotes instead of one.

Remote

Points at

Can you push?

origin

your fork

Yes

upstream

the original project

No — read only

A1. Setup (once per project)

  1. Click Fork on the project's GitHub page.

  2. Clone your fork — this sets origin automatically:

git clone <your_fork_url>
cd <repository_name>

  1. Add the original project as upstream:

git remote add upstream <original_repo_url>
git remote -v

Confirm the output shows origin = your fork and upstream = the project.

  1. Read the project's CONTRIBUTING.md. Their rules override this workflow — commit format, sign-off requirements, whether they want rebase or merge.

GitHub
├── original-owner/project ← upstream (read only)
└── you/project ← origin (your fork, you can push)
│
└── cloned to your machine

A2. Starting work

Your fork does not update itself. Always sync from upstream first:

git fetch upstream
git checkout main
git merge upstream/main
git checkout -b fix/some-bug

Branching off a stale main is the single most common cause of unnecessary conflicts in open-source PRs.

Never commit to your fork's main. Keep it a clean mirror of upstream so syncing is always a fast-forward.

A3. Develop, review, commit

Identical to Phase 2 of the main workflow — git status, git diff, git add, git commit.

Push to your fork:

git push -u origin fix/some-bug

A4. Syncing with the project (rebase)

There are three different "mains" in a fork setup, and only one is the real one:

Ref

What it is

Fresh?

main

your local branch

only as fresh as your last sync

origin/main

your fork's main on GitHub

stale — forks don't auto-update

upstream/main

the project's main

the real thing

So rebase onto upstream/main:

git fetch upstream
git rebase upstream/main

Handle conflicts exactly as in Phase 3, step 3. Then force push to your fork:

git push --force-with-lease origin fix/some-bug

--force-with-lease matters even more here: GitHub's "Allow edits by maintainers" is on by default, so a maintainer may have pushed a commit to your branch.

A5. Opening the PR

Push your branch, then open your fork's page on GitHub. A banner appears: "branch had recent pushes — Compare & pull request." Click it.

Check the four dropdowns at the top of the PR form:

base repository: original-owner/project base: main
head repository: you/project compare: fix/some-bug

Left = where it's going (the project). Right = where it's coming from (your fork).

The common mistake is leaving base repository set to your own fork. That opens a PR to yourself, and maintainers never see it.

A6. After it's merged

You don't run the merge — a maintainer does. Once it lands:

delete your branch on your fork (GitHub UI, or:)

git push origin --delete fix/some-bug
​
git checkout main
git fetch upstream
git merge upstream/main
git push origin main # keep your fork's main in sync
git branch -D fix/some-bug



Appendix B: Working on Two Branches at Once (git worktree)

When this applies: you need a second branch checked out while your current work stays exactly as it is — most often an urgent fix while your working tree is dirty.

This is optional. git stash solves the same problem with commands you already know. Worktrees are better when the switch is disruptive: uncommitted work you don't want to risk, different dependencies per branch, or a long-running process you don't want to interrupt.

B1. What a worktree is

A normal clone gives you one folder with one branch checked out:

~/code/
└── project/ ← working tree (your files)
├── .git/ ← the database: all history, all branches
├── src/
└── node_modules/

git worktree lets you have multiple folders sharing one .git:

cd ~/code/project
git worktree add ../project-hotfix -b fix/login-crash main
~/code/
├── project/ ← branch: feat/checkout (dirty, mid-work)
│ ├── .git/ ← THE database
│ └── src/
│
└── project-hotfix/ ← branch: fix/login-crash
├── .git ← a FILE, not a folder: points to ../project/.git
└── src/

One database, two checkouts.

B2. What's shared and what isn't

Shared across worktrees?

Commits, branches, tags, remotes

Yes — instantly, no fetch needed

Which branch you're on, staging area, files on disk

No — each worktree is independent

So if you commit in one worktree, git log in the other sees it immediately — but your files don't change. That's the point: your feature work stays frozen.

B3. Example: the hotfix interrupt

2:00pm — deep in a feature, 40 modified files, nothing committed.

2:15pm — prod breaks:

git fetch
git worktree add ../project-hotfix -b fix/login-crash origin/main
cd ../project-hotfix
cp ../project/.env . # untracked files do NOT come along
npm install
project/ feat/checkout ← untouched, still dirty
project-hotfix/ fix/login-crash ← clean, branched from origin/main

Fix it, then follow the normal workflow — git status, git diff, git add, git commit, git push -u origin fix/login-crash, open the PR.

After it's merged:

cd ../project
git worktree remove ../project-hotfix
git branch -D fix/login-crash

Your feature branch is exactly as you left it — editor tabs, dev server, everything.

To pull the fix into your feature branch afterwards, that's just the normal Phase 3 rebase:

git fetch
git rebase origin/main

B4. Other common uses

  • Reviewing a PR while running your own code. Two dev servers, two browser windows, side by side.

  • Branches with incompatible dependencies. Separate node_modules / venv / build cache per folder, so switching is cd instead of a reinstall.

  • Long test suites. Run them in one worktree, keep coding in the other.

B5. Commands

git worktree list # what exists now
git worktree add <path> -b <new> <base> # new branch
git worktree add <path> <existing-branch> # existing branch
git worktree add <path> <tag-or-sha> # detached, for inspection only
git worktree remove <path> # proper cleanup
git worktree prune # after an accidental rm -rf

B6. Rules

  1. Run the command from inside the repo, put the path outside it (../name). Nesting a worktree inside the repo makes it show up in git status and confuses build tools.

  2. One branch per worktree. Git refuses to check out the same branch twice — a branch is a single pointer, and two folders moving it would corrupt each other.

  3. Untracked files don't come along. .env, node_modules, venvs, local config all need setting up in the new folder.

  4. Change the port. Two dev servers can't both use 3000.

  5. Use git worktree remove, never rm -rf. Deleting the folder directly leaves stale metadata behind.

  6. Remove the worktree before deleting the branch. Git won't delete a branch that's checked out in a live worktree.

Contents

  • Phase 0: The Mental Model
  • Phase 1: Initial Setup
  • Phase 2: Feature Development
  • 1. Start from an up-to-date main
  • 2. Make your changes
  • 3. Review before you commit
  • 4. Stage and commit
  • 5. Push to GitHub
  • Phase 3: Syncing With main (Rebase)
  • 1. Update your local main
  • 2. Rebase your branch onto it
  • 3. Handle conflicts
  • 4. Force push after rebasing
  • Phase 4: Merge and Cleanup
  • 1. Open a Pull Request
  • 2. Squash and merge
  • 3. Delete the remote branch
  • 4. Clean up locally
  • Quick Reference
  • Start work
  • Save work
  • Sync with main
  • After merge
  • Rules
  • Appendix A: Contributing Without Write Access (Fork Workflow)
  • A1. Setup (once per project)
  • A2. Starting work
  • A3. Develop, review, commit
  • A4. Syncing with the project (rebase)
  • A5. Opening the PR
  • A6. After it's merged
  • delete your branch on your fork (GitHub UI, or:)
  • Appendix B: Working on Two Branches at Once (git worktree)
  • B1. What a worktree is
  • B2. What's shared and what isn't
  • B3. Example: the hotfix interrupt
  • B4. Other common uses
  • B5. Commands
  • B6. Rules