DEV Community

Cover image for Enhancing Git Security and Workflow: A Comprehensive Guide to Signed Commits and a Linear History
Waffeu Rayn
Waffeu Rayn

Posted on

Enhancing Git Security and Workflow: A Comprehensive Guide to Signed Commits and a Linear History

In the world of version control, maintaining a secure and clean project history is crucial, especially for collaborative and open-source projects. This guide breaks down two essential best practices—signed commits and a linear history—and provides detailed steps on how to implement them.


1. The Power of Signed Commits

A signed commit is cryptographically verified to have been made by the person to whom it is attributed. This is a critical security measure in modern software development. It prevents malicious actors from impersonating trusted contributors and provides an undeniable audit trail.

How It Works

Signed commits rely on asymmetric cryptography using a public/private key pair. You use your private key to digitally sign your commits, and others can use your publicly available key to verify the signature.

  • Private Key: A secret key stored securely on your local machine, often protected by a strong passphrase. You use this to create a digital signature for each commit.
  • Public Key: This key is derived from your private key and can be shared freely. You upload it to your GitHub account.

When you push a signed commit, GitHub uses your public key to verify that the signature on the commit was created by your private key. If the signatures match, the commit gets a green "Verified" badge, guaranteeing non-repudiation.

How to Implement Signed Commits

Follow these steps to generate a GPG key, add it to GitHub, and configure Git to automatically sign your commits.

  1. Install GPG: Ensure you have GPG (GNU Privacy Guard) installed on your machine.
  * **macOS:** `brew install gpg`
  * **Windows:** Download from the [GnuPG website](https://www.gnupg.org/download/index.html).
  * **Linux (Debian/Ubuntu):** `sudo apt-get install gpg`
Enter fullscreen mode Exit fullscreen mode
  1. Generate a GPG Key:
    Use this command to generate a new key. Follow the prompts to select the key type, size, and set a strong passphrase.

    gpg --full-generate-key
    
  2. Export Your Public Key:
    First, list your secret keys to get your key ID.

    gpg --list-secret-keys --keyid-format=long
    

    Your key ID will be a 16-character string in the output (e.g., 3AA5C34371567BD2). Then, export the public key.

    gpg --armor --export [your-key-ID]
    

    Copy the full output, including the -----BEGIN PGP PUBLIC KEY BLOCK----- and -----END PGP PUBLIC KEY BLOCK----- lines.

  3. Add the Key to GitHub:
    Go to your GitHub Settings > SSH and GPG keys and click "New GPG key." Paste the public key you just copied and save. Your account is now ready to verify signed commits.

  4. Configure Git:
    Tell Git which key to use for signing and enable automatic commit signing globally.

    # Tell Git about your signing key
    git config --global user.signingkey [your-key-ID]
    
    # Automatically sign all commits
    git config --global commit.gpgsign true
    

From now on, every commit you make will be automatically signed. If you prefer to sign commits on a per-commit basis, you can use the -S flag: git commit -S -m "Your commit message".

Note: For GitHub Actions, you cannot use a local GPG key, as the runner is a temporary virtual machine. Instead, GitHub automatically signs commits made by the github-actions[bot] user via the GitHub API, providing a verified commit for your automated workflows.


2. The Discipline of a Linear History

In addition to securing individual commits, maintaining a clean project history is vital for team collaboration. A linear history ensures your Git commits follow a single, straight line without the usual branching and merging. It makes a project’s history incredibly easy to read and manage.

How It's Achieved: The Rebase Workflow

A linear history is primarily achieved by using git rebase instead of git merge. This rewrites your branch's history to place your commits on top of the latest version of the main branch.

Here is a typical rebase workflow for a feature branch:

  1. Update your local main branch:

    git checkout main
    git pull
    
  2. Move your feature branch's commits on top of main:

    git checkout my-feature-branch
    git rebase main
    

If you encounter conflicts during the rebase, resolve them, add the changes (git add .), and then run git rebase --continue. Once the rebase is complete, your feature branch will have a clean, linear history, and you can push your changes.

Interactive Rebase for Cleaning Up Commits

Use git rebase -i to clean up your commit history before pushing. This is perfect for condensing multiple work-in-progress commits into a single, meaningful commit.

# Rebase the last 5 commits on your current branch
git rebase -i HEAD~5
Enter fullscreen mode Exit fullscreen mode

Pushing Rebased Commits

Because git rebase rewrites history, a standard git push will fail. You must use a force push, but the safest way is with the --force-with-lease flag. This prevents you from accidentally overwriting someone else's work.

git push --force-with-lease
Enter fullscreen mode Exit fullscreen mode

Merging in a Linear Workflow

In a linear workflow, you still use a "merge" action, but you do it via pull requests on platforms like GitHub, which offer specific types that avoid creating a merge commit:

  • Fast-Forward Merge: If your feature branch is a direct extension of the main branch, Git simply moves the main pointer forward.
  • Squash and Merge: This combines all your commits into a single, new commit on the main branch, which is great for keeping the main branch history clean.
  • Rebase and Merge: This performs a rebase and then a fast-forward merge, preserving all of your individual commits in a clean, linear order.

3. Enforcing Best Practices with Branch Protection Rules

Once you understand these concepts, you can enforce them using GitHub's branch protection rules. These rules are a powerful administrative tool for ensuring the integrity of your key branches (like main or develop).

To configure them, navigate to Settings > Branches in your GitHub repository and click "Add rule."

The Most Useful Rules

  • Require signed commits: This prevents anyone from merging a pull request that contains an unsigned commit. It's a non-negotiable step for maintaining a secure project.
  • Require linear history: This prevents the use of standard merge commits, forcing all merges to be a rebase or squash. It's an essential setting for maintaining a clean history.
  • Restrict push access: This allows you to specify that only certain users or teams (like administrators) can push directly to a protected branch, forcing all other changes to go through a pull request.
  • Restrict who can create and delete branches: This provides a safety net against accidental or malicious deletions by preventing unauthorized users from creating branches that match a protection rule or from deleting a critical branch.

Top comments (0)