Learn Git Advanced Workflow
Here is the scenario: you are a full-stack developer working on a veterinary clinic management application. You’re in the middle of implementing a new feature for appointment scheduling, but an urgent bug fix is needed in production. This scenario walks you throughgit add --patch, conventional commits with headers, bodies, and footers, and git worktree via a realistic workflow.
Part 1: Understanding the Starting Point
Repository Structure
clinic-app/
├── src/
│ ├── components/
│ │ ├── AppointmentForm.tsx
│ │ ├── AppointmentList.tsx
│ │ └── PatientCard.tsx
│ ├── services/
│ │ ├── appointmentService.ts
│ │ └── authService.ts
│ └── App.tsx
├── tests/
│ └── appointmentService.test.ts
├── package.json
└── README.mdYou’re on the feature/appointment-scheduler branch where you've made changes across multiple files:
- AppointmentForm.tsx: Added new form validation and accidentally left debug logging code.
- appointmentService.ts: Implemented appointment creation API and improved error handling.
- README.md: Updated documentation.
Suddenly, your manager reports a critical bug in production: the authentication service is returning incorrect user roles, causing patients to see restricted areas.
Doing the task, you will learn how to:
- Use
git add --patchto selectively stage only the appointment form validation (not the debug code). - Create a proper commit with a conventional commit format.
- Use
git worktreeto create an isolated environment for the hotfix without stashing your work. - Continue work on the feature branch while the hotfix is being prepared.
Part 2: Setting Up the Scenario
Ensure you have Git 2.25+ (for modern worktree features):
git --versionSet up the Scenario
- Create the repository:
git init clinic-app
cd clinic-app# Create initial commit
touch README.md package.json
git add .
git commit -m "chore: init"# Create main and develop branches
git checkout -b develop
git checkout main
- Create the feature branch:
git checkout -b feature/appointment-scheduler- Create the file structure:
mkdir -p src/{components,services} tests
touch src/components/{AppointmentForm.tsx,AppointmentList.tsx,PatientCard.tsx}
touch src/services/{appointmentService.ts,authService.ts}
touch src/App.tsx
touch tests/appointmentService.test.ts
touch README.mdPart 3: Goal 1, Git Add Patch (git add -p)
What is git add --patch?
git add --patch (or git add -p) allows you to interactively stage hunks (chunks of code) instead of entire files. This is powerful for:
- Separating concerns in a single file
- Staging only the good code and leaving debug code unstaged
- Creating more atomic, focused commits
The Scenario Modification
Edit src/components/AppointmentForm.tsx:
import React, { useState } from 'react';export const AppointmentForm: React.FC = () => {
const [date, setDate] = useState('');
const [time, setTime] = useState('');
const [reason, setReason] = useState(''); // Good code: proper validation
const validateForm = (): boolean => {
if (!date || !time || !reason) {
console.error('Form validation failed: missing required fields');
return false;
}
if (new Date(date) < new Date()) {
console.error('Form validation failed: date is in the past');
return false;
}
return true;
}; // DEBUG CODE (left accidentally)
const debugInfo = () => {
console.log('DEBUG: AppointmentForm component mounted');
console.log('DEBUG: Current state -', { date, time, reason });
console.log('DEBUG: validateForm function called');
}; const handleSubmit = (e: React.FormEvent) => {
debugInfo(); // Should not be here! if (validateForm()) {
console.log('Submitting appointment:', { date, time, reason });
// Call API
}
}; return (
<form onSubmit={handleSubmit}>
<input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
<input type="time" value={time} onChange={(e) => setTime(e.target.value)} />
<textarea value={reason} onChange={(e) => setReason(e.target.value)} />
<button type="submit">Book Appointment</button>
</form>
);
};
Edit src/services/appointmentService.ts:
export interface Appointment {
id: string;
patientId: string;
date: string;
time: string;
reason: string;
}export const appointmentService = {
async createAppointment(data: Omit<Appointment, 'id'>): Promise<Appointment> {
try {
const response = await fetch('/api/appointments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}); // Improved error handling
if (!response.ok) {
const error = await response.json();
throw new Error(`Appointment creation failed: ${error.message}`);
} return response.json();
} catch (error) {
// Better error propagation
if (error instanceof Error) {
console.error('Appointment service error:', error.message);
throw error;
}
throw new Error('Unknown error in appointment service');
}
}, async getAppointments(patientId: string): Promise<Appointment[]> {
const response = await fetch(`/api/appointments?patientId=${patientId}`);
return response.json();
},
};
Edit README.md:
# Clinic Management Application
## New Features
### Appointment Scheduler
- Book appointments with date and time validation
- View all patient appointments
- Automatic reminders before appointments
### Installation
```bash
npm install
npm start
```Now use git add -p
- Check the current status:
git statusYou’ll see all three files as untracked or modified.
- Start interactive patch mode:
git add -p2. Walk through the hunks:
When you run git add -p src/components/AppointmentForm.tsx, Git will show you each hunk and ask:
Stage this hunk [y,n,q,a,d,s,e,?]?
Available options:
y- stage this hunkn- don't stage this hunks- split this hunk into smaller hunkse- manually edit this hunkq- quit?- show help
- Your approach:
# First file
git add -p src/components/AppointmentForm.tsx
# When you see the validation function: press 'y'
# When you see the debug code: press 'n'# Second file (all good changes)
git add -p src/services/appointmentService.ts
# Press 'y' for all hunks# Third file
git add -p README.md
# Press 'y' for all hunks
- Verify your staging area:
git diff --cachedThis shows only what you staged (good code, no debug).
git diffThis shows unstaged changes (debug code).
Part 4: Goals 2, Conventional Commits (Header, Body, Footer)
What are Conventional Commits?
Conventional commits follow a structured format that makes your commit history readable, automatable, and suitable for semantic versioning.
Format:
<type>(<scope>): <description>
<body>
<footer>Commit Type Reference
| Type | Purpose | Example |
|----------|----------------------------------|----------------------------------|
| `feat` | New feature | `feat(appointments): add booking` |
| `fix` | Bug fix | `fix(auth): correct role check` |
| `refactor` | Code refactor (no feature change) | `refactor(api): extract helper` |
| `docs` | Documentation changes | `docs: update README` |
| `test` | Add or update tests | `test(auth): add role tests` |
| `chore` | Maintenance tasks | `chore: update dependencies` |Your First Conventional Commit
After using git add -p to stage the good code, create a commit:
git commitThis opens your editor (nano or vim). Then type:
feat(appointments): implement form validation and improved error handling- Add comprehensive form validation with date/time checks
- Validate that appointment dates are in the future
- Implement improved error handling in appointment service
- Add specific error messages for troubleshootingThe form now properly validates all required fields before submission.
The service layer provides better error propagation and logging,
making debugging easier for developers and users.Closes #234
Related to #231
Breaking this down:
Get Faizul Fuadi’s stories in your inbox
Join Medium for free to get updates from this writer.
Header: feat(appointments): implement form validation and improved error handling
- Type:
feat(new feature) - Scope:
appointments(what part of the system) - Description: Clear, concise description (present tense, lowercase)
Body:
- Explains the why and what, not just code changes
- Use bullet points for clarity
- Wrap at 72 characters for readability
- Leave a blank line between the header and the body
Footer:
Closes #234- Automatically closes GitHub issue #234Related to #231- References related issues- Other options:
Fixes #X,Resolves #X,BREAKING CHANGE: ...
Common Footer Keywords
| Keyword | Purpose |
|---------|---------|
| `Closes` | Closes the referenced issue |
| `Fixes` | Fixes the bug in referenced issue |
| `Resolves` | Resolves the referenced issue |
| `Related to` | References a related issue (doesn't close it) |
| `BREAKING CHANGE:` | Indicates breaking API/behavior changes |
| `Co-authored-by:` | Credits multiple authors |View Your Commit
git log --oneline
git log -1 --format=fullerPart 5: Goals 3, Git Worktree
What is git worktree?
git worktree allows you to have multiple working directories from the same repository, each checked out to a different branch. This solves the problem where you need to work on multiple branches simultaneously without:
- Stashing/unstashing work
- Losing your IDE state
- Switching contexts constantly
Real-World Scenario
Your manager needs the hotfix NOW. Instead of interrupting your appointment scheduler work, you’ll:
- Create a worktree for the hotfix branch
- Work on the hotfix in a separate directory
- Continue working on your feature branch in the original directory
Step 1: Create a Hotfix Worktree
First, ensure your main/master branch exists and is up to date:
git checkout main
git pull # or update manuallyNow create a worktree:
git worktree add ../clinic-app-hotfix fix/auth-role-bug --track -b fix/auth-role-bugBreaking this down:
git worktree add- Create a new worktree../clinic-app-hotfix- Path to the new working directory (outside main repo)fix/auth-role-bug- Branch name for the hotfix--track- Track the remote branch-b- Create the branch if it doesn't exist
Your directory structure now looks like:
parent-directory/
├── clinic-app/ # Your original worktree (feature work)
│ └── src/
│ ├── components/
│ ├── services/
│ └── ...
└── clinic-app-hotfix/ # New worktree (hotfix work)
└── src/
├── components/
├── services/
└── ...Step 2: Work in the Hotfix Worktree
Navigate to the hotfix directory:
cd ../clinic-app-hotfixNow create the bug (so you can fix it):
Edit src/services/authService.ts:
export interface User {
id: string;
name: string;
role: 'admin' | 'vet' | 'patient';
}export const authService = {
// BUG: roles are inverted!
getRoleLevel(role: string): number {
switch (role) {
case 'patient':
return 3; // BUG: patient should not be level 3
case 'vet':
return 2;
case 'admin':
return 1; // BUG: admin should be highest
default:
return 0;
}
}, hasPermission(userRole: string, requiredRole: string): boolean {
// Patients with level 3 shouldn't access everything!
return this.getRoleLevel(userRole) >= this.getRoleLevel(requiredRole);
}, getCurrentUser(): User | null {
// Simulated: in real app, get from localStorage/session
return {
id: '123',
name: 'John Vet',
role: 'vet',
};
},
};
Add and commit the fix using patch mode and conventional commits:
git add -pStage the bugfix, then commit:
git commitfix(auth): correct role permission hierarchyThe role level mapping was inverted, causing patients to have
administrative permissions and blocking admin access to certain features.Fixed the role levels so that:
- patient: level 1 (lowest permissions)
- vet: level 2 (standard permissions)
- admin: level 3 (highest permissions)Fixes #999
BREAKING CHANGE: Role hierarchy has been corrected. Code relying on
the old (buggy) hierarchy will need to be updated."
Step 3: Return to Your Feature Branch
Navigate back to your original repository:
cd ../clinic-appYour feature branch work is exactly as you left it! Continue working:
git log --oneline
# You'll still see your appointments feature commitsStep 4: Manage Your Worktrees
List all worktrees:
git worktree listOutput:
/path/to/clinic-app abc1234 [feature/appointment-scheduler]
/path/to/clinic-app-hotfix def5678 [fix/auth-role-bug]Step 5: Cleanup After Hotfix
Once the hotfix is merged to main and deployed:
# From the hotfix worktree directory
cd ../clinic-app-hotfix# Switch to main and merge
git checkout main
git pull
git merge fix/auth-role-bug# Remove the worktree
cd ../clinic-app
git worktree remove ../clinic-app-hotfix
Or use the shorthand:
git worktree remove fix/auth-role-bugList worktrees again to confirm:
git worktree listNow only the main repo remains:
/path/to/clinic-app [feature/appointment-scheduler]Part 6: Advanced Patterns
Pattern 1: Using Patch Mode with --edit
If hunks don’t split the way you want, use -e to manually edit:
git add -p --editYour editor opens with the full diff. Remove lines you don’t want to stage.
Pattern 2: Conventional Commits with Breaking Changes
git commitrefactor(auth)!: redesign permission system- Replace role hierarchy with permission-based system
- All components now use new authService APIBREAKING CHANGE: The getRoleLevel() method has been removed.
Use hasPermission() instead. Migrate code before upgrading."
The ! in the header (refactor(auth)!) and BREAKING CHANGE: In footer, both indicate breaking changes.
Pattern 3: Multiple Worktrees for Parallel Development
# Feature A
git worktree add ../clinic-app-feature-a feature/appointment-scheduler
# Feature B
git worktree add ../clinic-app-feature-b feature/payment-integration
# Hotfix
git worktree add ../clinic-app-hotfix fix/auth-bug main# Your directory structure
# ├── clinic-app/
# ├── clinic-app-feature-a/
# ├── clinic-app-feature-b/
# └── clinic-app-hotfix/Now you can work on three things simultaneously without context switching!
Pattern 4: Temporary Worktree for Merge Testing
# Test what happens if you merge develop into feature
git worktree add ../clinic-app-test --detach HEAD
cd ../clinic-app-test
git merge develop
# Test... does it compile? Do tests pass?
cd ../clinic-app
git worktree remove ../clinic-app-test
# Your real branches are untouchedPart 7: Exercises, Practice makes perfect
Exercise 1: Selective Staging
- Edit
src/components/PatientCard.tsxwith two separate concerns:
- Add new patient status field (good)
- Add console.log debugging (not good)
2. Use git add -p to stage only the status field change
3. Write a conventional commit for the status field feature
Exercise 2: Complete Commit Workflow
Create a feature that fixes error handling and closes an issue:
git add -p
git commitfeat(api): improve error messagesDisplay user friendly error messages when API calls fail.- Add error boundary component
- Map server errors to user messages
- Add logging for debuggingCloses #456
Related to #450"
Exercise 3: Worktree Workflow
- Create a worktree for a documentation branch:
git worktree add ../clinic-app-docs docs/api-guide2. In the main repo, continue feature work
3. In the docs worktree, add API documentation
4. Return to main repo and list both worktrees
5. Remove the docs worktree
Part 8: Troubleshooting
Issue: Can’t stage hunk (too complex)
Use s to split hunks:
Stage this hunk [y,n,q,a,d,s,e,?]? sIf split doesn’t work, use e to edit manually.
Issue: Worktree directory already exists
git worktree add ../path # Error: directory existsEither remove the directory or use a different path.
Issue: Can’t remove worktree (still in use)
# Navigate AWAY from the worktree first
cd ../clinic-app
git worktree remove ../clinic-app-hotfixIssue: Worktree is locked
# If Git says worktree is locked
git worktree prune
# Or force remove
git worktree remove --force ../clinic-app-hotfixKey Takeaways
| Concept | When to Use | Benefit |
|---------|------------|---------|
| `git add -p` | Multiple units of work in one file | Atomic, focused commits |
| Conventional commits | All commits | Readable history, semantic versioning |
| `git worktree` | Multiple branches needed simultaneously | No context switching, faster workflow |References
- Git documentation:
git add --patch - Conventional Commits: https://www.conventionalcommits.org/
- Git worktree:
git worktree --help




