Collaborating with Git and GitHub
We’ve already explored how to use Git and GitHub from an individual perspective, covering the most common workflows for personal projects. However, the true power of Git and GitHub emerges when teams collaborate on shared code.
The Challenge of Team Collaboration
While Git and GitHub facilitate teamwork, successful collaboration requires careful coordination. Each developer must:
- Stay aware of changes made by teammates
- Ensure their contributions integrate smoothly with the existing codebase
- Avoid accidentally overwriting or conflicting with others’ work
Without proper coordination, developers can unintentionally break the shared codebase by introducing incompatible changes or undoing another’s work.
Teams typically adopt specific workflows to help members collaborate effectively. An ideal team workflow should:
- Keep everyone informed about ongoing development efforts
- Verify that new code integrates properly with the existing codebase
- Provide clear processes for contribution and review
One of the most popular team-based approaches is the GitHub flow (also called the feature branch workflow). This straightforward workflow has gained popularity due to its simplicity and effectiveness.
The GitHub Flow
The GitHub flow is a streamlined workflow designed for team collaboration using Git branches and GitHub pull requests. This approach effectively:
- Isolates each developer’s work to prevent code conflicts
- Creates visibility across the team about ongoing development
- Establishes a code review process that ensures quality before code reaches production
Here’s an overview of the workflow. We’ll dig in deeper into each step in the following sections.
- The
main
branch represents production code- code in the
main
branch should always be deployable - all changes to the
main
branch should be made through pull requests - the
main
branch should be protected to prevent direct pushes
- code in the
- Developers create a new branch for each task
- branch off from
main
to work on features, fixes, or improvements - branch names should be descriptive and follow a consistent naming convention
- branch off from
- Developers make changes in their branches
- work on the task in isolation, committing changes to the branch
- keep commits small and focused on a single change or feature
- write clear commit messages that explain the purpose of each change
- use
git pull
to keep the branch up to date withmain
- push changes to the remote branch on GitHub
- Open a pull request when ready for review or production
- when the branch is ready for review, open a pull request on GitHub
- provide a clear description of the changes and their purpose
- tag relevant team members for review
- Review and discuss
- team members provide feedback in the pull request
- you respond to feedback by making changes or explaining your decisions
- Test thoroughly
- all changes should pass automated tests
- when possible, team members should manually test the changes
- Merge after approval
- after sufficient review and approval from teammates, merge your branch into
main
- resolve any merge conflicts that arise
- ensure the code is up to date with
main
before merging - squash commits if necessary to keep the commit history clean
- after sufficient review and approval from teammates, merge your branch into
- Deploy changes
- once merged, deploy the changes to production
- ensure the deployment process is automated and reliable
- monitor the deployment for any issues or bugs
Source
flowchart TD B[main branch] --> C[Create feature branch] C --> D[Make changes in feature branch] D -->|"When ready"| E[Open/Update Pull Request] E --> F[Review and discuss] F -->|"Changes needed"| D F -->|"Approved"| G[Test thoroughly] G -->|"Tests fail"| D G -->|"Tests pass"| H[Merge to main] H --> I[Deploy changes] B -.pull from main.-> D
Understanding Git Branches
Git’s core purpose is to store a series of snapshots (commits) of your project over time, creating a commit history. Without branching, this history is linear—just one commit after another in the main branch:
Source
gitGraph commit id: "1" commit id: "2" commit id: "3" commit id: "4" commit id: "5"
Branches allow the commit history to diverge into parallel paths, each with its own unique commits:
Source
gitGraph commit commit branch feature1 checkout feature1 commit commit checkout main commit branch feature2 checkout feature2 commit checkout feature1 commit commit checkout feature2 commit commit checkout main commit
Key Branch Concepts:
- Each new branch must start from an existing commit
- Up to the starting commit, branches share history
- After the starting point, branch histories diverge
For example, in the diagram above, the feature1
branch was created from the main
branch at commit 1-*
. Both branches share commits 0-*
and 1-*
but their histories differ after that point.
Branches create isolated environments where developers can work without affecting others. This isolation:
- Prevents code conflicts between team members
- Allows experimentation without risking the main codebase
- Enables multiple features to be developed simultaneously
- Facilitates organized code reviews before integration
This branch-based workflow ensures that changes are properly validated before they affect the production environment.
Read git branches: intuition & reality by Julia Evans for a deeper understanding of how branches work in Git.
Working with Branches
Let’s explore how to work with branches in a practical scenario.
Imagine we’re working in a repository with a single main branch containing a few commits:
git log --oneline
ed1523e (HEAD -> main) Add document title39cfcb0 Add Hello, World header5c19c53 Create index.html
We can list all branches with:
git branch
* main
Task: Adding CSS
We’ve been assigned to add CSS to the application. Following good practices, we’ll create a new branch for this work:
git branch add-css
Checking our branches shows:
git branch
add-css* main
The asterisk (*
) indicates that main
is still our active branch. To work in our new branch, we need to switch to it:
git checkout add-css
At this point, both branches point to the same commit.
Let’s modify the HTML to link a stylesheet and create the empty CSS file:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="style.css"> <title>Greetings</title> </head> <body> <h1>Hello, World!</h1> </body></html>
After creating these files, let’s commit our changes:
git add style.css index.htmlgit commit -m "Create style.css and link it to HTML"
Looking at our commit history now shows the branch divergence:
git log --oneline
22854e0 (HEAD -> add-css) Create style.css and link it to HTMLed1523e (origin/main, main) Add document title39cfcb0 Add Hello, World header5c19c53 Create index.html
Visually, our repository now looks like:
Source
gitGraph commit id: "1" commit id: "2" commit id: "3" branch add-css checkout add-css commit id: "4"
The origin/main
in our log indicates we have a remote repository. We can verify this:
git remote -v
origin https://github.com/<username>/<repo>.git (fetch)origin https://github.com/<username>/<repo>.git (push)
Let’s push our new branch to GitHub:
git push --set-upstream origin add-css
Now our branch is visible in the GitHub interface in the branches dropdown.
Let’s add some CSS to our stylesheet:
html { font-family: sans-serif;}
h1 { font-variant: small-caps;}
Then commit and push:
git add style.cssgit commit -m "Do some work on font styling"git push
Task: Adding Interactivity with JavaScript
Meanwhile, another developer has been tasked with adding JavaScript functionality, starting from the same main
branch:
git checkout maingit checkout -b handle-title-click
This developer creates index.js
:
const title = document.getElementById("title")title.addEventListener("click", function () { alert("You clicked the title!")})`
And modifies index.html
:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="index.js"></script> <title>Greetings</title> </head> <body> <h1 id="title">Hello, World!</h1> <h1>Hello, World!</h1> </body></html>
They commit and push their changes:
git add index.html index.jsgit commit -m "Add title click handler"git push --set-upstream origin handle-title-click
Visually, our repository now looks like:
Source
gitGraph commit id: "1" commit id: "2" commit id: "3" branch add-css checkout add-css commit id: "4" commit id: "5" checkout main branch handle-title-click checkout handle-title-click commit id: "6"
Both features are now complete and ready to be considered for merging into the main branch. According to GitHub Flow, the next step is to create pull requests for each branch.
Before we explore pull requests, let’s understand what merging means in Git.
Merging Branches
A pull request is fundamentally a request to merge one branch (containing your changes) into another branch. Most commonly, pull requests merge feature branches into the main
branch.
In Git, merging refers to the git merge
operation, which combines multiple commit sequences into a unified commit history. When you merge branches:
- Changes from the target branch are combined into your current branch
- The branch you’re merging into must be your active branch (the one marked with an asterisk
*
ingit branch
)
Unless conditions allow for a fast-forward merge, Git creates a special merge commit when combining branches. This merge commit represents the integration of changes from the target branch into your current branch.
For example, imagine we have a new feature
branch that we want to merge into the main
branch of our repo:
Source
gitGraph commit id: "1" commit id: "2" commit id: "3" branch feature checkout feature commit id: "4" checkout main commit id: "5" checkout feature commit id: "6" checkout main commit id: "7"
After merging the feature
branch into main
, a new merge commit will be added to main that incorporates the changes from the feature branch:
Source
gitGraph commit id: "1" commit id: "2" commit id: "3" branch feature checkout feature commit id: "4" checkout main commit id: "5" checkout feature commit id: "6" checkout main commit id: "7" merge feature id: "Merge Commit"
This process ensures the main
branch now contains all code implemented in the feature branch.
Fast-Forward Merges
A fast-forward merge is a special merge that doesn’t create a merge commit. This type of merge is only possible under specific circumstances:
- There must be a direct linear path between the current branch and target branch
- The current branch’s tip must be a direct ancestor of the target branch’s tip
Visually, this looks like:
Source
gitGraph commit id: "1" commit id: "2" commit id: "3" branch feature checkout feature commit id: "4" commit id: "5" commit id: "6"
In this scenario, Git can simply “fast forward” the current branch pointer to the position of the target branch:
Source
gitGraph commit id: "1" commit id: "2" commit id: "3" branch feature checkout feature commit id: "4" commit id: "5" commit id: "6" checkout main merge feature id: "Fast-Forward Merge"
If the branches have diverged in any way (both have unique commits), Git cannot perform a fast-forward merge and will instead create a merge commit using a three-way merge process, as discussed above.
GitHub Pull Requests
GitHub pull requests provide a structured mechanism for incorporating proposed changes into the codebase. They serve multiple purposes:
- Notify team members about proposed changes
- Create a dedicated space to review and discuss modifications
- Allow collaborators to provide feedback and request adjustments
- Facilitate formal approval (or rejection) of changes before merging
At its core, a pull request is a request to merge one branch into another—typically merging a feature branch into the main
branch. Both branches must exist on GitHub for a pull request to be created.
Since we’ve pushed our add-css
branch to GitHub and want to merge it into main
(which is also on GitHub), we have everything needed to create a pull request.
When you push a branch to GitHub, the platform often displays a helpful banner with a prompt:
Alternatively, you can navigate to the Pull Requests tab in your repository and click the New pull request button:
In the comparison page:
- Select your branch (e.g.,
add-css
) in the “compare” dropdown - Keep main as the “base” branch
- Review the displayed commits and code differences
- Click “Create pull request”
After clicking “Create pull request,” you’ll need to provide:
- Title: A concise description of the changes
- Description: A more detailed explanation of what the changes accomplish
Then click “Create pull request” again. Note that you should only select “Create draft pull request” for branches not yet ready for review or merging.
After creation, you’ll be taken to the pull request’s “Conversation” tab, which provides:
- A summary of the pull request
- A list of commits included
- A discussion area for general feedback
The “Files changed” is another important tab that shows a line-by-line diff of all code modifications, where reviewers can add specific comments and conduct a thorough code review.
This structured process ensures code changes are properly reviewed before being incorporated into the main codebase.
Code Reviews
Code review is a critical part of the development process that improves code quality and distributes knowledge across the team. Since pull requests are the final check before code reaches production, thorough reviews are essential. Let’s explore GitHub’s tools for effective code reviews.
Imagine another developer has created a pull request for their handle-title-click
branch. Let’s walk through reviewing their changes.
Conversation Tab
The “Conversation” tab serves as a general discussion forum for the pull request. You can use this space for:
- Broad comments about the approach
- Questions about implementation decisions
- High-level feedback
While you can provide general feedback here, detailed code review is better conducted in the “Files changed” tab.
Files Changed Tab
The “Files changed” tab is specifically designed for thorough code reviews. It displays a line-by-line diff of all changes and offers specialized review tools:
If you identify a potential bug or have a suggestion about specific code:
- Hover over the relevant line
- Click the blue ”+” button that appears (or drag to select multiple lines)
- Type your comment
- Choose either:
- Add single comment: For standalone feedback
- Start a review: To begin a formal review requiring resolution
When you start a review, your comment is marked as “pending” until you submit the complete review:
After reviewing a file, you can mark it as “viewed” to:
- Collapse its diff
- Track your progress through the review
- Help focus on remaining files
Continue adding comments to any files that need attention, for example:
After reviewing all files, submit your complete review by:
- Clicking “Review changes”
- Adding a summary comment
- Selecting one of three options:
- Comment: General feedback with no explicit approval
- Approve: Changes look good and can be merged
- Request changes: Issues must be addressed before merging
In our case, the current implementation does not work, so we need to request changes.
The pull request author should then:
- Make commits addressing your requested changes, or
- Explain why they believe the changes aren’t necessary
Once satisfied with their response, you can either approve the changes or dismiss your review.
After the required number of approvals (determined by your team), you can merge the pull request by clicking “Merge pull request” on the conversation tab.
Once merged, all team members should:
- Pull the updated
main
branch to their local repositories - Delete the feature branch both on GitHub and locally to reduce clutter
This structured review process ensures code quality while maintaining clear documentation of decisions made during development.
Resolving Merge Conflicts
When checking our pull request after another developer’s changes have been merged into the main
branch, we may encounter a situation where our PR can no longer be automatically merged due to conflicts:
We must resolve these conflicts by adding at least one commit before our pull request can be merged.
While GitHub offers a “Resolve conflicts” button with a web-based editor, it’s generally better practice to resolve conflicts locally in our development environment.
To address these conflicts, follow these steps:
-
Pull the latest changes to
main
locally:Terminal window git checkout maingit pull -
Ensure you’re on your feature branch:
Terminal window git checkout add-css -
Merge the updated
main
branch into your feature branch:Terminal window git merge mainGit will report the conflicts:
Auto-merging index.htmlCONFLICT (content): Merge conflict in index.htmlAutomatic merge failed; fix conflicts and then commit the result. -
Open the conflicted file in your editor, where you’ll see something like:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><<<<<<< HEAD<link rel="stylesheet" href="style.css">=======<script src="index.js"></script>>>>>>>> main<title>Greetings</title></head><body><h1 id="title">Hello, World!</h1></body></html>In this example, the conflict occurred because both developers added different lines at the same position in
index.html
. One added a<script>
tag while we added a<link>
tag. -
Edit the file to keep both changes, as they’re compatible.
-
Mark the conflict as resolved:
Terminal window git add index.htmlgit commitgit push
This should make your pull request automatically mergeable again. Once you receive the required approvals, you can merge the pull request.
After any pull request is merged, all team members should update their local main
branch with the latest code. It’s crucial that everyone works with the most recent version of the codebase when creating new feature branches to avoid additional conflicts.
Rebasing
While git rebase
is a powerful tool for rewriting commit history, it can be complex and is often best avoided in favor of simpler alternatives like merging. Rebasing is particularly useful for:
- Cleaning up commit history before merging (for example on your own feature branch), alternatively you can use a “squash and merge” approach (
git merge --squash
) - Integrating changes from one branch into another while maintaining a linear history (which might be prefered by some teams over merging)
However, it can lead to confusion and conflicts if not used carefully.
In general, merges, rebases, and squashes are all valid strategies but are situationally dependent. The key is to understand the implications of each approach and choose the one that best fits your needs.
Enforcing Branch Protection
While we’ve explored the GitHub flow workflow, you may have noticed that nothing actually enforces this process. Currently:
- Pull requests can be merged without any reviews or approvals
- Developers with write access can push commits directly to the
main
branch - The GitHub flow is followed only through team agreement, not technical enforcement
To properly enforce the GitHub flow, we can implement branch protection rules (assuming we have administrative permissions on the repository):
- Navigate to your repository on GitHub
- Go to Settings → Branches
- Click the Add classic branch protection rule button
When creating your protection rule:
- Branch name pattern: Enter “main” to protect only the
main
branch- You can use patterns to protect multiple branches, but for now we’ll focus on
main
- You can use patterns to protect multiple branches, but for now we’ll focus on
- Enable key protections:
- ✅ Require a pull request before merging
- ✅ Require approvals (this option appears after enabling pull request requirement)
You can customize protection further based on your team’s needs:
- Require status checks to pass before merging: Particularly useful once you’ve set up continuous integration, as it prevents merging code that fails automated tests
- Require conversation resolution before merging: Ensures all discussion points are addressed
Once these rules are in place:
- Direct pushes to the
main
branch will be blocked - All changes must go through the pull request workflow
- Code must receive the required number of approvals before merging
This effectively enforces the GitHub flow process, ensuring code quality and proper review before changes reach your production branch.
READMEs and Other Repository FIles
Your README.md
file is the first thing users see when they visit your repository. It should provide a clear overview of the project, including:
- Project description
- Installation instructions
- Usage examples
- Where to get help
- Contribution guidelines
- Contributor list
- Credits, inspiration, alternatives
You can find out more about what constitutes a good README in The Art of README.
LICENSE
The LICENSE
file contains the legal terms under which the project’s code can be used, modified, and distributed. It’s important to choose an appropriate license that aligns with your project’s goals and community values.
Licenses can vary widely in terms of permissions and restrictions. For example, some licenses allow for commercial use, while others may restrict it. Some licenses require modifications to be shared under the same license, while others do not.
Choosing the right license is crucial for open-source projects, as it defines how others can use your code. It’s important to consider the implications of different licenses on your project’s future and community.
For example, the MIT License is a permissive license that allows for commercial use and modification, while the GNU General Public License (GPL) requires that any modifications be shared under the same license (i.e., copyleft).
The copyleft licensing ensures that a work remains freely available for use, modification, and distribution, with the condition that derivatives also retain these freedoms. It’s often described as a reciprocal license because it requires anyone who distributes the work or its derivatives to do so under the same or compatible terms, preventing the addition of restrictive proprietary conditions.
Check out Choose an open source license for a guide to selecting the right license for your project.
If you project is not code, you can still use a license file to clarify how others can use your work. For example, if you have a blog or website, you might want to specify whether others can reuse your content, and under what conditions. See also the Creative Commons licenses for non-code projects.
CONTRIBUTING.md
The CONTRIBUTING.md
file is a markdown file that provides guidelines for contributing to the project. It typically includes information on how to report issues, submit pull requests, and any specific coding standards or practices the team follows.
CODE_OF_CONDUCT.md
The CODE_OF_CONDUCT.md
file outlines the expected behavior of contributors and maintainers in the project. It sets the tone for a respectful and inclusive community, providing guidelines for communication and conflict resolution.
SECURITY.md
The SECURITY.md
file provides guidelines for reporting security vulnerabilities in the project. It typically includes information on how to report issues, the process for handling security reports, and any specific security practices the team follows.
CHANGELOG.md
The CHANGELOG.md
file is a markdown file that provides a chronological list of changes made to the project. It typically includes information on new features, bug fixes, and any breaking changes introduced in each release. This file helps users and contributors understand the evolution of the project over time.
ARCHITECTURE.md
The ARCHITECTURE.md
file provides an overview of the project’s architecture and design decisions. It typically includes information on the project’s structure, key components, and any specific architectural patterns or practices the team follows. This file helps new contributors understand the project’s design and how to navigate its codebase.
GitHub Issues and Projects
GitHub provides a powerful issue tracking system that allows teams to manage tasks, bugs, and feature requests. Issues can be linked to pull requests, making it easy to track the status of work.
GitHub also offers a project management feature that allows teams to create Kanban-style boards to organize and prioritize work. Projects can be linked to issues and pull requests, providing a comprehensive view of the team’s progress.
Issues
While enabled by default, you can toggle the Issues tab on or off in the repository Settings.
After clicking the Issues tab, you can create a new issue by clicking the New issue button. This will take you to a form where you can provide:
- Title: A concise description of the issue
- Description: A more detailed explanation of the issue, including steps to reproduce, expected behavior/acceptance criteria, and any relevant screenshots or logs
- Labels: Tags to categorize the issue (e.g., bug, enhancement, question)
- Assignees: Team members responsible for addressing the issue
- Milestone: A specific release or version the issue is associated with
- Projects: A project board the issue is linked to
Let’s imagine that we want to add a colored background to our HTML document. We can create a new issue titled “Add background color” and provide a description with the following information:
## Description
Add backgroudn color to the document. Apply the style to the `<body>` tag. Use color [#99ccff](https://www.colorhexa.com/99ccff).
## Acceptance Criteria
- The background color should be a light shade of blue- The color should be applied to the entire document
GitHub uses Markdown for formatting, so you can use headings, lists, and links in your issue description.
On the issue page, you can see the issue’s details, change its status, and add comments. An issue is like a living document that can be updated as the task progresses. You can add comments to provide updates, ask questions, or clarify requirements.
Often, an issue will appear in the backlog of a project. The backlog is a prioritized list of tasks that need to be completed. Depending on your team’s workflow, your backlog might have issues that are not yet “ready” for development, i.e., the issue is not fully defined or the team is not ready to start working on it.
You can create relationships between issues, for example if you want to follow an Epic > User Story > Task hierarchy. This helps navigate complex projects and understand how different issues relate to each other.
Our background color issue might be a sub-task of a larger Epic issue that covers the entire UI redesign. You can create this relationship by linking the issues together.
All issues get automatically numbered, which is useful for referencing them in pull requests or comments.
Let’s create a new branch for this issue:
git checkout -b 1-add-background-color
Then we can implement the changes and commit them:
html { font-family: sans-serif;}
h1 { font-variant: small-caps;}
body { background-color: #99ccff;}
git add style.cssgit commit -m "Add background color to the document"
When opening a pull request, you can reference the issue by including #<issue-number>
in the pull request description. This creates a link to the issue.
If you use certain keywords such as “closes” or “fixes”, GitHub will automatically close the issue when the pull request is merged.
In the repository Settings, you can also configure issue templates to standardize the information collected when creating new issues. This is particularly useful for larger teams or projects with specific requirements. For example, if your project is open-source, you might want to create a template for bug reports that includes sections for reproduction steps, expected behavior, and actual behavior.
When you have a large number of issues, you can use labels to categorize them. Labels are tags that can be applied to issues and pull requests to indicate their status, priority, or type. For example, you might have labels for “bug,” “enhancement,” “question,” or “high priority.” This makes it easier to filter and search for specific issues.
For larger projects, you can create milestones to group issues and pull requests into specific releases or versions. This helps track progress toward specific goals and provides a clear timeline for the project.
Check open-source projects on GitHub to see how they use issues and labels. For example, the React repository has well-maintained issues and labels that help users navigate the project.
Projects
Similarly to issues, the Projects tab can be toggled on or off in the repository Settings.
Projects are linked to the owner of the repository (either a user or an organization). One or more projects can be linked to a repository, and one or more repositories can be linked to a project.
You can create a new project by clicking the New project button. You’ll have a choice between different templates, such as Boards, Tables, or Roadmaps. You can always reconfigure the layout of your project later.
It’s very common for teams to follow a Kanban-style workflow, where issues are organized into columns representing different stages of development. For example, you might have columns for “To Do,” “In Progress,” and “Done.” You can create these columns in your project and then drag and drop issues between them as their status changes.
Common columns include:
- Backlog: Issues that are not yet ready for development
- To Do: Issues that are ready to be worked on
- Blocked: Issues that cannot be worked on due to dependencies or other issues (could be put back in the backlog instead)
- In Progress: Issues currently being worked on
- In Review: Issues that are ready for review
- Done: Issues that have been completed and merged
Source
kanban [Backlog] [Create issue templates] [Style typography of main content] [Create the about page] [To Do] [Add a list of greetings] [Add a footer] [In Progress] [Wrap content in main tag] [In Review] [Add background color] [Done] [Add basic styling] [Create an alert when clicking the title]
Every team and organization has its own workflow, so you can customize your project to fit your needs.
You can configure specific workflows that will act on your items based on different triggers:
For example, when a pull request is merged, you can automatically move the associated issue to the “Done” column. This helps keep your project board up to date without manual intervention.
Definition of Done
The Definition of Done (DoD) is a shared understanding among the team of what it means for a task to be considered complete. This definition should be agreed upon by the entire team and can include criteria such as:
- Code is written and committed
- Code is linted and formatted according to team standards
- Code is reviewed and approved by at least one other team member
- Code is tested and passes all automated tests
- Code is merged into the
main
branch - Code passes acceptance criteria defined in the issue (if applicable)
- Documentation is updated (if applicable)
- Issue is closed and linked to the pull request
Depending on the type of project, the DoD may also include additional criteria, for example:
- Code is deployed to a staging (or production) environment
- All images are optimized
- Release notes are updated
- Compliance standards are met
- Accessibility standards are met
The DoD should be visible to the entire team and can be included in the project board or as a checklist in the issue template. This helps ensure that everyone is on the same page and that tasks are completed to a consistent standard.
Every team should have its own DoD, and it should be reviewed and updated regularly to reflect the team’s evolving needs. With a good DoD, the team knows how to test their code and what to look for when reviewing pull requests.
Tags and Versioning
Versioning is the process of assigning unique version numbers to different releases of a software product. This helps track changes and updates over time.
You can use Git’s tagging feature to create version tags. Tags are like branches that don’t change over time. They are used to mark specific points in the commit history as important.
You can create a lightweight tag with:
git tag v1.0
You can also create an annotated tag with:
git tag -a v1.0 -m "Version 1.0"
Annotated tags are stored as full objects in the Git database and include a message, author, and date. They can be signed. This is useful for keeping track of important releases.
A tag is thus asssociated with a specific commit, and you can use it to refer to that commit in the future. For example, if you want to check out the code from version 1.0, you can do:
git checkout v1.0
By default, tags are not pushed to the remote repository. You can push a specific tag with:
git push origin v1.0
It’s common to prefix your tags with a v
(for “version”) to indicate that they are version tags. For example, you might have tags like v1.0
, v1.1
, and v2.0
.
You can use tags for other purposes, for example by tagging commits used for demos, specific deployments, or backups.
Semantic Versioning
Semantic Versioning (SemVer) is a versioning scheme that uses three numbers separated by dots (e.g., 1.0.0
). Each number has a specific meaning:
- Major version: Incremented for incompatible API changes
- Minor version: Incremented for backward-compatible new features
- Patch version: Incremented for backward-compatible bug fixes
- Pre-release version: Optional, used for pre-release versions (e.g.,
1.0.0-alpha
)
SemVer is widely used in the software industry and is a good choice for most projects. It provides a clear and consistent way to communicate changes to users and developers.
It’s common to use version major version 0 (0.x.x
) for early development, where anything may change at any time. Once the software is stable and ready for production, you can start using version 1.0.0
.
Check the SemVer specification for more details on how to use it. Note that the v
prefix is not part of the SemVer specification. You might tag a commit with v1.0.0
, but the SemVer version number itself is just 1.0.0
.
You can use a simpler versioning scheme, such as 1.0
, 1.1
, etc., if your project doesn’t require the complexity of SemVer.
Ubuntu’s versionsing schene, for example, includes the year and month of release (e.g., 20.04 for April 2020).
Pre-Releases
Pre-release versions are used to indicate that a version is not yet stable or ready for production. They are typically used for alpha
, beta
, or release candidate (rc
) versions.
alpha
are early versions of the software that are not feature-complete and may contain bugs. They are typically used for internal testing and feedback.beta
versions are more stable than alpha versions and are typically used for external testing. They may still contain bugs, but they are feature-complete and ready for user feedback.rc
versions are the final versions of the software that are ready for production. They may still contain minor bugs, but they are feature-complete and have been thoroughly tested.
You can find other identifies such as nightly
or canary
in some projects. These are typically used for continuous integration or continuous delivery (CI/CD) pipelines, where the software is built, tested automatically on a regular basis, and sometimes exposed to a limited set of users.
It is very common for software engineering teams to internally use their own software (also known as dogfooding) before releasing it to customers. This allows them to test the software in a real-world environment and gather early feedback. It helps developers identify bugs, improve usability, and ensure the product meets real-world needs by experiencing it as a user would. For example, a company building a messaging app might have its team use it for internal communication to catch issues early.
Creating Releases in GitHub
A common practice is to create a release in GitHub for each version of your project. A release is a snapshot of your code at a specific point in time, along with release notes and other information.
The release bundles the code and any assets (e.g., binaries, documentation) into a single package that can be downloaded and used by others.
To create a release:
- Go to the Releases section in your repository
- Click the Create a new release button
- Select the tag you want to use for the release (or create a new tag)
- Provide a title and description for the release
- Optionally, attach any assets (e.g., binaries, documentation)
- Click the Publish release button
If you haven’t created a tag yet, one will be created using the latest commit on the branch you selected.
In your description, you can include release notes that summarize the changes in this version. This is a good place to include information about any breaking changes, new features, or bug fixes. Many projects also include the name of the contributors who worked on the items listed.
Additional Resources
- Version Control and Branch Management in Software Engineering at Google
- Code Review in Software Engineering at Google
- Dealing with diverged git branches by Julia Evans
- Naming conventions for Git Branches — a Cheatsheet
ARCHITECTURE.md
by Alex Kladov- Awesome README
- Check out Linear, Jira, Trello, Notion, Basecamp as alternatives to GitHub Projects (non-exhaustive list)