{"slug": "new-features-in-git-2-54-easier-rebasing-hooks-and-statistics", "title": "New features in Git 2.54: easier rebasing, hooks, and statistics", "summary": "Git 2.54 introduces new features including easier rebasing through the `git history` command, which allows users to reword or split commits without checking out the associated branch first. The update also includes hooks defined in config and repository statistics. These additions aim to simplify common Git tasks and make the tool more accessible.", "body_md": "In this post I describe some of the nice new features [released in Git 2.54](https://lore.kernel.org/git/xmqqa4uxsjrs.fsf@gitster.g/T/#u), including easier simple rebases, hooks defined in config, and some stats about your git repo. I learned about these from [other](https://github.blog/open-source/git/highlights-from-git-2-54/) [posts](https://about.gitlab.com/blog/whats-new-in-git-2-54-0/), and these are the things that caught my eye.\n\n[Easier simple rebases with ](#easier-simple-rebases-with-git-history)`git history`\n\n`git history`\n\nI'm a big fan of interactive rebasing with `git rebase -i`\n\n, particularly when using a tool like [Rider](/working-with-git-in-jetbrains-rider/) which makes working out exactly what you need to do that much easier:\n\nBut the reality is that `rebase`\n\nis often daunting to people. You *can* mess it up, and if you end up with merge conflicts on the way, things can easily get very confusing. And sometimes, you don't really *need* all the power of a full rebase.\n\nI've written a lot about\n\n[rebasing]in the past, including[stacked branches],[and]`git absorb`\n\n[. If you don't know about these tools, I highly recommend checking them out!]`--update-refs`\n\nIf you don't need to do anything fancy with `git rebase`\n\nthen the new `git history`\n\ncommand might be for you. In Git 2.54, `git history`\n\nsupports two commands:\n\n`git history reword <commit>`\n\nlets you change the commit message for a specific commit.`git history split <commit>`\n\nlets you split a specific commit in two.\n\nThose are obviously a *tiny* subset of things that you can do with an interactive rebase, but they're also things that you might want to do relatively often. The other nice thing is that you can run these *without* having to check out the branch they're associated with first.\n\n[Rewording commits with ](#rewording-commits-with-git-history-reword)`git history reword`\n\n`git history reword`\n\nFor example, imagine you have this small set of branches, where we currently have `master`\n\nchecked out, and we're working on that, but there's a separate branch `issue-83`\n\nThat `wip`\n\ncommit at the base of the branch `issue-83`\n\ndoesn't have a good commit message, it should be describing what it does, and was probably meant to be tidied up later. Previously, this is the flow we'd need to take:\n\n```\ngit checkout issue-83        # checkout the branch\ngit rebase -i origin/master  # start an interactive rebase\n```\n\nThis would open up an editor, and we'd need to find the commit we want and change the action from `pick`\n\nto `reword`\n\n:\n\n```\nreword 055db13 # wip\npick f44696a # Fix the underlying cause # empty\n\n# Rebase 69d0f46..f44696a onto 69d0f46 (2 commands)\n#\n# Commands:\n# p, pick <commit> = use commit\n# r, reword <commit> = use commit, but edit the commit message\n# e, edit <commit> = use commit, but stop for amending\n# ...\n```\n\nAfter closing the editor, Git would start rebasing, and then pause and open our editor so we could reword the commit. That obviously works, and if you have the branch already checked out it's not a big deal, especially if you're using an IDE like Rider that makes it even easier.\n\nHowever, with `git history reword`\n\nyou can do the same thing in one shot, from anywhere, *without* having to checkout the branch first. You simply pass in the commit that you want to change, and Git opens the editor, waits for the update, and rewrites the commit message and fixes the rest of the history:\n\n```\ngit history reword 055db134ac326766b1566a64cd81873c69b1dc58   # this is the only step\n```\n\nThe whole operation is very fast as well, because Git isn't walking through, updating the working directory as it replays commits, it's just fixing the commits, and then fixing up the descendant hashes.\n\n[Splitting a commit with ](#splitting-a-commit-with-git-history-split)`git history split`\n\n`git history split`\n\nIn general, I like to create self-contained commits in my branches, and to sequence them in such a way that it makes it easier for reviewers that review commit-by-commit. Sometimes, however, I accidentally make a commit which, in hindsight, is too big, and which I want to split. Another scenario is where I accidentally include a file in a commit which was meant to be in a different commit.\n\nTypically, I would handle this by using `git rebase -i`\n\nto start an interactive rebase, pause on the problematic commit, do a `git reset HEAD~`\n\nto \"erase\" the commit from history (while keeping the index intact) and then make my two separate commits, before continuing with `git rebase --continue`\n\n. This is a workflow that I'm very comfortable with, but I'm *sure* many people wouldn't be.\n\n`git history split`\n\nessentially does the same thing in one hit, though just like `git history reword`\n\n, it *doesn't* require you to have the branch you're editing checked out. Instead, you use the built-in hunk selector to choose which parts of the commit should be pulled out into a \"parent\" commit.\n\nThis feature is apparently inspired by\n\n[Jujustu]'s`jj split`\n\ncommand.`jj`\n\nis a tool I keep feeling like Ishouldlook at, but with my`git`\n\nmuscle-memory as it is, I just don't have much of an incentive. But if youdon'tlike git, maybe take a look!\n\nFor example, let's imagine the commit we just reworded also needs to be split. We initiate using\n\n```\ngit history split 1153957368717fbe4dd19866315fbf53b17a0993\n```\n\nGit immediately starts showing you diffs, and you need to decide whether they should be included in the parent commit, or kept in the existing commit:\n\n```\ndiff --git a/src/NetEscapades.Configuration.Yaml/NetEscapades.Configuration.Yaml.csproj b/src/NetEscapades.Configuration.Yaml/NetEscapades.Configuration.Yaml.csproj\nindex 4a249b0..953865b 100644\n--- a/src/NetEscapades.Configuration.Yaml/NetEscapades.Configuration.Yaml.csproj\n+++ b/src/NetEscapades.Configuration.Yaml/NetEscapades.Configuration.Yaml.csproj\n@@ -21,7 +21,7 @@\n\n   <ItemGroup>\n     <PackageReference Include=\"Microsoft.SourceLink.GitHub\" Version=\"1.0.0-*\" PrivateAssets=\"all\" />\n-    <PackageReference Include=\"YamlDotNet\" Version=\"13.0.1\" />\n+    <PackageReference Include=\"YamlDotNet\" Version=\"16.3.0\" />\n     <PackageReference Include=\"Microsoft.Extensions.Configuration\" Version=\"2.0.0\" />\n     <PackageReference Include=\"Microsoft.Extensions.Configuration.FileExtensions\" Version=\"2.0.0\" />\n   </ItemGroup>\n(1/1) Stage this hunk [y,n,q,a,d,?]?\n```\n\nAt the end, you can see the question `(1/1) Stage this hunk [y,n,q,a,d,?]?`\n\nThis is showing you the valid options. If you type `?`\n\nand push `Enter`, you can see what each of the options does:\n\n```\ny - stage this hunk\nn - do not stage this hunk\nq - quit; do not stage this hunk or any of the remaining ones\na - stage this hunk and all later hunks in the file\nd - do not stage this hunk or any of the later hunks in the file\n? - print help\n```\n\nIf you stage the hunk, then it's added to the parent commit, otherwise it stays in the existing commit.\n\nOnce you've staged (or not) all of the hunks in the commit, Git opens your editor twice, once for each commit. The editor is pre-populated with the existing commit message in both cases, but you can change both of them. After the editor closes, the split is complete\n\nAgain, being able to do this *without* having to check out the branch makes this command both convenient and fast!\n\n[Limitations with ](#limitations-with-git-history)`git history`\n\n`git history`\n\nThe main limitation with `git history`\n\nis that it can't be used on any segments of history that contain merge commits; it will just refuse if you try:\n\n``` bash\n$ git history reword a626aa2b9296ed0530356de98fb94bbd78802f5b\nerror: replaying merge commits is not supported yet!\n```\n\nAlso if you're not used to using the interactive hunk staging (e.g. using `git add -p`\n\n) then you might find working with `git history split`\n\na little tricky. As much as I use the command line for many Git operations, I much prefer using a GUI whenever I need to partially stage files, and that's just not possible with `git history split`\n\n.\n\nThe other main limitation is that these are the *only* things you can do. For me, I don't know how often I would end up doing *just* these operations and *not* need to do anything else that would require a full `git rebase`\n\n. I can see myself *occasionally* using the `git history reword`\n\n, but that's probably about it.\n\nThe other thing to be aware of is that the `git history`\n\ncommand is currently marked experimental, so it may well change in the future.\n\n[Setting up Git hooks in repository configuration](#setting-up-git-hooks-in-repository-configuration)\n\n[Git hooks](https://git-scm.com/docs/githooks) are, as the name implies, hook points that let you run scripts automatically when Git performs certain actions. The most common hooks are probably \"pre-commit\" hooks, which run just as you create a commit, and \"pre-push\" hits, which run just as you push to a remote.\n\nThese hooks are a great way to, for example, enforce that code is always run through a linter before it's committed. I've seen people add pre-push hooks that automatically run all the unit tests, to ensure you're never pushing broken code.\n\nThe main downside with hooks was often that they were sometimes a bit tricky to setup. In Git 2.54 you can now configure hooks using \"normal\" config instead! For example, let's say I want to ensure I run `dotnet format`\n\njust before I commit. I could add a pre-commit hook to do this by running the following:\n\n```\ngit config set hook.formatter.event pre-commit\ngit config set hook.formatter.command \"dotnet format\"\n```\n\nThis adds a section to the local git config for the repository that looks like the following:\n\n```\n[hook \"formatter\"]\n\tevent = pre-commit\n\tcommand = dotnet format\n```\n\nThe \"formatter\" name is arbitrary, but this config shows the hook that triggers the event, and the command that will run. With this, any time you create a commit, the hook will kick in and run `dotnet format`\n\n.\n\nFor what it's worth, I don't tend to use hooks that much, mostly because I find the slowdown they add to be too disruptive to my flow. But I suspect it's something that will becoming increasingly common in the era of AI agents where you can make sure that you're really enforcing the rules on your agents!\n\nYou can add multiple hooks for the same event using this approach, as well as using the \"traditional\" style. If you want to see all of the hooks that are going to run, you can use the `git hooks list <event>`\n\ncommand to see them:\n\n``` bash\n$ git hook list pre-commit\nformatter\n```\n\nWhen I originally saw this feature, I *thought* that it implied that you could finally *share* config in the repository itself, but that's not the case. It's still not possible to have anyone who clones the repo to have the hooks enabled by default, and likely never will, as this by definition would provide an easy way to get remote code execution on anyone that clones your repo!\n\n[Getting some git repository stats with ](#getting-some-git-repository-stats-with-git-repo-structure)`git repo structure`\n\n`git repo structure`\n\nThe final tool I'm calling out in this post is the `git repo structure`\n\ncommand, which can give you some statistics about the size and layout of yourGgit repository. This seems like something which is not going to be an issue for many people, but if you're working on a high velocity repository, then these details could be very important, as it affects how well your CI and repo hosting is going to perform!\n\nPerformance-wise, the size on disk of your repository, as well as it's *inflated* size are important factors in repo performance, as well as the number of commits and the directory structure. All those stats are available in the `git repo structure`\n\ncommand:\n\n``` bash\n$ git repo structure\nCounting objects: 245390, done.\n| Repository structure      | Value      |\n| ------------------------- | ---------- |\n| * References              |            |\n|   * Count                 |   1.63 k   |\n|     * Branches            |      1     |\n|     * Tags                |    241     |\n|     * Remotes             |   1.39 k   |\n|     * Others              |      0     |\n|                           |            |\n| * Reachable objects       |            |\n|   * Count                 | 245.39 k   |\n|     * Commits             |  15.21 k   |\n|     * Trees               | 121.16 k   |\n|     * Blobs               | 109.01 k   |\n|     * Tags                |      3     |\n|   * Inflated size         |   3.19 GiB |\n|     * Commits             |  13.85 MiB |\n|     * Trees               | 273.41 MiB |\n|     * Blobs               |   2.91 GiB |\n|     * Tags                |    491 B   |\n|   * Disk size             | 406.63 MiB |\n|     * Commits             |   8.52 MiB |\n|     * Trees               |  14.77 MiB |\n|     * Blobs               | 383.34 MiB |\n|     * Tags                |    438 B   |\n|                           |            |\n| * Largest objects         |            |\n|   * Commits               |            |\n|     * Maximum size    [1] |  66.30 KiB |\n|     * Maximum parents [2] |      2     |\n|   * Trees                 |            |\n|     * Maximum size    [3] | 238.24 KiB |\n|     * Maximum entries [4] |   2.09 k   |\n|   * Blobs                 |            |\n|     * Maximum size    [5] |  86.33 MiB |\n|   * Tags                  |            |\n|     * Maximum size    [6] |    191 B   |\n\n[1] 5dda78ddb94fa922091fa7ecea007d944b41af05\n[2] 473cbd76eb66679abaabd17046b469e172bbe386\n[3] f857ca54b07540dfb53b88be29e58d6c98686d39\n[4] f857ca54b07540dfb53b88be29e58d6c98686d39\n[5] cba4d794d0ea43222038ce1df62e63b2a88ef52c\n[6] 62b75ef5f602aa209bc278bfea67b173c966a083\n```\n\nIf, like me, these numbers don't really mean a lot to you, then this is just interesting numbers for the sake of it 😅 I'm sure they're really important if you work at GitHub or GitLab though 😀\n\nThere's many more small features in Git 2.54, these were just the ones that caught my eye, so go update!\n\n[Summary](#summary)\n\nIn this post I described some of the new features [released in Git 2.54](https://lore.kernel.org/git/xmqqa4uxsjrs.fsf@gitster.g/T/#u). Most interesting to me was the introduction of `git history`\n\nfor simplified rebasing. You can now reword commits using `git history reword`\n\nwithout having to check out the branch first or do a full rebase. Similarly, you can split a commit in two using `git history split`\n\n. Support was also added for config-based hooks and viewing statistics about your repository using `git repo structure`\n\n.", "url": "https://wpnews.pro/news/new-features-in-git-2-54-easier-rebasing-hooks-and-statistics", "canonical_source": "https://andrewlock.net/new-features-in-git-2-54-easier-rebasing-hooks-and-statistcs/", "published_at": "2026-04-28 10:00:00+00:00", "updated_at": "2026-05-23 21:36:15.161911+00:00", "lang": "en", "topics": ["developer-tools", "open-source"], "entities": ["Git", "Rider"], "alternates": {"html": "https://wpnews.pro/news/new-features-in-git-2-54-easier-rebasing-hooks-and-statistics", "markdown": "https://wpnews.pro/news/new-features-in-git-2-54-easier-rebasing-hooks-and-statistics.md", "text": "https://wpnews.pro/news/new-features-in-git-2-54-easier-rebasing-hooks-and-statistics.txt", "jsonld": "https://wpnews.pro/news/new-features-in-git-2-54-easier-rebasing-hooks-and-statistics.jsonld"}}