git —local // windows, no server
0 / 8 exercises
Hands-on training · Git for Windows

A remote is just a folder you can reach.

No GitHub. No server process. No SSH keys. If your machine can open a path in File Explorer, Git can push to it — a second drive, a mapped network share, even a USB stick. This training builds that workflow command by command in a Git sandbox that runs right here in the page.

Type commands in the terminal on the right. It runs a simplified Git — enough to be honest about how the real thing behaves, not enough to replace it.

01

The mental model

Two kinds of repository, one connection between them, and nothing else running.

Git has no concept of a server. What people call “the remote” is a saved shortcut: a name like origin pointing at a location Git can read and write. On a hosted setup that location is a URL. In a local setup it is a path — D:\repos\project.git or \\FS01\Engineering\repos\project.git. Push and pull then become file copies between two .git directories.

Working repository vs bare repository

 Working repositoryBare repository
Created bygit initgit init --bare
ContainsYour files + a hidden .git folderThe contents of .git, and nothing else
You edit in itYes — this is where you workNever. There are no files to open.
Safe to push toNo (Git refuses by default)Yes. This is its whole job.
Namedprojectproject.git by convention
Why bare matters Pushing to a repository that has files checked out would change the history under someone's feet while their editor has files open. Git blocks it. The bare repo has no checked-out files, so there is nothing to disturb — which is exactly why the shared copy on the drive is bare.

Where the bare repo can live

  • Another folder on the same PC — D:\repos\project.git. A real backup only if it is a different physical disk.
  • A mapped network drive — S:\repos\project.git. The usual choice for a small team on a file server.
  • A UNC path, no drive letter needed — \\FS01\Engineering\repos\project.git.
  • Removable media — E:\project.git on a USB drive, for moving work between disconnected machines.

Access control is whatever NTFS says. Anyone who needs to push needs Modify rights on that folder; read-only users can still clone and fetch.

02

Setting up Git for Windows

Three shells ship in the box, and four settings decide how much trouble you have later.

Which shell to use

ShellPath styleNotes
Git Bash/c/Dev/projectUnix-style. Drive letters become /c/, /s/. Best for following any Git documentation you find.
PowerShellC:\Dev\projectFine for Git. Watch out: > writes UTF-16 by default, which surprises people writing files.
Command PromptC:\Dev\projectWorks. Fewest surprises with backslashes and drive letters.

Git accepts C:\Dev\project, C:/Dev/project and /c/Dev/project in most places. This sandbox accepts all three too, so you can practise whichever you use.

First-run configuration

# who you are — stamped into every commit you make
git config --global user.name "Your Name"
git config --global user.email "you@company.com"

# call the first branch main instead of master
git config --global init.defaultBranch main

# line endings: check out CRLF, store LF (the Windows installer default)
git config --global core.autocrlf true

# allow paths longer than 260 characters
git config --global core.longpaths true

# check what you ended up with
git config --global --list
Windows gotcha — dubious ownership Cloning from a share often produces fatal: detected dubious ownership in repository at 'S:/repos/project.git'. Git is refusing to trust a repo owned by a different account. If you trust the share, clear it with git config --global --add safe.directory S:/repos/project.git — note the forward slashes.
Windows gotcha — antivirus and indexing Real-time scanning of .git is the single biggest cause of “Git feels slow” on Windows. Ask for an exclusion on your working folders and the repo folder on the share, and turn off Windows Search indexing for both.
03

Your first working repository

Edit, stage, commit. Everything else in Git is built on this loop.

A file lives in one of three places at any moment: the working tree (what is on disk), the index (what you have staged for the next commit), and history (what is committed). git add moves a change from the first to the second, git commit from the second to the third.

# make a folder and turn it into a repository
cd C:\Dev
mkdir project
cd project
git init

# see where you stand — run this constantly
git status

# stage and record
git add analysis.m
git commit -m "Add signal analysis entry point"

# stage everything that changed
git add .
git commit -m "Add filter helper and notes"

git log --oneline
Commit messages that survive One line, imperative, under ~60 characters: Fix bearing wrap at 360 degrees. Six months from now git log --oneline is the only interface anyone has to your reasoning.
Exercise 1 · Build a repository0/3
  • ✓Create C:\Dev\project and run git init inside it
  • ✓Stage analysis.m with git add
  • ✓Make your first commit
04

Creating the local remote

One command on the shared drive, and you have the equivalent of a hosting service.

# from anywhere — the folder is created for you
git init --bare S:\repos\project.git

# or start it from an existing repo, history and all
git clone --bare C:\Dev\project S:\repos\project.git

Look inside afterwards and you will see HEAD, config, objects\, refs\ — the contents of a .git folder, promoted to the top level. That is the entire difference.

Two ways to start

  • Empty bare repo first (git init --bare) — when the project does not exist yet, or you want to push an existing local repo up to it.
  • Bare clone (git clone --bare) — when a working repo already has history you want on the share immediately.
Do not put the working repo on the share Copying C:\Dev\project wholesale onto S:\ and having everyone open it looks like it works, then quietly corrupts. Two people running Git against one working tree over SMB is not a supported arrangement. Bare on the share, working copies on local disks.
Exercise 2 · Stand up the shared repo0/1
  • ✓Create a bare repository at S:\repos\project.git
A colleague pushes to S:\repos\project — a normal clone, not bare — and gets refusing to update checked out branch. What fixes it?
05

Connecting and pushing

Name the path once, then forget it.

# give the path a name
git remote add origin S:\repos\project.git

# confirm — fetch and push URLs are listed separately
git remote -v

# first push: -u remembers the pairing so later you can just type "git push"
git push -u origin main

# from then on
git push

Writing the path

FormWhere it works
S:\repos\project.gitCMD and PowerShell. In Git Bash the backslashes get eaten as escapes — quote it.
S:/repos/project.gitEverywhere. The safest form to type and the one to put in documentation.
/s/repos/project.gitGit Bash only.
//FS01/Engineering/repos/project.gitUNC in Git Bash — forward slashes, no mapped drive needed.
"D:/My Projects/project.git"Any path containing spaces must be quoted.
file:///S:/repos/project.gitValid everywhere, and forces a full object transfer instead of the local shortcut — see below.
Mapped drives and elevation A drive letter mapped in your normal session does not exist in an elevated one. If S:\ works in your shell but not in an admin shell, use the UNC path instead of the letter. Mapping is per-user, per-session.

Understanding the rejection you will eventually hit

! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'S:/repos/project.git'

This is not a failure of your setup. Someone pushed commits you do not have yet, and Git will not silently drop them. Run git pull, deal with any merge, then push again. Never reach for --force on a shared repository to make this go away — it deletes their work.

Exercise 3 · Connect and publish0/2
  • ✓Add origin pointing at S:\repos\project.git
  • ✓Push main to it with upstream tracking (-u)
06

Cloning from the share

How the second machine — or the second person — gets a copy.

# clone into a folder named after the repo, minus the .git
cd C:\Dev
git clone S:\repos\project.git

# or name the folder yourself
git clone S:\repos\project.git project-clone

The clone arrives with origin already configured, full history, and the default branch checked out. Nothing else to set up.

Hardlinks: fast, and occasionally a trap When source and destination are on the same volume, Git hardlinks the object files instead of copying them — instant, and almost no extra disk used. The clone is still a real repository, but the two share physical bytes, so it is not a backup. For an independent copy use git clone --no-hardlinks, or a file:/// URL, which always transfers properly.
Exercise 4 · Make a second working copy0/1
  • ✓Clone the share into C:\Dev\project-clone
07

Staying in sync

Fetch looks, pull applies. Know which one you are asking for.

CommandEffect
git fetchDownloads new commits and updates origin/main. Your branch and your files are untouched.
git pullfetch, then merges origin/main into your branch. Your files change.
git log --oneline main..origin/mainAfter a fetch: what they did that you have not got.
git log --oneline origin/main..mainWhat you have that is not pushed yet.

If your history has not moved since the last sync, a pull is a fast-forward — your branch pointer just slides along theirs. If you both committed, Git makes a merge commit joining the two lines.

When the same file changed on both sides

CONFLICT (content): Merge conflict in analysis.m
Automatic merge failed; fix conflicts and then commit the result.

Git writes both versions into the file between markers. Open it, decide what the file should say, delete the markers, then:

git add analysis.m
git commit          # Git supplies the merge message
Get out of a merge you did not want git merge --abort puts everything back exactly as it was before the pull. It is always available while the conflict is unresolved.
Exercise 5 · Take someone else's changes0/2

Use the button to have a colleague push to the share from their own machine, then bring it into C:\Dev\project.

  • ✓Fetch and inspect what arrived (git fetch, then git log --oneline main..origin/main)
  • ✓Pull it into your branch
Exercise 6 · Survive a conflict0/1

Have the colleague edit analysis.m, then edit and commit the same file yourself before pulling. Resolve what happens.

  • ✓Resolve a merge conflict and commit the merge
You want to see whether a colleague has pushed anything, without touching your working files. Which command?
08

Branches on a shared drive

The cheapest way for several people to use one bare repo without colliding.

# create and switch in one step
git switch -c feature/kalman-tuning
# older syntax, same thing
git checkout -b feature/kalman-tuning

# publish it to the share
git push -u origin feature/kalman-tuning

# what exists, locally and on the share
git branch -a

# fold it back into main
git switch main
git pull
git merge feature/kalman-tuning
git push

# clean up
git branch -d feature/kalman-tuning
git push origin --delete feature/kalman-tuning
A convention worth adopting On a shared drive with no pull requests and no permissions per branch, agree that everyone works on their own branch and only merges into main from a clean, freshly pulled state. It removes almost every fetch first rejection.
Exercise 7 · Publish a branch0/2
  • ✓Create branch feature/kalman-tuning and commit something on it
  • ✓Push the branch to the share
09

Windows-specific traps

Every one of these has cost a real team a real afternoon.

Line endings

Windows editors write CRLF; Git prefers to store LF. With core.autocrlf true the conversion happens on the way in and out, and you will see warning: LF will be replaced by CRLF — that is informational, not an error. The durable fix is a .gitattributes file committed to the repo, so the rules travel with the project instead of living in each person's global config:

# .gitattributes — commit this at the repo root
* text=auto

*.m      text eol=crlf
*.txt    text
*.md     text

# binaries: never touch these
*.mat    binary
*.fig    binary
*.slx    binary
*.mdl    binary
*.mlx    binary
*.p      binary
*.mexw64 binary
*.xlsx   binary
*.pdf    binary
Why the binary list matters Without it, Git may treat a .mat or .slx as text and “helpfully” rewrite byte sequences that look like line endings. The file still opens — until one day it does not, and the corruption is already in history.

Case-insensitive filesystem

Windows treats Analysis.m and analysis.m as the same file; Git does not. Renaming only the case appears to do nothing. Force it through Git so the change is recorded:

git mv -f Analysis.m analysis.m
git commit -m "Normalise filename case"

Path length

Filename too long on checkout means the 260-character limit. Set git config --global core.longpaths true, and keep working folders shallow — C:\Dev\ beats a path nested under your Documents folder.

Files that cannot be merged

Two people editing one .slx or .xlsx produces a conflict Git cannot resolve, because there are no lines to combine. The answer is social, not technical: agree who owns which binary model, or split the work so two people are never in the same file. If you need Git to stop even trying, add *.slx -merge to .gitattributes.

OneDrive, Dropbox and folder sync

Do not sync a Git repository with a file-sync client Sync tools copy files in whatever order they like and resolve their own conflicts by duplicating files. Applied to .git, that produces a repository that fails git fsck and cannot be recovered. Use Git to move Git data. Keep working repos out of synced folders.

Housekeeping on the bare repo

# run occasionally on the share — compacts objects
cd S:\repos\project.git
git gc

# verify integrity
git fsck
Which of these will reliably damage a repository?
10

Backup and moving work between machines

Including machines that will never see the same network.

Mirror the bare repo

# make a complete mirror — all branches, all tags
git clone --mirror S:\repos\project.git D:\backup\project.git

# refresh it later
cd D:\backup\project.git
git remote update

Bundles — one file, full history

A bundle is a repository squeezed into a single file you can email, burn, or carry on a USB stick. Clone from it exactly as you would from a folder.

# everything
git bundle create D:\transfer\project.bundle --all

# only what is new since a known commit — much smaller
git bundle create D:\transfer\update.bundle a1b2c3d..main

# on the far side
git clone D:\transfer\project.bundle project
# or into an existing repo
git bundle verify D:\transfer\update.bundle
git pull D:\transfer\update.bundle main

When the share moves

git remote set-url origin \\FS02\Engineering\repos\project.git
git remote -v

Nothing else changes — history is identical in every copy, so repointing the name is the whole migration.

11

Reference

The commands and the error messages, in the order you meet them.

Daily loop

CommandWhat it does
git statusWhere you are and what has changed
git pullStart of session: take what others pushed
git add .Stage everything changed
git commit -m "..."Record the staged snapshot
git pushEnd of a piece of work: publish to the share
git log --oneline --graph --allSee the shape of the history
git diff / git diff --stagedUnstaged changes / staged changes
git restore <file>Throw away uncommitted edits to a file
git restore --staged <file>Unstage, keeping the edit
git switch <branch>Move to another branch
git stash / git stash popPark work in progress, get it back

Local-remote specific

git init --bare S:\repos\x.gitCreate the shared repository
git remote add origin S:/repos/x.gitPoint at it
git remote set-url origin ...Repoint after a move
git push -u origin mainFirst push, remembers the pairing
git clone --no-hardlinks src dstIndependent copy on the same volume
git clone --mirror src dstFull backup of a bare repo
git bundle create f.bundle --allHistory as a single portable file

Error messages, decoded

MessageCause and fix
does not appear to be a git repositoryPath typo, share not mounted, or the bare repo was never created. Check it in File Explorer first.
! [rejected] main -> main (fetch first)Someone pushed ahead of you. git pull, resolve, push again.
refusing to update checked out branchYou pushed to a non-bare repo. Push to a bare one instead.
detected dubious ownershipRepo on the share is owned by another account. Add it to safe.directory.
Filename too long260-char limit. core.longpaths true, shallower folders.
LF will be replaced by CRLFInformation, not an error. Line-ending conversion is doing its job.
destination path already existsThe clone target folder is there and non-empty. Pick another name.
Permission denied on pushNTFS rights on the share folder. You need Modify, not Read.
unable to create file ... Invalid argumentA filename legal on Linux but not Windows (:, ?, *, trailing dot). Rename it in a repo that can.
Your push is rejected with fetch first. What is the correct next command?
Which path form is safest to write in a team's setup instructions?