]> git.ipfire.org Git - thirdparty/git.git/commitdiff
builtin: add new "history" command
authorPatrick Steinhardt <ps@pks.im>
Wed, 7 Jan 2026 10:10:14 +0000 (11:10 +0100)
committerJunio C Hamano <gitster@pobox.com>
Thu, 8 Jan 2026 02:01:59 +0000 (11:01 +0900)
When rewriting history via git-rebase(1) there are a few very common use
cases:

  - The ordering of two commits should be reversed.

  - A commit should be split up into two commits.

  - A commit should be dropped from the history completely.

  - Multiple commits should be squashed into one.

  - Editing an existing commit that is not the tip of the current
    branch.

While these operations are all doable, it often feels needlessly kludgey
to do so by doing an interactive rebase, using the editor to say what
one wants, and then perform the actions. Also, some operations like
splitting up a commit into two are way more involved than that and
require a whole series of commands.

Another problem that rebases have is that dependent branches are not
being updated. The use of stacked branches has grown quite common with
competiting version control systems like Jujutsu though, so it clearly
is a need that users have. While rebases _can_ serve this use case if
one always works on the latest stacked branch, it is somewhat awkward
and very easy to get wrong.

Add a new "history" command to plug these gaps. This command will have
several different subcommands to imperatively rewrite history for common
use cases like the above.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
.gitignore
Documentation/git-history.adoc [new file with mode: 0644]
Documentation/meson.build
Makefile
builtin.h
builtin/history.c [new file with mode: 0644]
command-list.txt
git.c
meson.build
t/meson.build
t/t3450-history.sh [new file with mode: 0755]

index 78a45cb5bec99153c74b1978e789535e58115e14..24635cf2d6f4a37843ea7091da07c0d101dcac5b 100644 (file)
@@ -79,6 +79,7 @@
 /git-grep
 /git-hash-object
 /git-help
+/git-history
 /git-hook
 /git-http-backend
 /git-http-fetch
diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc
new file mode 100644 (file)
index 0000000..5a9d931
--- /dev/null
@@ -0,0 +1,56 @@
+git-history(1)
+==============
+
+NAME
+----
+git-history - EXPERIMENTAL: Rewrite history
+
+SYNOPSIS
+--------
+[synopsis]
+git history [<options>]
+
+DESCRIPTION
+-----------
+
+Rewrite history by rearranging or modifying specific commits in the
+history.
+
+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
+
+This command is related to linkgit:git-rebase[1] in that both commands can be
+used to rewrite history. There are a couple of major differences though:
+
+* linkgit:git-history[1] can work in a bare repository as it does not need to
+  touch either the index or the worktree.
+* linkgit:git-history[1] does not execute any linkgit:githooks[5] at the
+  current point in time. This may change in the future.
+* linkgit:git-history[1] by default updates all branches that are descendants
+  of the original commit to point to the rewritten commit.
+
+Overall, linkgit:git-history[1] aims to provide a more opinionated way to modify
+your commit history that is simpler to use compared to linkgit:git-rebase[1] in
+general.
+
+If you want to reapply a range of commits onto a different base, or interactive
+rebases if you want to edit a range of commits.
+
+LIMITATIONS
+-----------
+
+This command does not (yet) work with histories that contain merges. You
+should use linkgit:git-rebase[1] with the `--rebase-merges` flag instead.
+
+Furthermore, the command does not support operations that can result in merge
+conflicts. This limitation is by design as history rewrites are not intended to
+be stateful operations. The limitation can be lifted once (if) Git learns about
+first-class conflicts.
+
+COMMANDS
+--------
+
+Several commands are available to rewrite history in different ways:
+
+GIT
+---
+Part of the linkgit:git[1] suite
index f02dbc20cbcb866ca2234212c5e769224f439ea1..fd2e8cc02d689f55332652f98c9527f86e3018d0 100644 (file)
@@ -64,6 +64,7 @@ manpages = {
   'git-gui.adoc' : 1,
   'git-hash-object.adoc' : 1,
   'git-help.adoc' : 1,
+  'git-history.adoc' : 1,
   'git-hook.adoc' : 1,
   'git-http-backend.adoc' : 1,
   'git-http-fetch.adoc' : 1,
index 1c64a5d2aea3e3403893617c06f03e1aa6ba839c..c0569ed8e476d73633b22cf1b8d6c26d96540c45 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1418,6 +1418,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o
 BUILTIN_OBJS += builtin/grep.o
 BUILTIN_OBJS += builtin/hash-object.o
 BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/history.o
 BUILTIN_OBJS += builtin/hook.o
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
index 1b35565fbd9a3ce184fbe1d2e5c682d662bded9d..93c91d07d4bfdc4f3cd101f2bdb7f88bda92598b 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -172,6 +172,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix, struc
 int cmd_grep(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_hash_object(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_help(int argc, const char **argv, const char *prefix, struct repository *repo);
+int cmd_history(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_hook(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_index_pack(int argc, const char **argv, const char *prefix, struct repository *repo);
 int cmd_init_db(int argc, const char **argv, const char *prefix, struct repository *repo);
diff --git a/builtin/history.c b/builtin/history.c
new file mode 100644 (file)
index 0000000..f6fe326
--- /dev/null
@@ -0,0 +1,22 @@
+#include "builtin.h"
+#include "gettext.h"
+#include "parse-options.h"
+
+int cmd_history(int argc,
+               const char **argv,
+               const char *prefix,
+               struct repository *repo UNUSED)
+{
+       const char * const usage[] = {
+               N_("git history [<options>]"),
+               NULL,
+       };
+       struct option options[] = {
+               OPT_END(),
+       };
+
+       argc = parse_options(argc, argv, prefix, options, usage, 0);
+       if (argc)
+               usagef("unrecognized argument: %s", argv[0]);
+       return 0;
+}
index accd3d0c4b55249ad120452711e9d819fa083d45..f9005cf45979f114142a8a8474c8fcfaa3747bf7 100644 (file)
@@ -115,6 +115,7 @@ git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                                ancillaryinterrogators          complete
+git-history                             mainporcelain           history
 git-hook                                purehelpers
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
diff --git a/git.c b/git.c
index c5fad56813f437cac4e3f52a7f96b2dc594fa25a..744cb6527e065e262989d9cd464b476e084e008a 100644 (file)
--- a/git.c
+++ b/git.c
@@ -586,6 +586,7 @@ static struct cmd_struct commands[] = {
        { "grep", cmd_grep, RUN_SETUP_GENTLY },
        { "hash-object", cmd_hash_object },
        { "help", cmd_help },
+       { "history", cmd_history, RUN_SETUP },
        { "hook", cmd_hook, RUN_SETUP },
        { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
        { "init", cmd_init_db },
index a5a4e99b259cf98f9b808d0ebf9dfc64a7f09621..3a1d12caa4b94fd36bec5fdc17db17543368b9da 100644 (file)
@@ -610,6 +610,7 @@ builtin_sources = [
   'builtin/grep.c',
   'builtin/hash-object.c',
   'builtin/help.c',
+  'builtin/history.c',
   'builtin/hook.c',
   'builtin/index-pack.c',
   'builtin/init-db.c',
index 459c52a48972e441e9325616940f5bca13b9ec83..73006b095afc9fcb19d8c6bee3477ffb6967b59b 100644 (file)
@@ -387,6 +387,7 @@ integration_tests = [
   't3436-rebase-more-options.sh',
   't3437-rebase-fixup-options.sh',
   't3438-rebase-broken-files.sh',
+  't3450-history.sh',
   't3500-cherry.sh',
   't3501-revert-cherry-pick.sh',
   't3502-cherry-pick-merge.sh',
diff --git a/t/t3450-history.sh b/t/t3450-history.sh
new file mode 100755 (executable)
index 0000000..417c343
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test_description='tests for git-history command'
+
+. ./test-lib.sh
+
+test_expect_success 'does nothing without any arguments' '
+       git history >out 2>&1 &&
+       test_must_be_empty out
+'
+
+test_expect_success 'raises an error with unknown argument' '
+       test_must_fail git history garbage 2>err &&
+       test_grep "unrecognized argument: garbage" err
+'
+
+test_done