Keeping track of code changes within your projects should be a critical piece of your development workflow. Keeping a record of changes with VCS, or Version Control Systems, allows you to manage versioning more easily, roll back changes in a straightforward way, and can even be built into your greater test/deployment workflows. While VCS is extremely useful as a solo developer, as team size grows, having tools to manage and merge code that multiple individuals are working on simultaneously becomes critical to maintain efficiency (and not rip your hair out!)
While many VCS systems exist, this article will focus exclusively on git and its use within a team. While other systems exist for tracking changes, git has quickly become the most widely used VCS, especially in web development. After reading this article, you should have an understanding of how to use git as well as create a basic workflow and branching strategy.
Let’s begin by defining some of the key concepts of a git workflow, discussing the tools you can use to interact with git, and learning how to move changes from your local machine to a remote repository.
Git workflows can get fairly complicated quickly. Here, we will cover the basics needed to understand a simple git team workflow. If you are interested in learning more, I’d encourage you to take a look at the git documentation. That link also includes some good cheat sheets that can be used as a reference.
To start working with git, you’ll need an understanding of branches and snapshots.
Branches are at the core of most VCS systems including git. When you create a branch, you’re telling the system you are no longer working on changes in the main line of development.
What makes git unique when compared with other VCS systems is how it creates these branches. Branches are typically an expensive operation, dissuading developers from creating workflows that require frequent merging or branching. With git, every time you commit changes to a branch, a ‘snapshot’ of the changes since the last commit are saved. This way, git is able to rebuild your repository to the state it was in at a given time, without storing/processing large amounts of data. For more on how git handles branching, see Git Branching Documentation.
Once the changes you’re making are complete, you’re able to merge those changes back to the main line of development and delete your branch. Whether you’re making changes large or small, you should always create a branch that contains your changes before merging those changes back to the main line of development.
For our workflow, we’ll be creating a branch per feature we want to build. We’ll work on those changes in the new branch we made and merge them back to the master branch once the feature is complete.
Interacting with git
There are many ways to interact with git on your system. Many developers choose to manage their git workflow directly from a terminal. If you’re less comfortable in a terminal or simply don’t want to keep switching back and forth between windows, many popular IDEs include built-in git support including VS code, Atom, and Sublime Text.
If you are sticking with the terminal experience, consider using a shell like ZSH to have more information about your repo at a glance (and getting all the other awesome features that come with ZSH). You can also setup something similar purely in bash, as described here.
The final thing you will need is a place to host your remote git repository. Many options exist with varying features and free/paid access.
Connecting to a remote / Creating a repository
Now that we have our tools set up and a basic understanding of snapshots and branches, we’re ready to create a new repository or connect to an existing one.
Creating a new repository locally is as easy as creating a new directory for the project, changing into the new directory, and initializing a new repository.
See your IDE’s documentation for how to initialize a git repository through their software.
You should see a success message and, if you’re using ZSH or enabled git features in bash, you should now see information about the current branch (master) next to the path.
We now have a working git repository! Next, we’ll need to make our first commit and connect our local repository to our remote git host. Here, we’ll look at connecting to GitHub, but the steps will be very similar regardless of which provider you use.
Since in this example, we’re working with an empty directory, we’ll need to create a file to commit to the repository. This file can contain anything for example purposes.
cat > 123.txtsome sample text
By default, git will only track changes to files you specify. Before we can commit the new file, we need to add the file to the index. Often, you will want git to track changes across all files in the directory. Use one of the following commands to tell git to begin tracking changes on the new file.
#add a single filegit add filename.ext#add all filesgit add -A
Similarly, you can remove files from the index using
git rm filename.ext.
We’re now ready to make our first commit and hook our repository up to our host.
We will commit all our changes to the repository using a simple command with a message attached, detailing what we changed.
git commit -m “first commit”
If successful, you’ll see another success message with some data around which files were added/changed.
To hook our repository up to our host, we’ll use the git remote command as shown below. Here, origin refers to the remote repository hosted at GitHub we’re connecting to. While
git remote connects us to the remote repository,
git push actually begins the upload process to the remote repository.
git remote add origin email@example.com:[username]/[reponame].gitgit push -u origin master
NOTE: Before this command will run, you’ll need to setup SSH keys and attach them to your Github (or similar) account. For Github, follow these instructions.
We now have a working local git repository that is connected to an origin repository in Github and can start working on our project.
Example Workflow for Teams
Our project’s repository will have two main branches: master and development. The master branch will contain working, production ready code. Pushing code to the master branch will essentially be a release of our project. The development branch will contain all our completed features that are not yet released.
As we discussed before, we will be creating a branch for each feature we are implementing within our project. We’ll create this branch when we begin working on the feature, making sure we have the latest stable working code from the development branch as our starting point. Once we’re done building the feature, we will merge our feature branch back to the development branch.
If you’re following along with this example, you will only have a Master branch in your repository. To create the Development branch, simply run the following in the root path of your project to create and checkout the new branch.
git checkout -b development
Let’s go through an example workflow for implementing featureX.
First, we’ll want to make sure we’re in the development branch. Running
git status will give you information around which branch you’re currently in and any staged or unstaged file changes. You should see “On branch development nothing to commit”
Now we’ll use the same command to create our featurex branch
git checkout -b featurex
We are now ready to implement the feature. As a best practice, you will want to regularly commit your code to your branch with good messages that make it clear what work was done. For this example, create a new file, add it to the index, and commit the change.
cat > newfilegit add newfilegit commit -m “added blank newfile”
Once the feature is completed and tested, we will want to merge the feature branch into the development branch. Before we do this, we’ll want to run
git fetch to ensure we have references to any changes made to the development branch in-between the creation of our feature branch and now. To merge, we’ll switch to the development branch and then use the merge command.
git fetch origingit checkout developmentgit merge featurex
Since other changes may have been merged into the development branch since we checked out our branch, we’ll need to run a command to ensure we have the latest changes from the current remote development branch. While git fetch updates the index, git pull will actually pull in the changes and update your files.
git pull origin development
Our changes made in the featurex branch have now been merged into the development branch. To push these to our remote setup at GitHub, we need to tell git to push the development branch to the remote repository “origin”.
git push origin development
We can now safely delete our featurex branch since the work for that feature is complete and merged back into the development branch.
git branch -d featurex
Once we have enough features ready in our development branch, we will follow a similar path for creating a release and pushing to master. Since we are now working on the default branch (master) many of the commands we ran in previous steps require less arguments.
git checkout developmentgit fetchgit checkout mastergit merge developmentgit pullgit push
Leveling up your Workflow
Beyond the simple workflow above, many teams choose to incorporate some of the strategies below to keep their codebase’s history clean, build in automated tests/deployment, and allow project maintainers more control over changes being made to the codebase.
Pull requests allow a project maintainer to have more control before accepting a code change. They are essentially a request to merge changes into a remote branch and must be approved by an authorized user. Your remote git host’s documentation can provide more information on how to create these in their platform.
While this is critical for large open source projects with thousands of known/unknown developers making changes simultaneously, it can also be a helpful code review tool for your team.
Many teams will choose to use a successful git push to a branch as a flag to kick off other deployment or test processes for the project. Many git hosts provide webhooks and app marketplaces with pre-built integrations to help your team automate workflows.
- See this BigCommerce blog post on using CI/CD!
- Github Webhooks
- BitBucket Webhooks
- GitHub Apps
- BitBucket Apps
Another popular tweak to the above methodology for larger teams or teams working with large numbers of branches is rebasing commits.
Rebasing allows you to squash several commits into a single commit and is typically used to keep a cleaner version history for complex projects.
It’s important to note, rebasing does more than simply squash many commits into one and is significantly different from the merge strategy we used in this example. Rebasing, as opposed to merging, would move the beginning of your feature branch to the development branch’s current endpoint.
You should now have a basic understanding of the tools, commands, and strategies used to set up a successful git workflow for your team and keep your codebase and its history clean and useful.
For more information on git, see the documentation, tutorial videos, and recommended reading from the official git website.