How to manage multiple remote repositories with git (terminal)
Tags: beginners, git, github, writing
Published: 2020-10-29 06:47:08

It is not uncommon to have more than one repo to keep track of; sometimes we have a local copy of the main repo that we work on and fiddle with until we are happy to submit a well-prepared pull request to the main repo.

In this guide, we will go through the steps that I use when setting up and working with multiple remote repositories. In this case, we will be working with two namely origin which is our copy and upstream which is the main repo.

## Fork the repository

To get started we will first fork and clone the main repo so that we have our own version to work on and test things out before we submit changes to the main repo.

Open the GitHub page of the repository that you want to work on and click on "Fork" in the top right corner. Select the account where you would like to fork to.

## Clone the repository locally 
```
$ git clone https://github.com/eugenedorfling/leanpub-guide.git
Cloning into 'leanpub-guide'...
remote: Enumerating objects: 30, done.
remote: Counting objects: 100% (30/30), done.
remote: Compressing objects: 100% (18/18), done.
remote: Total 30 (delta 11), reused 24 (delta 7), pack-reused 0
Unpacking objects: 100% (30/30), done.
Automatically creates origin remote 
```
Now that you have a local copy of your fork, "change directory" to the cloned directory.

```
cd leanpub-guide/
```
By default, cloning a repository will set the URL of that repository as the 'origin' remote. Let's confirm that by listing all configured remotes:

```
$ git remote -v
origin  https://github.com/eugenedorfling/leanpub-guide.git (fetch)
origin  https://github.com/eugenedorfling/leanpub-guide.git (push)

$ git remote get-url origin
https://github.com/eugenedorfling/leanpub-guide.git

```
`git remote -v` lists all configured remotes where `git remote get-url origin` shows the URL of the remote named 'origin'

## Configure upstream remote

Now we want to configure the 'main' repo, from which we forked, as the 'upstream' remote.

First, let's check if the name 'upstream' is available by getting the URL for 'upstream'

```
$ git remote get-url upstream
fatal: No such remote 'upstream'
```
Above you can see that we have no such remote so let's configure it next.

To configure the upstream remote we will use the `git remote add upstream https://main-repo-url.git` command as seen below:

```
$ git remote add upstream https://github.com/ritza-co/leanpub-guide.git
```
The above adds the remote URL with the name 'upstream'. Note you can give these remotes any name that makes sense to you as it is not uncommon to have more than two remotes configured.

To update the name of an existing remote use the command `git remote set-url upstream https://new-url.git` where 'upstream' is the name of the remote that you want to edit.

To remove a remote completely use the command `git remote remove upstream` where 'upstream' is the name of the remote that you want to remove.

Now, let's confirm that we have two remotes configured one for our fork called 'origin' and one for the main repo called 'upstream'. We can do this by listing all remotes with `git remote -v` or you can check them individually with `git remote get-url upstream` as seen below.
```
$ git remote -v
origin  https://github.com/eugenedorfling/leanpub-guide.git (fetch)
origin  https://github.com/eugenedorfling/leanpub-guide.git (push)
upstream        https://github.com/ritza-co/leanpub-guide.git (fetch)
upstream        https://github.com/ritza-co/leanpub-guide.git (push)

$ git remote get-url upstream
https://github.com/ritza-co/leanpub-guide.git

```
If you want to see more information about a particular remote, use the `git remote show` command as seen below:

```
$ git remote show origin
* remote origin
  Fetch URL: https://github.com/eugenedorfling/leanpub-guide.git
  Push  URL: https://github.com/eugenedorfling/leanpub-guide.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

$ git remote show upstream
* remote upstream
  Fetch URL: https://github.com/ritza-co/leanpub-guide.git
  Push  URL: https://github.com/ritza-co/leanpub-guide.git
  HEAD branch: master
  Remote branch:
    master new (next fetch will store in remotes/upstream)
  Local ref configured for 'git push':
    master pushes to master (up to date)
```

## Make some changes to your local repo and push to origin 

Now, let's add a file and push it to our origin repo.

First, we do a sanity check to make sure we are working on the correct branch with the command `git status`.
```
$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean
```
Above we can see that we are working on branch 'origin/master' which is exactly where we want to be.

Now we'll add a simple text file and then add and commit it to be pushed.
```
$ vim firstfile.txt
$ git add .
$ git commit -m 'add firstfile to origin'
[master 43e5983] add firstfile to origin
 1 file changed, 1 insertion(+)
 create mode 100644 firstfile.txt
```
With `vim firstfile.txt` we add a text file. With `git add .` we add all(`.`) files to be committed, then we commit them with `git commit -m 'add firstfile to origin'` where `-m 'add firstfile to origin'` is the commit message.

Now, let's push our changes to our origin repo:
```
$ git push origin 
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 342 bytes | 342.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/eugenedorfling/leanpub-guide.git
   d3600bc..43e5983  master -> master
```

## Make some changes and push to upstream to simulate changes from "their end"

From here we will `fetch` our upstream repo with `git fetch upstream`. The command `fetch` only makes a local copy of the upstream repo which you can explore and work on, it does not merge the upstream with what you have locally like `git pull` will try to do.

```
$ git fetch upstream 
From https://github.com/ritza-co/leanpub-guide
 * [new branch]      master     -> upstream/master
```
From here we can `checkout` the upstream master branch and work on it without overwriting or merging any of our origin files.

```
$ git checkout upstream/master 
Note: checking out 'upstream/master'.

You are in a 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at d3600bc add latest published versions
```
From here let's list the files in the directory to verify that 'firstfile.txt', which we created earlier on the 'origin/master', is not there.

```
$ ls
01-getting-started.md  02-first-chapter.md  03-book-cover-publish.md
```
Now let's create a branch and add a file to that branch then push to 'upstream/branch'.

First we 'checkout' a new branch with ` git checkout -b upstream-change` where 'upstream-change' is the name of the new branch and `-b` allows you to create and checkout to a new branch. This lets you use one command instead of creating the branch first ` git branch branch-name` and then switching to it `git checkout branch-name`.

```
$ git checkout -b upstream-change
Switched to a new branch 'upstream-change'
```
Then, let's add a file to this branch and push it to upstream.

```
$ vim secondfile.txt
$ ls
01-getting-started.md  02-first-chapter.md  03-book-cover-publish.md  secondfile.txt

$ git add .
$ git commit -m 'add-secondfile-upstream'
[upstream-change a6d2e71] add-secondfile-upstream
 1 file changed, 1 insertion(+)
 create mode 100644 secondfile.txt

$ git status
On branch upstream-change
nothing to commit, working tree clean 

$ git push upstream 
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 330 bytes | 330.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote: 
remote: Create a pull request for 'upstream-change' on GitHub by visiting:
remote:      https://github.com/ritza-co/leanpub-guide/pull/new/upstream-change
remote: 
To https://github.com/ritza-co/leanpub-guide.git
 * [new branch]      upstream-change -> upstream-change
```
Pushing the branch to upstream returns a URL where you can go and merge the branch into master through a Pull Request on the GitHub web interface. For this tutorial, I will not be showing this part but you are welcome to check out [this guide on](https://guides.github.com/activities/hello-world/#:~:text=Pull%20Requests%20are%20the%20heart,the%20content%20from%20both%20branches.) on GitHub for more.

Now you can checkout back to master as we are going to look at the differences between origin and upstream next. You can do this with `git checkout master`

## Check the different commits between origin and upstream 

Now we want to compare the two repositories and see if there are any differences, ie. commits that were made to the main repo that we don't have on ours. 

First, we need to do a `fetch` again of the latest upstream repo.

```
$ git fetch upstream
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (1/1), done.
From https://github.com/ritza-co/leanpub-guide
   d3600bc..3ef9c08  master     -> upstream/master
```


Next, we want to see what commits we have in the upstream that we don't have on our origin.

```
$ git log upstream/master ^master
commit dfedb0bf3725619b04720fa44d332e1ed57280d5 (upstream/master)
Author: eugenedorfling <70516548+eugenedorfling@users.noreply.github.com>
Date:   Wed Oct 28 16:28:46 2020 +0200

    Update secondfile.txt

commit 3ef9c0885e6a9a9967f87d660e21e82eee60d5e3
Merge: d3600bc a6d2e71
Author: eugenedorfling <70516548+eugenedorfling@users.noreply.github.com>
Date:   Wed Oct 28 09:13:56 2020 +0200

    Merge pull request #1 from ritza-co/upstream-change
    
    add-secondfile-upstream

commit a6d2e718676cc1ba63228e118381f91c4dd527ef (HEAD -> upstream-change, upstream/upstream-change)
Author: eugenedorfling <eldorfling@gmail.com>
Date:   Wed Oct 28 09:01:50 2020 +0200

    add-secondfile-upstream
```
The above command pulls the logs and shows the commits that are in 'upstream/master' but not(^) in master(checked out origin/master). We can see that there are three commits in upstream that we don't have in our local master. One where a file was added to a branch and one where that branch was merged into the upstream master and one where the file 'secondfile.txt' was updated.

Then we want to check which commits we have in our origin master that the 'upstream/master' does not have by reversing the above command as follows:

```
$ git log master ^upstream/master
commit 0630646154be76ac6de3e603488dfcf79d363c38 (HEAD -> master, origin/master, origin/HEAD)
Author: eugenedorfling <eldorfling@gmail.com>
Date:   Wed Oct 28 16:37:50 2020 +0200

    add some text

commit 43e598384f2802ca486aeef62c1652e08a3f5f59
Author: eugenedorfling <eldorfling@gmail.com>
Date:   Wed Oct 28 08:28:26 2020 +0200

    add firstfile to origin
```
Above we can see that we have two commits that are not in the upstream/master. One where 'firstfile.txt' was added and one where the file was edited.

## Cherry-picking commits to push to upstream / origin

Now we can look a little deeper at what exactly was changed by doing a `git diff` between the two commits we have in our origin git log.

```
$ git diff 43e598384f2802ca486aeef62c1652e08a3f5f59 0630646154be76ac6de3e603488dfcf79d363c38
diff --git a/firstfile.txt b/firstfile.txt
index 31b052a..951c992 100644
--- a/firstfile.txt
+++ b/firstfile.txt
@@ -1 +1,3 @@
 Here is some text that we want to push to the origin/master branch.
+
+Another line added
\ No newline at end of file
```
Above we do a `git diff` between the two commits that we have by copying and pasting the commit id's found in the `git log` command from the previous step.

So we can see some text was added: 'Another line added'. We don't want to move this change over to the upstream, we only want to add 'firstfile.txt' before it was edited. We can accomplish this by cherry-picking the commit we want into a new branch and push that to upstream. Be sure to checkout to the master branch if you haven't already.

First, we want to reset our origin branch to match the upstream branch.

```
$ git reset --hard upstream/master 
HEAD is now at dfedb0b Update secondfile.txt

$ git status
On branch master
Your branch and 'origin/master' have diverged,
and have 3 and 2 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)
```
Above we first did a reset to match our local master with the upstream master. From here we can do a `git pull` to merge origin and upstream but we don't want that so we will work on a new branch that we can push to upstream without changing our origin master branch.

Now that our master matches that of the upstream we can create a new branch and cherry-pick the commits we want to keep.

```
$ ls
01-getting-started.md  02-first-chapter.md  03-book-cover-publish.md  secondfile.txt

$ git checkout -b add-origin-commit-to-upstream
Switched to a new branch 'add-origin-commit-to-upstream'

$ git cherry-pick 43e598384f2802ca486aeef62c1652e08a3f5f59
[add-origin-commit-to-upstream b3fa649] add firstfile to origin
 Date: Wed Oct 28 08:28:26 2020 +0200
 1 file changed, 1 insertion(+)
 create mode 100644 firstfile.txt

$ ls
01-getting-started.md  02-first-chapter.md  03-book-cover-publish.md  firstfile.txt  secondfile.txt

$ cat firstfile.txt 
Here is some text that we want to push to the origin/master branch.
$
```

Above we verify with  `ls` which files exist in the repo. Then we `cherry-pick` the commit by pasting the commit-id form before (where the file was added). Doing `ls` again shows that 'firstfile.txt' was added. Lastly, we confirm that the other commit was not added (the second line of text).

If you do a `git status` now you will see that the working tree is clean because the `cherry-pick` command takes the commit and adds it to this branch directly, there is no need to commit again.
```
$ git status
On branch add-origin-commit-to-upstream
nothing to commit, working tree clean
```
Now we can push this branch to upstream, create a pull request and merge it into the master branch of upstream.

```
$ git push upstream add-origin-commit-to-upstream 
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 344 bytes | 344.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote: 
remote: Create a pull request for 'add-origin-commit-to-upstream' on GitHub by visiting:
remote:      https://github.com/ritza-co/leanpub-guide/pull/new/add-origin-commit-to-upstream
remote: 
To https://github.com/ritza-co/leanpub-guide.git
 * [new branch]      add-origin-commit-to-upstream -> add-origin-commit-to-upstream
```
Once you have created the PR and merged it into master, do a `fetch` from upstream and verify that the firstfile.txt is there.

```
$ git fetch upstream 
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 11 (delta 4), reused 5 (delta 2), pack-reused 0
Unpacking objects: 100% (11/11), done.
From https://github.com/ritza-co/leanpub-guide
 * [new branch]      add-origin-commit-to-upstream -> upstream/add-origin-commit-to-upstream
 + 0630646...9a290d7 master                        -> upstream/master  (forced update)
 * [new branch]      upstream-change               -> upstream/upstream-change
```
From the above output, we can already see that the add-origin-commit-to-upstream branch was added but let's verify that the commit and 'firstfile.txt' are there.

```
$ git log upstream/master 
commit 9a290d7d655d952259d19cd8f9dd5a5ab38612be (upstream/master)
Merge: dfedb0b b3fa649
Author: eugenedorfling <70516548+eugenedorfling@users.noreply.github.com>
Date:   Wed Oct 28 17:20:11 2020 +0200

    Merge pull request #2 from ritza-co/add-origin-commit-to-upstream
    
    add firstfile to upstream

commit b3fa649afdfc906700fcf36bd31ee7c6090446b6 (upstream/add-origin-commit-to-upstream)
Author: eugenedorfling <eldorfling@gmail.com>
Date:   Wed Oct 28 08:28:26 2020 +0200

    add firstfile to origin

$ git checkout upstream/master 
Note: checking out 'upstream/master'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 9a290d7 Merge pull request #2 from ritza-co/add-origin-commit-to-upstream

$ ls
01-getting-started.md  02-first-chapter.md  03-book-cover-publish.md  firstfile.txt  secondfile.txt
```
Above we check the log of upstream/master to confirm the new file commit is listed there. Then we checkout to the upstream/master branch to verify the actual file it there with the `ls` command.

## Syncing upstream with origin

Let's say you want to sync your origin with upstream, you can do this with the `merge` command.

First, check that you are on the local master branch.
```
$ git checkout master 
Previous HEAD position was 9a290d7 Merge pull request #2 from ritza-co/add-origin-commit-to-upstream
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
```
Now make sure you have the latest copy of the upstream by doing a `fetch`

```
$ git fetch upstream 
```
Then we will merge upstream with our local master branch.
```
$ git merge upstream/master 
[master 77566b0] Merge remote-tracking branch 'upstream/master'.
```
Once the merge is complete, you can push to origin and upstream to make sure both remote repositories are in sync.
```
$ git push origin
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 336 bytes | 168.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/eugenedorfling/leanpub-guide.git
   0630646..77566b0  master -> master

$ git push upstream 
Counting objects: 8, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 808 bytes | 808.00 KiB/s, done.
Total 8 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), completed with 1 local object.
To https://github.com/ritza-co/leanpub-guide.git
   9a290d7..77566b0  master -> master

```
Lastly, check the git log to verify that origin/master and upstream/master are the same.

```
$ git log
commit 77566b03978eacdefaaa114b777e10f601909d64 (HEAD -> master, upstream/master, origin/master, origin/HEAD)
Merge: 0630646 9a290d7
Author: eugenedorfling <eldorfling@gmail.com>
Date:   Wed Oct 28 17:57:38 2020 +0200

    Merge remote-tracking branch 'upstream/master'
```
Above in the second line, you can see that the following are all in sync (HEAD -> master, upstream/master, origin/master, origin/HEAD).

Hopefully, you now have a better idea of how to work with multiple remote repositories, configuring remotes, cherry-picking commits and syncing different repositories.

Wishing you well on your git adventures!