Appendix G: Git for Teams


Goals


While Git is useful for solo projects, its primary purpose is to facilitate development on teams.

Today we’ll learn some additional git concepts and commands that are useful when working on larger projects.

Branches

A git commit stores metadata as well as a reference to the status of the files at the time of the commit.

This metadata includes:

  • Commit ID (a SHA-1 Hash)
  • Author information (name, email)
  • Commit message
  • Timestamp
  • Parent(s) (zero or more)

When you are working solo, your git history typically is linear.

In the examples below, history runs from top to bottom, and the arrows point from child to parent.

initial commit

fixed something

added test

main
added README

Commits in git are immutable, once you’ve made a commit, you cannot change it.

The label main is a branch reference, and works like a bookmark that applies to a given commit. Every time you commit, the active branch reference moves forward onto the latest commit.

We can use the git switch command to change our active branch:

The command git switch -c <branchname> creates a new branch and switches to it.

Once a branch exists, you can use git switch <branchname>.

Warning

Older versions of git do not have git switch.

If you’re using a system that doesn’t support git switch the commands git checkout -b and git checkout can be used to create & switch branches instead.

Each commit only has the changes in its parents, so we could use branches to create experimental work that we didn’t yet want on main.

# switch to a branch, any commits made will not be on main
git switch -c red

# after making edits to files
git commit -am "trying red theme"

initial commit

fixed something

added test

main
added README

red
trying red theme

The new commit only moved the red label forwards.

Switching between the two will reset the state of the repository to the way things were at the most recent commit.

Warning

You generally only switch branches between working on things. This guide will not cover switching between branches when you have uncommitted changes.

So git switch main would switch back to the status of the repository before adding the red theme.

From here, it’d be possible to make another branch with the same parent:

# switch back to main (from red)
git switch main

# make a new branch for blue theme
git switch -c blue

# again, after edits to files
git commit -am "trying blue theme"

# this time, we realize we needed another change
git commit -am "adjusting font color"

Now our tree would resemble:

initial commit

fixed something

added test

main
added README

red
trying red theme

trying blue theme

blue
adjust font color

Now we have three branch identifiers, we could continue to allow blue and red to develop independently.

Eventually however, we will want to bring one or both back to main.

Merging

A branch created multiple nodes that had the same parent.

A merge typically creates a node that has multiple parents.

If we had a commit history like:

...

Initial

...

red

...

blue

main

And we decided we were happy with blue, we would want to merge it onto main.

# ensure we are editing the destination branch, main
$ git switch main

# merge in blue
$ git merge blue

This creates a new commit that is the child of both blue and main:

blue

merge commit

main

...

...

If the destination branch does not have changes of its own, Git may perform a fast-forward merge, where it does not need to create a commit with two parents.

Merge Conflicts

If two branches modify the same file(s) there is a risk of a merge conflict.

Perhaps we try to merge the red branch on top of our newly merged main and blue.

But both modified a file base_template.html.

Auto-merging base_template.html
CONFLICT (content): Merge conflict in base_template.html
Automatic merge failed; fix conflicts and then commit the result.

At this point, your repository will be in a “merge conflict” state. Git will have modified the file to show you the conflicts, in this case two different CSS files were added to the HTML:

<title>My Website</title>
<head>
<<< HEAD
<link rel="stylesheet" href="theme/blue.css">
=======
<link rel="stylesheet" href="theme/red.css">
>>> red
</head>
<body>

The <<< HEAD and >>> red lines show you the two different versions of the file split by ======.

The portion between <<< HEAD and ==== is the version of the file that was on the current branch, in this case main.

The portion between ==== and >>> red is the version of the file that was on the branch we’re merging in, in this case red.

We make a choice here, perhaps we are going to support both:

<title>My Website</title>
<head>
<link rel="stylesheet" href="theme/blue.css">
<link rel="stylesheet" href="theme/red.css">
</head>
<body>

Whatever change we make, we would then make a git commit and git push the same way that we would any change.

It is a good idea to run your tests after a merge, to ensure that the changes on the two branches weren’t in conflict with one another.

(You have good tests for your code right?)

Aborting a Merge

Sometimes you attempt a merge and discover you aren’t prepared to resolve the conflict.

In this case, you can abort the merge with git merge --abort.

This will rewind your repository to the state it was in before you tried to merge, so you can consider other approaches.

Deleting Branches

When we’re done with a branch we can delete it:

git branch -d login-page

All that this command does is delete the branch reference, the underlying commits will never be deleted.

If you try to delete a branch that isn’t yet merged into the branch you’re working from, Git will warn you and prevent you from doing this.

Caution

If you want to do it anyway, you can use git branch -D.

This is a git command that can cause you to lose work, be careful with it.

Remote Branches

So far, we’ve been working with branches that only exist on our local machine. To share branches with other developers, we need to push them to a remote repository.

Pushing

To work with remote branches, you’ll need a remote set up, which we saw in Part 1. (If you created/cloned the repo from GitHub a remote already exists).

To push a branch to GitHub:

# push the ui branch to the origin remote
git push origin ui

If you’d like to be able to just type git push to push the current branch, you can set up a default remote branch:

# sets it as default
git push -u origin ui

From then on, you can just type git push to push the ui branch to the remote.

Fetch & Pull

If you want to pull a remote branch that exists on the remote but not locally (e.g. to check out a teammates work), you can use git fetch:

# fetch the login-page branch from the origin remote
git fetch origin login-page

This will create a local branch called origin/login-page that you can check out & work with as usual.

If your intent is to merge all of the changes from the remote branch into your current branch, you can use git pull:

# fetch the login-page branch from the origin remote, and merge it into the current branch
git pull origin login-page

Deleting Remote Branches

If you want to delete a remote branch, you can use git push with the --delete flag:

git push origin --delete login-page   # delete the login-page branch from the origin remote

(You can also do this from GitHub’s web interface, which is handy if you’re using Pull Requests.)

Working in Teams

When working solo you may find branches useful, or you may prefer to stick to a trunk-based workflow without branching.

When working in teams however, branches are close to essential.

Team members can do their work on branches, merging to a central branch (often main) after work is ready.

“GitHub Flow”

A model that works well for solo work or small to mid-sized teams is the “GitHub Flow” model.

In this model there is only one long-lived branch, usually called main.

All work is done on feature branches, which are merged into main when they are ready.

This means you never commit directly to main, the only commits on main are merges from feature branches.

General workflow:

  • Create a feature branch aimed at tackling a specific problem
  • Make commits on the feature branch as needed
  • When the feature is ready, open a pull request. This lets the team review the code and discuss it.
  • Once the pull request is approved, merge it into main.
  • Delete the feature branch.

You can read more about this workflow here: https://docs.github.com/en/get-started/quickstart/github-flow

GitHub Pull Requests

Whether or not you adopt “github flow”, you may still want to use pull requests instead of manually merging to main.

This is a process that allows team members to submit work on its own branch for review, gather feedback & improve their branch, and when ready– it can be merged into main.

GitHub has a guide on using Pull Requests that you should read if you plan to use them.

Branches off Branches?

It is possible to branch off of any branch, not just main. You may do this if exploring multiple options on your own feature branch, for instance.

Especially until you’re comfortable working with branches, I would recommend minimizing the number of branches you have at once.

In general, the longer a branch lives the harder it becomes to merge back to main. Small, short-lived branches are much less likely to have merge conflicts or other issues.

Good Commit Messages

If you are working solo, you may not write commit messages that are particularly descriptive:

  • “made edits”
  • “more edits…”
  • “ughhh not working”
  • “finally done”
  • “clean up”

But, when working with a team, it’s important to write a message that will help others understand the change:

A good commit message should:

  • Have a first line that is a summary of the change (<50 characters)
  • Have a blank line after the summary
  • Have a more detailed description of the change as needed
  • Be written in the imperative (e.g. “Add” instead of “Added”)
  • Explain why the change was made, not what the change was (since that will be in the diff)

Further Reading


  1. There are commands that will “replace” a commit, but this creates a new commit ID and thus a new commit.↩︎