{"id":95374,"date":"2026-04-20T09:43:57","date_gmt":"2026-04-20T16:43:57","guid":{"rendered":"https:\/\/github.blog\/?p=95374"},"modified":"2026-04-27T15:03:23","modified_gmt":"2026-04-27T22:03:23","slug":"highlights-from-git-2-54","status":"publish","type":"post","link":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/","title":{"rendered":"Highlights from Git 2.54"},"content":{"rendered":"<!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.0 Transitional\/\/EN\" \"http:\/\/www.w3.org\/TR\/REC-html40\/loose.dtd\">\n<html><body><p>The open-source Git project just <a href=\"https:\/\/lore.kernel.org\/git\/xmqqa4uxsjrs.fsf@gitster.g\/\">released Git 2.54<\/a> with features and bug fixes from over 137 contributors, 66 of them new. We last caught up with you on the latest in Git back <a href=\"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-52\/\">when 2.52 was released<\/a>.<\/p>\n\n\n\n<p>To celebrate this most recent release, here is GitHub&rsquo;s look at some of the most interesting features and changes introduced since last time.<\/p>\n\n\n\n<p><\/p><blockquote><p>&#128161; Since the last Git release we wrote about was Git 2.52, this blog post covers the highlights from both the 2.53 and 2.54 releases.<\/p><\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-rewrite-history-with-git-history\">Rewrite history with <code>git history<\/code><\/h2>\n\n\n\n<p>The Git project has a long history of providing tools to rewrite your repository&rsquo;s history. <a href=\"https:\/\/git-scm.com\/docs\/git-rebase\/2.54.0\"><code>git rebase &ndash;i<\/code><\/a> is the most well-known, and it&rsquo;s remarkably flexible: you can reorder, squash, edit, and drop commits. But that flexibility comes with complexity: an interactive rebase operates on a range of commits, updates your <a href=\"https:\/\/git-scm.com\/docs\/gitglossary\/2.54.0#def_working_tree\">working tree<\/a> and <a href=\"https:\/\/git-scm.com\/docs\/gitglossary\/2.54.0#def_index\">index<\/a> as it goes, and can leave you in a conflicted state that you need to resolve before proceeding.<\/p>\n\n\n\n<p>For simpler cases, all of that machinery can feel like overkill. If all you want to do is fix a typo in a commit message three commits back, or split one commit into two, an interactive rebase works, but requires you to set up a to-do list, mark the right commit for editing, and then drive the rebase to completion.<\/p>\n\n\n\n<p>Git 2.54 introduces a new experimental command that is designed for exactly these simpler cases: <code><a href=\"https:\/\/git-scm.com\/docs\/git-history\/2.54.0\">git history<\/a><\/code>. The <code>history<\/code> command currently supports two operations: <code>reword<\/code> and <code>split<\/code>.<\/p>\n\n\n\n<p><code>git history reword &lt;commit&gt; <\/code> opens your editor with the specified commit&rsquo;s message and rewrites it in place, updating any branches that descend from that commit. Unlike <code>git rebase<\/code>, it doesn&rsquo;t touch your working tree or index, and it can even operate in a <a href=\"https:\/\/git-scm.com\/docs\/gitglossary\/2.54.0#def_bare_repository\">bare repository<\/a>.<\/p>\n\n\n\n<p><code>git history split &lt;commit&gt;<\/code> lets you interactively split a commit into two by selecting which hunks should be carved out into a new parent commit. The interface will look familiar if you&rsquo;ve ever used <code>add<\/code> in interactive mode via <code>git add &ndash;p<\/code>:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code\"><code>$ git history split HEAD\ndiff --git a\/bar b\/bar\nnew file mode 100644\nindex 0000000..50810a5\n--- \/dev\/null\n+++ b\/bar\n@@ -0,0 +1 @@\n+bar\n(1\/1) Stage addition [y,n,q,a,d,p,?]? y <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"$ git history split HEAD\ndiff --git a\/bar b\/bar\nnew file mode 100644\nindex 0000000..50810a5\n--- \/dev\/null\n+++ b\/bar\n@@ -0,0 +1 @@\n+bar\n(1\/1) Stage addition [y,n,q,a,d,p,?]? y\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p>After selecting hunks, Git creates a new commit with those changes as the parent of the original commit (which retains whatever hunks you didn&rsquo;t select) and rewrites any descendent branches to point at the updated history.<\/p>\n\n\n\n<p>There are a couple of intentional limitations worth noting. The history command does not support histories that contain merge commits, and it will refuse to perform any operation that would result in a merge conflict. By design, <code>git history<\/code> is meant for targeted, non-interactive rewrites, not the kind of open-ended history rewriting typically relegated to <code>git rebase &ndash;i<\/code>.<\/p>\n\n\n\n<p>The history command is built on top of <code>git replay<\/code>&lsquo;s core machinery, which was itself extracted into a library as part of this work. That foundation means that <code>git history<\/code> benefits from <code>replay<\/code>&lsquo;s ability to operate without touching the working tree, making it a natural fit for scripting and automation in addition to interactive use.<\/p>\n\n\n\n<p>This command is still marked as experimental, so its interface may evolve. Give it a try with <code>git history reword<\/code> and <code>git history split<\/code>, available in Git 2.54.<\/p>\n\n\n\n<p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/3e0db84c88c57e70ac8be8c196dfa92c5d656fbc...d205234cb05a5e330c0f7f5b3ea764533a74d69e\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>, <a href=\"https:\/\/github.com\/git\/git\/compare\/aa95f87c740011f7d21555c5ad7f0870faf4b5c8...1278a26544e81dddf564fd7730890a7e023ed367\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>, <a href=\"https:\/\/github.com\/git\/git\/compare\/2eec0f51156ea872174bbd08f355155f381a568e...d1f33c753de68f63c945c3213f439081ed11c27b\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>, <a href=\"https:\/\/github.com\/git\/git\/compare\/dd33e738a469cb7841a4a6132bdce1809d0772aa...d563ecec2845467880f5742e178a9723afef495a\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>, <a href=\"https:\/\/github.com\/git\/git\/compare\/f1743ad69a492d1ca3773bfdddf7f5ffd278c19b...26b9946dd756a2efc29f898e53327676a22adc3e\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-config-based-hooks\">Config-based hooks<\/h2>\n\n\n\n<p>If you&rsquo;ve ever wanted to share a Git <a href=\"https:\/\/git-scm.com\/docs\/githooks\">hook<\/a> across multiple repositories, you&rsquo;ve probably had to reach for a third-party hook manager, or manually symlink scripts into each repository&rsquo;s <code>$GIT_DIR\/hooks<\/code> directory. That&rsquo;s because, historically, Git hooks could only be defined as executable scripts living in one place: the <code>hooks<\/code> subdirectory of your <code>.git<\/code> directory (or whatever <a href=\"https:\/\/git-scm.com\/docs\/git-config\/2.54.0#Documentation\/git-config.txt-corehooksPath\"><code>core.hooksPath<\/code><\/a> points to).<\/p>\n\n\n\n<p>That meant that if you wanted to run a linter before every commit across all of your repositories, you had to copy the script into each repository, which can be tedious and error-prone. Alternatively, you could set <code>core.hooksPath<\/code> to point to a shared directory, but that causes all of your repositories to share the exact same set of hooks, with no way to mix and match.<\/p>\n\n\n\n<p>Git 2.54 introduces a new way to define hooks: in your configuration files. Instead of placing a script at <code>.git\/hooks\/pre-commit<\/code>, you can now write:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code\"><code>[hook \"linter\"]\n   event = pre-commit\n   command = ~\/bin\/linter --cpp20 <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value='[hook \"linter\"]\n   event = pre-commit\n   command = ~\/bin\/linter --cpp20' tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p>The <code>hook.&lt;name&gt;.command<\/code> key specifies the command to run, and <code>hook.&lt;name&gt;.event<\/code> specifies which hook event should trigger it. Since this is just configuration, it can live in your per-user <code>~\/.gitconfig<\/code>, a system-wide <code>\/etc\/gitconfig<\/code>, or in a repository&rsquo;s local config. That makes it straightforward to define a set of hooks centrally and have them apply everywhere.<\/p>\n\n\n\n<p>Even better, you can now run <em>multiple<\/em> hooks for the same event. If you want both a linter and a secrets scanner to run before every commit, you can configure them independently:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code\"><code>[hook \"linter\"]\n   event = pre-commit\n   command = ~\/bin\/linter --cpp20\n\n[hook \"no-leaks\"]\n   event = pre-commit\n   command = ~\/bin\/leak-detector<\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value='[hook \"linter\"]\n   event = pre-commit\n   command = ~\/bin\/linter --cpp20\n\n[hook \"no-leaks\"]\n   event = pre-commit\n   command = ~\/bin\/leak-detector' tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p>Git will run them in the order it encounters their configuration. The traditional hook script in <code>$GIT_DIR\/hooks<\/code> still works, and runs last, so existing hooks are unaffected. You can see which hooks are configured (and where they come from) with <code>git hook list<\/code>:<\/p>\n\n\n<div class=\"wp-block-code-wrapper\">\n<pre class=\"wp-block-code\"><code>$ git hook list pre-commit\nglobal    linter  ~\/bin\/linter --cpp20\nlocal    no-leaks    ~\/bin\/leak-detector <\/code><\/pre>\n<clipboard-copy aria-label=\"Copy\" class=\"code-copy-btn\" data-copy-feedback=\"Copied!\" value=\"$ git hook list pre-commit\nglobal    linter  ~\/bin\/linter --cpp20\nlocal    no-leaks    ~\/bin\/leak-detector\" tabindex=\"0\" role=\"button\"><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-copy js-clipboard-copy-icon\"><path d=\"M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z\"><\/path><path d=\"M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z\"><\/path><\/svg><svg aria-hidden=\"true\" height=\"16\" viewbox=\"0 0 16 16\" version=\"1.1\" width=\"16\" class=\"octicon octicon-check js-clipboard-check-icon\"><path d=\"M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z\"><\/path><\/svg><\/clipboard-copy><\/div>\n\n\n<p>Individual hooks can be disabled without removing their configuration by setting <code>hook.&lt;name&gt;.enabled = false<\/code>, which is particularly handy when a hook is defined in a system-level config but you need to opt a specific repository out.<\/p>\n\n\n\n<p>Along the way, Git&rsquo;s internal handling of hooks has been modernized. Many built-in hooks that were previously invoked through ad-hoc code paths (like <code>pre-push<\/code>, <code>post-rewrite<\/code>, and the various <code>receive-pack<\/code> hooks) have been migrated to use the new hook API, meaning they all benefit from the new configuration-based hook machinery.<\/p>\n\n\n\n<p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/4aa72ea1f64e8ddcd1865c76b24591c0916c0b5d...005f3fbe07a20dd5f7dea57f6f46cd797387e56a\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>, <a href=\"https:\/\/github.com\/git\/git\/compare\/9a8aebae972de22ecd5adb92fec9d77147949c8a...ec1c4d974ac74afb4f0574d29f7bbb30c1c46431\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>, <a href=\"https:\/\/github.com\/git\/git\/compare\/4e5821732e684f21a35288d8e67f453ca2595083...5c58dbc887a1f3530cb29c995f63675beebb22e9\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-geometric-repacking-during-maintenance-by-default\">Geometric repacking during maintenance by default<\/h2>\n\n\n\n<p>Returning readers of this series <a href=\"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-52\/#advanced-repository-maintenance-strategies\">may recall<\/a> our coverage of the new <code>geometric<\/code> strategy within <code>git maintenance<\/code>, which was introduced in Git 2.52. That strategy works by inspecting the contents of your repository to determine if some number of <a href=\"https:\/\/git-scm.com\/book\/en\/v2\/Git-Internals-Packfiles\">packfiles<\/a> can be combined to form a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Geometric_progression\">geometric progression<\/a> by object count. If they can, Git performs a geometric repack, condensing the contents of your repository without needing to perform a full garbage collection.<\/p>\n\n\n\n<p>In 2.52, the <code>geometric<\/code> strategy was available as an opt-in choice via the <code>maintenance.strategy<\/code> configuration. In 2.54, it becomes the default strategy for manual maintenance. That means when you run <code>git maintenance run<\/code> without specifying a strategy, Git will now use the geometric approach instead of the traditional <code>gc<\/code> task.<\/p>\n\n\n\n<p>In practice, this means that your repositories will be maintained more efficiently out of the box. The geometric strategy avoids the expensive all-into-one repacks that <code>gc<\/code> performs, instead combining packs incrementally when possible and falling back to a full <code>gc<\/code> only when it would consolidate the entire repository into a single pack. Along the way, it keeps your <a href=\"https:\/\/git-scm.com\/docs\/git-commit-graph\/2.54.0\">commit-graph<\/a>, <a href=\"https:\/\/git-scm.com\/docs\/git-reflog\/2.54.0\">reflogs<\/a>, and other auxiliary data structures up to date.<\/p>\n\n\n\n<p>If you were already using <code>maintenance.strategy = geometric<\/code> in your configuration, nothing changes. If you hadn&rsquo;t set a strategy (or were relying on the old <code>gc<\/code> default), you&rsquo;ll start seeing the benefits of geometric repacking automatically. The <code>gc<\/code> strategy is still available if you prefer it and can be selected with <code>maintenance.strategy = gc<\/code>.<\/p>\n\n\n\n<p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/1ebfc2171310ed5ca2bcd8c1255d45f03e56dda7...452b12c2e0fe7a18f9487f8a090ce46bef207177\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-tip-of-the-iceberg\">The tip of the iceberg&hellip;<\/h2>\n\n\n\n<p>Now that we&rsquo;ve covered some of the larger changes in more detail, let&rsquo;s take a closer look at a selection of some other new features and updates in this release.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><p>The&nbsp;<code>git add&nbsp;&ndash;p<\/code>&nbsp;command, Git&rsquo;s tool for interactively staging individual hunks, received a handful of usability improvements in this release. When navigating between hunks with the&nbsp;<code>J<\/code>&nbsp;and&nbsp;<code>K<\/code>&nbsp;keys, Git now shows whether&nbsp;you&rsquo;ve&nbsp;previously accepted or skipped each hunk, so you&nbsp;don&rsquo;t&nbsp;have to remember your earlier decisions.<\/p><p>Separately, a new&nbsp;<code>--no-auto-advance<\/code>&nbsp;flag changes how&nbsp;<code>git add&nbsp;&ndash;p<\/code>&nbsp;handles the transition between files.&nbsp;Normally, once you&rsquo;ve made a decision on every hunk in a file, the session automatically moves on to the next one.&nbsp;With&nbsp;<code>--no-auto-advance<\/code>, the session stays put after&nbsp;you&rsquo;ve&nbsp;decided on the last hunk, letting you use&nbsp;<code>&lt;<\/code>&nbsp;and&nbsp;<code>&gt;<\/code>&nbsp;to move between files at your own pace. This can be useful when you want to review your decisions holistically before&nbsp;<em>committing&nbsp;<\/em>to&nbsp;them.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/2b53e8b3ee7f143af785e2a39ce4e1614ff6c66e...8cafc305e22a59efb92472d4132616e24d3184c6\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>,&nbsp;<a href=\"https:\/\/github.com\/git\/git\/compare\/f19f1b6cf37d22cf317b5c3b52a11eede1abe267...417b181f99ce53f50dea6541430cfe1f1f359a6a\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p><code>git replay<\/code>, the experimental command for replaying commits onto a new base without touching the working tree, continues to mature. This release brings several improvements:&nbsp;<code>replay<\/code>&nbsp;now performs atomic reference updates by default (instead of printing&nbsp;<a href=\"https:\/\/git-scm.com\/docs\/git-update-ref\/2.54.0\" target=\"_blank\" rel=\"noreferrer noopener\"><code>update-ref<\/code><\/a>&nbsp;commands to&nbsp;<code>stdout<\/code>), has learned a new&nbsp;<code>--revert<\/code>&nbsp;mode that reverses the changes from a range of commits, can now drop commits that become empty during replay, and supports replaying all the way down to the root commit.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/7bf3785d0973d229fa21a76122c7e4735a2b1ffb...0ee71f4bd035db61342c2c5a25984e4545347c11\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>,&nbsp;<a href=\"https:\/\/github.com\/git\/git\/compare\/05ddb9ee8a4c619fbb0e7309fe291bff5cd7c987...2760ee49834953c0860fa5d7983a6af4d27cb6a9\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>,&nbsp;<a href=\"https:\/\/github.com\/git\/git\/compare\/03311dca7f91f69e9e0c532fce1c1e3c0a9fa34d...e8b79a96ebaa2113391d14bfcdabe239f6ff8611\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>,&nbsp;<a href=\"https:\/\/github.com\/git\/git\/compare\/d8c553bbed21761a8af3fa40a20518e210e78a0d...23d83f8ddbef9adcb87671358b473e55cf90c90b\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p>Git&rsquo;s HTTP transport now handles&nbsp;<a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc6585#section-4\" target=\"_blank\" rel=\"noreferrer noopener\">HTTP 429 &ldquo;Too Many Requests&rdquo;<\/a>&nbsp;responses. Previously, a 429 from the server would be treated as a fatal error. Git can now retry the request, honoring the server&rsquo;s&nbsp;<code>Retry-After<\/code>&nbsp;header when present, or&nbsp;fall&nbsp;back to a configurable delay via the new&nbsp;<code>http.retryAfter<\/code>&nbsp;setting.&nbsp;The&nbsp;new&nbsp;<code>http.maxRetries<\/code>&nbsp;and&nbsp;<code>http.maxRetryTime<\/code>&nbsp;configuration options provide control over how many times to retry and how long to wait, respectively.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/270e10ad6dda3379ea0da7efd11e4fbf2cd7a325...640657ffd06999ec1ec3b1d030b7f5aac6b7f57b\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p><code>git log &ndash;L<\/code>, which traces the history of a range of lines within a file, has historically used its own custom output path that bypassed much of Git&rsquo;s standard diff machinery. As a result, it was incompatible with several useful options, including the <code>-S<\/code> and <code>-G<\/code> &ldquo;pickaxe&rdquo; options for searching by content changes.<\/p><p>This release reworks <code>git log &ndash;L<\/code> to route its output through the standard diff pipeline, making it compatible with patch formatting options and pickaxe searches for the first time.<\/p><p>Say you want to trace the history of <code>strbuf_addstr()<\/code> in <code>strbuf.c<\/code>, but only see commits where <code>len<\/code> was added or removed within that function: <\/p><pre class=\"wp-block-code\"><code>$ git log -L :strbuf_addstr:strbuf.c -S len --oneline -1\na70f8f19ad2 strbuf: introduce strbuf_addstrings() to repeatedly add a string\n\ndiff --git a\/strbuf.c b\/strbuf.c\n--- a\/strbuf.c\n+++ b\/strbuf.c\n@@ -316,0 +316,9 @@\n+void strbuf_addstrings(struct strbuf *sb, const char *s, size_t n)\n+{\n+      size_t len = strlen(s);\n+\n+      strbuf_grow(sb, st_mult(len, n));\n+      for (size_t i = 0; i &lt; n; i++)\n+              strbuf_add(sb, s, len);\n+}<\/code><\/pre>\n<p>Prior to this release, options like <code>-S<\/code>, (and <code>-G<\/code>, <code>--word-diff<\/code>, along with <code>--color-moved<\/code>) were silently ignored when used with <code>-L<\/code>. Now they work together naturally: <code>-L<\/code> scopes the output to the function you care about, and <code>-S<\/code> filters down to just the commits that touched the symbol you&rsquo;re searching for within it. <\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/fb5516997ef3f882d8e53ce70ba6077533683621...512536a09ea2964e93226f219898ee0a09d85a70\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p>Incremental multi-pack indexes, which we first covered in&nbsp;<a href=\"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-47\/#incremental-multi-pack-indexes\" target=\"_blank\" rel=\"noreferrer noopener\">our discussion of Git 2.47<\/a>&nbsp;and followed up on in&nbsp;<a href=\"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-50\/#incremental-multi-pack-reachability-bitmaps\" target=\"_blank\" rel=\"noreferrer noopener\">Git 2.50<\/a>, received further work in this release. The MIDX machinery now supports&nbsp;<em>compaction<\/em>, which merges smaller MIDX layers together (along with their associated reachability bitmaps) to keep the number of layers in the chain manageable. This is&nbsp;an important step&nbsp;toward making incremental MIDXs practical for long-lived repositories that accumulate many layers over time.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/ce74208c2fa13943fffa58f168ac27a76d0eb789...d54da84bd9de09fc339accff553f1fc8a5539154\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p><code>git status<\/code> learned a new <code>status.compareBranches<\/code> configuration option. By default, <code>git status<\/code> shows how your current branch compares to its configured upstream (e.g., &ldquo;Your branch is ahead of &lsquo;origin\/main&rsquo; by 3 commits&rdquo;). With <code>status.compareBranches<\/code>, you can ask it to also compare against your push remote, or both: <\/p><p><\/p><pre class=\"wp-block-code\"><code>[status]\n   compareBranches = @{upstream} @{push}<\/code><\/pre><p>This is useful if your push destination differs from your upstream, as is common in triangular workflows where you fetch from one remote and push to another (like a fork).<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/d79fff4a11a5...68791d7506aa\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p>Say you have a series of&nbsp;commits, and&nbsp;want to add a trailer to each one of them. You could do this&nbsp;manually, or&nbsp;automate it with something like:&nbsp;<code>git rebase -x &lsquo;git commit --amend --no-edit --trailer=&rdquo;Reviewed-by: A U Thor&nbsp;&lt;a href=\"mailto:&lt;author@example.com&gt;&rdquo;&rsquo;<\/code>, but that&rsquo;s&nbsp;kind of a&nbsp;mouthful.<\/p><p>In Git 2.54,&nbsp;<code>git rebase<\/code>&nbsp;learned a&nbsp;new&nbsp;<code>--trailer<\/code>&nbsp;option, which appends a trailer to every rebased commit via the&nbsp;<a href=\"https:\/\/git-scm.com\/docs\/git-interpret-trailers\" target=\"_blank\" rel=\"noreferrer noopener\"><code>interpret-trailers<\/code><\/a>&nbsp;machinery.&nbsp;Instead of the monstrosity above, we can now write&nbsp;<code>git rebase&nbsp;--trailer&nbsp;\"Reviewed-by: A&nbsp;&lt;a href=\"mailto:<a>&lt;a@example.com<\/a>&gt;\"<\/code>&nbsp;and achieve the same effect.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/a7a079c2c4bc7b269229a6ea6c147b6b2d5b2684...e4f9d6b0ab2e1903765258991a6265599d0007ce\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p>When signing commits, a&nbsp;<a href=\"https:\/\/git-scm.com\/book\/ms\/v2\/Git-Tools-Signing-Your-Work\" target=\"_blank\" rel=\"noreferrer noopener\">signature<\/a>&nbsp;remains&nbsp;valid even when it was signed with a GPG key that has since expired.&nbsp;Previously, Git displayed these signatures with&nbsp;a scary&nbsp;red color, which could be misleading&nbsp;and lead you to interpret the signature itself as invalid. Git now correctly treats a valid signature made with a since-expired key as a good signature.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/9eb5b3b999cb89d4a09dcf1012784e74154026de...90695bbdaea86064398c26eb259043cadcf99a86\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p><code>git blame<\/code>&nbsp;learned a new&nbsp;<code>--diff-algorithm<\/code>&nbsp;option, allowing you to select which&nbsp;<a href=\"https:\/\/git-scm.com\/docs\/diff-options\/2.54.0#Documentation\/diff-options.txt---diff-algorithmpatienceminimalhistogrammyers\" target=\"_blank\" rel=\"noreferrer noopener\">diff algorithm<\/a>&nbsp;(e.g.,&nbsp;<code>histogram<\/code>,&nbsp;<code>patience<\/code>, or&nbsp;<code>minimal<\/code>) is used when computing blame. This can sometimes produce meaningfully different (and more useful) blame output, depending on the nature of the changes in your repository&rsquo;s history.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/716e871d50dc63a6f436442b127571b9268a75a3...ffffb987fcd3b3d6b88aceed87000ef4a5b6114e\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p>Under the hood, a significant amount of work went into&nbsp;restructuring Git&rsquo;s object database (ODB) internals. The ODB source API has been refactored to use a pluggable backend design, with individual functions like&nbsp;<code>read_object()<\/code>,&nbsp;<code>write_object()<\/code>, and&nbsp;<code>for_each_object()<\/code>&nbsp;now dispatched through function pointers on a per-source basis. While none of this is user-visible today, it&nbsp;lays&nbsp;the groundwork for future features like alternative storage backends or more flexible object database configurations.<\/p><p>[<a href=\"https:\/\/ttps\/\/github.com\/git\/git\/compare\/2cc71917514657b93014134350864f4849edfc83...3565faf28c2059c6260d53ac71a303b1c04b0a7b\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>,&nbsp;<a href=\"https:\/\/github.com\/git\/git\/compare\/d0413b31ddcce6ae6ffaff0a30a67ffbd1a7c648...d6fc6fe6f8b74e663d6013f830b535f50bfc1414\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>,&nbsp;<a href=\"https:\/\/github.com\/git\/git\/compare\/2f8c3f6a5a6d6a3de205be709e1a598b9d4b0b3e...83869e15fa9ef3b0ea2adbfe2fe68a309f95b856\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>,&nbsp;<a href=\"https:\/\/github.com\/git\/git\/compare\/2e3028a58c1f1fbf08538443fc30a48ac4f6bacf...109bcb7d1d2f0d2f0514beec15779190c0b89575\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p><code>git&nbsp;backfill<\/code>, the experimental command for downloading missing blobs in a&nbsp;<a href=\"https:\/\/git-scm.com\/docs\/partial-clone\" target=\"_blank\" rel=\"noreferrer noopener\">partial clone<\/a>, learned to accept revision and&nbsp;<a href=\"https:\/\/git-scm.com\/docs\/gitglossary\/2.54.0#Documentation\/gitglossary.txt-pathspec\" target=\"_blank\" rel=\"noreferrer noopener\">pathspec<\/a>&nbsp;arguments. Previously,&nbsp;<code>backfill<\/code>&nbsp;would always download blobs reachable from&nbsp;<code>HEAD<\/code>&nbsp;across the entire tree. You can now scope it to a particular range of history (e.g.,&nbsp;<code>git backfill main~100..main<\/code>) or a subset of paths (e.g.,&nbsp;<code>git backfill -- '*.c'<\/code>), including&nbsp;pathspecs&nbsp;with wildcards.<\/p><p>This makes backfill much more practical for large partial clones where you only need historical blobs for a specific area of the repository.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/cd79c76a51f776bf46a849db04ce2cc45c5c5d6d...46d1f4cf4dcb8aaf799f78410af829e149086f36\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p>Git&rsquo;s alias configuration has historically been limited to ASCII alphanumeric characters and hyphens. That meant alias names like &ldquo;h&auml;mta&rdquo; (Swedish for &ldquo;fetch&rdquo;) or &ldquo;&#29366;&#24907;&rdquo; (Japanese for &ldquo;status&rdquo;) were off-limits. Git 2.54 lifts that restriction with a new subsection-based syntax:<\/p><p><\/p><pre class=\"wp-block-code\"><code>[alias \"h&auml;mta\"]\n    command = fetch\n<\/code><\/pre><p>The traditional <code>[alias] co = checkout<\/code> syntax continues to work for ASCII names. The new subsection form supports any characters (except newlines and <code>NUL<\/code> bytes), is matched case-sensitively as raw bytes, and uses a command key for the alias definition. Shell completion has been updated to handle these aliases as well.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/ac78c5804e080aa8f0307155eda85465a2a1b1dd...edd8ad18a643d47dd92b08ab865bf7f4a26f50bc\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>, <a href=\"https:\/\/github.com\/git\/git\/compare\/08c36099359e6a5c694f9abb97e630a247bc8dfb...73cc549559398626f33063f64ece9e558e654c95\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n\n\n\n<li><p>The histogram diff algorithm received a fix for a subtle output quality issue. After any diff algorithm runs, Git performs a &ldquo;compaction&rdquo; phase that&nbsp;shifts&nbsp;and merges change groups to produce cleaner output. In some cases, this shifting could move a change group across the anchor lines that the histogram algorithm had chosen, producing a diff that was technically correct but visually redundant. Git now detects when this happens and re-diffs the affected region, resulting in tighter output that better matches what you would expect.<\/p><p>[<a href=\"https:\/\/github.com\/git\/git\/compare\/7f13e5c8c744ec8da268b6f774d16f2ea729f48e...e417277ae99687b576e48cb477a7a50241ea0096\" target=\"_blank\" rel=\"noreferrer noopener\">source<\/a>]<\/p><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-rest-of-the-iceberg\">&hellip;the rest of the iceberg<\/h2>\n\n\n\n<p>That&rsquo;s just a sample of changes from the latest release. For more, check out the release notes for <a href=\"https:\/\/github.com\/git\/git\/blob\/v2.54.0\/Documentation\/RelNotes\/2.53.0.adoc\">2.53<\/a> and <a href=\"https:\/\/github.com\/git\/git\/blob\/v2.54.0\/Documentation\/RelNotes\/2.54.0.adoc\">2.54<\/a>, or <a href=\"https:\/\/github.com\/git\/git\/tree\/v2.54.0\/Documentation\/RelNotes\">any previous version<\/a> in the <a href=\"https:\/\/github.com\/git\/git\">Git repository<\/a>.<\/p>\n<\/body><\/html>\n","protected":false},"excerpt":{"rendered":"<p>The open source Git project just released Git 2.54. Here is GitHub\u2019s look at some of the most interesting features and changes introduced since last time.<\/p>\n","protected":false},"author":1282,"featured_media":95394,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_gh_post_show_toc":"yes","_gh_post_is_no_robots":"","_gh_post_is_featured":"yes","_gh_post_is_excluded":"","_gh_post_is_unlisted":"","_gh_post_related_link_1":"","_gh_post_related_link_2":"","_gh_post_related_link_3":"","_gh_post_sq_img":"","_gh_post_sq_img_id":"","_gh_post_cta_title":"","_gh_post_cta_text":"","_gh_post_cta_link":"","_gh_post_cta_button":"","_gh_post_recirc_hide":"","_gh_post_recirc_col_1":"","_gh_post_recirc_col_2":"","_gh_post_recirc_col_3":"","_gh_post_recirc_col_4":"","_featured_video":"","_gh_post_additional_query_params":"","_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_wpas_customize_per_network":false,"_links_to":"","_links_to_target":""},"categories":[3330,67],"tags":[132],"coauthors":[2189],"class_list":["post-95374","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-git","category-open-source","tag-git"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.3 (Yoast SEO v27.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Highlights from Git 2.54 - The GitHub Blog<\/title>\n<meta name=\"description\" content=\"The open source Git project just released Git 2.54. Here is GitHub\u2019s look at some of the most interesting features and changes introduced since last time.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Highlights from Git 2.54\" \/>\n<meta property=\"og:description\" content=\"The open source Git project just released Git 2.54. Here is GitHub\u2019s look at some of the most interesting features and changes introduced since last time.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/\" \/>\n<meta property=\"og:site_name\" content=\"The GitHub Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-04-20T16:43:57+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-27T22:03:23+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/04\/git254.png\" \/>\n\t<meta property=\"og:image:width\" content=\"4804\" \/>\n\t<meta property=\"og:image:height\" content=\"2520\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Taylor Blau\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Taylor Blau\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/\"},\"author\":{\"name\":\"Taylor Blau\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/f2a5dc09d09f41c8c731679cc07da524\"},\"headline\":\"Highlights from Git 2.54\",\"datePublished\":\"2026-04-20T16:43:57+00:00\",\"dateModified\":\"2026-04-27T22:03:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/\"},\"wordCount\":2278,\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2026\\\/04\\\/git254.png?fit=4804%2C2520\",\"keywords\":[\"Git\"],\"articleSection\":[\"Git\",\"Open Source\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/\",\"url\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/\",\"name\":\"Highlights from Git 2.54 - The GitHub Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2026\\\/04\\\/git254.png?fit=4804%2C2520\",\"datePublished\":\"2026-04-20T16:43:57+00:00\",\"dateModified\":\"2026-04-27T22:03:23+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/f2a5dc09d09f41c8c731679cc07da524\"},\"description\":\"The open source Git project just released Git 2.54. Here is GitHub\u2019s look at some of the most interesting features and changes introduced since last time.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/#primaryimage\",\"url\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2026\\\/04\\\/git254.png?fit=4804%2C2520\",\"contentUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2026\\\/04\\\/git254.png?fit=4804%2C2520\",\"width\":4804,\"height\":2520,\"caption\":\"git 2.54 is here!\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/highlights-from-git-2-54\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/github.blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Open Source\",\"item\":\"https:\\\/\\\/github.blog\\\/open-source\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Git\",\"item\":\"https:\\\/\\\/github.blog\\\/open-source\\\/git\\\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Highlights from Git 2.54\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/github.blog\\\/#website\",\"url\":\"https:\\\/\\\/github.blog\\\/\",\"name\":\"The GitHub Blog\",\"description\":\"Updates, ideas, and inspiration from GitHub to help developers build and design software.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/github.blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/f2a5dc09d09f41c8c731679cc07da524\",\"name\":\"Taylor Blau\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d5f3476f26b6f99cbb6b467e7ed7482f5762c8157bc73f569196e428bdcbea25?s=96&d=mm&r=g2ce44289191883c54a58a554d8fc874a\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d5f3476f26b6f99cbb6b467e7ed7482f5762c8157bc73f569196e428bdcbea25?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d5f3476f26b6f99cbb6b467e7ed7482f5762c8157bc73f569196e428bdcbea25?s=96&d=mm&r=g\",\"caption\":\"Taylor Blau\"},\"description\":\"Taylor Blau is a Principal Software Engineer at GitHub where he works on Git.\",\"sameAs\":[\"https:\\\/\\\/ttaylorr.com\"],\"url\":\"https:\\\/\\\/github.blog\\\/author\\\/ttaylorr\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Highlights from Git 2.54 - The GitHub Blog","description":"The open source Git project just released Git 2.54. Here is GitHub\u2019s look at some of the most interesting features and changes introduced since last time.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/","og_locale":"en_US","og_type":"article","og_title":"Highlights from Git 2.54","og_description":"The open source Git project just released Git 2.54. Here is GitHub\u2019s look at some of the most interesting features and changes introduced since last time.","og_url":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/","og_site_name":"The GitHub Blog","article_published_time":"2026-04-20T16:43:57+00:00","article_modified_time":"2026-04-27T22:03:23+00:00","og_image":[{"width":4804,"height":2520,"url":"https:\/\/github.blog\/wp-content\/uploads\/2026\/04\/git254.png","type":"image\/png"}],"author":"Taylor Blau","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Taylor Blau","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/#article","isPartOf":{"@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/"},"author":{"name":"Taylor Blau","@id":"https:\/\/github.blog\/#\/schema\/person\/f2a5dc09d09f41c8c731679cc07da524"},"headline":"Highlights from Git 2.54","datePublished":"2026-04-20T16:43:57+00:00","dateModified":"2026-04-27T22:03:23+00:00","mainEntityOfPage":{"@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/"},"wordCount":2278,"image":{"@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2026\/04\/git254.png?fit=4804%2C2520","keywords":["Git"],"articleSection":["Git","Open Source"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/","url":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/","name":"Highlights from Git 2.54 - The GitHub Blog","isPartOf":{"@id":"https:\/\/github.blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/#primaryimage"},"image":{"@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2026\/04\/git254.png?fit=4804%2C2520","datePublished":"2026-04-20T16:43:57+00:00","dateModified":"2026-04-27T22:03:23+00:00","author":{"@id":"https:\/\/github.blog\/#\/schema\/person\/f2a5dc09d09f41c8c731679cc07da524"},"description":"The open source Git project just released Git 2.54. Here is GitHub\u2019s look at some of the most interesting features and changes introduced since last time.","breadcrumb":{"@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/#primaryimage","url":"https:\/\/github.blog\/wp-content\/uploads\/2026\/04\/git254.png?fit=4804%2C2520","contentUrl":"https:\/\/github.blog\/wp-content\/uploads\/2026\/04\/git254.png?fit=4804%2C2520","width":4804,"height":2520,"caption":"git 2.54 is here!"},{"@type":"BreadcrumbList","@id":"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-54\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/github.blog\/"},{"@type":"ListItem","position":2,"name":"Open Source","item":"https:\/\/github.blog\/open-source\/"},{"@type":"ListItem","position":3,"name":"Git","item":"https:\/\/github.blog\/open-source\/git\/"},{"@type":"ListItem","position":4,"name":"Highlights from Git 2.54"}]},{"@type":"WebSite","@id":"https:\/\/github.blog\/#website","url":"https:\/\/github.blog\/","name":"The GitHub Blog","description":"Updates, ideas, and inspiration from GitHub to help developers build and design software.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/github.blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/github.blog\/#\/schema\/person\/f2a5dc09d09f41c8c731679cc07da524","name":"Taylor Blau","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/d5f3476f26b6f99cbb6b467e7ed7482f5762c8157bc73f569196e428bdcbea25?s=96&d=mm&r=g2ce44289191883c54a58a554d8fc874a","url":"https:\/\/secure.gravatar.com\/avatar\/d5f3476f26b6f99cbb6b467e7ed7482f5762c8157bc73f569196e428bdcbea25?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d5f3476f26b6f99cbb6b467e7ed7482f5762c8157bc73f569196e428bdcbea25?s=96&d=mm&r=g","caption":"Taylor Blau"},"description":"Taylor Blau is a Principal Software Engineer at GitHub where he works on Git.","sameAs":["https:\/\/ttaylorr.com"],"url":"https:\/\/github.blog\/author\/ttaylorr\/"}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/github.blog\/wp-content\/uploads\/2026\/04\/git254.png?fit=4804%2C2520","jetpack_shortlink":"https:\/\/wp.me\/pamS32-oOi","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/95374","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/users\/1282"}],"replies":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/comments?post=95374"}],"version-history":[{"count":26,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/95374\/revisions"}],"predecessor-version":[{"id":95625,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/95374\/revisions\/95625"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media\/95394"}],"wp:attachment":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media?parent=95374"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/categories?post=95374"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/tags?post=95374"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/coauthors?post=95374"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}