Skip to content
Guides

Git - Advanced Techniques and Workflow for Development Teams

Published on:
·6 min read·Author: MDS Software Solutions Group

Git Advanced Techniques

poradniki

Git - Advanced Techniques and Workflow

Git is by far the most popular version control system in the world. Most developers know the basics - git add, git commit, git push - but the real power of Git lies in advanced techniques that can radically improve team productivity. In this article, we will discuss branching strategies, advanced commands, automation with hooks, commit conventions, and much more.

Branching Strategies#

Choosing the right branching strategy is one of the most important architectural decisions in a project. Each popular strategy has its pros and cons, and the choice should depend on team size, deployment frequency, and project specifics.

Git Flow#

Git Flow is a classic strategy proposed by Vincent Driessen. It is based on two main branches - main (production) and develop (development) - and three types of supporting branches.

# Initialize Git Flow
git flow init

# Start a new feature
git flow feature start login-page

# Work on the feature
git add .
git commit -m "feat: add login form component"

# Finish the feature - merge into develop
git flow feature finish login-page

# Start a release
git flow release start 1.2.0

# Finish the release - merge into main and develop
git flow release finish 1.2.0

# Production hotfix
git flow hotfix start fix-auth-bug
git flow hotfix finish fix-auth-bug

Advantages of Git Flow:

  • Clear separation of production and development code
  • Works well with planned release cycles
  • Easy hotfix management

Disadvantages of Git Flow:

  • Complexity - many branches to manage
  • Long integration cycles can lead to conflicts
  • Not suitable for CI/CD with frequent deployments

Trunk Based Development#

Trunk Based Development (TBD) is an approach where all developers work on a single main branch. Short-lived feature branches (maximum 1-2 days) are quickly merged back into trunk.

# Create a short-lived feature branch
git checkout -b feature/add-search main

# Fast iteration - small commits
git commit -m "feat: add search input component"
git commit -m "feat: add search API integration"

# Frequent rebase onto main
git fetch origin
git rebase origin/main

# Quick merge back to main
git checkout main
git merge feature/add-search
git push origin main

# Using feature flags to hide unfinished features
# if (featureFlags.isEnabled('new-search')) { ... }

Advantages of TBD:

  • Minimizes merge conflicts
  • Ideal for CI/CD
  • Enforces small, frequent commits

Disadvantages of TBD:

  • Requires a mature team and good practices
  • Necessity of using feature flags
  • More difficult management of multiple versions

GitHub Flow#

GitHub Flow is a simplified version, ideal for open source projects and teams practicing continuous delivery.

# Create a branch from main
git checkout -b feature/user-dashboard main

# Work and commits
git add .
git commit -m "feat: add user dashboard layout"
git push -u origin feature/user-dashboard

# Create a Pull Request on GitHub
gh pr create --title "Add user dashboard" --body "Implements #42"

# After review and approval - merge via web interface
# or from the command line
gh pr merge --squash

When to choose which strategy?

| Criterion | Git Flow | TBD | GitHub Flow | |-----------|----------|-----|-------------| | Team size | Large teams | Small-medium | Small-medium | | Release cycle | Planned | Continuous | Continuous | | Complexity | High | Low | Low | | CI/CD | Optional | Required | Recommended |

Interactive Rebase - Rewriting History#

Interactive rebase is one of the most powerful Git tools. It allows you to rewrite commit history - merging, splitting, reordering, and editing messages.

# Interactive rebase of the last 5 commits
git rebase -i HEAD~5

# Rebase onto main branch
git rebase -i main

In the editor, you will see a list of commits with available actions:

pick a1b2c3d feat: add user model
pick d4e5f6g fix: typo in user model
pick g7h8i9j feat: add user controller
pick j1k2l3m feat: add user routes
pick m4n5o6p fix: missing import

# Available commands:
# p, pick = use commit
# r, reword = use commit, but edit the message
# e, edit = use commit, but stop for editing
# s, squash = merge with previous commit
# f, fixup = like squash, but discard the message
# d, drop = remove commit

Common use cases:

# Combine fixes with the main commit (squash)
pick a1b2c3d feat: add user model
fixup d4e5f6g fix: typo in user model
pick g7h8i9j feat: add user controller
pick j1k2l3m feat: add user routes
fixup m4n5o6p fix: missing import

# Change the commit message (reword)
reword a1b2c3d feat: add user model with validation

# Split a commit into smaller ones
edit a1b2c3d feat: add user model and controller
# After stopping:
git reset HEAD~1
git add src/models/user.ts
git commit -m "feat: add user model"
git add src/controllers/user.ts
git commit -m "feat: add user controller"
git rebase --continue

Important: Never rebase commits that have already been pushed to a remote repository and are shared with other developers. Rewriting shared history leads to serious problems.

Cherry-pick - Selecting Commits#

Cherry-pick allows you to move a single commit from one branch to another without merging the entire branch.

# Move a single commit
git cherry-pick a1b2c3d

# Move a range of commits
git cherry-pick a1b2c3d..f6g7h8i

# Cherry-pick without automatic commit
git cherry-pick --no-commit a1b2c3d

# Resolving conflicts during cherry-pick
git cherry-pick a1b2c3d
# ... resolve conflicts ...
git add .
git cherry-pick --continue

# Abort cherry-pick
git cherry-pick --abort

Common use cases:

  • Moving hotfixes between branches
  • Selectively picking changes from feature branches
  • Backporting fixes to older versions

Git Bisect - Binary Search for Bugs#

git bisect helps find the commit that introduced a bug using binary search. It is extremely effective with a long commit history.

# Start a bisect session
git bisect start

# Mark the current commit as bad
git bisect bad

# Mark the last known good commit
git bisect good v1.0.0

# Git will select a commit in the middle - test and mark
git bisect good  # lub git bisect bad

# Continue until Git finds the guilty commit
# ...

# End the session
git bisect reset

Automating bisect with a test script:

# Automatic bisect with a test command
git bisect start HEAD v1.0.0
git bisect run npm test

# Bisect with a custom script
git bisect run ./scripts/test-regression.sh

# The script should return 0 (good) or 1 (bad)

Git Stash - Temporary Storage of Changes#

Stash allows you to temporarily set aside uncommitted changes to switch to other work.

# Basic stash
git stash

# Stash with a description
git stash push -m "work in progress: login form"

# Stash including new files (untracked)
git stash push -u -m "WIP: new feature with new files"

# List stashed changes
git stash list
# stash@{0}: On feature/login: work in progress: login form
# stash@{1}: On main: quick experiment

# Restore the last stash
git stash pop

# Restore a specific stash
git stash pop stash@{1}

# Restore without removing from stash
git stash apply stash@{0}

# Preview changes in stash
git stash show -p stash@{0}

# Create a branch from stash
git stash branch feature/from-stash stash@{0}

# Delete a stash
git stash drop stash@{0}

# Clear all stashed changes
git stash clear

Git Worktrees - Parallel Work on Multiple Branches#

Worktrees allow you to have multiple branches checked out simultaneously in different directories, sharing a single .git repository.

# Add a worktree for an existing branch
git worktree add ../project-hotfix hotfix/critical-bug

# Add a worktree with a new branch
git worktree add -b feature/new-ui ../project-new-ui main

# List active worktrees
git worktree list
# /home/user/project          a1b2c3d [main]
# /home/user/project-hotfix   d4e5f6g [hotfix/critical-bug]
# /home/user/project-new-ui   g7h8i9j [feature/new-ui]

# Work in a separate worktree
cd ../project-hotfix
git add .
git commit -m "fix: critical auth bypass"
git push origin hotfix/critical-bug

# Return to the main directory
cd ../project

# Remove a worktree
git worktree remove ../project-hotfix

# Clean up non-existent worktrees
git worktree prune

Advantages of worktrees:

  • No need to stash changes when switching context
  • Ability to simultaneously build/test different branches
  • Shared .git - saves disk space

Git Submodules - Dependency Management#

Submodules allow you to embed one Git repository inside another as a subdirectory.

# Add a submodule
git submodule add https://github.com/org/shared-lib.git libs/shared

# Clone a repository with submodules
git clone --recurse-submodules https://github.com/org/main-project.git

# Initialize submodules after cloning without --recurse
git submodule init
git submodule update

# Update a submodule to the latest version
cd libs/shared
git fetch
git checkout v2.0.0
cd ../..
git add libs/shared
git commit -m "chore: update shared-lib to v2.0.0"

# Update all submodules
git submodule update --remote --merge

# Remove a submodule
git submodule deinit libs/shared
git rm libs/shared
rm -rf .git/modules/libs/shared

Git Hooks - Process Automation#

Hooks are scripts that run automatically in response to Git events. They can be used for validation, code formatting, running tests, and many other tasks.

Pre-commit Hook#

#!/bin/sh
# .git/hooks/pre-commit

# Check code formatting with Prettier
echo "Running Prettier check..."
npx prettier --check "src/**/*.{ts,tsx,js,jsx}" || {
    echo "ERROR: Code is not formatted. Run: npx prettier --write ."
    exit 1
}

# Run the linter
echo "Running ESLint..."
npx eslint "src/**/*.{ts,tsx}" --max-warnings 0 || {
    echo "ERROR: ESLint errors. Fix them before committing."
    exit 1
}

# Check TypeScript types
echo "Running type check..."
npx tsc --noEmit || {
    echo "ERROR: TypeScript type errors."
    exit 1
}

echo "All checks passed!"

Commit-msg Hook#

#!/bin/sh
# .git/hooks/commit-msg

commit_msg=$(cat "$1")

# Validate Conventional Commits format
pattern="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,72}$"

if ! echo "$commit_msg" | head -1 | grep -qE "$pattern"; then
    echo "ERROR: Commit message does not comply with Conventional Commits."
    echo ""
    echo "Format: <type>(<scope>): <description>"
    echo ""
    echo "Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
    echo ""
    echo "Examples:"
    echo "  feat(auth): add OAuth2 login flow"
    echo "  fix(api): handle null response from payment gateway"
    echo "  docs: update API documentation"
    exit 1
fi

Managing Hooks with Husky#

Instead of manually managing hooks in .git/hooks/, you can use Husky:

# Install Husky
npm install -D husky

# Initialize
npx husky init

# Add a pre-commit hook
echo "npx lint-staged" > .husky/pre-commit

# lint-staged configuration in package.json
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{json,md,yml}": ["prettier --write"]
  }
}

Conventional Commits and Semantic Versioning#

Conventional Commits is a specification for the structure of commit messages that enables automatic changelog generation and version management.

Conventional Commits Format#

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Commit types and their impact on versioning:

| Type | Description | SemVer | |-----|------|--------| | feat | New feature | MINOR (1.x.0) | | fix | Bug fix | PATCH (1.0.x) | | docs | Documentation | - | | style | Formatting | - | | refactor | Refactoring | - | | perf | Performance optimization | PATCH | | test | Tests | - | | build | Build system | - | | ci | CI configuration | - | | chore | Other | - |

Breaking Changes cause a MAJOR version bump (x.0.0):

feat(api)!: change authentication endpoint response format

BREAKING CHANGE: The /auth/login endpoint now returns
a different JSON structure. See migration guide.

Automating Versioning#

# Install semantic-release
npm install -D semantic-release @semantic-release/changelog @semantic-release/git

# Configuration in .releaserc.json
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    "@semantic-release/git"
  ]
}

Monorepo Strategies#

Monorepo is an approach where multiple projects/packages reside in a single repository. Git offers tools that help efficiently manage such structures.

Sparse Checkout#

# Enable sparse checkout
git sparse-checkout init --cone

# Select only needed directories
git sparse-checkout set packages/frontend packages/shared

# Add another directory
git sparse-checkout add packages/backend

# List active directories
git sparse-checkout list

# Disable sparse checkout
git sparse-checkout disable

Monorepo Tools#

# Turborepo - build only changed packages
npx turbo run build --filter=...[HEAD~1]

# Nx - dependency analysis
npx nx affected --target=test --base=main

# pnpm workspaces - dependency management
pnpm install --filter @myorg/frontend

Filtering History in Monorepo#

# Logs only for a specific directory
git log --oneline -- packages/frontend/

# Diff only for a specific package
git diff main -- packages/api/

# Blame for a file in a package
git blame packages/shared/src/utils.ts

Git LFS - Large File Storage#

Git LFS solves the problem of storing large files (graphics, 3D models, binary data) in Git repositories.

# Install Git LFS
git lfs install

# Track large files
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/videos/**"

# Check configuration
cat .gitattributes
# *.psd filter=lfs diff=lfs merge=lfs -text
# *.zip filter=lfs diff=lfs merge=lfs -text

# Add .gitattributes to the repository
git add .gitattributes
git commit -m "chore: configure Git LFS for binary files"

# Normal Git usage - LFS works transparently
git add assets/logo.psd
git commit -m "feat: add new logo design"
git push

# Check LFS status
git lfs status
git lfs ls-files

Signing Commits (GPG/SSH)#

Signing commits allows you to verify the author's identity and ensures code integrity.

# Configure GPG signing
gpg --gen-key
gpg --list-secret-keys --keyid-format=long

# Set the key in Git
git config --global user.signingkey ABC123DEF456
git config --global commit.gpgsign true

# Sign a commit
git commit -S -m "feat: add verified feature"

# Verify signature
git log --show-signature

# Signing with SSH key (Git 2.34+)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

# Verify SSH signatures
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers
echo "user@example.com ssh-ed25519 AAAA..." >> ~/.ssh/allowed_signers

The .gitattributes File#

.gitattributes allows you to control how Git treats different file types - from line ending conversion to merge strategies.

# Normalize line endings
* text=auto

# Force LF for source files
*.js text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.json text eol=lf
*.yml text eol=lf
*.md text eol=lf

# Binary files - no conversion
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.woff2 binary

# Custom merge strategy for lock files
package-lock.json merge=ours
yarn.lock merge=ours
pnpm-lock.yaml merge=ours

# Diffing binary files
*.pdf diff=pdf

# Git LFS
*.psd filter=lfs diff=lfs merge=lfs -text

# Export - exclude from archives
.github/ export-ignore
tests/ export-ignore
.gitignore export-ignore

Advanced Merge Strategies#

Git offers different merge strategies that can be chosen depending on the situation.

# Default strategy (ort in newer versions)
git merge feature/new-ui

# Merge preserving history (no fast-forward)
git merge --no-ff feature/new-ui

# Ours strategy - keep our version
git merge -s ours legacy-branch

# Merge preferring our changes on conflicts
git merge -X ours feature/conflicting

# Merge preferring their changes on conflicts
git merge -X theirs feature/external-update

# Squash merge - one commit from the entire branch
git merge --squash feature/many-commits
git commit -m "feat: add complete user management module"

# Octopus merge - merge multiple branches at once
git merge feature/a feature/b feature/c

Rerere - Remembering Resolved Conflicts#

# Enable rerere (reuse recorded resolution)
git config --global rerere.enabled true

# Git remembers how you resolved conflicts
# and will automatically apply the same resolutions
# when it encounters identical conflicts in the future

# Preview remembered resolutions
git rerere diff

# Clear rerere cache
git rerere forget <plik>

Resolving Conflicts#

Conflicts are inevitable in team work. Here are proven approaches to resolving them.

# Check conflict status
git status

# Preview conflicts in a file
# <<<<<<< HEAD
# our version of the code
# =======
# their version of the code
# >>>>>>> feature/other-branch

# Use a merge tool
git mergetool

# Resolve using a specific version
git checkout --ours -- src/config.ts    # our version
git checkout --theirs -- src/config.ts  # their version

# Abort merge
git merge --abort

# Abort rebase
git rebase --abort

Best practices for resolving conflicts:

  1. Frequent merging/rebasing - the more often you synchronize with the main branch, the smaller the conflicts
  2. Small commits - it is easier to understand and resolve conflicts in small changes
  3. Team communication - inform when you are working on shared files
  4. Visual tools - VS Code, IntelliJ, Beyond Compare make it easier to resolve complex conflicts

Useful Aliases and Configuration#

# Useful aliases
git config --global alias.lg "log --oneline --graph --all --decorate"
git config --global alias.st "status -sb"
git config --global alias.co "checkout"
git config --global alias.br "branch -vv"
git config --global alias.unstage "reset HEAD --"
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.amend "commit --amend --no-edit"
git config --global alias.wip "commit -am 'WIP: work in progress'"

# Useful configuration
git config --global pull.rebase true
git config --global push.autoSetupRemote true
git config --global fetch.prune true
git config --global diff.algorithm histogram
git config --global merge.conflictstyle zdiff3
git config --global init.defaultBranch main
git config --global core.autocrlf input

Summary#

Advanced Git techniques can significantly improve team productivity:

  • Branching strategies - choose Git Flow, Trunk Based Development, or GitHub Flow depending on project needs
  • Interactive rebase - maintain a clean, readable commit history
  • Cherry-pick and bisect - precise change management and bug hunting
  • Stash and worktrees - efficient context switching
  • Hooks - automation of validation and quality control
  • Conventional Commits - structured messages enabling automation
  • Git LFS - handling large binary files
  • Signing commits - verifying identity and code integrity

The key is to gradually adopt these techniques and adapt them to the specifics of your project. You do not need to use everything at once - start with what will bring the greatest benefit to your team.

Need Support?#

At MDS Software Solutions Group, we help with:

  • Designing Git workflows tailored to your team
  • Configuring CI/CD with advanced branching strategies
  • Implementing Conventional Commits and automatic versioning
  • Migrating to monorepo and optimizing large repositories
  • Training in advanced Git techniques

Contact us to discuss your project!

Author
MDS Software Solutions Group

Team of programming experts specializing in modern web technologies.

Git - Advanced Techniques and Workflow for Development Teams | MDS Software Solutions Group | MDS Software Solutions Group