Chapters

Hide chapters

Advanced Git

Second Edition · Git 2.32 · Console

Section I: Advanced Git

Section 1: 7 chapters
Show chapters Hide chapters

11. Forking Workflow
Written by Jawwad Ahmad

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In this chapter, you’ll learn all about the Forking Workflow. You use the Forking Workflow 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 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

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:

Unfortunately, GitHub no longer shows the fork-in-a-book-on-a-copier image :[
Unfortunately, GitHub no longer shows the fork-in-a-book-on-a-copier image :[

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 path/to/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% (15/15), 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 the git-book-fizzbuzz directory 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'
- 7
+ Fizz
...
...
    self.assertEqual(fizzbuzz_for_num(11, fizz_divisor=7, buzz_divisor=11), "Buzz")
AssertionError: '11' != 'Buzz'
- 11
+ Buzz
...
...
    self.assertEqual(fizzbuzz_for_num(77, fizz_divisor=7, buzz_divisor=11), "FizzBuzz")
AssertionError: '77' != 'FizzBuzz'
- 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 fizzbuzz_for_num method to start using
the fizz_divisor and buzz_divisor parameters that were added
to the method signature in 85ca623.

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

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 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 8034fbf Add option to use words other than Fizz and Buzz
git push -f origin main

Updating your fork via GitHub

GitHub recently added the option of updating your fork directly from the upstream repository.

$ git pull

From https://github.com/{username}/git-book-fizzbuzz
   8034fbf..98b4ef3  main       -> origin/main
Updating 8034fbf..98b4ef3
Fast-forward
 README.md        |  4 ++--
 fizzbuzz.py      |  2 ++
 test_fizzbuzz.py | 10 ++++++++++
 3 files changed, 14 insertions(+), 2 deletions(-)

Updating your fork using an upstream remote

This method of updating is slightly more involved, but until very recently, it was the only way you could do so. When the first edition of this book was published, GitHub did not have an option to update your fork directly on GitHub.

git checkout main
git reset head~2 --hard
HEAD is now at 8034fbf Add option to use words other than Fizz and Buzz
git push -f origin main

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
061a436 (origin/fix-divisors-bug, fix-divisors-bug) Fix bug in which alterna...
98b4ef3 (upstream/main) Update README to reflect updated name of book
85ca623 Add parameters to allow using divisors other than 3 and 5
8034fbf (HEAD -> main, origin/main, origin/HEAD) Add option to use words oth...
...
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

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
* 061a436 (HEAD -> development, origin/fix-divisors-bug, fix-divisors-bug) F...
* 98b4ef3 (upstream/main, origin/main, origin/HEAD, main) Update README to r...
| * c7580ff (acr-from-fetch-head) Add start and end parameters to the fizzbu...
|/
* 85ca623 Add parameters to allow using divisors other than 3 and 5
* 8034fbf Add option to use words other than Fizz and Buzz
...
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
* 061a436 (HEAD -> development, origin/fix-divisors-bug, fix-divisors-bug) F...
* 98b4ef3 (upstream/main, origin/main, origin/HEAD, main) Update README to r...
| * c7580ff (allow-custom-range, acr-from-fetch-head) Add start and end para...
|/
* 85ca623 Add parameters to allow using divisors other than 3 and 5
* 8034fbf Add option to use words other than Fizz and Buzz
...

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 remote add upstream https://github.com/raywenderlich/git-book-fizzbuzz.git
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 named jawwad:

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/jawwadahmad/git-book-fizzbuzz.git (fetch)
origin  https://github.com/jawwadahmad/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)
From https://github.com/jawwad/git-book-fizzbuzz
 * [new branch]      add-type-hints     -> jawwad/add-type-hints
 * [new branch]      allow-custom-range -> jawwad/allow-custom-range
 * [new branch]      fix-divisors-bug   -> jawwad/fix-divisors-bug
 * [new branch]      main               -> jawwad/main
c7580ff (jawwad/allow-custom-range, allow-custom-range, acr-from-pull, acr-from-fetch-head)
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

  • The Forking Workflow is used to contribute to repositories that you don’t have push access to, like open-source repositories.
  • Forking involves three steps: Clicking Fork on GitHub, cloning your fork, and optionally 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.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now