🔵 Beginning
Start here if you have never used Git before. By the end of this section you will have Git installed, your identity set up, and be able to connect to GitLab.
1. What is Git?
Git is a tool that keeps a full history of every change ever made to your project. Think of it like a save system in a video game — except instead of one save slot, you have thousands, all labelled with what changed and when. You can go back to any point in history at any time.
When you work with a team, Git also makes sure everyone's changes can be combined together without overwriting each other's work.
The four areas you need to know:
not tracked yet
next commit
on your machine
the team
- Your Files — the files you are actively editing right now
- Staging Area — changes you have chosen to include in your next save point
- Your Local Copy — your complete project history, stored on your own machine
- GitLab — the shared copy your whole team can see and download
2. Install Git on Windows
Go to git-scm.com and click the Windows download button. Run the installer. Most options can stay as default, but pay attention to these screens:
- "Choosing the default editor" — change this to Visual Studio Code if you use VS Code, or leave it as Notepad
- "Adjusting your PATH environment" — keep the recommended option: "Git from the command line and also from 3rd-party software"
- "Configuring the line ending conversions" — keep the default: "Checkout Windows-style, commit Unix-style line endings"
Once installed, verify it worked. Open Git Bash (search for it in the Start menu) or PowerShell and type:
git --version
You should see something like git version 2.47.0.windows.1. If you do, Git is installed.
3. Terminal Basics
A terminal (also called a command line or console) is a text window where you type commands. You will use it to run all Git commands. There are two options on Windows:
- Git Bash — installed with Git. Uses Linux-style commands. Most Git tutorials online use this.
- PowerShell — built into Windows. You may already know it. Git commands work the same, but some other commands look different.
How to open a terminal:
- Git Bash: Press Win, type
Git Bash, press Enter - PowerShell: Press Win + X, then click Windows PowerShell or Terminal
Essential commands to navigate folders:
# Show where you are
pwd
# List files and folders here
ls
# Move into a folder
cd FolderName
# Move up one level
cd ..
# Create a new folder
mkdir my-project
# Clear the screen
clear
4. First-time Setup
Before you can make your first commit, Git needs to know your name and email. This is recorded on every change you make so your teammates know who did what.
Run these two commands once — you never need to do this again on the same machine:
git config --global user.name "Your Name"
git config --global user.email "you@company.com"
Use your real name and the same email address you used to sign up to GitLab. To check it worked:
git config --list
You should see your name and email in the output.
5. Connect to GitLab (HTTPS)
To push and pull from GitLab, you need to prove who you are. We use a Personal Access Token (PAT) — think of it as a password that only works for Git.
Step 1 — Create your token on GitLab:
- Log in to GitLab
- Click your avatar (top-right) → Edit profile
- In the left menu click Access Tokens
- Click Add new token
- Give it a name like
my-laptop-git - Set an expiry date (1 year is common)
- Tick the write_repository scope
- Click Create personal access token
- Copy the token shown — you will never see it again after you close this page
Step 2 — Store it so Windows remembers it:
When you clone or push for the first time, Git will ask for your username and password. Enter:
- Username: your GitLab username
- Password: the Personal Access Token (not your GitLab login password)
Windows will save this in Credential Manager automatically. You will not be asked again on this machine.
6. Opening a Terminal from Your IDE
You do not need to open a separate terminal window — both IDEs have one built in.
Visual Studio Code (Angular projects):
- Press Ctrl + ` (backtick, the key above Tab) to open the integrated terminal
- It opens in Git Bash by default if Git Bash is installed
- To switch to PowerShell: click the + dropdown arrow in the terminal panel → select PowerShell
Visual Studio 2024 / 2026 (.NET projects):
- Go to View → Terminal to open the Developer PowerShell
- Alternatively, right-click the solution in Solution Explorer → Open in Terminal
8. Remote Tracking — origin/main vs main
This confuses almost everyone at first. When you clone a repo, Git creates two kinds of references on your machine:
main— your local branch. You commit here. It only moves when you commit.origin/main— a read-only snapshot of what GitLab'smainlooked like the last time you rangit fetch,git pull, orgit clone. It does not update automatically when teammates push.
This is why git status says things like "Your branch is behind 'origin/main' by 2 commits" — it's comparing against the cached snapshot, not the live server. Run git fetch to refresh the snapshot.
git branch -a. Remote branches are prefixed with remotes/origin/.9. .gitignore — Patterns and Debugging
The .gitignore file tells Git which files to never track. Pattern syntax:
| Pattern | What it matches |
|---|---|
*.log | Any file ending in .log |
bin/ | The entire bin folder (trailing slash = folder) |
/config.js | Only config.js at the root, not in subfolders |
**/logs | Any folder named logs anywhere in the tree |
!important.log | Exception — don't ignore this file even if a rule above matches it |
Debug why a file is being ignored:
git check-ignore -v filename.js
# Output tells you exactly which rule matched and from which .gitignore file
Already committed a file that should be ignored:
git rm --cached .env # stop tracking it (file stays on disk)
echo ".env" >> .gitignore
git commit -m "chore: stop tracking .env"
Global .gitignore — rules that apply to every repo on your machine (great for IDE and OS files):
git config --global core.excludesfile ~/.gitignore_global
# Then add entries like .DS_Store, Thumbs.db, .idea/, .vs/ to that file
10. Verify Your Setup
Let's make sure everything works end-to-end by cloning a real repo from GitLab.
Go to any GitLab project your team has, click the blue Clone button, and copy the Clone with HTTPS URL. Then run:
git clone https://gitlab.company.com/team/some-repo.git
cd some-repo
git log --oneline -5
If you see a list of recent commits, your setup is working. You are ready to use Git.
🟢 Day-to-Day
These are the commands and ideas you will use every single day. Get comfortable with these before moving to the Expert section.
1. The Core Workflow
Every day with Git follows the same loop:
Pull — get the latest changes from GitLab before you start:
git pull
Check what has changed — after editing files:
# See which files changed
git status
# See exactly what lines changed
git diff
Stage — choose which changes to include in your next save point:
# Stage one specific file
git add src/MyFile.cs
# Stage all changed files at once
git add .
Commit — save a snapshot with a message describing what you did:
git commit -m "Add login endpoint to AuthController"
"Fix null error when user email is missing". Bad: "fix stuff".Push — send your commits to GitLab so the team can see them:
git push
See the history — view past commits:
# Full history
git log
# Compact one line per commit
git log --oneline
2. Staging Selectively — git add -p
Instead of staging a whole file, you can choose individual chunks of changes with git add -p. This is useful when you made several unrelated edits to one file and want to split them into separate, focused commits.
git add -p
Git shows each changed section and asks what to do. The main keys:
- y — stage this chunk
- n — skip it (leave for a later commit)
- s — split the chunk into smaller pieces
- q — quit, leave the rest unstaged
- ? — show all options
git add -p forces you to review every line before it's staged — a good habit for catching debug logs or accidental changes before they land in the repo.3. Reading History — git log
The default git log is verbose. These flags make it useful:
# Compact view with branch graph
git log --oneline --graph --all
# Filter by author
git log --author="Alice"
# Filter by date
git log --since="1 week ago"
# Search commit messages
git log --grep="login"
# Find when a specific piece of code was added or removed
git log -S "getUserById"
# Show what files changed in each commit
git log --stat
# Inspect one specific commit in full
git show a1b2c3d
git log -S "someFunction" (called the "pickaxe") finds every commit that added or removed that exact string — extremely useful for tracking down when something was introduced or deleted.4. git blame — Who Changed What
git blame shows who last changed each line of a file and in which commit. Essential when you find a bug and need context, or when you want to know who to ask about a piece of code.
# See author and commit for each line
git blame src/auth/login.js
# Blame a specific range of lines only
git blame -L 20,40 src/auth/login.js
5. Branching
A branch is your own private workspace. When you start on a new feature or bug fix, you create a branch so your changes do not affect the rest of the team until you are ready.
Think of branches like separate copies of the project — except they are very cheap to create and Git is smart enough to merge them back together later.
# Create a new branch and switch to it
git switch -c feature/user-login
# Switch to an existing branch
git switch main
# List all branches
git branch
# Delete a branch (only after it has been merged)
git branch -d feature/user-login
feature/user-login or fix/null-error keep things organised. Use a short description of what you are working on.main. Always work on a branch and merge it through a Merge Request.6. Detached HEAD — What It Means
Normally HEAD points to a branch name, and the branch moves forward as you commit. A detached HEAD means HEAD is pointing directly to a commit hash instead of a branch — any commits you make won't belong to any branch and will be lost when you switch away.
It happens when you run git checkout <commit-hash> or git checkout <tag> to inspect old code. Git warns you:
HEAD detached at a1b2c3d
How to get out safely:
# Just go back to a branch (discard any commits made in detached state)
git switch main
# Or, if you made commits you want to keep — save them to a new branch first
git switch -c my-exploration-branch
7. Merge Requests on GitLab
When your feature branch is ready, you open a Merge Request (MR) on GitLab. This is how your code gets reviewed by a teammate and merged into the main branch.
How to open a Merge Request:
- Push your branch:
git push -u origin feature/your-branch - Go to your GitLab project — a yellow banner will appear: "Create Merge Request". Click it.
- Fill in a clear title and a short description of what changed and why
- Set the target branch to
main(ordevelopif your team uses GitFlow) - Assign a reviewer from your team
- Tick Delete source branch when merge request is accepted — keeps the repo tidy
- Click Create Merge Request
Your reviewer will read the code, leave comments if needed, and approve it. Once approved, click Merge.
Closes #42 (replace 42 with the issue number) and GitLab will close the issue when the MR is merged.4. Resolving Conflicts
A conflict happens when two people edit the same lines in the same file. Git cannot decide which version to keep, so it asks you to choose.
When a conflict happens, the file will contain markers like this:
<<<<<<< HEAD (your version)
public string GetName() => "Alice";
=======
public string GetName() => "Bob";
>>>>>>> feature/rename-user (their version)
Everything between <<<<<<< and ======= is your version. Everything between ======= and >>>>>>> is theirs. Edit the file to keep what is correct, then remove all the markers.
Steps to fix a conflict:
- Open the conflicted file in your editor
- Decide what the file should look like (often a mix of both versions)
- Delete the
<<<<<<<,=======, and>>>>>>>lines - Save the file
- Stage and commit:
git add src/MyFile.cs
git commit -m "Resolve merge conflict in GetName method"
Visual Studio users: Right-click the conflicted file in Solution Explorer → Resolve Conflicts to open the built-in merge tool.
5. Staying in Sync
While you work on your branch, your teammates keep pushing to main. Your branch falls behind. Bring their changes in regularly to avoid a large conflict later.
# Download the latest changes from GitLab (does NOT change your files yet)
git fetch origin
# Apply the latest main onto your branch
git rebase origin/main
fetch vs pull:
git fetch— downloads changes from GitLab but does not touch your files. Safe to run any time.git pull— downloads AND immediately applies changes to your current branch.
git fetch origin and git rebase origin/main on your feature branch every morning. It keeps your branch close to main and makes the final merge much smoother.10. Fast-forward vs Non-fast-forward Merges
When you merge a branch, Git uses a fast-forward if the target branch hasn't moved since you branched off — it just slides the pointer forward, no merge commit needed. If both sides have new commits, Git creates a merge commit with two parents.
GitLab MRs use --no-ff by default, so merging always creates a merge commit — making it clear exactly when each feature landed in main.
11. Commit Message Conventions
Conventional Commits is a lightweight standard that makes your history searchable and the purpose of every commit immediately clear.
Format: <type>: <short description>
| Type | When to use | Example |
|---|---|---|
feat | New feature | feat: add user profile page |
fix | Bug fix | fix: null error when email is missing |
refactor | Code change, no behaviour change | refactor: extract validation to helper |
chore | Build, config, dependencies | chore: update NuGet packages |
docs | Documentation only | docs: update API auth steps |
test | Adding or fixing tests | test: add coverage for login edge cases |
! after the type — feat!: rename GetUser to FindUser. Even just using fix: and feat: consistently makes git log --grep="fix:" useful for filtering.12. Stash — Save Work Without Committing
Sometimes you are in the middle of something and need to switch to another task urgently — but your changes are not ready to commit. Stash saves your in-progress work temporarily.
# Save your current changes away
git stash
# (Do your urgent task, switch branches, etc.)
# Bring your saved changes back
git stash pop
# See all saved stashes
git stash list
# Delete a stash you no longer need
git stash drop
7. .NET-Specific Tips
A few things to know when using Git with .NET solutions.
Always use a .gitignore for .NET. Without it, Git will try to track thousands of generated files in bin/ and obj/. Add this to a file named .gitignore at the root of your repo:
bin/
obj/
.vs/
*.user
*.suo
.idea/
appsettings.Development.json
appsettings.Local.json
appsettings.Development.json or any file containing connection strings, passwords, or API keys. Add these files to .gitignore and share them with teammates through a password manager or secure channel.Conflicts in .csproj files are common when two people add a NuGet package at the same time. They are XML files — resolve the conflict markers as normal, keep both <PackageReference> entries, then run dotnet build to confirm the file is valid.
After pulling changes that include new NuGet packages, run:
dotnet restore
🟣 Expert
These topics are not needed on day one. Come back to this section once you are comfortable with the Day-to-Day workflow.
1. Interactive Rebase — Clean Up Before Merging
Before opening a Merge Request, your branch might have messy commits like "fix typo", "actually fix typo", "oops wrong file". Interactive rebase lets you tidy these into one clean commit.
# Rewrite the last 4 commits interactively
git rebase -i HEAD~4
This opens an editor with a list of your commits and these options:
pick— keep this commit as-isreword— keep the commit but change its messagesquash— combine this commit into the one above itfixup— same as squash but discard this commit's messagedrop— delete this commit entirely
Change the words at the start of each line, save, and close the editor. Git will replay the commits in the new order.
After rebasing, push with:
git push --force-with-lease origin feature/your-branch
2. git bisect — Find the Bug Commit
Bisect does a binary search through your commit history to find exactly which commit introduced a bug. Instead of manually checking commits one by one, Git cuts the search space in half each round — 100 commits takes only ~7 checks.
git bisect start
git bisect bad # current state is broken
git bisect good v1.2.0 # last known good (tag, hash, or branch)
# Git checks out a midpoint. Test it, then tell Git:
git bisect good # bug not present here
git bisect bad # bug present here
# Repeat until Git identifies the exact commit
git bisect reset # return to your branch when done
git bisect run npm test — Git runs the script at each step and determines good/bad automatically based on the exit code.3. git worktree — Two Branches at Once
A worktree lets you check out a second branch into a separate folder without stashing or cloning again. Both folders share the same .git history.
Typical use: you're mid-feature and an urgent hotfix arrives. Instead of stashing everything, open a second worktree for the hotfix.
# Check out the hotfix branch into a sibling folder
git worktree add ../MyApp-hotfix hotfix/login-crash
# Both folders are now independent working directories
# ./MyApp ← your feature, untouched
# ./MyApp-hotfix ← hotfix branch, ready to work in
git worktree list # see all active worktrees
git worktree remove ../MyApp-hotfix # clean up when done
4. Git Hooks — Automate Quality Gates
Hooks are scripts in .git/hooks/ that Git runs automatically at specific points. They're how teams enforce rules without relying on people to remember.
| Hook | When it runs | Common use |
|---|---|---|
pre-commit | Before commit is created | Run linter, block direct commits to main |
commit-msg | After you type the message | Enforce Conventional Commits format |
pre-push | Before push sends data | Run tests, block force-push to main |
.git/hooks/ are not tracked by Git — they won't be shared with the team. To share hooks, commit them to a .githooks/ folder and configure: git config core.hooksPath .githooks.5. Submodules — Using Another Repo Inside Your Repo
A submodule lets you include a separate GitLab repository inside your own. Use this when your team has a shared library that lives in its own repo and needs to be used by multiple other repos.
.sln file, use <ProjectReference> in your .csproj instead — that is just a normal .NET feature, not a submodule.Add a submodule:
git submodule add https://gitlab.company.com/team/SharedLib.git libs/SharedLib
git commit -m "Add SharedLib as submodule"
git push
Clone a repo that has submodules:
# Clone and download submodules in one command
git clone --recurse-submodules https://gitlab.company.com/team/MyApp.git
# Or if you already cloned without submodules
git submodule update --init --recursive
Update a submodule to its latest version:
cd libs/SharedLib
git pull origin main
cd ../..
git add libs/SharedLib
git commit -m "Update SharedLib to latest"
git push
git pull followed by git submodule update --recursive to get the new version.3. Safe Force Push
After an interactive rebase, your local branch history no longer matches what is on GitLab. A normal git push will be rejected. You need a force push.
Always use --force-with-lease instead of --force:
git push --force-with-lease origin feature/your-branch
--force-with-lease checks that nobody else has pushed to the branch since you last fetched. If someone did, Git will stop and warn you instead of overwriting their work. --force does not do this check.
main, develop, or any shared branch.4. Cherry-pick — Copy One Commit to Another Branch
Cherry-pick copies a single commit from one branch and applies it to another. Useful when you made a fix on the wrong branch, or need a specific change without merging everything else.
# Find the commit hash you want
git log --oneline
# Switch to the branch you want to copy it to
git switch main
# Apply that one commit
git cherry-pick a1b2c3d
5. Tags & Releases
Tags mark a specific point in history — typically a release version. Unlike branch names, tags do not move as you add more commits.
# Create a tag for the current commit
git tag -a v1.2.0 -m "Release version 1.2.0"
# Push the tag to GitLab
git push origin v1.2.0
# List all tags
git tag
# Push all tags at once
git push origin --tags
On GitLab, tags appear under Repository → Tags and can be turned into Releases with release notes.
9. Clean History — Remove a Secret or Large File
If you accidentally committed a password or API key and pushed it, it lives in every clone of the repo. git filter-repo rewrites the entire history to remove it permanently.
# Install once: pip install git-filter-repo
# Remove a file from all history
git filter-repo --path secrets.json --invert-paths
# Force-push all branches
git push origin --force --all
10. Undoing Things
Here is how to undo common mistakes. The right command depends on whether you have already pushed your changes:
| Situation | Command |
|---|---|
| Unstage a file (keep changes) | git restore --staged MyFile.cs |
| Discard unsaved changes to a file | git restore MyFile.cs |
| Undo last commit, keep changes | git reset HEAD~1 |
| Undo a commit that was already pushed | git revert <hash> |
git reset --hard permanently deletes uncommitted changes with no undo. Only use it when you are certain you want to throw away your work. For commits already pushed to a shared branch, always use git revert instead — it creates a new commit that undoes the change, rather than rewriting history.11. Git Aliases — Shortcuts for Daily Commands
Aliases let you define short names for long commands. Set them once in your global config and they work in every repo.
# Pretty log with branch graph
git config --global alias.lg "log --oneline --graph --all --decorate"
# Undo last commit, keep changes staged
git config --global alias.undo "reset --soft HEAD~1"
# Unstage a file
git config --global alias.unstage "restore --staged"
# Short status
git config --global alias.st "status -sb"
After setting up: git lg, git undo, git st — less typing every day.
12. Git Internals — How Git Actually Stores Things
You don't need this to use Git daily — but understanding it makes everything else make sense. Why does rebase "rewrite history"? Why is force-push dangerous? It comes from this.
Git stores four types of objects, each identified by a SHA hash:
| Object | What it stores |
|---|---|
| blob | Contents of one file (no filename, just bytes) |
| tree | A folder — maps filenames to blobs and sub-trees |
| commit | Pointer to a tree + parent commit(s) + author + message |
| tag | A named pointer to a specific commit |
Why this explains common confusion:
- Rebase "rewrites history" because a commit's hash is computed from its content + parent hash. Change the parent → new hash → it's literally a new commit object.
- Force-push is dangerous because the remote just stores which hash a branch name points to. Force-push replaces that pointer. The old commits still exist for ~30 days but nothing points to them.
- Git is fast because if two commits share identical files, they point to the same blob object — no duplication.
# Explore internals yourself
git cat-file -t HEAD # shows object type: "commit"
git cat-file -p HEAD # shows commit contents
git cat-file -p HEAD^{tree} # shows the root folder snapshot
13. GitFlow vs GitHub Flow
These are two common patterns for how a team organises branches. Choose one and stick to it.
| GitHub Flow | GitFlow | |
|---|---|---|
| Best for | Web apps, continuous delivery | Versioned releases, mobile, libraries |
| Main branches | main only | main + develop |
| Feature work | Branch off main, MR back to main | Branch off develop, MR back to develop |
| Releases | Deploy directly from main | Create a release/x.y branch, merge to main + tag |
| Hotfixes | Branch off main, MR to main | Branch off main, MR to main AND develop |
| Complexity | Simple — one long-lived branch | More structure, more branch management |
📋 Terminal Cheatsheet
Quick reference for terminal commands. Git commands are the same in both shells — only the navigation commands differ.
Navigation Commands
| What it does | Git Bash | PowerShell |
|---|---|---|
| Show current folder path | pwd | pwd or Get-Location |
| List files in current folder | ls | dir or ls |
| Go into a folder | cd FolderName | cd FolderName |
| Go up one level | cd .. | cd .. |
| Create a new folder | mkdir my-folder | mkdir my-folder |
| Clear the screen | clear | cls |
| Open current folder in Explorer | explorer . | explorer . |
| Open current folder in VS Code | code . | code . |
Git Quick Reference
| What it does | Command (same in both shells) |
|---|---|
| See what has changed | git status |
| See line-by-line changes | git diff |
| Stage a file | git add filename.cs |
| Stage everything | git add . |
| Commit staged changes | git commit -m "message" |
| Push to GitLab | git push |
| Pull from GitLab | git pull |
| Create and switch to new branch | git switch -c feature/name |
| Switch to existing branch | git switch branch-name |
| See commit history | git log --oneline |
| Save work-in-progress | git stash |
| Restore stashed work | git stash pop |