The intention of this short article is to act as a cheat sheet for the initial bootstrapping of a local git
repository.
By bootstrapping, I’m specifically referring to a few of the common ways to initiate a project. These are commands that we usually blindly copy and paste into a terminal on our way to more important things, like changing the world with our crappy little React applications.
Cloning An Existing Project
The simplest is the venerable git-clone
command. This is a convenience command that does several things under the covers to make life simpler and more enjoyable for you, busy person that you are:
- clones (copies) a repository into a new directory
- creates remote-tracking branches for every branch in the cloned repository
- creates and checks out a new branch that’s forked from the cloned repository’s currently active branch
So, let’s say that we wanted to get a copy of the tmux
project onto our local filesystem. Just do the following:
$ git clone git@github.com:tmux/tmux.git
Cloning into 'tmux'...
remote: Enumerating objects: 47120, done.
remote: Counting objects: 100% (126/126), done.
remote: Compressing objects: 100% (61/61), done.
remote: Total 47120 (delta 72), reused 103 (delta 65), pack-reused 46994
Receiving objects: 100% (47120/47120), 16.59 MiB | 18.47 MiB/s, done.
Resolving deltas: 100% (36240/36240), done.
By default, git-clone
will newly-create a tmux
directory for us. The name is taken from the name of the project repository. Isn’t that nice.
We could have called the directory by another name, like
donuts
, by supplying it after the repositoryURL
:$ git clone git@github.com:tmux/tmux.git donuts
Other than the name of the directory, the result is the same.
So, there it went, gleefully downloading all the git
objects and refs, without a care in the world. Let’s now change into the newly-created tmux
directory and list all the remote branches and followed by a listing of all the branches (both local and remote):
$ cd tmux
$ git branch --remotes
origin/HEAD -> origin/master
origin/master
origin/sixel
origin/sixel-passthrough
$ git branch --all
* master
remotes/origin/HEAD -> origin/master remotes/origin/master
remotes/origin/sixel
remotes/origin/sixel-passthrough
master
is our only local branch, alone and friendless. The asterisk (*
) means that it is the current branch.
To get some understanding of what git-clone
has done for us, let’s take a look at the local config file, located in the .git
directory in the project root:
.git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = git@github.com:tmux/tmux.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
Or, using git-config
:
$ git config --local --list
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=git@github.com:tmux/tmux.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
- It’s named the remote repository
origin
and is tracking it. - It’s created a local
master
branch that is tracking the remote one (origin/master
).
You can also get individual key values:
$ git config --local --get remote.origin.url
git@github.com:tmux/tmux.git
$ git config --local --get remote.origin.fetch
+refs/heads/*:refs/remotes/origin/*
These are each separate operations that git-clone
does for you. We’ll see later how to do that ourselves.
Creating A New Project
We’ll turn now to the situation where you’ve created a project and want to push it to one of the git
platforms. You know which one you love. I’m not here to tell you how to live your life.
No matter the platform, the git
commands are the same. It’s git
, after all.
First, the setup, which I’m not going over:
$ mkdir gitlab-dev
$ cd gitlab-dev
[make files and do stuff]
$ git init
Initialized empty Git repository in /home/btoll/projects/gitlab-dev/.git/
$ git add .
$ git commit -m "Initial commit"
This should all be familiar to everyone on the planet. Now, the bits that are helpfully listed on git
platforms tell us what to do next:
$ git remote add origin git@gitlab.com:btoll/gitlab-dev.git
$ git push -u origin master
Check out the man page for
git-remote
, it’s simply fabulous.
But before we mindlessly copy and paste that into the shell, let’s pause and take a look at the local git
config:
.git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
The long option of
-u
is--set-upstream
, which I wish the platforms would use instead of the short form.
By the way, a nice way to open the config file, whether it’s the system config file (/etc/gitconfig
), the global config file ($HOME/.gitconfig
) or the local one (.git/config
), is to use the -e
or --edit
switch.
The default is to open the local config file. For the system config, use the --system
switch, and for the global config, use the --global
switch.
Any changes, of course, are permanent.
$ git config --edit
Results in:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
Note the presence of only the core
section. Above, where the git-clone
command was issued, it also added the remote
and branch
sections for us. We’ll look now at the suggested commands, and what they add to the config file.
First, let’s set the upstream remote:
$ git remote add origin https://gitlab.com/btoll/gitlab-dev.git
Although nothing is written to stdout
, delightful things have probably happened. If we open the config file, we’ll now see the remote
section has been added for us:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://gitlab.com/btoll/gitlab-dev.git
fetch = +refs/heads/*:refs/remotes/origin/*
This should look familiar, as its one of the things the git-clone
command got us. Specifically, it’s telling git
where to fetch all the git
objects and refs for a particular remote repository by the value of url
, and it’s naming it “origin”.
Next, let’s invoke the second command:
$ git push -u origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 1.62 KiB | 829.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:btoll/gitlab-dev.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
And, again, the local config file:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = git@github.com:btoll/gitlab-dev.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
Now, it’s added the branch
section for us, which allows us to git-push
and git-pull
without needing to specify the remote
and branch
. This is known as remote-tracking a branch.
Rolling Your Own
Lastly, I’d like to demonstrate some more steps that we can do by hand. This will be illustrative, as it further shows what we get for free by using some of the higher-level git
commands.
By the way, “Rolling Your Own” is an absolutely awful name for this section, but I didn’t really know what to call it. Caching and naming things are hard problems.
In this final scenario, we’ll imagine that we don’t want to use git-clone
because we want to really internalize what some of the underlying commands are doing.
Let’s start with another initialized empty git
repository:
$ mkdir gitlab-dev
$ cd gitlab-dev
[make files and do stuff]
$ git init
Initialized empty Git repository in /home/btoll/projects/gitlab-dev/.git/
Now, let’s go ahead and begin manually doing what git-clone
would automatically do for us. And, once more, let’s look at the current state of the local config:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
So far, there’s no information in the config that would tell git
where to fetch any of the remote objects and refs.
First, we need to track the remote repository:
$ git remote add origin git@gitlab.com:btoll/gitlab-dev.git
As we’ve seen previously, this adds the remote
section to the config:
[remote "origin"]
url = git@gitlab.com:btoll/gitlab-dev.git
fetch = +refs/heads/*:refs/remotes/origin/*
Next, we’ll add a local branch that tracks a remote one:
$ git checkout --track origin/master
fatal: 'origin/master' is not a commit and a branch 'master' cannot be created from it
Um, what happened?
Let’s see what branches git
knows about:
$ git branch -a
$
None. Well, that’s a bummer.
Well, we need to fetch all of the git
objects and refs from the remote repository that we named as the origin
:
$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), 1.60 KiB | 1.60 MiB/s, done.
From gitlab.com:btoll/gitlab-dev
* [new branch] master -> origin/master
$ git branch -a
remotes/origin/master
Ok, great. Let’s now try to pull:
$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to rebase against.
See git-pull(1) for details.
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=origin/<branch> master
Fortunately, git
has some pretty nice warning and error messages that are actually helpful. This is telling us that it doesn’t have enough information to pull from a remote repository because there is nothing in the config file that tells it how to do that (specifically, it doesn’t know which branch to pull).
It is noteworthy that
git-pull
will fetch thegit
remote repository’s objects and refs if they haven’t been fetched yet.
One of the reasons this confuses people is because some git
commands will add the branch
section to the config, and so they’re not used to having to do it themselves.
The first way to fix the above error is to include both the remote
and branch
in the command:
$ git pull origin master
From gitlab.com:btoll/gitlab-dev
* branch master -> FETCH_HEAD
Of course, this works, but you’ll have to do it every time. To fix this so you can simply do git pull
(and git push
) without any other arguments, you’ll need to set the upstream branch, as the helpful error message indicates:
$ git branch --set-upstream-to=origin/master master
Branch 'master' set up to track remote branch 'master' from 'origin'.
Open the config file to see the result:
[branch "master"]
remote = origin
merge = refs/heads/master
Alternatively, we could fetch the objects and refs when we add the origin
remote with the -f
switch:
$ git remote add -f origin git@gitlab.com:btoll/gitlab-dev.git
Updating origin
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), 1.60 KiB | 1.60 MiB/s, done.
From gitlab.com:btoll/gitlab-dev
* [new branch] master -> origin/master
This saves us the step from having go manually fetch the objects and refs ourselves. Check out the config file after the command is finished and the downloading is done:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = git@github.com:btoll/benjamintoll.com
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
Summary
It’s always good to revisit git
commands that we use every day and perhaps take for granted.