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 repository | Bare repository | |
|---|---|---|
| Created by | git init | git init --bare |
| Contains | Your files + a hidden .git folder | The contents of .git, and nothing else |
| You edit in it | Yes — this is where you work | Never. There are no files to open. |
| Safe to push to | No (Git refuses by default) | Yes. This is its whole job. |
| Named | project | project.git by convention |
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.giton 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.
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
| Shell | Path style | Notes |
|---|---|---|
| Git Bash | /c/Dev/project | Unix-style. Drive letters become /c/, /s/. Best for following any Git documentation you find. |
| PowerShell | C:\Dev\project | Fine for Git. Watch out: > writes UTF-16 by default, which surprises people writing files. |
| Command Prompt | C:\Dev\project | Works. 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
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..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.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
git log --oneline is the only interface anyone has to your reasoning.- ✓Create
C:\Dev\projectand rungit initinside it - ✓Stage
analysis.mwithgit add - ✓Make your first commit
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.
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.- ✓Create a bare repository at
S:\repos\project.git
S:\repos\project — a normal clone, not bare — and gets
refusing to update checked out branch. What fixes it?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
| Form | Where it works |
|---|---|
S:\repos\project.git | CMD and PowerShell. In Git Bash the backslashes get eaten as escapes — quote it. |
S:/repos/project.git | Everywhere. The safest form to type and the one to put in documentation. |
/s/repos/project.git | Git Bash only. |
//FS01/Engineering/repos/project.git | UNC 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.git | Valid everywhere, and forces a full object transfer instead of the local shortcut — see below. |
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.
- ✓Add
originpointing atS:\repos\project.git - ✓Push
mainto it with upstream tracking (-u)
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.
git clone --no-hardlinks, or a
file:/// URL, which always transfers properly.- ✓Clone the share into
C:\Dev\project-clone
Staying in sync
Fetch looks, pull applies. Know which one you are asking for.
| Command | Effect |
|---|---|
git fetch | Downloads new commits and updates origin/main. Your branch and your files are untouched. |
git pull | fetch, then merges origin/main into your branch. Your files change. |
git log --oneline main..origin/main | After a fetch: what they did that you have not got. |
git log --oneline origin/main..main | What 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
git merge --abort puts everything back exactly as it was before the pull. It is always available while
the conflict is unresolved.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, thengit log --oneline main..origin/main) - ✓Pull it into your branch
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
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
main from a clean, freshly pulled state. It removes almost every
fetch first rejection.- ✓Create branch
feature/kalman-tuningand commit something on it - ✓Push the branch to the share
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
.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
.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
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.
Reference
The commands and the error messages, in the order you meet them.
Daily loop
| Command | What it does |
|---|---|
git status | Where you are and what has changed |
git pull | Start of session: take what others pushed |
git add . | Stage everything changed |
git commit -m "..." | Record the staged snapshot |
git push | End of a piece of work: publish to the share |
git log --oneline --graph --all | See the shape of the history |
git diff / git diff --staged | Unstaged 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 pop | Park work in progress, get it back |
Local-remote specific
git init --bare S:\repos\x.git | Create the shared repository |
git remote add origin S:/repos/x.git | Point at it |
git remote set-url origin ... | Repoint after a move |
git push -u origin main | First push, remembers the pairing |
git clone --no-hardlinks src dst | Independent copy on the same volume |
git clone --mirror src dst | Full backup of a bare repo |
git bundle create f.bundle --all | History as a single portable file |
Error messages, decoded
| Message | Cause and fix |
|---|---|
does not appear to be a git repository | Path 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 branch | You pushed to a non-bare repo. Push to a bare one instead. |
detected dubious ownership | Repo on the share is owned by another account. Add it to safe.directory. |
Filename too long | 260-char limit. core.longpaths true, shallower folders. |
LF will be replaced by CRLF | Information, not an error. Line-ending conversion is doing its job. |
destination path already exists | The clone target folder is there and non-empty. Pick another name. |
Permission denied on push | NTFS rights on the share folder. You need Modify, not Read. |
unable to create file ... Invalid argument | A filename legal on Linux but not Windows (:, ?, *, trailing dot). Rename it in a repo that can. |