General

Git Strategies for Working on Teams

Dustin Goodman
4 min read
Git Strategies for Working on Teams
This article was written over 18 months ago and may contain information that is out of date. Some content may still be relevant, but please refer to official documentation for the latest information.

Background

Every team I’ve worked on has had different expectations and rules around git. Some had strict guidelines that created developer experience issues. Others had rules that were so loose that there was no consistency on the team.

These are my thoughts on what I’ve discovered to be a healthy balance between the strict and loose rules on teams. I hope to make suggestions for your team, and process that don’t sound dogmatic, and eventually help.

Always Branch and Pull Request to Main

This may seem like a no brainer to some, but no one should ever be interacting on your repo’s main branch directly. All changes to upstream main should be handled via pull requests (PR) with the exception of the initial repository commit. This will make all the suggestions throughout the article function properly. But it should also make it so your main branch will remain healthy assuming you have CI/CD working on PR. It also enables peer review on work which is a good habit. I still PR on my personal projects to make so I understand a set of changes in context as well.

Merges to main via Squash and Merge

All git hosting services have a “Squash and Merge” button on PRs which is an alias for:

git checkout main
git merge --squash <branch>
git commit

This takes all the commits on the specified branch and reduces them into a single atomic commit that gets inserted into the main branch. This is great for a few reasons:

  1. Keeps your git history clean
    1. omits merge commits
    2. keeps merge historically fully sequential
  2. Creates a single commit describing a bulk of changes but still makes the history of those changes available via a link to the original PR for full context
  3. Helps using bisect strategies to identify when a change was introduced, and reference the PR in which that change was introduced

The following is an example output of the squash and merge strategy:

git history example with squash merge

Individual Branching

I have two rules I suggest for individual branches:

  1. conform to some naming convention the team sets
  2. use a non-destructive strategy for commits and updates once a review has occurred

Rule 1 exists for a variety of reasons but primarily to avoid naming collisions. Rule 2, on the other hand, exists for a reviewer’s sake. All net new changes should be done via commits once a review has be initiated. Otherwise, it’s hard for a reviewer to track what they’ve already looked at versus what’s changed. Be nice to your reviewers. Once that PR is open, you may not use --amend or rebase on existing commits moving forward.

Otherwise, how you keep your branch up-to-date with the main branch is entirely your choice. Merge is typically the easiest if there are conflicts, especially when trying to resolve, and is usually best for novice developers. Rebase is great if you don’t have any merge conflicts or merge commits, and lets you get all the latest changes in the main brach. At the end of the day, do what is best for you.

The squash and merge strategy makes it so your branch can be whatever you want it to be. Do you commit every 5 minutes? Fine! Have 15 merge commits from main? That’s also fine! All these actions tell a story but squash and merge to main keeps that history on your branch, and PR and does not impact upstream main.

Long Running Epic Branches

All my rules above are great for single changes applied directly to main. However, some teams work on long running epic branches to avoid introducing changes to main that are not ready for release. The best way to avoid long running epic branches is feature flags, but some teams aren’t able to use these for one reason or another.

That’s ok! For this situation, I recommend the following:

  1. Create a base integration branch that is kept up with main using the merge strategy
  2. Create individual branches off the integration branch and follow my instructions on individual branching from above
  3. Squash merge branches back into the integration
  4. Squash merge the integration back into main when ready

I’ve been on teams that try to do fancy rebase --onto strategies with the integration branch, and then the following individual branches. At the end of the day, this creates a lot of unnecessary git work for teams, and wastes time that is better spent working on features, bugs, and tech debt. This is because branches have to be updated in a particular way, and sometimes the conflicts that arise lead to lost changes and duplicated effort and work. Simplify your process and simplify your teams’ lives.

Conclusion

This only covers a few of the most common situations, but these general rules should help for teams and individuals in the way that is best for them while keeping the upstream main branch healthy and easier to debug when critical issues are identified.

I hope this helps you and your team on your next project and allows more time for technical debt and other important work that may have been lost to git related issues.

About the author

Dustin Goodman

Dustin Goodman

Engineer Manager

Engineering Manager with a passion for web and application development. Speaker and writer on work experiences and software development. Dog dad and musician fueled by coffee.

Keep reading

View all posts →

"How do I undo my most recent commit?" - Mastering the git reset command

Ever messed up a commit? Learn how to undo it like a pro! Our new blog post breaks down the git reset command, helping you navigate those "oops" moments with confidence....

Mattia Magi2 mins
Git

Ensuring Accurate Workflow Status in GitHub for Enhanced Visibility

Master the nuances of GitHub workflows with our latest blog post. Discover key strategies to ensure your workflows accurately reflect the true status of tests and tasks, preventing misleading green checks. ...

William Mimura3 mins
GitHubGit

Mastering Git Rerere: Solving Repetitive Merge Conflicts with Ease

Are you curious to discover one of the hidden powers of Git? Incorporate git rerere into your Git workflow, and say goodbye to the frustration of repetitive merge conflicts....

Mattia Magi4 mins
Git

A Deep Dive into SvelteKit Routing with Our Starter.dev GitHub Showcase Example

Introduction SvelteKit is an excellent framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem based routing. At the heart of SvelteKit is a filesystem based router. The routes of your app — i.e. the URL paths that users can access — are defined by the directories in your codebase. In this tutorial, we are going to discuss SvelteKit routing with an awesome SvelteKit GitHub showcase built by This Dot Labs. The showcase is built with the SvelteKit starter kit on starter.dev. We are going to tackle: Filesystem based router +page.svelte +page.server +layout.svelte +layout.server +error.svelte Advanced Routing Rest Parameters (group) layouts Matching Below is the current routes folder. Prerequisites You will need a development environment running Node.js; this tutorial was tested on Node.js version 16.18.0, and npm version 8.19.2. Filesystem based router The src/routes is the root route. You can change src/routes to a different directory by editing the project config. Each route directory contains one or more route files, which can be identified by their + prefix. +page.svelte A +page.svelte component defines a page of your app. By default, pages are rendered both on the server (SSR) for the initial request, and in the browser (CSR) for subsequent navigation. In the below example, we see how to render a simple login page component: +page.ts Often, a page will need to load some data before it can be rendered. For this, we add a +page.js (or +page.ts, if you're TypeScript inclined) module that exports a load function. +page.server.ts If your load function can only run on the server— ie, if it needs to fetch data from a database or you need to access private environment variables like API key— then you can rename +page.js to +page.server.js, and change the PageLoad type to PageServerLoad. To pass top user repository data, and user’s gists to the client rendered page, we do the following: The page.svelte gets access to the data by using the data variable which is of type PageServerData. +layout.svelte As there are elements that should be visible on every page, such as top level navigation or a footer. Instead of repeating them in every +page.svelte, we can put them in layouts. The only requirement is that the component includes a for the page content. For example, let's add a nav bar: +layout.server.ts Just like +page.server.ts, your +layout.svelte component can get data from a load function in +layout.server.js, and change the type from PageServerLoad type to LayoutServerLoad. +error.svelte If an error occurs during load, SvelteKit will render a default error page. You can customize this error page on a per route basis by adding an +error.svelte file. In the showcase, an error.svelte page has been added for authenticated view in case of an error. Advanced Routing Rest Parameters If the number of route segments is unknown, you can use spread operator syntax. This is done to implement Github’s file viewer. svelte kit scss.starter.dev/thisdot/starter.dev/blob/main/starters/svelte kit scss/README.md would result in the following parameters being available to the page: (group) layouts By default, the layout hierarchy mirrors the route hierarchy. In some cases, that might not be what you want. In the GitHub showcase, we would like an authenticated user to be able to have access to the navigation bar, error page, and user information. This is done by grouping all the relevant pages which an authenticated user can access. Grouping can also be used to tidy your file tree and ‘group’ similar pages together for easy navigation, and understanding of the project. Matching In the Github showcase, we needed to have a page to show issues and pull requests for a single repo. The route src/routes/(authenticated)/[username]/[repo]/[issues] would match /thisdot/starter.dev github showcases/issues or /thisdot/starter.dev github showcases/pull requests but also /thisdot/starter.dev github showcases/anything and we don't want that. You can ensure that route parameters are well formed by adding a matcher— which takes only issues or pull requests, and returns true if it is valid– to your params directory. ...and augmenting your routes: If the pathname doesn't match, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404. Note: Matchers run both on the server and in the browser. Conclusion In this article, we learned about basic and advanced routing in SvelteKit by using the SvelteKit showcase example. We looked at how to work with SvelteKit's Filesystem based router, rest parameters, and (group) layouts. If you want to learn more about SvelteKit, please check out the SvelteKit and SCSS starter kit and the SvelteKit and SCSS GitHub showcase. All the code for our showcase project is open source. If you want to collaborate with us or have suggestions, we're always welcome to new contributions. Thanks for reading! If you have any questions, or run into any trouble, feel free to reach out on Twitter....

Ian Sam Mungai5 mins
SvelteGit