Git fork and pull collaboration diagram showing upstream repo, fork, clone, and feature branch workflow
Last updated on

Git+GitHub Collaboration Notes


This note provides some info on how to collaborate on software projects using git+GitHub. It is focused on the Fork & Pull collaboration model. This model is described as:

In the fork and pull model, anyone can fork an existing repository and push changes to their personal fork without needing access to the source repository.

Note: While this guide focuses on GitHub, the core git commands work across all major hosting services including GitLab, Bitbucket, and others. The UI steps for creating pull/merge requests will differ slightly between platforms.

Definitions

For me, the terminology was one of the most confusing things about learning to collaborate on git+GitHub. I’m not sure if the definitions I give below are “standard” (again feedback welcome!). But for the sake of clarity, these are the definitions I’ll use here.

  • repository (or repo)

    n.: A location for software or documents, typically hosted in GitHub or GitLab. “My code is stored in a repo on GitHub.

  • fork

    n.: A copy of a repo that you created by forking that repo in GitHub. “My fork of that repo got munged.

    v.: To make a copy of a repo. “Feel free to fork my repo on GitHub.

  • clone

    n.: A local copy of a repo. “I added some files to my clone of the repo.” I’ve heard people refer to a clone as a repo. But for this gist, I’ll try to keep them separate to make it clear what I’m referring to.

    v.: To make a copy of a repo to your local machine. “I forked your repo, but I haven’t yet cloned it to my dev box.

  • remote

    n.: The location of a repo or a fork. The url of a remote is added to a clone and a clone can have more than one remote. “Here’s the url of my repo that you can add as a remote for your clone.

    A remote has a name. Two commonly used names for remotes are “origin” and “upstream”. “origin” usually refers to the fork from which you generated your clone, and “upstream” typically refers to the original GitHub repo from which you generated your fork:

    +----------+
    | original |              +----+
    |   repo   |------------->|fork|
    +----------+              +----+
          ^                      ^
          |                      |  "origin"
          |                      |
          |     "upstream"    +-----+
          +-------------------|clone|
                              +-----+
  • branch

    n.: Development in git happens on a branch. The metaphor is that a repo is a tree and modifications to the repo happen on its branches. We create a new branch from an existing branch. Once a new branch has been created, changes made to that branch won’t affect any other branch, including the branch from which it originated. (The tree metaphor is not perfect because in git we can merge two branches.)

    Each branch must be given a name. Here is GitHub’s branch naming guideline. There is one branch, the “main” branch, that is special. (Historically this was called “master”, but GitHub changed the default to “main” in 2020.) Well, actually, there is nothing special about this branch. It’s only special by convention. The convention is that the “main” branch should always contain a stable, buildable version of the code. (So don’t develop on the “main” branch!) Before code from a branch is merged into “main”, it should be reviewed by one or more developers familiar with the repo. In addition to human review, automated tests (often via GitHub Actions) are run to make sure the code is as bug-free as possible.

Collaboration Details

Let’s say someone has a repo on GitHub and you’d like to contribute. The first step in the “Fork & Pull” collaboration model is to fork their repo. This is easy — just click on the “Fork” button on their repo’s GitHub page.

Once you have forked the repo you have your own copy and are can begin making changes to the code. To get started, run the following on your development machine:

git clone https://github.com/YOUR-GITHUB-USER-NAME/REPO-NAME.git

Replacing “YOUR-GITHUB-USER-NAME” with your GitHub user name, and “REPO-NAME” with the name of the repo you forked. This will create a clone of your fork. The clone will be put into a subdir whose name is the same as the REPO-NAME.

Before you begin working with your clone, do the following:

cd REPO-NAME
git remote add upstream https://github.com/ORIGINAL-AUTHOR-GITHUB-USER-NAME/REPO-NAME.git

This will add a new remote called “upstream” to your clone. So your clone will now have two remotes: “origin” and “upstream”. The “origin” remote refers to your fork of the original repo and “upstream” refers to the original repo you forked from. I’ll talk about why we need the “upstream” remote below.

Making changes and saving them to “origin”

  1. Before changing any code, create a new branch:

    $ git checkout -b BRANCH-NAME

    This says to create and then switch to a new branch named “BRANCH-NAME”.

  2. Make some changes to the code. For example, modify the .gitignore file:

    $ vim .gitignore # make changes and quit vim

  3. Add and commit the changes:

    $ git add .gitignore

    $ git commit -m "your commit message here"

  4. Push the changes to “origin” (your fork):

    $ git push origin BRANCH-NAME

Getting your changes to “upstream”

This is also known as creating a “pull request”. Why is it called a “pull” request? Because you are asking the owner of the original “upstream” repo to consider “pulling” your changes into their repo.

After you completed step 4 above, your changes will live in your fork as well as in your local clone. If you view your fork on GitHub, it will show you your recently-pushed branch (BRANCH-NAME). Now you can click on the “Pull Request” button to create the pull request.

Keeping your fork up to date

You’re probably not the only person generating pull requests to the original “upstream” repo. So to update your fork you can run these commands in your local working dir:

git checkout main
git fetch upstream
git merge upstream/main
git push origin main

This will checkout the “main” branch in your clone (just in case you happened to be on another branch), fetch and then merge changes from the “main” branch in the “upstream” repo, and then push all of this new code to the “main” branch in your fork (“origin”).

Other Git Notes

Good Git Guides

Git Submodules

Tagging

Creating an annotated tag in Git is simple. The easiest way is to specify -a when you run the git tag command:

$ git tag -a v1.4 -m 'my version 1.4'
$ git tag
v0.1
v1.3
v1.4

The -m specifies a tagging message, which gets stored with the tag. If you don’t specify a message on the command line, git launches your editor so you can add a message.

You can see the tag data along with the commit that was tagged by using the git show command.

Note that by default the git push command does not transfer tags to remotes. So you must push the tag like so:

git push origin tag TAG-NAME

Rebasing

Assume the following history exists and the current branch is called “topic”:

                 A---B---C topic
                /
           D---E---F---G main

From this point, the result of the following commands:

git checkout topic
git rebase main

would be this:

                         A'--B'--C' topic
                        /
           D---E---F---G main

Note: you can also rebase from the “upstream” main branch with:

git rebase upstream/main

In case of a conflict, git rebase will stop at the first conflict and leave markers in the code. You can use git diff to locate the markers <<<<<< and make edits to resolve the conflicts.

For each file you edit/fix, you need to tell Git that the conflict has been resolved. This can be done with:

git add FILENAME

After resolving the conflicts, you can continue the rebasing process with:

git rebase --continue

Alternatively, you can undo the git rebase with:

git rebase --abort

Discarding unstaged changes

git stash
git pull
git stash drop

Pull vs Fetch

git pull does a git fetch followed by a git merge.

Working on someone else’s pull request

If someone has created a pull request against the “upstream” repo and you’d like to make changes to the branch from which they issued the pull request, you can do:

git fetch upstream
git checkout BRANCH-NAME

make changes and then:

git push upstream BRANCH-NAME

Working with remotes

Show all remotes

git remote -v

Add a new remote

git remote add REMOTE-NAME https://github.com/ORIGINAL-AUTHOR-GITHUB-USER-NAME/REPO-NAME.git

Get more info about a remote

git remote show REMOTE-NAME

Rename a remote

git remote rename OLD-REMOTE-NAME NEW-REMOTE-NAME

Remove a remote

git remote rm REMOTE-NAME

Change the URL of a remote

git remote set-url REMOTE-NAME NEW-GIT-URL

Fetching from a remote (doesn’t merge)

git fetch REMOTE-NAME

Modern GitHub Tools and Features

GitHub CLI (gh)

The GitHub CLI is an official command-line tool that brings GitHub features directly to your terminal. It’s incredibly useful for streamlining your workflow without switching to the browser.

Installation

# macOS
brew install gh

# Windows
winget install --id GitHub.cli

# Linux (Debian/Ubuntu)
sudo apt install gh

Common Commands

# Authenticate with GitHub
gh auth login

# Create a pull request from the current branch
gh pr create --title "My feature" --body "Description of changes"

# List open pull requests
gh pr list

# View a pull request in the browser
gh pr view --web

# Check out a pull request locally
gh pr checkout 123

# Create an issue
gh issue create --title "Bug report" --body "Description"

# Clone a repo (shorter than git clone)
gh repo clone owner/repo

# Fork a repo and clone it
gh repo fork owner/repo --clone

The GitHub CLI also supports creating draft PRs, assigning reviewers, and managing labels—all from the command line.

GitHub Actions

GitHub Actions is GitHub’s built-in CI/CD platform that automates workflows directly in your repository. It’s become the standard way to run tests, build projects, and deploy code on GitHub.

Basic Workflow Example

Create a file at .github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

This workflow runs automatically on every push to main and on every pull request targeting main. GitHub Actions integrates seamlessly with pull requests, showing check statuses directly on the PR page.

Branch Protection Rules

For repositories with multiple contributors, branch protection rules help maintain code quality:

  1. Go to SettingsBranchesAdd rule
  2. Common protections include:
    • Require pull request reviews before merging
    • Require status checks to pass (e.g., GitHub Actions tests)
    • Require branches to be up to date before merging
    • Restrict who can push to the branch

Draft Pull Requests

When you’re still working on changes but want early feedback, create a draft PR:

gh pr create --draft --title "WIP: New feature"

Draft PRs signal that your work isn’t ready for final review. When you’re ready, convert it to a regular PR by clicking “Ready for review” or using:

gh pr ready

Code Review Best Practices

Modern GitHub collaboration emphasizes thorough code review:

  • Use suggested changes: Reviewers can propose specific code changes that authors can accept with one click
  • Request specific reviewers: Use gh pr create --reviewer username or CODEOWNERS files
  • Use review threads: Keep discussions focused on specific lines of code
  • Require approvals: Configure branch protection to require one or more approving reviews