Git v2.0.0, what changed, and why should you care

Git v2.0.0 is a backward-incompatible release, which means you should expect differences since the v1.x series.

Unless you’ve been following closely the Git mailing list, you probably don’t know the history behind the v2.0 release, which started long time ago (more than three years). It all started with a mail from Junio C Hamano, asking for developers to submit ideas for changes that normally would not happen because they break backwards compatibility, he invited us to think as if “we were writing Git from scratch”. This big release that would break backwards compatibility was going to be named “1.8.0” and people started to submit ideas for this important release. Eventually too much time passed, the versioning scheme changed, v1.8.0 was released, and the changes proposed for v1.8. slipped into what is now v2.0.

Parts of v2.0 have been already been deployed one way or the other (for example if you have configured ‘push.default = simple’), but finally today we have v2.0 final. And here are the big changes that we got.

‘git push’ default has changed

Here’s what the release notes say:

When "git push [$there]" does not say what to push, we have used the
traditional "matching" semantics so far (all your branches were sent
to the remote as long as there already are branches of the same name
over there).  In Git 2.0, the default is now the "simple" semantics,
which pushes:

 - only the current branch to the branch with the same name, and only
   when the current branch is set to integrate with that remote
   branch, if you are pushing to the same remote as you fetch from; or

 - only the current branch to the branch with the same name, if you
   are pushing to a remote that is not where you usually fetch from.

You can use the configuration variable "push.default" to change
this.  If you are an old-timer who wants to keep using the
"matching" semantics, you can set the variable to "matching", for
example.  Read the documentation for other possibilities.

Is that clear? Given the bad track record of Git documentation it wouldn’t surprise me if you didn’t get what this chunk of text is trying to say at all. Personally I find it much easier to read the code to figure out what is happening.

So let me try to explain. When you type ‘git push’ (without any arguments), Git uses the configuration ‘push.default’ in order to find out what to push. Before ‘push.default’ defaulted to ‘matching’, and now it defaults to ‘simple’.

The ‘matching’ configuration essentially converts ‘git push‘ into ‘git push origin :‘, which means push all the matching branches, so if you have a local ‘master’, and there’s a remote ‘master’, ‘master’ is pushed; if you have a local and remote ‘fix-1’, ‘fix-1’ is pushed, if you have a local ‘ext-feature-1’, but there’s no matching remote branch, it’s not pushed, and so on.

The ‘simple’ configuration pushes a single branch instead, and it uses your configured upstream branch (see this post for a full explanation of the upstream branch), so if your current branch is ‘master’, and if ‘origin/master’ is the upstream of your ‘master’ branch, ‘git push’ will basically be the same as ‘git push origin master‘, or to be more specific ‘git push origin master:master‘ (the upstream branch can have a different name).

Note: If you are not familiar with the src:dst syntax; you can push a local branch ‘src’ and have the ‘dst’ name on the server, so you don’t need to rename a local branch, you can do ‘git push origin foobar:feature-a’, and your local branch “foobar” will be named “feature-a” on the server. This has nothing to do with v2.0.

However, if the current branch is ‘fix-1’ and the upstream is ‘origin/master’, ‘git push’ will complain that the name of the destination branch is not the same, because it doesn’t know if to do ‘git push origin fix-1:master‘ or ‘git push origin fix-1:fix-1‘.

Additionally if you do ‘git push github‘ (not the remote of your upstream branch), Git will simply use the name of the current branch, essentially ‘git push github fix-1‘ (‘fix-1’ being the name of the current branch).

This mode is anything but simple to describe. But perhaps the name is OK, because you can expect it to “simply work”.

Would I care?

If you don’t type ‘git push’, but instead specify what and where to push… you don’t care.

If you have configured ‘push.default’ already, which most likely you already did, because otherwise you will be getting the following annoying message all the time since two years ago… you don’t care.

warning: push.default is unset; its implicit value is changing in
Git 2.0 from 'matching' to 'simple'. To squelch this message
and maintain the current behavior after the default changes, use:

  git config --global push.default matching

To squelch this message and adopt the new behavior now, use:

  git config --global push.default simple

When push.default is set to 'matching', git will push local branches
to the remote branches that already exist with the same name.

In Git 2.0, Git will default to the more conservative 'simple'
behavior, which only pushes the current branch to the corresponding
remote branch that 'git pull' uses to update the current branch.

See 'git help config' and search for 'push.default' for further information.
(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode
'current' instead of 'simple' if you sometimes use older versions of Git)

So, most likely you don’t care.

‘git add’ in directory

Here’s what the release notes say:

When "git add -u" and "git add -A" are run inside a subdirectory
without specifying which paths to add on the command line, they
operate on the entire tree for consistency with "git commit -a" and
other commands (these commands used to operate only on the current
subdirectory).  Say "git add -u ." or "git add -A ." if you want to
limit the operation to the current directory.

Although this is a clearer explanation, it’s not very clear what is changing, so let me give you can example.

Say you have modified two files, ‘README’ and ‘test/basic.t’, then you go to the ‘test’ directory, and run ‘git add -u‘, in pre-v2.0 only ‘test/basic.t’ will be staged, in post-v2.0 both files will be staged. If you run the command in the top level directory, nothing changes.

Would I care?

If you haven’t seen the following warning while doing ‘git add -u‘ or ‘git add -A‘, or if you don’t even use those options, you are fine.

warning: The behavior of 'git add --update (or -u)' with no path argument from a
subdirectory of the tree will change in Git 2.0 and should not be used anymore.
To add content for the whole tree, run:

  git add --update :/
  (or git add -u :/)

To restrict the command to the current directory, run:

  git add --update .
  (or git add -u .)

With the current Git version, the command is restricted to the current directory.

‘git add’ adds removals

Here’s what the release notes say:

"git add " is the same as "git add -A " now, so that
"git add dir/" will notice paths you removed from the directory and
record the removal.  In older versions of Git, "git add " used
to ignore removals.  You can say "git add --ignore-removal " to
add only added or modified paths in , if you really want to.

Again, it should be clearer with an example. Say you removed the file ‘test/basic.t’ and added a new file ‘test/main.t’, those changes are not staged, so you stage them with ‘git add test/’, pre-v2.0 ‘test/basic.t’ would remain tracked, post-v2.0, ‘test/basic.t’ is removed from the stage.

Would I care?

If you haven’t seen the following warning while doing ‘git add‘, you are fine.

warning: You ran 'git add' with neither '-A (--all)' or '--ignore-removal',
whose behaviour will change in Git 2.0 with respect to paths you removed.
Paths like 'test/basic.t' that are
removed from your working tree are ignored with this version of Git.

* 'git add --ignore-removal ', which is the current default,
  ignores paths you removed from your working tree.

* 'git add --all ' will let you also record the removals.

Run 'git status' to check the paths you removed from your working tree.

The rest

The "-q" option to "git diff-files", which does *NOT* mean "quiet",
has been removed (it told Git to ignore deletion, which you can do
with "git diff-files --diff-filter=d").

Most people don’t use this command, thus don’t care.

"git request-pull" lost a few "heuristics" that often led to mistakes.

Again, most people don’t use this command, which is mostly broken anyway.

The default prefix for "git svn" has changed in Git 2.0.  For a long
time, "git svn" created its remote-tracking branches directly under
refs/remotes, but it now places them under refs/remotes/origin/ unless
it is told otherwise with its "--prefix" option.

If you don’t use ‘git svn’, you don’t care. If you don’t see a difference between ‘trunk’ and ‘origin/trunk’, you don’t care.

tl;dr

You probably don’t care about these backward-incompatible changes. Sure, Git v2.0.0 received a good dosage of new features and bug-fixes, but so did v1.9.0, and all the versions before.

Given the fact that Git v2.0.0 has been cooking for three years, I think it’s a big missed opportunity that nothing really changed, specially given that in previous user surveys people have said the user-interface and documentation needs to improve, and there have been patches to try to do so. In a separate post I discuss what I think Git v2.0.0 should have included.

15 thoughts on “Git v2.0.0, what changed, and why should you care

  1. True, it is unfortunate that we (the Git Development Community) suck at documentation.

    I would say that `simple` is `upstream` for upstream repository, `current` for other repositories (if I understand it correctly).

    It is usually easier to explain on examples, but Git documentation (especially reference documentation) does not use them enough…

    Like

  2. @Jakub Narębski

    True, it is unfortunate that we (the Git Development Community) suck at documentation.

    It also doesn’t help that when somebody tries to simplify the documentation that person gets his knees shot.

    I would say that `simple` is `upstream` for upstream repository, `current` for other repositories (if I understand it correctly).

    Yes, except that in the case of “upstream” the branch has to have the same name as the upstream branch. And then you have to explain “upstream” and “current”.

    Like

  3. Nice post…really !

    You didn’t mention the “pull.ff” config option.
    It seems to be a workaround for the pull’s issue described in your precedent post: “Is ‘git pull’ broken? If so, what’s the fix?”, isn’t it ?

    Also, why don’t you recommend using the rebase strategy for the pull ?
    History is much more clean this way, no ?

    Like

  4. @Thierry Laude

    You didn’t mention the “pull.ff” config option.

    No, because that’s a new feature, not really something that was different in 1.9 (IOW; not a backwards incompatible change).

    And yes, it partially fixes the problem, but only for those people that set this config up, which means it doesn’t solve the real problem, which is newbies doing “evil merges” by mistake. And then when it errors out, it doesn’t show a properly help message (not user-friendly at all). And then it doesn’t solve the problem when you do actually want a merge, but with the swapped parents.

    Like

  5. I agree but why don’t you enforce a pull/rebase strategy rather than pull/merge with the project policy ?
    With pull requests you can refuse not compliant contributions and if you give permissions rebase is more simple and clean than merge in the same master branch.

    You can also propose feature branches and control integration via the pull request.

    Not sure if it’s a real problem.
    (I mean in a centralised workflow)

    Like

  6. @Thierry Laude Git doesn’t enforce anything; all workflows are supported. Personally I always use topic branches, but that doesn’t mean other workflows should not be supported by Git.

    Like

  7. Pingback: Git v2.0.0 : fate attenzione ….. | The (in)Fluent Coder

  8. Pingback: What’s missing in Git v2.0.0 | Felipe Contreras

  9. With regards to the backwards incompatibility of Git 2.0: does this mean it can not be used with an older server version? Or is it just that the instance of that a repo can not be used with an older version of Git, but other clones that are fine to use older versions? i.e. You clone from GitHub using Git 2.0, someone else clones the same repo using Git 1.9.2, and both that push and pull changes with no issue?

    Like

  10. Backward compatibility between server and client is preserved (client and server negotiate their capabilities). Backward compatibility between clients using the same repository is preserved IIRC (I don’t think the index format has changed). What is “broken” is that commands behave differently that they used to (though for some commands you can configure them to use old behavior).

    Backward compatibility between client and server was broken only twice: once with addition of submodules (old clients do not understand them, if repo uses them), once with packed refs (old client using old “dumb” HTTP protocol won’t understand packed refs).

    Like

  11. Thank you for clearing that up. I’ve been holding off on a Git upgrade, since I couldn’t tell if there were going to be client/server issues.

    Like

  12. I’m about to setup a centralized Git repository in my company.
    Some of the client workstations are on Debian 7 (wheezy) where the latest stable Git version is 1.7.10
    Others client workstations are on Debian 8 (jessy) where the latest stable Git version is 2.1.4
    Considering the backward incompatibility issues listed in this article. Should I prefer a Debian 8 (Git 2.1.4) or Debian 7 (Git 1.7.10) server for my centralized repository ?
    Thanks for your help

    Like

  13. I frequently switch between Git 1.x and Git 2.x on the same project, depending on which tool I’m using at the time (Git for Windows with 2.0, or Github for Desktop with 1.x), with very little issue.

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.