
Announcing the GitHub Innovation Graph
Explore a universe of data about how the world is building software together on GitHub.
A look at some of the new features in the latest Git release.
The open source Git project just released Git 2.22 with features and bug fixes brought to you from over 74 contributors, 18 of them new. Here’s our look at some of the most exciting features and changes introduced since Git 2.21.
You might have used git rebase
before to alter the history of your repository. If you haven’t, here’s a quick primer: git rebase
replays a series of commits on a new initial commit. For example, you might have used git rebase
to make sure that your feature branch was based on the latest changes from upstream. Say that you have a repository structure that looks like:
o (my-feature)
/
o --- o --- o (master)
Let’s say that while you were working on my-feature
, the branch you started from (master
) changed, so now your repository looks like:
o --- o --- o (my-feature)
/
o --- o --- o --- o --- o (master)
How do you make sure that your branch my-feature
merges into master
cleanly? You could just merge it, but the end result might be hard for others to understand if you have to resolve conflicts. If you haven’t yet shared the branch with reviewers, they might prefer to see your commits as if they had been written directly on top of the current master.
So, you might rebase it. Remember: rebasing is an operation that takes a series of commits and applies them on top of a new base. So, after rebasing my-feature
on the latest from master
, you’ll instead get:
o --- o --- o (my-feature)
/
o --- o --- o --- o --- o (master)
All that git rebase
did was take the first “new” commit in (my-feature), applied it on top of the new tip of master, and then so on through all of the remaining commits on my-feature
in order, until there were none left.
Now, what if your example is more complicated? Let’s say that my-feature
has its own branching structure that you want to preserve while replaying the commits. To further complicate things, let’s also assume that you want to use some of rebase
‘s interactive features, like dropping, reordering, and renaming commits.
If you didn’t mind giving up those interactive features, you could have used --preserve-merges
. You can use git rebase -i --preserve-merges
and edit the history interactively, but some of your repository’s structure might not remain intact.
Git has a new option --rebase-merges
, since 2.18, but in 2.22 the old option is officially deprecated in favor of --rebase-merges
. Using --rebase-merges
allows you to preserve the structure of your changes, while also giving you the full power of interactivity.
Here’s an example. Let’s say that you have a branching structure based on master
, but upstream (say, origin/master
) has changed since you created your branch. You want to replay your commits on the latest from upstream, preserve the branching structure, and make a few modifications to a commit message along the way (we’ll simulate this by fixing a typo).
In Git 2.22, this is what that might look like:
[source]
Given some set of two or more branches, how can you tell what history they have in common? As it turns out, Git has a precise way to answer this question. Git calls this a merge base: the most recent common ancestor among a set of commits.
When might you want to compute a merge base in practice? The obvious answer is: when you’re merging! Git computes this common ancestor as the base for a three-way merge of the content (hence the name “merge base”). But you might also want to use this common ancestor as a cutoff point for listing commits. Running git log A...B
will show all of the commits in A
and B
down to their common ancestor; in other words, what happened since the two diverged (you can also use --left-right
to see which commit is on which side).
This “triple-dot” notation is associated with merge bases in other contexts, too. Running git diff A...B
will show the differences between B
and the merge base of A
and B
. That’s another way of showing what has happened on B
.
What’s another instance that you might want to use a merge-base? Say that you’re working on a feature branch, and you decide that part of the way through, you’d like to start over on a different branch. Let’s also say that you’d like to start at the same place on master
that your existing feature branch was cut from.
How do you create such a thing? Well, you could manually inspect the git log
, but this might be cumbersome if your history is particularly large. You could invoke:
$ git merge-base master my-feature
to compute the SHA-1 of the merge base between master
and my-feature
, copy the SHA-1 you got back, and then paste it into:
$ git branch my-other-feature <sha-1>
Now in Git 2.22, git branch
and git checkout -b
have both learned the triple-dot merge base syntax. To specify that you’d like to create a branch from the merge base of two other branches (say, A
and B
), you can now run:
$ git branch my-other-feature A...B
# or...
$ git checkout -b my-other-feature A...B
Here’s an example:
[source]
git symbolic-ref
(if you were aware of its existence), or hacked up the output of git branch
with a combination of grep
and awk
, but both seem like rather unfulfilling options.git branch --show-current
to get the name of the branch you currently have checked out. (If you want to use this output in a machine-readable setting, git symbolic-ref
is still preferred, though.) [source]dir/
directory as it was on master
. To do this, you might run git checkout master -- dir
. But what if one of the files, dir/file.txt
, wasn’t present at all on master
? What does it mean to “go back” to that state?master
into your copy of dir/
. It will copy any contents that are present in master
into your working tree, but won’t delete tracked files that master
doesn’t have. The result is a combination of the two: what you had before and what was in master
.dir/
into the exact set of contents from master
, both adding and deleting as appropriate. In Git 2.22, there’s now a way to express that: git checkout --no-overlay -- dir
. The default behavior remains unchanged (i.e., the same as passing --overlay
). [source]git diff --function-context
(to show the function context nearest each hunk), it also will accept git diff --no-function-context
. This is a result of using Git’s own parse-options
API, which is used across many sub-commands to ensure that command-line options are parsed consistently.git diff
command was written prior to the parse-options
API, so it had its own hand-crafted parser. In Git 2.22, git diff
now uses the parse-options
API, meaning you should expect more consistent command-line options parsing in more parts of Git. [source]git log
‘s --format
option has allowed you to list those trailers as part of a custom format.$ git log --pretty="%(trailers:key=Reviewed-by,valueonly)" | grep '.' | sort | uniq -c | sort -rn | head -5
[source]
git bisect
tries to show you a pretty version of the commit. But ever since it was introduced in 2005, bisect
has used the diff-tree
plumbing to show you the commit, which is a long way from “pretty”. By default, it shows you only the top-level of the tree. So you might find out that the commit in question changed the src/
directory. Not helpful. It also shows the machine-readable --raw
diff format, giving you only the before and after hashes, with no clue as to what actually changed. And for merge commits, it shows nothing at all!bisect
will show a full --stat
diff, summarizing the changes to each file by line count (for merges, this counts the differences that were brought in by the merge). It stops short of showing the full content-level diff, but you can view that yourself with git show
. [source]v2.22.0
tag points to this commit.$ git tag -f -m "updated message" <my-tag> <my-tag>
Which you may have written if you meant “update <my-tag>
with this message and leave the thing it points to unchanged”. But, that invocation creates a new tag which points at the old tag, when you most likely meant to point it at the thing that the old tag points at.To help prevent you from making this mistake, Git now warns you when you create a tag pointing to another tag. [source]
That’s just a sampling of changes from the latest version. Read the full release notes for 2.22, or find the release notes for previous versions in the Git repository.