Tangram Vision started our mission to make integrating and deploying multi-sensor applications easy in 2020. While starting a new venture is invariably hard, the pandemic has changed many of the ways in which we all work. Notably, our engineering team has been working fully remote from the very beginning. There are a lot of ways in which remote collaboration is made possible, from various tools to the way one’s team interacts. In particular, our engineering team has placed GitLab at the core of our remote workflow, because it reinforces our values and perspectives around working well remotely.
Aspects of Success When Working Remote
“Working remote” is a pretty broad topic. At Tangram Vision, we wanted to list some of the practices we believe promote successfully working remote. In the context of writing software, these are some of the most important high-level considerations for our remote workflow:
- Mutual sense of trust and understanding
- Asynchronous work-cycle, and
- Division of responsibility
Fostering a mutual sense of trust and understanding is a crucial component to any team. Especially when dealing with large or complex software systems, it is not possible for a single person to keep the entirety of the project in their head at one time. Being able to bring others up to a shared level of understanding about parts of the project they may be unfamiliar with is one way to help foster trust among your teammates. Trust and understanding go hand-in-hand, and lead to a culture where positive collaboration is the default.
Working asynchronously can seem nerve-wracking if you’re not used to working remotely. However, one of the strongest advantages of working remotely is the flexibility. Schedules are far less constrained (for better or worse) than when working at a fixed location and time. In order to minimize the dependency on needing others to be available to accomplish our goals, we structure our workflow in such a way that information is not lost if something cannot be addressed immediately. This means documenting everything ruthlessly and building a “digital paper trail” of all our work. Through transparency building on top of trust, we are able to make more informed decisions on the urgency and importance of different tasks, and we’re able to operate without blocking on every task.
Division of responsibility may seem a bit out-of-place on this list but is a crucial part of working remotely and encourages an asynchronous feedback loop. Dividing responsibility allows our team to operate independently, but more important than that is that it encourages us to not build silos. With strong trust as a base, we divide responsibility for documenting, reviewing, and ensuring the quality and spirit of work on the team. While these are all smaller examples of a greater process, the division of responsibility is a crucial part in building sustainable processes.
In the following sections, we’ll try to demonstrate which features of GitLab contribute towards this remote strategy.
Why GitLab? Why Not X?
There are many providers out there for Git hosting. Picking a provider can be difficult since there’s a lot of feature overlap between different providers. However, GitLab as a company operates entirely remotely, and they publish some great guides to working all-remote. This can be seen in a lot of the tools and workflows they promote. More importantly, GitLab’s own messaging around what aspects and behaviors encourage a strong remote environment echo our own values with regards to remote work.
Many of the features in the standard GitLab workflow help us express the three aspects of success we listed above.
What’s in a Workflow, Anyway?
There’s probably a million different git workflows out there. Git is extremely flexible and can accommodate a variety of ways to organize work across branches, tags, commits, etc. In many cases a workflow that works well for one team may not work for another. This can be because of any number of these factors:
- Team size
- Team distribution
- Do you have separate teams for development & operations, or does your organization rely on some kind of DevOps roles?
- Project requirements
- Safety critical code vs. end-user application
- Device firmware vs. Web application
- Monorepo vs. many small repositories
- Public vs. private work
- Are you building an open-source project?
- Do you incorporate open-source alongside proprietary code?
- Programming language
- Your choice in language might dictate your continuous integration pipeline, or how you structure the files in your repository
Many of these questions can dictate different aspects of the process. Unfortunately, there’s no way to immediately make recommendations without knowing your team, but at a high level many of the steps are the same for typical workflows.
GitLab Workflows That Works Remotely
At Tangram Vision, we’ve settled into a pretty typical workflow using GitLab’s free offering. Work tends to follow this formula:
-
Make a new branch to address a specific feature or issue
- This branch is often named according to what you’re working on. We often use a prefix for our branches. e.g. if you’re working on adding a feature to a sub-module in the code, we might call our branch
<submodule>/<feature-name>
.
💡 This is mostly a convention, so experiment with what works for your team!
- This branch is often named according to what you’re working on. We often use a prefix for our branches. e.g. if you’re working on adding a feature to a sub-module in the code, we might call our branch
-
Create a new merge request for that branch, titled “WIP: ”
- The WIP at the beginning stands for “work in progress.” This signals to others that this branch isn’t ready yet, so don’t start reviewing the branch as the work is incomplete.
- Making the MR early ensures that all your work will have continuous integration (CI) pipelines run on every push.
Work on the branch, committing and pushing code as you go!
-
When the branch is ready for review, we add a reviewer, remove “WIP” from the name of the merge request, and wait for our reviewer to review our code.
- The reviewer is typically a maintainer on the project, or if you are the only maintainer, someone else on the project who can give your code a second pass.
Once the code is reviewed and approved, a maintainer needs to merge it into the default branch!
And that’s it. From a surface level, we’re really not doing anything particularly special here. At each step along the way though, there’s a lot to appreciate when working remotely. Let’s get into what that means.
Using Branches
Having everybody commit directly to your default branch is generally a mistake. Using branches breaks up your work into atomic components, and allows for the use of merge requests (discussed in the next section). But what is a default branch anyway? Well, this may be a branch named master
, main
, develop
, or something else entirely. This is usually a branch that either needs to always be working (e.g. what you ship if you're doing continuous deployment), or is a branch that you cut other releases off of.
Again, it is generally a mistake to have everyone in your org committing to the same branch at the same time. Doing so quickly devolves into chaos as changes from one team member start propagating to every other team member, and soon others will get frustrated that nothing ever builds.
In the short term, team members lose trust and confidence in their work since they can’t be sure what changes they’re running every time they pull and then build. Additionally, this hurts understanding, as team members are less likely to review a single commit in isolation, compared to reviewing an entire branch when it comes time for a unit of work to be merged back in. Instead, opt for smaller branches that attempt to solve a single issue (or at least, a group of related issues) at a time. This divides responsibility across the team to be considerate of others working, and helps keep work asynchronous, since changes pushed to branches aren’t going to derail work for the entire team if they cause issues or break builds.
By using branches we can leverage merge requests, which are an excellent means to collaborate with others. One consequence of remote work is that it can sometimes encourage “lone-wolf” behaviour, where members of the team do not communicate or collaborate at all, and just push more and more work to the repository. Branches and merge requests are at the core of our workflow because they allow us to both enforce and contribute to healthy collaboration among team members.
How can we enforce this then? Fortunately, GitLab comes with some built-in tools to prevent your team from committing willy-nilly to any branch. The feature is called:
Protected Branches
At Tangram Vision, we utilize the protected branches feature of GitLab. What that means is we prevent others from being able to directly push to our default branch at all. You can find this setting in Settings → Repository → Protected Branches in your repository settings.
Protecting branches is one way in which we divide responsibility, by enforcing the use of merge requests to integrate code into the default branch. It’s important to understand roles here, as it is not typically desirable to have everyone in your organization be a maintainer on every repository. As the old saying goes, “the fastest way to starve a horse is to assign two people to feed it.”
Merge Requests
Merge requests are one of the most integral tools to our workflow. Merge requests are where we:
- Review code
- Run automated tests
Merge requests gate code from being merged into our default branch. This effectively means that all code needs to be reviewed and all tests need to be run (and pass) before code makes it back into any kind of release. Let’s look at a few ways that these properties of merge requests help us work better remotely, starting with code review!
Code Review
All code at Tangram has to undergo a code review. Responsibility for getting code reviewed is divided across the team. While we usually ask the maintainer of a repository to participate in the code review, maintainers themselves also have to have their code reviewed. This is beneficial for a number of reasons:
- Reviews are a mechanism for sharing knowledge and fostering understanding between teammates working on the same code base. As the author of a merge request, you have the opportunity to teach others about what approach you took, and why. As the reviewer, you can help contribute to the context surrounding a change, helping build understanding around the decisions made.
- They allow a second pair of eyes to check your work, or suggest next steps.
- They’re one of many mechanisms for seeking dissent. This means understanding concerns and conflicting viewpoints from others affected by the decision. Receiving dissent does not mean that a choice shouldn’t be made; it just needs to be made with consideration.
In direct contrast, some things code reviews are not:
- Are not for criticizing code style. Formatters and linters exist to do this job consistently for an entire codebase; these tools should be relied on and deployed automatically, rather than relying on a reviewer’s subjective style preferences.
- Are not a tool for rejecting changes. The focus should always be on an attitude of finding solutions and improving the end result of the code. A healthy attitude can go a long way towards empowering others and making them feel like their contributions are not only welcome but desired. The purpose is to build trust and understanding, not to shoot down ideas for improvement.
Code reviews are one of the primary ways in which a remote team gets to collaborate, so the spirit of our reviews is really important. When done correctly, code reviews help promote understanding of the code being reviewed, while building stronger trust in the systems we’re designing since we always have (at least) a second pair of eyes on every change. They also are one way in which we divide responsibility, since everyone on the team has to participate in having their code reviewed.
Lastly, by doing all code reviews in the merge request itself, we build a paper trail of documentation for the future. This means that we can look back at exactly when a change was introduced, and find any discussion about potential trade-offs next to a change. This gives the team confidence in understanding the context behind a change, months or years after it has been introduced. Moreover, it encourages team members to be able to work asynchronously, as that context is not held in any single individual’s head, but instead written and made explicit.
Automated CI and Testing
There’s probably enough here we could write a whole blog post on its own, but suffice to say we set up our own GitLab CI runners that can be hosted on DigitalOcean or Amazon infrastructure. The number of free minutes that come with GitLab’s free offering are pretty limited so having our own runners is pretty crucial to having CI.
Code reviews do not need to focus on determining which tests are broken as a result of some work (if any), because the CI pipeline does this for us. Responsibility for ensuring that the pipeline is in a good state is divided across the team. By keeping the pipeline in a good state, we can build trust in the state of the code-base, which is essential when beginning new work.
One thing that isn’t required, but is helpful, is making work-in-progress (WIP) merge requests before a branch is complete and ready for review. The advantage of these is that our CI pipeline runs in the background on every push to Gitlab. This is useful even if you’re not working remotely, but means that you get early feedback on your changes from CI before you submit it for full review. This helps decouple the feedback process from development a little bit, making the whole workflow a bit more asynchronous.
Committing Work to Your Branch
There’s little to say here since this will be largely dictated by the individual contributors themselves. One thing to keep in mind is keeping a clean and consistent history through your commit messages. This excellent article by Chris Beams offers more on the topic than we can add ourselves. Here at Tangram Vision, this article helps define our own goalposts for what makes a good commit message.
Commit messages are another great way to help communicate intent across a remote team, and serve as a form of documentation on “why” changes were made. This is another reason we don’t mandate that changes are squashed before merging, as the history helps contextualize how the code grows and changes over time. Overall, this is another way in which we make our context explicit through documenting the process. This builds trust and understanding across the team, and encourages a more asynchronous workflow as team members are encouraged to read the git log to understand changes made to the system!
Bringing This All Together
Our general workflow isn’t terribly complex, and represents a fairly standard workflow on GitLab’s free offering. Despite this, we can see how GitLab’s default workflow and many of the settings available are geared towards encouraging remote work and making it easier.
GitLab is one way in which Tangram is building a remote-first workplace. We lean on trust, an asynchronous workflow, and dividing responsibility across the organization to remote software development a success. Whether it is protecting branches, encouraging the use of merge requests, running CI pipelines or just keeping a collaborative spirit around code review, GitLab helps us do our jobs every day.
As always, feel free to reach out to us through our website or on Twitter if you have something to add! What are some ways in which you or your organization use GitLab to help foster better remote work?
Top comments (0)