Collaborative Git Workflow

Introduction [🔗]

In this workflow, we’ll all be working on the same repository at once. In this case, the repository is a cookbook that we’re each going to add our favorite recipe to. Rather than using the cloud copy of repo as a backup, we’ll use it as the “true” copy of the cookbook. The local copies on all of our computers will become the “working” copies. In git terminology, the true copy is the “upstream” repo and our copies are the “downstream” repos, because changes in the upstream will flow all the way downstream. These distinctions are just conceptual – Git doesn’t distinguish between what different copies of a repo mean. It’s all just in our minds đŸ¤¯.

Getting a copy of the repository [🔗]

Let’s take a look at the landing page of our cookbook repo:

It’s not too different from our solo workflow – a repo filled with text files. In this case, the readme file README.md contains the cookbook’s table of contents, and the recipes are organized into categorical folders.

Let’s download a working copy of this repo. This is called cloning. To clone the repo, start by clicking the green <> Code button (1). Make sure “SSH” is selected (2), and then copy the ssh url below (3):

Head over to Sublime Merge and open a new tab with Ctrl + T (or Command + T on MacOS). Paste the ssh url into the Source URL box. The other options control where the repo will be downloaded to on your computer. The Repository Name will be the name of the folder contianing all the cookbook files, and Destination Path will be where on the computer that folder is placed. Note the destination path, and then go ahead and click the “Clone” button:

ℹī¸ By default, the cloned repo’s name will be the same as the upstream repo name. We could change it to whatever we wanted, though – this is useful if we want to clone multiple copies of the same repo for different purposes.

After a couple of seconds, Sublime Merge should succesfully download all the cookbook files:

Let’s go find them in our file explorer. Go to the “Destination Path” the repo was cloned to and open the folder up. Yup, there it all is:

If this were a solo workflow, we’d be all ready to add our recipe. But because we’re sharing this repo with others, we’ve gotta be careful!

The workflow [🔗]

Recall during the “backups” tutorial how much trouble we got into when the repo’s GitHub copy and local copy had different changes. This is guaranteed to happen when multiple people are working on the same repo. So, rather that letting the main timeline naturally diverge multiple times (which would be very confusing and difficult to resolve!), we’re going to intentionally create a branched version of the timeline (generally just called a “branch”).

The current branch that we’re viewing in the file explorer is called the checked out branch. This is the same terminology we were using in the solo workflow to describe checking out a commit. Let’s update that definition:

📖 “Check out” - verb

  • To “check out a branch” means to make all files in the repo reflect the most recent commit of a branch’s version of the timeline. All future commits will be to this version of the branched timeline.
  • To “check out a commit” means to make all files in the repo reflect their contents at that commit’s point in the timeline.
  • To “check out a file” usually means to return a file to its present-day contents

Anyway, here’s the workflow of exactly how we’re going to use branches:

  1. We clone the shared repo to our computer
  2. If we’re just using our repo’s contents, say to look up a recipe, we use the main timeline
  3. If we want to change something, we create a new branch and check it out. We’ll call this our working branch.
  4. We make our changes and commit them. That commit goes to the HEAD of the checked out branch
  5. We push our branch to origin (that is, we back up our branch to the cloud)
  6. We use GitHub’s website to merge that branch back into the main timeline. Our changes are now “upstream”.
  7. Everyone else pulls our upstream changes to their local repos.
  8. They merge main into their working branches.

Branching the timeline [🔗]

So let’s go to Sublime Merge, click the ... button at the top next to the little search icon, and click Create Branch...

We’ll be asked to give the branch a name. We can name it whatever we want, with a few rules:

In Naomi’s case, she’s adding a new cookie recipe. Type out your branch name and hit enter:

Our branch will get created and automatically checked out. Let’s notice a few things about the Sublime Merge window now:

  1. The name of the currently checked-out branch is in the box at the top-center of the screen. New commits we make will be added to this branch of the timeline
  2. Our branch is now in the branch list on the left. We can use this list to switch the checked out branch at any point.
  3. We can see there’s a label with our branch name in the commit list. This indicates where the head of the branch is (which is to say, the most recent commit on the branch).
    • Notice that we can see multiple branches are currently headed at the initial commit: our branch, and also main. That’s because we haven’t changed anything yet, so the branch timelines haven’t diverged.

Anyway, let’s get working. Go to the repo’s folder in the system file explorer and add a new text file for your recipe:

Also open the readme.md file and:

Back in Sublime Merge, commit the changes with a descriptive commit message. Be sure to stage both the readme changes and also the recipe’s text file. We have to click the stage buttons for both. If we miss one, we’ll have to make another follow-up commit.

Note the timeline now: the labels are in different places. Something new has happened on the new-cookie-recipe timeline: we’ve added our recipe in a new commit. The timelines have diverged, and our branch is ahead of main:

Let’s push our branch to GitHub, so the cloud copy of the repo has both the main timeline and our working copy’s timeline. Click the push button in the top right:

The timeline labels change again! Now there is a new one:

There is the new-recipe branch on our computer (the blue label), and the backed-up copy of the timeline in the cloud (the grey label that stars with origin/).

Let’s go over to the repo’s landing page on GitHub and hit the refresh button. Note the yellow notification box mentioning our branch:

If you click the little box that says “main” beneath that, you can see a list of branches on the remote repo. It now includes ours:

Neato.

How to do we merge these changes into the main timeline, though?

Merging the timelines with pull requests [🔗]

In general, the process of combining one branch’s commits into another is called merging. Both branches remain in existence. The branch we’re merging from remains completely unchanged. The branch we’re merging into gains the changes from all the commits on the from-branch.

We can merge branches using Sublime Merge. In general, we will. But the main branch is special. Because the main branch on GitHub is the one “true” source of our files across all collaborators, we want to only change it on GitHub. That’s instead of changing main on our local copy, and pushing those changes to GitHub.

This process of changing main directly on the origin copy of the repo is called a pull request (“PR”), because we’re requesting that the collaborative group “accepts” our changes and “pulls” them into main. Here’s the process:

  1. We make a new branch and add commits to it.
  2. We push our branch to origin
  3. We issue a PR requesting that our branch gets merged into main
  4. The group reviews the changes this would make using GitHub’s website
  5. If unsatisfied with those changes, we can add new commits to the branch and they’ll show up in the PR.
  6. Once we’re all satisfied, the group verbally approves the PR.
  7. One of the approvers clicks the “Merge” button. The main timeline now includes our changes.

Okay! Let’s do it!

We could use the yellow notification box on GitHub to issue our PR, but after awhile that notification box might disappear. Here’s a consistent way to start a PR on GitHub:

Switch to the branch we want to merge into main. In the screenshot below, it’s the “new cookie” branch. On your computer, though, it’ll be your branch that you want to click:

GitHub will let us know how this branch relates to main in terms of how many commits ahead (or behind) it is. Click the Contribute button (1), and then click Open pull request (2):

From here, we’ll get a chance to write a description of our changes. Put something informative, as this will help our team members understand the changes they are reviewing:

Note that, on the right, you can also click Reviewers to tag the GitHub accounts of teammates you want to look at your changes. You can even assign someone to it under the Assignees box. The assignees are generally responsible for hitting the merge button once everyone is happy with your changes.

Click the green Create button. The PR will get issued, and you can share the page’s link from the browser’s URL box with your teammates so they can take a look. On the PR page, you can click the Commits tab to view the commit timeline that this PR will merge into main. You can click the Files changed tab to view the actual content that will be added to the repo’s files. You can have a discussion with your teammates on the Conversation tab, like a discussion in the comments on an Instagram post:

Once everyone is happy with the changes, have a reviewer click the Merge button. Click the “Confirm” button to seal the deal.

🔍🤷‍♀ī¸What if there is no green merge button?

Check out the discussion below

✋⚠ī¸Note that you can’t undo a merge!!⚠ī¸âœ‹

As long as your branches are unmerged you can add commits to your working branch, or even delete the working branch and start again from scratch. As soon as you merge to the main timeline, though, your changes are “real” and everyone will eventually have to include them in their own work.

Once our working branch is merged, we’ll have the opportunity to delete it. This is because the branch is no longer “needed” – its commits exist in the main timeline, and if we do work in the future it should be on a new branch that starts from the new most recent head of main. Go ahead and click “delete”:

Dealing with merge conflicts [🔗]

🔍🤷‍♀ī¸What if I didn’t have trouble merging my PR?

Read anyway! You will some day!!

If someone else had their PR merged before ours, GitHub might not let us merge the PR just yet. We’ll see something like this:

What’s happening is a merge conflcit, just like the ones we encountered when pushing/pulling in the backups lesson and when reverting content in the solo workflow lesson. GitHub offers online tools to resolve those conflicts, but let’s handle them in Sublime Merge for now.

Back in Sublime Merge, hit the “pull” button:

Now we’ll see the source of the conflict – someone else’s PR beat us to the punch:

What we want to do is merge main into our working branch (the opposite direction of the PR), so we can take care of the merge conflicts. Then we’ll push these changes on our working branch to the cloud, and because the conflicts are all resolved we’ll be able to merge the PR.

Find the origin/main label in the timeline view. Right-click that commit (1), and select Merge into (2):

Leave all the merge options at their defaults and click “Merge”:

The summary tab will tell us which files have conflicts we need to fix:

In this case it’s README.md, so let’s open it up in notepad and fix all the conflicts we see that are surrounded by >>>>>>> ... <<<<<<< brackets:

In Sublime Merge, stage and commit the changes. Then push your branch:

When we refresh the PR page on GitHub, our new work should show up and we’ll now be able to merge:

Wrapping up [🔗]

Once you’ve merged a PR, pull the most recent version of the main timeline down to your local repo.

Do this by right-clicking the main branch and checking it out:

And then hit the pull button:

We’ll now see that our main timeline contains the changes from all the recipe PRs that have been merged thus far:

And that’s the process! It may feel complicated this first time, but it’s a battle-tested workflow used across the world to collaboratively work on computer code. In time it will become second nature. And before then, you have plenty of Git mentors around you who would love to help should you get stuck :) .