Is everything about the remote these days? - The story of Git, part four

It was two weeks since my last post, it wasn't a long time, I guess... I'll try to maintain this sequence - one post every Saturday around noon, and after several weeks of that, I'll take a two weeks long break, just to gather some thoughts, and ideas. I hope it doesn't mess up your schedule, but I'm still trying to navigate these online creator waters. ;)

You probably opened this blogpost and thought - okay, let me see what he has to say about remote work, and all that stuff, but how does Git fit into this discussion? Well, this article is not about the remote work and however it may or may not affect us, it is related to the Git remotes, and how to configure and play with them.

This is the fourth part of my blog post series about Git. In the first part we've covered git objects - blobs, trees and commits. In the second part we've talked about branches, tags and HEAD. Third part introduced the TBD - Trunk-Based Development branching strategy... Now, sit down, relax, take a cup of hot or cold beverage, whatever your preference, and dive with me into the world of git remotes.

Long story short - in Git, you were able to use remotes long before it was cool! ;)

Now to some "serious stuff".

What are Git remotes?

Remotes, or remote repositories are versions of your project that are hosted on the Internet, somewhere on the network, or even on your machine, but in a different location - elsewhere, rather than remote.[1] Why would you need to host your code somewhere? Main reason - collaboration. You are able to collaborate with others by using and/or creating remote repositories. Those repositories can be private or public - you can share them with specific people, or the whole world. You decide that when you create your remote repository somewhere on the internet. Some of the platforms where you can easily create and store remote repositories include github.com, gitlab.com, bitbucket.org, etc. There is no best platform for hosting git repositories, all of them have some pros and cons. On some of my projects I prefer using gitlab.com, and on the other, github.com is my go to platform. It really depends on your use case. But that is not the point now. For the sake of this example, I'll use several repositories that I've created on the github.com.

The main thing to remember about remote repositories is that there can be one or more remotes configured for your repository. Usually we end up working with one remote repository - the famous origin. What if we have more than one, how can we take on the management of those repositories?

Working with remotes

Firstly, to view remote repositories, you can go and run git remote or git remote -v to see the complete list. The output will be somewhat similar to below one.

$ git remote -v
origin  git@github.com:alternaivan/vigilant-couscous.git (fetch)
origin  git@github.com:alternaivan/vigilant-couscous.git (push)

In the example above, I'm using one that I've created on github.com for testing purposes. Now, what would it be like to add the another remote? The use case behind another remote could be if you, for example, have chosen to fork some open source project, you can add an upstream remote, which will point to the original repository, and your origin remote will point to the one you have forked. Example on adding another remote is below.

$ git remote add upstream git@github.com:alternaivan/laughing-robot.git
$ git remote -v
origin  git@github.com:alternaivan/vigilant-couscous.git (fetch)
origin  git@github.com:alternaivan/vigilant-couscous.git (push)
upstream        git@github.com:alternaivan/laughing-robot.git (fetch)
upstream        git@github.com:alternaivan/laughing-robot.git (push)

As you can see in the output above, we now have a new remote called upstream with both fetch and push set to the url of the new repository. This second repository is also a testing one I've created for this post.

Let's say somebody made a change on upstream and we want to apply it to the our copy of the repository and push it to our remote. In order to do that, we do the following.

$ git checkout main

# Showing the logs
$ git lg
* e09d81a - (HEAD -> main, upstream/main, origin/main) Adding upstream (2 hours ago) <Test>
* e7906f8 - my second commit (6 days ago) <Test>
* 6f4b9fd - my first commit (6 days ago) <Test>

# Fetching changes from the upstream
$ git fetch upstream 
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 296 bytes | 296.00 KiB/s, done.
From github.com:alternaivan/laughing-robot
   e09d81a..f19b14c  main       -> upstream/main

# Showing the logs
$ git lg
* e09d81a - (HEAD -> main, origin/main) Adding upstream (2 hours ago) <Test>
* e7906f8 - my second commit (6 days ago) <Test>
* 6f4b9fd - my first commit (6 days ago) <Test>

# Merging from upstream/main to main
$ git merge upstream/main main
Updating e09d81a..f19b14c
Fast-forward
 upstream.md | 2 ++
 1 file changed, 2 insertions(+)

# Showing the logs
$ git lg
* f19b14c - (HEAD -> main, upstream/main) Adding to upstream repo (2 minutes ago) <Test>
* e09d81a - (origin/main) Adding upstream (2 hours ago) <Test>
* e7906f8 - my second commit (6 days ago) <Test>
* 6f4b9fd - my first commit (6 days ago) <Test> 

# Pushing to our (origin) remote repository
$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 316 bytes | 316.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:alternaivan/vigilant-couscous.git
   e09d81a..f19b14c  main -> main

# Showing the logs
$ git lg
* f19b14c - (HEAD -> main, upstream/main, origin/main) Adding to upstream repo (3 minutes ago) <Test>
* e09d81a - Adding upstream (2 hours ago) <Test>
* e7906f8 - my second commit (6 days ago) <Test>
* 6f4b9fd - my first commit (6 days ago) <Test>$ git push 

Now, to explain the output of the command above. First, we check for logs before getting changes from upstream. In the output of the git lg command we can see that our HEAD/main, upstream/main and origin/main all point to the same commit.

Why git lg and not git log? The first one is the alias which will pretty-print the logs and info you'll need, and the second one is the native Git command. The alias looks like this (you can paste this in your git repository directory): git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --".

Second, in order to see what has changed on the remote upstream repo, we need to get the changes from there. We can do that in two ways - fetch or pull. First one is the "safer" one - it will only fetch the latest changes from the remote repository, while the second one will fetch and will try to merge those changes. Why is the fetch safer one? Because pull will always try and merge changes, and if it fails, git will scream at you. I always go for the fetch option, even though when I started tinkering with git I almost always used pull and on some occasions I even got headaches from git "screaming" at me that it's not able to merge changes in my current working directory... but more on that in some other post.

After that we again show logs, and you can see that now the upstream/main is not pointing to any commit we have in the logs. Why? Because it was updated and it went forward, so we don't have it in our git log.

Fourth step shows the merging of upstream/main into main branch. After that we again show the logs and can see now how everything except origin/main got updated. Now the last step is to push our changes to origin/main branch, as seen above. With that we actually finish with the updating our remote repo with the changes from another remote.

Additional actions on Git remotes

Below, you can find some of the additional actions on remote repositories you can perform:

  1. show information about the remote
$ git remote show upstream
* remote upstream
  Fetch URL: git@github.com:alternaivan/laughing-robot.git
  Push  URL: git@github.com:alternaivan/laughing-robot.git
  HEAD branch: main
  Remote branch:
    main tracked
  Local ref configured for 'git push':
    main pushes to main (up to date)
  1. renaming the remote
$ git remote rename upstream up-stream

$ git remote -v
origin  git@github.com:alternaivan/vigilant-couscous.git (fetch)
origin  git@github.com:alternaivan/vigilant-couscous.git (push)
up-stream       git@github.com:alternaivan/laughing-robot.git (fetch)
up-stream       git@github.com:alternaivan/laughing-robot.git (push)
  1. removing the remote
$ git remote remove up-stream

$ git remote -v
origin  git@github.com:alternaivan/vigilant-couscous.git (fetch)
origin  git@github.com:alternaivan/vigilant-couscous.git (push)

Conclusion

In order to share your work, or save it somewhere other than the local machine, use git remotes. If you want to contribute to some open-source project, follow their guidlines, usually that would require of you to fork the repo, add the main repo as an upstream in your remotes and interact with it in the described way. Choice is yours (or theirs, in this sense).

This was all for now. I hope you found this, or some - Git related or unrelated blogpost of mine useful. Feel free to share the content I'm creating and also to subscribe, if you haven't already, it's free, and it would mean a lot that somebody is interested in these scribblings.

Footnotes


  1. https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes ↩︎