Launching a new project is exciting, but comes with the natural caveat of being extremely fast paced. In an environment focused on agile software delivery, the main priority is delivering working code and getting to market. In the heat of the rush, it's easy to let some housekeeping fall behind. Managing git can feel like a second-class citizen, but lifting it up to a core priority can have major benefits in the long run. It's important to lay the groundwork early on to pave a road to team success. Let's take a look at some key principles that will keep merge conflicts to a minimum, make end-to-end delivery quicker, and keep the team moving at all times.
1. Get the team involved in the review and merge process.
I have found that developers tend to lean toward the category of people that like to have control. It's in our nature. One project I have worked on had a centralized review and merge setup. Developers would work on a ticket and create a pull request. At some unknown time in the future, one of the two organization leaders would get around to testing the ticket for code quality and meeting requirements, and then merge the PR. While this can have benefits, it has major drawbacks. First, the process is slow - two people that also have other role responsibilities can only do so much in a given time frame. In addition, the code base gets a very one-sided, opinionated treatment. By allowing your entire team to review one another's work, pull requests get from A to B much faster, and the views expressed can spark conversations that result in overall better quality. Speed can be crucial in the early stages of a project, and benefits everyone at all stages, but keeping the open pull request list short has its own benefits, as explained shortly.
Action item - Empower the whole team to review one another's work. Trust them when a PR is approved. Create a hub for developers to notify others that they have a PR needing review. A simple step here would be to create a "PR Review" channel in Slack or your team's messaging application.
2. Keep the list of pull requests short.
It is important to treat all pull requests with the same sense of urgency. Picture this scenario: A developer creates a pull request with a major refactor that lays the groundwork for a planned feature. At the same time, a few pull requests are created with critical priority that add features that clients are demanding, fix bugs, or refactor in other helpful ways. Over time, more of these so-called "critical" pull requests are created, reviewed, and merged. All the while more "non-critical" pull requests are piling up in the queue. Now we land at a time where there are over a dozen pull requests pending. The most obvious drawback is that we have a ton of great work done that we are not benefitting from building on top of. However, a secondary repercussion of this large queue is that we probably have a slew of pull requests that will generate major conflicts. I've joined projects with pull requests over three months old! What are the chances that those code changes even make sense anymore? How many hours will have to go into merging or rebasing those branches to make them compatible with the current development branch? This is simply valuable development time wasted. Referring back to the first point, getting everyone in on the review and merge process can help keep this list short and everyone happy.
Action item - As mentioned in point #1, creating a group messaging channel or email list to post new PR's can be beneficial. Encourage developers to seek reviews from one another. Ideally, set a maximum number of PR's. Once the number has been reached, everyone is in "review mode." They should drop what they're doing, pick a PR, and review it. This will motivate developers to stay on top of it in the first place!
3. Smaller pull requests promote a better workflow.
This might seem counter to point #2, but with the aid of point #1, it will be a non-issue. Large pull requests mean more files and lines changed, and if everyone is making the best use of refactoring principles, this is even more true. Pull requests of large size require more time to complete, and this means that the developer spends even more time rebasing and fixing conflicts. Once merged, the number of other affected pull requests is larger, causing even more pull requests to be put on hold while developers fix merge conflicts to get them updated. When possible, break down a feature into sub-tasks that can be committed independently without breaking anything. If a feature requires a refactor, perform the refactor and put up a pull request with that change alone. If everyone is helping each other with code reviews, this should make its way through the pipeline in no time at all. While waiting, the original developer can simply branch off of the refactor branch and perform a rebase --onto
once the refactor branch is merged. This allows everyone to rebase more frequently with less headache, assuring that everyone is working on the latest code at all times. An added benefit is that when a developer is relying on another developer's refactor, it will arrive in the current working branch more quickly, allowing them to progress forward earlier.
Action item - Review the current tasks assigned to developers and make sure they're divided into the most logical subtasks possible. If you have colocated developers, you can even use this as an opportunity to have them work on the same main task simultaneously as they can refer to one another to ensure they know what to expect when the other is finished their part.
4. Keep sprints short; rebase early and often.
Okay, I'm cheating here. Two points in one. However, I argue that they are one and the same. In a perfect world, we would create a develop
branch, or a sprint2.1
branch, and at the end of the planned time period, we would merge that branch into master, and it would become the live code. Unfortunately, the world is not so perfect. Users find bugs and file reports, and we are forced to merge bug fixes straight into the master
branch, or whatever you may call your production branch. This means that the code that your developers are working on top of may not actually be the latest. By keeping sprints short, we minimize the frequency at which the root branch changes per sprint, thus minimizing how much work we need to do when it's time to prepare the sprint branch with the root branch. I once spent two years on a project with six-month sprints. The developer in charge of keeping sprint branches sync'd with master
only rebased the sprint branch once or twice per sprint. This resulted in a two and a half hour ordeal for him, and generally a full day's worth of bug fixes for everyone. This doesn't even factor in the hours of work required to rebase the existing pull request branches on top of the new sprint branch afterward. In the end, pick at least one (if not both) of the key takeaways from this point: Keep sprints short, rebase the working sprint branch often, or both.
Action item - Determine the shortest possible sprint schedule that is realistic for your project. When the deadline approaches, rollover remaining work into the next sprint, and adjust plans accordingly. Ideally, plan such that the current sprint branch can be deployed live at a moment's notice. Then, bug fixes can even go into the sprint branch. Feature flags may be necessary to make sure none of the unfinished sprint work gets seen by the users.
5. Don't forget about the business.
Here's a point that sometimes gets lost, and it's more related to code reviews and the technical aspect. In the end, if you're getting paid to write or manage code, you're working for a business. Any business has a client or user-base to please. It's how the business continues to exist. When reviewing code, it is important to evaluate the code in the context of the time frame in which it is needed. When reviewing a bug fix, especially one going straight to production, don't be afraid to hit the checkmark if it's not perfect. If a pull request to fix a critical bug is not perfect, but it works, approve it. This is even more crucial for those straight-to-production fixes. In fact, it's better to modify as little as possible when a code change will go straight to master. Avoid refactoring, as mush as it hurts to do so.
Conclusion
Git itself can be a monster to tame, but by following a solid foundation of guidelines, and getting the entire team onboard, the development process can flow more smoothly. Ultimately, when your team moves as swift as a coursing river, with the force of a great typhoon, and with the strength of a raging fire, software goes from a plan to a profit.