Unit 3 · Lesson 2

Basic Git: Clone, Commit, Push, Pull

Lesson 1 introduced the four zones. This lesson puts commands on the arrows between them. By the end you'll have a complete, repeatable workflow for every coding session — from cloning a repo for the first time to getting a teammate's changes into your local copy.

By the end of this lesson, you will:

  • Clone a GitHub repository to your local machine and understand what that action creates on disk
  • Use git status to read the state of your working directory and staging area at any point
  • Stage changes with git add and create a commit with a meaningful message
  • Push commits to GitHub and pull a teammate's commits into your local repo
  • Write commit messages that follow the Conventional Commits format used by Team 2910 and most professional FRC teams
  • Configure a WPILib-appropriate .gitignore so build artifacts never reach the repository

Commands Are the Arrows

In Lesson 1 you built a mental model of four zones: working directory, staging area, local repository, and remote. Git commands are the arrows that move your changes between those zones. Every command in this lesson corresponds to one of those arrows — and knowing which zone your changes are in at any moment is how you know which command to run next.

The workflow tracer below steps through a complete FRC coding session from first clone to first push. Work through it before reading the individual command sections — the whole picture makes each piece easier to place.

A Complete FRC Session, Step by Step

FRC Git Workflow Tracer
Working Directory 📄 empty
Staging Area 📋 empty
Local Repo 📁 empty
Remote (GitHub) ☁️ main @ origin
click Next Step to begin
↑ step through the workflow to see each command in context
step 0 / 9

The Commands in Depth

git clone — copying a remote repo to your machine

git clone downloads a repository from GitHub (or any remote) and creates a local copy with the full commit history. It also automatically sets up the remote — Git remembers the URL you cloned from as origin, so future push and pull commands know where to send and receive changes.

# Clone Team 2910's robot repo into a folder called "robot-2025" git clone https://github.com/FRCTeam2910/2025CompetitionRobot.git robot-2025 Cloning into 'robot-2025'... remote: Enumerating objects: 2841, done. remote: Counting objects: 100% (2841/2841), done. Receiving objects: 100% (2841/2841), 4.21 MiB | 12.04 MiB/s, done. # After clone, your folder contains the full project AND a hidden .git/ directory # That .git/ folder IS the local repository — the entire history lives there
💡 You only clone once per machine

Cloning sets up everything — the files, the .git folder, and the remote reference. After that first clone, you use git pull to get updates. A common mistake is cloning the same repo again after a computer restart instead of just running git pull. You end up with two copies and confusion about which one to edit. Clone once, pull often.

git status — reading the current state

git status is the most important command in your daily workflow. It tells you which files have changed in your working directory, which changes are staged for the next commit, and whether your local branch is ahead of or behind the remote. Run it constantly — before staging, before committing, before pushing. It costs nothing and tells you everything.

git status 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: src/main/java/frc/robot/Robot.java Untracked files: (use "git add <file>..." to include in what will be committed) src/main/java/frc/robot/subsystems/IntakeSubsystem.java no changes added to commit (use "git add" and/or "git commit -a") # "modified" = file existed and was changed (unstaged) # "Untracked" = new file Git has never seen before # Green text in a terminal = staged; Red text = not staged

git add — moving changes to the staging area

Staging is a deliberate step: you choose exactly which changes belong in the next commit. This lets you make multiple unrelated edits in a session and commit them as separate, focused snapshots — "add intake motor" as one commit, "fix dashboard label typo" as another.

# Stage a specific file git add src/main/java/frc/robot/subsystems/IntakeSubsystem.java # Stage all changed and new files in the project (use carefully — see .gitignore below) git add . # After staging, git status shows the file in green under "Changes to be committed" git status On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: src/main/java/frc/robot/subsystems/IntakeSubsystem.java

git commit — creating a permanent snapshot

A commit takes everything in the staging area and writes it to the local repository as a permanent, named snapshot. The -m flag attaches your message. Every commit gets a unique SHA hash — a 40-character fingerprint — that lets Git (and you) refer back to exactly this state forever.

git commit -m "feat(intake): add IntakeSubsystem with beam break detection" [main 3f8a2c1] feat(intake): add IntakeSubsystem with beam break detection 1 file changed, 47 insertions(+) create mode 100644 src/main/java/frc/robot/subsystems/IntakeSubsystem.java # The SHA "3f8a2c1" is the short form of this commit's unique identifier # You can always refer back to this exact state with: git checkout 3f8a2c1

git push — sending your commits to GitHub

git push uploads your local commits to the remote repository. Until you push, your commits exist only on your machine — a laptop that gets lost, broken, or wiped takes your unpushed commits with it. Push at the end of every session, and consider pushing after every significant commit during long sessions.

# Push the current branch to origin (GitHub) git push origin main Enumerating objects: 6, done. Counting objects: 100% (6/6), done. Writing objects: 100% (4/4), 1.24 KiB | 1.24 MiB/s, done. To https://github.com/FRCTeam2910/2025CompetitionRobot.git a1b2c3d..3f8a2c1 main -> main # After a successful push, your commits are safe on GitHub # Teammates can now pull your IntakeSubsystem

git pull — getting your teammates' changes

git pull downloads commits from the remote and merges them into your local branch. Run it at the start of every session and whenever a teammate tells you they pushed something. If you edited the same lines a teammate also changed, Git will alert you to a merge conflict — Lesson 3 covers resolving those.

# Pull latest changes from GitHub before starting work git pull origin main remote: Enumerating objects: 9, done. remote: Counting objects: 100% (9/9), done. Updating a1b2c3d..f9e3b12 Fast-forward src/main/java/frc/robot/subsystems/ShooterSubsystem.java | 82 ++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/main/java/frc/robot/subsystems/ShooterSubsystem.java # "Fast-forward" means your teammate's commits stacked cleanly on yours — no conflict

git log — reading the commit history

The commit history is the most useful debugging tool you didn't know you had. When something breaks, git log shows you every change made and when. The --oneline flag gives a compact view perfect for scanning a build session's changes.

git log --oneline -8 f9e3b12 feat(shooter): add ShooterSubsystem with velocity PID 3f8a2c1 feat(intake): add IntakeSubsystem with beam break detection a1b2c3d fix(drive): correct swerve module array order (FL/FR swapped) 7c4d881 refactor: extract motor configuration into configureMotor() helper 2e9f563 feat(drive): add swerve drive kinematics and odometry 8b1a774 chore: add WPILib .gitignore and project structure 4f3c920 chore: initial commit from WPILib template

Read the log above from bottom to top — that's the actual sequence this robot's code was built in. When the swerve drive broke during testing, the team ran git diff a1b2c3d 2e9f563 to see exactly what changed between the two commits and found the FL/FR swap in under two minutes.

Commit Messages: The Difference Between a Log and a Story

A commit message is the only explanation future-you — or a teammate at competition — will have for why a change was made. The message lives in the permanent history of the repo. It costs nothing to write a good one and costs time to have written a bad one.

Team 2910 follows the Conventional Commits format, which is standard across professional software engineering and most competitive FRC teams:

# Format: type(scope): short description # # type: feat | fix | refactor | chore | docs | test | perf # scope: the mechanism or file area affected (intake, drive, arm, shooter...) # description: imperative mood, lowercase, no period, under 72 chars feat(intake): add beam break sensor to detect game piece acquisition fix(arm): correct encoder offset constant from 45.0 to 42.3 degrees refactor(drive): extract current limit config into helper method chore: update .gitignore to exclude simulation outputs docs: add setup instructions to README for new programmers

The scope in parentheses is optional but strongly encouraged on a robot repo — it lets you scan the log and immediately know which mechanism each commit touches without reading the full description.

Commit Message Workshop

The messages below were pulled from real FRC repos (names changed). Select any one to see why it creates problems and what a better version looks like.

Commit Message Workshop — select a message to see the analysis click to compare →
01 fix
02 added stuff
03 PLEASE WORK
04 updated robot code for competition
05 fixed arm + intake + shooter + dashboard + auto
❌ This message

✅ Better version

The .gitignore File: Keeping Build Artifacts Out

A .gitignore file tells Git to never track certain files or folders. In a WPILib project, the build system generates hundreds of megabytes of compiled class files, Gradle caches, and simulation outputs. None of these belong in the repository — they're regenerated automatically on every build, they change constantly, and including them buries actual code changes in noise. Every FRC repo should have a .gitignore committed before any other files.

📄 .gitignore — WPILib project (place in the repo root)
# Build outputs
build/
.gradle/
bin/
out/

# WPILib simulation
simulation/
*.halsim

# IDE files
.vscode/
.idea/
*.iml

# OS files
.DS_Store
Thumbs.db

# Vendor library caches
vendordeps/*.json.bak
Why each section matters:
build/Compiled Java bytecode — regenerated on every build. Committing it bloats the repo by megabytes per commit.
.gradle/Gradle daemon and cache files. Specific to your machine and version. Causes "works on my machine" conflicts.
simulation/HAL simulation outputs. Developer-specific and large. Not useful to teammates.
.vscode/Editor settings. Each programmer has their own preferences. Committing these forces your settings on everyone.
.DS_StoremacOS folder metadata. Meaningless to Windows and Linux users. Appears as noise in every pull request.
🔍 Event Observation

The single most common Git-related pull request problem I see during code review is a team that committed their build/ directory. A typical WPILib build directory is 80–200 MB. Pushing it bloats the repository, makes cloning take minutes instead of seconds, and fills every git diff with thousands of lines of compiled bytecode changes. Worse, it hides the actual code changes — a PR that touched three Java files shows as "847 files changed" because every compiled output was regenerated. A five-line .gitignore in the first commit prevents this for the life of the project.

💡 WPILib generates a .gitignore for you

When you create a new project with the WPILib VS Code extension, a .gitignore is included automatically. It covers the most important patterns. Before your first git add ., run git status and verify that build/ and .gradle/ are not listed as untracked files. If they appear, your .gitignore is missing or in the wrong location.

The Daily Habit: Starting and Ending Every Session

Good Git hygiene is about consistent habit, not heroic effort. Every FRC programmer on 2910 follows the same pattern at the start and end of every coding session:

# ── Start of session ────────────────────────────────────────────────────────── git pull origin main # get any changes teammates pushed overnight git status # confirm you're starting from a clean state # ── During the session (after completing a focused task) ────────────────────── git status # see what changed git add src/main/java/frc/robot/subsystems/IntakeSubsystem.java git commit -m "feat(intake): add game piece detection via beam break" # ── End of session ──────────────────────────────────────────────────────────── git status # confirm nothing uncommitted git push origin main # your work is now safe on GitHub
🔍 LRI Observation

The most common conversation I have with teams at competition about Git goes like this: "Our robot code was working, someone pushed something last night, and now autonomous is broken." I ask them to run git log --oneline -5 and show me the screen. Within thirty seconds we've identified the breaking commit. Then it's git revert <sha> or git checkout <sha> -- filename.java and the issue is resolved before the next match. Teams that don't commit frequently can't do this — they have one commit from the start of the week and no way to identify what changed. Commit after every meaningful unit of work. The overhead is thirty seconds. The insurance value is hours.

🔌 System Check

⚙️ Git Hygiene for Competition Readiness
  • Run git pull before touching any file at the start of every session. Editing files while your local repo is behind the remote is the leading cause of merge conflicts. A 10-second pull at the start prevents an hour of conflict resolution later.
  • Run git status before every git add and before every git push. It's free information about your current state. Committing the wrong files or pushing an unexpected change because you didn't check is avoidable.
  • Commit one logical change per commit — not one commit per session. "Added everything for today" is not a commit message. A commit should represent a single, describable, reversible change. If you need to revert one thing tomorrow, you want to revert one commit — not undo a day's work.
  • Never commit build artifacts. Verify build/ and .gradle/ are in .gitignore before the first push to a new repo. Once build artifacts are in the history, they're difficult to remove without rewriting commits.
  • Push before you leave the shop. "I'll push it later" has ended competition robots. A laptop left at home, a battery that died, a failed drive — any of these takes your local commits with them. The repo on GitHub is your backup. Use it.
  • Use git log --oneline as a diagnostic tool, not just a historical record. When autonomous breaks, the first thing to run is the log. The breaking commit is almost always visible in the last five entries.

Knowledge Check

Click an answer to check your understanding.

You've made changes to Robot.java and run git commit -m "update" without running git add first. What is the result?
  • 1Git automatically stages and commits all changed files when you run git commit
  • 2Nothing is committed — the staging area is empty, so Git reports "nothing to commit" and your changes remain only in the working directory
  • 3Git creates an empty commit with the message "update"
  • 4Git commits all untracked files but skips modified files
After a late-night session, a teammate ran git push origin main and confirmed it succeeded. The next morning you find the build/ directory was included in that push. What almost certainly caused this, and how do you prevent it on the next project?
  • 1Git push always includes build artifacts — this is expected behavior
  • 2The build/ directory was committed because git push was run without a prior git pull
  • 3The .gitignore file was missing or didn't include build/, so git add . staged the entire build directory; the fix is to add build/ to .gitignore before the first commit on any new project
  • 4GitHub automatically scans for build artifacts and includes them — you need a GitHub Actions workflow to prevent this
Autonomous broke between yesterday's practice and today. You want to identify which commit introduced the bug. Which command gives you the most useful starting point?
  • 1git status — shows what files are currently different from the last commit
  • 2git pull origin main — downloads the latest working version from GitHub
  • 3git log --oneline -10 — shows the last ten commits with their SHA hashes and messages, letting you identify when the change was introduced and then use git diff or git checkout to investigate
  • 4git clone — re-clones the repo from GitHub to get a fresh copy
💪 Practice Prompt

Your First Real Commit

This prompt puts your WPILib project from Unit 0 under version control from scratch. Every step is a real terminal command — do them in order, in a terminal, in your project folder.

  1. Initialize the repo. Open a terminal in your WPILib project folder and run git init. Then run git status. You should see every project file listed as untracked. Note how many files appear — this is why the next step matters.
  2. Verify your .gitignore. Check whether a .gitignore already exists in your project root (WPILib creates one). Open it and confirm build/ and .gradle/ are present. If they're missing, add them. Run git status again — build/ should no longer appear in the untracked list.
  3. Stage the right files. Run git add src/ build.gradle settings.gradle gradlew gradlew.bat .gitignore. Then run git status and confirm only source files and project configuration files are staged — not build output directories.
  4. Make your first commit. Write a commit message that follows the Conventional Commits format: git commit -m "chore: initial WPILib project setup". Run git log --oneline to confirm the commit appears.
  5. Connect to GitHub. Create a new repository on GitHub (do not initialize with a README — you already have commits). Follow GitHub's instructions to add the remote and push: git remote add origin <your-url> followed by git push -u origin main.
  6. Make a meaningful second commit. Open Robot.java and add a comment with your team number, the season year, and your name. Stage it, write a proper commit message (docs(robot): add team attribution header), and push. Verify it appears on GitHub.
  7. Practice the log. Run git log --oneline. You should see two commits. Practice running git diff <sha1> <sha2> between your two commits. Identify which lines changed.
  8. Bonus: Ask a teammate to clone your repo, add a comment to a different file, commit with a proper message, and push. Then pull their change. Observe what git log --oneline shows afterward — whose commit comes first, and why?