Until now we have only worked with a local repository. Remotes are used to synchronize changes between your local repository and another location. This enables you to collaborate with other authors and can prevent loss of work in the event of disk failure on your local machine.
docker run -it gitforpragmatists/01-basics-04-remotes
As usual, we can use git status
to find out what’s going on.
$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
Our local repository is “1 commit behind origin/main
”. What does that mean?
Typically you have one remote and it’s called origin
, there’s nothing special about the name but it is the default that git will provide. origin/main
is the main
branch in the remote repository called origin
.
We can get up to date with origin
by merging
$ git merge
Updating a7a83a5..f2900e4
Fast-forward
CakeRecipe.md | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 CakeRecipe.md
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
Invoking git merge
without a branch argument has merged the configured remote branch into our local branch. By convention they have the same name. There’s nothing special about this merge, all the fast forward stuff from branch and merge still applies. It’s just like we have two local branches called main
and origin/main
and origin/main
happens to be in whatever state main
was on origin
last time we synchronized.
So we’re actually not necessarily up to date with origin
. Just the last time we synchronized with the remote. If someone else has pushed some work we might be out of date. Let’s fetch
the latest state.
$ git fetch
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), 947 bytes | 33.00 KiB/s, done.
From ../remote
f2900e4..f520ee6 main -> origin/main
* [new branch] add-cookie-recipe -> origin/add-cookie-recipe
$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
We’ve retrieved an updated HEAD
for origin/main
and a new branch add-cookie-recipe
. If we check the status, we’re 1 behind again. Let’s catch up.
$ git merge
Updating f2900e4..f520ee6
Fast-forward
CakeRecipe.md | 8 ++++++++
1 file changed, 8 insertions(+)
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
Fetching and then merging is such a common sequence that there is a command to combine the actions git pull
I’ll exit
the exercise and restart it to demonstrate pull
$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
$ git pull
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), 947 bytes | 63.00 KiB/s, done.
From ../remote
f2900e4..f520ee6 main -> origin/main
* [new branch] add-cookie-recipe -> origin/add-cookie-recipe
Updating a7a83a5..f520ee6
Fast-forward
CakeRecipe.md | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 CakeRecipe.md
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
I’ve included pull
for completeness but personally, I rarely use it. I like to have confidence about what will happen when I merge and give myself the opportunity to do something else if the remote state is surprising. In the above example my local branch got updated to a commit I’d never even seen before when I pulled.
If it’s a branch that I haven’t worked on that I just want to get up to date with then I’ll use pull
.
So far we’ve been taking other people’s commits from remotes, let’s push
some of our own.
I like the sound of that add-cookie-recipe
branch. Let’s take a look.
git log origin/add-cookie-recipe
commit 089221cd0953b5efbaeb345334d512688f544423 (origin/add-cookie-recipe)
Author: Elliot <elliot@gitforpragmatists.xyz>
Date: Fri Sep 16 19:48:27 2022 +0000
Add basic cookie recipe
commit f520ee6d3d088b24d6798a8a93992b6b326c017f (HEAD -> main, origin/main)
Author: Elliot <elliot@gitforpragmatists.xyz>
Date: Fri Sep 16 19:48:26 2022 +0000
Add cake method
...
It’s one ahead of main
so we can just do a fast-forward merge.
$ git merge origin/add-cookie-recipe
Updating f520ee6..089221c
Fast-forward
CookieRecipe.md | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 CookieRecipe.md
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
$ git log
commit 089221cd0953b5efbaeb345334d512688f544423 (HEAD -> main, origin/add-cookie-recipe)
Author: Elliot <elliot@gitforpragmatists.xyz>
Date: Fri Sep 16 19:48:27 2022 +0000
Add basic cookie recipe
commit f520ee6d3d088b24d6798a8a93992b6b326c017f (origin/main)
Author: Elliot <elliot@gitforpragmatists.xyz>
Date: Fri Sep 16 19:48:26 2022 +0000
Add cake method
We’ve got 1 new commit on our branch and we’re now “ahead of ‘origin/main’ by 1 commit”.
Using git push
as instructed
$ git push
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To ../remote
f520ee6..089221c main -> main
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
Now origin
is up to date with us and everyone can enjoy the new cookie recipe.
Since merging remote branches is much the same as merging two local branches, you may be wondering what happens if we get a conflict.
I’ve provided a branch ex-add-pie
in that situation, there’s some work that we don’t have on the local branch on the remote and vice versa.
$ git switch ex-add-pie
Switched to branch 'ex-add-pie'
Your branch and 'origin/ex-add-pie' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
If we try to push our change we get rejected.
$ git push
To ../remote
! [rejected] ex-add-pie -> ex-add-pie (non-fast-forward)
error: failed to push some refs to '../remote'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
If we merge and resolve the conflict, we’re able to push
$ git merge
Auto-merging PieRecipe.md
CONFLICT (content): Merge conflict in PieRecipe.md
Automatic merge failed; fix conflicts and then commit the result.
$ vim PieRecipe.md
# In the editor I resolved the conflict as before in 01-03-branch-and-merge
$ git status
On branch ex-add-pie
Your branch and 'origin/ex-add-pie' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: PieRecipe.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git add PieRecipe.md
$ git commit
[ex-add-pie d94a5c9] Merge remote-tracking branch 'refs/remotes/origin/ex-add-pie' into ex-add-pie
$ git push
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 4 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 741 bytes | 92.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0), pack-reused 0
To ../remote
b4c50e6..d94a5c9 ex-add-pie -> ex-add-pie
There is another option to resolving the conflict which is to erase the remote commits by force
ing the push. It’s a destructive option so think carefully before you do it.
$ git switch ex-add-pie
Switched to branch 'ex-add-pie'
Your branch and 'origin/ex-add-pie' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
$ git push --force
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 425 bytes | 53.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
To ../remote
+ b4c50e6...a0ac42f ex-add-pie -> ex-add-pie (forced update)
We’ve forced the remote branch to align with our HEAD
. You can usually assume that the previous HEAD
is no longer referenced and therefore may be unrecoverable so use with caution. Typically I would only use this if I was overwriting an amended commit where it doesn’t make sense to merge the changes and I was confident nobody else had pulled the state I was overwriting.
For slightly more safety, I’d recommend --force-with-lease
. This flag will cause a force push but only if the HEAD
you are overwriting is the HEAD
you last saw when you fetched. Using a lease protects you from overwriting what you think is work you are willing to throw away when someone else has checked in work after you last fetched.
$ git push --force-with-lease
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 425 bytes | 60.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
To ../remote
+ b4c50e6...a0ac42f ex-add-pie -> ex-add-pie (forced update)
To create a local copy of a remote repository you can use git clone
. For example let’s clone this public repository from GitHub with git clone https://github.com/Git-For-Pragmastists/clone-me.git
$ cd /home/gitstudent
$ git clone https://github.com/Git-For-Pragmastists/clone-me.git
Cloning into 'clone-me'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (4/4), done.
$ cd clone-me
$ git remote -v
origin https://github.com/Git-For-Pragmastists/clone-me.git (fetch)
origin https://github.com/Git-For-Pragmastists/clone-me.git (push)
clone
sets up the remote for you with the default name of origin
.
If you don’t clone or have multiple remotes you’ll need to manage them with git remote
To list your remotes you can issue git remote
. The output is not especially useful without the --verbose
flag which can be abbreviated to -v
$ git remote
origin
$ git remote --verbose
origin https://github.com/Git-For-Pragmastists/clone-me.git (fetch)
origin https://github.com/Git-For-Pragmastists/clone-me.git (push)
$ git remote -v
origin https://github.com/Git-For-Pragmastists/clone-me.git (fetch)
origin https://github.com/Git-For-Pragmastists/clone-me.git (push)
To add a remote to an existing git repository we can use git remote add
. For example we can add this public repository as an additional remote.
$ git remote add additional https://github.com/Git-For-Pragmastists/add-me-as-a-remote.git
$ git remote -v
additional https://github.com/Git-For-Pragmastists/add-me-as-a-remote.git (fetch)
additional https://github.com/Git-For-Pragmastists/add-me-as-a-remote.git (push)
origin https://github.com/Git-For-Pragmastists/clone-me.git (fetch)
origin https://github.com/Git-For-Pragmastists/clone-me.git (push)
$ git fetch additional
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 701 bytes | 43.00 KiB/s, done.
From https://github.com/Git-For-Pragmastists/add-me-as-a-remote
* [new branch] main -> additional/main
$ git merge additional/main
Updating 378261d..92eeab3
Fast-forward
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
It’s a copy of the repository we cloned earlier and it has a branch called main
which is 1 commit ahead of origin/main
. We can merge additional/main
into our local main
and see that we’re now 1 ahead of origin/main
. We can’t push that change since the repository is read only.
If you want to remove a remote you can like so.
$ git remote -v
additional https://github.com/Git-For-Pragmastists (fetch)
additional https://github.com/Git-For-Pragmastists (push)
origin https://github.com/Git-For-Pragmastists/clone-me.git (fetch)
origin https://github.com/Git-For-Pragmastists/clone-me.git (push)
$ git remote remove additional
$ git remote -v
origin https://github.com/Git-For-Pragmastists/clone-me.git (fetch)
origin https://github.com/Git-For-Pragmastists/clone-me.git (push)
For this exercise the remote is just another directory inside the container. There are many services you can use to host a remote repository for you such as: GitHub, GitLab and Bitbucket.
They tend to offer a web UI which you can use to configure additional features such as read/write access control to your repository, interfaces to manage requests merge branches and perform code review, and running activities based on git events like running the automated test suite on push.
Want to stay up to date with Git for Pragmatists? 📬
Sign up for our mailing list!