Git 2.13 has been released

The open source Git project has just released Git 2.13.0, with features and bugfixes from over 65 contributors. Before we dig into the new features, we have a brief security…

|
| 8 minutes

The open source Git project has just released Git 2.13.0, with features and bugfixes from over 65 contributors. Before we dig into the new features, we have a brief security announcement.

For those running their own Git hosting server, Git 2.13 fixes a vulnerability in the git shell program in which an untrusted Git user can potentially run shell commands on a remote host. This only affects you if you’re running a hosting server and have specifically configured git shell. If none of that makes sense to you, you’re probably fine. See this announcement for more details. As neither GitHub.com nor GitHub Enterprise uses git shell, both are unaffected.

Phew. With that out of the way, let’s get on to the fun stuff.

SHA-1 collision detection

Did I say fun? Oops, we’re not there yet.

You may have heard that researchers recently found the first collision in SHA-1, the hash function Git uses to identify objects. Their techniques may eventually be used to conduct collision-based attacks against Git users. Fortunately those same researchers also provided a way to detect content that is trying to exploit this technique to create collisions. In March, GitHub.com began using that implementation to prevent it being used as a potential platform for conducting collision attacks.

Git 2.13 ships with similar changes, and will detect and reject any objects that show signs of being part of a collision attack. The collision-detecting SHA-1 implementation is now the default. The code is included with Git, so there’s no need to install any additional dependencies. Note that this implementation is slower than the alternatives, but in practice this has a negligible effect on the overall time of most Git operations (because Git spends only a small portion of its time computing SHA-1 hashes in the first place).

In other collision detection news, efforts have continued to develop a transition plan and to prepare the code base for handling new hash functions, which will eventually allow the use of stronger hash algorithms in Git.

[collision detection, SHA-1 transition 1, SHA-1 transition 2]

More convenient pathspecs

You’ve probably passed path arguments to Git before, like:

$ git log foo.c
$ git grep my_pattern program.rb

But you may not have known that to Git, the foo.c and program.rb arguments are actually pathspecs, a Git-specific pattern for matching paths. Pathspecs can be literal paths, prefixes, or wildcards:

$ git log Documentation/      # Everything under the Documentation/ directory
$ git log '*.c'               # C files anywhere in the tree

But they also have a powerful extension syntax. Pathspecs starting with :(magic) enable special matching features. The complete list can be found in the pathspec section of git help glossary, but let’s look at a few here.

For instance, you may want to exclude some files from a grep, which you can do with the :(exclude) directive:

$ git grep this.is.a src
src/foo.c:this is a C file
src/foo.rb:this is a ruby file

$ git grep this.is.a -- src ':(exclude)*.c'
src/foo.rb:this is a ruby file

There are a few things to note in that example. The first is that we had to put our pathspec after a -- (double-dash) separator. This is necessary because most Git commands actually take a combination of revisions and pathspecs. The full syntax is [<revisions>] -- [<pathspecs>]. If you omit the double-dash, Git will check each argument to see if it’s either a valid object name or a file in the filesystem. But since our exclude pattern is neither, without the double-dash Git would give up and complain (this may change in a future version of Git; wildcards like *.c used to have the same problem, but the rules were recently loosened to resolve them as pathspecs). More information is available via git help cli.

The second thing to note is that typing :(exclude) is a pain, and we have to quote it from the shell. But there’s a solution for that: short form pathspec magic. The short form for exclude is ! (exclamation point). This is easy to remember, since it matches the syntax in other parts of Git, like .gitignore files.

$ git grep this.is.a -- src ':!*.c'
src/foo.rb:this is a ruby file

That’s shorter than exclude, but we still have to quote, since the exclamation point triggers history expansion in most shells. Git 2.13 adds ^ (caret) as a synonym for the exclamation point, letting you do the same thing without any shell quoting:

$ git grep this.is.a -- src :^*.c
src/foo.rb:this is a ruby file

Ah, much better. Technically we would need to also quote the *.c wildcard from the shell, but in practice it works out. Unless you have a file that starts with :^ and ends in .c, the shell will realize that the wildcard matches nothing and pass it through to Git verbatim.

But wait, there’s more! Git 2.13 also adds the attr token, which lets you select files based on their gitattributes values. For instance, if you use Git LFS, you may want to get a list of files which have been configured to use it:

$ git ls-files
.gitattributes
README
video.mp4

$ git ls-files ':(attr:filter=lfs)'
video.mp4

You can even define your own attributes in order to group files. Let’s say you frequently want to grep a certain set of files. You can define an attribute, and then select those files using that attribute:

$ echo 'libfoo/ vendored' >>.gitattributes
$ echo 'imported-tool/ vendored' >>.gitattributes
$ git grep -i license -- ':(attr:vendored)'

And if you want to get really fancy, you can combine the attr and exclude tokens:

$ git grep foobar -- ':(exclude,attr:vendored)'

Note that the attr token is not yet supported in all parts of the code. Some commands may report that it cannot be used with them, but this is likely to be expanded in future versions of Git.

[negative pathspecs, attribute pathspecs]

Conditional configuration

Git’s configuration system has several levels of priority: you can specify options at the system level, the user level, the repository level, or for an individual command invocation (using git -c). In general, an option found in a more specific location overrides the same option found in a less specific one. Setting user.email in a repository’s .git/config file will override the user-level version you may have set in ~/.gitconfig.

But what if you need to set an option to one value for a group of repositories, and to another value for a different group? For example, you may use one name and email address when making commits for your day job and another when working on open source. You can set the open source identity in the user-level config in your home directory and then override it in the work repositories. But that’s tedious to keep up to date, and if you ever forget to configure a new work repository, you’ll accidentally make commits with the wrong identity!

Git 2.13 introduces conditional configuration includes. For now, the only supported condition is matching the filesystem path of the repository, but that’s exactly what we need in this case. You can configure two conditional includes in your home directory’s ~/.gitconfig file:

[includeIf "gitdir:~/work/"]
  path = .gitconfig-work
[includeIf "gitdir:~/play/"]
  path = .gitconfig-play

Now you can put whatever options you want into those files:

$ cat ~/.gitconfig-work
[user]
name = Serious Q. Programmer
email = serious.programmer@business.example.com

$ cat ~/.gitconfig-play
[user]
name = Random J. Hacker
email = rmsfan1979@example.com

The appropriate config options will be applied automatically whenever you’re in a repository that’s inside your work or play directories.

[conditional includes]

Bits and bobs

  • --decorate=auto is now the default for git log. When output is sent to the user’s terminal, commits that are pointed to directly by a branch or tag will be “decorated” with the name of the branch. [source]

  • git branch‘s output routines have been ported to the ref-filter system shared by git for-each-ref and git tag. This means you can now use git branch --format= to get custom output. See git help for-each-ref for the list of substitutions. As a side note, these patches are from Karthik Nayak, Git’s Google Summer of Code student from 2015. Though his GSoC project to introduce ref-filter was completed almost two years ago, he’s continued contributing to the project. Great work! [source]

  • git branch, git tag, and git for-each-ref all learned the --no-contains option to match their existing --contains option. This can let you ask which tags or branches don’t have a particular bug (or bugfix). [source]

  • git stash now accepts pathspecs. You can use this to create a stash of part of your working tree, which is handy when picking apart changes to turn into clean commits. [source]

  • The special branch names @{upstream}, @{u}, and @{push} are now case-insensitive. This is especially convenient as both @ and { require holding down the shift key on most keyboards, making it easy to accidentally type a capital U. Now you can hold that shift key AS LONG AS YOU WANT. [source]

  • More commands have learned to recurse into submodules in the past few versions of Git, including checkout, grep, and ls-files. git status --short also now reports more information about submodules. [source, source, source, source]

  • The last few versions of Git have cleaned up many corner cases around repository discovery and initialization. As a final step in that work, Git 2.13 introduced a new assertion to catch any cases that were missed. After being tested for months in development versions, this shouldn’t trigger. But it’s possible that you may see BUG: setup_git_env called without repository. If you do, please consider making a bug report. [source]

The whole kit and caboodle

That’s just a sampling of the changes in Git 2.13, which contains over 700 commits. Check out the the full release notes for the complete list.

Written by

Related posts