3.11. Basic Git Workflow
Table of Contents
Overview
In this chapter you will learn how to use Git in your daily work as a backend developer. You already know what Git is and how to install it from previous chapters. Here, we focus on the typical workflow you will repeat in almost every project.
We will walk step by step from cloning a repository to making changes, committing, pushing, pulling, and handling simple conflicts, with many concrete command examples.
The Life Cycle of a Change
A basic Git workflow for a backend project usually follows this pattern:
- Get the code on your machine:
git clonefor a new project- or
git pullto update an existing local copy - Create a new branch for your work:
git checkout -b feature/...- Work on files:
- edit, add, delete, move
- See what changed:
git statusgit diff- Stage the changes you want to keep:
git add ...- Commit with a clear message:
git commit -m "..."- Push your branch to the remote:
git push origin your-branch- Open a Pull Request / Merge Request on GitHub / GitLab
- After review, merge to main branch
- Update your local main / master:
git checkout maingit pull origin main
We will now go through each step in detail with examples.
Cloning a Repository
To work on a project that already exists on GitHub or GitLab, you clone it.
Example: Cloning from GitHub
Imagine you have a repository at:
https://github.com/example-user/todo-api.git
Open a terminal in the directory where you want the project folder:
git clone https://github.com/example-user/todo-api.git
Git will create a new directory todo-api and download the full history.
Move into the project folder:
cd todo-apiCheck which branch you are on:
git branchExample output:
* main
This means your current branch is main.
Cloning with SSH
If you set up SSH keys, you might clone with SSH instead of HTTPS:
git clone git@github.com:example-user/todo-api.git
For everyday workflow, cloning is done once per machine. After that you usually work with git pull and branches.
Checking Repository Status
Before and after changes, you should know what is going on in your repository. The main tool is:
git statusExample Scenario
You just cloned the project:
git statusOutput:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree cleanThis means:
- You are on branch
main - Your local
mainis the same asorigin/main(the remote version) - There are no local changes
Now edit a file, for example app.py, and save it. Run:
git statusOutput:
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)
modified: app.py
no changes added to commit (use "git add" and/or "git commit -a")Git tells you:
- One file,
app.py, has been modified - It is not yet staged for commit
git status is safe and useful. Run it often.
Creating and Switching Branches
You rarely commit directly to main in team projects. Instead you create a new branch for each feature or bug fix.
Creating a Branch
Common naming patterns:
| Type | Example Branch Name |
|---|---|
| Feature | feature/add-user-registration |
| Bug fix | fix/login-timeout |
| Chore | chore/update-dependencies |
| Experiment | experiment/new-cache-strategy |
To create and switch to a new branch:
git checkout -b feature/add-login-endpointThis is shorthand for:
git branch feature/add-login-endpoint
git checkout feature/add-login-endpointCheck your branches:
git branchExample output:
main
* feature/add-login-endpointThe star shows your current branch.
Switching Between Branches
If you already created a branch:
git checkout main
git checkout feature/add-login-endpointGit will refuse to switch branches if you have uncommitted changes that would be overwritten. In that case you must commit, stash, or discard those changes first.
Making and Inspecting Changes
Once you are on your feature branch, you change code as needed.
Editing Files
Example backend project structure:
todo-api/
app/
main.py
models.py
routes.py
tests/
test_tasks.pyYou might:
- Add a new route in
routes.py - Change a model in
models.py - Add a test in
test_tasks.py
After edits, run:
git statusExample output:
On branch feature/add-login-endpoint
Changes not staged for commit:
modified: app/routes.py
modified: app/models.py
Untracked files:
tests/test_auth.pyThis tells you:
- Two tracked files are modified
- One new file is untracked
Viewing What Changed
To see the exact changes, use git diff.
Diff of all unstaged changes
git diffExample output:
diff --git a/app/routes.py b/app/routes.py
index 1234567..89abcde 100644
--- a/app/routes.py
+++ b/app/routes.py
@@ -1,4 +1,9 @@
-from fastapi import APIRouter
+from fastapi import APIRouter, HTTPException
router = APIRouter()
+@router.post("/login")
+def login(username: str, password: str):
+ # temporary implementation
+ return {"token": "fake-token"}
This shows additions starting with + and deletions starting with -.
Diff of a specific file
git diff app/models.py
Use q to quit the diff viewer.
Staging Changes with `git add`
Before you commit, you must stage the changes you want to include.
Think of staging as selecting which changes go into the next snapshot.
Staging All Changes
To stage everything at once:
git add .or
git add -AThis includes:
- Modified tracked files
- New files
- Deleted files
Use this only when you are sure that all changes belong together.
Staging Specific Files
Most of the time, it is better to be explicit.
git add app/routes.py
git add tests/test_auth.pyCheck status:
git statusExample output:
On branch feature/add-login-endpoint
Changes to be committed:
modified: app/routes.py
new file: tests/test_auth.py
Changes not staged for commit:
modified: app/models.pyNow:
routes.pyandtest_auth.pyare stagedmodels.pyis still unstaged and will not be part of the next commit
Staging Parts of a File (Optional but Useful)
Sometimes a file has unrelated changes that you want in separate commits.
You can stage interactively:
git add -p app/routes.pyGit will show you each change chunk and ask if you want to stage it.
This is advanced but very useful for clean history.
Writing Commits
Once you have staged the correct changes, you create a commit. A commit is a snapshot with a message that explains why you changed the code.
Creating a Commit
git commit -m "Add basic login endpoint and tests"Check status again:
git statusExample:
On branch feature/add-login-endpoint
Changes not staged for commit:
modified: app/models.py
nothing added to commit but untracked files present (use "git add" to track)Your commit was created with the staged changes only.
The unstaged changes remain in your working directory.
Good Commit Messages
A clear commit message is very important for future you and for your teammates.
Some guidelines:
- Use the imperative form: "Add", "Fix", "Update" instead of "Added" or "Fixes"
- Be specific: explain the intent of the change
- Keep the summary line short, around 50 characters
Examples:
Add login endpoint and auth testsFix task ordering in list endpointUpdate README with setup instructionsRefactor user service to use repository
Bad examples:
ChangesFixWork in progress
Important rule: Every commit should represent a logical, consistent change that can be explained in one sentence.
Multiple Commits for One Task
A full feature might need several commits, for example:
Add User model and migrationImplement login endpointAdd tests for login endpoint
Small, focused commits are easier to review and debug.
Viewing History and Details
To see your commit history:
git logExample output:
commit 7c9a1e6f0d0c3b6f2cb22b6dabae129a85f46899 (HEAD -> feature/add-login-endpoint)
Author: Your Name <you@example.com>
Date: Thu Aug 22 14:37:10 2026 +0000
Add login endpoint and auth tests
commit 5ab3c884a8da1e5910f8207b3c229e4b1a942c46 (origin/main, main)
Author: Teammate <teammate@example.com>
Date: Wed Aug 21 10:12:01 2026 +0000
Add task listing endpointUseful options:
git log --onelineshort viewgit log --oneline --graph --allsimple graphical representation
To see changes of a specific commit:
git show 7c9a1e6You can copy the first few characters of the commit hash.
Pushing Your Work to the Remote
So far your commits exist only on your machine. To share them or create a Pull Request, you must push them to the remote repository.
First Push of a New Branch
If your branch does not exist on the remote yet:
git push -u origin feature/add-login-endpointExplanation:
originis the default name of the remote you cloned from-usetsorigin feature/add-login-endpointas the upstream, so next time you can just typegit push
Next pushes on the same branch:
git pushPushing Updates
After more commits, just run:
git pushIf someone else or a CI system already changed the remote version of your branch, Git might refuse to push and tell you to pull first. We will cover this next.
Pulling Updates from Remote
To get changes from the remote into your local branch use:
git pullThis is equivalent to:
git fetch
git mergeUpdating main
You will frequently update your local main branch:
git checkout main
git pull origin main
This pulls the latest changes from origin/main into your local main.
Updating a Feature Branch
There are two common approaches:
- Merge latest main into your feature branch:
git checkout feature/add-login-endpoint
git merge main- Rebase your branch on top of main (more advanced, typically used in clean-history workflows). In beginner teams, a simple merge is usually enough and safer.
After pulling or merging, check status and run tests to ensure everything works.
Handling Simple Merge Conflicts
Sometimes git pull or git merge will result in a conflict. This happens when Git cannot automatically decide how to combine changes.
Example Conflict Scenario
- Teammate changes
app/routes.pyinmain - You also changed
app/routes.pyin your branch - You run:
git checkout main
git pull origin main
git checkout feature/add-login-endpoint
git merge mainGit output:
Auto-merging app/routes.py
CONFLICT (content): Merge conflict in app/routes.py
Automatic merge failed; fix conflicts and then commit the result.Conflict Markers
Open the file with the conflict:
@app.get("/tasks")
def list_tasks():
<<<<<<< HEAD
return task_service.get_all_tasks()
=======
return task_service.list_all()
>>>>>>> mainExplanation:
<<<<<<< HEADshows your current branch version=======separates the two versions>>>>>>> mainshows the version from the branch you merged in
You must edit the file manually and choose or combine the correct version. For example:
@app.get("/tasks")
def list_tasks():
# Use new unified service method
return task_service.list_all()When finished, remove all conflict markers.
Completing the Merge
After fixing all conflicts in all files:
git add app/routes.py
git commitGit will open an editor with a default merge commit message, or you can set it from the command line:
git commit -m "Merge branch 'main' into feature/add-login-endpoint"Then push:
git pushYou have now resolved the conflict and updated your branch.
Typical Workflow Example: Adding a New API Endpoint
To make it concrete, here is a full example of a simple daily workflow.
1. Update main
git checkout main
git pull origin main2. Create a feature branch
git checkout -b feature/add-completed-filter
Goal: Add a completed query parameter to the tasks list endpoint.
3. Implement the change
Edit app/routes.py:
from fastapi import APIRouter, Query
@router.get("/tasks")
def list_tasks(completed: bool | None = Query(default=None)):
return task_service.list_tasks(completed=completed)
Edit tests/test_tasks.py and add tests.
4. Check status and diff
git status
git diffVerify that only the intended changes are present.
5. Stage files
git add app/routes.py tests/test_tasks.pyCheck:
git statusOutput should list the two files under "Changes to be committed".
6. Commit
git commit -m "Add completed filter to tasks list endpoint"7. Push
git push -u origin feature/add-completed-filter8. Create Pull Request / Merge Request
- Go to GitHub or GitLab
- Open a new Pull Request from
feature/add-completed-filtertomain - Add description and assign reviewers
9. Apply review changes (if any)
Reviewer asks you to adjust tests.
Edit files again, then:
git status
git add tests/test_tasks.py
git commit -m "Adjust completed filter tests to check default value"
git pushThe Pull Request updates automatically.
10. Merge and cleanup
After approval, merge the Pull Request through the web interface.
Locally:
git checkout main
git pull origin main
git branch -d feature/add-completed-filter # delete local branch
git push origin --delete feature/add-completed-filter # optional, delete remote branchCommon Commands Quick Reference
| Task | Command example |
|---|---|
| Clone repository | git clone URL |
| Check branch and status | git status |
| List branches | git branch |
| Create and switch to new branch | git checkout -b feature/name |
| Switch to existing branch | git checkout main |
| See changes (unstaged) | git diff |
| See staged changes | git diff --cached |
| Stage specific file | git add path/to/file.py |
| Stage all changes | git add . |
| Commit with message | git commit -m "Message" |
| Show history | git log or git log --oneline |
| Push new branch | git push -u origin feature/name |
| Push existing branch | git push |
| Pull changes into current branch | git pull |
| Merge another branch into current | git merge main |
| Delete local branch | git branch -d feature/name |
| Delete remote branch | git push origin --delete feature/name |
Key daily rules for Git workflow
- Always work in a branch, not directly on
main, in team projects. - Run
git statusandgit diffbefore every commit. - Keep commits small and focused, with clear messages.
- Pull and update
mainregularly, especially before starting new work.
By practicing this workflow repeatedly in your backend projects, Git will become a natural part of how you develop features and collaborate with others.
Views: 7
KAHIBARO