In this chapter you will learn how to retrieve and share changes with other users' Git repositories by learning the following topics:
-
How to download a remote repository
-
How to send/receive changes from a remote repository
-
How to create and receive branches
-
How to merge commits from one branch to another
As you learned in 01-LocalGit.adoc, it’s possible to work entirely with Git as a local version control system and never share changes with others. Usually, however, if you’re using a version control system, you’ll want to share changes with others: from simply sending files to a remote server for backup to collaborating as part of a large development team. Team collaboration will also require knowledge of how to create and interact with branches for working on different features in parallel. Let’s start by adding a remote repository.
Typically when using version control, you’ll want to share your commits with other people using other computers. With a traditional, centralized version control system (such as Subversion or CVS), the repository is usually stored on another machine. As you make a commit, it’s sent over the network, checked whether it can apply (there may be other changes since you last checked), and then committed to the version control system where others can see it.
With a distributed version control system like Git, every user has a complete repository on their own computer. Though there may be a centralized repository that people send their commits to, it won’t be accessed unless specifically requested. All commits, branches, and history are stored offline unless users choose to send or receive commits from another repository.
Git add/commit/checkout cycle shows the local Git cycle we used in 01-LocalGit.adoc. Files in the local working directory are modified and added with git add
to the index staging area. The contents of the index staging area are committed with git commit
to form a new commit, which is stored in the local repository directory. Later, this repository can be queried to view the differences between versions of files using git diff
. In Checking out a local branch: git checkout you’ll also see how to use git checkout
to change to different local branches' versions of files.
Git add/commit/push/pull/checkout cycle shows the remote Git cycle we’ll look at in this chapter. As in the local workflow, files are modified, added, committed, and can be checked out. But there are now two repositories: a local repository and remote repository.
If your local repository needs to send or receive data to a repository on another machine, it’ll need to add a remote repository. A remote repository is one that’s typically stored on another computer. git push
sends your new commits to it and git fetch
retrieves any new commits made by others from it.
In 01-LocalGit.adoc you created a local repository on your machine. Please sign up for a GitHub account and create a remote repository on GitHub (detailed in B-CreatingAGitHubAccountAndRepository.adoc). You can use another Git hosting provider, but this book will assume the use of GitHub (as it’s the most widely used).
The first action you’re concerned with is adding a reference for your newly-created remote repository (also known as a remote) on GitHub to your previous local repository so you can push and fetch commits.
-
Change directory to the Git repository, in my case
cd /Users/mike/GitInPracticeRedux/
. -
Run
git remote add origin
with your repository URL appended. So if your username isGitInPracticeReader
and your repository is namedGitInPracticeRedux
, rungit remote add origin https://github.com/GitInPracticeReader/GitInPracticeRedux.git
. There will be no output.
You can verify this remote has been created successfully by running git remote --verbose
. The output should resemble the following:
# git remote --verbose
origin https://github.com/GitInPracticeReader/GitInPracticeRedux.git (fetch) (1)
origin https://github.com/GitInPracticeReader/GitInPracticeRedux.git (push) (2)
-
fetch URL
-
push URL
In the remote listing:
-
"fetch URL (1)" specifies the URL that
git fetch
uses to fetch new remote commits. -
"push URL (2)" specifies the URL that
git push
uses to send new local commits.
Note
|
When do the fetch and push URLs differ?
These won’t differ unless they’ve been set to do so by the git remote command or by Git configuration. It’s almost never necessary to do this, so I won’t cover it in this book.
|
You’ve added a remote named origin
that points to the remote GitInPracticeRedux
repository belonging to the GitInPractice
user on GitHub. You can now send and receive changes from this remote. Nothing has been sent or received yet; the new remote is effectively just a named URL pointing to the remote repository location.
git remote
can also be called with the rename
and remove
(or rm
) subcommands to alter remotes accordingly.
git remote show
will query and show verbose information about the given remote.
git remote prune
will delete any remote references to branches that have been deleted from the remote repository by other users. Don’t worry about this for now; remote branches will be covered in Pushing a local branch remotely.
Note
|
What is the default name for a remote?
You can have multiple remote repositories connected to your local repository so the remote repositories are named. Typically if you have a single remote repository, it will be named origin .
|
With centralized version control systems, the central server always stores the authoritative version of the code. Clients to this repository will typically only store a small proportion of the history and require access to the server to perform most tasks. With a distributed version control system like Git, every local repository has a complete copy of the data. Which repository stores the authoritative version in this case? It turns out that this is merely a matter of convention; Git itself doesn’t deem any particular repository to have any higher priority than another. Typically in organizations there will be a central location (like with a centralized version control) that is treated as the authoritative version and which people are encouraged to push their commits and branches to.
The lack of authority for a particular repository with distributed version control systems is sometimes seen as a liability but can actually be a strength. The Linux kernel project (for which Git was original created) makes use of this to provide a network of trust and a more manageable way of merging changes. When Linus Torvalds, the self-named "benevolent dictator" of the project, tags a new release, this is generally considered a new release of Linux. What’s in his repository (well, his publicly accessible one; he will have multiple repositories between various personal machines that he doesn’t make publicly accessible) is generally considered to be what’s in Linux. Linus has trusted lieutenants from whom he can pull and merge commits and branches. Rather than every single merge to Linux needing to be done by Linus, he can leave some of it to his lieutenants (who leave some to their sub-lieutenants and so on), so each person only needs to worry about verifying and including the work of a small number of others. This particular workflow may not make sense in many organizations, but it demonstrates how distributed version control systems can allow different ways of managing merges to centralized version control.
You’ll eventually wish to send commits made in the local repository to a remote. To do this always requires an explicit action. Only changes specifically requested will be sent and the Git command-line tool (which can operate over HTTP, SSH, or its own protocol (git://
)) will ensure that only the differences between the repositories are sent. As a result, you can push small changes from a large local repository to a large remote repository very quickly as long as they have most commits in common.
Let’s push the changes you made in our repository in 01-LocalGit.adoc to the newly created remote you made in Adding a remote repository: git remote add.
You wish to push the changes from the local GitInPracticeRedux
repository to the origin
remote on GitHub.
-
Change directory to the Git repository, in my case
cd /Users/mike/GitInPracticeRedux/
. -
Run
git push --set-upstream origin master
and enter your GitHub username and password when requested. The output should resemble the following:
# git push --set-upstream origin master
Username for 'https://github.com': GitInPractice (1)
Password for 'https://[email protected]': (2)
Counting objects: 6, done. (3)
Delta compression using up to 8 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 602 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To https://github.com/GitInPracticeReader/GitInPracticeRedux.git (4)
* [new branch] master -> master (5)
Branch master set up to track remote branch master from origin. (6)
-
username entry
-
password entry
-
object preparation/transmission
-
remote URL
-
local/remote branch
-
set tracking branch
From the push output you can see:
-
"username entry (1)" and "password entry (2)" are those for your GitHub account. They may only be asked for the first time you push to a repository depending on your operating system of choice (which may decide to save the password for you). They’re always required to
push
to repositories but are only required forfetch
when fetching from private repositories. -
"object preparation/transmission (3)" can be safely ignored in this or future figures; it’s simply Git communicating details on how the files are being sent to the remote repository and isn’t worth understanding beyond basic progress feedback.
-
"remote URL (4)" matches the push URL from the
git remote --verbose
output earlier. It is where Git has sent the local commits to. -
"local/remote branch (5)" indicates that this was a new branch on the remote. This is because the remote repository on GitHub was empty until we pushed this; it had no commits and thus no
master
branch yet. This was created by thegit push
. Themaster → master
indicates the local master branch (the first of the two) has been pushed to the remotemaster
branch (the second of the two). This may seem redundant, but it’s shown here because it’s possible (but ill-advised due to the obvious confusion it causes) to have local and remote branches with different names. Don’t worry about local or remote branches for now, as these will be covered in Creating a new local branch from the current branch: git branch. -
"set tracking branch (6)" is shown because the
--set-upstream
option was passed togit push
. By passing this option, you’ve told Git that you want the localmaster
branch you’ve just pushed to track theorigin
remote’s branchmaster
. Themaster
branch on theorigin
remote (which is often abbreviated asorigin/master
) is now known as the tracking branch (or upstream) for your localmaster
branch.
You have pushed your master
branch’s changes to the origin
remote’s master
branch.
The git push
--set-upstream
(or -u
) flag and explicit specification of origin
and master
are only required the first time you push to create a remote branch (without it some versions of Git may output fatal: The current branch master has no upstream branch.
). After that, a git push
with no arguments will default to running the equivalent of git push origin master
. This is set up by default by git clone
when you clone a repository.
git push
can take an --all
flag which will push all branches and tags (introduced later in 05-AdvancedBranching.adoc) at once. Be careful when doing this: you may push some branches with work in-progress.
git push
can take a --force
flag, which will disable some checks on the remote repository to allow rewriting of history. This is very dangerous. Don’t use this flag until after later reading (and rereading) 06-RewritingHistoryAndDisasterRecovery.adoc.
A tracking branch is the default push or fetch location for a branch. This means in future you could run git push
with no arguments on this branch and it’ll do the same thing as running git push origin master
--push the current branch to the origin
remote’s master
branch.
Local repository after git push
shows the state of the repository after the git push
. There’s one addition since we last looked at it in 01-LocalGit.adoc: the origin/master
label. This is attached to the commit which matches the currently known state of the origin
remote’s master
branch.
GitHub repository after git push
shows the remote repository on GitHub after the git push
. The latest commit SHA-1 there matches your current latest commit on the master
branch seen in Local repository after git push
(although they’re different lengths; remember SHA-1s can always be shortened as long as they remain unique). To update this in the future, you’d run git push
again to push any local changes to GitHub.
It’s useful to learn how to create a new Git repository locally and push it to GitHub. But you’ll usually be downloading an existing repository to use as your local repository. This process of creating a new local repository from an existing remote repository is known as cloning a repository.
Some other version control systems (such as Subversion) will use the terminology of checking out a repository. The reasoning for this is that Subversion is a centralized version control system, so when you download a repository locally, you’re only actually downloading the latest revision from the repository. With Git, it’s known as cloning because you’re making a complete copy of that repository by downloading all commits, branches, tags (introduced later in 05-AdvancedBranching.adoc); the complete history of the repository onto your local machine.
As you just pushed the entire contents of the local repository to GitHub, let’s remove the local repository and recreate it by cloning the repository on GitHub.
You wish to remove the existing GitInPracticeRedux
local repository and recreate it by cloning from GitHub.
-
Change to the directory where you want the new
GitInPracticeRedux
repository to be created—say,cd /Users/mike/
to create the new local repository in/Users/mike/GitInPracticeRedux
. -
Run
rm -rf GitInPracticeRedux
to remove the existingGitInPracticeRedux
repository. -
Run
git clone
with your repository URL appended. So if your username isGitInPracticeReader
and your repository is namedGitInPracticeRedux
rungit clone https://github.com/GitInPracticeReader/GitInPracticeRedux.git
. The output should resemble the following:
# git clone https://github.com/GitInPracticeReader/GitInPracticeRedux.git
Cloning into 'GitInPracticeRedux'... (1)
remote: Counting objects: 6, done. (2)
remote: Compressing objects: 100% (5/5), done.
remote: Total 6 (delta 0), reused 6 (delta 0)
Unpacking objects: 100% (6/6), done.
Checking connectivity... done
-
destination directory
-
object preparation/transmission
From the clone output you can see:
-
"destination directory (1)" is the directory in which the new
GitInPracticeRedux
local repository was created. -
"object preparation/transmission (2)" can be safely ignored again (although if you’re wondering why there were six objects, remember the different objects in the object store in 01-LocalGit.adoc).
You’ve cloned the GitInPracticeRedux
remote repository and created a new local repository containing all its commits in /Users/mike/GitInPracticeRedux
.
You can verify this remote has been created successfully by running git remote --verbose
. The output should resemble the following:
# git remote --verbose
origin https://github.com/GitInPracticeReader/GitInPracticeRedux.git (fetch) (1)
origin https://github.com/GitInPracticeReader/GitInPracticeRedux.git (push) (2)
-
fetch URL
-
push URL
git clone
can take --bare
or --mirror
flags, which will create a repository suitable for hosting on a server. This will be covered more in chapter 13.
git clone
can take a --depth
flag followed by a positive integer, which will create a shallow clone. A shallow clone is one where only the specified number of revisions are downloaded from the remote repository, but it’s limited, as it cannot be cloned/fetched/pushed from or pushed to. This can be useful for reducing the clone time for very large repositories.
git clone
can take a --recurse-submodules
(or --recursive
) flag, which will initialize all the Git submodules in the repository. This will be covered more later in 12-CreatingACleanHistory.adoc.
Local repository after git clone
shows the state of the repository after the git clone
. It’s identical to the state after the git push
in Local repository after git push
. This shows that the clone was successful and the newly created local repository has the same contents as the deleted old local repository.
Cloning a repository has also created a new remote called origin
. origin
is the default remote and references the repository that the clone originated from (which is https://github.com/GitInPracticeReader/GitInPracticeRedux.git in this case).
Now let’s learn how to pull new commits from the remote repository.
git pull
downloads the new commits from another repository and merges the remote branch into the current branch.
If you run git pull
on the local repository, you just see a message stating Already up-to-date.
git pull
in this case contacted the remote repository, saw that there were no changes to be downloaded, and let us know that it was up to date. This is expected, as this repository has been pushed to but not updated since.
To test git pull
let’s create another clone of the same repository, make a new commit, and git push
it. This will allow downloading new changes with git pull
on the original remote repository.
To create another cloned, local repository and push a commit from it:
-
Change to the directory where you want the new
GitInPracticeRedux
repository to be created — for example,cd /Users/mike/
to create the new local repository in/Users/mike/GitInPracticeReduxPushTest
. -
Run
git clone
with your repository URL and destination directory appended. So if your username isGitInPracticeReader
, repository is namedGitInPracticeRedux
and destination directory is namedGitInPracticeReduxPushTest
rungit clone https://github.com/GitInPracticeReader/GitInPracticeRedux.git GitInPracticeReduxPushTest
to clone into theGitInPracticeReduxPushTest
directory. -
Change directory to the new Git repository: in our example,
cd /Users/mike/GitInPracticeReduxPushTest/
. -
Modify the
GitInPractice.asciidoc
file. -
Run
git add GitInPractice.asciidoc
. -
Run
git commit --message 'Improve joke comic timing.'
. -
Run
git push
.
Now that you’ve pushed a commit to the GitInPracticeRedux
remote on GitHub, you can change back to your original repository and git pull
from it. Keep the GitInPracticeReduxPushTest
directory around as we’ll use it later.
You wish to pull new commits into the current branch on the local GitInPracticeRedux
repository from the remote repository on GitHub.
-
Change directory to the original Git repository:
cd /Users/mike/GitInPracticeRedux/
. -
Run
git pull
. The output should resemble the following:
# git pull
remote: Counting objects: 5, done. (1)
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/GitInPracticeReader/GitInPracticeRedux (2)
6b437c7..85a5db1 master -> origin/master (3)
Updating 6b437c7..85a5db1 (4)
Fast-forward (5)
GitInPractice.asciidoc | 5 +++-- (6)
1 file changed, 3 insertions(+), 2 deletions(-) (7)
-
object preparation/transmission
-
remote URL
-
remote branch update
-
local branch update
-
merge type
-
lines changed in file
-
diff summary
You can see from the pull output:
-
"object preparation/transmission (1)" can be safely ignored again.
-
"remote URL (2)" matches the remote repository URL we saw used for
git push
. -
"remote branch update (3)" shows how the state of the
origin
remote’smaster
branch was updated, and that this can be seen inorigin/master
.origin/master
is a valid ref that can be used with tools such asgit diff
, sogit diff origin/master
will show the differences between the current working tree state and theorigin
remote’smaster
branch. -
"local branch update (4)" shows that after
git pull
downloaded the changes from the other repository, it merged the changes from the tracking branch into the current branch. In this case yourmaster
branch had the changes from themaster
branch on the remoteorigin
merged in. You can see in this case that the SHA-1s match those in the "remote branch update (3)". It has been updated to include the new commit (85a5db1
). -
"merge type (5)" was a fast-forward merge which means that no merge commit was made. Fast-forward merges will be explained in Merging an existing branch into the current branch: git merge.
-
"lines changed in file <6>" is the same as the lines changed from
git commit
in 01-LocalGit.adoc orgit diff
in 01-LocalGit.adoc. It’s showing a summary of the changes that have been pulled into yourmaster
branch. -
"diff summary <7>" is the same as the diff summary from
git commit
in 01-LocalGit.adoc orgit diff
in 01-LocalGit.adoc .
git pull
can take a --rebase
flag which will perform a rebase rather than a merge. This will be covered later in 06-RewritingHistoryAndDisasterRecovery.adoc.
Note
|
Why did a merge happen?
It may be confusing that a merge has happened here. Didn’t you just ask for the updates from that branch? You haven’t created any other branches, so why did a merge happen? In Git, all remote branches (which includes the default master branch) are only linked to your local branches if the local branch is tracking the remote branch. As a result, when you’re pulling in changes from a remote branch into your current branch, you may sometimes result in a situation where you’ve made local changes and the remote branch has also received changes. In this case, a merge must be made to reconcile the differing local and remote branch.
|
You can see from Local repository after git pull
that a new commit has been added to the repository and that both master
and origin/master
have been updated.
You’ve pulled the new commits from the GitInPracticeRedux
remote repository into your local repository, and Git has merged them into your master
branch. Now let’s learn how to download changes without applying them onto your master branch.
Remember that git pull
performs two actions: fetching the changes from a remote repository and merging them into the current branch. Sometimes you may wish to download the new commits from the remote repository without merging them into your current branch (or without merging them yet). To do this, you can use the git fetch
command. git fetch
performs the fetching action of downloading the new commits but skips the merge step (which you can manually perform later).
To test git fetch
, let’s use the GitInPracticeReduxPushTest
local repository again to make another new commit and git push
it. This will allow downloading new changes with git fetch
on the original remote repository.
To push another commit from the GitInPracticeReduxPushTest
repository:
-
Change directory to the
GitInPracticeReduxPushTest
repository; for examplecd /Users/mike/GitInPracticeReduxPushTest/
. -
Modify the
GitInPractice.asciidoc
file. -
Run
git add GitInPractice.asciidoc
. -
Run
git commit --message 'Joke rejected by editor!'
. -
Run
git push
.
Now that you’ve pushed another commit to the GitInPracticeRedux
remote on GitHub, you can change back to your original repository and git fetch
from it. If you wish, you can now delete the GitInPracticeReduxPushTest
repository by running a command like rm -rf /Users/mike/GitInPracticeReduxPushTest/
.
You wish to fetch new commits to the local GitInPracticeRedux
repository from the GitInPracticeRedux
remote repository on GitHub without merging into your master
branch.
-
Change directory to the Git repository:
cd /Users/mike/GitInPracticeRedux/
. -
Run
git fetch
. The output should resemble the following:
# git fetch
remote: Counting objects: 5, done. (1)
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/GitInPracticeReader/GitInPracticeRedux (2)
85a5db1..07fc4c3 master -> origin/master (3)
-
object preparation/transmission
-
remote URL
-
remote branch update
The git fetch
output is the same as the first part of the git pull
output. But the SHA-1s are different again, as a new commit was downloaded. This is because git fetch
is effectively half of what git pull
is doing. If your master
branch is tracking the master
branch on the remote origin
, then git pull
is directly equivalent to running git fetch && git merge origin/master
.
You’ve fetched the new commits from the remote repository into your local repository without merging them into your master
branch.
You can see from Remote repository after git fetch
that another new commit has been added to the repository, but this time only origin/master
has been updated, and master
has not. To see this, you may need to select the origin
remote and master
remote branch in the GitX sidebar. Selecting commits by remote branches is a feature sadly not available in gitk
To clean up our local repository, let’s do another quick git pull
to update the state of the master
branch based on the (already fetched) origin/master
.
To pull new commits into the current branch on the local GitInPracticeRedux
repository from the remote repository on GitHub:
-
Change directory to the Git repository; for example
cd /Users/mike/GitInPracticeRedux/
. -
Run
git pull
. The output should resemble the following:
# git pull
Updating 85a5db1..07fc4c3 (1)
Fast-forward (2)
GitInPractice.asciidoc | 4 +--- (3)
1 file changed, 1 insertion(+), 3 deletions(-) (4)
-
local branch update
-
merge type
-
lines changed in file
-
diff summary
This shows the latter part of the first git pull
output we saw. No more changes were fetched from the origin
remote and the local master
branch hadn’t been updated. As a result, this git pull
behaved the same as running git merge origin/master
.
Local repository after git fetch
then git pull
shows that the master
branch has now been updated to match the origin/master
latest commit once more.
Note
|
Should I use pull or fetch?
I prefer to use git fetch over git pull . This means I can continue to fetch regularly in the background, and only include these changes in my local branches when it’s convenient and in the method I find most appropriate, which may be merging or rebasing (or resetting, which you will see later in 06-RewritingHistoryAndDisasterRecovery.adoc). Additionally, I sometimes work in situations where I have no internet connection (such as on planes) and using git fetch is superior in these situations; it can fetch changes without requiring any human interaction in the case of a merge conflict, for example.
|
We’ve talked about local branches and remote branches but haven’t actually created any ourselves yet. Let’s learn about how branches work and how to create them.
When committing in Git, the history continues linearly; what was the most recent commit becomes the parent commit for the new commit. This parenting continues back to the initial commit in the repository. You can see an example of this in Committing without using branches:
Sometimes this linear approach isn’t enough for software projects. Sometimes you may need to make new commits that are not yet ready for public consumption. This requires branches.
Branching allows two independent tracks through history to be created and committed to without either modifying the other. Programmers can happily commit to their independent branch without the fear of disrupting the work of another branch. This means that they can, for example, commit broken or incomplete features rather than having to wait for others to be ready for their commits. It also means they can be isolated from changes made by others until they’re ready to integrate them into their branch. Committing to multiple branches shows the same commits as Committing without using branches if they were split between two branches instead for isolation.
When a branch is created and new commits are made, that branch advances forward to include the new commits. In Git, a branch is no more than a pointer to a particular commit. This is unlike other version control systems such as Subversion, in which branches are just a subdirectory of the repository.
The branch is pointed to a new commit when a new commit is made on that branch. A tag is similar to a branch, but points to a single commit and remains pointing to the same commit even when new commits are made. Typically tags are used for annotating commits; for example, when you release version 1.0 of your software, you may tag the commit used to build the 1.0 release with a "1.0" tag. This means you can come back to it in future, rebuild that release, or check how certain things worked without fear that it will be somehow changed automatically.
Branching allows two independent tracks of development to occur at once. In Committing to multiple branches, the separate-files
branch was used to separate the content from a single file and split it into two new files. This allowed refactoring of the book structure to be done in the separate-files
branch while the default branch (known as master
in Git) could be used to create more content. In version control systems like Git where creating a branch is a quick, local operation, branches may be used for every independent change.
Some programmers will create new branches whenever they work on a new bug fix or feature and then integrate these branches at a later point; perhaps after requesting a review of their changes from others. This means even for programmers working without a team, it can be useful to have multiple branches in use at any one point. For example, you may be working on a new feature but realize that a critical error in your application needs fixed immediately. You could quickly create a new branch based off the version used by customers, fix the error, and switch branch back to the branch you’d been committing the new feature to.
-
Change directory to the Git repository:
cd /Users/mike/GitInPracticeRedux/
. -
Run
git branch chapter-two
. There will be no output.
You can verify the branch was created by running git branch
, which should have the following output:
# git branch
chapter-two (1)
* master (2)
-
new branch
-
current branch
From the branch output:
-
"new branch (1)" was created with the expected name.
-
"current branch (2)" is indicated by the
*
prefix, which shows you’re still on the master branch as before.git branch
creates a new branch but doesn’t change to it.
You’ve created a new local branch named chapter-two
that currently points to the same commit as master
.
git branch
can take a second argument with the start point for the branch. This defaults to the current branch you’re on; for example, git branch chapter-two
is the equivalent of git branch chapter-two master
if you’re already on the master branch. This can be used to create branches from previous commits, which is sometimes useful if, say, the current master
branch state has broken unit tests that you need to be working.
git branch
can take a --track
flag which, combined with a start point, will set the upstream for the branch (similarly to git push --set-upstream
but without pushing anything remotely yet).
You can see from Local repository after git branch chapter-two
that there’s a new branch label for the chapter-two
branch. In the GitX GUI the label colors indicate:
-
Orange—the currently checked-out local branch
-
Green—a non-checked-out local branch
-
Blue—a remote branch
Note that print editions of the book are in printed in grayscale, so these colors may not be visible. Instead please compare them to GitX on your computer.
Branch pointers shows how these two branch pointers point to the same commit.
You’ve seen how git branch
creates a local branch but doesn’t change to it. To do that requires using git checkout
.
Note
|
Can branches be named anything?
Branches can’t have spaces or two consecutive dots (.. ) anywhere in their name, so chapter..two would be an invalid branch name and git branch will refuse to create it. The dots case is due to the special meaning of .. for a commit range for the git diff command (which we saw used in 01-LocalGit.adoc).
|
Note
|
What names should I use for branches?
Name branches according to their contents. For example, the chapter-two branch we’ve created here describes that the commits in this branch will be referencing the second chapter. I recommend a format of describing the branch’s purpose in multiple words separated by hyphens. For example, a branch that is performing cleanup on the test suite should be named test-suite-cleanup .
|
Once you’ve created a local branch, you’ll want to check out the contents of another branch into Git’s working directory. The state of all the current files in the working directory will be replaced with the new state based on the revision that the new branch is currently pointing to.
-
Change directory to the Git repository; for example,
cd /Users/mike/GitInPracticeRedux/
. -
Run
git checkout chapter-two
. The output should beSwitched to branch 'chapter-two'
.
You’ve checked out the local branch named chapter-two
and moved from the master
branch.
Note
|
Why do Subversion and Git use
As mentioned earlier, some other version control systems (such as Subversion) use checkout to mean different things?checkout to refer to the initial download from a remote repository, but git checkout is used here to change branches. This may be slightly confusing until we look at Git’s full remote workflow. Git add/commit/checkout workflow shows Git’s local workflow again. Under closer examination, git checkout and svn checkout behave similarly; both check out the contents of a version control repository into the working directory, but Subversion’s repository is remote and Git’s repository is local. In this case, git checkout is requesting the checkout of a particular branch so the current state of that branch is checked out into the working directory.
|
Afterward, the HEAD pointer (seen in HEAD pointer with multiple branches) is updated to point to the current, chapter-two
branch pointer, which in turn points to the top commit of that branch. The HEAD pointer moved from the master
to the chapter-two
branch when you ran git checkout chapter-two
; setting chapter-two
to be the current branch.
Note
|
Will
Make sure you’ve committed any changes on the current branch before checking out a new branch. If you don’t do this, git checkout overwrite any uncommitted changes?git checkout will refuse to check out the new branch if there are changes in that branch to a file with uncommitted changes. If you wish to overwrite these uncommitted changes anyway you can force this with git checkout --force . Another solution is git stash which allows temporary storage of changes and will be covered later in 03-FilesystemInteractions.adoc.
|
Now that you’ve created a new branch and checked it out, it would be useful to push any new commits made to the remote repository. To do this requires using git push
again.
You wish to push the changes from the local chapter-two
branch to create the remote branch chapter-two
on GitHub.
-
Change directory to the Git repository, such as
cd /Users/mike/GitInPracticeRedux/
. -
Run
git checkout chapter-two
to ensure you’re on thechapter-two
branch. -
Run
git push --set-upstream origin chapter-two
. The output should resemble:
git push --set-upstream origin chapter-two
Total 0 (delta 0), reused 0 (delta 0) (1)
To https://github.com/GitInPracticeReader/GitInPracticeRedux.git
* [new branch] chapter-two -> chapter-two (2)
Branch chapter-two set up to track remote branch
chapter-two from origin. (3)
-
object preparation/transmission
-
local/remote branch
-
set tracking branch
The push output is much the same as the previous git push
run:
-
"object preparation/transmission (1)" (although still ignorable) shows that no new objects were sent. The reason for this is because the
chapter-two
branch still points to the same commit as themaster
branch; it’s effectively a different name (or, more accurately, ref) pointing to the same commit. As a result no more commit objects have been created and therefore no more were sent. -
"local/remote branch (2)" has
chapter-two
as the branch name. -
"set tracking branch (3)" has
chapter-two
as the branch name.
You’ve pushed your local chapter-two
branch and created a new remote branch named chapter-two
on the remote repository.
Remember that now the local chapter-two
branch is tracking the remote chapter-two
branch so any future git pull
or git push
on the chapter-two
branch will use the origin
remote’s chapter-two
branch.
As you’ll hopefully have anticipated, Local repository after git push --set-upstream origin chapter-two
shows the addition of another remote branch named origin/chapter-two
.
At some point we have a branch that we’re done with and we want to bring all the commits made on it into another branch. This process is known as a merge
.
When a merge is requested, all the commits from another branch are pulled into the current branch. Those commits then become part of the history of the branch. Please note from Merging a branch into master that the commit in which the merge is made has two parents commits rather than one; it’s joining together two separate paths through the history back into a single one. After a merge, you may decide to keep the existing branch around to add more commits to it and perhaps merge again at a later point (only the new commits will need to be merged next time). Alternatively, you may delete the branch and make future commits on the Git’s default master
branch and create another branch when needed in the future.
You wish to make a commit on the local branch named chapter-two
and merge this into the master
branch.
-
Change directory to the Git repository; for example,
cd /Users/mike/GitInPracticeRedux/
. -
Run
git checkout chapter-two
to ensure you’re on thechapter-two
branch. -
Modify the contents of
GitInPractice.asciidoc
and rungit add GitInPractice.asciidoc
. -
Run
git commit --message 'Start Chapter 2.'
. -
Run
git checkout master
to check out the branch you wish to mergechapter-two
into. -
Run
git merge chapter-two
. The output should resemble the following:
# git merge chapter-two
Updating 07fc4c3..ac14a50 (1)
Fast-forward (2)
GitInPractice.asciidoc | 2 ++
1 file changed, 2 insertions(+) (3)
-
local branch update
-
merge type
-
diff summary
The output may seem familiar from the git pull
output. Remember this is because git pull
actually does a git fetch && git merge
.
-
"local branch update (1)" shows the changes that have been merged into the local
master
branch. Note that the SHA-1 has been updated from the previousmaster
SHA-1 (07fc4c3
) to the currentchapter-two
SHA-1 (ac14a50
). -
"merge type (2)" was a fast-forward merge. This means that no merge commit (a commit with multiple parents) was needed, so none was made. The
chapter-two
commits were made on top of themaster
branch but no more commits had been added to themaster
branch before the merge was made. In Git’s typical language: the merged commit (tip of thechapter-two
branch) is a descendent of the current commit (tip of themaster
branch). If there had been another commit on themaster
branch before merging then this merge would’ve created a merge commit. If there had been conflicts between the changes made in both branches that couldn’t automatically be resolved then a merge conflict would be created and need to be resolved. -
"diff summary <3>" shows a summary of the changes that have been merged into your
master
branch from thechapter-two
branch.
You’ve merged the chapter-two
branch into the master
branch.
This brings the commit that was made in the chapter-two
branch into the master
branch.
Note
|
What if you try and merge the same commit into a branch multiple times?
git merge won’t merge the same commit into a branch multiple times; it will simply exit and output Already up-to-date. rather than performing the merge.
|
You can see from Local repository after git merge chapter-two
that now the chapter-two
and master
branches point to the same commit once more.
So far merges may have sounded too good to be true; you can work on multiple things in parallel and combine them at any later point in any order. Not so fast, my merge-happy friend; I haven’t told you about merge conflicts yet.
A merge conflict occurs when both branches involved in the merge have changed the same part of the same file. Git will try to automatically resolve these conflicts but sometimes is unable to do so without human intervention. This case produces a merge conflict.
## Chapter 1 (1)
<<<<<<< HEAD (2)
It is a truth universally acknowledged, that a single person in (3)
possession of good source code, must be in want of a version control
system.
## Chapter 2
// TODO: write two chapters
======= (4)
// TODO: think of funny first line that editor will approve. (5)
>>>>>>> separate-files (6)
-
Unchanged line
-
Current marker
-
Current line
-
Branch separator
-
Incoming line
-
Incoming marker
When a merge conflict occurs, the version control system will go through any files that have conflicts and insert something similar to the preceding markers. These markers indicate the versions of the file on each branch.
-
"Unchanged line (1)" is provided for context.
-
"Current marker (2)" starts the current branch section containing the lines from the current branch (referenced by
HEAD
here). -
"Current line (3)" shows a line from the current branch.
-
"Branch separator (4)" starts the section containing the lines from the incoming branch.
-
"Incoming line (5)" shows a line from the incoming branch.
-
"Incoming marker (6)" marker ends the section containing the lines from the incoming branch (referenced by
separate-files
; the name of the branch being merged in).
Note
|
How can conflict markers be found quickly?
When searching a large file for the merge conflict markers, you should enter <<<< into your text editor’s find tool to quickly locate them.
|
The person performing the merge will need to manually edit the file to produce the correctly merged output, save it, and mark the merge as resolved. Sometimes resolving the conflict will involve picking all the lines of a single version: either the previous version’s lines or the new branch’s lines. Other times, resolving the conflict will involve combining some lines from the previous version and some lines from the new branch. In cases where other files have been edited (like this example), it may also involve putting some of these lines into other files.
When conflicts have been resolved, a merge commit can be made. This will store the two parent commits and the conflicts that were resolved so they can be inspected in the future. Unfortunately sometimes people will pick the wrong option or merge incorrectly, so it’s good to be able to later see what conflicts they had to resolve.
We’ll cover resolving merge conflicts in more detail later in 05-AdvancedBranching.adoc.
A rebase is a method of history rewriting in Git that is similar to a merge. A rebase involves changing the parent of a commit to point to another.
Rebasing a branch on top of master shows a rebase of the separate-files
branch onto the master
branch. The rebase operation has changed the parent of the first commit in the separate-files
branch to be the last commit in the master
branch. This means all the content changes from the master
branch are now included in the separate-files branch
and any conflicts were manually resolved but weren’t stored (as they would be in a merge conflict).
We’ll cover rebasing in more detail later in 06-RewritingHistoryAndDisasterRecovery.adoc. All that’s necessary to remember for now is that it’s a different approach to a merge that can be used for a similar outcome (pulling changes from one branch into another).
Now that the chapter-two
branch has been merged into the master
branch, the new commit that was made in the chapter-two
branch is now in the master
branch. This means that we can push the master
branch to push all the chapter-two
changes to origin/master
. Once this is done (and assuming we don’t want to make any more commits to the chapter-two
branch) then origin/chapter-two
can be safely deleted.
Note
|
Why delete the branches?
Sometimes branches in version control systems are kept around for a long time and sometimes they’re very temporary. A long-running branch may be one that represents the version deployed to a particular server. A short-running branch may be a single bug fix or feature that has been completed. In Git, once a branch has been merged, the history of the branch is still visible in the history and the branch can be safely deleted as a merged branch is, at that point, just a ref to an existing commit in the history of the branch it was merged into.
|
You wish to push the current master
branch and delete the branch named chapter-two
on the remote origin
.
-
Change directory to the Git repository; for example,
cd /Users/mike/GitInPracticeRedux/
. -
Run
git checkout master
to ensure you are on themaster
branch. -
Run
git push
. -
Run
git push --delete origin chapter-two
. The output should resemble the following:
# git push origin :chapter-two
To https://github.com/GitInPracticeReader/GitInPracticeRedux.git (1)
- [deleted] chapter-two (2)
-
remote URL
-
deleted branch
From the deletion output:
-
"remote URL (1)" shows the remote repository that the branch was deleted from.
-
"deleted branch (2)" shows the name of the branch (
chapter-two
) that has been deleted from the remote repository.
You have deleted the chapter-two
branch from the remote repository.
In Local repository after git push origin :chapter-two
you can see that the origin/master
has been updated to the same commit as master
and that origin/chapter-two
has now been removed.
The chapter-two
branch has all its commits merged into the master
branch and the remote branch deleted so the local branch can now be deleted too.
-
Change directory to the Git repository; for example,
cd /Users/mike/GitInPracticeRedux/
. -
Run
git checkout master
to ensure you’re on themaster
branch. -
Run
git branch --delete chapter-two
. The output should beDeleted branch chapter-two (was ac14a50).
You’ve deleted the chapter-two
branch from the local repository.
Local repository after git branch --delete chapter-two
shows the final state with all evidence of the chapter-two
branch now removed (other than the commit message).
Note
|
Why delete the remote branch before the local branch?
We had merged all the chapter-two changes into the master branch and pushed this to origin/master . As a result, the chapter-two and origin/chapter-two branches are no longer needed. But Git will refuse to delete a local branch with git branch --delete if it hasn’t been merged into the current branch or its changes haven’t been pushed to its tracking branch (origin/chapter-two in this case). Deleting origin/chapter-two first means that the local chapter-two branch can be deleted by git branch --delete without Git complaining that chapter-two has changes that need pushed to origin/chapter-two .
|
In this chapter you hopefully learned:
-
How to push your local repository to a remote repository
-
How to clone an existing remote repository
-
How to push and pull changes to/from a remote repository
-
That fetching allows you to obtain changes without modifying local branches
-
That pulling is the equivalent to fetching then merging
-
How to check out local and remote branches
-
How to merge branches and then delete from the local and remote repository