Subject: Re: [PATCH v6 04/11] builtin: add new "history" command

From: Phillip Wood <[email-masked]>
Date: Mon, 17 Nov 2025 16:28:06 +0000

Hi Patrick

On 27/10/2025 11:33, Patrick Steinhardt wrote:
> When rewriting history via git-rebase(1) there are a couple of very

There's more than a couple of items in this list, s/couple of/few/?

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

I'd add editing an existing commit to this list, even if we don't 
implement it initially

> diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc
> new file mode 100644
> index 00000000000..6bdfeb50e8b
> --- /dev/null
> +++ b/Documentation/git-history.adoc
> @@ -0,0 +1,44 @@
> +git-history(1)
> +==============
> +
> +NAME
> +----
> +git-history - EXPERIMENTAL: Rewrite history of the current branch
> +
> +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.

Excellent - keeping our options open is a very good idea

> +This command is similar to linkgit:git-rebase[1] and uses the same
> +underlying machinery.

This isn't strictly true now that we're baisg "git history" on the 
replay machinery.

> You should use rebases 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.
> +
> +Note that this command does not (yet) work with histories that contain
> +merges. You should use linkgit:git-rebase[1] with the `--rebase-merges`
> +flag instead.
> +
> +COMMANDS
> +--------
> +
> +Several commands are available to rewrite history in different ways:
> +
> +CONFIGURATION
> +-------------
> +
> +include::includes/cmd-config-section-all.adoc[]
> +
> +include::config/sequencer.adoc[]

This probably isn't relevant now we're not using the sequencer.

Thanks

Phillip

> +
> +GIT
> +---
> +Part of the linkgit:git[1] suite
> diff --git a/Documentation/meson.build b/Documentation/meson.build
> index 9d24f2da544..d1f6bde7c16 100644
> --- a/Documentation/meson.build
> +++ b/Documentation/meson.build
> @@ -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,
> diff --git a/Makefile b/Makefile
> index 01c171b4f03..1380ee1e196 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -1395,6 +1395,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
> diff --git a/builtin.h b/builtin.h
> index 1b35565fbd9..93c91d07d4b 100644
> --- 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
> index 00000000000..f6fe32610b0
> --- /dev/null
> +++ b/builtin/history.c
> @@ -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;
> +}
> diff --git a/command-list.txt b/command-list.txt
> index accd3d0c4b5..f9005cf4597 100644
> --- a/command-list.txt
> +++ b/command-list.txt
> @@ -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 c5fad56813f..744cb6527e0 100644
> --- 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 },
> diff --git a/meson.build b/meson.build
> index ae8d4fef059..2d789612a01 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -604,6 +604,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',
> diff --git a/t/meson.build b/t/meson.build
> index 401b24e50e0..019435918fa 100644
> --- a/t/meson.build
> +++ b/t/meson.build
> @@ -384,6 +384,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
> index 00000000000..417c343d43b
> --- /dev/null
> +++ b/t/t3450-history.sh
> @@ -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
>