Part of the Salesforce Admin Git & sf CLI series. Articles are standalone; read in any order. Foundation: Why Version Control · Environment Setup · Connect Your Org
TL;DR
- Six commands cover 90% of daily git use:
git init,git status,git add,git commit,git log,git diff git diffis the most powerful command for Salesforce work: it shows you exactly which lines changed inside a Flow, Object, or Profile XML file- The workflow is always the same: retrieve metadata from your org → stage with
git add→ save a snapshot withgit commit→ compare versions withgit diff - You don't need GitHub or a remote repository to start: git works entirely on your local machine
What You'll Learn
- What each of the six core git commands does and when to use it
- How to initialise a git repository in your Salesforce project folder
- How to stage and commit retrieved metadata
- How to use
git diffto see exactly what changed inside a Flow XML file - A complete eight-step walkthrough: retrieve a Flow, commit it, update it in the org, retrieve again, diff the changes
The Problem
You've retrieved some metadata from your Salesforce org. It's sitting in a folder on your machine. Now what?
Without git, those files are just files. They have no history, no comparison capability, no rollback. If your Flow XML file changes next week, you won't know what it looked like this week. You can't ask "what line changed?" because there's nothing to compare against.
Git solves this by turning your folder into a version-controlled repository. Every time you commit, git takes a snapshot of all your files. Every subsequent snapshot can be compared against any previous one. You get a full history of every change: what file changed, which lines changed, and a message you wrote explaining why.
This article is hands-on. By the end you will have run all six core git commands against a real Salesforce Flow file.
Common Questions This Article Answers:
- What git commands do I actually need to know?
- What does
git initdo? - What is
git statusshowing me? - What is
git diffand why does it matter for Salesforce metadata? - How do I save a snapshot of my metadata?
- How do I see the history of my changes?
Quick Answer
Here are the six commands and what each one does. The sections below explain each one in detail.
# Start tracking a folder with git
git init
# See which files have changed since your last commit
git status
# See exactly what changed inside a file (line by line)
git diff
# Stage a file — tell git you want to include it in the next snapshot
git add force-app/main/default/flows/My_Flow.flow-meta.xml
# Save a snapshot (commit) with a message describing what changed
git commit -m "feat: update My_Flow to add email step"
# See the history of all your commits
git log --oneline
These six commands: init, status, add, commit, log, diff cover the vast majority of day-to-day git work for a Salesforce admin. Everything else is built on top of them.
What Is git init?
git init turns an ordinary folder into a git repository.
When you run it, git creates a hidden .git folder inside your project directory. That folder contains the entire history of your repository: every commit, every snapshot, every file that was ever staged. You never need to touch .git directly; git manages it for you.
git init
You only run this once per project. If you cloned or downloaded a repository that already exists, .git is already there and you don't need to run git init.
What it looks like:
Initialized empty Git repository in /Users/yourname/my-sf-project/.git/
After running git init, git is watching your folder, but it hasn't recorded anything yet. All your files are "untracked": git sees them but has made no promises to remember them.
A typical Salesforce project structure after initialising git looks like this:
my-sf-project/
├── .git/ ← git's internal database — don't touch
├── .gitignore ← tells git what to ignore
├── force-app/
│ └── main/
│ └── default/
│ └── flows/
│ └── My_Flow.flow-meta.xml
└── sfdx-project.json
git status: What's Changed?
git status tells you the current state of your working directory relative to the last commit (snapshot).
git status
It answers three questions at once:
- Are there changes git is tracking that haven't been committed yet?
- Are there new files git doesn't know about yet?
- Is there anything staged and ready to commit?
Reading the output:
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: force-app/main/default/flows/My_Flow.flow-meta.xml
Untracked files:
(use "git add <file>..." to include in what will be committed)
force-app/main/default/flows/New_Flow.flow-meta.xml
- "modified": git is tracking this file and it has changed since the last commit
- "untracked": git has never seen this file before; you'd need to
git addit to start tracking it - "nothing to commit, working tree clean": everything is committed; no changes since the last snapshot
git status is safe to run at any time. It makes no changes to anything. Run it constantly, before and after every operation.
git diff: See Exactly What Changed
git diff is the most powerful command for Salesforce metadata work. It shows you the exact lines that changed inside a file, not just that a file changed.
# See all uncommitted changes across your entire project
git diff
# See changes in one specific file
git diff force-app/main/default/flows/My_Flow.flow-meta.xml
Reading the output:
Lines starting with - (in red) were removed. Lines starting with + (in green) were added. Lines with neither were unchanged and are shown for context.
--- a/force-app/main/default/flows/My_Flow.flow-meta.xml
+++ b/force-app/main/default/flows/My_Flow.flow-meta.xml
@@ -42,6 +42,14 @@
<decisions>
<name>Check_Account_Type</name>
</decisions>
+ <actionCalls>
+ <name>Send_Email_Notification</name>
+ <actionName>emailSimple</actionName>
+ <actionType>emailSimple</actionType>
+ <inputParameters>
+ <name>emailBody</name>
+ </inputParameters>
+ </actionCalls>
This tells you precisely: a new actionCalls block was added to the Flow after line 44. The email notification step was added. You can see this even six months later when you're trying to remember what changed.
For Salesforce admins, git diff is where version control delivers its most immediate value. The Flow UI in Setup doesn't show you this. Your deployment logs don't show you this. Git does.
Comparing two specific commits:
# Compare what changed between two commits (use hashes from git log)
git diff abc1234 def5678 force-app/main/default/flows/My_Flow.flow-meta.xml
git add: Stage Your Changes
git add tells git which changed files you want to include in the next commit (snapshot).
This step exists because you sometimes change multiple files but only want to commit some of them. Staging gives you precise control.
# Stage a single specific file
git add force-app/main/default/flows/My_Flow.flow-meta.xml
# Stage an entire folder (all changed files inside it)
git add force-app/main/default/flows/
# Stage everything that has changed
git add .
After staging, run git status again. Staged files appear under "Changes to be committed" (in green) rather than "Changes not staged for commit" (in red).
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: force-app/main/default/flows/My_Flow.flow-meta.xml
Nothing has been saved to history yet. git add only prepares the snapshot. git commit actually saves it.
If you staged something by mistake:
# Remove a file from staging without losing your changes
git restore --staged force-app/main/default/flows/My_Flow.flow-meta.xml
git commit: Save a Snapshot
git commit takes everything you've staged and saves it as a permanent snapshot in your repository's history.
git commit -m "feat: update My_Flow to add email step"
The -m flag lets you write the commit message inline. Without -m, git opens a text editor and waits for you to type a message there.
The commit message matters. It's the note you leave your future self explaining why this change was made. A good commit message is specific:
# Good — describes what changed and why
git commit -m "feat: add email notification step to My_Flow"
git commit -m "fix: correct Send_Welcome_Email recipient field"
git commit -m "chore: baseline all Flows from production as of 2026-05-22"
# Not useful — no information about what changed
git commit -m "update"
git commit -m "changes"
git commit -m "stuff"
A common convention is to prefix messages with a type:
feat:: a new feature or additionfix:: a bug fixchore:: maintenance, baseline snapshots, administrative tasksrefactor:: restructuring without behaviour change
After committing, git status will show "nothing to commit, working tree clean."
git log: Your History
git log shows the history of all commits in the current repository.
# Full log — shows author, date, full commit message for each commit
git log
# Compact one-line format — easiest for scanning history
git log --oneline
# Show what files changed in each commit
git log --stat
Sample git log --oneline output:
a3f8c12 feat: add email notification step to My_Flow
d14b9e1 fix: correct recipient field in Send_Welcome_Email flow
8c2f004 chore: retrieve Account object baseline from sandbox
3ab71cc chore: initial project setup
Each line shows a short hash (the unique identifier for that commit) followed by the commit message.
To see the full diff of what changed in a specific commit:
# Show full details and diff for the most recent commit
git show HEAD
# Show full details and diff for a specific commit by hash
git show a3f8c12
Practical Walkthrough: Retrieve a Flow, Commit It, Change It, See the Diff
This walkthrough uses a real Salesforce Flow to demonstrate the complete git workflow from scratch. You'll need sf CLI connected to a sandbox. See Connect Your Org if you haven't done that yet.
Substitute My_Flow with the API name of any Flow in your org, and mysandbox with your org alias.
Step 1: Retrieve your Flow from the org
sf project retrieve start --metadata "Flow:My_Flow" --target-org mysandbox
This pulls the current version of My_Flow from your sandbox and writes it to force-app/main/default/flows/My_Flow.flow-meta.xml. The --metadata flag specifies the type and name. The --target-org flag specifies which connected org to retrieve from.
Step 2: Check what was retrieved
git status
You should see the Flow file listed under "Untracked files" (if this is the first time you've retrieved it) or "Changes not staged for commit" (if you've committed it before).
Step 3: Stage and commit the baseline
git add force-app/main/default/flows/
git commit -m "chore: baseline My_Flow from sandbox"
This saves a snapshot of your Flow as it currently exists in the org. This is your baseline: the "before" that every future diff will compare against.
After this commit, git status shows "nothing to commit, working tree clean."
Step 4: Make a change to the Flow in Setup
Go to your sandbox, open Setup, find your Flow, and add a step. For example, add an email notification action or a new decision element. Save and activate the Flow.
You haven't changed any files on your machine yet. The change only exists in the org.
Step 5: Retrieve the updated Flow
sf project retrieve start --metadata "Flow:My_Flow" --target-org mysandbox
sf CLI overwrites your local file with the new version from the org. Your disk now has the updated XML.
Step 6: See exactly what changed
git diff force-app/main/default/flows/My_Flow.flow-meta.xml
Git compares the file on disk against the committed baseline. You'll see the exact XML lines that were added or removed when you made your change in Setup. Lines starting with + are new; lines starting with - were removed.
This is the moment version control pays off. You can see precisely what the Flow change consisted of: not just "the Flow changed," but exactly which XML blocks were added, modified, or removed.
Step 7: Commit the change
git add force-app/main/default/flows/My_Flow.flow-meta.xml
git commit -m "feat: add email notification step to My_Flow"
Step 8: See your history
git log --oneline
You'll see two commits:
a3f8c12 feat: add email notification step to My_Flow
8c2f004 chore: baseline My_Flow from sandbox
You now have a permanent, auditable record of exactly what changed, when, and why. That record will be there in six months when someone asks "what did we change in that Flow back in May?"
Troubleshooting
"fatal: not a git repository"
You ran a git command outside a git repository. Either you haven't run git init yet, or you're in the wrong directory. Run pwd to check where you are, navigate to your project root, and make sure .git/ exists there.
# Check you're in the right place
pwd
ls -la | grep .git
If .git/ is missing, run git init to initialise the repository.
"nothing to commit, working tree clean"
This message is not an error. It means all your current files match the last committed snapshot. There are no staged changes and no unstaged changes. This is the expected state after a clean commit.
If you expected to see changes, check that you've retrieved the latest metadata from your org (sf project retrieve start) and that you're looking in the right directory.
"Changes not staged for commit" after git add
You staged the file but then changed it again after staging. Run git add again to re-stage the latest version, then commit.
Commit message editor opens unexpectedly
You ran git commit without the -m flag. An editor (usually vim or nano) has opened waiting for your message. In vim, press i to enter insert mode, type your message, then press Escape, type :wq, and press Enter. To avoid this, always use git commit -m "your message".
Frequently Asked Questions
Q: Do I need GitHub to use git?
A: No. Git is local software that runs entirely on your machine. GitHub is a website that hosts git repositories online for sharing and backup. It's optional. Everything in this article works with no GitHub account and no internet connection. You get the full benefit of version history, diffs, and rollback without ever touching GitHub.
Q: What's the difference between git add and git commit?
A: git add stages changes: it marks which files you want to include in the next snapshot. git commit actually saves the snapshot. Think of it as packing a box (git add) and then sealing and labelling it (git commit). You can stage multiple files across multiple git add calls, then commit them all together in one snapshot.
Q: Can I undo a commit?
A: Yes. For the most recent commit, git revert HEAD creates a new commit that undoes all the changes from the last commit, without deleting history. For earlier commits, git revert <hash> targets a specific one. Avoid git reset --hard until you understand it. It permanently discards committed history and can cause data loss.
Q: How often should I commit?
A: Commit after each logical unit of work: retrieved a set of metadata, made a specific change, fixed a specific issue. For Salesforce admin work, a practical rhythm is: retrieve a metadata type, commit the baseline, then commit each meaningful change separately. Frequent, well-labelled commits make your history far more useful than infrequent bulk commits with vague messages.
Q: What should I put in a .gitignore for Salesforce projects?
A: At minimum, ignore .sfdx/ (sf CLI's local cache), .sf/ (newer sf CLI config), node_modules/ if you have any, and any .env files. Many teams also ignore *.lock files. The Salesforce Extension Pack for VS Code generates a default .gitignore when it creates a project. It's a good starting point.
Key Takeaways
git initstarts tracking a folder: run it once per projectgit statusshows you what's changed: run it constantly, it's safe and informativegit diffshows the exact line-level changes inside files: this is where version control delivers real value for metadata workgit addstages the files you want in the next snapshotgit commit -m "message"saves the snapshot permanently with a descriptive labelgit log --onelinegives you a scannable history of every change you've committed- The workflow is always: retrieve →
git status→git diff→git add→git commit - You don't need GitHub, a remote server, or any internet connection to use any of these commands
What's Next?
Now that you can track changes with git, the next step is sending those changes back to Salesforce.
- Article 11: Deploying Salesforce Metadata with sf CLI: how to deploy your local metadata files back to a sandbox or production org, run check-only validations, and read deploy results
- Article 12: Org-Dependent Packages: how to manage metadata that has dependencies on other packages in your org
Resources & References
- Git documentation: git-scm.com: the official git reference, including the free Pro Git book
- Salesforce DX Developer Guide: Salesforce Developers: Salesforce's own guide to source-driven development
- sf CLI Command Reference: project retrieve start: full flag reference for the retrieve command
- Connect Your Org: how to authenticate sf CLI to your sandbox before running retrieve commands