When working on a software project, it’s important to know how to manage your changes effectively. Git provides a structured way to do this through the commands git add
, git commit
, and git push
. Each command has a distinct role in the process of taking your local edits and ensuring they become part of your codebase’s official history.
Git manages a directed acyclic graph of snapshots. Every commit
represents a snapshot of your project’s files at a certain point in time. Before something becomes a commit, it first resides in something known as the staging area. The git add
command moves changes from your working directory (the place where you edit files) into this staging area (an index, or record of what’s about to be committed). Then git commit
converts whatever is staged into a new snapshot. Finally, git push
transfers those snapshots from your local repository to a remote repository, making them visible to your team.
In other words, think of git add
as preparing your changes, git commit
as finalizing them into a checkpoint in your project’s history, and git push
as publishing that checkpoint for others to pull.
How git add
works
git add
takes your modified files and places them into the staging area, also known simply as “the index.” This staging area is an intermediate state. Files remain staged until you commit them. Without this step, Git won’t know which changes you intend to include in your next commit.
For example, say you’ve edited a file named app.js
. When you run:
git add app.js
Git scans and records the changes you’ve made to app.js
in its staging area. If you want to stage multiple files at once, you can run:
git add .
This command stages all changed files, excluding those ignored by .gitignore
. Internally, Git is creating a list of changes (a record of new content and removed lines) that will be included in the next commit. Think of it as organizing the papers on your desk before placing them into a binder. The staging area ensures you have control over what goes into each snapshot, allowing you to split your changes into meaningful chunks and maintain a clean commit history.
How git commit
finalizes changes
After staging your changes with git add
, the next step is git commit
. Running git commit
transforms what’s in the staging area into an immutable snapshot stored within Git’s repository history. Each commit is given a unique hash (a cryptographic checksum) to identify it. This hash is basically an ID tied to that exact version of your files.
A typical git commit
command looks like this:
git commit -m "Add new feature to handle user input"
The -m
flag allows you to include a message directly in the command. Commits should always have a descriptive message that explains what changes were made and why. This is important for future reference, especially when you or your teammates need to understand how the code has evolved over time. Under the hood, when you run git commit
, Git:
- Captures the current state of the staged files
- Links this snapshot to the previous commit, forming a chain (the project’s history)
- Saves a reference (a pointer) to this commit in the repository
When you look at your repository’s commit history later using git log
, you’ll see these messages along with commit hashes and authorship details.
How git push
shares changes with others
Up until now, all changes you’ve made have been local. Even if you create multiple commits, those commits live only on your machine, inside your local Git repository. To share these changes with others, you use git push
.
git push
sends your local commits to a remote repository (often hosted on platforms like GitHub, GitLab, or Bitbucket). A typical command is:
git push origin main
Here, origin
is the default name for the remote repository you cloned from, and main
is the branch you’re pushing to. When you run this command, Git compares your local branch’s commits to the remote’s commits and uploads any commits that are missing on the remote side.
Under the hood, git push
communicates with the remote repository through the Git protocol, SSH, or HTTPS. It transfers your commits, along with any objects (such as file changes and directory snapshots) that the remote repository doesn’t have yet. Once complete, your new commits appear on the remote repository’s history. This means your teammates can now git pull
those changes into their own local environments and continue from where you left off.
Example workflow
Let’s say you are working on a new feature. You’ve cloned a repository from GitHub and made some changes to app.js
and styles.css
. Here’s what you might do:
Check your current status
Terminalgit statusGit will show you which files are modified and which are untracked. For example:
TerminalOn branch mainYour branch is up to date with 'origin/main'.Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git restore <file>..." to discard changes in working directory)modified: app.jsmodified: styles.cssStage your changes
Terminalgit add app.js styles.cssNow these files are in the staging area. Running
git status
again will show:TerminalChanges to be committed:(use "git restore --staged <file>..." to unstage)modified: app.jsmodified: styles.cssCommit your changes
Terminalgit commit -m "Improve UI and add input validation in app.js"Now a snapshot of these changes is recorded. Running
git log
will show your new commit at the top:Terminalcommit d4c3f2a (HEAD -> main)Author: Your Name <your.email@example.com>Date: Wed Dec 11 14:28:23 2024 -0500Improve UI and add input validation in app.jsPush your changes
Terminalgit push origin mainThis sends your commits to the
main
branch on the remote repository. After pushing, teammates can pull these changes and benefit from your latest updates.
Best practices for managing code changes
- Commit frequently: Commit small units of work often. This makes it easier to understand what changed and why.
- Write descriptive commit messages: Good commit messages make it much easier to navigate project history and troubleshoot issues.
- Use feature branches: Instead of working directly on
main
, create separate branches for new features, then merge them back when ready. - Check your status often: Use
git status
to see what’s changed and what’s staged. This helps you stay organized and ensures you don’t commit unintended files.
Using the Graphite CLI for streamlined Git workflows
The Graphite CLI enhances Git workflows by simplifying commands and introducing pull request (PR) stacking.
Simplified Git commands
Graphite CLI reduces complexity in tasks like staging, committing, and pushing changes. For example, this single command stages all files, creates a commit, and pushes a branch:
gt create --all --message "feat: Implement new API"
Submit it as a pull request with:
gt submit
Pull request stacking
PR stacking lets you break large tasks into manageable layers. Start with a base PR:
gt create --all --message "feat(api): Add base endpoint"gt submit
Then stack a dependent PR:
gt create --all --message "feat(ui): Add UI for feature"gt submit --stack
If a lower PR needs updates, gt modify
propagates changes throughout the stack:
gt modify --all --commit --message "fix: Address feedback"
Keeping stacks in sync
Stay aligned with the latest changes from main
using:
gt sync
This pulls updates, rebases stacks, and resolves conflicts.
The Graphite CLI simplifies workflows, enabling faster, incremental development. Learn more at Graphite's CLI documentation.
Summary
By combining git add
, git commit
, and git push
, you create a powerful workflow for maintaining a clean, traceable record of your project’s evolution. For even greater efficiency, the Graphite CLI builds on this foundation by streamlining these operations and adding advanced features like pull request stacking and automated syncing. Together, these tools ensure every version of your code is stored, identified, and easily accessible, supporting seamless collaboration and long-term maintainability.