Read Anthropic’s case study about Graphite Reviewer

Git add, commit, and push

Greg Foster
Greg Foster
Graphite software engineer
Try Graphite


Note

This guide explains this concept in vanilla Git. For Graphite documentation, see our CLI docs.


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.

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:

Terminal
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:

Terminal
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.

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:

Terminal
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.

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:

Terminal
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.

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:

  1. Check your current status

    Terminal
    git status

    Git will show you which files are modified and which are untracked. For example:

    Terminal
    On branch main
    Your 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.js
    modified: styles.css
  2. Stage your changes

    Terminal
    git add app.js styles.css

    Now these files are in the staging area. Running git status again will show:

    Terminal
    Changes to be committed:
    (use "git restore --staged <file>..." to unstage)
    modified: app.js
    modified: styles.css
  3. Commit your changes

    Terminal
    git 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:

    Terminal
    commit d4c3f2a (HEAD -> main)
    Author: Your Name <your.email@example.com>
    Date: Wed Dec 11 14:28:23 2024 -0500
    Improve UI and add input validation in app.js
  4. Push your changes

    Terminal
    git push origin main

    This sends your commits to the main branch on the remote repository. After pushing, teammates can pull these changes and benefit from your latest updates.

  • 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.

The Graphite CLI enhances Git workflows by simplifying commands and introducing pull request (PR) stacking.

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:

Terminal
gt create --all --message "feat: Implement new API"

Submit it as a pull request with:

Terminal
gt submit

PR stacking lets you break large tasks into manageable layers. Start with a base PR:

Terminal
gt create --all --message "feat(api): Add base endpoint"
gt submit

Then stack a dependent PR:

Terminal
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:

Terminal
gt modify --all --commit --message "fix: Address feedback"

Stay aligned with the latest changes from main using:

Terminal
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.

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.

Built for the world's fastest engineering teams, now available for everyone