builtin: add new "history" command

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>
This commit is contained in:
Patrick Steinhardt 2026-01-07 11:10:14 +01:00 committed by Junio C Hamano
parent e90167c783
commit 884f4055f3
11 changed files with 103 additions and 0 deletions

1
.gitignore vendored
View File

@ -79,6 +79,7 @@
/git-grep
/git-hash-object
/git-help
/git-history
/git-hook
/git-http-backend
/git-http-fetch

View File

@ -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

View 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,

View File

@ -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

View File

@ -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);

22
builtin/history.c Normal file
View File

@ -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;
}

View 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

1
git.c
View File

@ -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 },

View 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',

View 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',

17
t/t3450-history.sh Executable file
View File

@ -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