Interacting with branches in remote repositories

We see that having many branches in a single repository is very useful. Easy branching and merging allows for powerful development models, which are utilizing advanced branching techniques, such as topic branches. This means that remote repositories will also contain many branches. Therefore, we have to go beyond just the repository to the repository interaction, which was described in Chapter 5, Collaborative Development with Git. We have to consider how to interact with multiple branches in the remote repositories.

We also need to think about how many local branches in our repository relate to the branches in the remote repositories (or, in general, other refs). The other important knowledge is how the tags in the local repository relate to the tags in other repositories.

Understanding the interaction between repositories, the branches in these repositories, and how merge changes (in Chapter 7, Merging Changes Together) is required to truly master collaboration with Git.

Upstream and downstream

In software development, the upstream refers to a direction toward the original authors or the maintainers of the project. We can say that the repository is upstream from us if it is closer (in the repository-to-repository steps) to the blessed repository—the canonical source of the software. If a change (a patch or a commit) is accepted upstream, it will be included either immediately or in a future release of an application, and all the people downstream would receive it.

Similarly, we can say that a given branch in a remote repository (the maintainer repository) is an upstream branch for given local branch, if changes in that local branch are to be ultimately merged and included in the remote branch.

Note

A quick reminder: the upstream repository and the upstream branch in the said remote repository for a given branch are defined, respectively, by the branch.<branchname>.remote and branch.<branchname>.merge configuration variables. The upstream branch can be referred to with the @{upstream} or @{u} shortcut.

The upstream is set while creating a branch out of the remote-tracking branch, and it can be modified using either git branch --set-upstream-to or git push --set-upstream.

The upstream branch does not need to be a branch in the remote repository. It can be a local branch, though we usually say then that it is a tracked branch rather than saying that it is an upstream one. This feature can be useful when one local branch is based on another local branch, for example, when a topic branch is forked from other topic branch (because it contains the feature that is a prerequisite for the latter work).

Remote-tracking branches and refspec

While collaborating on a project, you would be interacting with many repositories (see the Collaborative Development With Git section of this chapter). Each of these remote (public) repositories you are interacting with will have their own notion of the position of the branches. For example, the master branch in the remote repository origin needs not to be at the same place as your own local master branch in your clone of the repository. In other words, they need not point to the same commit in the DAG of revisions.

Remote-tracking branches

To be able to check the integration status, for example, what changes are there in the origin remote repository that are not yet in yours, or what changes did you make in your working repository that you have not yet published, you need to know where the branches in the remote repositories are (well, where they were the last time you contacted these repositories). This is the task of remote-tracking branches—the references that track where the branch was in the remote repository.

Remote-tracking branches

Fig 5: Remote-tracking branches. The branch master in remote origin is fetched into the remote-tracking branch origin/master (full name refs/remotes/origin/master). Grayed out text in the fetch command denotes the default implicit parameters.

To track what happens in the remote repository, remote-tracking branches are updated automatically; this means that you cannot create new local commits on top of them (as you would lose these commits during update). You need to create the local branch for it. This can be done, for example, with simply git checkout <branchname>, assuming that the local branch with the given name does not already exist. This command creates a new local branch out of the remote branch <branchname> and sets the upstream information for it.

Refspec – remote to local branch mapping specification

As described in Chapter 2, Exploring Project History, local branches are in the refs/heads/ namespace, while remote-tracking branches for a given remote are in the refs/remotes/<remote name>/ namespace. But that's just the default. The fetch (and push) lines in the remote.<remote name> configuration describe the mapping between branches (or refs in general) in the remote repository and the remote-tracking branches (or other refs) in the local repository.

This mapping is called refspec; it can be either explicit, mapping branches one by one, or globbing, describing a mapping pattern.

For example, the default mapping for the origin repository is:

[remote "origin"]
  fetch = +refs/heads/*:refs/remotes/origin/*

This says that, for example, the contents of the master branch (whose full name is refs/heads/master) in the remote repository origin is to be stored in the local clone of repository in the remote-tracking branch origin/master (whose full name is refs/remotes/origin/master). The plus + sign at the beginning of the pattern tells Git to accept the updates to the remote-tracking branch that are not fast-forward, that is, are not descendants of the previous value.

The mapping can be given using the fetch lines in the configuration for the remote, as above, or can be also passed as arguments to a command (it is often enough to specify just the short name of the reference instead of the full refspec). The configuration is taken into account only if there are no refspecs on the command line.

Fetching and pulling versus pushing

Sending changes (publishing) to the remote repository is done with git push, while getting changes from it is done with git fetch. These commands send changes in the opposite direction. You should remember, however, that your local repository has the very important difference—it has you sitting at the keyboard available to run other Git commands.

That is why there is no equivalent in the local-to-remote direction to git pull, which combines getting and integrating changes (see the next section). There is simply nobody there to resolve possible conflicts (problems doing automated integration).

In particular, there is a difference between how branches and tags are fetched, and how are they are pushed. This will be explained in detail later on.

Pull – fetch and update current branch

Many times, you want to incorporate changes from a specific branch of a remote repository into the current branch. The pull command downloads changes (running git fetch with parameters given); then, it automatically integrates the retrieved branch head into the current branch. By default, it calls git merge to integrate changes, but you can make it to run git rebase instead The latter can be done either with the --rebase option, or the pull.rebase configuration option to git pull, or with branch.<branch name>.rebase to configure this for the individual branch.

Note that if there is no configuration for the remote (you are doing the pull by URL), Git uses the FETCH_HEAD ref to store tips of the fetched branches.

There is also the git request-pull command to create information about published or pending changes for the pull-based workflows, for example, for a variant of the blessed repository workflow. It creates a plain text equivalent of the GitHub merge requests, one which is particularly suitable to send by e-mail.

Pushing to the current branch in a nonbare remote repository

Usually, the repositories you push to are created for synchronization and are bare, that is, without a working area. A bare repository doesn't even have the concept of the current branch (HEAD)–there is no work tree, therefore, there is no checked out branch.

Sometimes, however, you might want to push to the nonbare repository. This may happen, for example, as a way of synchronizing two repositories, or as a mechanism for deployment (for example, of a web page or a web application). By default, Git on the server (in the nonbare repository you are pushing into) will deny the ref update to the currently checked out branch. This is because it brings HEAD out of sync with the working tree and the staging area, which is very confusing if you don't expect it. You can, however, enable such a push by setting receive.denyCurrentBranch to warn or ignore (changing it from the default value of refuse).

You can even make Git update the working directory (which must be clean, that is, without any uncommitted changes) by setting the said configuration variable to updateInstead.

An alternative and a more flexible solution to using git push for deployment is to configure appropriate hooks on the receiving side—see Chapter 10, Customizing and Extending Git, for information on hooks in general, and Chapter 11, Git Administration, for details on their use on the server.

The default fetch refspec and push modes

We usually fetch from public repositories with all the branches made public. We most often want to get a full update of all the branches. That's why git clone sets up the default fetch refspec in a way shown in the Refspec – remote to local branch mapping specification section of this chapter. The common exception to "fetch all" rule is following a pull request. But in this case, we have the repository and the branch (or the signed tag) stated explicitly in the request, and we will run the pull command with provided parameters: git pull <URL> <branch>.

On the other side, in the private working repository, there are usually many branches that we don't want to publish or, at least, we don't want to publish them yet. In most cases, we would want to publish a single branch: the one we were working on and the one we know is ready. However, if you are the integration manager, you would want to publish a carefully selected subset of the branches instead of just one single branch.

This is yet another difference between fetching and pushing. That's why Git doesn't set up push refspec by default (you can configure it manually nonetheless), but instead relies on the so-called push modes (configured using push.default) to decide what should be pushed where. This configuration variable, of course, applies only while running the git push command without branches to push stated explicitly on the command line.

Tip

Using git push to sync out of a host that one cannot pull from

When you work on two machines, machineA and machineB, each with its own work tree, a typical way to synchronize between them is to run git pull from each other. However, in certain situations, you may be able to make the connection only in one direction, but not in the other (for example, because of a firewall or intermittent connectivity). Let's assume that you can fetch and push from machineB, but you cannot fetch from machineA.

You want to perform push from machineB to machineA in such way, that the result of the operation is practically indistinguishable from doing fetch while being on machineA. For this you need to specify, via refspec, that you want to push local branch into its remote-tracking branch.

machineB$ git push machineA:repo.git 
   refs/heads/master:refs/remotes/machineB/master

The first parameter is the URL in the scp-like syntax, the second parameter is refspec. Note that you can set these all up in the config file in case you need to do something like this more often.

Fetching and pushing branches and tags

The next section will describe which push modes are available and when to use them (for which collaboration workflows). But first, we need to know how Git behaves with respect to tags and branches while interacting with remote repositories.

Because, pushing is not the exact opposite of fetching, and because branches and tags have different objectives (branches point to the lines of development and tags name specific revisions), their behavior is subtly different.

Fetching branches

Fetching branches is quite simple. With the default configuration, the git fetch command downloads changes and updates remote-tracking branches (if possible). The latter is done according to the fetch refspec for the remote.

There are, of course, exceptions to this rule. One such exception is mirroring the repository. In this case all the refs from the remote repository are stored under the same name in the local repository. The git clone --mirror would generate the following configuration for origin:

[remote "origin"]
  url = https://git.example.com/project
  fetch = +refs/*:refs/*
  mirror = true

The names of refs that are fetched, together with the object names they point at, are written to the .git/FETCH_HEAD file. This information is used, for example, by git pull; this is necessary if we are fetching via URL and not via a remote name. It is done because, when we fetch by the URL, there are simply no remote-tracking branches to store the information on the fetched branch to be integrated.

You can delete remote-tracking branches on case by case basis with git branch -r -d; you can remove on case by case basis remote-tracking branches for which the corresponding branch in the remote repository no longer exists with git remote prune (or in modern Git with git fetch --prune).

Fetching tags and automatic tag following

The situation with tags is a bit different. While we would want to make it possible for different developers to work independently on the same branch (for example, an integration branch such as master), though in different repositories, we would need all developers to have one specific tag to always refer to the same specific revision. That's why the position of branches in remote repositories is stored using a separate per-remote namespace refs/remotes/<remote name>/* in remote-tracking branches, but tags are mirrored—each tag is stored with the same name, in refs/tags/* namespace.

Note

Though where the positions of tags in the remote repository are stored can, of course, be configured with the appropriate fetch refspec; Git is that flexible. One example where it might be necessary is the fetching of a subproject, where we want to store its tags in a separate namespace (more information on this issue in Chapter 9, Managing Subprojects - Building a Living Framework).

This is also why, by default, while downloading changes, Git would also fetch and store locally all the tags that point to the downloaded objects. You can disable this automatic tag following with the --no-tags option. This option can be set on the command line as a parameter, or it can be configured with the remote.<remote name>.tagopt setting.

You can also make Git download all the tags with the --tags option, or by adding the appropriate fetch refspec value for tags:

fetch = +refs/tags/*:refs/tags/*

Pushing branches and tags

Pushing is different. Pushing branches are (usually) governed by the selected push mode. You push a local branch (usually just a single current branch) to update a specific branch in the remote repository, from refs/heads/ locally to refs/heads/ in remote. It is usually a branch with the same name, but it might be a differently named branch configured as upstream—details will be provided later. You don't need to specify the full refspec: using the ref name (for example, name of a branch) means pushing to the ref with the same name in the remote repository, creating it if it does not exist. Pushing HEAD means pushing the current branch into the branch with the same name (not to the HEAD in remote—it usually does not exist).

Usually, you push tags explicitly with git push <remote repository> <tag> (or tag <tag> if by accident there is both a tag and branch with the same name—both mean the +refs/tags/<tag>:refs/tags/<tag> refspec). You can push all the tags with --tags (and with appropriate refspec), and turn on the automatic tag following with --follow-tags (it is not turned on by default as it is for fetch).

As a special case of refspec, pushing an "empty" source into some ref in remote deletes it. The --delete option to git push is just a shortcut for using this type of refspec. For example, to delete a ref matching experimental in the remote repository, you can run:

$ git push origin :experimental

Note that the remote server might forbid the deletion of refs with receive.denyDeletes or hooks.

Push modes and their use

The behavior of git push, in the absence of the parameters specifying what to push, and in the absence of the configured push refspec, is specified by the push mode. Different modes are available, each suitable for different collaborative workflows from Chapter 5, Collaborative Development with Git.

The simple push mode – the default

The default push mode in Git 2.0 and later is the so-called simple mode. It was designed with the idea of minimum surprise: the idea that it is better to prevent publishing a branch, than to make some private changes accidentally public.

With this mode, you always push the current local branch into the same named branch in the remote repository. If you push into the same repository you fetch from (the centralized workflow), it requires the upstream to be set for the current branch. The upstream is named the same as the branch.

This means that, in the centralized workflow (push into the same repository you fetch from), it works like upstream with the additional safety that the upstream must have the same name as the current (pushed) branch. With triangular workflow, while pushing to a remote that is different from the remote you normally pull from, it works like current.

This is the safest option; it is well-suited for beginners, which is why it is the default mode. You can turn it on explicitly with git config push.default simple.

The matching push mode for maintainers

Before version 2.0 of Git, the default push mode was matching. This mode is most useful for the maintainer (also known as the integration manager) in a blessed repository workflow. But most of the Git users are not maintainers; that's why the default push mode was changed to simple.

The maintainer would get contributions from other developers, be it via pull request or patches sent in an e-mail, and put them into topic branches. He or she could also create topic branches for their own contributions. Then, the topic branches considered to be suitable are merged into the appropriate integration branches (for example, maint, master, and next) – merging will be covered in Chapter 7, Merging Changes Together. All this is done in the maintainer's private repository.

The public blessed repository (one that everyone fetches from, as described in Chapter 5, Collaborative Development with Git) should contain only long-running branches (otherwise, other developers could start basing their work on a branch that suddenly vanishes). Git cannot know by itself which branches are long-lived and which are short-lived.

With the matching mode, Git will push all the local branches that have their equivalent with the same name in the remote repository. This means that only the branches that are already published will be pushed to the remote repository. To make a new branch public you need to push it explicitly the first time, for example:

$ git push origin maint-1.4

Note

Note that with this mode, unlike with other modes, using git push command without providing list of branches to push can publish multiple branches at once, and may not publish the current branch.

To turn on the matching mode globally, you can run:

$ git config push.default matching

If you want to turn it on for a specific repository, you need to use a special refspec composed of a sole colon. Assuming that the said repository is named origin and that we want a not forced push, it can be done with:

$ git config remote.origin push :

You can, of course, push matching branches using this refspec on the command line:

$ git push origin :

The upstream push mode for the centralized workflow

In the centralized workflow, there is the single shared central repository every developer with commit access pushes to. This shared repository will have only long-lived integration branches, usually only maint and master, and sometimes only master.

One should rather never work directly on master (perhaps with the exception of simple single-commit topics), but rather fork a topic branch for each separate feature out of the remote-tracking branch:

$ git checkout -b feature-foo origin/master

In the centralized workflow, the integration is distributed: each developer is responsible for merging changes (in their topic branches), and publishing the result to the master branch in the central repository. You would need to update the local master branch, merge the topic branch to it, and push it:

$ git checkout master
$ git pull
$ git merge feature-foo
$ git push origin master

An alternate solution is to rebase the topic branch on the top of the remote-tracking branch, rather than merging it. After rebasing, the topic branch should be an ancestor of master in the remote repository, so we can simply push it into master:

$ git checkout feature-foo
$ git pull --rebase
$ git push origin feature-foo:master

In both the cases, you are pushing the local branch (master in the merge-based workflow, the feature branch in the rebase-based workflow) into the branch it tracks in the remote repository; in this case, origin's master.

That is what the upstream push mode was created for:

$ git config push.default upstream

This mode makes Git push the current branch to the specific branch in the remote repository—the branch whose changes are usually integrated into the current branch. This branch in the remote repository is the upstream branch (and can be referenced as @{upstream}). Turning this mode on makes it possible to simplify the last command in both examples to the following:

$ git push

The information about the upstream is created either automatically (while forking off the remote-tracking branch), or explicitly with the --track option. It is stored in the configuration file and it can be edited with ordinary configuration tools. Alternatively, it can be changed later with the following:

$ git branch --set-upstream-to=<branchname>

The current push mode for the blessed repository workflow

In the blessed repository workflow, each developer has his or her own private and public repository. In this model, one fetches from the blessed repository and pushes to his or her own public repository.

In this workflow, you start working on a feature by creating a new topic branch for it:

$ git checkout -b fix-tty-bug origin/master

When the features are ready, you push it into your public repository, perhaps rebasing it first to make it easier for the maintainer to merge it:

$ git push origin fix-tty-bug

Here, it is assumed that you used pushurl to configure the triangular workflow, and the push remote is origin. You would need to replace origin here with the appropriate name of the publishing remote if you are using a separate remote for your own public repository (using a separate repository makes it possible to use it not only for publishing, but also for synchronization between different machines).

To configure Git so when on fix-tty-bug branch it is enough to just run git push, you need to set up Git to use the current push mode, which can be done with the following:

$ git config push.default current

This mode will push the current branch to the branch with the same name at the receiving end.

Note that, if you are using a separate remote for the publishing repository, you would need to set up the remote.pushDefault configuration option to be able to use just git push for publishing.

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

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