(Previously: Part 3: Remotes)

In the previous chapter/section, we learned about remotes and pushed our local source files up to a new, empty, remote repository for other developers to collaborate.

This time, we’re going to do the opposite: Create an empty local repository and set it up so that we can work on an existing remote repo, our “cow-tipper” project on GitHub.

Building a local repository

I’m pretty sure I know what we need to do:

  • Initialize a new empty repository with git init
  • ensure the default branch is “main” (instead of “master”)
  • add a <remote> referencing git@github.com:buster-kitten/cow-tipper.git
  • fetch from that remote to populate the remote-tracking branch “<remote>/main”
  • set the upstream branch on local “main” to be the “<remote>/main”

In a new directory:

$ git init --initial-branch=main
error: unknown option `initial-branch'

Wut? It turns out that the initial-branch option is new in Git version 2.28, and here in Linux I’m using 2.25. We’ll have to use an alternative method: I’ll create it with the default, and then re-name it:

$ git init
Initialized empty Git repository in /home/buster/Projects/cow_tupper/.git/
$ git branch -m master main
error: refname refs/heads/master not found
fatal: Branch rename failed

Wut!? “master” isn’t real? But:

$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)

Well, then, let’s just create a “main” branch directly and worry about deleting “master” later:

$ git checkout -b main
Switched to a new branch 'main'

And add the remote, which we’ll call “pluto” for fun. It’s pretty remote. Then we can fetch:

$ git remote add pluto git@github.com:buster-kitten/cow-tipper.git
$ git fetch pluto
remote: Enumerating objects: 68, done.
remote: Counting objects: 100% (68/68), done.
remote: Compressing objects: 100% (46/46), done.
remote: Total 68 (delta 18), reused 68 (delta 18), pack-reused 0
Unpacking objects: 100% (68/68), 6.90 KiB | 441.00 KiB/s, done.
From github.com:buster-kitten/cow-tipper
 * [new branch]      main       -> pluto/main

Cool. Now we can make “main” a remote-tracking branch, right?

$ git branch --set-upstream-to pluto/main
fatal: branch 'main' does not exist

Wut!? Now “main” isn’t real?

$ git branch --verbose
$

No results? But:

$ git status
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)

So we’re “on branch main”, but “branch ‘main’ doesn’t exist”. This is confusing… some might call it a “bug”, others “by design”. It is, for sure, unfortunate.

Okay, let’s not waste any more time: If you remember from earlier, “A branch in Git is just a pointer to a specific commit.” But we don’t have any commits yet. However, the repository metadata knows that the branch will be called “main”, just as soon as the first commit is made. That’s why we get the mixed messages.

So what we actually need to do at this point, is this:

$ git checkout main
Branch 'main' set up to track remote branch 'main' from 'pluto'.
Already on 'main'
$ git status
On branch main
Your branch is up to date with 'pluto/main'.

nothing to commit, working tree clean
$ git branch --verbose
* main 92502d2 Added header comment lines

Here’s what happened, and I’m going to quote and paraphrase StackOverflow author torek directly because they wrote a great explanation:

“You had a repository that is in a peculiar state: it has no commits, so it has no branches. At the same time, it does have a current branch, which is master. In other words, the current branch is a branch that does not exist.

Whenever you run git checkout <name> and there is no branch named <name>, Git checks to see if there is exactly one remote-tracking branch named <remote>/<name>. If so, Git creates a new branch <name> that has <remote>/<name> as its upstream branch.”

torek @ stack overflow

This is a bit funky, and I really am not a fan of received wisdom, but it explains what we observed above.

Let’s recap the necessary commands, but skip the stuff that didn’t work, the duplicate commands, and do things in the optimal sequence:

$ git init
$ git remote add pluto git@github.com:buster-kitten/cow-tipper.git
$ git fetch pluto
remote: Enumerating objects: 68, done.
remote: Counting objects: 100% (68/68), done.
remote: Compressing objects: 100% (46/46), done.
remote: Total 68 (delta 18), reused 68 (delta 18), pack-reused 0
Unpacking objects: 100% (68/68), 6.90 KiB | 415.00 KiB/s, done.
From github.com:buster-kitten/cow-tipper
 * [new branch]      main       -> pluto/main
$ git checkout -b main
Switched to a new branch 'main'
$ ls -l
total 0

No files? Where are my files?

$ git branch --verbose
$

That returns nothing. Huh. Try checking out the branch a second time:

$ git checkout main
Branch 'main' set up to track remote branch 'main' from 'pluto'.
Already on 'main'
$ git branch -v
* main 92502d2 Added header comment lines
$ ls -l
total 12
-rw-rw-r-- 1 buster buster 152 Sep 17 13:22 cow_lib.py
-rw-rw-r-- 1 buster buster 533 Sep 17 13:22 cow_tipper.py
-rw-rw-r-- 1 buster buster 265 Sep 17 13:22 README.md

That’s better.

I have not found an explanation of why we needed to do a second git checkout in order to get our working tree populated. Suggestions on a postcard, please.

Attack of the Clones

None of that stuff really matters because you’re much more likely to use a Git command that bundles all those steps together into one:

Clone

$ git clone <url>

The Git clone command replicates a remote repository into a brand new local repository on your local file system, complete with remotes, remote-tracking branches, and a working tree of source files. Of course, it makes some choices for you:

  • creates a local repository
  • creates a remote called “origin”
  • creates a remote-tracking branch called “origin/master”
  • fetches the latest changes
  • creates a “master” branch tracking “origin/master”
  • merges (i.e. populates) the “master” branch from “origin/master”

Example:

$ git clone git@github.com:buster-kitten/cow-tipper.git
Cloning into 'cow-tipper'...
remote: Enumerating objects: 68, done.
remote: Counting objects: 100% (68/68), done.
remote: Compressing objects: 100% (46/46), done.
remote: Total 68 (delta 18), reused 68 (delta 18), pack-reused 0
Receiving objects: 100% (68/68), 6.92 KiB | 3.46 MiB/s, done.
Resolving deltas: 100% (18/18), done.
$ cd cow-tipper
$ ls -l
total 12
-rw-rw-r-- 1 buster buster 152 Sep 17 13:34 cow_lib.py
-rw-rw-r-- 1 buster buster 533 Sep 17 13:34 cow_tipper.py
-rw-rw-r-- 1 buster buster 265 Sep 17 13:34 README.md

And that’s why clone is typically the first Git command you’re likely to encounter, even before you learn about add, commit, push, fetch, merge, and pull.

How do I…?

Show more information about a remote

$ git remote show <alias>

Delete a remote branch

$ git push <remote> --delete <branch>

This deletes the pointer representing <branch> on the <remote> server .

Delete a remote reference

The remote remove command deletes all remote-tracking branches and config settings related to the remote:

$ git remote remove <alias>

Further reading

And that’s all for now. I hope you enjoyed following along and maybe even found it useful. Cheers.