GitLearn — Team Git Guide | Git from Beginner to Expert
GitLearn
⟳ Flow
15px

🔵 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:

Your Files
editing right now
not tracked yet
git add
Staging Area
queued for
next commit
git commit
Your Local Copy
full history
on your machine
git push
GitLab
shared with
the team

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:

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:

💡
You can use either one. This guide shows both side by side. Pick the one you are most comfortable with and stick to it.

How to open a 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
💡
Press Tab to auto-complete folder and file names. Press to go back to the last command you typed.

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:

  1. Log in to GitLab
  2. Click your avatar (top-right) → Edit profile
  3. In the left menu click Access Tokens
  4. Click Add new token
  5. Give it a name like my-laptop-git
  6. Set an expiry date (1 year is common)
  7. Tick the write_repository scope
  8. Click Create personal access token
  9. Copy the token shown — you will never see it again after you close this page
⚠️
Copy and save your token somewhere safe (like a password manager) before closing the page. GitLab will not show it again.

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:

Windows will save this in Credential Manager automatically. You will not be asked again on this machine.

💡
If your token expires, open Control Panel → Credential Manager → Windows Credentials and remove the GitLab entry. The next Git push will ask you for a new token.

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):

Visual Studio 2024 / 2026 (.NET projects):

💡
VS Code also has a built-in Source Control panel (Ctrl + Shift + G) for staging and committing with clicks instead of commands. It is great for reviewing changes visually, but learn the terminal commands first so you understand what is happening.

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:

Your machine: main → commits A, B, C, D (your local work) origin/main → commits A, B, C (last fetch snapshot — D not here yet) GitLab right now: main → commits A, B, C, E, F (teammates pushed E and F since you last fetched)

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.

💡
See all local and remote tracking branches: 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
*.logAny file ending in .log
bin/The entire bin folder (trailing slash = folder)
/config.jsOnly config.js at the root, not in subfolders
**/logsAny folder named logs anywhere in the tree
!important.logException — 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 latest  →  Make changes  →  Stage  →  Commit  →  Push

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"
💡
Write commit messages in plain English. Say what you did and why if it is not obvious. Good: "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:

💡
Even when you only have one thing to commit, 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
💡
VS Code users: Install the GitLens extension (free) — it shows inline blame on every line as you type. Hover any line to see the full commit message and author without running any command.

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
💡
Branch names like feature/user-login or fix/null-error keep things organised. Use a short description of what you are working on.
⚠️
Never commit directly to 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:

  1. Push your branch: git push -u origin feature/your-branch
  2. Go to your GitLab project — a yellow banner will appear: "Create Merge Request". Click it.
  3. Fill in a clear title and a short description of what changed and why
  4. Set the target branch to main (or develop if your team uses GitFlow)
  5. Assign a reviewer from your team
  6. Tick Delete source branch when merge request is accepted — keeps the repo tidy
  7. Click Create Merge Request

Your reviewer will read the code, leave comments if needed, and approve it. Once approved, click Merge.

💡
You can link an MR to a GitLab issue automatically. In your MR description, write 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:

  1. Open the conflicted file in your editor
  2. Decide what the file should look like (often a mix of both versions)
  3. Delete the <<<<<<<, =======, and >>>>>>> lines
  4. Save the file
  5. Stage and commit:
git add src/MyFile.cs
git commit -m "Resolve merge conflict in GetName method"
💡
VS Code users: When a conflict exists, VS Code shows coloured sections with buttons — Accept Current Change, Accept Incoming Change, Accept Both. Use these instead of editing the markers manually.

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:

💡
Get into the habit of running 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.

Fast-forward: A ─ B ─ C ─ D ─ E ← main just slides, no merge commit Non-fast-forward: A ─ B ─ C ─ F ─ M ← M is the merge commit └─ D ─ E ─┘

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
featNew featurefeat: add user profile page
fixBug fixfix: null error when email is missing
refactorCode change, no behaviour changerefactor: extract validation to helper
choreBuild, config, dependencieschore: update NuGet packages
docsDocumentation onlydocs: update API auth steps
testAdding or fixing teststest: add coverage for login edge cases
💡
Breaking changes: add ! 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
⚠️
Never commit 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:

Change the words at the start of each line, save, and close the editor. Git will replay the commits in the new order.

⚠️
Only rebase commits that exist on your own branch and have not been pushed to a shared branch. Rebasing already-pushed commits rewrites history and causes problems for teammates.

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
💡
Automate with a test script: 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-commitBefore commit is createdRun linter, block direct commits to main
commit-msgAfter you type the messageEnforce Conventional Commits format
pre-pushBefore push sends dataRun tests, block force-push to main
⚠️
Hooks in .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.

💡
Submodules are for separate Git repositories. If you have multiple projects inside one .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
⚠️
When someone pushes a submodule update, all teammates must run 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.

⚠️
Force push only on your own feature branches. Never force-push to 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.

⚠️
This rewrites every commit hash. All team members must delete their local clone and re-clone afterwards. Coordinate before running this.
# 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
⚠️
Always rotate the secret after this. The file was public while it was in history — treat it as compromised regardless.

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 filegit restore MyFile.cs
Undo last commit, keep changesgit reset HEAD~1
Undo a commit that was already pushedgit 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
blobContents of one file (no filename, just bytes)
treeA folder — maps filenames to blobs and sub-trees
commitPointer to a tree + parent commit(s) + author + message
tagA named pointer to a specific commit

Why this explains common confusion:

# 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 forWeb apps, continuous deliveryVersioned releases, mobile, libraries
Main branchesmain onlymain + develop
Feature workBranch off main, MR back to mainBranch off develop, MR back to develop
ReleasesDeploy directly from mainCreate a release/x.y branch, merge to main + tag
HotfixesBranch off main, MR to mainBranch off main, MR to main AND develop
ComplexitySimple — one long-lived branchMore structure, more branch management
💡
If you are not sure which to use, start with GitHub Flow. It is simpler and works for most teams. Move to GitFlow if you find you need structured release 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 pathpwdpwd or Get-Location
List files in current folderlsdir or ls
Go into a foldercd FolderNamecd FolderName
Go up one levelcd ..cd ..
Create a new foldermkdir my-foldermkdir my-folder
Clear the screenclearcls
Open current folder in Explorerexplorer .explorer .
Open current folder in VS Codecode .code .

Git Quick Reference

What it does Command (same in both shells)
See what has changedgit status
See line-by-line changesgit diff
Stage a filegit add filename.cs
Stage everythinggit add .
Commit staged changesgit commit -m "message"
Push to GitLabgit push
Pull from GitLabgit pull
Create and switch to new branchgit switch -c feature/name
Switch to existing branchgit switch branch-name
See commit historygit log --oneline
Save work-in-progressgit stash
Restore stashed workgit stash pop