Trading-systems ops · git fundamentals

The limbo state

Why your commit didn't go through.

Two machines, one shared file, one git pull that refuses to merge. By the end of this lesson you'll see exactly why — and never be confused by the same error again.

Setup: laptop + remote server
Symptom: pull blocked
Cause: uncommitted edit on remote
Fix: two commands
First: the basics

Git is a snapshot recorder, not a save button.

Most file-saving tools ("Save As", Dropbox, Google Drive) overwrite or version-by-timestamp. Git does something different: every time you commit, it takes a frozen snapshot of your project — and you can return to any snapshot, ever, indefinitely.

Dropbox / Save As

You save → it overwrites the previous version (or stores a copy with a timestamp). To go back, you hunt through timestamped folders.

# your folder over time
strategy_v1.py
strategy_v2.py
strategy_v2_FINAL.py
strategy_v2_FINAL_REAL.py
strategy_v2_FINAL_REAL_v3.py   ← we've all seen this

Each version is a complete copy. No connection between them. No story of *why* each version exists.

Git

One file: strategy.py. Each commit is a labelled snapshot with a hash, an author, and a message. Switch between any of them with one command.

# git log
7116485 ramp to 3 lots per tranche
50d88a3 extract shared lock-helper
bce1ab9 pin per-tranche sl_percent
4ba5fb5 delete unsafe MARKET helpers
6a8cd21 SENSEX exchange-prefix fix

Each line is a complete saved state. The message says why. The hash is a fingerprint.

Every other concept in this lesson builds on this one idea: a commit is a snapshot you can return to. Don't lose that picture.

Vocabulary 1 of 3

A repository is a folder under git's watch.

Any folder on your computer can be turned into a git repository. The folder still holds your code as normal — git just adds a hidden .git/ subdirectory that records every snapshot you've ever taken.

Mac · ~/Documents/ExecutionProject
$ ls -la
drwxr-xr-x  18 kapil  staff   576 Mar  3 14:00 .
drwxr-xr-x   8 kapil  staff   256 Mar  3 14:00 .git/           ← git's storage
-rw-r--r--   1 kapil  staff  1234 Mar  3 14:00 main.py
-rw-r--r--   1 kapil  staff   876 Mar  3 14:00 main_rsi_sniper.py
drwxr-xr-x   5 kapil  staff   160 Mar  3 14:00 config/
drwxr-xr-x   8 kapil  staff   256 Mar  3 14:00 core/

$ cat .git/HEAD
ref: refs/heads/main                                ← we're on the main branch
git init
turn this folder into a repo (creates .git/)
git clone
download an existing repo from GitHub (copies .git/ too)
rm -rf .git/
remove the repo and lose ALL history (your code files stay)

The ExecutionProject folder on your Mac and on Lightsail are both git repositories — both have their own .git/. They're two independent records of the same project, connected by pushing/pulling through GitHub.

Vocabulary 2 of 3

A commit is one snapshot, named by its hash.

Every commit gets a unique SHA-1 hash (40 hex characters; we usually show the first 7). It records: which files changed, what the new content is, who made the change, when, and a message describing why.

Mac · git show 7116485
commit 7116485e83c4148c4ba5fb56e83c4148c4ba5fb5      ← the hash (we shorten to 7 chars)
Author: Kapil <imkapilagar@gmail.com>             ← who
Date:   Tue Mar  3 14:08:23 +0530                  ← when (IST)

    config(rsi_sniper): ramp to 3 lots per tranche;     ← message: what + why
    pin live values in tests; note I1 deferral

diff --git a/config/strategies/rsi_sniper.py b/config/strategies/rsi_sniper.py
-    {"id": "T1", ..., "lots": 1, ...}                  ← what was there before
+    {"id": "T1", ..., "lots": 3, ...}                  ← what's there now
Commits are immutable — once made, the hash and content never change. (You can make new commits that undo old ones, but you can't edit history.)
Two different snapshots = two different hashes. Change one character in any file → entirely new hash.
A good commit message explains why, not what. The diff already shows what changed; the message has to add the context the diff can't.
Vocabulary 3 of 3

A remote is a copy of your repo, somewhere else.

Your Mac has its own copy of the repo. Lightsail has its own copy. GitHub has another copy. Each is independent. They stay in sync by pushing commits up to GitHub and pulling commits down from it. GitHub is the meeting place — the "remote" that everyone agrees is canonical.

💻 Mac
Your dev machine. Has its own .git/. You commit and push from here.
commits a, b, c, d
☁️ GitHub (remote)
The shared canonical copy. Everyone pulls from and pushes to here.
commits a, b, c, d
☁️ Lightsail
Production box. Has its own .git/. Pulls to get new code.
commits a, b, c, d

git push

Uploads commits from your local repo to the remote. Other machines can now git pull to get them.

# on Mac, after committing
git push origin main
# origin = nickname for GitHub
# main = the branch you're pushing

git pull

Downloads commits from the remote into your local repo. Plays them on top of your local history.

# on Lightsail, to get new code
git pull
# implicit: pull from "origin", branch "main"

When you run git pull on the server and it errors, you're trying to download commits from GitHub. The error usually isn't about the network — it's about a conflict on disk. (We'll see exactly which one in two slides.)

One last piece before the worked example

On every machine, code lives in four separate places.

This is the mental model that makes the limbo bug obvious. Three of these we've already met — the fourth is where the trap lives.

1. Working dir
The files you edit with nano/vim. Lives on disk.
your edits go here first
2. Staging area
New idea → a holding zone between your edits and your commits.
"I plan to commit this"
3. Local history
The commits you've made on this machine. git log shows these.
past commits
4. GitHub (remote)
The shared copy. Commits land here after git push.
pushed commits

The new concept here is the staging area (slot 2). Most students assume git commit takes your edits directly and saves them — but git actually has a two-step ritual: first git add (which moves into staging), then git commit (which moves from staging into history).

Why two steps? Because a single edit session might span many files; staging lets you pick exactly which ones go into a particular commit. We'll see this matter when we walk through the live case study next.

The puzzle

You edited the file. Why isn't your change "saved"?

On Lightsail you opened config/strategies/rsi_sniper.py, changed lots: 1 → 3, hit Ctrl+S, and walked away. A few days later you ran git pull to get new code from GitHub and saw this:

ubuntu@LIGHTSAIL-PROD ~/ExecutionProject
$ git pull
Updating cf26643..7116485
error: Your local changes to the following files would be overwritten by merge:
        config/strategies/rsi_sniper.py
Please commit your changes or stash them before you merge.
Aborting

The file is sitting on disk. The edits look fine when you cat the file. But git refuses to do anything with it. Why?

Mental model · zoom in

The same four areas, each one explained.

We previewed these three slides ago. Here's each one in detail — what it is, where it lives, what moves things in and out. A single file can exist in any subset of these at any time, and a lot of confusion comes from not knowing which subset it's in right now.

1. Working directory
The files you see when you ls and edit with nano/vim. Lives on disk. Changes here are not tracked by git until you tell it.
rsi_sniper.py
2. Staging area
A holding zone. Files you've told git "I want this in the next commit" via git add. Not yet saved to history.
(empty)
3. Local history
Permanent snapshots. Each git commit appends one. This is what git log shows. Lives in .git/ on your machine.
(empty)
4. GitHub (remote)
A copy on someone else's server. Reachable only after git push. Other machines pull from here.
(empty)
git add
git commit
git push

Each verb moves your file across exactly one boundary. None of them happen automatically. Git doesn't watch the disk.

Interactive · follow the file

Watch a single edit travel through all four areas.

1. Working dir
On disk. Editor changes show up here first.
2. Staging
"I want this in my next commit."
3. Local history
Permanent snapshot.
4. GitHub
Remote, sharable.
git add
git commit
git push
Try it →
Start here. Click "1. edit file" to simulate opening rsi_sniper.py in nano and changing lots: 1 → 3. Watch where the change lands.
The trap

What if you stop after step 1?

You're on Lightsail at 14:30 IST. Market's moving. You nano the file, fix the lot count, save, and run. The strategy works. No error, no warning. The file does what it says.

1. Working dir
Your edit lives here.
rsi_sniper.py LIMBO
2. Staging
Empty. git add never ran.
(empty)
3. Local history
Empty for this edit. No commit exists.
(empty)
4. GitHub
Doesn't know your edit exists.
(empty)

The file works. The strategy reads it. The trades fire correctly. But the edit only exists in one of git's four places — and it's the volatile one. Git history doesn't know. GitHub doesn't know. Your Mac doesn't know.

Days later · the collision

Now someone pushes to GitHub. You go to pull.

💻 Mac

Edits → commit → push (disciplined)
Pushed BFO → BSE_FO fix to GitHub
Working tree clean

All four areas hold the same content. Nothing in limbo.

☁️ Lightsail

Edited rsi_sniper.py 5 days ago
Never ran git add or git commit
Working dir ≠ local history

One area (working dir) holds an edit the other three don't know about.

git pull on Lightsail tries to update the same file from GitHub. Mac's commit changed line 37. Your uncommitted edit changed line 55. Git sees the collision before it tries to merge — and refuses, because applying the pull would silently overwrite your uncommitted work.

This is a feature, not a bug. Git is protecting you. The error message is git saying: "I won't lose your work without you telling me what to do with it."

The error · word by word

Now the message makes sense.

ubuntu@LIGHTSAIL-PROD ~/ExecutionProject
$ git pull
Updating cf26643..7116485            # GitHub has new commits. Git wants to apply them.
error: Your local changes to the following files would be overwritten by merge:
        config/strategies/rsi_sniper.py     # ← The limbo file. Edit in working dir only.
Please commit your changes or stash them before you merge.
Aborting                              # Nothing changes. Pull is rolled back. You're at the old state.
0
commits actually pulled in
10
commits still waiting on GitHub
1
file in limbo, blocking everything

The fix is to resolve the limbo. Either commit the edit (it becomes a real change you can keep), or discard it (admit it was a mistake / test / overtaken by main). After that, the pull goes through.

Recovery · pick one

Resolve the limbo, then pull.

Path A · Keep the edit

If the local edit was real work (e.g. you really did want lots=3 there), commit it first. The pull will then merge cleanly (or conflict if main also touched the same lines — manageable).

# on Lightsail
git add config/strategies/rsi_sniper.py
git commit -m "ops: bump lots to 3"
git pull   # may auto-merge
git push   # back to GitHub

Path B · Discard the edit

If the local edit was a test / wrong / superseded by main's incoming commit, throw it away. Pull then runs cleanly with zero conflict.

# on Lightsail
git diff config/strategies/rsi_sniper.py
       # READ what you're about to lose
git checkout -- config/strategies/rsi_sniper.py
       # DISCARD — the edit is gone
git pull

Never run Path B without Path A's git diff first. Discarding is irreversible — git doesn't save the edit anywhere before deleting it.

The trap, step by step

A worked example: how the four places fall out of sync.

1

The laptop ships code · main advances

You commit a real change to strategy.py on your laptop and push it to GitHub. The remote is now ahead of the server.

2

The server has a limbo edit · uncommitted tweak

At some point, someone SSH'd in and edited strategy.py directly on the server — a quick parameter bump, never committed. It just sits there in the working dir.

3

Pull blocked · error: would be overwritten

You run git pull on the server. Git refuses: the incoming version of strategy.py collides with the uncommitted edit. Git won't silently throw your edit away, so it stops.

4

Recovery · decide, then act

Path A if the local edit matters: git diff, then commit it, then pull (git merges).
Path B if the local edit is junk: git diff to confirm, then git checkout -- <file>, then git pull. Either way: two or three commands, working tree clean, server back in sync.

How to never see this again

One rule: no edit rests in limbo.

Every edit on every machine goes forward (commit + push) or backward (discard). Nothing in between. This is the entire fix.

After every edit on Lightsail

# save it forward
git add <file>
git commit -m "ops: what + why"
git push origin main

Now the edit lives in all four places. Mac can pull it. Future you can read the commit message and remember why.

If the edit was a test / mistake

# confirm what you're throwing away
git diff <file>
# then nuke it
git checkout -- <file>

Working tree clean again. Free to pull, switch branches, do whatever. No phantom edits hanging around.

A clean git status is the goal state. Aim for it after every edit session.
If git status shows modified:, you have a decision to make right now, not later.
Edits made under time pressure are exactly the edits that get forgotten. So have the discipline before the pressure, not during.
The other way pull fails

The file git never knew about.

Last time the limbo was an edit to a tracked file. There's a second flavour, and it's just as common: a whole new file you created on a machine but never git added. Git treats it as a stranger — not a missing commit, but an unknown object sitting in the folder.

💻 Mac

Created scripts/pnl_total.py directly
git status shows ?? scripts/pnl_total.py
Never ran git add

☁️ Lightsail

Same file copied over via scp weeks ago
git status shows ?? scripts/pnl_total.py
Also never added

Both machines have a working file. Both are running it. Neither has told git the file exists. The two copies are independent strangers that happen to share a name — git on Mac has no idea Lightsail's file is "the same" file, and vice versa.

The ?? prefix in git status is git's way of saying: "I see this thing in the folder, but it's not under my care. I won't track it, won't push it, won't pull it. Until you say otherwise, it's invisible to my history."

Mac commits · Lightsail tries to pull

Now git sees a name collision it can't resolve safely.

1

Mac: git add + commit + push

Now pnl_total.py exists in git history. The remote on GitHub has it. Mac's working dir and history agree. Clean.

2

Lightsail: git pull

Git fetches the new commit. It sees: "a brand-new file called scripts/pnl_total.py should land on the shelf, owned by me." It walks to the slot — and finds a stranger's file already sitting there.

3

Git stops. Refuses to overwrite.

Git has no record of when the existing file appeared, what it contains, or whether you'd want it preserved. So it would rather block the pull than risk destroying unbacked-up work.

ubuntu@LIGHTSAIL-PROD ~/ExecutionProject
$ git pull
error: The following untracked working tree files would be
overwritten by merge:
        scripts/pnl_total.py           # ← the stranger
Please move or remove them before you merge.
Aborting                              # pull rolled back, you're stuck

Note the wording: untracked working tree files would be overwritten. This is a different error from the tracked-file version (Your local changes ... would be overwritten by merge). Same protection principle, different cause.

Recovery · the safe move

Diff first. Delete second. Pull third.

Step 1 · Confirm the stranger is safe to discard

The two copies are almost certainly identical (you put both there). But "almost" isn't "is." Diff the local stranger against the incoming version before you delete anything.

# on Lightsail
git fetch origin
git diff origin/main -- scripts/pnl_total.py
        # shows what would CHANGE if you accepted the pull

No output = files are byte-identical. Some output = the remote has changes you'd be picking up (which is fine, that's why you're pulling).

Step 2 · Remove the stranger, then pull

Once you've confirmed the diff is harmless (or actively wanted), delete the untracked copy. The slot becomes empty, git happily writes the new tracked version, and from this moment forward the file is under git's care everywhere.

# on Lightsail
rm scripts/pnl_total.py
git pull
        # now resolves cleanly

Future git pulls for this file will just work — no ??, no collision. The one-time dance is over.

Never rm an untracked file without diffing first. Untracked means git has zero copies — nothing to recover from if you were wrong.
The real fix is preventative: never let a file sit untracked across multiple machines. The moment you create a file you want to keep, git add + commit + push it from one machine. The other machines pick it up via pull from then on.
Lesson · takeaway

Saving the file is not
saving the change.

Git's job is to track history, not to watch your disk. An edit on disk is just one of four places it has to be to be safely recorded. The other three only happen when you tell git so. The "limbo state" is when you skip steps 2-3-4 and walk away — and it's always free in the moment, but it always costs you later.

There are two flavours of limbo, and both end in the same error: git pull aborts. One is an edit to a tracked file. The other is a whole new file that was never tracked. Same protection principle: git won't destroy work it can't recover.

Trading systems run on two or three machines. Multiply this trap by the machine count. Every machine you SSH into is another candidate for limbo. The fix is the same on each: edit → add → commit → push, every single time.

Next lesson → Branching: parallel lines of work in the same repo

← → navigate · F fullscreen · click to advance
1 / 16