Git Best Practices for Development Teams

Git is an essential tool for collaborative software development. Establishing clear best practices helps maintain a clean, consistent, and efficient workflow for teams. This document outlines common practices for feature branching, integration, and pull requests.

1. Feature Branch Workflow

Always work on dedicated feature branches. This isolates development efforts, prevents conflicts in the main codebase, and simplifies code reviews.

Creating a Feature Branch

Before starting new work, always create a new branch from the latest main (or master, develop) branch.

# First, ensure your main branch is up-to-date
git checkout main
git pull origin main
 
# Then, create and switch to your new feature branch
git checkout -b feature/my-new-feature-name

Naming Conventions: Use clear, descriptive names. Common patterns include feature/<feature-name>, bugfix/<bug-description>, or chore/<task-description>.

2. Keeping Your Branch Up-to-Date (Fetch and Rebase)

Before pushing your feature branch for review, it’s crucial to integrate the latest changes from the main branch. Rebasing is generally preferred over merging for feature branches as it creates a cleaner, linear history.

Fetch Latest Changes from Origin

Regularly fetch the latest changes from the remote repository.

git fetch origin

Rebase Your Feature Branch

Before pushing, rebase your feature branch onto the main branch. This rewrites your branch’s history, placing your commits after the latest commits from main.

# First, ensure you are on your feature branch
git checkout feature/my-new-feature-name
 
# Rebase onto the main branch
git rebase origin/main

If conflicts occur during rebase, resolve them, git add the resolved files, and then continue the rebase with git rebase --continue. If you get into trouble, you can always abort with git rebase --abort.

Force Push After Rebase (Use with Caution!)

After a successful rebase, you’ve rewritten your branch’s history. If you’ve already pushed your branch to the remote, you’ll need to force push. Only force push branches that you are exclusively working on. Never force push to shared branches (like main).

git push --force-with-lease origin feature/my-new-feature-name

--force-with-lease is safer than --force as it prevents overwriting work on the remote if others have pushed changes to the same branch in the meantime.

3. Pull Requests (PRs)

Once your feature is complete and rebased, create a Pull Request to merge your changes into the main branch.

Create a Pull Request

  • Push your feature branch to the remote:
    git push origin feature/my-new-feature-name
  • Go to your Git hosting platform (GitHub, GitLab, Bitbucket) and create a new Pull Request from your feature/my-new-feature-name branch to main.

Best Practices for PRs:

  • Descriptive Title and Description: Clearly explain what the PR does, why it was needed, and any relevant context.
  • Link Issues/Tasks: Reference any related issue trackers (e.g., Jira tickets, GitHub issues).
  • Code Review: Engage in constructive code reviews. Provide clear feedback and respond to comments promptly.
  • Squash and Merge/Rebase Merge: After review and approval, the team should decide on a merge strategy (e.g., squash commits into a single commit for a cleaner main history, or rebase merge to preserve a linear history without an extra merge commit).

4. Example Workflow Summary

  1. Start New Work:
    git checkout main
    git pull origin main
    git checkout -b feature/my-new-feature
  2. Develop and Commit Locally:
    # Make changes
    git add .
    git commit -m "feat: implement part of feature X"
    # Repeat as needed
  3. Update and Prepare for PR:
    git fetch origin
    git rebase origin/main # Resolve conflicts if any
    git push --force-with-lease origin feature/my-new-feature
  4. Create Pull Request:
    • Open PR from feature/my-new-feature to main on your Git platform.
  5. Review, Approve, and Merge.

5. Git Tagging for Releases

Tags are used to mark specific points in a repository’s history as being important, typically for marking release versions (e.g., v1.0.0, v2.1.3).

There are two main types of tags in Git:

  • Lightweight Tags: A simple pointer to a specific commit. It’s just a name for a commit, with no other information stored.
  • Annotated Tags: A full object in the Git database. They are checksummed; contain the tagger’s name, email, and date; have a tagging message; and can be signed and verified with GNU Privacy Guard (GPG). It is recommended to use annotated tags for releases.

Creating an Annotated Tag

For releases, always use an annotated tag.

# Syntax: git tag -a <tagname> -m "<tag_message>"
git tag -a v1.0.0 -m "Initial release of version 1.0.0"

Creating a Lightweight Tag

If you want a temporary tag or don’t need the extra information, you can use a lightweight tag.

# Syntax: git tag <tagname>
git tag v1.0.0-rc1

Pushing Tags to Remote

By default, git push does not push tags. You need to explicitly push them.

# Push a single tag
git push origin v1.0.0
 
# Push all tags at once
git push origin --tags

Listing Tags

You can list all existing tags.

git tag

You can also search for tags that match a particular pattern.

# List all tags starting with v1.
git tag -l "v1.*"

Deleting Tags

If you need to delete a tag, you can do so locally and remotely.

# Delete a local tag
git tag -d v1.0.0-rc1
 
# Delete a remote tag
git push origin --delete v1.0.0-rc1

6. Example Workflow Summary

  1. Start New Work:
    git checkout main
    git pull origin main
    git checkout -b feature/my-new-feature
  2. Develop and Commit Locally:
    # Make changes
    git add .
    git commit -m "feat: implement part of feature X"
    # Repeat as needed
  3. Update and Prepare for PR:
    git fetch origin
    git rebase origin/main # Resolve conflicts if any
    git push --force-with-lease origin feature/my-new-feature
  4. Create Pull Request:
    • Open PR from feature/my-new-feature to main on your Git platform.
  5. Review, Approve, and Merge.
  6. Tag Release (on main after merge):
    # Ensure you are on the main branch and have the latest changes
    git checkout main
    git pull origin main
     
    # Create and push the tag
    git tag -a v1.1.0 -m "Release version 1.1.0"
    git push origin v1.1.0

By following these practices, teams can maintain a robust, collaborative, and easy-to-understand Git history.