Remote
repositories
Git, as we have seen in the
introduction, is a distributed VCS. This means that, apart from the local, we
can have a copy of the repository hosted in a remote server that, apart from
making public the source code of the project, is used for collaborative development.
The most popular platform
for Git repositories hosting is GitHub.
Unfortunately, GitHub does not offer private repositories in its free plan. If
you need a hosting platform with unlimited private repositories, you can
use Bitbucket. And, if you are
looking for hosting your repositories in your own server, the available option
is GitLab.
For this section, we will
need to use one of the options mentioned above.
Writing
changes in the remote
The first thing we need to
do in the remote hosting is to create a repository, for which a URL with the
following format will be created:
https://hosting-service.com/username/repository-name
|
Once having a remote
repository, we have to link it with our local repo. This is made with the remote
add <remote-name> <repo-url> command:
git remote add origin
https://hosting-service.com/username/repository-name
|
origin is which is
named by default by Git, similarly to master branch, but it does not
have to be necessarily.
Now, in our local
repository, the remote repository is identified as origin. We can now
start to “send” information to it, which is made with push option:
git push [remote-name] [branch-name | --all] [--tags] |
Let’s see some examples of
how it works:
git push origin --all # Updates the remote with all the local branches
git push origin master dev
# Updates remote's mater and dev branches
git push origin
--tags # Sends tags to remotes
|
This is an example output of
a successful master branch update:
Counting objects: 3, done.
Writing objects: 100%
(3/3), 235 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused
0 (delta 0)
To
https://hosting-service.com/username/repository-name.git
* [new
branch] master -> master
|
What
has Git internally with this?
Well, now, a directory .git/refs/remotes has
been created, and, inside it, another directory, origin (because
that’s the name we have given to the remote). Here, Git creates a file for
each branch exiting in the remote repository, with a reference to it. This
reference is just the SHA1 id of the last commit of the given branch of the remote
repository. This is used by Git to know if in remote repo are any changes that
can be applied to the local repository. We will cover this in detail later in
the following sections.
Note: a repository can have
as many remotes as we want. For example, we could have the remote of the same
repository in both GitHub and Bitbucket:
git remote add github https://github.com/username/repository-name
git remote add bitbucket
https://bitbucket.org/username/repository-name
|
Cloning
a repository
The cloning of a repository
is actually made once, when we are going to start to work with a remote
repository.
For cloning remote
repositories there’s no mystery, we just have to use the clone option,
specifying the URL of the repository:
git clone https://hosting-service.com/username/repository-name |
Which would create a local
directory with the repository, with the reference to the remote it has been
cloned from.
By default, when cloning a
repository, only the default branch is created (master, generally). The way to
create the other branches locally is making a checkout to them.
Remote branches can be shown
with branch option with -r flag:
git branch -r |
They will be shown with the
format <remote-name>/<branch-name>. To create the local
branch, is enough to make a checkout to <branch-name>.
Updating
remote references: fetching
Fetching the remote
repository means updating the reference of a local branch, to put it even
with the remote branch.
Let’s consider a collaborative
scenario where two developers are pushing changes to the same repo. In some
moment, one of the developers wants to update its reference to master, to
the last push of the other developer, as is shown in the following image.
The content of the .git/refs/remotes/origin/master file
of Alice’s repository would be, before any update, the following:
5bfc81ce5f7a0b26b493be0c99f1966a1896c972
|
Bob’s, however, will be updated, since it has been him the last who has updated the remote repository:
37db4f82e346665f6048cc9e4b7cd48c83c6ebcb
|
Now, Alice wants to have in her local repository the changes made by Bob. For that, she has to fetch the master branch:
git fetch origin master |
(Alternatively, she can
fetch every branch with --all flag):
git
fetch --all
|
The output of the fetch would be something similar to the following:
remote: Counting objects:
3, done.
remote: Total 3 (delta 0),
reused 0 (delta 0)
Unpacking objects: 100%
(3/3), done.
From https://hosting-service.com/alice/repository-name
*
branch
master -> FETCH_HEAD
5bfc81c..37db4f8
master -> origin/master
|
And, now, the content of Alice’s .git/refs/remotes/origin/master file would be the same of remote’s:
37db4f82e346665f6048cc9e4b7cd48c83c6ebcb
|
With this, Alice has updated the reference to remote’s master branch, but the changes have not been applied in the repository.
fetch does not apply changes directly to the local repository. Is the first of two steps that have to be made. The other step is to merge the remote branch, which has just been updated; with the local branch. This is just a merge as any other, but where the remote name has to be indicated:
git merge origin/master |
Once the merge is finished, Alice will have the latest version of master branch.
Note: in this merge, we have
not applied the no-fast forward. In this cases, would not have many sense to
apply it, since we are merging the intrinsically same branch, but that is
located somewhere else.
Fetching and merging remotes at once: pulling
Git has an option to apply
remote changes at once, that is, fetching and merging the branch in one
command. This option is pull.
Considering the exactly same
scenario seen above, we could just execute:
git pull origin master |
And would do the fetch, followed by the merge.
Use the way you fell more
comfortable with. There’s no better way; actually, they do exactly
the same, but expressed differently.
Conflicts updating remote repository
Let’s return to the scenario
shown above. What would happen if, without updating her local repository, Alice
would create a commit and push it to the remote repository? Well, she would
receive an ugly message from Git:
! [rejected] master -> master (fetch first)
error: failed to push some
refs to 'https://hosting-service.com/alice/repository-name.git'
hint: Updates were
rejected because the remote contains work that you do
hint: not have locally.
This is usually caused by another repository pushing
hint: to the same ref. You
may want to first integrate the remote changes
hint: (e.g., 'git pull
...') before pushing again.
hint: See the 'Note about
fast-forwards' in 'git push --help' for details.
|
The push would be rejected because Alice has not continued the history of the repository from a point that has been already registered in the remote. So, before writing changes in the remote repository, she would need first to fetch & merge, or pull, the remote with her changes.
A bad way to resolve conflicts
There’s still another way,
which can not be exactly considered as resolving. Is about forcing pushes
with --force flag.
Forcing a push is about, basically, one thing: overwrite the remote repository branch (or the whole repository) with the one that is being pushed. So, in the above scenario, if Alice forces a push of her repository, the 37db4f8 commit will disappear. This would leave Bob working in something similar to a “parallel universe” that cannot coexist with the current reality of the project, since his work is based on a state that no longer exist in the project.
In conclusion, don’t force pushes when you are working with other developers, at least if you don’t want to have an intense argument with them.
If you Google for “git force push” in the images section, you will see some memes that perfectly explain graphically what a forced push is considered as.
No comments:
Post a Comment