Skip to main content

Git & GitHub

Comprehensive notes for fresher developers joining the team.


What is Git?โ€‹

Git is a distributed version control system (VCS) that tracks changes in your source code over time. It lets you save snapshots of your project, collaborate with teammates, and roll back to any previous state without losing work.

GitHub is a cloud-based hosting platform for Git repositories. It adds collaboration features on top of Git โ€” pull requests, code reviews, issue tracking, and project management.

FeatureGitGitHub
TypeCommand-line tool (local)Web platform (cloud)
PurposeTrack code changesHost & collaborate on repositories
Works offlineYesNo
Installed onYour machineAccessed via browser / CLI
Core featureVersion controlPull requests, Issues, CI/CD

Analogy: Git is like track-changes in a document editor. GitHub is like Google Docs โ€” it stores your document online and lets others view and edit it.


Learning Flowโ€‹

PhaseTopics
Phase 1 โ€“ Setup & Core ConceptsInstallation, GitHub account, SSH Authentication, Git Architecture, Basic Workflow
Phase 2 โ€“ Essential Commandsinit, clone, add, commit, status, log, diff, blame
Phase 3 โ€“ Branching & MergingBranches, Merge, Conflict Resolution
Phase 4 โ€“ Remote Repositoriesremote, push, pull, fetch, Forks
Phase 5 โ€“ GitHub CollaborationPull Requests, Code Reviews, Issues, Branch Protection, GitHub Flow
Phase 6 โ€“ Best PracticesCommit conventions, .gitignore, UOW Branching, Stash, Common Mistakes
Advanced TopicsRebase, Interactive Rebase, Reset, Revert, Cherry-pick, Reflog, Tags & Releases, git bisect, git worktree, Hooks, Submodules, Signing commits, GitHub Actions

Phase 1 โ€“ Setup & Core Conceptsโ€‹

1. Installation & Initial Configurationโ€‹

Step 1 โ€” Create a GitHub account:

  1. Go to github.com and click Sign up
  2. Use your FortisureIT email address (yourname@fortisureit.com)
  3. Choose a username and complete email verification
  4. After signing in, go to Settings โ†’ Password and authentication and enable Two-Factor Authentication (2FA) โ€” required for team access

Step 2 โ€” Install Git:

Download from https://git-scm.com/downloads and follow the installer. Use the recommended settings on each step.

After installing, open Git Bash (search "Git Bash" in the Windows Start menu) โ€” this is your terminal for running Git commands.

Verify the installation:

git --version
# git version 2.43.0

Step 3 โ€” Configure your identity (do this once on every machine):

git config --global user.name "your full name"
git config --global user.email "yourname@fortisureit.com"

Use your GitHub username and your FortisureIT email โ€” these are attached to every commit you make.

Step 4 โ€” Set your default branch name to main:

git config --global init.defaultBranch main

Step 5 โ€” Check your configuration:

git config --global --list
# user.name=your-github-username
# user.email=yourname@fortisureit.com

Note: --global applies to all repositories on your machine. Omit it to configure only the current repository.

Optional โ€” Git GUI clients:

If you prefer a visual interface over the command line, these tools provide a graphical view of commits, branches, and diffs:

ToolDescription
GitHub DesktopSimple, official GitHub client
SourceTreeFeature-rich, shows branch graphs clearly
GitKrakenVisual and powerful, good for beginners

Tip: Learn the command line first โ€” GUI clients are a supplement, not a replacement.

Practice: Git Installation Guide ยท First-Time Git Setup


2. Authenticating with GitHub โ€” SSH Setupโ€‹

GitHub requires authentication every time you push, pull, or interact with a private or organisation repository. SSH keys are the recommended method โ€” you set them up once and never type a password again.

Why SSH over HTTPS? GitHub removed support for password-based HTTPS authentication in 2021. HTTPS now requires a Personal Access Token (PAT) โ€” a token you generate, copy, and manage yourself. SSH uses a cryptographic key pair stored on your machine: more secure, and completely seamless after the one-time setup. Always use SSH.

Step 1 โ€” Check for an existing SSH keyโ€‹

Before generating a new key, check whether one already exists on your machine:

ls -al ~/.ssh

Look for files named id_ed25519.pub, id_rsa.pub, or id_ecdsa.pub. If you see one of these, you already have a key pair โ€” skip to Step 3.

Step 2 โ€” Generate a new SSH keyโ€‹

ssh-keygen -t ed25519 -C "yourname@fortisureit.com"
  • -t ed25519 โ€” the key algorithm. Ed25519 is the modern standard, preferred over the older RSA.
  • -C "..." โ€” a comment/label so you can identify the key on GitHub.

You will be prompted:

Enter file in which to save the key (/c/Users/YourName/.ssh/id_ed25519):
โ†’ Press Enter to accept the default location

Enter passphrase (empty for no passphrase):
โ†’ Set a strong passphrase โ€” you will enter it once per session when the SSH agent loads

This creates two files in ~/.ssh/:

FilePurpose
id_ed25519Your private key โ€” never share this, never move it off your machine
id_ed25519.pubYour public key โ€” this is what you add to GitHub

Security rule: The private key (id_ed25519) must never leave your machine. Treat it like a password. Only the .pub file is shared externally.

Step 3 โ€” Add the key to the SSH agentโ€‹

The SSH agent holds your private key in memory so you don't have to enter your passphrase every time you push or pull.

# Start the SSH agent in the background
eval "$(ssh-agent -s)"
# Agent pid 1234

# Add your private key to the agent
ssh-add ~/.ssh/id_ed25519
# Enter passphrase for /c/Users/YourName/.ssh/id_ed25519: (enter it)
# Identity added: /c/Users/YourName/.ssh/id_ed25519 (yourname@fortisureit.com)

Make the agent start automatically on Windows (Git Bash):

Add these lines to your ~/.bashrc or ~/.bash_profile so the agent starts every time you open Git Bash:

# Auto-start SSH agent and load key on Git Bash launch
eval "$(ssh-agent -s)" > /dev/null
ssh-add ~/.ssh/id_ed25519 2>/dev/null

Step 4 โ€” Copy your public keyโ€‹

# Print the public key to the terminal โ€” select all and copy manually
cat ~/.ssh/id_ed25519.pub

# Windows shortcut โ€” copy directly to clipboard
clip < ~/.ssh/id_ed25519.pub

The output looks like this (one long line):

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAbc123...xyz yourname@fortisureit.com

Step 5 โ€” Add the public key to GitHubโ€‹

  1. Go to github.com โ†’ click your profile picture (top right) โ†’ Settings
  2. In the left sidebar: SSH and GPG keys
  3. Click "New SSH key"
  4. Title: give it a descriptive name โ€” e.g., Work Laptop, Home PC
  5. Key type: leave as Authentication Key
  6. Key: paste the public key you copied in Step 4
  7. Click "Add SSH key" โ€” confirm with your GitHub password or 2FA prompt if asked

Step 6 โ€” Test the connectionโ€‹

ssh -T git@github.com

Expected success output:

Hi your-username! You've successfully authenticated, but GitHub does not provide shell access.

If you see Permission denied (publickey), the key was not added correctly โ€” go back to Step 4, re-copy the key carefully (ensure no extra whitespace), and re-add it in GitHub.

Step 7 โ€” Clone repositories using SSHโ€‹

Now use SSH URLs instead of HTTPS when cloning:

# โœ… SSH โ€” no credential prompts ever
git clone git@github.com:Fortisure-IT/repository-name.git

# โŒ HTTPS โ€” requires a token on every push/pull without credential caching
git clone https://github.com/Fortisure-IT/repository-name.git

SSH URL format: git@github.com:organisation-or-username/repo-name.git

Step 8 โ€” Switch an existing clone from HTTPS to SSHโ€‹

If you already cloned a repository with HTTPS, switch it to SSH without re-cloning:

# Check the current remote URL
git remote -v
# origin https://github.com/Fortisure-IT/repo.git (fetch)
# origin https://github.com/Fortisure-IT/repo.git (push)

# Switch to SSH
git remote set-url origin git@github.com:Fortisure-IT/repo.git

# Verify the change
git remote -v
# origin git@github.com:Fortisure-IT/repo.git (fetch)
# origin git@github.com:Fortisure-IT/repo.git (push)

Alternative: HTTPS with a Personal Access Token (PAT)

If SSH cannot be set up in your environment, HTTPS is still an option โ€” but GitHub requires a Personal Access Token instead of your account password:

  1. Go to GitHub โ†’ Settings โ†’ Developer settings โ†’ Personal access tokens โ†’ Tokens (classic)
  2. Click "Generate new token (classic)"
  3. Give it a name, set an expiry, and tick the repo scope (full repository access)
  4. Copy the token โ€” you will not be shown it again

When Git prompts for credentials over HTTPS, use:

  • Username: your GitHub username
  • Password: paste your PAT (not your GitHub account password)

To avoid entering it every time, cache it:

# Windows โ€” stores credentials in Windows Credential Manager (persistent)
git config --global credential.helper manager

# Linux / Mac โ€” cache in memory for 1 hour
git config --global credential.helper "cache --timeout=3600"

Recommendation: Set up SSH. It is more secure, requires no token management or expiry, and eliminates credential prompts permanently.


3. Git Architecture โ€” The Three Areasโ€‹

Understanding Git's three-area model is the foundation for everything else.

Working Directory  โ†’  Staging Area (Index)  โ†’  Local Repository  โ†’  Remote Repository
(your files) (git add) (git commit) (git push)
AreaDescriptionCommand to move to next area
Working DirectoryFiles you are actively editing on diskgit add <file>
Staging AreaFiles queued up for the next commitgit commit -m "message"
Local RepositoryCommitted history stored in .git/ foldergit push
Remote RepositoryRepository hosted on GitHubโ€”

Key insight: You control exactly what goes into a commit. You can add only specific files to staging even if more files have changed.


4. Basic Git Workflowโ€‹

Every day as a developer, you will repeat this loop:

# 1. Check what changed
git status

# 2. Stage the changes you want to commit
git add filename.js
# or stage everything
git add .

# 3. Commit with a descriptive message
git commit -m "feat: add user login validation"

# 4. Push to the remote repository
git push origin main

Phase 2 โ€“ Essential Commandsโ€‹

5. Creating & Cloning Repositoriesโ€‹

Start a new repository from scratch:

mkdir my-project
cd my-project
git init # creates a .git/ folder โ€” this is now a Git repo

Clone an existing repository from GitHub:

# Clone using SSH (recommended)
git clone git@github.com:Fortisure-IT/repository-name.git

# After cloning, move into the project folder โ€” this step is easy to forget
cd repository-name

# Clone into a specific folder name
git clone git@github.com:Fortisure-IT/repository-name.git my-folder
cd my-folder

Tip: Use git clone when joining an existing project. Use git init only when starting a brand-new project from scratch.


6. git status โ€” Your Best Friendโ€‹

git status tells you everything about the current state of your working directory and staging area.

git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed: โ† โœ… staged (will go into next commit)
modified: src/App.js

Changes not staged for commit: โ† โš ๏ธ modified but not staged
modified: src/index.css

Untracked files: โ† โŒ Git doesn't know about these yet
src/NewComponent.js

Run git status constantly โ€” before staging, before committing, before pushing.


7. git add โ€” Staging Changesโ€‹

# Stage a single file
git add src/App.js

# Stage multiple files
git add src/App.js src/utils.js

# Stage all changes in the current directory and below
git add .

# Stage parts of a file interactively (advanced โ€” for careful commits)
git add -p filename.js
CommandWhat it stages
git add .All new and modified files in current directory
git add -AAll new, modified, and deleted files across entire repo
git add src/All files inside the src/ folder
git add *.jsAll .js files in current directory

Best practice: Prefer git add . or explicit file names. Avoid git add -A unless you know exactly what has changed.


8. git commit โ€” Saving a Snapshotโ€‹

# Commit with a message inline
git commit -m "feat: add login button to header"

# Open editor to write a longer commit message
git commit

# Stage all tracked modified files and commit in one step
git commit -am "fix: correct typo in welcome message"

Important: A commit is permanent in your history. Write clear, descriptive messages โ€” your teammates (and future you) will thank you.


9. git log โ€” Viewing Historyโ€‹

# Full log
git log

# Compact one-line view
git log --oneline

# Visual branch graph
git log --oneline --graph --all

# Last N commits
git log -5

# Commits by a specific author
git log --author="John"

Example output of git log --oneline:

a3f92c1 feat: add user profile page          โ† most recent
8b21d4e fix: resolve null pointer in login
c10a993 chore: update dependencies
7ffe201 feat: initial project setup

Each entry shows: short hash + commit message.


10. git diff โ€” Seeing What Changedโ€‹

# Show unstaged changes (working directory vs staging area)
git diff

# Show staged changes (staging area vs last commit)
git diff --staged

# Compare two commits
git diff abc1234 def5678

# Compare two branches
git diff main feature/login

Tip: Use git diff --staged before every commit to review exactly what you are about to save.


11. git blame โ€” Finding Who Changed Whatโ€‹

git blame shows the commit hash, author, and timestamp for every line in a file. It answers the question: who last changed this line, and when?

# Show blame for an entire file
git blame src/App.js

Example output:

a3f92c1 (John Smith  2024-01-15 09:23 +0000  40) function handleLogin() {
8b21d4e (Sarah Jones 2024-02-03 14:10 +0000 41) const user = auth.getUser();
a3f92c1 (John Smith 2024-01-15 09:23 +0000 42) if (!user) return null;
8b21d4e (Sarah Jones 2024-02-03 14:10 +0000 43) return redirect("/dashboard");
a3f92c1 (John Smith 2024-01-15 09:23 +0000 44) }

Each line shows: commit hash ยท author ยท date ยท line number ยท code

# Blame only a specific range of lines (lines 40 to 60)
git blame -L 40,60 src/App.js

# Ignore whitespace-only changes (useful when code was reformatted)
git blame -w src/App.js

# Show the full commit hash instead of the abbreviated one
git blame --abbrev=0 src/App.js

Workflow: blame โ†’ show โ†’ understand

Once you identify the commit responsible for a line, inspect the full context of that change:

# 1. Find who last changed line 42
git blame -L 42,42 src/App.js
# a3f92c1 (John Smith 2024-01-15 09:23...)

# 2. See the full commit โ€” message, diff, and all files that changed
git show a3f92c1

When to use git blame:

  • Debugging: "who introduced this logic and why?"
  • Learning: "who should I ask about how this part of the codebase works?"
  • Code review: "was this a recent change or has it always been this way?"

Tip: VS Code with the GitLens extension shows blame information inline โ€” author and timestamp appear next to each line without opening a terminal.

Note: git blame shows who made the last edit to a line, not the original author. If a line was only reformatted by a code-style tool, that tool's commit will appear. Use git blame -w to ignore whitespace changes and surface the meaningful author.


Phase 3 โ€“ Branching & Mergingโ€‹

12. Understanding Branchesโ€‹

A branch is an independent line of development. The default branch is called main. You create a new branch to work on a feature or bug fix without affecting main.

main:    A --- B --- C
\
feature/login: D --- E
# List all local branches
git branch

# List all branches including remote
git branch -a

# Create a new branch
git branch feature/login

# Switch to a branch
git checkout feature/login

# Create and switch in one command (preferred)
git checkout -b feature/login

# Modern syntax (Git 2.23+)
git switch -c feature/login

Best practice: Always branch off from main (or the main development branch). Never commit directly to main.


13. Merging Branchesโ€‹

Once your feature is ready, you merge it back into main.

# Step 1: Switch to the target branch
git checkout main

# Step 2: Make sure it is up to date
git pull origin main

# Step 3: Merge your feature branch
git merge feature/login

Types of merge:

TypeWhen it happensResult
Fast-forwardmain has no new commits since you branchedLinear history โ€” no merge commit
3-way mergeBoth branches have divergedCreates a merge commit
# Force a merge commit even if fast-forward is possible (shows history clearly)
git merge --no-ff feature/login

14. Resolving Merge Conflictsโ€‹

A conflict occurs when the same lines were changed on both branches. Git cannot decide which version to keep โ€” you must resolve it manually.

# After running git merge, if there's a conflict:
git status # shows conflicted files marked as "both modified"

A conflict looks like this inside the file:

<<<<<<< HEAD
const greeting = "Hello"; โ† your current branch version
=======
const greeting = "Hi there"; โ† the incoming branch version
>>>>>>> feature/login

Resolution steps:

  1. Open the conflicted file
  2. Decide which version to keep (or combine them)
  3. Delete the conflict markers (<<<<<<<, =======, >>>>>>>)
  4. Save the file
  5. Stage and commit the resolution
git add src/App.js
git commit -m "merge: resolve conflict in greeting message"

Tip: VS Code and most IDEs highlight conflicts and provide a visual "Accept Current / Accept Incoming / Accept Both" interface โ€” use it.


15. Deleting Branchesโ€‹

Once a branch is merged, delete it to keep the repository clean.

# Delete a local branch (safe โ€” only if fully merged)
git branch -d feature/login

# Force delete (even if not merged)
git branch -D feature/login

# Delete a remote branch
git push origin --delete feature/login

Phase 4 โ€“ Remote Repositoriesโ€‹

16. git remote โ€” Linking to GitHubโ€‹

A remote is a version of your repository stored on another server (like GitHub).

# View remotes
git remote -v

# Add a remote named "origin"
git remote add origin git@github.com:Fortisure-IT/repo.git

# Change the URL of an existing remote
git remote set-url origin git@github.com:Fortisure-IT/new-repo.git

# Remove a remote
git remote remove origin

When you git clone, Git automatically creates a remote called origin pointing to the cloned URL.


17. git push โ€” Uploading to GitHubโ€‹

# Push current branch to remote
git push origin main

# Push a new local branch to remote for the first time
git push -u origin feature/login
# The -u flag sets the upstream so future pushes just need: git push

# Push all local branches
git push --all origin

Important: Never force-push to shared branches like main unless explicitly instructed by your team lead.

# โŒ Dangerous โ€” overwrites remote history
git push --force origin main

# โœ… Safer alternative (fails if someone else has pushed)
git push --force-with-lease origin feature/my-branch

18. git pull & git fetch โ€” Getting Updatesโ€‹

# fetch + merge in one step (commonly used)
git pull origin main

# Download remote changes without merging
git fetch origin

# After fetching, see what changed
git log HEAD..origin/main --oneline

# Then merge manually
git merge origin/main
CommandWhat it does
git fetchDownloads changes but does NOT update your working files
git pullDownloads changes AND merges them into your current branch

Best practice: Use git fetch then review changes before merging, especially on critical branches.


19. Forking a Repositoryโ€‹

A fork is your own personal copy of someone else's repository on GitHub. Used when contributing to open-source or to repositories where you don't have write access.

Team note: Within FortisureIT, you will not use forks for day-to-day work. You push branches directly to the team repository and open PRs there. Forking is an open-source pattern โ€” learn it so you understand how public contributions work, but it is not part of your regular internal workflow.

Fork workflow:

# 1. Click "Fork" on GitHub โ€” creates a copy under your account
# 2. Clone YOUR fork (using SSH)
git clone git@github.com:your-username/repo.git

# 3. Add the original repo as upstream
git remote add upstream git@github.com:original-owner/repo.git

# 4. Keep your fork in sync with the original
git fetch upstream
git merge upstream/main

# 5. Push to your fork, then open a Pull Request to the original

Phase 5 โ€“ GitHub Collaborationโ€‹

20. Pull Requests (PRs)โ€‹

A Pull Request is a proposal to merge your branch into another branch on GitHub. It is the primary collaboration mechanism โ€” teammates review your code before it is merged.

Creating a PR:

  1. Push your branch to GitHub
  2. Go to the repository on GitHub
  3. Click "Compare & pull request"
  4. Write a clear title and description explaining:
    • What you changed
    • Why you changed it
    • How to test it
  5. Assign reviewers
  6. Click "Create pull request"

Team PR title conventions:

PR typeTitle formatExample
Feature / fix branch โ†’ UOW branch[#issue-number] Short description[#1234] Add user login flow
UOW branch โ†’ mainUOW: Unit of Work NameUOW: Allstar Teambuilding

PR checklist before requesting review:

  • All tests pass
  • No merge conflicts with target branch
  • Code is self-reviewed (read your own diff)
  • PR title follows the team convention above
  • Meaningful PR description is filled in

Best practice: Keep PRs small and focused. One PR = one feature or one bug fix. Large PRs are harder to review and more likely to have hidden bugs.


21. Code Reviewsโ€‹

When you are asked to review a PR:

  • Check for logic errors, not just style
  • Ask questions, don't give commands: "What happens if this is null?" instead of "Fix this"
  • Approve when the code is good enough to ship, not perfect
  • Use GitHub's "Request changes" if something must be fixed before merging

Responding to review feedback:

  • Address all comments โ€” either fix the code or explain why you disagree
  • Reply "Done" or "Fixed in [commit]" to resolved comments
  • Don't push back on style comments defensively โ€” your team's conventions matter

22. Issues & GitHub Flowโ€‹

Issues are GitHub's task/bug tracker. Use them to report bugs, request features, or discuss ideas.

GitHub Flow โ€” the simple branching strategy used by most teams:

1. Create a branch from main
git checkout -b feature/add-search-bar

2. Make commits on your branch
git commit -m "feat: add search bar component"

3. Open a Pull Request
โ†’ describe your changes

4. Discuss and review with teammates

5. Merge into main once approved

6. Delete the branch
git branch -d feature/add-search-bar

Key rule: main is always deployable. Never commit broken code to main.

Practice: GitHub Flow Guide ยท About Pull Requests


23. Branch Protection Rulesโ€‹

Branch protection prevents accidental direct pushes to important branches and enforces peer review.

Recommended protections for the team (set in GitHub โ†’ Settings โ†’ Branches):

BranchProtections to enable
mainRequire PR before merging, require approvals (min 1), block direct pushes
uow-id_uow-nameRequire PR before merging, optional peer review

What these rules enforce:

  • No one can push directly to main โ€” all changes must go through a PR
  • A PR must be approved by at least one reviewer before it can be merged
  • Optionally: require status checks (CI tests) to pass before merging

Why it matters: Branch protection is your last line of defence against broken code reaching production. Even senior developers benefit from it โ€” it removes the option to bypass the process accidentally.


Phase 6 โ€“ Best Practicesโ€‹

24. Writing Good Commit Messagesโ€‹

A great commit message communicates why a change was made, not just what.

Team commit format (used for day-to-day commits on feature branches):

[#issue-number] Short description of the change

Examples:

git commit -m "[#1234] Fix user login redirect"
git commit -m "[#1234] Implement user authentication flow"
git commit -m "[#87] Resolve null pointer in dashboard query"

This links every commit directly to the GitHub issue it belongs to โ€” making history easy to trace.


Conventional Commits format (used for semantic versioning and changelogs):

<type>(<scope>): <short description>

[optional body โ€” explains what and why]

[optional footer โ€” references issues, breaking changes]

Types:

TypeWhen to use
featA new feature
fixA bug fix
choreMaintenance tasks (updating deps, configs)
docsDocumentation changes only
styleFormatting, missing semicolons โ€” no logic change
refactorCode restructuring โ€” no feature or bug fix
testAdding or updating tests

Examples:

# โœ… Good
git commit -m "feat(auth): add remember-me checkbox to login form"
git commit -m "fix(api): handle null response from user endpoint"
git commit -m "docs: add setup instructions to README"

# โŒ Bad
git commit -m "fix"
git commit -m "updated stuff"
git commit -m "WIP"
git commit -m "asdfgh"

Rules:

  • Subject line โ‰ค 72 characters
  • Use present tense: "add feature" not "added feature"
  • No full stop at the end of the subject line
  • Reference issue numbers when applicable: fix(login): correct redirect โ€” closes #42

25. .gitignore โ€” What Not to Trackโ€‹

A .gitignore file tells Git which files to ignore entirely. Never commit:

  • node_modules/ โ€” install from package.json instead
  • .env files โ€” contain secrets and credentials
  • Build outputs (dist/, build/, .next/)
  • OS-generated files (.DS_Store, Thumbs.db)
  • IDE config (.vscode/, .idea/)

Example .gitignore for a JavaScript project:

# Dependencies
node_modules/

# Environment variables
.env
.env.local
.env.production

# Build output
dist/
build/
.next/

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/

# Logs
*.log
npm-debug.log*

Important: If you accidentally committed a file that should be ignored, removing it from .gitignore is not enough โ€” you must also stop tracking it:

git rm --cached .env
git commit -m "chore: remove .env from tracking"

Practice: gitignore.io โ€” generate .gitignore for any stack


26. Branching Strategy & Naming Conventionsโ€‹

The team uses a UOW (Unit of Work) branching model with three tiers:

main
โ””โ”€โ”€ uow-id_uow-name โ† all tickets for a feature set merge here
โ””โ”€โ”€ issue-number-description โ† one branch per GitHub issue

Tier 1 โ€” Main branch:

BranchPurpose
mainAlways stable and production-ready. No direct pushes.

Tier 2 โ€” UOW Release branches:

  • Naming: {uow-id}_{uow-name} โ€” an underscore separates the ID from the name
  • The UOW ID follows the format YY.NN or YY.NN-a (year ยท sequence ยท optional variant)
  • Examples: 26.01-a_smartpo-phase-ii, 26.02_director-dashboard
  • Groups all related feature tickets for a specific unit of work
  • Merged into main once all tickets in the UOW are complete and reviewed

Note: The /release suffix was dropped โ€” it caused conflicts with how Git interprets / in ref names. The UOW branch is now simply the ID and name joined by an underscore.

Tier 3 โ€” Feature / Issue branches:

  • Naming: issue-number-short-descriptive-text โ€” no # prefix in the branch name
  • Examples: 1234-fix-user-login, 87-add-search-bar
  • Created for each individual GitHub issue / ticket
  • Always use lowercase with hyphens
  • Merged into the corresponding UOW branch (not directly into main)

Note: The # symbol is only used when referencing a ticket number inside a commit message (e.g. [#1234] Fix login redirect), not in the branch name itself.

Complete example workflow:

# 1. Create a feature branch from the UOW branch
git checkout 26.01-a_smartpo-phase-ii
git checkout -b 1234-add-new-feature

# 2. Develop and commit โ€” use # only in the commit message, not the branch name
git commit -m "[#1234] Implement user authentication flow"

# 3. Push and open a PR โ†’ target: 26.01-a_smartpo-phase-ii
git push origin 1234-add-new-feature

# 4. After PR is merged, the UOW branch accumulates all tickets

# 5. When all UOW tickets are done, open a PR: 26.01-a_smartpo-phase-ii โ†’ main
# PR title: "UOW: Smartpo Phase II"
# โœ… Good branch names
1234-fix-user-login
87-add-search-bar
26.01-a_smartpo-phase-ii

# โŒ Bad branch names
#1234-fix-user-login โ† don't use # in branch names
my-branch
test123
john-working-on-stuff
HOTFIX

Branch hygiene:

# Regularly sync your branch with the UOW branch to avoid large conflicts later
git fetch origin
git rebase origin/26.01-a_smartpo-phase-ii

# Or merge if rebasing is not appropriate
git merge origin/26.01-a_smartpo-phase-ii

Rule: Create a new branch for every GitHub issue โ€” no matter how small. Never commit directly to main or the UOW release branch.


27. Common Mistakes & How to Fix Themโ€‹

Mistake 1: Committed to the wrong branch

# Move the last commit to a new branch instead
git branch feature/correct-branch # create branch pointing at current commit
git reset HEAD~1 --soft # undo commit on current branch (keep changes staged)
git stash # temporarily store staged changes
git checkout feature/correct-branch # switch to correct branch
git stash pop # restore changes
git commit -m "feat: your message" # commit on the right branch

Mistake 2: Committed a file that should be ignored

git rm --cached path/to/file.env
echo "path/to/file.env" >> .gitignore
git commit -m "chore: untrack sensitive file"

Mistake 3: Wrote a bad commit message

# Only safe to do if you have NOT pushed yet
git commit --amend -m "fix: correct message here"

Mistake 4: Want to discard uncommitted changes to a file

# โš ๏ธ This permanently discards unsaved changes
git checkout -- src/App.js
# Modern syntax
git restore src/App.js

Mistake 5: Accidentally staged a file

git restore --staged src/App.js   # unstage but keep the changes

Mistake 6: Pushed a commit that needs to be undone

# โŒ Don't reset and force-push on a shared branch โ€” this rewrites history
# and disrupts everyone who has already pulled your commits
git reset --hard HEAD~1
git push --force

# โœ… Use git revert instead โ€” adds a new "undo" commit, safe on shared branches
git revert HEAD
git push

See Advanced Topic A4 for the full git revert reference.


28. Quick Reference โ€” Most Used Commandsโ€‹

TaskCommand
Check statusgit status
Stage filegit add filename
Stage allgit add .
Commitgit commit -m "message"
View historygit log --oneline
See who changed a linegit blame filename
Create & switch branchgit checkout -b branch-name
Switch branchgit checkout branch-name
Merge branchgit merge branch-name
Push branchgit push origin branch-name
Pull latestgit pull origin main
Clone repogit clone git@github.com:org/repo.git
View remotesgit remote -v
Delete local branchgit branch -d branch-name
Unstage filegit restore --staged filename
Discard changesgit restore filename
Stash changesgit stash
Apply stashgit stash pop

29. Git Stash โ€” Temporarily Set Aside Changesโ€‹

Stash is useful when you need to switch branches but aren't ready to commit your work.

# Save your current uncommitted work
git stash

# List all stashes
git stash list
# stash@{0}: WIP on feature/login: a3f9 feat: start login form
# stash@{1}: WIP on main: 8b21 fix: header typo

# Apply the most recent stash and remove it from the stash list
git stash pop

# Apply a specific stash (without removing it)
git stash apply stash@{1}

# Drop a specific stash
git stash drop stash@{0}

# Stash with a description
git stash push -m "half-finished login validation"

Practice: Git Stash Documentation ยท Atlassian Git Stash Tutorial


Summary โ€” Key Principles for Beginnersโ€‹

  1. Commit early, commit often โ€” small commits are easier to review and revert than large ones
  2. Always branch โ€” never work directly on main
  3. Write meaningful commit messages โ€” your future self will read them
  4. Pull before you push โ€” sync with the remote to avoid conflicts
  5. Never commit secrets โ€” use .env files and .gitignore
  6. Review your diff before committing โ€” git diff --staged
  7. Keep PRs small and focused โ€” one PR per feature or bug fix
  8. Ask before force-pushing โ€” it can overwrite teammates' work
  9. Set up SSH โ€” authenticate with a key pair, not a password or token
  10. Use git revert on shared branches โ€” never git reset --hard on commits others have pulled
  11. git reflog is your safety net โ€” almost nothing in Git is truly lost within 90 days

Practice: Learn Git Branching (interactive) ยท Pro Git Book (free) ยท GitHub Skills


Advanced Topicsโ€‹

These topics go beyond the day-to-day workflow. Master the core phases above before coming back here. These are not required for your first few weeks โ€” they are here for when you are ready to go deeper.


A1. git rebase โ€” Replaying Commitsโ€‹

Rebase moves or replays your commits on top of another branch, producing a clean, linear history instead of a merge commit.

What happens visually:

Before rebase:

main: A --- B --- C
\
feature/login: D --- E

After: git rebase main (run on feature/login)

main: A --- B --- C
\
feature/login: D' --- E' โ† commits replayed on top of C

The commits D and E are rewritten as D' and E' โ€” same changes, new hashes.

# Switch to your feature branch
git checkout feature/login

# Rebase onto main (replay your commits on top of the latest main)
git rebase main

# Rebase onto the remote main (most common daily use)
git rebase origin/main

Handling conflicts during rebase:

Unlike a merge which creates one conflict resolution commit, rebase pauses at each conflicting commit.

# When a conflict occurs, Git pauses and shows:
# CONFLICT (content): Merge conflict in src/App.js
# error: could not apply a3f92c1... feat: add login button

# Step 1: Fix the conflict in the file
# Step 2: Stage the resolved file
git add src/App.js

# Step 3: Continue to the next commit
git rebase --continue

# If you want to skip the conflicting commit entirely (rarely needed)
git rebase --skip

# If you want to cancel the entire rebase and go back to before
git rebase --abort

Rule: Never rebase a branch that has been pushed and shared with others. Rebase rewrites commit hashes โ€” if someone else has your old commits, their history will diverge from yours.

# โœ… Safe โ€” rebasing a local branch you haven't pushed
git rebase main

# โœ… Safe โ€” rebasing a branch only you are using
git rebase origin/main

# โŒ Dangerous โ€” rebasing after others have pulled your branch
git rebase main # then git push --force (disrupts teammates)

Practice: Atlassian Rebase Guide ยท Learn Git Branching


A2. Interactive Rebase โ€” Rewriting Historyโ€‹

Interactive rebase (git rebase -i) lets you edit, reorder, squash, or drop commits before they are finalised. It is a powerful tool for cleaning up your commit history before opening a PR.

# Interactively rebase the last 3 commits
git rebase -i HEAD~3

# Interactively rebase all commits since branching off main
git rebase -i origin/main

This opens an editor showing your commits oldest-first:

pick a3f92c1 feat: add login form
pick 8b21d4e fix: typo in label
pick c10a993 fix: another typo

# Commands:
# pick = keep the commit as-is
# reword = keep commit but edit its message
# edit = keep commit but pause to amend it
# squash = merge into previous commit (keeps both messages)
# fixup = merge into previous commit (discards this commit's message)
# drop = remove this commit entirely

Common interactive rebase workflows:

Squash multiple commits into one (clean up WIP commits before PR):

# Before squash โ€” messy history:
# feat: start login form
# fix: forgot semicolon
# fix: another typo
# fix: actually fixed it this time

# Change "pick" to "fixup" on all the fix commits:
pick a3f92c1 feat: start login form
fixup 8b21d4e fix: forgot semicolon
fixup c10a993 fix: another typo
fixup 7ffe201 fix: actually fixed it this time

# After squash โ€” clean history:
# feat: start login form

Reword a commit message:

pick a3f92c1 feat: add logn form    โ† typo in message
# Change to:
reword a3f92c1 feat: add logn form
# Git will pause and open the editor for you to fix the message

Drop a commit (remove it from history entirely):

# Change "pick" to "drop" โ€” that commit's changes disappear
drop 8b21d4e fix: debug console.log accidentally committed

Best practice: Squash or fixup "work-in-progress" commits before opening a PR so reviewers see a clean, logical history. Do this only on branches not yet shared with others.

Practice: Git Interactive Rebase ยท Atlassian Squashing Commits


A3. git reset โ€” Undoing Commitsโ€‹

git reset moves the HEAD pointer (and optionally the working directory) backward to a previous commit. There are three modes โ€” understand them before using.

Commit A  โ†’  Commit B  โ†’  Commit C   โ† HEAD (currently here)

After git reset HEAD~1 (go back 1 commit), HEAD points to B. What happens to C's changes depends on the mode:

ModeCommandHEAD movesStaging areaWorking directory
Softgit reset --soft HEAD~1โœ… Back 1Changes from C stay stagedUnchanged
Mixed (default)git reset HEAD~1โœ… Back 1Changes from C unstagedUnchanged
Hardgit reset --hard HEAD~1โœ… Back 1ClearedโŒ Changes from C discarded
# --soft: undo last commit, keep changes staged (ready to re-commit differently)
git reset --soft HEAD~1

# --mixed (default): undo last commit, keep changes in working directory but unstaged
git reset HEAD~1
git reset --mixed HEAD~1 # same thing

# --hard: undo last commit and discard all changes permanently
git reset --hard HEAD~1

# Reset to a specific commit hash
git reset --hard a3f92c1

# Undo the last 3 commits (soft โ€” keeps changes)
git reset --soft HEAD~3

Warning: git reset --hard permanently discards changes in your working directory. There is no undo unless you use git reflog (see A8).

When to use each mode:

ScenarioMode to use
Undo a commit but want to recommit with a better message--soft
Undo a commit and re-stage selectively--mixed
Completely throw away bad commits and their changes--hard
Unstage a file without changing itgit reset HEAD filename (same as git restore --staged)

Rule: Never use git reset on commits that have already been pushed to a shared branch. Use git revert instead (see A4).

Practice: Atlassian Git Reset Guide


A4. git revert โ€” Safely Undoing Pushed Commitsโ€‹

git revert creates a new commit that undoes the changes of a previous commit. It does not rewrite history โ€” it adds to it. This makes it safe to use on shared branches.

# Revert the most recent commit
git revert HEAD

# Revert a specific commit by hash
git revert a3f92c1

# Revert without immediately opening the commit message editor
git revert --no-edit HEAD

# Revert multiple commits (creates one revert commit per commit)
git revert HEAD~3..HEAD

# Stage the revert but don't commit yet (useful to edit the message)
git revert --no-commit HEAD

What revert looks like in history:

A --- B --- C --- D (revert of C)

Commit D contains the exact opposite of what C did โ€” effectively undoing C while preserving the full history.

reset vs revert โ€” which to use:

SituationUse
Commit not yet pushedgit reset (rewrite local history cleanly)
Commit already pushed to shared branchgit revert (safe, adds undo commit)
Need to undo changes on mainAlways git revert
# โŒ Dangerous on a shared branch โ€” rewrites history others already have
git reset --hard HEAD~1
git push --force

# โœ… Safe on a shared branch โ€” creates a new undo commit
git revert HEAD
git push

Practice: Git Revert Documentation ยท Atlassian Undoing Changes


A5. Merge Conflicts โ€” Deep Diveโ€‹

Basic conflict resolution was covered in topic 14. This section covers the full picture โ€” types of conflicts, strategies, and how to approach complex situations.

Types of conflicts:

TypeCauseExample
Content conflictSame lines edited differently on both branchesBoth branches changed const greeting
Rename / move conflictFile renamed on one branch, edited on anotherBranch A renames utils.js โ†’ helpers.js; Branch B edits utils.js
Delete / modify conflictOne branch deletes a file, the other modifies itBranch A deletes config.js; Branch B adds a function to it
Add / add conflictBoth branches add a new file with the same name but different contentRare, but occurs in parallel feature work

Anatomy of a conflict marker โ€” full example:

<<<<<<< HEAD
// Current branch (the branch you ran git merge FROM)
function getUser(id) {
return users.find(u => u.id === id);
}
||||||| merged common ancestor โ† shows the ORIGINAL state before either change (only visible with diff3 style)
function getUser(id) {
return users[id];
}
=======
// Incoming branch (the branch you are merging IN)
function getUser(id) {
const user = users.find(u => u.id === id);
if (!user) throw new Error(`User ${id} not found`);
return user;
}
>>>>>>> feature/user-validation

Enable the 3-way diff style to see the common ancestor (highly recommended):

git config --global merge.conflictstyle diff3

This adds the ||||||| merged common ancestor section which shows what the code looked like before either branch touched it โ€” makes it much easier to understand who changed what and why.

Aborting a merge:

# If mid-conflict you decide to back out of the merge entirely
git merge --abort

# Verify you are back to the pre-merge state
git status

Checking which files have conflicts:

git status
# Both modified: src/App.js
# Both modified: src/utils.js

# List only conflicted files
git diff --name-only --diff-filter=U

Accepting one side entirely (skipping manual editing):

# Accept your version (HEAD) for a specific file โ€” discard incoming
git checkout --ours src/App.js
git add src/App.js

# Accept their version (incoming branch) for a specific file โ€” discard yours
git checkout --theirs src/App.js
git add src/App.js

Caution: --ours and --theirs throw away one side completely. Only use this when you are certain the other side's changes are not needed.

After resolving all conflicts:

# Stage every resolved file
git add .

# Complete the merge
git commit
# Git pre-fills a merge commit message โ€” you can keep or edit it

Practice: Git Merge Conflicts ยท Pro Git โ€” Basic Merge Conflicts


A6. Merge & Rebase Conflict Toolsโ€‹

VS Code built-in conflict editor:

VS Code highlights conflict regions and shows action buttons inline:

Accept Current Change | Accept Incoming Change | Accept Both Changes | Compare Changes
<<<<<<< HEAD
your version
=======
their version
>>>>>>> feature/login

Use "Compare Changes" to open a side-by-side diff view for complex conflicts.

Configure VS Code as the merge tool:

git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# Launch the merge tool for all conflicted files
git mergetool

Other popular merge tools:

ToolCommand to configure
VS Codegit config --global merge.tool vscode
IntelliJ / WebStormBuilt-in via IDE Git menu
vimdiffgit config --global merge.tool vimdiff
Beyond Comparegit config --global merge.tool bc

A7. git cherry-pick โ€” Applying Specific Commitsโ€‹

Cherry-pick copies one or more specific commits from any branch and applies them to your current branch. Useful when you need a bug fix from another branch without merging the entire branch.

# Apply a single commit to the current branch
git cherry-pick a3f92c1

# Apply multiple commits
git cherry-pick a3f92c1 8b21d4e c10a993

# Apply a range of commits (exclusive of the start hash)
git cherry-pick a3f92c1..c10a993

# Cherry-pick without immediately committing (stage the changes only)
git cherry-pick --no-commit a3f92c1

# If a conflict occurs during cherry-pick
git cherry-pick --continue # after resolving conflicts
git cherry-pick --abort # to cancel entirely

Practical example:

# A critical fix was merged to main but you need it on your feature branch now
git checkout feature/dashboard

# Find the fix commit hash on main
git log main --oneline
# a3f92c1 fix(api): handle null response from user endpoint โ† you need this

# Apply just that commit to your branch
git cherry-pick a3f92c1

# Now your branch has the fix without merging all of main

Note: Cherry-pick creates a new commit with the same changes but a different hash. The original commit remains on its branch.

Practice: Git Cherry-pick Documentation ยท Atlassian Cherry-pick


A8. git reflog โ€” The Safety Netโ€‹

git reflog records every movement of HEAD โ€” including commits, resets, rebases, and checkouts. It is your recovery tool when you think you've lost work.

# View the reflog (chronological list of HEAD positions)
git reflog

# Output:
# a3f92c1 HEAD@{0}: commit: feat: add dashboard
# 8b21d4e HEAD@{1}: reset: moving to HEAD~1
# c10a993 HEAD@{2}: commit: fix: login redirect
# 7ffe201 HEAD@{3}: checkout: moving from main to feature/dashboard

Each entry has a HEAD@{N} reference you can use to restore any state.

Recovering commits after an accidental git reset --hard:

# You ran git reset --hard and lost commits โ€” find them in reflog
git reflog

# Find the commit hash just before the reset (e.g. c10a993 at HEAD@{2})
# Create a new branch at that point to recover the work
git checkout -b recovery/lost-work c10a993

# Or reset back to it on your current branch
git reset --hard c10a993

Recovering a deleted branch:

# You deleted a branch and realize you needed it
git reflog

# Find the last commit on that branch (e.g. 8b21d4e)
git checkout -b feature/recovered-branch 8b21d4e

Important: Reflog is local only โ€” it is not pushed to GitHub. It also expires (default 90 days for reachable commits, 30 days for unreachable ones).

Practice: Git Reflog Documentation ยท Atlassian Git Reflog


A9. Merge vs Rebase โ€” When to Use Whichโ€‹

This is one of the most debated topics in Git. Both achieve the same end goal โ€” integrating changes from one branch into another โ€” but with different history shapes.

Merge:

main:          A --- B --- C -------M   โ† merge commit
\ /
feature/login: D --- E

Rebase:

main:          A --- B --- C
\
feature/login: D' --- E' โ† replayed on top of C, linear history
MergeRebase
History shapeNon-linear (has merge commits)Linear (clean, no merge commits)
Preserves original branch historyโœ… YesโŒ No (rewrites commit hashes)
Safe on shared/public branchesโœ… YesโŒ No
Easier to undoโœ… Just revert the merge commitHarder
Cleaner git logโŒ More noiseโœ… Easier to read
Conflict frequencyOne set of conflicts per mergeOne set of conflicts per replayed commit

Team guidelines โ€” when to use each:

SituationRecommended approach
Syncing your feature branch with the latest maingit rebase origin/main (clean history on your branch)
Merging a finished feature branch into main via PRMerge (preserves the fact that a feature existed as a branch)
Cleaning up commits before opening a PRgit rebase -i (interactive squash/fixup)
Hotfix needs to go to multiple branchesgit cherry-pick
Undoing a merged feature in maingit revert (safe, preserves history)
# Typical daily workflow combining both:

# 1. Keep your branch up to date with main (rebase for linear history)
git fetch origin
git rebase origin/main

# 2. Clean up commits before PR (squash WIP commits)
git rebase -i origin/main

# 3. Push your cleaned branch
git push -u origin feature/my-feature
# (If you've already pushed and need to force after rebase)
git push --force-with-lease origin feature/my-feature

# 4. Open PR โ†’ team reviews โ†’ merge into main via GitHub

Golden rule: Rebase to clean up your own local/feature branch history. Merge to integrate completed work into shared branches.

Practice: Merging vs Rebasing ยท Pro Git โ€” Rebasing


A10. Tags & Releasesโ€‹

Git tags mark specific commits as significant โ€” typically a release point. Tags are permanent references unlike branch names.

Two types of tags:

TypeCommandDescription
Lightweightgit tag v1.0.0Just a pointer to a commit โ€” like a branch that doesn't move
Annotatedgit tag -a v1.0.0 -m "Release v1.0.0"Stores tagger name, date, and message โ€” preferred for releases
# Create an annotated tag on the current commit
git tag -a v1.0.0 -m "First stable release"

# Tag a specific past commit
git tag -a v0.9.0 a3f92c1 -m "Beta release"

# List all tags
git tag

# View tag details
git show v1.0.0

# Push a tag to GitHub (tags are not pushed by default)
git push origin v1.0.0

# Push all tags at once
git push origin --tags

# Delete a local tag
git tag -d v1.0.0

# Delete a remote tag
git push origin --delete v1.0.0

Team release convention โ€” Semantic Versioning (MAJOR.MINOR.PATCH):

Version partWhen to incrementExample
MAJORBreaking change โ€” incompatible with previous versionv1.0.0 โ†’ v2.0.0
MINORNew feature added, backwards-compatiblev1.0.0 โ†’ v1.1.0
PATCHBug fix, backwards-compatiblev1.0.0 โ†’ v1.0.1

Creating a GitHub Release:

  1. Push your tag: git push origin v1.0.0
  2. On GitHub: go to Releases โ†’ Draft a new release
  3. Select the tag, write release notes describing what changed
  4. Click Publish release

Team convention: Tags and releases are created on main after a UOW branch has been merged.

Practice: Semantic Versioning ยท GitHub Releases


A11. git bisect โ€” Finding the Commit That Introduced a Bugโ€‹

git bisect performs a binary search through your commit history to find the exact commit that introduced a bug. Instead of checking commits one by one, it halves the search space each step.

# Start a bisect session
git bisect start

# Mark the current commit as broken
git bisect bad

# Mark a known-good commit (e.g. a tag or hash from before the bug appeared)
git bisect good v1.2.0

# Git checks out a commit halfway between โ€” you test it, then tell Git:
git bisect good # if this commit is fine
git bisect bad # if this commit has the bug

# Git keeps narrowing down until it identifies the first bad commit
# Output: "abc1234 is the first bad commit"

# End the bisect session and return to HEAD
git bisect reset

Automated bisect with a test script:

# If you have a script that exits 0 for good and non-zero for bad:
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
git bisect run npm test -- --testFile=src/login.test.js
# Git automatically runs the script at each step and finds the bad commit

Practice: Git Bisect Documentation ยท Atlassian Git Bisect


A12. git worktree โ€” Multiple Working Directoriesโ€‹

git worktree lets you check out multiple branches simultaneously in separate directories โ€” without stashing or switching branches. Useful when you need to review or hotfix another branch while mid-feature.

# Check out a branch into a separate folder (worktree)
git worktree add ../hotfix-branch fix/99-critical-crash

# Now you have two working directories:
# /my-project โ† your current branch (feature work in progress)
# /hotfix-branch โ† fix/99-critical-crash checked out here

# Work in the hotfix worktree, commit, push โ€” independently of your main work

# List all worktrees
git worktree list

# Remove a worktree when done
git worktree remove ../hotfix-branch

When to use: You're mid-feature and a critical production bug needs a fix immediately โ€” instead of stashing everything and switching branches, just add a worktree.

Practice: Git Worktree Documentation


A13. Git Hooks โ€” Automating Actionsโ€‹

Git hooks are scripts that Git runs automatically before or after events like commit, push, or merge. They live in .git/hooks/ and are local โ€” not pushed to GitHub.

Common hooks:

HookTriggeredCommon use
pre-commitBefore a commit is createdRun linter, formatter, or tests
commit-msgAfter commit message is writtenEnforce message format (e.g. [#issue])
pre-pushBefore git push runsRun the full test suite
post-mergeAfter a merge completesAuto-install dependencies (npm install)

Example โ€” pre-commit hook that runs ESLint:

# .git/hooks/pre-commit
#!/bin/sh
npx eslint src/
if [ $? -ne 0 ]; then
echo "ESLint failed. Fix errors before committing."
exit 1
fi

Make the hook executable:

chmod +x .git/hooks/pre-commit

Sharing hooks with the team using Husky:

Since .git/hooks/ is not committed, use Husky to version-control hooks in your repository:

npm install --save-dev husky
npx husky init

# Create a pre-commit hook
echo "npx lint-staged" > .husky/pre-commit

Practice: Git Hooks Documentation ยท Husky


A14. Git Submodules โ€” Repositories Inside Repositoriesโ€‹

A submodule embeds one Git repository inside another as a dependency. Useful when your project depends on a shared library or external repo that is maintained separately.

# Add a submodule
git submodule add git@github.com:org/shared-lib.git libs/shared-lib

# This creates a .gitmodules file and a reference commit

# Clone a repo that has submodules (submodules are NOT cloned by default)
git clone --recurse-submodules git@github.com:org/my-project.git

# If you forgot --recurse-submodules at clone time
git submodule update --init --recursive

# Update all submodules to their latest remote commit
git submodule update --remote

# Check submodule status
git submodule status

Important: When you commit in the parent repo after a submodule update, you're committing the new submodule pointer (hash), not the submodule's files.

Caution: Submodules add complexity. Prefer package managers (npm, pip) for dependencies where possible. Use submodules only when you need to track a specific version of another repo's code directly.

Practice: Pro Git โ€” Submodules


A15. Signing Commits โ€” Verifying Authorshipโ€‹

Signing commits with a GPG or SSH key proves that a commit genuinely came from you โ€” GitHub shows a "Verified" badge on signed commits.

# Generate a GPG key (if you don't have one)
gpg --full-generate-key

# List your GPG keys and get the key ID
gpg --list-secret-keys --keyid-format=long

# Configure Git to use your key
git config --global user.signingkey YOUR_KEY_ID

# Sign commits by default
git config --global commit.gpgsign true

# Sign a single commit manually
git commit -S -m "feat: add signed commit"

# Sign a tag
git tag -s v1.0.0 -m "Signed release v1.0.0"

# Verify a signed commit
git verify-commit HEAD

Add your GPG key to GitHub:

  1. Export your public key: gpg --armor --export YOUR_KEY_ID
  2. Go to GitHub โ†’ Settings โ†’ SSH and GPG keys โ†’ New GPG key
  3. Paste the key and save

Practice: GitHub โ€” Managing Commit Signature Verification


A16. GitHub Actions โ€” CI/CD Basicsโ€‹

GitHub Actions is GitHub's built-in automation platform. It runs workflows in response to repository events (push, PR, release, schedule).

Key concepts:

TermDescription
WorkflowA YAML file in .github/workflows/ defining automation
EventWhat triggers the workflow (e.g. push, pull_request)
JobA set of steps that run on a runner (virtual machine)
StepA single command or action within a job
ActionA reusable unit of work (e.g. actions/checkout)

Example โ€” Run tests on every PR:

# .github/workflows/ci.yml
name: CI

on:
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

With this workflow, every PR automatically runs the test suite and GitHub blocks merging if tests fail.

Example โ€” Auto-deploy on push to main:

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- name: Deploy
run: ./scripts/deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Note: Secrets (like API keys and tokens) are stored in GitHub โ†’ Settings โ†’ Secrets and variables โ†’ Actions โ€” never hardcoded in workflow files.

Practice: GitHub Actions Quickstart ยท GitHub Actions Documentation