adnenre
#Git

Git Workflows Unleashed: Collaboration, Automation, and Best Practices

A comprehensive guide to mastering Git, from basic commands to advanced workflows..

Git is a distributed version control system created by Linus Torvalds in 2005. It tracks changes in source code during software development, allowing multiple developers to collaborate efficiently. Unlike centralized systems, every developer has a full copy of the repository, enabling offline work and robust branching/merging.

Key Features:

  • Distributed: Every clone is a full backup.
  • Branching and Merging: Lightweight branches encourage feature‑based development.
  • Data Integrity: Everything is checksummed (SHA‑1) before storage.
  • Staging Area: Changes can be selectively committed.
  • Speed: Most operations are local, making Git fast.

#2. Installation and Configuration

#Installation

  • Linux (Debian/Ubuntu):
    sudo apt update
    sudo apt install git
  • macOS: Install via Homebrew or download from git-scm.com.
    brew install git
  • Windows: Download the installer from git-scm.com. It includes Git Bash, a Unix‑like shell.

#Initial Configuration

Set your identity (required for commits):

git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

Other useful global settings:

git config --global core.editor "code --wait"   # VS Code as editor
git config --global merge.tool vimdiff           # merge tool
git config --global color.ui auto                # colored output

View configuration:

git config --list

#3. Basic Git Workflow

#Creating a Repository

# Initialize a new repo
git init my-project
cd my-project

# Clone an existing repo
git clone https://github.com/user/repo.git

#Making Changes

# Check status
git status

# Add files to staging
git add file.txt
git add .               # all changes

# Commit staged changes
git commit -m "Add initial version of file.txt"

# View commit history
git log --oneline

#Working with Remotes

# Add a remote
git remote add origin https://github.com/user/repo.git

# Push changes
git push origin main

# Pull latest changes
git pull origin main

# Fetch changes without merging
git fetch origin

#4. Branching and Merging

Branches are lightweight pointers to commits. The default branch is usually main (or master).

#Creating and Switching Branches

# Create a new branch
git branch feature/login

# Switch to it
git checkout feature/login

# Create and switch in one command
git checkout -b feature/login

#Merging

# Switch to target branch (e.g., main)
git checkout main

# Merge the feature branch
git merge feature/login

By default, Git performs a fast‑forward merge if possible; otherwise, it creates a merge commit.

#Deleting Branches

# Delete local branch (after merge)
git branch -d feature/login

# Delete remote branch
git push origin --delete feature/login

#5. Merge Conflicts: Causes and Resolution

A merge conflict occurs when two branches modify the same part of the same file, and Git cannot automatically decide which change to keep.

#Example Conflict Scenario

  1. On main, you edit README.md and commit.
  2. On feature/docs, you also edit the same line of README.md and commit.
  3. Merging feature/docs into main produces a conflict.
git checkout main
git merge feature/docs

Output:

Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

#Resolving the Conflict

Open the conflicted file. Git marks the conflicting sections:

<<<<<<< HEAD
This line is from main.
=======
This line is from feature/docs.
>>>>>>> feature/docs
  • HEAD is your current branch (main).
  • The section between ======= and >>>>>>> is from the incoming branch.

Edit the file to keep the desired changes, remove the conflict markers, then:

git add README.md
git commit -m "Merge feature/docs: resolved conflict in README"

#Using a Merge Tool

Launch a visual merge tool:

git mergetool

Configure your preferred tool (e.g., meld, vimdiff).


#6. Rebasing: When and How

Rebasing rewrites commit history by applying commits from one branch onto the tip of another. It creates a linear history, avoiding merge commits.

#Basic Rebase

# While on feature branch
git rebase main

This takes all commits on feature that are not in main and replays them on top of main.

#Interactive Rebase

Use -i to squash, reorder, or edit commits:

git rebase -i HEAD~3   # last 3 commits

An editor opens; you can change pick to squash, edit, etc.

#Golden Rule of Rebasing

Never rebase commits that have been pushed to a shared repository. Rebasing rewrites history, causing trouble for collaborators.


#7. Cherry-Picking Commits

cherry-pick applies a specific commit (from any branch) to your current branch. Useful for selectively bringing bug fixes or features.

#Example

Suppose a bug was fixed on develop in commit a1b2c3d, and you need that fix on main without merging the whole branch.

git checkout main
git cherry-pick a1b2c3d

If conflicts arise, resolve them and continue:

git add resolved-file.txt
git cherry-pick --continue

Or abort:

git cherry-pick --abort

#Real‑Use Case

  • Hotfix on production: you fix a bug on a hotfix branch, then cherry‑pick that commit into main and develop.
  • Backporting a feature to an older release.

#8. Stashing Changes

git stash temporarily saves uncommitted changes, allowing you to switch branches without committing.

# Save current changes
git stash save "WIP: login form"

# List stashes
git stash list

# Apply most recent stash (keeps it)
git stash apply

# Apply and drop
git stash pop

# Drop a specific stash
git stash drop stash@{2}

#9. Working with Remotes (GitHub/GitLab)

#Cloning a Repository

git clone https://github.com/username/repo.git

#Pushing to a Remote

git push origin main

If your local branch has a different name:

git push origin local-branch:remote-branch

#Pull Requests / Merge Requests

  • On GitHub/GitLab, you create a PR/MR from your feature branch to main.
  • Reviewers comment, and after approval, the branch is merged (often via the web UI).

#Keeping Fork Updated

git remote add upstream https://github.com/original/repo.git
git fetch upstream
git checkout main
git merge upstream/main
git push origin main

#10. Git Hooks: Automating Your Workflow

Git hooks are scripts that run automatically on certain events. They reside in .git/hooks/ (rename .sample files to activate).

#Client-Side Hooks

HookTrigger
pre-commitBefore commit message is requested. Use for linting, formatting.
prepare-commit-msgBefore commit message editor. Modify default message.
commit-msgAfter commit message is saved. Validate message format.
pre-pushBefore push. Run tests or checks.

Example pre-commit hook (runs linter and prevents commit if it fails):

#!/bin/sh
# .git/hooks/pre-commit
npm run lint
if [ $? -ne 0 ]; then
  echo "Linting failed. Commit aborted."
  exit 1
fi

Make it executable: chmod +x .git/hooks/pre-commit.

Example commit-msg hook (enforces conventional commit format):

#!/bin/sh
# .git/hooks/commit-msg
msg=$(cat $1)
if ! echo "$msg" | grep -qE "^(feat|fix|docs|style|refactor|test|chore):"; then
  echo "Commit message must start with type: feat|fix|docs|..."
  exit 1
fi

#Server-Side Hooks

On the remote repository (GitHub/GitLab), you can use webhooks or CI scripts instead of traditional server‑side hooks. However, self‑hosted Git servers can use hooks like post-receive.

Example post-receive hook (deploy after push):

#!/bin/bash
# .git/hooks/post-receive
while read oldrev newrev refname
do
  branch=$(git rev-parse --symbolic --abbrev-ref $refname)
  if [ "$branch" == "main" ]; then
    git --work-tree=/var/www/html checkout -f
    echo "Deployed to production"
  fi
done

#11. Integration with CI/CD (GitHub Actions, GitLab CI)

Modern platforms offer CI/CD directly integrated with Git.

#GitHub Actions Example

Create .github/workflows/ci.yml:

name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      - run: npm test
      - run: npm run build

#GitLab CI Example

Create .gitlab-ci.yml:

stages:
  - test
  - build

test:
  stage: test
  script:
    - npm ci
    - npm test

build:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/

#Webhooks for External Services

  • GitHub/GitLab can send HTTP POST requests to a URL (e.g., a deployment service) on events like push, pull_request.
  • Useful for triggering builds, notifications, or updating documentation.

#12. Conventional Commits

Conventional Commits is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history, which makes it easier to write automated tools on top of.

#Why Conventional Commits?

  • Automatic changelog generation: Tools can generate CHANGELOGs from commits.
  • Semantic versioning: Determine the next version number based on the types of commits (fix → PATCH, feat → MINOR, breaking changes → MAJOR).
  • Clear communication: Team members quickly understand the purpose of each commit.
  • Triggering builds and deployments: Some CI tools can react to commit types.

#Specification

The commit message should be structured as follows:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Types:

  • feat – A new feature for the user.
  • fix – A bug fix for the user.
  • docs – Documentation changes.
  • style – Code style changes (formatting, missing semi‑colons, etc.) – no production code change.
  • refactor – Code change that neither fixes a bug nor adds a feature.
  • perf – Code change that improves performance.
  • test – Adding missing tests or correcting existing tests.
  • chore – Changes to the build process or auxiliary tools.

Scope: Optional, can be anything specifying the place of the commit change (e.g., auth, api).

Breaking change: Indicated by adding a ! after the type/scope, or a footer BREAKING CHANGE:.

#Examples

feat: add user login endpoint

fix(api): handle null response from payment gateway

docs: update README with setup instructions

refactor!: drop support for Node 14

BREAKING CHANGE: The `validate` function now returns a promise instead of a boolean.

#Tools and Automation

Commitlint – enforces conventional commits via a pre‑commit hook or CI.

npm install --save-dev @commitlint/{config-conventional,cli}
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

Then add a commit-msg hook (using Husky):

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'

Standard Version or semantic-release – automatically bump version, generate changelog, and create a release based on commit messages.

Example with semantic-release (GitHub Actions):

- name: Release
  uses: cycjimmy/semantic-release-action@v4
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

#13. Versioning and Tagging

#Semantic Versioning

SemVer uses the format MAJOR.MINOR.PATCH:

  • MAJOR – incompatible API changes
  • MINOR – backwards‑compatible new functionality
  • PATCH – backwards‑compatible bug fixes

#Relation with Conventional Commits

Using Conventional Commits, you can automatically derive the next version:

  • fix → PATCH
  • feat → MINOR
  • BREAKING CHANGE or ! → MAJOR

#Tagging Releases

Tags mark specific points in history, usually for releases.

#Creating a Tag

# Lightweight tag (just a pointer)
git tag v1.0.0

# Annotated tag (recommended) – stores author, date, message
git tag -a v1.0.0 -m "Release version 1.0.0"

#Pushing Tags

git push origin v1.0.0
git push --tags            # push all tags

#Deleting Tags

# Local
git tag -d v1.0.0

# Remote
git push origin --delete v1.0.0

#14. Real-World Scenarios

#Scenario 1: Hotfix on Production with Cherry-Pick

Situation: A critical bug is discovered in production (tag v1.0.0). You need to fix it quickly and deploy a new version (v1.0.1).

Steps:

  1. Create a hotfix branch from the production tag:

    git checkout -b hotfix v1.0.0
  2. Fix the bug, commit using conventional commit:

    git add .
    git commit -m "fix: prevent crash on empty input"
  3. Test, then tag the fix:

    git tag v1.0.1
    git push origin v1.0.1
  4. Deploy v1.0.1 to production.

  5. Cherry‑pick the fix into main and develop:

    git checkout main
    git cherry-pick hotfix   # or the commit hash
    git push origin main
    
    git checkout develop
    git cherry-pick hotfix
    git push origin develop
  6. Delete the hotfix branch:

    git branch -d hotfix

#Scenario 2: Resolving a Complex Merge Conflict

Situation: A feature branch (feature/payment) has been worked on for weeks. Meanwhile, main has many changes. When trying to merge, there are multiple conflicting files.

Resolution Strategy:

  1. Update feature/payment with latest main to resolve conflicts locally before merging into main:
    git checkout feature/payment
    git merge main
  2. For each conflicted file, open and resolve manually or use a merge tool.
  3. After resolving all, commit the merge:
    git add .
    git commit -m "Merge branch 'main' into feature/payment"
  4. Now feature/payment is up‑to‑date. Test thoroughly.
  5. Finally, merge into main (should be conflict‑free):
    git checkout main
    git merge feature/payment
    git push origin main

#Scenario 3: Automating Code Quality with Pre-Commit Hooks

Situation: The team wants to enforce code formatting and conventional commits.

Implementation:

  1. Install husky, lint-staged, commitlint.
  2. Configure package.json:
    {
      "scripts": {
        "lint": "eslint src --fix",
        "format": "prettier --write src"
      },
      "lint-staged": {
        "*.js": ["npm run lint", "npm run format"]
      }
    }
  3. Set up Husky hooks:
    npx husky add .husky/pre-commit "npx lint-staged"
    npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"
  4. On every commit, staged files are linted and formatted; if commit message doesn’t follow conventional commits, commit aborts.

#Scenario 4: Triggering a Deployment via Webhook

Situation: Whenever a new tag is pushed, automatically deploy to a staging server.

Implementation (using GitHub webhook + a simple Node.js server):

  1. On GitHub, add a webhook pointing to https://your-server.com/webhook with content type application/json, trigger on “Pushes”.
  2. On your server, create an endpoint that listens for POST requests, verifies the secret, and if the payload contains a new tag, runs a deployment script. Example (Node.js/Express):
    app.post('/webhook', (req, res) => {
      const event = req.headers['x-github-event'];
      const payload = req.body;
      if (event === 'push' && payload.ref.startsWith('refs/tags/')) {
        exec('./deploy.sh', (err, stdout) => {
          console.log(stdout);
        });
      }
      res.sendStatus(200);
    });

#15. Best Practices and Tips

  • Write meaningful commit messages: Use imperative tense (“Add feature”, “Fix bug”). Follow Conventional Commits.
  • Commit often, push regularly.
  • Keep branches short‑lived to avoid painful merges.
  • Never rebase public branches.
  • Use .gitignore to exclude build artifacts, secrets, and IDE files.
  • Sign your commits with GPG for authenticity.
  • Use Git aliases for common commands:
    git config --global alias.co checkout
    git config --global alias.br branch
    git config --global alias.st status
  • Leverage git reflog to recover lost commits.
  • Protect important branches on GitHub/GitLab (require PR reviews, status checks).
  • Automate versioning with tools like semantic-release to reduce manual work.

This guide covers the essential Git skills every developer needs, with practical examples you’ll face daily. By adopting Conventional Commits and integrating hooks, you’ll create a more maintainable and automated development workflow. Practice these scenarios, and Git will become a powerful ally in your development journey.

Share this post