Parts of this page was adapted from the Long Term Ecological Research (LTER) Network’s workshop “Collaborative Coding with GitHub”. Those materials can be found at lter.github.io/workshop-github
1 Overview
At this point, you’re likely at least somewhat familiar with the fundamental operations of Git and GitHub; you know how to stage your edits, make (informative) commit messages, and sync (pull/push) those changes between a remote repository and a local clone of that repository. However, in prior modules, a handful of topics have been raised but only touched on in a superficial way. This module is where we will dig into greater detail on some of these “advanced” topics.
We’ll discuss branches and forks–what they are, how they differ, and when/how to use them–how they relate to pull requests, and how to un-do problematic commits with varying levels of severity.
2 Core Vocabulary
We’ve covered a number of the fundamental tools for working with Git and GitHub already. However, to this point, we’ve only lightly touched on “branches” and “forks,” two terms that sometimes arise in advanced Git/GitHub collaborations.
A “branch” is essentially a working area in your Git repository that is separate from, but connected to, your main working area. Branches can be incredibly useful when you have a task to work on but you don’t want to risk damaging the version of your code that already works. Note that “branch” can be either a noun or a verb as with many of the Git vocabulary words discussed earlier (e.g., “commit”, “push”).
Typically, branches are typically created with a specific task/sub-task in mind and deleted once that task is done. There are some exceptions to this but generally keeping a manageable number of active branches and deleting inactive branches will make it easier for your team to collaborate in the right branch(es).
As an analogy, imagine that you want to put a better engine in your car but you don’t want to risk damaging your car as you go about that job. In Git terms, you’d create a copy of your car that you could work on (i.e., a branch) while still running errands or driving to work with the version of your car before you started tinkering (i.e., the main branch). When you feel that you’ve finished upgrading your car, you can seamlessly combine the two cars, keeping all improvements made in the branch version. You could instead decide that the branch version of your car isn’t actually worthwhile and abandon/delete it instead of merging it with the ‘actual’ version of your car.
| Benefits | When to Use | When Not to Use |
|---|---|---|
| - Enables parallel development and experimentation - Facilitates isolation of features or bug fixes - Provides flexibility and control over project workflows |
- When working on larger projects with multiple features or bug fixes simultaneously - When you want to maintain a stable main branch while developing new features or resolving issues on separate branches - When collaborating with teammates on different aspects of a project and later integrating their changes |
- When working on small projects with a single developer or limited codebase - When the project scope is simple and doesn’t require extensive branch management - When there is no need to isolate features or bug fixes |
A “fork” is a duplicate of a GitHub repository that has a different owner than the original. Any repository that you can view on GitHub, you can fork. This includes (1) any public repository, and (2) private repositories to which you have access.
Forks are most commonly used when you want to use someone else’s work to ‘jump start’ your own, but aren’t collaborating with that person and likely don’t want to re-integrate your changes into their version of the project.
Returning to our car analogy, a fork would be when you like a car that you see someone else driving so you make a copy of it and then modify your copy to better suit your lifestyle and aesthetic tastes. You could offer your changes back to the original car owner but probably if they wanted those edits, you’d be a formal collaborator and you’d be working in a branch of their repository rather than your own fork.
| Benefits | When to Use | When Not to Use |
|---|---|---|
| - Enables independent experimentation and development - Provides a way to contribute to a project without direct access - Allows for creating separate, standalone copies of a repository |
- When you want to contribute to a project without having direct write access to the original repository - When you want to work on an independent variation or extension of an existing project - When experimenting with changes or modifications to a project while keeping the original repository intact |
- When collaborating on a project with direct write access to the original repository - When the project does not allow external contributions or forking - When the project size or complexity doesn’t justify the need for independent variations |
The difference between forks and branches is a source of great confusion for many (even veteran!) Git and GitHub users but hopefully this table helps to clarify!
| FAQ | Branch | Fork |
|---|---|---|
| Does making one also create a new repository? | No | Yes! |
| Does repository ownership change when you make one? | No | Yes! |
| Does Git know the “parent” repository/branch? | Yes! | No |
| Does GitHub know the “parent” repository/branch | Yes! | Yes! |
3 Pull Requests
Regardless of whether you branch or fork, when you want to merge your changes into another branch or back to the original repository, it is good practice to use a “pull request” to do this! A pull request (a.k.a. “PR”) is a GitHub feature that allows collaborators on a project to do a specific review of the content from a branch/fork as a final check before those edits are integrated into the another branch/repository.
| Benefits | When to Use | When Not to Use |
|---|---|---|
| - Facilitates code review and discussion - Allows for collaboration and feedback from team members - Enables better organization and tracking of proposed changes |
- When working on a shared repository with a team and wanting to contribute changes in a controlled and collaborative manner - When you want to propose changes to a project managed by others and seek review and approval before merging them into the main codebase |
- When working on personal projects or individual coding tasks without the need for collaboration - When immediate changes or fixes are required without review processes - When working on projects with a small team or single developer with direct write access to the repository |
4 Branch Workflow
Before diving into the specifics of how to use branches while working with Git, let’s take a high-level look at what a branch workflow might look like. Before you start, identify a ‘branch-worthy’ task–this should be a specific, actionable task that can only be done by potentially breaking core project files that need to be unbroken while the task is being pursued.
Once the task is identified:
- Make sure your local repository’s main branch is up-to-date with the remote’s main branch
- Create a branch
- Work in the branch as you would normally with Git
- I.e., make commits as needed and periodically sync with the remote repository
- When the branch-worthy task is done, merge the branch back into the main branch of the repository
- Open a pull request if you want an added layer of checks/safety before integrating your changes
- Switch your local repository back to main and pull the now-integrated changes
- Delete the branch (locally and on the remote)
As you can see from the above text, branches have a few more steps than the Git/GitHub operations we’ve discussed so far. That said, they can be a powerful tool in service of collaborative work because you can have multiple branches active at the same all working on separate tasks. This approach can be an easy (or at least easier) workflow for working together while avoiding conflicts.
Now we’ve gone over this big picture overview, let’s walk step-by-step through creating, working in and ultimately merging branches!
4.1 Create a Branch
Before you create a branch, pull from/sync with GitHub as a precaution so that you are certain your local repository has the most up-to-date content. Failing to do so is a recipe for a brutal merge conflict when you are finished with your branch and want to merge it back into the main flow of your repository.
To begin, go to the “source control” section of Positron.
To create a branch, hover over the “Changes” dropdown menu and click ... to expand the set of Git operations that are visible. From there, scroll down to “Branch” and click it. Finally, click “Create Branch…”.
In the resulting field (top middle of Positron), give your new branch an informative name. In this example we haven’t given our new branch a great name but in a “real” repository you will greatly appreciate having concise but descriptive branch names. Once you’re happy with the name, hit the Enter key on your computer.
Now that you’ve made your new branch locally, you need to send it up to GitHub. Positron calls this “Publishing” but this isn’t conceptually different from any other sync/push–essentially you’re just letting GitHub know that you have a branch locally that it doesn’t have a record of yet. To publish the branch, click the “Publish Branch” button.
You’ll know that this worked when you look at the branch diagram in the bottom left corner of Positron’s source control menu. Note how the second commit from the top has the “origin/main” label while the top-most one has the label that matches whatever you named your new branch.
To create a branch, click the purple button in the “Git” tab of RStudio that shows two rectangles connected by a diamond at right-angles from one another.
In the resulting dialogue box, give your new branch an informative name. In this example we haven’t given our new branch a great name but in a “real” repository you will greatly appreciate having concise but descriptive branch names. Once you’re happy with the name, click the “Create” button (you can ignore the other options and buttons on this dialogue box).
This will create a confirmation message that is superficially similar to the format of messages returned by other Git actions.
You may also notice that in your Git tab where previously it said “main” it now shows whatever name you chose for your branch.
4.2 Work in the Branch
You can now work in a branch in the same way that you work with GitHub via your IDE of choice when you are not using branches.
- Make edits
- Commit changes locally
- Pull from GitHub to reduce the chances of a conflict
- Push your committed changes to GitHub
The reason you use the same workflow is–as previously stated–even if you don’t typically use branches, all work in Git is functionally done in the “main” branch of your repository so your work in this new branch should use the same order of operations as work done in the “main” branch.
4.3 Open & Merge a Pull Request
When you are done with your work in the branch, you will want to merge your new branch with the “main” branch of the repository. This branch merging is most easily done via GitHub so the following instructions are agnostic to IDE and purposefully exclude the repository name from the screen capture area. To start, push/sync your final commit(s) from your local branch with GitHub.
After you push/sync your changes, GitHub should recognize this and automatically create a button at the top of your repository’s home page for you to start the process of creating a “pull request.” Pull requests are how you merge branches on GitHub and the entire process is conducted entirely in the browser so we’ll leave our IDEs until the pull request is completed.
To start the branch merging process, click the “Compare & pull request” button
You will then be prompted to write a title and message for your pull request to give some broader context for what the branch does. This is especially valuable if you are not the one reviewing pull requests as this can help someone quickly familiarize themselves with what you have done.
Once you’re satisfied with your title and message, click the “Create pull request” button.
That done, GitHub will send you to a page that looks very much like a GitHub issue (see the module on issues for more detail). At the top is whatever title and message you just wrote when opening the pull request following by a list of all of the commits in that branch.
Those commits are hyperlinks in case you want to view the specific differences to files edited in this branch.
Note also that if you realize you forgot to do something in your branch (or if someone asks you change something) you can return to your IDE and commit/pull/push and it will automatically update on the pull request. Pull requests are for merging a whole branch, not for merging just a part of the work in the branch.
You or your team can also post messages on a pull request as needed (see the text box at the bottom of the below picture). If you are ready to merge a pull request from your branch into the “main” branch click the “Merge pull request” button.
GitHub will open another text box where you can add a commit message to your acceptance of the pull request. If whoever opened the pull request was sufficiently detailed in their opening comment(s) this may not need to be terribly detailed but it can’t hurt!
Once your message is written, click the “Confirm merge” button.
Your pull request has now been merged! Now we need to do some minor housekeeping in GitHub that–fortunately–GitHub makes really accessible.
4.4 Post-Merge Housekeeping
Branches are meant to be short-lived and deleted once the specific purpose for which they were created has been accomplished. So, once the pull request is merged, we should click the “Delete branch” button. This will ensure that the number of active branches remains manageable and also will let you re-use branch names later on if you deem that necessary.
After you click “Delete branch” it will be replaced by a “Restore branch” button so you could always reclaim it if the deletion was premature.
Finally, if we return to the home page of the repository, we can see that the most recent commit is whatever we put in the pull request text field right before we merged it.
4.5 Update the Local Clone
Now that the branches have been merged on GitHub, we need to make sure our local clone gets those updates from GitHub. This is particularly important if our IDE is still in the branch we created earlier because–now that we’ve deleted the branch’s counterpart in GitHub–we’ll get an error if we attempt to push from that branch.
First, in the “source control” part of Positron, hover over the “Changes” menu and click ... to expand the set of Git operations that are visible. In the resulting dropdown, scroll down and click “Checkout to…”. Note that you may want to close your open files before doing this, particularly if some files were created in the branch because they wouldn’t yet exist in the “main” branch locally (you merged on GitHub with the pull request but haven’t pulled those updates locally).
This should open up a list of the available branches in the top middle of Positron. Find the “main” branch in the list of available branches and click it.
Now that you’re back in the “main” branch, pull the latest changes from GitHub. You should receive all of the commits that you just merged via pull request earlier.
You will know this was successful when you look at the branch diagram at the bottom of the “source control” menu and see that the line for your experimental branch now re-connects to the leftmost branch’s line.
First, in the top right corner of RStudio, click the active branch name and switch to back to the “main” branch. Note that you may want to close your open files before doing this, particularly if some files were created in the branch because they wouldn’t yet exist in the “main” branch locally (you merged on GitHub with the pull request but haven’t pulled those updates locally).
This should create a message that looks like the following image. You should ignore the part of the message telling you that your are up to date; you are not up to date with GitHub yet. The message is referring to the status of your local clone’s version of the “main” branch.
Now that you’re back in the “main” branch, pull the latest changes from GitHub. You should receive all of the commits that you just merged via pull request earlier. This will create a message that is something like the following image–though of course it will list all changed files so it may be a longer message than what is pictured below if you edited more files.
4.6 Branch Housekeeping
Now that our “main” branch is updated, we need to do the same sort of branch housekeeping that we did in GitHub after merging the pull request. In your IDE’s Terminal, delete the finished branch with the following command line code. Remember to replace “BRANCH_NAME” with whatever you named your branch!
git branch -d BRANCH_NAME
Once you’ve deleted that branch, you’ll need to “prune” your branches. The previous code deleted the local version of your branch but your IDE still ‘thinks’ that GitHub has an equivalent of the branch. To reduce the potential for confusion, prune your local clone with the following command line code. Just like branch deletion, this should code should be run in the Terminal.
git remote update origin --prune
Once you’ve run those two lines of code, confirm that it worked by following the below instructions for your IDE!
Click the “Checkout to…” button in the dropdown menu of Git operations. The resulting list of branches should only list the “main” branch under each sub-heading.
5 Fork Workflow
Forking is (arguably) one of the more straightforward GitHub operations but before we cover it in detail, let’s review the broader context. You should only make a fork if all of the following are true:
- You don’t have edit access to the original repository (and can’t reasonably ask for it)
- Your work can’t be done in a branch
- It is unlikely that any edits you want to make will/should ever be integrated back into the original repository
If you’re confident that a fork is warranted:
- From the GitHub landing page for the repository, click the “Fork” button
- Clone your fork of the repository
- Work in your fork as you would normally with Git
- If warranted, open a pull request to merge your edits back into the original repository
Note that in many cases, you won’t do step 4 and in some cases you may not even do step 3. For example, some people fork repositories that have key resources (e.g., education materials, non-CRAN R packages), just so they can guarantee that those resources stay available to them; even if the original repository is deleted/archived, your fork remains safe and active!
5.1 Create a Fork
To start, go to the repository landing page for the repo you want to fork. In the top right of the repository’s GitHub page there is a “Fork” button (between “Unwatch” and “Star”), click it to begin forking.
This redirects you to a page that is very similar to the page for creating a new repository de novo.
Here you can select who you want to own the repository from a dropdown including any organizations you are a member of and your username if you want to personally own the fork. You can also change the repository name (though the default is to retain the same name) and add a description of your purpose for the fork.
You may notice that in this page you do not have the option to specify public versus private or any of the ‘initialize’ steps (e.g., README, gitignore, or license). Forks will inherit these settings from the repository they are forked from so they do not need to specified here.
Once you are happy with the owner of the fork, the name, and the description, click the green “Create fork” button.
Depending on the amount of content in the repository you are forking and your internet speed this may take anywhere from a few seconds to 1-2 minutes so you may need to wait for a moment while GitHub creates a new duplicate repository under the control of the owner you specified.
After the process completes the page will refresh and you will find yourself on the landing page for your new forked repository!
This repository has a fork icon in the top left (to the left of the owner/repository name) and includes a link to the repository that it came from just beneath that.
There is also a new status bar indicating how ‘ahead of’ or ‘behind’ the fork is relative to the original repository. If the “parent” repository is updated (i.e., someone pushes changes to it after you forked) you can click the “Fetch upstream” button to integrate those changes with your fork.
From here on you can work within your fork as you would within any other repository! Clone the fork into your local computer and work as you normally would.
If you decide that your changes are a meaningful improvement that the parent repository could benefit from, you can click the “Contribute” button to begin the process of submitting a pull request to integrate your edits with the parent.
6 Git Resets
What if you make a commit that you want to undo (i.e., remove from your repository’s history)? Git offers a command known as a “reset” to undo commits. There are three levels of severity to a reset; see the table below for more details on each.
| Reset Type | Un-Does Commit | Un-Stages File(s) | Discards Changes | Use-Case |
|---|---|---|---|---|
| Soft | Yes! | No | No | You make a commit but want to edit a typo in the commit message |
| Mixed (Default) |
Yes! | Yes! | No | Your commit included files you actually didn’t mean to include at all (or at least not in that commit) |
| Hard | Yes! | Yes! | Yes! | This is a “scorched earth” solution that is highly situational |
Once you’ve chosen the type of reset that you need, you can use the following syntax to execute it. Note that this is Command Line code that must be run in the “Terminal” pane of Positron/RStudio.
To undo just the most recent commit:
git reset --soft HEAD~1
To undo every commit after a specific commit, you can use the target commit’s “hash” (unique letter/number string identifying that commit). This method does not care how long ago the target commit was so use with caution!
git reset --mixed <commit-hash>
It is much safer to just roll back one commit at a time until you’re happy with how things look than it is to use this approach!
6.1 Finding a Particular Commit
Before you can reset to a specific commit, you need to be able to find the “hash,” and even if you just want to roll back one commit, it’s probably a good idea to take a quick glance at what that commit actually included before un-doing it!
To see your most recent commits (in reverse chronological order), you can run the following Command Line code.
git log --oneline
The commit hashes are the 7-character letter/number strings (e.g., f3b8b9a) on the left of the output from the command above.
A similar view can be found in GitHub by checking out the commit history of your remote repository but if you’re trying to un-do a commit that you haven’t pushed, GitHub can’t show you that commit.
6.2 Local vs. Remote Commits
All of the above only affects local commits (i.e., commits that have not been synced/pushed). If you realize that you want to undo a commit that you’ve already pushed to GitHub, you’ll need to both reset the commit and “force” push back to GitHub to get the remote repository to reset in the same way.
git reset --hard HEAD~1
git push --force
6.3 Reset vs. Revert
If resetting feels too extreme, you could also revert a commit. Reverting moves the status of all files back to what they were under a particular commit but retains the full commit history–including commits made after that focal commit. You can then make a new commit where you officially change files back to that old status but you don’t lose the history of the changes you now skip over as you would with a reset.
To revert a repository:
git revert <commit-hash>



























