Version control & git workflow
Git is your undo system, not a chore you do at the end of the day. On a CodeCanyon project you are layering custom work on top of someone else’s code that keeps shipping updates — so the way you commit, branch, and capture server changes decides whether the next vendor update is a five-minute reconcile or a weekend of pain. This guide teaches the mental model. The playbooks are where you actually run the commands.
Why this matters
Section titled “Why this matters”Three forces make version control non-optional here:
- You move fast and break things — a granular history means a mistake costs one step, not three days.
- The vendor keeps updating — clean, separated commits let git replay your customizations onto a new vendor release instead of dumping 800 files into one merge conflict.
- The server changes behind your back — installers, admin-panel edits, and uploads create files that only exist on the server, and they need a way back into git.
Get these three right and the project stays recoverable, auditable, and calm.
The breadcrumb rule — more, smaller commits
Section titled “The breadcrumb rule — more, smaller commits”The single most important habit for solo work: drop a commit every time something works. Think of commits as breadcrumbs in a forest. Small breadcrumbs every ten steps mean you can always walk back to the last working state. One giant boulder per mile means getting lost costs you the whole mile.
flowchart TD A["Make a change"] --> B{"Does it work,<br/>even partially?"} B -->|"Yes"| C["Commit it 🥖"] B -->|"No"| D["Keep working"] D --> E{"Broke it?"} E -->|"Yes"| F["git restore .<br/>(back to last commit)"] E -->|"No"| D C --> A F --> AThis is the opposite of “wait until it’s done.” Each commit is a save point you can return to, so breaking the next thing never costs you the last working thing.
Small commits win on every axis that matters to a solo developer:
| You need to… | With large commits | With small commits |
|---|---|---|
| Find a bug | Search 2,000 lines | Search 10 lines |
| Undo a mistake | Lose days of work | Lose one step |
| Remember what you did | ”What was in that commit?” | Clear, searchable history |
| Merge a vendor update | Massive conflicts | Isolated, manageable conflicts |
The 3 golden rules
Section titled “The 3 golden rules”- One idea = one commit. Never mix “fix login” with “add dark mode”. If dark mode looks bad, you revert that one commit and the login fix survives.
- Commit when it works, even partially. Migration runs? Commit. Page loads without error? Commit. You can always
git restore .back to the last green state. - Write searchable messages. In six months you will search
git logforstripe— so writeAdd: Stripe payment integration, neverwip,fix, orupdates.
Local vs remote — the two-stage safety net
Section titled “Local vs remote — the two-stage safety net”Git gives you two levels of saving, and they have different rules. Commit locally as messily and as often as you like — nobody sees it and you have full undo power. Push to remote only when you’re confident, because once history is shared it’s much harder to rewrite.
flowchart LR subgraph local["Local — your machine"] direction TB L1["Commit constantly"] L2["Messy is fine"] L3["Experiment freely"] L4["Full undo power"] end subgraph remote["Remote — GitHub / server"] direction TB R1["Cloud backup"] R2["Shared history"] R3["Harder to undo"] end local -->|"only when confident"| remoteThe pattern for a normal day: git pull in the morning, commit locally all day without pushing, then push everything at the end of the day or at each milestone. The decision of when to push is simple:
| Situation | Push? |
|---|---|
| Feature works and is tested | Yes |
| End of session (backup) | Yes |
| Before a risky operation | Yes |
| Experiment that might fail | No — keep it local |
| Messy “WIP” commits | No — keep it local |
Because local commits can be rewritten or deleted freely, you can clean up a messy local history before pushing if you ever need to (squash the noise into one clean commit). After pushing, treat history as shared and don’t rewrite it.
Branches — when to make one, and the vendor-safe model
Section titled “Branches — when to make one, and the vendor-safe model”For everyday solo work, a quick one-commit fix can go straight to your working branch. Branch when the work is bigger or riskier, so an experiment that fails can be thrown away without touching your main line.
| Work size | Branch? | Name |
|---|---|---|
| 1-commit fix | No, direct to working branch | — |
| 2–3 commit fix | Optional | fix/login-bug |
| New feature (4+ commits) | Yes | feature/dark-mode |
| Experiment that might fail | Yes | try/new-layout |
| Emergency production fix | Yes | hotfix/critical-bug |
That’s the task branch question. CodeCanyon projects add a second, more important layer: a long-lived branch model that protects the vendor baseline. The whole point is to keep a pristine copy of exactly what the vendor shipped, so a future update is a clean three-way diff against it.
flowchart TD author["author/* — frozen pristine vendor<br/>(never commit work here)"] develop["develop — your day-to-day work"] staging["staging — pre-prod QA"] production["production — live source of truth"] author -.->|"diff against"| develop develop -->|"PR"| staging staging -->|"PR"| productionThe contract:
author/*holds the untouched vendor release, one branch per version. It is sacred — never land custom work on it. It exists so “what did we change from the vendor?” is a clean diff.developis where you work. Reconcile, don’t clobber, when others are pushing too.stagingthenproductionare reached by promotion through pull requests, never an ad-hoc deploy.
No force-pushing to any of these shared branches, ever. The branch model is what makes a vendor update calm — see the vendor-updates playbook for the actual replay-your-customizations procedure.
Commit strategy by project type
Section titled “Commit strategy by project type”The breadcrumb rule is universal, but the flavor changes with what you’re building.
| Project type | Strategy | Key habit |
|---|---|---|
| Greenfield (from scratch) | Maximum frequency | Treat git like Ctrl+S; commit everything that works, don’t squash early |
| Legacy (inherited code) | Cautious atomic | Tag a backup before touching anything; one change per commit; explain why |
| Marketplace / CodeCanyon | Distinct customization commits | Tag every vendor version; keep each customization type in its own commit |
| Reusable module / package | Component-based | One component per commit, so you can cherry-pick or revert just that part |
The marketplace flavor is the one that matters most here, and it’s why small distinct commits pay off so heavily at upgrade time:
flowchart TD subgraph good["Small distinct commits"] G0["Vendor: Import v2.5.1 (tagged)"] --> G1["Brand: Add logo"] G1 --> G2["Config: Analytics"] G2 --> G3["Style: Custom colors"] G3 --> G4["Feature: Arabic language"] end good -->|"vendor v2.6.0 released"| goodResult["Git replays each change<br/>— conflicts isolated to one file"]
subgraph bad["One big commit"] B0["All my customizations<br/>(800 files)"] end bad -->|"vendor v2.6.0 released"| badResult["Massive conflict<br/>— redo everything by hand"]For a CodeCanyon project specifically: tag every vendor import (vendor-v2.5.1), keep each kind of customization in its own commit, and never modify vendor core files when an override seam will do. Your commit list literally is the list of what you changed — which is exactly what you need before a vendor update. The decision rule for “override vs edit vendor source” is taught in the customizations playbook.
The daily loop and recovery moves
Section titled “The daily loop and recovery moves”A normal session is a short, repeatable cycle. Pull at the start, commit small all the way through, push at the end.
- Start —
git pullon your working branch; confirm a clean state. - Work — make a change,
git add,git commitwith a clear message. Repeat every time something works. - Recover if needed — if you break something, use the right undo move (below) instead of piling more changes on a broken state.
- Finish —
git pusheverything for the day, optionally tag a milestone.
When something breaks, reach for the smallest move that fixes it:
| Goal | Command | Notes |
|---|---|---|
| Undo uncommitted changes | git restore . | Back to the last commit |
| Undo the last commit, keep files | git reset --soft HEAD~1 | The changes stay staged |
| Undo a specific older commit | git revert <hash> | Safe after pushing — adds a new commit |
| Find when a bug appeared | git bisect | Binary-search the history |
| Search history by message | git log --oneline --grep="login" | Why searchable messages matter |
A visual git client like GitKraken makes all of this less abstract: the timeline shows every branch as a graph, merge conflicts become a point-and-click three-way diff (base / yours / theirs), and there’s a literal undo button. It’s optional — everything here works from the command line — but for someone who finds the CLI cryptic, seeing three months of branch history on one screen turns “terrifying” vendor merges into something readable.
The reverse-capture loop — getting server changes back into git
Section titled “The reverse-capture loop — getting server changes back into git”Here’s the CodeCanyon-specific twist that trips people up. When you deploy and run the web installer (or a super-admin edits settings in the admin panel, or users upload files), things change on the server that don’t exist in your repo:
- the installer writes a
storage/installedmarker; - config rows get written into the database;
- uploads land in
storage/uploads.
How do those get back into git? A reverse git capture workflow — a GitHub Action that SSHes into the server, compares the deployed files against your branch, and opens a pull request with the differences for you to review and merge.
flowchart TD T["Trigger workflow<br/>(manual or scheduled)"] --> S["SSH to server,<br/>rsync deployed files"] S --> C["Compare against<br/>the branch on GitHub"] C --> B["Create capture branch<br/>+ commit the diff"] B --> P["Open a Pull Request"] P --> R{"Review:<br/>no secrets?<br/>expected files?"} R -->|"Looks good"| M["Squash & merge"] R -->|"Wrong files"| F["Fix excludes / .gitignore<br/>+ re-run"]Two facts about this loop save hours of confusion:
- It compares the server against the branch on GitHub — not your local checkout. So local uncommitted work doesn’t matter to the capture, but if you push a new file to GitHub that isn’t on the server yet, the capture will read that as a deletion. The safe habit: run the capture right after a deploy, before you push divergent commits.
- The workflow’s exclude list and the deploy’s
clear_pathsmust match exactly. Files that live only in git (your admin/docs folders,deploy.php) are protected from deletion by aGIT_ONLY_PATHSlist; if it drifts from what the deploy clears, the capture can silently delete real files. Always review every deleted file in the PR before merging, and never auto-merge a capture PR.
You run this loop from the server-sync runbook and the continuous-deploy → server sync phase — and because a capture PR can carry credentials or installer secrets, the review step is a security gate, not a rubber stamp.
Where to execute this
Section titled “Where to execute this”This guide is the mental model. When you’re ready to do the work, the commands live in the playbooks and handbook: