Git Daily Workflow Guide
Practical guide to common Git workflows for day-to-day development tasks within our GitFlow branching strategy.
Overview
This guide covers the practical Git commands and workflows you'll use daily. For the overall branching strategy and GitFlow principles, see Branching Strategy.
Most developers spend 90% of their time in three workflows:
- Creating and working on feature branches
- Keeping feature branches up-to-date with develop
- Creating merge requests and addressing review feedback
Starting a New Feature
1. Create Feature Branch from Latest Develop
# 1. Switch to develop and get latest changes
git checkout develop
git pull origin develop
# 2. Create and switch to feature branch
git checkout -b feature/JIRA-1234-add-payment-retry
# 3. Verify you're on the correct branch
git branch
# Output shows: * feature/JIRA-1234-add-payment-retry
# 4. Push branch to remote (sets up tracking)
git push -u origin feature/JIRA-1234-add-payment-retry
Branch naming pattern: feature/<TICKET>-<short-description>
Examples:
feature/JIRA-1234-add-payment-retryfeature/JIRA-5678-improve-error-messagesbugfix/JIRA-9012-fix-null-pointer
Pushing your feature branch immediately after creation provides several technical and collaboration benefits:
- Backup and disaster recovery: If your local machine fails, work is recoverable from the remote repository. Git stores complete history on the remote, so you can clone and continue from any other machine.
- CI/CD pipeline execution: GitLab triggers pipeline jobs on push events. Early pushes enable CI to build, test, and scan your code incrementally rather than discovering multiple issues at MR creation time.
- Team visibility: Remote branches appear in GitLab's branch list and your team's Git GUIs. This prevents duplicate work (two developers unknowingly implementing the same feature) and enables teammates to pull your branch for collaboration.
- Early feedback through draft MRs: Draft merge requests allow architectural review before significant implementation. Reviewers can comment on approach, suggest different patterns, or identify integration issues while changes are still easy to pivot.
Making Commits
Commit Workflow
# 1. Check what files have changed
git status
# 2. Review specific changes
git diff src/services/PaymentService.java
# 3. Stage files for commit
git add src/services/PaymentService.java
git add src/config/RetryConfig.java
# Or stage all changes (use carefully)
git add .
# 4. Verify what's staged
git status
# 5. Commit with meaningful message
git commit -m "feat(payments): add retry logic for failed payments
Implements exponential backoff with configurable max retries.
Prevents overwhelming payment gateway during outages.
Relates to JIRA-1234"
# 6. Push to remote
git push origin feature/JIRA-1234-add-payment-retry
Commit Message Format
Follow Conventional Commits:
<type>(<scope>): <subject>
[optional body]
[optional footer]
Common types:
feat: New featurefix: Bug fixrefactor: Code restructuringtest: Adding testsdocs: Documentation changeschore: Maintenance tasks
Example commits:
# Feature with context
git commit -m "feat(auth): add JWT refresh token mechanism
Implements automatic token refresh before expiration.
Reduces forced logouts and improves user experience.
JIRA-5432"
# Bug fix
git commit -m "fix(payments): prevent race condition in balance updates
Added pessimistic locking to account balance queries.
Prevents concurrent transactions from causing negative balances.
Fixes JIRA-6789"
# Refactoring
git commit -m "refactor(services): extract validation to separate service
Moved payment validation logic from controller to PaymentValidationService.
Improves testability and separation of concerns.
JIRA-3456"
# Tests
git commit -m "test(payments): add integration tests for retry mechanism
Tests retry behavior with exponential backoff.
Covers max retries, success after retry, and final failure scenarios.
JIRA-1234"
Make small, logical commits rather than large, monolithic ones. Each commit should represent a single logical change that leaves the codebase in a working state:
- [GOOD] Easier to review: Small commits (50-200 lines) can be thoroughly reviewed in 5-10 minutes. Reviewers understand the intent and catch subtle bugs. Large commits (1000+ lines) overwhelm reviewers, leading to superficial approval without deep analysis.
- Easier to revert if needed: If a commit introduces a bug discovered later,
git revert <commit-hash>cleanly undoes that specific change. With large commits mixing multiple concerns, reverting removes good code along with the problematic code. - [GOOD] Better git history: Descriptive small commits create narrative documentation of how the codebase evolved. Commands like
git log --onelineshow a clear story.git blamereveals why specific lines exist. Large "implemented feature X" commits hide this context. - Simpler merge conflict resolution: During rebase, Git applies commits one-by-one. Small commits mean fewer files changed per commit, reducing conflict surface area. When conflicts do occur, the small scope makes them easier to reason about and resolve correctly.
Keeping Feature Branch Updated
Option 1: Rebase (Recommended)
Rebasing creates a cleaner, linear history by replaying your commits on top of the latest develop. Technically, Git creates new commits with different SHAs (even though the diff content is identical) by changing the parent commit reference. Each commit in your feature branch is reapplied one-by-one onto the new base, preserving the commit message and author but generating a new SHA-1 hash. This rewrites history, which is why force-push is required afterward - the remote branch still points to the old SHAs while your local branch has new SHAs.
# 1. Fetch latest changes
git fetch origin
# 2. Rebase your feature branch onto latest develop
git checkout feature/JIRA-1234-add-payment-retry
git rebase origin/develop
# 3. If no conflicts, force push (your branch only!)
git push --force-with-lease origin feature/JIRA-1234-add-payment-retry
Rebase workflow diagram:
When to use rebase:
- [GOOD] Your feature branch hasn't been reviewed yet
- [GOOD] You're the only developer on the branch
- [GOOD] You want clean, linear history
- [GOOD] Before creating a merge request
Always use --force-with-lease instead of --force.
Technical difference: --force unconditionally updates the remote ref to match your local ref, discarding any remote commits. --force-with-lease adds a safety check: it verifies that the remote branch's current SHA matches what your local Git tracking knows (the ref value from your last fetch). If someone else pushed commits after your last fetch, the remote SHA won't match, and Git will reject the push with "stale info" error. This prevents accidentally overwriting a teammate's work.
Example scenario:
- You fetch at 9am (remote is at commit abc123)
- Teammate pushes at 9:30am (remote is now def456)
- You rebase and try to push at 10am
--forcewould replace def456 with your commits (losing teammate's work)--force-with-leasedetects abc123 ≠ def456 and rejects the push
Option 2: Merge (Safer for Shared Branches)
Merging preserves the branch history but creates merge commits.
# 1. Fetch and merge latest develop
git checkout feature/JIRA-1234-add-payment-retry
git fetch origin
git merge origin/develop
# 2. If conflicts occur, resolve them (see conflict resolution below)
# 3. Push normally (no force needed)
git push origin feature/JIRA-1234-add-payment-retry
When to use merge:
- [GOOD] Multiple developers working on the same feature branch
- [GOOD] Merge request is already under review
- [GOOD] You want to preserve exact history
- [GOOD] Less comfortable with rebasing
Comparison: Rebase vs Merge
| Aspect | Rebase | Merge |
|---|---|---|
| History | Linear, cleaner | Preserves branch points |
| Safety | Rewrites history | Preserves all commits |
| Conflicts | Resolve per commit | Resolve once |
| Team work | Solo branch only | Safe for shared branches |
| Force push | Required | Not needed |
Creating a Merge Request
Step 1: Ensure Branch is Ready
# 1. Make sure all changes are committed
git status
# Should show: "nothing to commit, working tree clean"
# 2. Ensure branch is up-to-date with develop
git fetch origin
git rebase origin/develop # or merge
# 3. Run tests locally
npm run test # frontend
./gradlew test --no-daemon # backend
# 4. Push latest changes
git push origin feature/JIRA-1234-add-payment-retry
Step 2: Create Merge Request
Using GitLab UI
- Navigate to your project in GitLab
- Click Create merge request (appears after pushing)
- Fill in MR template (see Pull Request Guidelines)
- Set base branch to
develop - Add reviewers
- Click Create merge request
Using GitLab CLI
# Install glab if needed: https://gitlab.com/gitlab-org/cli
glab mr create \
--base develop \
--title "[JIRA-1234] feat: Add payment retry mechanism" \
--description "$(cat << 'EOF'
## Summary
Implements exponential backoff retry mechanism for failed payment processing.
## Changes
- Added RetryConfig with configurable max attempts and delays
- Implemented exponential backoff in PaymentService
- Added comprehensive unit and integration tests
- Updated API documentation
## Testing
- [x] Unit tests pass (PaymentServiceTest)
- [x] Integration tests with TestContainers
- [x] Manual testing with simulated payment failures
- [x] Performance testing shows <50ms overhead
## Related
- Implements [JIRA-1234](https://jira.company.com/browse/JIRA-1234)
- Related to [JIRA-1111] (infrastructure improvements)
EOF
)" \
--label "feature" \
--assignee "@me"
Step 3: Draft Merge Requests for Early Feedback
# Create draft MR for early feedback
glab mr create --base develop --draft \
--title "[JIRA-1234] WIP: Add payment retry mechanism"
# Mark as ready later via UI or:
glab mr update --ready
Create draft merge requests early (after initial commit) to maximize feedback and minimize wasted effort:
- Architectural feedback: Reviewers can validate your approach before hundreds of lines are written. If you're heading in the wrong direction, course correction is trivial with 1-2 commits but painful with 20 commits.
- Stakeholder visibility: Product owners and managers can see implementation progress through the GitLab MR UI without interrupting developers. The MR description and commit history provide status updates.
- Continuous integration: CI pipelines run on every push, accumulating test results and code quality metrics over time. This distributes the feedback loop - fixing one test failure per day is manageable, fixing 30 failures at once is overwhelming.
- Pair programming enabler: Teammates can fetch your feature branch (
git fetch origin feature/JIRA-1234 && git checkout FETCH_HEAD) to collaborate, add commits, or debug issues together. The MR serves as a shared workspace.
Addressing Review Feedback
Making Changes After Review
# 1. Make requested changes
vim src/services/PaymentService.java
# 2. Commit the fixes
git add src/services/PaymentService.java
git commit -m "fix(payments): address review feedback on error handling
- Added specific exception types instead of generic Exception
- Improved logging with contextual information
- Added null checks for edge cases"
# 3. Push changes
git push origin feature/JIRA-1234-add-payment-retry
# 4. Comment in MR that feedback is addressed
# GitLab will automatically notify reviewers
Responding to Review Comments
# If reviewer requests changes to specific commits:
# Option 1: Add new commit (simpler)
git add <files>
git commit -m "fix(payments): address review comments on validation"
# Option 2: Amend last commit (if feedback is on most recent commit)
git add <files>
git commit --amend --no-edit
git push --force-with-lease origin feature/JIRA-1234-add-payment-retry
# Option 3: Interactive rebase (for changes to older commits)
git rebase -i HEAD~3 # Edit last 3 commits
# Mark commits to edit, make changes, continue rebase
git push --force-with-lease origin feature/JIRA-1234-add-payment-retry
Only amend commits or rebase if:
- No one else is working on this branch
- You understand the implications
- You use
--force-with-leaseto push
Otherwise, add new commits to address feedback.
Viewing History and Changes
Check Current Status
# View working directory status
git status
# View commit history
git log --oneline --graph --decorate --all
# View last 5 commits
git log --oneline -5
# View changes not yet staged
git diff
# View changes staged for commit
git diff --staged
# View changes in specific file
git diff src/services/PaymentService.java
Compare Branches
# Compare your feature branch to develop
git diff develop...feature/JIRA-1234-add-payment-retry
# List files changed between branches
git diff --name-only develop...feature/JIRA-1234-add-payment-retry
# See commits in your feature not in develop
git log develop..feature/JIRA-1234-add-payment-retry
# See commits with detailed changes
git log -p develop..feature/JIRA-1234-add-payment-retry
Search History
# Find commits by author
git log --author="Jane Doe"
# Find commits with message containing text
git log --grep="payment retry"
# Find commits that changed a specific file
git log -- src/services/PaymentService.java
# Find when a specific line was changed
git blame src/services/PaymentService.java
# Find which commit introduced a bug (binary search)
git bisect start
git bisect bad # Current commit is bad
git bisect good v1.4.0 # Known good version
# Git will check out commits; mark each as good/bad
git bisect reset # When done
Undoing Changes
Undo Uncommitted Changes
# Discard changes in specific file
git checkout -- src/services/PaymentService.java
# Discard all uncommitted changes (DANGEROUS!)
git reset --hard HEAD
# Unstage file (keep changes)
git reset HEAD src/services/PaymentService.java
# Unstage all files (keep changes)
git reset HEAD
Undo Last Commit
# Undo commit but keep changes staged
git reset --soft HEAD~1
# Undo commit and unstage changes (keep in working directory)
git reset --mixed HEAD~1
# or simply
git reset HEAD~1
# Undo commit and discard changes (DANGEROUS!)
git reset --hard HEAD~1
Revert a Commit
# Create new commit that undoes a previous commit
git revert <commit-hash>
# Revert without creating commit immediately
git revert -n <commit-hash>
git commit -m "revert: undo payment retry feature
Reverting due to production issues with external gateway.
Will reintroduce after fixing timeout configuration."
Difference: Reset vs Revert
| Command | Effect | Use Case |
|---|---|---|
git reset | Rewrites history | Local commits not pushed |
git revert | Creates new commit | Already pushed to shared branch |
Stashing Changes
Stashing temporarily saves uncommitted changes (both staged and unstaged) by creating special commits in a separate stash stack, then resets your working directory to match HEAD. Stash commits aren't part of any branch history and are stored in .git/refs/stash. Each stash entry contains three commits: one for the index (staged changes), one for the working tree (unstaged changes), and one parent commit representing the state when stash was created. This allows Git to reapply both staged and unstaged changes in their original state when you pop/apply the stash.
Basic Stashing
# Save current changes and clean working directory
git stash
# Or stash with descriptive message
git stash save "WIP: payment validation logic"
# View stashed changes
git stash list
# Output:
# stash@{0}: WIP: payment validation logic
# stash@{1}: On feature/JIRA-1234: experimental caching
# Apply most recent stash
git stash apply
# Apply and remove most recent stash
git stash pop
# Apply specific stash
git stash apply stash@{1}
# Remove stash without applying
git stash drop stash@{0}
# Clear all stashes
git stash clear
Advanced Stashing
# Stash including untracked files
git stash --include-untracked
# Stash only specific files
git stash push src/services/PaymentService.java
# Create branch from stash
git stash branch feature/JIRA-2345-new-idea stash@{0}
Common stash workflow:
# You're working on a feature when urgent bugfix is needed
git stash save "WIP: feature implementation"
# Fix the urgent bug
git checkout develop
git checkout -b bugfix/JIRA-9999-critical-fix
# ... make fix ...
git commit -m "fix: critical production bug"
git push origin bugfix/JIRA-9999-critical-fix
# Return to feature work
git checkout feature/JIRA-1234-add-payment-retry
git stash pop
Working with Tags
Tags mark specific points in history (usually releases).
Creating Tags
# Lightweight tag
git tag v1.5.0
# Annotated tag (recommended for releases)
git tag -a v1.5.0 -m "Release version 1.5.0
Features:
- Payment retry mechanism
- Improved error handling
- Performance optimizations
Bug Fixes:
- Fixed race condition in balance updates
- Resolved memory leak in cache"
# Tag specific commit
git tag -a v1.5.0 <commit-hash> -m "Release 1.5.0"
# Push tag to remote
git push origin v1.5.0
# Push all tags
git push origin --tags
Viewing Tags
# List all tags
git tag
# List tags matching pattern
git tag -l "v1.5.*"
# Show tag details
git show v1.5.0
# Checkout specific tag (detached HEAD)
git checkout v1.5.0
Deleting Tags
# Delete local tag
git tag -d v1.5.0
# Delete remote tag
git push origin --delete v1.5.0
Branch Management
Listing Branches
# List local branches
git branch
# List remote branches
git branch -r
# List all branches (local and remote)
git branch -a
# List branches with last commit
git branch -v
# List merged branches
git branch --merged develop
# List unmerged branches
git branch --no-merged develop
Switching Branches
# Switch to existing branch
git checkout develop
# Create and switch to new branch
git checkout -b feature/JIRA-5678-new-feature
# Modern alternative (Git 2.23+)
git switch develop
git switch -c feature/JIRA-5678-new-feature
Deleting Branches
# Delete local branch (safe - prevents if unmerged)
git branch -d feature/JIRA-1234-add-payment-retry
# Force delete local branch
git branch -D feature/JIRA-1234-add-payment-retry
# Delete remote branch
git push origin --delete feature/JIRA-1234-add-payment-retry
# Clean up remote tracking branches that no longer exist
git fetch --prune origin
Renaming Branches
# Rename current branch
git branch -m new-branch-name
# Rename specific branch
git branch -m old-branch-name new-branch-name
# Update remote
git push origin --delete old-branch-name
git push origin new-branch-name
git push origin -u new-branch-name
Collaboration Workflows
Working on Shared Feature Branch
# Before starting work, get latest changes
git checkout feature/JIRA-1234-shared-feature
git pull origin feature/JIRA-1234-shared-feature
# Make your changes and commit
git add .
git commit -m "feat(payments): add validation logic"
# Before pushing, pull again in case teammate pushed
git pull origin feature/JIRA-1234-shared-feature
# Resolve any conflicts, then push
git push origin feature/JIRA-1234-shared-feature
Cherry-Picking Commits
Cherry-picking applies the diff (patch) from a specific commit onto your current branch as a new commit with a different SHA. Git doesn't copy the commit itself; instead, it calculates the diff introduced by the target commit (comparing it to its parent), applies that diff to your current HEAD, and creates a new commit. The new commit has the same commit message and author, but different parent commit and timestamp, resulting in a different SHA-1 hash. This means the same logical change exists in two places with different commit identities.
# Copy specific commit to current branch
git cherry-pick <commit-hash>
# Cherry-pick without committing (review first)
git cherry-pick -n <commit-hash>
# Cherry-pick multiple commits
git cherry-pick <commit-hash-1> <commit-hash-2>
# Cherry-pick range of commits
git cherry-pick <start-hash>^..<end-hash>
Example use case:
# Urgent fix made on feature branch needs to go to develop
git checkout develop
git pull origin develop
git cherry-pick abc123 # Hash of the fix commit
git push origin develop
Pulling Changes from Another Feature Branch
# Merge specific commits from another feature
git checkout feature/JIRA-1234-my-feature
git cherry-pick <commit-from-other-feature>
# Or merge entire branch
git merge feature/JIRA-5678-teammate-feature
Useful Git Aliases
Add to ~/.gitconfig to speed up common commands:
[alias]
# Status and logs
st = status
s = status --short
l = log --oneline --graph --decorate --all -10
ll = log --graph --pretty=format:'%C(yellow)%h%Creset -%C(red)%d%Creset %s %C(green)(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
# Branching
co = checkout
br = branch
cob = checkout -b
# Committing
cm = commit -m
ca = commit --amend
cane = commit --amend --no-edit
# Updating
pu = pull
pup = pull --prune
rb = rebase
rbc = rebase --continue
rba = rebase --abort
# Pushing
ps = push
pf = push --force-with-lease
# Diffs
d = diff
ds = diff --staged
# Stashing
sl = stash list
ss = stash save
sp = stash pop
# Cleaning
cleanup = "!git branch --merged develop | grep -v '\\*\\|develop\\|main' | xargs -n 1 git branch -d"
# Undo
undo = reset --soft HEAD~1
unstage = reset HEAD
Using aliases:
# Instead of: git status
git st
# Instead of: git checkout -b feature/JIRA-1234
git cob feature/JIRA-1234
# Instead of: git commit --amend --no-edit
git cane
# Instead of: git push --force-with-lease
git pf
Daily Workflow Checklist
Starting Your Day
- Update local develop:
git checkout develop && git pull origin develop - Review assigned merge requests
- Check CI/CD pipeline status for your branches
- Review blockers or failed builds
Before Creating MR
- All changes committed:
git status - Branch updated with latest develop:
git rebase origin/develop - Tests pass locally:
npm test(frontend) /./gradlew test(backend) - Code linted/formatted:
npm run lint(frontend) /./gradlew spotlessApply(backend) - No merge conflicts with develop
Before Ending Your Day
- Commit work in progress
- Push commits to remote (backup)
- Update Jira ticket status
- Note any blockers in daily standup notes
Common Pitfalls to Avoid
Don't: Commit to Wrong Branch
# BAD: Accidentally on develop
git checkout develop
# ... make changes ...
git commit -m "feat: add new feature" # WRONG BRANCH!
Fix: See Troubleshooting Guide
Don't: Force Push to Shared Branches
# NEVER DO THIS on main, develop, or shared branches
git push --force origin develop # DANGEROUS!
Instead: Only force push to your own feature branches, and use --force-with-lease
Don't: Merge Without Updating
# BAD: Creating MR from outdated branch
git checkout -b feature/JIRA-1234 develop # Created 2 weeks ago
# ... work for 2 weeks ...
git push origin feature/JIRA-1234 # Missing 2 weeks of develop changes!
Instead: Regularly update feature branch with latest develop
Don't: Use Generic Commit Messages
# BAD
git commit -m "fixes"
git commit -m "update"
git commit -m "WIP"
git commit -m "."
Instead: Use Conventional Commits with descriptive messages
Further Reading
- GitFlow Branching Strategy - Overall branching model and policies
- Git Troubleshooting Guide - Solving common Git problems
- Merge Request Guidelines - Creating effective merge requests
- Code Review Process - Reviewing and responding to feedback
External Resources
- Pro Git Book - Comprehensive Git reference
- Conventional Commits - Commit message standard
- GitLab CLI Documentation - glab command reference
Summary
Key Takeaways:
- Start features from latest develop: Always pull before creating branch
- Commit frequently: Small, logical commits with meaningful messages
- Keep branch updated: Regular rebase or merge from develop
- Create MRs early: Use draft MRs for feedback
- Use conventional commits: Enables automated changelogs and clear history
- Test before pushing: Catch issues early
- Force push safely: Use
--force-with-leaseonly on your own branches - Stash wisely: Save WIP when switching contexts
- Clean up branches: Delete merged branches regularly
- Communicate: Comment on MRs, update Jira, ask for help when stuck