Git and GitHub Best Practices at Necko Technologies
by Jérôme Dauge, Co-Founder
Git and GitHub Best Practices at Necko Technologies
In today's fast-paced development environment, maintaining a clean, understandable codebase history is crucial for team collaboration and project maintenance. At Necko Technologies, we've implemented a set of Git and GitHub practices that have significantly improved our development workflow. These practices help us maintain code quality, streamline releases, and make our Git history a valuable documentation resource rather than a confusing mess.
The Problem with Unstructured Commits
We've all seen (or created) Git histories that look like this:

This kind of history provides little to no value when you need to understand what changed and why. It's difficult to generate meaningful changelogs, track features, or identify when bugs were introduced. To address this problem, we've standardized on several best practices.
Conventional Commits: Adding Meaning to Commit Messages
We follow the Conventional Commit specification, which provides a lightweight convention on top of commit messages. Using this standard gives our commit messages clear structure and intent, making our repository history readable and useful.
Each commit message follows this format:
<type>(<scope>): <subject>
<body>
Types
We use the following types to categorize our commits:
feat
: A new featurefix
: A bug fixbuild
: Changes to build system or dependenciesci
: Changes to CI configuration files and scriptsdocs
: Documentation changes onlyperf
: Performance improvementsrefactor
: Code changes that neither fix bugs nor add featureschore
: Maintenance tasks that don't modify source filestest
: Adding or correcting testsmeta
: Repository metadata changes
Scopes for Better Context
We emphasize using scopes to provide additional context. Different commit types typically use specific scope conventions:
- For
feat
,fix
,revert
, andrefactor
: We use the package/module name where changes were made - For
docs
: Usuallyreadme
or the specific package with updated documentation - For
deps
: The programming language (e.g.,node
,python
) orgithub-action
- For
ci
: Typically the name of the CI job
Handling Formatting Commits
One particularly useful practice we've adopted is properly handling formatting commits. When you run tools like black
or rome
across the entire codebase, it can generate a commit that touches numerous files but only for formatting purposes. This can obscure the actual history of the code when using git blame
.
We solve this with a .git-blame-ignore-revs
file containing the SHA-1 hashes of formatting commits:
# Formatting with black v22.3.0
abd05e89c208c29e6d8afd9170b8326b540a1207
# Prettier reformatting
e7f9e82a0f6b5b17927639e9a77f882a9a57013f
This file is natively supported by Git and GitHub, though you need to enable it in your global Git config:
git config --global blame.ignoreRevsFile .git-blame-ignore-revs
Building a Clean, Linear History
While conventional commits provide structure, we also need to keep our repository history clean and focused. We achieve this through a disciplined approach to feature branches and pull requests.
Feature Branch Workflow
Our development process begins with feature branches:
- Create a new branch for each feature, fix, or improvement
- Make frequent commits in this branch (they don't need to follow conventions)
- Push regularly to back up your work
- When ready, create a pull request

Structured Pull Requests
Pull request titles are crucial in our workflow. They must follow the conventional commit format since they'll become the commit message in the main branch. We enforce this using a GitHub action that validates PR titles.
Squash Merging: The Key to Clean History
When merging pull requests, we exclusively use squash merging. This condenses all the commits from the feature branch into a single, well-formatted commit on the main branch.
Without squash merging, our history would look messy:

With squash merging, we get a clean, linear history:

Simplified Branch Strategy
We keep our branch strategy intentionally simple:
- One long-lived branch:
main
- Short-lived feature branches that are deleted after merging
Every push to main
triggers a non-production deployment, while releases trigger production deployments.
Automated Release Management
For release management, we leverage release-please, which automates version bumping and changelog generation based on conventional commits.
The process works like this:
- We commit changes following conventional commit standards
- Release-please creates a PR that includes version bumps and changelog updates
- When we're ready to release, we merge the PR
- GitHub Actions create a new release tag and trigger deployment
This results in a clean history with clear release points:

Customizing Changelog Sections
We've customized our release-please configuration to include additional commit types in our changelogs:
[
{
"type": "feat",
"section": "Features",
"hidden": false
},
{
"type": "fix",
"section": "Bug Fixes",
"hidden": false
},
{
"type": "chore",
"section": "Miscellaneous Chores",
"hidden": true
},
{
"type": "revert",
"section": "Reverts",
"hidden": false
},
{
"type": "docs",
"section": "Documentation",
"hidden": false
},
{
"type": "refactor",
"section": "Code Refactoring",
"hidden": false
},
{
"type": "deps",
"section": "Dependencies",
"hidden": false
},
{
"type": "ci",
"section": "Continuous Integration",
"hidden": false
}
]
This configuration lives in release-please-config.json
when using the manifest
release type, or in the changelog-types
field of the release-please
job in your GitHub Actions workflow.
Benefits We've Seen
Since implementing these practices, we've experienced several significant improvements:
- Better collaboration - Team members can quickly understand what changed and why
- Automated versioning - Semantic versioning is automatically determined from our commit messages
- Comprehensive changelogs - Generated automatically with minimal effort
- Cleaner git history - Making it easier to track down when and why changes were introduced
- Streamlined releases - Less manual work and fewer errors in the release process
Getting Started in Your Team
If you're interested in adopting these practices for your team, we recommend starting with:
- Standardizing on conventional commits
- Setting up branch protection rules to enforce PR reviews
- Configuring squash merging as the default merge strategy
- Adding the release-please GitHub action to your workflow
The initial adjustment period takes a few weeks, but the long-term benefits to your development workflow are well worth the investment.
Conclusion
A disciplined approach to Git and GitHub workflows might seem like extra work initially, but it has proven invaluable for our team at Necko Technologies. By combining conventional commits, feature branches, squash merging, and automated releases, we've created a development process that scales well with team size and project complexity.
These practices have turned our Git history from a confusing jumble of meaningless commits into a valuable resource that helps us understand our codebase's evolution and automatically drives our release process.