Thursday, May 18, 2017

Git repo - a tale of two remotes

One repo, two remotes

So you have a Git repo linked to a particular remote. But you need your code available somewhere else as well. Be it because of functionality which you want to keep private, a deploy to cloud environment or the reason lies elsewhere.

How can one approach this kind of situation, i.e. how to add and manage multiple remotes?


One remote

As an example we will be working with an extremely simple repo called test. Let's create it first.

dm@Z580:~/workspace$ mkdir test
dm@Z580:~/workspace$ cd test
dm@Z580:~/workspace/test$ git init
Initialized empty Git repository in /home/dm/workspace/test/.git/
dm@Z580:~/workspace/test$ git checkout -b master
Switched to a new branch 'master'
dm@Z580:~/workspace/test$ echo "test file" > testfile.txt
There, a remote-less Git repo with a branch called master containing only a single file testfile.txt.

Next we will add a GitHub remote called origin and list all available remotes. Of course, you have to create the repo on GitHub first in order to obtain it's URL.

dm@Z580:~/workspace/test$ git remote add origin git@github.some_user/test.git
dm@Z580:~/workspace/test$ git remote -v
origin git@github.com:some_user/test.git (fetch)
origin git@github.com:some_user/test.git (push)
Now it's time to push our local repo (it's master branch) to GitHub.
dm@Z580:~/workspace/test$ git add testfile.txt
dm@Z580:~/workspace/test$ git commit -m 'Initial commit'
[master (root-commit) f49cafd] Initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 testfile.txt
dm@Z580:~/workspace/test$ git push -u origin master 
Counting objects: 3, done.
Writing objects: 100% (3/3), 229 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.some_user/test.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

Adding another remote

Right, that's a public GitHub remote. We will carry on and add a new private Bitbucket remote as well (Bitbucket offers it for free!). First create the repo on Bitbucket and then add a new remote to our local repo. List remotes to see what's available.

dm@Z580:~/workspace/test$ git remote add bitbucket ssh://git@bitbucket.org/some_user/test.git
dm@Z580:~/workspace/test$ git remote -v
bitbucket ssh://git@bitbucket.org/some_user/test.git (fetch)
bitbucket ssh://git@bitbucket.org/some_user/test.git (push)
origin git@github.com:some_user/test.git (fetch)
origin git@github.com:some_user/test.git (push)
Same as with GitHub, the Bitbucket remote has no branch. Let's push our local master branch to the new remote. Afterwards, list all available branches to see what happened.
dm@Z580:~/workspace/test$ git push -u bitbucket master
Writing objects: 100% (3/3), 229 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ssh://git@bitbucket.org/some_user/test.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from bitbucket. 
dm@Z580:~/workspace/test$ git branch -a
* master
  remotes/bitbucket/master
  remotes/origin/master
As you can see, the master branch is now present in each repo - local and both remotes.

Private content

We are finally going to add and push a private file. To keep content separated, we will create a new branch called private.

dm@Z580:~/workspace/test$ git checkout -b private
Switched to a new branch 'private'
dm@Z580:~/workspace/test$ echo "private file" > privatefile.txt
dm@Z580:~/workspace/test$ git add privatefile.txt
dm@Z580:~/workspace/test$ git commit -m 'Add a private file'
[private 42caa23] Add a private file
 1 file changed, 1 insertion(+)
 create mode 100644 privatefile.txt
We need to setup branch tracking prior to pushing our private content to remote. In other words - specify which remote branch is tracked by our local private branch.

Branch mapping

Currently, no mapping is defined.

dm@Z580:~/workspace/test$ git branch -vv
  master  f49cafd [bitbucket/master] Initial commit
* private bdc169d Add a private file
As our local and remote branch names are different, we have to change our repo's config. Change the git push behavior to be specific.
dm@Z580:~/workspace/test$ git config push.default upstream
Now we can setup upstream and push our private content.
dm@Z580:~/workspace/test$ git branch -u bitbucket/master 
Branch private set up to track remote branch master from bitbucket.
dm@Z580:~/workspace/test$ git push 
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 298 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ssh://git@bitbucket.org/some_user/test.git
   f49cafd..67bc2eb  private -> master
Good, so if you now check both remotes you will see that privatefile.txt is present only on Bitbucket. Exactly as we had in mind!

Although, one more thing requires our attention. The local master branch is still pointed to Bitbucket. Thankfully, the fix is quite straightforward.

dm@Z580:~/workspace/test$ git branch -vv
  master  f49cafd [bitbucket/master: behind 1] Initial commit
* private 67bc2eb [bitbucket/master] Add a private file
dm@Z580:~/workspace/test$ git checkout master 
Switched to branch 'master'
Your branch is behind 'bitbucket/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
dm@Z580:~/workspace/test$ git branch -u origin/master 
Branch master set up to track remote branch master from origin.
dm@Z580:~/workspace/test$ git status 
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working directory clean
dm@Z580:~/workspace/test$ git branch -vv
* master  f49cafd [origin/master] Initial commit
  private 67bc2eb [bitbucket/master] Add a private file

Final touches

Don't you think that it's a bit weird we have an origin and Bitbucket remotes? Also, there are master and private branches. Renaming both GitHub remote and branch will be more clean and concise.

dm@Z580:~/workspace/test$ git remote rename origin github
dm@Z580:~/workspace/test$ git branch -m master public
dm@Z580:~/workspace/test$ git remote -v
bitbucket ssh://git@bitbucket.org/some_user/test.git (fetch)
bitbucket ssh://git@bitbucket.org/some_user/test.git (push)
github git@github.com:some_user/test.git (fetch)
github git@github.com:some_user/test.git (push)
dm@Z580:~/workspace/test$ git branch -vv
  private 67bc2eb [bitbucket/master] Add a private file
* public  f49cafd [github/master] Initial commit
Now, that's better. Local branch public is tracking master on GitHub and private is tracking bitbucket's master. Moreover, using git push will push only to the linked remote branch.