Home iOS & Swift Books Advanced Git

11
Forking Workflow Written by Jawwad Ahmad

In this chapter, you’ll learn all about the Forking Workflow. You use the Forking Workflow when you want to contribute to a project to which you only have read-only access. It’s mainly used when contributing to open source projects, but you can also use it with private repositories.

When you don’t have push access to a project, you’ll need to push your changes to a public copy of the project. This personal, public copy of the project is called a fork. The original, or source, repository is conventionally referred to as the upstream repository.

To request that the upstream repository merge a branch from your fork, you then create a pull request with the branch that has your changes.

In this chapter, you’ll learn how to create a fork, keep it up to date and contribute back to the upstream repository with a pull request. You’ll also learn how to merge in open pull requests and branches from other forks.

Getting started

As a software developer, you’ve likely heard of FizzBuzz. In case you haven’t, it’s a programming task where, for numbers from 1 to 100, you print either the number itself or a word. For multiples of three, you print Fizz, for multiples of five you print Buzz, and for multiples of both three and five, you print FizzBuzz.

For example, here are the first fifteen items:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Buzz
13
14
FizzBuzz

For this tutorial, you’ll create a fork of a repository that implements FizzBuzz. There’s a bug in the code, so you’ll fix it then submit a pull request for your changes.

In a browser, open the following URL for the repository:

https://github.com/raywenderlich/git-book-fizzbuzz

Note: The git-book-fizzbuzz repository uses main instead of master as the default branch.

Now, click the Fork button at the top-right corner of the page:

You’ll see a progress screen indicating that GitHub is creating your fork:

Once GitHub finishes, it will redirect you to the newly-created fork, under your personal GitHub account. You’ll see the URL of the page change to https://github.com/{your-github-username}/git-book-fizzbuzz.

Next, click on the Code button drop-down, then click the clipboard icon to copy the repository’s URL:

Now, open Terminal and cd to the starter folder of this project:

cd {your/path/to}/forking-workflow/projects/starter

Next, type git clone, add a space and paste the copied repository URL.

You should have the following, with your GitHub username in place of {username}:

git clone https://github.com/{username}/git-book-fizzbuzz.git

Press Enter to execute the command. You’ll see the following, confirming the clone:

Cloning into 'git-book-fizzbuzz'...
...
Resolving deltas: 100% (14/14), done.

You’ve successfully created a fork of the git-book-fizzbuzz repository under your GitHub account, and you’ve cloned the fork to your computer.

Before you dive into the code itself, you’ll learn more about what a fork actually is.

A fork is simply a clone

In the previous section, you created a fork and then cloned it. So if a fork is just a clone, then you cloned your clone!

git clone https://github.com/raywenderlich/git-book-fizzbuzz.git upstream-git-book-fizzbuzz
diff -r git-book-fizzbuzz upstream-git-book-fizzbuzz -x logs -u
...
 [remote "origin"]
-	url = https://github.com/{username}/git-book-fizzbuzz.git
+	url = https://github.com/raywenderlich/git-book-fizzbuzz.git
...
Binary files git-book-fizzbuzz/.git/index and upstream-git-book-fizzbuzz/.git/index differ
cd upstream-git-book-fizzbuzz
git remote set-url origin https://github.com/{username}/git-book-fizzbuzz.git
cd ..
diff -r git-book-fizzbuzz upstream-git-book-fizzbuzz -x logs -u
rm -rf upstream-git-book-fizzbuzz

Exploring the code

Change to git-book-fizzbuzz and open fizzbuzz.py in an editor.

cd git-book-fizzbuzz
open fizzbuzz.py # or open manually in an editor of your choice
if __name__ == "__main__":
    main()
def main():
    fizzbuzz()
def fizzbuzz():
    for n in range(1, 101):
        value = fizzbuzz_for_num(n)
        print(value)
def fizzbuzz_for_num(
    n,
    fizz_divisor=3,
    fizz_word="Fizz",
    buzz_divisor=5,
    buzz_word="Buzz",
):
    should_fizz = n % 3 == 0
    should_buzz = n % 5 == 0
    if should_fizz and should_buzz:
        return fizz_word + buzz_word
    elif should_fizz:
        return fizz_word
    elif should_buzz:
        return buzz_word
    else:
        return str(n)
 def fizzbuzz_for_num(
     n,
+    fizz_divisor=3,
     fizz_word="Fizz",
+    buzz_divisor=5,
     buzz_word="Buzz",
 ):
python test_fizzbuzz.py
...
    self.assertEqual(fizzbuzz_for_num(7, fizz_divisor=7, buzz_divisor=11), "Fizz")
AssertionError: '7' != 'Fizz'
...
    self.assertEqual(fizzbuzz_for_num(11, fizz_divisor=7, buzz_divisor=11), "Buzz")
AssertionError: '11' != 'Buzz'
...
    self.assertEqual(fizzbuzz_for_num(77, fizz_divisor=7, buzz_divisor=11), "FizzBuzz")
AssertionError: '77' != 'FizzBuzz'
...
Ran 6 tests in 0.001s

FAILED (failures=3)

Fixing the custom divisors bug

Create a new branch for your fix named fix-divisors-bug:

git checkout -b fix-divisors-bug
11)    should_fizz = n % 3 == 0  # replace 3 with fizz_divisor
12)    should_buzz = n % 5 == 0  # replace 5 with buzz_divisor
...
-    should_fizz = n % 3 == 0
-    should_buzz = n % 5 == 0
+    should_fizz = n % fizz_divisor == 0
+    should_buzz = n % buzz_divisor == 0
     if should_fizz and should_buzz:
...
python test_fizzbuzz.py
test_divisible_by_both (__main__.TestFizzBuzz) ... ok
test_divisible_by_five (__main__.TestFizzBuzz) ... ok
test_divisible_by_none (__main__.TestFizzBuzz) ... ok
test_divisible_by_three (__main__.TestFizzBuzz) ... ok
test_with_alternate_divisors (__main__.TestFizzBuzz) ... ok
test_with_alternate_words (__main__.TestFizzBuzz) ... ok

----------------------------------------------------------------
Ran 6 tests in 0.001s

OK
git commit -a --file=../commit_message.txt
Fix bug in which alternate divisors were not used

This commit updates the code in the fizzbuzz_for_num method to
start using the fizz_divisor and buzz_divisor parameters that
were added to the method signature in a previous commit

Verified the fix by running existing tests in test_fizzbuzz.py
which were previously failing and now are all passing

Opening a pull request

Run the following to push the current branch to your fork:

git push -u origin head
git push --set-upstream origin fix-divisors-bug  # same as above

...
remote: Create a pull request for 'fix-divisors-bug' on GitHub by visiting:
remote:      https://github.com/{username}/git-book-fizzbuzz/pull/new/fix-divisors-bug
...

Rewinding your main branch

Unfortunately, there won’t be any updates to the upstream repository from the time that you cloned (or perhaps ever!), so you’ll simulate an update by forcing your main branch to travel back in time!

git checkout main
git reset head~2 --hard
HEAD is now at 27e6f9a Move the "Fizz" and "Buzz" strings int...
git push -f origin main

Adding upstream and fetching updates

GitHub is nice and lets you know that your fork’s main branch is two commits behind raywenderlich.com’s main branch. But it doesn’t actually give you a server-side option of updating your branch directly from upstream. Clicking a button would be too easy, right? :]

git remote add upstream https://github.com/raywenderlich/git-book-fizzbuzz.git
origin https://github.com/{username}/git-book-fizzbuzz.git (fetch)
origin https://github.com/{username}/git-book-fizzbuzz.git (push)
upstream https://github.com/raywenderlich/git-book-fizzbuzz.git (fetch)
upstream https://github.com/raywenderlich/git-book-fizzbuzz.git (push)
git fetch upstream
From https://github.com/raywenderlich/git-book-fizzbuzz
 * [new branch]      main       -> upstream/main
d1dcc72 (origin/fix-divisors-bug, fix-divisors-bug) Fix bug i...
85ca623 (upstream/main) Add parameters to allow using divisor...
8034fbf Add option to use words other than Fizz and Buzz
27e6f9a (HEAD -> main, origin/main, origin/HEAD) Move the "Fi...
...
git merge upstream/main
git push

Fetching changes from other forks

You may occasionally want to merge feature branches from other forks into your fork. Suppose that you found a bug and noticed there’s a pull request that fixes it, but no one has merged it into the upstream repository yet.

git checkout -b development
git merge fix-divisors-bug

-def fizzbuzz():
-    for n in range(1, 101):
+def fizzbuzz(start=1, end=100):
+    for n in range(start, end + 1):
         value = fizzbuzz_for_num(n)
         print(value)

Fetching directly from a URL

To fetch from a URL, just use that URL in place of the remote name. So, for example, instead of git fetch upstream you’d run:

git fetch https://github.com/raywenderlich/git-book-fizzbuzz.git
git fetch {remote_url} {remote_branch_name}
git fetch {remote_url} {remote_branch_name:local_branch_name}
git fetch https://github.com/jawwad/git-book-fizzbuzz.git allow-custom-range
From https://github.com/jawwad/git-book-fizzbuzz
 * branch            allow-custom-range -> FETCH_HEAD
cat .git/FETCH_HEAD
c7580ff4a6231bbcfd21b46ddbb204ef472f590b		branch 'allow-custom-range' of https://github.com/jawwad/git-book-fizzbuzz
git branch acr-from-fetch-head FETCH_HEAD
* d1dcc72 (HEAD -> development, origin/fix-divisors-bug, fix-...
| * c7580ff (acr-from-fetch-head) Add start and end parameter...
|/
* 85ca623 (upstream/main, origin/main, origin/HEAD, main) Add...
...
git fetch https://github.com/jawwad/git-book-fizzbuzz.git allow-custom-range:allow-custom-range
From https://github.com/jawwad/git-book-fizzbuzz
 * [new branch]      allow-custom-range -> allow-custom-range
* d1dcc72 (HEAD -> development, origin/fix-divisors-bug, fix-...
| * c7580ff (allow-custom-range, acr-from-fetch-head) Add sta...
|/
* 85ca623 (upstream/main, origin/main, origin/HEAD, main) Add...
...

Fetching a pull request

Any branches that are part of a pull request are available on the upstream repository in a special reference that uses the format: pull/{ID}/head. So for this pull request, it would be pull/3/head.

git fetch upstream pull/3/head:acr-from-pull
git log --oneline acr-from-pull
c7580ff (allow-custom-range, acr-from-pull, acr-from-fetch-head)

Adding an additional remote

Run the following to add jawwad’s fork as an additional remote:

git remote add jawwad https://github.com/jawwad/git-book-fizzbuzz.git
jawwad https://github.com/jawwad/git-book-fizzbuzz.git (fetch)
jawwad https://github.com/jawwad/git-book-fizzbuzz.git (push)
origin https://github.com/{username}/git-book-fizzbuzz.git (fe..
origin https://github.com/{username}/git-book-fizzbuzz.git (pu..
upstream https://github.com/raywenderlich/git-book-fizzbuzz.git
upstream https://github.com/raywenderlich/git-book-fizzbuzz.git
From https://github.com/jawwad/git-book-fizzbuzz
 * [new branch]      add-type-hints     -> jawwad/add-type-hints
 * [new branch]      allow-custom-range -> jawwad/allow-custo...
 * [new branch]      fix-divisors-bug   -> jawwad/fix-divisor...
 * [new branch]      main               -> jawwad/main
git remote rm jawwad

Merging the pull request

Run the following to merge the allow-custom-range branch:

git merge allow-custom-range --no-edit
git branch -d acr-from-pull acr-from-fetch-head
git push -u origin head

Key points

  • You use the Forking Workflow to contribute to repositories that you don’t have push access to, like open-source repositories.
  • Forking involves three main steps: Clicking Fork on GitHub, cloning your fork, and adding a remote named upstream.
  • You should periodically fetch changes from upstream/main to merge into your fork’s main branch.
  • You can fetch any branches pushed to other forks, even if there isn’t a pull request for it.
  • To fetch all changes from a named remote, use git fetch {remotename}.
  • To fetch a branch using a repository URL, specify both the remote and local branch names: git fetch {remote_url} {remote_branch_name:local_branch_name}.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.