Chapter 12
Understanding Remotes—Branches and Operations

Up until now, the examples that I've looked at, and the majority of things that I've talked about, have involved working with Git in the local environment. I previously defined the local environment as the three Git-related areas that reside on your local system—the working directory, the staging area, and the local repository—along with the supporting pieces, such as configuration, that work with them.

It is a departure from most other texts on Git to wait this long before diving more deeply into the remote side of Git and the remote repository. However, if you have been reading this book in the order it was written to learn about Git, you will have developed a firm foundation to help you understand the interactions with the remote side.

By now, you should be comfortable with how Git works in the local environment, and also understand how it manages those three levels that make up the local environment. And, certainly, you should be well acquainted with the warnings against modifying content that has already been put into the remote environment (the remote repository).

With that foundation, let's take a closer look at what I mean by remotes.

REMOTES

Whenever you talk about remotes in Git, this word can have several meanings. Most commonly, it refers to a remote repository. In its basic form, a remote repository is just a Git repository with a protocol for access and server-side hooks. As I discuss in Chapter 3, you can think of the remote repository as the public or server-side repository. In most other source management systems, this would be the only repository you have.

The remote repository is also the collection point for code from multiple users. If you are working with multiple people, each working in their own local environment, they may all be pushing code to share with others to a common remote repository (see Figure 12.1).

A schematic diagram of arrangement of local versus remote environments.

Figure 12.1 Arrangement of local versus remote environments

Another common use case of the term remote in Git is a local reference to a remote repository. Remote repositories typically have Internet-accessible addresses composed of URLs formatted for a particular network protocol, such as SSH, HTTP, or HTTPS. (I discuss these protocols in more detail in the following section.) Those addresses can be long, which can make them difficult to remember as well as difficult to type in with commands that need to access them. So, once a connection has been established, Git allows the user to use simple, one-word reference names to point to the URL. The default reference name in Git is origin.

As an example, I might have a Git repository stored on

https://corporateGitServer/BrentLaster/demoprojects/project1.git

That's a long URL to remember and type, and so it's difficult to get right every time I want to use some command to access the remote at that location. So, when I clone a copy of the project down to my local machine to work with, Git establishes the remote reference name as origin. This means that instead of having to type

$ git push https://corporateGitServer/BrentLaster/demoprojects/project1.git …

I can simply type

$ git push origin …

These remote reference names are one-way references—names that point to a location. Removing the name does not affect the remote repository; the names are just aliases for the repositories.

Remote Access Protocols

I'll now take a moment and talk about the various networking protocols that you can use to communicate with a Git remote. There are four available protocols: Local, Git, SSH, and HTTP.

Local

The Local protocol is essentially just filesystem access over something like a Network File System (NFS) mount or a shared drive. It can be convenient to share content between multiple users using an easily accessible location. However, this protocol is not authenticated or protected (other than as defined by your file system access). So, using this protocol involves the same risk as relying on any open access in a shared location. Also, it is only convenient as long as you are able to connect (and stay connected) to the shared resource.

An example of cloning down something using the Local protocol might be

$ git clone /var/git/repos/myproj.git

There is no special setup for using this protocol, other than providing the shared access.

Git

Git comes with a special daemon program that you can use to provide access to a repository over a dedicated port (9418). This is the fastest protocol, but it comes with some big drawbacks: no authentication and an all-or-none access model (if anyone can access it, everyone can access it). Setting up access through the port can also be difficult.

An example of cloning down something using this protocol might be

$ git clone git://repos/myproj.git

Setup for using this protocol generally involves installing a daemon, starting it up, and adding a special marker file to each project so that Git knows that it's okay for the daemon to access it.

To understand more about the use of the daemon program, see the help page for git-daemon.

$ git daemon --help

SSH

Secure Shell (SSH) is a more commonly used protocol. It is also one that most users and network administrators are familiar with; it is also inexpensive to set up and use.

If you're not familiar with SSH, it operates on the idea of authenticated access using private and public keys. The public key goes on the resource you need access to (the Git remote in this case), and the private key goes into a special ˜/.ssh subdirectory on your local machine. As long as the private key on the user's system corresponds to a public key on the desired resource, the user can connect and transfer information without having to log in each time. Authentication is handled using the keys.

There are two forms of paths that you can use with SSH:

$ git clone ssh://<username>@mygitserver.mycompany.com/myproject.git

or

$ git clone <username>@mygitserver.mycompany.com:myproject.git

Figure 12.2 illustrates the difference between the traditional login methods of access (top) and SSH access with keys (bottom).

Image described by caption and surrounding text.

Figure 12.2 Login access (top) versus SSH access (bottom)

HTTP

There are two types of HTTP access that you can use with Git: dumb HTTP and smart HTTP. The dumb HTTP mode, which is read-only, is simply handled and served like any other items under an HTTP server. To set it up, a Git repository is put under an HTTP document root, and a special Git hook (post-update) is set up.

The smart HTTP mode allows for read-and-write access using authentication or anonymous access. It is more similar to SSH in terms of operation but doesn't require the keys; instead, it can use standard username and password authentication. A common URL can be used for viewing, cloning, and pushing changes (assuming you have access).

Setup of the smart HTTP mode involves setting up the web configuration and permissions, in addition to having requests handled by a CGI script named git-http-backend, which is included with Git. Here is an example of cloning with this protocol:

$ git clone https://mycompany.com/repos/myproject.git

Now that you understand the basic concepts of remotes, you can look at the git remote command.

The Remote Command

The git remote command allows you to manage your connections and interactions with remote repositories. The syntax is as follows:

git remote [-v | --verbose]
git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=<fetch|push>] <name> <url>
git remote rename <old> <new>
git remote remove <name>
git remote set-head <name> (-a | --auto | -d | --delete | <branch>)
git remote set-branches [--add] <name> <branch>…
git remote get-url [--push] [--all] <name>
git remote set-url [--push] <name> <newurl> [<oldurl>]
git remote set-url --add [--push] <name> <newurl>
git remote set-url --delete [--push] <name> <url>
git remote [-v | --verbose] show [-n] <name>…
git remote prune [-n | --dry-run] <name>…
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)…]

As you can see, there are a lot of options here, but you will probably only use a few of them. Many of these options relate to the idea of a remote reference that I talked about earlier in this chapter; this is a short name that refers to the longer protocol string (https://…, git://…, ssh://…) that is the actual location of the remote repository.

Let's look at a couple of the common use cases for the git remote command. As I mentioned, when you clone down a remote repository, Git automatically sets up a remote (reference) named origin that maps to whatever location you cloned from. You can see what a remote maps to at any point by using the -v or --verbose option of the command. Here is an example:

$ git clone https://github.com/brentlaster/gradle-greetings

$ cd gradle-greetings

$ git remote -v
origin  https://github.com/brentlaster/gradle-greetings (fetch)
origin  https://github.com/brentlaster/gradle-greetings (push)

Now, for any of the commands that I need to use to interact with the remote repository at http://github.com/brentlaster/gradle-greetings, I can just use origin instead of typing out that longer path.

I can also add, remove, and rename remote references. Again, these are just references or aliases. Modifying these references does not affect the remote repository.

git remote add version2 https://github.com/brentlaster/gradle-greetings-sets

$ git remote -v
origin  https://github.com/brentlaster/gradle-greetings (fetch)
origin  https://github.com/brentlaster/gradle-greetings (push)
version2        https://github.com/brentlaster/gradle-greetings-sets (fetch)
version2        https://github.com/brentlaster/gradle-greetings-sets (push)

$ git remote rename version2 origin2

$ git remote -v
origin  https://github.com/brentlaster/gradle-greetings (fetch)
origin  https://github.com/brentlaster/gradle-greetings (push)
origin2 https://github.com/brentlaster/gradle-greetings-sets (fetch)
origin2 https://github.com/brentlaster/gradle-greetings-sets (push)

$ git remote rm origin2

$ git remote -v
origin  https://github.com/brentlaster/gradle-greetings (fetch)
origin  https://github.com/brentlaster/gradle-greetings (push)

Notice that there are also options to get and set the URL associated with the remote. This can be useful if you cloned with one protocol but want to use a different one now.

Now that you understand the basic idea of remotes, let's look at how Git interacts with them and how you keep track locally of where things are on the remote side.

How Git Interacts with the Remote Environment

Remote repositories in Git are dumb. This doesn't mean they're a bad idea, but rather that they don't have to do a lot of processing. So, you could say that the model around them is smart.

Git does not maintain a constant connection from the local repository to the remote repository. (If it did and it required this, you wouldn't be able to do disconnected development.) Rather, Git checks in with the remote repository to get updated status information and contents. This checking in occurs whenever a Git command that requires interaction with the remote repository is initiated in the local environment—essentially, when doing a fetch, pull, or push operation.

For the necessary duration of those operations, Git in the local environment establishes the connection to the remote repository, gathers information about where branches of interest are in the remote repository, and updates or downloads content as appropriate.

So, Git can get by with just temporary connections to the remote environment. This is similar to how most traditional source management systems work. A connection to the server is only established and used when updating content between the server and the local working area.

Remote Tracking Branches

Having the connection between the local environment and the remote environment as a temporary one, only activated when an operation demands, is a useful construct for keeping things simple. It also enables you to limit overhead and bandwidth, and support paradigms like disconnected development.

However, it is not as useful if you are working in your local environment and you want to know how your local changes compare to the versions of changes in the remote repository (status) or you want to pull content from the remote repository but not merge it in yet. These kinds of operations require some persisted knowledge of the state of the remote repository (between operations that establish physical connections to the remote environment).

Git persists this information about the state of the remote repository by setting up remote tracking branches in the local repository. Essentially, the local repository contains state information about the remote repository based on the last time you talked with it (connected to the remote).

These copies of the remote branches exist alongside the local branches in the local repository. As such, there has to be a way to distinguish the two branch types because you will usually have local branches with the same name as remote branches. For example, I will have a master branch on the remote and a master branch in the local repository.

To help distinguish the remote tracking branches from the local branches in the local repository, remote tracking branches have a namespace associated with them—the remote reference name, such as origin, origin2, and so on. For example, if your remote reference to the area you cloned from is called origin, then the remote tracking branch for master in the local repository will be named origin/master (or more accurately, remotes/origin/master). The local master branch will just be master. Table 12.1 summarizes the characteristics of the various branches.

Table 12.1 Summarizing the Types of Branches in Git

Category Where It Lives Description How It Is Updated Reference
Local Local repository Private branches created by a user in the local environment By commits, merges, rebases, and so on, in the local environment <branch name>
Example: master
Remote Remote repository Public branches that track updates from multiple users By pushes from the users' local environments Usually <branch name> on <remote reference>
Example: master on origin
Remote tracking Local repository Private branches that track the state of the public branch on the remote—as it was the last time you connected to the remote When a Git operation interacts with the remote—such as push, pull, fetch, or clone <remote reference>/<branch name> or
remotes/<remote reference>/<branch name>
Example: origin/master or remotes/origin/master

Let's see how this all fits in with the Git model by looking at the clone command.

Git Clone

As I discuss in the early chapters, cloning is how you start working with an existing Git repository. The idea is that you get a copy of the repository from the server (remote side) down to your local environment (a local directory).

You initiate this copy with the git clone command. The syntax is as follows:

git clone [--template=<template_directory>]
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
          [--dissociate] [--separate-git-dir <git dir>]
          [--depth <depth>] [--[no-]single-branch]
          [--recursive | --recurse-submodules] [--] <repository>
          [<directory>]

An example clone command is

$ git clone ssh://git@gitserver:repository

or

$ git clone http://gitserver/repository

When you do a clone, several things take place, including the following:

  • The .git repository is copied from the server (remote side) to the directory locally. This instantiates a new local environment.
  • Git creates remote tracking branches in the clone that correspond to all of the branches in the remote repository.
  • For the currently active HEAD in the remote, Git creates a corresponding local branch.
  • For the currently active HEAD, Git checks out a copy of the corresponding local branch.

Figure 12.3 illustrates the before and after state of a clone.

Image described by caption and surrounding text.

Figure 12.3 Start and end of a cloning operation

Notice that you start out with a remote repository containing a few commits and two separate branches: master and testing. From your point of view, these are the remote branches. When you do the clone, you get a copy of the remote repository cloned into a local repository. The existing branches in the remote each get their own remote tracking branches, namespaced with the name of the remote—that is, origin/master and origin/testing. For the active HEAD, a local branch, master, is created in the local repository and the contents are checked out into the working directory.

Remember that creating a branch is just creating a pointer in Git. A benefit of the remote tracking branches is to mark where the branches were located in the remote, so you can reference where they are pointing locally without having to maintain a persistent connection to the remote.

You can easily see the remote tracking branches after a clone by using the -r option on the git branch command.

$ git branch -r
  origin/HEAD -> origin/master
  origin/testing
  origin/master

Understanding Clone Paths

By default, a clone operation creates a local directory with the name of the repository. If there is a multi-level path, only the last part of the path is the actual repository. You can think of this like downloading a Zip file. The path in front of the Zip file is just the path; the last part is the actual Zip filename. And, when you download the Zip file, it is expanded into whatever structure is contained in the file. Likewise, the last piece of the repository path is the actual repository, and it is expanded into the .git structure and a checked-out version of the current HEAD. Figure 12.4 illustrates this idea.

Image described by caption and surrounding text.

Figure 12.4 A way to think about cloning multi-level paths

In most cases, repositories avoid the issue of multiple levels in paths by hyphenating all of the levels together as one name. For example, top/mid/my.git becomes top-mid-my.git, and the directory that is created when this path is cloned becomes C: op-mid-my.

If you want to clone to a different destination directory (one that's different from the name of the repository), you can simply add that directory as the last argument to the clone command.

$ git clone [email protected]:top/mid/my.git project1

In this case, Git creates the local subdirectory as project1 and then clones the repository into that subdirectory instead of into my.

Clone Options

So far, I've described the basic operation of the clone command. However, there are some other useful options that you should be aware of.

bare

The --bare option tells Git to create a bare repository. This is a clone of the repository that is done in a way more suitable for copying or migrating to another remote location. This option has the following characteristics:

  • Instead of creating a subdirectory with .git under it, it places the contents that would normally go into the .git directory in the subdirectory itself. So, you end up with repository.git instead of repository/.git.
  • The remote branches become local branches in the cloned repository (no remote tracking branches).
  • There is no checkout of a branch.
mirror

The --mirror option also tells Git to create a bare repository and implies --bare. However, instead of just copying the remote branches and making them into local branches, it copies all references in the repository, including things like notes or remote-tracking branches if those items are also in the repository. It's really a complete copy of a repository, just done in a Git way.

The mirror option also sets things up so that if you later need to re-copy the repository (thus overwriting it), you can do that with a git remote update command.

branch

The --branch (or -b) option tells Git to make HEAD in the cloned repository point to the branch specified by the option instead of master. This means that branch will also be the one that is checked out.

single-branch

The --single-branch option tells Git to only clone down one branch in the repository. By default, this is whatever HEAD is in the remote, although you can specify another branch using the --branch option.

depth

The depth option creates a shallow clone where the history is truncated to the number of commits specified as an argument to the option. So, --depth=1 would only clone down the latest changes (one commit).

Now that you can clone repositories down and you understand the basic relationships between remote and local branches, let's look at how to view and specify those relationships.

Viewing Information about Remote Branches

After cloning or establishing a connection to a remote branch and synching the content, there are several different ways to see information about remote branches.

First, you can view the list of remote branches with the -r option to git branch. The -r option here refers to remotes. Here's an example of how to use it:

$ git branch -r
  origin/HEAD -> origin/master
  origin/master
  origin/test
  origin/test2

In this case, you have three branches in the remote branch that you are now tracking in the local repository: master, test, and test2. Notice that this command also shows you where HEAD is pointing.

If you want to see the local branches as well as the remote ones, you can use the -a option (all) to the git branch command.

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/test
  remotes/origin/test2

Notice that this list now includes your one local branch (master) along with the remote tracking branches.

For any of these options, you can add the -v (verbose) option to see which commit is current on the branch—where the tip of the branch points to. This example shows the short version of the commit's SHA1 and the commit message associated with that commit:

git branch -av
* master                539358f update file
  remotes/origin/HEAD   -> origin/master
  remotes/origin/master 539358f update file
  remotes/origin/test   8391dbd update
  remotes/origin/test2  8391dbd update

If you had used the -rv option instead, you would not have seen the first line with the local master branch.

There is one more variation you can use with the branch command to find out additional information: the -vv flag. (Note that this is two v's side by side, not one w.) The two v's tell Git to show extra information that is very useful—namely, any tracking connections between local branches and the remote tracking (upstream) branches.

$ git branch -vv
* master 539358f [origin/master] update file

This example is telling you that your local master branch is set up to track origin/master. You could also use an -a or -r option with the -vv option. However, because those options would only add the same remote tracking branch list, no additional information would be output.

One other way to get the list of branches indirectly is to use the show option for your remote.

$ git remote show origin
* remote origin
  Fetch URL: git@diyvb:repos/remote_demo
  Push  URL: git@diyvb:repos/remote_demo
  HEAD branch: master
  Remote branches:
    master     tracked
    test       tracked
    test2      tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

At the bottom of the output, you can see that the local branch master is configured to work with the remote branch master.

Finally, if you take a look at the local config file, you can see where this configuration information is actually stored.

       $ cat .git/config
       [core]
             repositoryformatversion = 0
             filemode = true
             bare = false
             logallrefupdates = true
       [remote "origin"]
             url = git@diyvb:repos/remote_demo
             fetch = +refs/heads/*:refs/remotes/origin/*
       [branch "master"]
             remote = origin
             merge = refs/heads/master

Note the last section about branch master and the resemblance to the data from the git remote show output.

Now that you understand how to view the upstream and tracking branch information, let's look at how to set up the upstream and tracking relationship for branches that don't already have such a relationship.

Configuring Upstream Relationships for Branches

When I talk about upstream in Git as it relates to branches, I'm referring to which branch in the remote repository should correspond to a given branch in the local repository. Basically, if you're going to do any of the update operations, such as fetch, pull, or push, between a branch in the local repository and a branch in the remote repository, Git needs to know how to map between the two repositories.

As I discussed earlier, when you initially clone a repository down, you get remote-tracking branches that correspond to the remote branches in the remote repository. And, unless you have specifically cloned the repository as a bare repository (meaning one that is not intended for local use), you will also have a default local branch available for you to use. This default local branch (the current HEAD, usually master) will already be mapped to the corresponding remote tracking branch (for example, master is connected with origin/master). Thus, origin/master is the upstream of master.

Automatic Mapping

For other remote tracking branches, if you attempt to start working with a local branch with the same name, Git may establish the tracking relationship for you—or it may not. If you do the traditional git branch command to create a local branch with the same name as one of the remote tracking branches, Git happily allows you to do that, but it does not set up the upstream tracking for you. If, on the other hand, you begin by using a git checkout command to start working with a local branch that has the same name as a remote tracking branch, Git creates the local branch for you and establishes the upstream tracking connection. Take a look at the following example to see how this works:

$ git branch -av
* master                539358f update file
  remotes/origin/HEAD   -> origin/master
  remotes/origin/master 539358f update file
  remotes/origin/test   8391dbd update
  remotes/origin/test2  8391dbd update

$ git branch test

Notice that, at the start, you have the master, test, and test2 remote-tracking branches that were pulled into your clone from the remote. You then create a new branch named test using the branch command.

$ git branch -vv
* master 539358f [origin/master] update file
  test   539358f update file
$ git checkout test
Switched to branch 'test'

Taking a look at the branch -vv output, you see that while master is set up to track origin/master, test is not.

$ git checkout test2
Branch test2 set up to track remote branch test2 from origin.
Switched to a new branch 'test2'
$ git branch -vv
  master 539358f [origin/master] update file
  test   539358f update file
* test2  8391dbd [origin/test2] update

You then do a checkout command for a test2 branch. This time, because you did the checkout instead of the branch command, and because there is a remote-tracking branch with the corresponding name, Git creates the new local branch and sets up the upstream tracking for you. The branch -vv option confirms this for you.

Manual Mapping

You may have a situation where you create a local branch that does not have automatic upstream tracking done for it. In this case, it is up to the user to explicitly set up the upstream tracking using one of the following methods.

Git provides a way to specify what the upstream relationship should be when a branch is created. By default, if you choose an upstream branch as the starting point (meaning you supply it as the second branch in the following format), Git sets up the tracking.

$ git branch test origin/test
Branch test set up to track remote branch test from origin.

The same result occurs if you explicitly include the --track option.

$ git branch --track test origin/test
Branch test set up to track remote branch test from origin.
$ git branch -vv | grep test
  test   8391dbd [origin/test] update

In the previous note, I mentioned the autoSetupMerge configuration value. Having this value set implies --track by default. Similarly, if for some reason, you don't want to have the upstream tracking set up, you can supply the --no-track option.

$ git branch --no-track test origin/test
diyuser@diyvb:~/remote_demo4$ git branch -vv | grep test
  test   8391dbd update

Notice the absence of the upstream tracking branch information in the branch command output.

There is also an option named --set-upstream, which is the same as --track in most cases. However, this option is deprecated in Git and will be unsupported at some point, so it is best not to use it. As of this writing, if you use it, it will still work, but you will receive a message that it is being deprecated.

$ git branch --set-upstream test2 origin/test2
The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
Branch test2 set up to track remote branch test2 from origin. 

The other available option is the newer --set-upstream-to option. This option performs a similar function to --track and --set-upstream but is clearer.

$ git branch test
$ git branch -vv | grep test
  test   539358f update file
$ git branch --set-upstream-to=origin/test test
Branch test set up to track remote branch test from origin.
$ git branch -vv | grep test
  test   539358f [origin/test: ahead 4, behind 1] update file

Notice that when using the --set-upstream-to option, you set it to the desired value with an equals sign and pass the local branch after the upstream assignment. This option can be abbreviated as -u. Also note that you can set the upstream-tracking branch to any of the branches, not just the one that corresponds in name. In the following example, I set the upstream to be origin/test2 for the local test branch (instead of origin/test).

$ git branch test
$ git branch -vv | grep test
  test   539358f update file
$ git branch -u origin/test2 test
Branch test set up to track remote branch test2 from origin.
$ git branch -vv | grep test
  test   539358f [origin/test2: ahead 4, behind 1] update file

Next, you'll look at the push operation and see how the interaction between the different types of branches is used with it.

Push

As you saw in my earlier promotion model for Git, you can use the push command to update the remote repository. Basically, you can think of it as trying to take changes you've committed into your local repository and mirror them to the remote repository. As part of the same operation, push takes note of which commits the corresponding branches in the remote repository point to, and it updates the remote tracking branches in the local repository to point to the same mirrored versions there. The syntax for the push command is as follows:

git push [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-           run] [--receive-pack=<git-receive-pack>]
         [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
          [-u | --set-upstream]
          [--[no-]signed|--sign=(true|false|if-asked)]
          [--force-with-lease[=<refname>[:<expect>]]]
          [--no-verify] [<repository> [<refspec>…]]

Let's look at an example of what a push operation would look like between a local repository and a remote repository. In this case, you'll mimic the idea of pushing to an empty remote repository from an existing local repository.

Figure 12.5 shows a local repository with three commits that have been made into it and an empty remote repository.

Two schematic diagrams of a local repository with three commits that have been made into it and an empty remote repository.

Figure 12.5 Initial changes in the local repository

If you now do a push operation, Git takes your local changes and updates the remote repository with them (see Figure 12.6).

Two schematic diagrams of a local repository and remote repository after a push to the remote repository.

Figure 12.6 After a push to the remote repository

Git also creates a branch pointer in the local repository to indicate where the corresponding branch in the remote repository is pointing (see Figure 12.7). This creates the remote tracking branch that I have been talking about. This branch has the namespace of the remote reference (origin by default), so you can reference it as origin/master. You can now reference the origin/master branch as a representation of the master branch on the remote without being connected to the remote.

Two schematic diagrams of a local repository and remote repository with remote tracking branch created in the local repository.

Figure 12.7 Remote tracking branch created in the local repository

Seeing Status: Local versus Remote

Carrying this sequence one step further, suppose you now make a commit into the local repository that is not (yet) pushed over to the remote repository, as shown in Figure 12.8.

Two schematic diagrams of a local repository and remote repository after a commit into the local repository.

Figure 12.8 After a commit into the local repository

The interesting thing about this state is what a git status command would show.

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working directory clean

Notice the wording here. You can read the first sentence as: “Compared to the last time you checked in with the remote, your local branch has one newer commit on it.” This is because origin/master (the remote tracking branch) represents where master was the last time you communicated with the remote. To see the differences another way, you can use the git branch -av command.

$ git branch -av
* master                  253231d [ahead 1] <commit message>
  remotes/origin/master   bed5211 <previous commit message>

Notice the shorthand notation here, [ahead 1], which means the same thing as “master is ahead of origin/master by one commit.”

You can try one other variation by using the -vv option.

$ git branch -vv
* master   253231d [origin/master: ahead 1] <commit message>

Push Formats

The push command expects a repository and a reference to push to. For your purposes here, the repository will always be a remote reference name (such as origin) and the reference will always be one or two branch names. The reason I say one or two is because, while you typically only specify the branch you are pushing to, it is also possible to specify a different source branch to push from (see the following examples).

So, in most cases, you would use this form:

$ git push <remote repository> <remote branch>

You can, however, also use this form if you are pushing from a local branch that is named differently from the targeted remote branch:

$ git push <remote repository> <local branch>:<remote branch>

There is also a default form of the push command, which is just

$ git push

In the default form, the repository defaults to origin. However, the default branch behavior is configurable and requires some additional explanation, as I discuss in the section, “Understanding the Push Default Behavior.”

$ git push origin :

In this form, the command pushes all matching branches. For a description of what matching means here, see the section “Understanding the Push Default Behavior.”

$ git push origin HEAD

This is the same form as I described earlier, but it's worth mentioning because it provides a convenient way to just tell Git to push the current branch (in this case, HEAD) to the branch of the same name on the remote.

Finally, it's worth noting that you can push multiple branches on the command line or multiple local:remote pairs.

$ git push origin branch1 branch2 branch3
$ git push origin lbranch1:rbranch1 lbranch2:rbranch2 lbranch3:rbranch3

Understanding the Push Default Behavior

The reason you have different options for the push command's default behavior has to do with how you map branches from the local repository to branches in the remote repository. If no reference (branch) is specified directly on the command line, is already configured, or is implied by a command line option, Git needs to know what to do in terms of which local branch to push to which remote branch.

To specify your choice for the default value, you configure the Git configuration setting push.default. The possible values are as follows:

  • nothing—Don't push anything.
  • matching—Push all the matching branches. Matching here means that the names match between a local branch and a remote branch.
  • upstream—Push the current branch to the one defined as its upstream branch. (This was formerly known as tracking.)
  • current—Push the current branch to the branch on the remote side with the same name.
  • simple—Like upstream, but don't allow the push if the upstream's branch name is different from the local branch's name. (This is the default value as of Git 2.0.)

If you're only concerned with pushing to a single branch at a time, or if you want more control, you can use the simple, current, and upstream modes. If you want to push all corresponding branches, you can use matching.

Upstream, in the context of a Git remote environment, generally refers to the location of the remote repository, but it has a lot of context around it when setting up branches. I try to clarify that context in the earlier section, “Configuring Upstream Relationships for Branches.”

The simple, current, and upstream modes are for when you want to push out content from a single branch, even when the other branches are not yet ready to be pushed out.

Push Options

Push has many options that you can use to customize its behavior. I'll cover a few of the most common and significant ones here, but you can find more information online on the push help page.

all

The all option pushes all branches. An example would be:

$ git push --all

delete

The delete option deletes the reference on the remote repository. For example, the following command deletes a remote branch:

$ git push --delete origin testing
- [deleted]         testing

You can also use the colon (:) notation to tell Git to delete something. For example, this command has the same effect as the previous one.

$ git push origin :testing
- [deleted]         testing

The delete option is a global option to push that applies to all references specified on the command line. The colon (:) allows for specifying particular references to delete if there is a list.

tags, follow-tags

By default, push does not push tags over to the remote side. The option --tags tells Git to push tags. The --follow-tags option is more refined and pushes annotated tags that are reachable in the current commits and missing in the remote.

force

Git typically refuses to accept a push to the remote unless it can do a fast-forward merge—meaning the changes being pushed already contain the latest contents of the remote branch and no one else has made intervening changes. Here's an example of what Git returns in this case:

$ git push
To C:/Program Files/Git/./calc2.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'C:/Program Files/Git/./calc2.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.

I talk more about the best way to handle this situation in Chapter 13. However, there is a --force option (short version -f) that you can use to force the push to happen in these cases. This can be dangerous, though, and you should only use it if you truly understand the consequences.

You can use the force option as follows:

$ git push -f origin master
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (10/10), 856 bytes | 0 bytes/s, done.
Total 10 (delta 0), reused 0 (delta 0)
To C:/Program Files/Git/./calc2.git
 + 0caf33c…7f3fb23 master -> master (forced update)

Notice the “forced update” option message at the end.

Git also allows you to use a plus (+) sign in front of the branch argument to force an update.

$ git push origin +master
Counting objects: 14, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (14/14), 1.14 KiB | 0 bytes/s, done.
Total 14 (delta 0), reused 0 (delta 0)
To C:/Program Files/Git/./calc2.git
 + 7f3fb23…13f956a master -> master (forced update)

The difference in using the --force option and the plus sign is that the --force option is a global option to push and so applies to all branches being pushed. The plus sign is applied on a branch-by-branch basis as desired.

mirror

The mirror option tells Git to push everything (tags, remotes, heads, and so on) over. You typically use the --mirror option if you need to migrate or move an entire repository somewhere.

set-upstream

For every branch that is successfully pushed by the push operation or that is up-to-date, the set-upstream option adds an upstream tracking reference. This may be needed on initial pushes for branches that do not already have an upstream tracking reference. By setting the upstream tracking reference, you will also help to ensure that any subsequent operations that use this reference (such as a git pull) will know what to do. The short version of this option is -u.

tags

The tags option tells Git to push over all of the tags (all of the items under ref/tags in the .git directory). You need to specify this option to ensure tags are pushed along with the other content.

Now, let's look at how you can update the local environment with changes from the remote repository using the fetch and pull commands.

Fetch

Once a repository is cloned, the connection between the remote branches and remote tracking branches in the local repository is established. The fetch command allows you to get the latest updates to the remote tracking branches (as well as tags). The syntax is fairly straightforward.

git fetch [<options>] [<repository> [<refspec>…]]
git fetch [<options>] <group>
git fetch --multiple [<options>] [(<repository> | <group>)…]
git fetch --all [<options>]

To understand the basic mechanics of a fetch operation, take a look at Figure 12.9. This is a continuation of the set of commits you were using in the push discussion (shown in Figures 12.5 to 12.8).

Four schematic diagrams with two local repositories and two remote repositories before and after a fetch operation.

Figure 12.9 Before and after a fetch operation

On the left side of Figure 12.9 is your starting point. You've pushed the most recent commit you made locally over to the remote. However, someone else has also made a change and pushed it into the remote repository. Thus, you have an additional commit in the remote repository that you don't have in your local repository.

If you were to do a git status command at this point, you would get the following output:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

This may seem confusing at first because I just said that there was newer content in the remote repository that you don't have in your local repository. However, remember that you're using the remote tracking branch (origin/master) as your bookmark of where the corresponding branch on the remote is. And the remote tracking branch is only updated when you check in with the remote. So, based on the last time you checked in with the remote, both the remote master branch and your local master branch were pointing at the same commit.

Now, if you look at the right side of Figure 12.9, you see a fetch operation being performed. The fetch operation communicates with the remote repository to see if there are any updates there that it needs to reflect in the corresponding remote tracking branch. There are: there is a new commit on master in the remote repository, as I previously noted. So, Git updates the repository with the new commit and (only) moves the remote tracking branch (origin/master) to point to the new commit. Notice that your local branch, master, is not updated. Fetch only updates the contents of the repository and remote tracking branches.

After the fetch operation, if you check the status, you see something like this:

$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
nothing to commit, working directory clean

This tells you that your current branch master is one commit behind where the remote tracking branch is. (Another way to say this is that the remote tracking branch, and thus the remote, has one newer commit that your local branch is not yet aware of.) Notice that the output also tells you that your local branch can be fast-forwarded. I'll discuss what that means shortly.

Fetch Options

As with the push command, there are a few options you should know about for the fetch command.

depth

The depth option limits fetching to the number of commits specified as an argument to the option. For example, --depth=1 only fetches down the latest changes (latest commit).

force

Like push, fetch also has a force option. If you attempt to fetch from a branch on the remote repository to a remote tracking branch in the local repository and Git can't do a fast-forward merge, then it refuses to do the update. However, you can use the --force (-f) option to force Git to do the update. Of course, you should only do this if you understand the consequences and are sure you want to do it.

To illustrate this, I'll use an example of trying to fetch updates from one branch into a completely different branch. Attempting this when Git can't do a fast-forward merge between the two branches results in an error message like the following one:

$ git fetch origin master:ui
! [rejected]        master     -> ui  (non-fast-forward)

You can use the -f option to force the update to happen.

$ git fetch -f origin master:ui
+ fbcf72c…8a3f48b master     -> ui  (forced update) 

As an optional method of forcing the update, you can supply a plus (+) sign in front of the branches.

$ git fetch origin +master:ui
+ abaf124…8a3f48b master     -> ui  (forced update)

The -f (or --force) option is an option to fetch, and it applies to all branches passed to the command. You can use the plus (+) sign to specify forcing any particular branch if you don't want to do all of them.

Synchronizing Local Branches after a Fetch

Recall that in an earlier example, after fetching the latest updates, the status said that your local branch was one commit behind the remote tracking branch and could be “fast-forwarded.”

This means that a fast-forward merge can be done to bring the local branch up-to-date with the latest changes to the remote tracking branch (and thus the remote branch). The left side of Figure 12.10 shows the local repository after the fetch but before the merge. Note that the master branch is one commit behind the remote tracking branch.

Six schematic diagrams with two working directories, two local repositories, and two remote repositories before and after the merge.

Figure 12.10 The local repository before and after the merge

You can then do the merge.

$ git merge origin/master
Updating 253231d..ecf2390
Fast-forward
 …

You end up with the results on the right side of Figure 12.10. Notice that the master branch has been fast-forwarded to the same commit as the remote-tracking branch. Also, as I discuss in Chapter 9, the merge occurs in the working directory as well as the local repository. This is shown in Figure 12.10 by the change in contents in the working directory before and after the merge.

This is how you synchronize a local branch after a fetch updates. After the merge, your local repository, local branch, and files in the working directory all have the latest updates from the remote.

Pull

If you understand fetch and merge, then you understand the pull command. In its default form, the git pull command is essentially a fetch followed by a merge. Figure 12.11 represents a before-and-after view that is similar to the other commands.

Six schematic diagrams with two working directories, two local repositories, and two remote repositories before and after a pull operation.

Figure 12.11 Before and after a pull operation

The syntax for the pull command is also fairly straightforward:

git pull [options] [<repository> [<refspec>…]]

Let's look at a few of the options for the pull command.

Pull Options

Because the pull command is basically a combination of a fetch and merge, pull has a set of options that correspond to fetching and a set that correspond to merging. For extended information about merging or rebasing, refer to Chapter 9.

Options Related to Merging

Following are a few useful options related to merging that are supported by the pull operation.

no-commit

The no-commit option performs the merge but does not commit the results. It provides an opportunity to inspect the results of the merge before committing.

no-edit

The no-edit option tells Git not to invoke the editor before the commit—just to accept the automatically generated message.

rebase

The rebase option has several possible settings. The main idea is that instead of just doing a fetch and merge for the pull action, Git does a fetch and rebase. Beyond that, you can use other settings:

  • =true—After the fetch, this setting rebases the current branch on top of the upstream branch.
  • =false—This setting merges the current branch into the upstream branch.
  • =preserve—This setting rebases with the rebase operation's --preserve-merges option. This forces Git to also use merge commits that are part of the history as part of the rebase.
  • =interactive—This setting starts up the interactive mode of rebase.
strategy

The strategy option supplies a merge strategy to use (-s for short). Note that this option can be supplied more than once. If multiple instances are supplied, that indicates the order in which Git should try to use each strategy.

strategy-option

Specifying this option to pull allows you to supply a particular option to the chosen merge strategy (-X for short).

Options Related to Fetching

The other component of the pull operation has to do with fetching. Here are a few useful options related to that aspect.

depth

Like cloning, the depth option does a shallow pull where the history is truncated to the number of commits that you specify as an argument to the option. For example, --depth=1 only pulls down the latest changes (one commit).

One interesting point about this option for the pull command is that it can lengthen or shorten the depth that was previously used. For example, if you clone down a repository with a depth of 1, but later decide you want a depth of 3, you can use the pull command with that depth.

force

Like push and fetch, the pull command also has a force option. If you attempt to pull from a branch on the remote repository to a branch in the local repository and Git can't do a fast-forward merge, then it refuses to do the update. However, you can use the --force (-f) option to force Git to do the update. Of course, you should only do this if you understand the consequences and are sure you want to do it.

To illustrate this, I'll again use an example of trying to fetch updates from one branch into a completely different branch. Attempting this when Git can't do a fast-forward merge between the two branches results in an error message like this:

$ git pull origin features:docs
From https://github.com/brentlaster/calc2
 ! [rejected]        features   -> docs  (non-fast-forward)

You can use the -f option to force the update to happen.

$ git pull -f origin features:docs
From https://github.com/brentlaster/calc2
 + abaf124…b372aa6 features   -> docs  (forced update)

As an optional method of forcing the update, you can supply a plus (+) sign in front of the branches.

$ git pull origin +features:docs
From https://github.com/brentlaster/calc2
 + abaf124…b372aa6 features   -> docs  (forced update)

The -f (or --force) option is an option to fetch, and it applies to all branches that are passed to the command. You can use the plus (+) sign to specify forcing any particular branch if you don't want to do all of them.

SUMMARY

In this chapter, I switched to the other side of the Git environment: remotes. I clarified what the term remote can mean in Git—from an alias for the longer URL path to the remote repository, to the remote repository itself.

I talked about the various networking protocols (SSH, Git, Local, and HTTP—both smart and dumb) that you can use with Git to communicate between the local and remote sides.

I described in more detail how you clone down a copy of the remote repository to create a new local environment. I also explored some of the options that are available to limit or otherwise modify the set of content that you clone.

I discussed the differences between remote branches, remote tracking branches, and local branches, and I described how these branches work in practice. I showed you how to get a list of these branches and how Git tells you if you are behind or ahead locally, compared to the remote.

Finally, I discussed the main operations that you use to interact with the remote side: push, fetch, and pull. I explored how each of these operations affects the local environment, as well as some of the key options that you are likely to use with each one.

In the next chapter, I continue discussing remotes by looking at the typical workflow you can use with remotes, and exploring an alternative workflow that is popular for hosted sites.

About Connected Lab 8: Setting Up a GitHub Account and Cloning a Repository

This lab is intended to give you some practice in working with remotes. To do this, you'll set up a GitHub repository and see how some of the basic concepts such as forking, cloning, and adding remote references work in practice.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset