Skip to content

Commit 49e6a7c

Browse files
committed
Merge branch 'ps/history-split'
"git history" learned the "split" subcommand. * ps/history-split: builtin/history: implement "split" subcommand builtin/history: split out extended function to create commits cache-tree: allow writing in-memory index as tree add-patch: allow disabling editing of hunks add-patch: add support for in-memory index patching add-patch: remove dependency on "add-interactive" subsystem add-patch: split out `struct interactive_options` add-patch: split out header from "add-interactive.h"
2 parents dd33e73 + d563ece commit 49e6a7c

File tree

16 files changed

+1592
-341
lines changed

16 files changed

+1592
-341
lines changed

Documentation/git-history.adoc

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ SYNOPSIS
99
--------
1010
[synopsis]
1111
git history reword <commit> [--dry-run] [--update-refs=(branches|head)]
12+
git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]
1213

1314
DESCRIPTION
1415
-----------
@@ -57,6 +58,26 @@ The following commands are available to rewrite history in different ways:
5758
details of this commit remain unchanged. This command will spawn an
5859
editor with the current message of that commit.
5960

61+
`split <commit> [--] [<pathspec>...]`::
62+
Interactively split up <commit> into two commits by choosing
63+
hunks introduced by it that will be moved into the new split-out
64+
commit. These hunks will then be written into a new commit that
65+
becomes the parent of the previous commit. The original commit
66+
stays intact, except that its parent will be the newly split-out
67+
commit.
68+
+
69+
The commit messages of the split-up commits will be asked for by launching
70+
the configured editor. Authorship of the commit will be the same as for the
71+
original commit.
72+
+
73+
If passed, _<pathspec>_ can be used to limit which changes shall be split out
74+
of the original commit. Files not matching any of the pathspecs will remain
75+
part of the original commit. For more details, see the 'pathspec' entry in
76+
linkgit:gitglossary[7].
77+
+
78+
It is invalid to select either all or no hunks, as that would lead to
79+
one of the commits becoming empty.
80+
6081
OPTIONS
6182
-------
6283

@@ -72,6 +93,47 @@ OPTIONS
7293
descendants of the original commit will be rewritten. With `head`, only
7394
the current `HEAD` reference will be rewritten. Defaults to `branches`.
7495

96+
EXAMPLES
97+
--------
98+
99+
Split a commit
100+
~~~~~~~~~~~~~~
101+
102+
----------
103+
$ git log --stat --oneline
104+
3f81232 (HEAD -> main) original
105+
bar | 1 +
106+
foo | 1 +
107+
2 files changed, 2 insertions(+)
108+
109+
$ git history split HEAD
110+
diff --git a/bar b/bar
111+
new file mode 100644
112+
index 0000000..5716ca5
113+
--- /dev/null
114+
+++ b/bar
115+
@@ -0,0 +1 @@
116+
+bar
117+
(1/1) Stage addition [y,n,q,a,d,p,?]? y
118+
119+
diff --git a/foo b/foo
120+
new file mode 100644
121+
index 0000000..257cc56
122+
--- /dev/null
123+
+++ b/foo
124+
@@ -0,0 +1 @@
125+
+foo
126+
(1/1) Stage addition [y,n,q,a,d,p,?]? n
127+
128+
$ git log --stat --oneline
129+
7cebe64 (HEAD -> main) original
130+
foo | 1 +
131+
1 file changed, 1 insertion(+)
132+
d1582f3 split-out commit
133+
bar | 1 +
134+
1 file changed, 1 insertion(+)
135+
----------
136+
75137
GIT
76138
---
77139
Part of the linkgit:git[1] suite

add-interactive.c

Lines changed: 37 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include "git-compat-util.h"
44
#include "add-interactive.h"
55
#include "color.h"
6-
#include "config.h"
76
#include "diffcore.h"
87
#include "gettext.h"
98
#include "hash.h"
@@ -20,120 +19,18 @@
2019
#include "prompt.h"
2120
#include "tree.h"
2221

23-
static void init_color(struct repository *r, enum git_colorbool use_color,
24-
const char *section_and_slot, char *dst,
25-
const char *default_color)
26-
{
27-
char *key = xstrfmt("color.%s", section_and_slot);
28-
const char *value;
29-
30-
if (!want_color(use_color))
31-
dst[0] = '\0';
32-
else if (repo_config_get_value(r, key, &value) ||
33-
color_parse(value, dst))
34-
strlcpy(dst, default_color, COLOR_MAXLEN);
35-
36-
free(key);
37-
}
38-
39-
static enum git_colorbool check_color_config(struct repository *r, const char *var)
40-
{
41-
const char *value;
42-
enum git_colorbool ret;
43-
44-
if (repo_config_get_value(r, var, &value))
45-
ret = GIT_COLOR_UNKNOWN;
46-
else
47-
ret = git_config_colorbool(var, value);
48-
49-
/*
50-
* Do not rely on want_color() to fall back to color.ui for us. It uses
51-
* the value parsed by git_color_config(), which may not have been
52-
* called by the main command.
53-
*/
54-
if (ret == GIT_COLOR_UNKNOWN &&
55-
!repo_config_get_value(r, "color.ui", &value))
56-
ret = git_config_colorbool("color.ui", value);
57-
58-
return ret;
59-
}
60-
6122
void init_add_i_state(struct add_i_state *s, struct repository *r,
62-
struct add_p_opt *add_p_opt)
23+
struct interactive_options *opts)
6324
{
6425
s->r = r;
65-
s->context = -1;
66-
s->interhunkcontext = -1;
67-
s->auto_advance = add_p_opt->auto_advance;
68-
69-
s->use_color_interactive = check_color_config(r, "color.interactive");
70-
71-
init_color(r, s->use_color_interactive, "interactive.header",
72-
s->header_color, GIT_COLOR_BOLD);
73-
init_color(r, s->use_color_interactive, "interactive.help",
74-
s->help_color, GIT_COLOR_BOLD_RED);
75-
init_color(r, s->use_color_interactive, "interactive.prompt",
76-
s->prompt_color, GIT_COLOR_BOLD_BLUE);
77-
init_color(r, s->use_color_interactive, "interactive.error",
78-
s->error_color, GIT_COLOR_BOLD_RED);
79-
strlcpy(s->reset_color_interactive,
80-
want_color(s->use_color_interactive) ? GIT_COLOR_RESET : "", COLOR_MAXLEN);
81-
82-
s->use_color_diff = check_color_config(r, "color.diff");
83-
84-
init_color(r, s->use_color_diff, "diff.frag", s->fraginfo_color,
85-
diff_get_color(s->use_color_diff, DIFF_FRAGINFO));
86-
init_color(r, s->use_color_diff, "diff.context", s->context_color,
87-
"fall back");
88-
if (!strcmp(s->context_color, "fall back"))
89-
init_color(r, s->use_color_diff, "diff.plain",
90-
s->context_color,
91-
diff_get_color(s->use_color_diff, DIFF_CONTEXT));
92-
init_color(r, s->use_color_diff, "diff.old", s->file_old_color,
93-
diff_get_color(s->use_color_diff, DIFF_FILE_OLD));
94-
init_color(r, s->use_color_diff, "diff.new", s->file_new_color,
95-
diff_get_color(s->use_color_diff, DIFF_FILE_NEW));
96-
strlcpy(s->reset_color_diff,
97-
want_color(s->use_color_diff) ? GIT_COLOR_RESET : "", COLOR_MAXLEN);
98-
99-
FREE_AND_NULL(s->interactive_diff_filter);
100-
repo_config_get_string(r, "interactive.difffilter",
101-
&s->interactive_diff_filter);
102-
103-
FREE_AND_NULL(s->interactive_diff_algorithm);
104-
repo_config_get_string(r, "diff.algorithm",
105-
&s->interactive_diff_algorithm);
106-
107-
if (!repo_config_get_int(r, "diff.context", &s->context))
108-
if (s->context < 0)
109-
die(_("%s cannot be negative"), "diff.context");
110-
if (!repo_config_get_int(r, "diff.interHunkContext", &s->interhunkcontext))
111-
if (s->interhunkcontext < 0)
112-
die(_("%s cannot be negative"), "diff.interHunkContext");
113-
114-
repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key);
115-
if (s->use_single_key)
116-
setbuf(stdin, NULL);
117-
118-
if (add_p_opt->context != -1) {
119-
if (add_p_opt->context < 0)
120-
die(_("%s cannot be negative"), "--unified");
121-
s->context = add_p_opt->context;
122-
}
123-
if (add_p_opt->interhunkcontext != -1) {
124-
if (add_p_opt->interhunkcontext < 0)
125-
die(_("%s cannot be negative"), "--inter-hunk-context");
126-
s->interhunkcontext = add_p_opt->interhunkcontext;
127-
}
26+
interactive_config_init(&s->cfg, r, opts);
12827
}
12928

13029
void clear_add_i_state(struct add_i_state *s)
13130
{
132-
FREE_AND_NULL(s->interactive_diff_filter);
133-
FREE_AND_NULL(s->interactive_diff_algorithm);
31+
interactive_config_clear(&s->cfg);
13432
memset(s, 0, sizeof(*s));
135-
s->use_color_interactive = GIT_COLOR_UNKNOWN;
136-
s->use_color_diff = GIT_COLOR_UNKNOWN;
33+
interactive_config_clear(&s->cfg);
13734
}
13835

13936
/*
@@ -287,7 +184,7 @@ static void list(struct add_i_state *s, struct string_list *list, int *selected,
287184
return;
288185

289186
if (opts->header)
290-
color_fprintf_ln(stdout, s->header_color,
187+
color_fprintf_ln(stdout, s->cfg.header_color,
291188
"%s", opts->header);
292189

293190
for (i = 0; i < list->nr; i++) {
@@ -355,7 +252,7 @@ static ssize_t list_and_choose(struct add_i_state *s,
355252

356253
list(s, &items->items, items->selected, &opts->list_opts);
357254

358-
color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
255+
color_fprintf(stdout, s->cfg.prompt_color, "%s", opts->prompt);
359256
fputs(singleton ? "> " : ">> ", stdout);
360257
fflush(stdout);
361258

@@ -433,7 +330,7 @@ static ssize_t list_and_choose(struct add_i_state *s,
433330

434331
if (from < 0 || from >= items->items.nr ||
435332
(singleton && from + 1 != to)) {
436-
color_fprintf_ln(stderr, s->error_color,
333+
color_fprintf_ln(stderr, s->cfg.error_color,
437334
_("Huh (%s)?"), p);
438335
break;
439336
} else if (singleton) {
@@ -993,7 +890,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
993890
free(files->items.items[i].string);
994891
} else if (item->index.unmerged ||
995892
item->worktree.unmerged) {
996-
color_fprintf_ln(stderr, s->error_color,
893+
color_fprintf_ln(stderr, s->cfg.error_color,
997894
_("ignoring unmerged: %s"),
998895
files->items.items[i].string);
999896
free(item);
@@ -1015,10 +912,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
1015912
opts->prompt = N_("Patch update");
1016913
count = list_and_choose(s, files, opts);
1017914
if (count > 0) {
1018-
struct add_p_opt add_p_opt = {
1019-
.context = s->context,
1020-
.interhunkcontext = s->interhunkcontext,
1021-
.auto_advance = s->auto_advance
915+
struct interactive_options opts = {
916+
.context = s->cfg.context,
917+
.interhunkcontext = s->cfg.interhunkcontext,
918+
.auto_advance = s->cfg.auto_advance,
1022919
};
1023920
struct strvec args = STRVEC_INIT;
1024921
struct pathspec ps_selected = { 0 };
@@ -1030,7 +927,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps,
1030927
parse_pathspec(&ps_selected,
1031928
PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
1032929
PATHSPEC_LITERAL_PATH, "", args.v);
1033-
res = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected);
930+
res = run_add_p(s->r, ADD_P_ADD, &opts, NULL, &ps_selected, 0);
1034931
strvec_clear(&args);
1035932
clear_pathspec(&ps_selected);
1036933
}
@@ -1066,10 +963,10 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
1066963
struct child_process cmd = CHILD_PROCESS_INIT;
1067964

1068965
strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL);
1069-
if (s->context != -1)
1070-
strvec_pushf(&cmd.args, "--unified=%i", s->context);
1071-
if (s->interhunkcontext != -1)
1072-
strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext);
966+
if (s->cfg.context != -1)
967+
strvec_pushf(&cmd.args, "--unified=%i", s->cfg.context);
968+
if (s->cfg.interhunkcontext != -1)
969+
strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->cfg.interhunkcontext);
1073970
strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid :
1074971
s->r->hash_algo->empty_tree), "--", NULL);
1075972
for (i = 0; i < files->items.nr; i++)
@@ -1087,39 +984,39 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED,
1087984
struct prefix_item_list *files UNUSED,
1088985
struct list_and_choose_options *opts UNUSED)
1089986
{
1090-
color_fprintf_ln(stdout, s->help_color, "status - %s",
987+
color_fprintf_ln(stdout, s->cfg.help_color, "status - %s",
1091988
_("show paths with changes"));
1092-
color_fprintf_ln(stdout, s->help_color, "update - %s",
989+
color_fprintf_ln(stdout, s->cfg.help_color, "update - %s",
1093990
_("add working tree state to the staged set of changes"));
1094-
color_fprintf_ln(stdout, s->help_color, "revert - %s",
991+
color_fprintf_ln(stdout, s->cfg.help_color, "revert - %s",
1095992
_("revert staged set of changes back to the HEAD version"));
1096-
color_fprintf_ln(stdout, s->help_color, "patch - %s",
993+
color_fprintf_ln(stdout, s->cfg.help_color, "patch - %s",
1097994
_("pick hunks and update selectively"));
1098-
color_fprintf_ln(stdout, s->help_color, "diff - %s",
995+
color_fprintf_ln(stdout, s->cfg.help_color, "diff - %s",
1099996
_("view diff between HEAD and index"));
1100-
color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
997+
color_fprintf_ln(stdout, s->cfg.help_color, "add untracked - %s",
1101998
_("add contents of untracked files to the staged set of changes"));
1102999

11031000
return 0;
11041001
}
11051002

11061003
static void choose_prompt_help(struct add_i_state *s)
11071004
{
1108-
color_fprintf_ln(stdout, s->help_color, "%s",
1005+
color_fprintf_ln(stdout, s->cfg.help_color, "%s",
11091006
_("Prompt help:"));
1110-
color_fprintf_ln(stdout, s->help_color, "1 - %s",
1007+
color_fprintf_ln(stdout, s->cfg.help_color, "1 - %s",
11111008
_("select a single item"));
1112-
color_fprintf_ln(stdout, s->help_color, "3-5 - %s",
1009+
color_fprintf_ln(stdout, s->cfg.help_color, "3-5 - %s",
11131010
_("select a range of items"));
1114-
color_fprintf_ln(stdout, s->help_color, "2-3,6-9 - %s",
1011+
color_fprintf_ln(stdout, s->cfg.help_color, "2-3,6-9 - %s",
11151012
_("select multiple ranges"));
1116-
color_fprintf_ln(stdout, s->help_color, "foo - %s",
1013+
color_fprintf_ln(stdout, s->cfg.help_color, "foo - %s",
11171014
_("select item based on unique prefix"));
1118-
color_fprintf_ln(stdout, s->help_color, "-... - %s",
1015+
color_fprintf_ln(stdout, s->cfg.help_color, "-... - %s",
11191016
_("unselect specified items"));
1120-
color_fprintf_ln(stdout, s->help_color, "* - %s",
1017+
color_fprintf_ln(stdout, s->cfg.help_color, "* - %s",
11211018
_("choose all items"));
1122-
color_fprintf_ln(stdout, s->help_color, " - %s",
1019+
color_fprintf_ln(stdout, s->cfg.help_color, " - %s",
11231020
_("(empty) finish selecting"));
11241021
}
11251022

@@ -1154,7 +1051,7 @@ static void print_command_item(int i, int selected UNUSED,
11541051

11551052
static void command_prompt_help(struct add_i_state *s)
11561053
{
1157-
const char *help_color = s->help_color;
1054+
const char *help_color = s->cfg.help_color;
11581055
color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:"));
11591056
color_fprintf_ln(stdout, help_color, "1 - %s",
11601057
_("select a numbered item"));
@@ -1165,7 +1062,7 @@ static void command_prompt_help(struct add_i_state *s)
11651062
}
11661063

11671064
int run_add_i(struct repository *r, const struct pathspec *ps,
1168-
struct add_p_opt *add_p_opt)
1065+
struct interactive_options *interactive_opts)
11691066
{
11701067
struct add_i_state s = { NULL };
11711068
struct print_command_item_data data = { "[", "]" };
@@ -1208,15 +1105,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps,
12081105
->util = util;
12091106
}
12101107

1211-
init_add_i_state(&s, r, add_p_opt);
1108+
init_add_i_state(&s, r, interactive_opts);
12121109

12131110
/*
12141111
* When color was asked for, use the prompt color for
12151112
* highlighting, otherwise use square brackets.
12161113
*/
1217-
if (want_color(s.use_color_interactive)) {
1218-
data.color = s.prompt_color;
1219-
data.reset = s.reset_color_interactive;
1114+
if (want_color(s.cfg.use_color_interactive)) {
1115+
data.color = s.cfg.prompt_color;
1116+
data.reset = s.cfg.reset_color_interactive;
12201117
}
12211118
print_file_item_data.color = data.color;
12221119
print_file_item_data.reset = data.reset;

0 commit comments

Comments
 (0)