Revert "Merge branch 'hn/history-squash' into next"
This reverts commit 2790c83e4582832b6a5279db7d77d6d62a3ae8ad, reversing changes made to 3f0d50209424de9b2a3b57975503a767e485f62a. cf. <f5f7af53-df3e-4902-b350-8fcf8ccb02ad@gmail.com>
Junio C Hamano committed
Jul 29, 2026 at 08:48 UTC
11cd3fbfc2d0190af1a673947e223afbd5e17302
9 files changed
+72
-1412
Documentation/config/advice.adoc
-4
@@ -59,10 +59,6 @@ all advice messages.
59
forceDeleteBranch::
60
Shown when the user tries to delete a not fully merged
61
branch without the force option set.
62
- historyUpdateRefs::
63
- Shown when `git history squash` refuses because a ref points
64
- into the range being folded, to tell the user about
65
- `--update-refs=head`.
62
ignoredHook::
63
Shown when a hook is ignored because the hook is not
64
set as executable.
Documentation/git-history.adoc
+3
-52
@@ -12,7 +12,6 @@ git history drop <commit> [--dry-run] [--update-refs=(branches|head)] [--empty=(
12
git history fixup <commit> [--dry-run] [--update-refs=(branches|head)] [--reedit-message] [--empty=(drop|keep|abort)]
13
git history reword <commit> [--dry-run] [--update-refs=(branches|head)]
14
git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]
15
-git history squash [--dry-run] [--update-refs=(branches|head)] [--reedit-message] <revision-range>
15
16
DESCRIPTION
17
-----------
@@ -44,11 +43,8 @@ at once.
43
LIMITATIONS
44
-----------
45
47
-This command does not (yet) replay merge commits onto the rewritten
48
-history: if a commit that would be replayed is a merge, the operation is
49
-rejected, and you should use linkgit:git-rebase[1] with the
50
-`--rebase-merges` flag instead. The `squash` subcommand can still fold a
51
-merge that lies inside the range, as long as the range has a single base.
46
+This command does not (yet) work with histories that contain merges. You
47
+should use linkgit:git-rebase[1] with the `--rebase-merges` flag instead.
48
49
Furthermore, the command does not support operations that can result in merge
50
conflicts. This limitation is by design as history rewrites are not intended to
@@ -117,50 +113,6 @@ linkgit:gitglossary[7].
113
It is invalid to select either all or no hunks, as that would lead to
114
one of the commits becoming empty.
115
120
-`squash <revision-range>`::
121
- Fold all commits in _<revision-range>_ into the oldest commit of that
122
- range. The resulting commit keeps the oldest commit's authorship and
123
- takes the tree of the range's newest commit, so the whole range
124
- collapses into a single commit. Commits above the range are replayed
125
- on top of the result.
126
-+
127
-The range is given in the usual `<base>..<tip>` form, where _<base>_ is
128
-the commit just below the oldest commit to squash. For example, `git
129
-history squash HEAD~3..HEAD` folds the three most recent commits into
130
-one, and `git history squash HEAD~5..HEAD~2` squashes an interior range
131
-while leaving the two newest commits in place. Several revisions may be
132
-given, for example `HEAD~3..HEAD ^topic` to additionally exclude what is
133
-already on `topic`. Rev-list options may also be given, but any that would
134
-change how the range is walked are overridden with a warning.
135
-+
136
-The oldest commit's message is preserved by default, except that an `amend!`
137
-commit targeting it replaces its message. With `--reedit-message`, an editor
138
-opens pre-filled with the messages of all the folded commits so you can
139
-combine them. A merge commit inside the range is folded like any other, but
140
-the range must have a single base, so a range that reaches more than one entry
141
-point (for example a side branch that forked before the range and was later
142
-merged into it) is rejected.
143
-+
144
-A `fixup!`, `squash!`, or `amend!` commit is refused unless the commit it
145
-targets is also in the range, so the fold does not silently absorb a
146
-marker meant for a commit outside it. As an exception, a range made up entirely
147
-of markers for one target is combined into a single commit, keeping the last
148
-`amend!` message if there is one.
149
-+
150
-With `--reedit-message` the template mirrors `git rebase -i --autosquash`:
151
-each `fixup!`, `squash!`, or `amend!` is grouped under the commit it
152
-targets rather than shown in commit order. A `fixup!` message is dropped
153
-(commented out in full), a `squash!` keeps its body with only the marker
154
-subject commented, and an `amend!` replaces its target's message, unless
155
-a `squash!` folded into that target first, in which case it keeps its
156
-body like a `squash!`.
157
-+
158
-A branch or tag that points at a commit inside the range would be left
159
-dangling once those commits are folded away, so with the default
160
-`--update-refs=branches` the command refuses. Rerun with
161
-`--update-refs=head` to rewrite only the current branch and leave such
162
-refs pointing at the old commits.
163
-
116
OPTIONS
117
-------
118
@@ -171,8 +123,7 @@ OPTIONS
123
ref updates is generally safe.
124
125
`--reedit-message`::
174
- Open an editor to modify the rewritten commit's message. For `squash`
175
- the editor is pre-filled with the messages of all the folded commits.
126
+ Open an editor to modify the target commit's message.
127
128
`--empty=(drop|keep|abort)`::
129
Control what happens when a commit becomes empty as a result of the
advice.c
-1
@@ -58,7 +58,6 @@ static struct {
58
[ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates" },
59
[ADVICE_FORCE_DELETE_BRANCH] = { "forceDeleteBranch" },
60
[ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated" },
61
- [ADVICE_HISTORY_UPDATE_REFS] = { "historyUpdateRefs" },
61
[ADVICE_IGNORED_HOOK] = { "ignoredHook" },
62
[ADVICE_IMPLICIT_IDENTITY] = { "implicitIdentity" },
63
[ADVICE_MERGE_CONFLICT] = { "mergeConflict" },
advice.h
-1
@@ -25,7 +25,6 @@ enum advice_type {
25
ADVICE_FETCH_SHOW_FORCED_UPDATES,
26
ADVICE_FORCE_DELETE_BRANCH,
27
ADVICE_GRAFT_FILE_DEPRECATED,
28
- ADVICE_HISTORY_UPDATE_REFS,
28
ADVICE_IGNORED_HOOK,
29
ADVICE_IMPLICIT_IDENTITY,
30
ADVICE_MERGE_CONFLICT,
builtin/history.c
+38
-514
@@ -1,7 +1,6 @@
1
#define USE_THE_REPOSITORY_VARIABLE
2
3
#include "builtin.h"
4
-#include "advice.h"
4
#include "cache-tree.h"
5
#include "commit.h"
6
#include "commit-reach.h"
@@ -35,8 +34,6 @@
34
N_("git history reword <commit> [--dry-run] [--update-refs=(branches|head)]")
35
#define GIT_HISTORY_SPLIT_USAGE \
36
N_("git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]")
38
-#define GIT_HISTORY_SQUASH_USAGE \
39
- N_("git history squash [--dry-run] [--update-refs=(branches|head)] [--reedit-message] <revision-range>")
37
38
static void change_data_free(void *util, const char *str UNUSED)
39
{
@@ -111,7 +108,6 @@ enum commit_tree_flags {
108
static int commit_tree_ext(struct repository *repo,
109
const char *action,
110
struct commit *commit_with_message,
114
- const char *message_template,
111
const struct commit_list *parents,
112
const struct object_id *old_tree,
113
const struct object_id *new_tree,
@@ -141,16 +137,13 @@ static int commit_tree_ext(struct repository *repo,
137
original_author = xmemdupz(ptr, len);
138
find_commit_subject(original_message, &original_body);
139
144
- if (!message_template)
145
- message_template = original_body;
146
-
140
if (flags & COMMIT_TREE_EDIT_MESSAGE) {
141
ret = fill_commit_message(repo, old_tree, new_tree,
149
- message_template, action, &commit_message);
142
+ original_body, action, &commit_message);
143
if (ret < 0)
144
goto out;
145
} else {
153
- strbuf_addstr(&commit_message, message_template);
146
+ strbuf_addstr(&commit_message, original_body);
147
}
148
149
original_extra_headers = read_commit_extra_headers(commit_with_message,
@@ -171,25 +164,6 @@ out:
164
return ret;
165
}
166
174
-static int first_parent_tree_oid(struct repository *repo,
175
- struct commit *commit,
176
- struct object_id *out)
177
-{
178
- struct commit *parent = commit->parents ? commit->parents->item : NULL;
179
-
180
- if (!parent) {
181
- oidcpy(out, repo->hash_algo->empty_tree);
182
- return 0;
183
- }
184
-
185
- if (repo_parse_commit(repo, parent))
186
- return error(_("unable to parse parent commit %s"),
187
- oid_to_hex(&parent->object.oid));
188
-
189
- oidcpy(out, &repo_get_commit_tree(repo, parent)->object.oid);
190
- return 0;
191
-}
192
-
167
static int commit_tree_with_edited_message(struct repository *repo,
168
const char *action,
169
struct commit *original,
@@ -197,13 +171,23 @@ static int commit_tree_with_edited_message(struct repository *repo,
171
{
172
struct object_id parent_tree_oid;
173
const struct object_id *tree_oid;
174
+ struct commit *parent;
175
176
tree_oid = &repo_get_commit_tree(repo, original)->object.oid;
177
203
- if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0)
204
- return -1;
178
+ parent = original->parents ? original->parents->item : NULL;
179
+ if (parent) {
180
+ if (repo_parse_commit(repo, parent)) {
181
+ return error(_("unable to parse parent commit %s"),
182
+ oid_to_hex(&parent->object.oid));
183
+ }
184
+
185
+ parent_tree_oid = repo_get_commit_tree(repo, parent)->object.oid;
186
+ } else {
187
+ oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree);
188
+ }
189
206
- return commit_tree_ext(repo, action, original, NULL, original->parents,
190
+ return commit_tree_ext(repo, action, original, original->parents,
191
&parent_tree_oid, tree_oid, out, COMMIT_TREE_EDIT_MESSAGE);
192
}
193
@@ -491,10 +475,18 @@ static int commit_became_empty(struct repository *repo,
475
struct commit *original,
476
struct tree *result)
477
{
478
+ struct commit *parent = original->parents ? original->parents->item : NULL;
479
struct object_id parent_tree_oid;
480
496
- if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0)
497
- return -1;
481
+ if (parent) {
482
+ if (repo_parse_commit(repo, parent))
483
+ return error(_("unable to parse parent of %s"),
484
+ oid_to_hex(&original->object.oid));
485
+
486
+ parent_tree_oid = repo_get_commit_tree(repo, parent)->object.oid;
487
+ } else {
488
+ oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree);
489
+ }
490
491
return oideq(&result->object.oid, &parent_tree_oid);
492
}
@@ -682,7 +674,7 @@ static int cmd_history_fixup(int argc,
674
goto out;
675
676
if (!skip_commit) {
685
- ret = commit_tree_ext(repo, "fixup", original, NULL, original->parents,
677
+ ret = commit_tree_ext(repo, "fixup", original, original->parents,
678
&original_tree->object.oid, &merge_result.tree->object.oid,
679
&rewritten, flags);
680
if (ret < 0) {
@@ -838,9 +830,16 @@ static int split_commit(struct repository *repo,
830
struct tree *split_tree;
831
int ret;
832
841
- if (first_parent_tree_oid(repo, original, &parent_tree_oid) < 0) {
842
- ret = -1;
843
- goto out;
833
+ if (original->parents) {
834
+ if (repo_parse_commit(repo, original->parents->item)) {
835
+ ret = error(_("unable to parse parent commit %s"),
836
+ oid_to_hex(&original->parents->item->object.oid));
837
+ goto out;
838
+ }
839
+
840
+ parent_tree_oid = *get_commit_tree_oid(original->parents->item);
841
+ } else {
842
+ oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree);
843
}
844
original_commit_tree_oid = get_commit_tree_oid(original);
845
@@ -893,7 +892,7 @@ static int split_commit(struct repository *repo,
892
* The first commit is constructed from the split-out tree. The base
893
* that shall be diffed against is the parent of the original commit.
894
*/
896
- ret = commit_tree_ext(repo, "split-out", original, NULL, original->parents, &parent_tree_oid,
895
+ ret = commit_tree_ext(repo, "split-out", original, original->parents, &parent_tree_oid,
896
&split_tree->object.oid, &first_commit, COMMIT_TREE_EDIT_MESSAGE);
897
if (ret < 0) {
898
ret = error(_("failed writing first commit"));
@@ -910,7 +909,7 @@ static int split_commit(struct repository *repo,
909
old_tree_oid = &repo_get_commit_tree(repo, first_commit)->object.oid;
910
new_tree_oid = &repo_get_commit_tree(repo, original)->object.oid;
911
913
- ret = commit_tree_ext(repo, "split-out", original, NULL, parents, old_tree_oid,
912
+ ret = commit_tree_ext(repo, "split-out", original, parents, old_tree_oid,
913
new_tree_oid, &second_commit, COMMIT_TREE_EDIT_MESSAGE);
914
if (ret < 0) {
915
ret = error(_("failed writing second commit"));
@@ -1007,479 +1006,6 @@ out:
1006
return ret;
1007
}
1008
1010
-/*
1011
- * Resolve a "<base>..<tip>" revision range into the base commit just outside
1012
- * the range (which becomes the parent of the squashed commit), the oldest
1013
- * commit contained in the range (whose message the squash reuses), and the
1014
- * range tip (whose tree becomes the result). A merge inside the range is fine,
1015
- * but the range must have a single base and must not reach a root commit.
1016
- */
1017
-static int resolve_squash_range(struct repository *repo,
1018
- const char **argv,
1019
- struct commit **base_out,
1020
- struct commit **oldest_out,
1021
- struct commit **tip_out,
1022
- struct oidset *interior_out)
1023
-{
1024
- struct rev_info revs;
1025
- struct commit *commit, *base = NULL, *oldest = NULL, *tip = NULL;
1026
- struct commit_list *boundaries = NULL, *b;
1027
- struct strvec args = STRVEC_INIT;
1028
- size_t i;
1029
- int ret;
1030
-
1031
- repo_init_revisions(repo, &revs, NULL);
1032
- revs.reverse = 1;
1033
- revs.topo_order = 1;
1034
- revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
1035
- revs.simplify_history = 0;
1036
- revs.boundary = 1;
1037
-
1038
- strvec_push(&args, "ignored");
1039
- strvec_push(&args, "--ancestry-path");
1040
- strvec_pushv(&args, argv);
1041
- setup_revisions_from_strvec(&args, &revs, NULL);
1042
- if (args.nr != 1) {
1043
- ret = error(_("unrecognized argument: %s"), args.v[1]);
1044
- goto out;
1045
- }
1046
-
1047
- if (revs.reverse != 1 || revs.topo_order != 1 ||
1048
- revs.sort_order != REV_SORT_IN_GRAPH_ORDER ||
1049
- revs.simplify_history != 0 || revs.boundary != 1) {
1050
- warning(_("ignoring rev-list options that would change how the "
1051
- "range is walked"));
1052
- revs.reverse = 1;
1053
- revs.topo_order = 1;
1054
- revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
1055
- revs.simplify_history = 0;
1056
- revs.boundary = 1;
1057
- }
1058
-
1059
- /*
1060
- * A squash needs a base to reparent onto, so the range has to exclude
1061
- * something, as in "<base>..<tip>". A revision range with no such
1062
- * bottom commit cannot be squashed.
1063
- */
1064
- for (i = 0; i < revs.cmdline.nr; i++)
1065
- if (revs.cmdline.rev[i].flags & UNINTERESTING)
1066
- break;
1067
- if (i == revs.cmdline.nr) {
1068
- ret = error(_("not a '<base>..<tip>' revision range"));
1069
- goto out;
1070
- }
1071
-
1072
- if (prepare_revision_walk(&revs) < 0) {
1073
- ret = error(_("error preparing revisions"));
1074
- goto out;
1075
- }
1076
-
1077
- /*
1078
- * Set boundary commits aside for the base check below, and put every
1079
- * in-range commit but the tip into the interior set. A ref pointing
1080
- * at an interior commit would dangle once the range is folded away.
1081
- */
1082
- while ((commit = get_revision(&revs))) {
1083
- if (commit->object.flags & BOUNDARY) {
1084
- commit_list_insert(commit, &boundaries);
1085
- continue;
1086
- }
1087
- if (!oldest)
1088
- oldest = commit;
1089
- if (tip)
1090
- oidset_insert(interior_out, &tip->object.oid);
1091
- tip = commit;
1092
- }
1093
-
1094
- if (!oldest) {
1095
- ret = error(_("the revision range is empty"));
1096
- goto out;
1097
- } else if (oldest == tip) {
1098
- ret = error(_("the revision range holds a single commit; "
1099
- "nothing to squash"));
1100
- goto out;
1101
- } else if (!oldest->parents) {
1102
- BUG("an in-range commit must have a parent");
1103
- }
1104
- base = oldest->parents->item;
1105
-
1106
- /*
1107
- * A boundary other than the base is an in-range commit reaching a
1108
- * commit outside the range, so the range has more than one base.
1109
- */
1110
- for (b = boundaries; b; b = b->next) {
1111
- if (b->item != base) {
1112
- ret = error(_("the revision range has more than one base; "
1113
- "cannot squash"));
1114
- goto out;
1115
- }
1116
- }
1117
-
1118
- *base_out = base;
1119
- *oldest_out = oldest;
1120
- *tip_out = tip;
1121
- ret = 0;
1122
-
1123
-out:
1124
- commit_list_free(boundaries);
1125
- reset_revision_walk();
1126
- release_revisions(&revs);
1127
- strvec_clear(&args);
1128
- return ret;
1129
-}
1130
-
1131
-static const char *autosquash_target(const char *subject)
1132
-{
1133
- const char *rest;
1134
-
1135
- while (skip_prefix(subject, "fixup! ", &rest) ||
1136
- skip_prefix(subject, "squash! ", &rest) ||
1137
- skip_prefix(subject, "amend! ", &rest))
1138
- subject = rest;
1139
- return subject;
1140
-}
1141
-
1142
-static int reject_dangling_fixups(struct repository *repo,
1143
- struct commit *base,
1144
- struct commit *tip,
1145
- struct commit *oldest,
1146
- struct commit **msg_source,
1147
- struct commit **amend_source)
1148
-{
1149
- struct todo_list todo = TODO_LIST_INIT;
1150
- struct replay_opts opts = REPLAY_OPTS_INIT;
1151
- struct rev_info revs;
1152
- struct commit *commit, *last_amend = NULL;
1153
- struct strvec args = STRVEC_INIT;
1154
- char *dangling_subject = NULL, *dangling_target = NULL;
1155
- bool mixed_target = false, all_fixups_one_target;
1156
- bool past_oldest_group = false;
1157
- int i, ret, nr_dangling = 0;
1158
-
1159
- *msg_source = oldest;
1160
- *amend_source = NULL;
1161
-
1162
- repo_init_revisions(repo, &revs, NULL);
1163
- strvec_push(&args, "ignored");
1164
- strvec_push(&args, "--reverse");
1165
- strvec_push(&args, "--topo-order");
1166
- strvec_pushf(&args, "%s..%s", oid_to_hex(&base->object.oid),
1167
- oid_to_hex(&tip->object.oid));
1168
- setup_revisions_from_strvec(&args, &revs, NULL);
1169
-
1170
- if (prepare_revision_walk(&revs) < 0) {
1171
- ret = error(_("error preparing revisions"));
1172
- goto out;
1173
- }
1174
- while ((commit = get_revision(&revs)))
1175
- strbuf_addf(&todo.buf, "pick %s\n",
1176
- oid_to_hex(&commit->object.oid));
1177
-
1178
- if (todo_list_parse_insn_buffer(repo, &opts, todo.buf.buf, &todo) < 0 ||
1179
- todo_list_rearrange_squash(&todo) < 0) {
1180
- ret = error(_("could not check the range for fixups"));
1181
- goto out;
1182
- }
1183
-
1184
- for (i = 0; i < todo.nr; i++) {
1185
- const char *message, *subject_start, *target;
1186
- char *subject;
1187
- size_t sublen;
1188
-
1189
- message = repo_logmsg_reencode(repo, todo.items[i].commit,
1190
- NULL, NULL);
1191
- sublen = find_commit_subject(message, &subject_start);
1192
-
1193
- if (todo.items[i].command != TODO_PICK) {
1194
- if (!past_oldest_group &&
1195
- starts_with(subject_start, "amend! "))
1196
- *amend_source = todo.items[i].commit;
1197
- repo_unuse_commit_buffer(repo, todo.items[i].commit, message);
1198
- continue;
1199
- }
1200
- if (i)
1201
- past_oldest_group = true;
1202
-
1203
- subject = xmemdupz(subject_start, sublen);
1204
- target = autosquash_target(subject);
1205
- if (target != subject) {
1206
- nr_dangling++;
1207
- if (!dangling_target) {
1208
- dangling_target = xstrdup(target);
1209
- dangling_subject = xstrdup(subject);
1210
- } else if (strcmp(dangling_target, target)) {
1211
- mixed_target = true;
1212
- }
1213
- if (starts_with(subject, "amend! "))
1214
- last_amend = todo.items[i].commit;
1215
- }
1216
- free(subject);
1217
- repo_unuse_commit_buffer(repo, todo.items[i].commit, message);
1218
- }
1219
-
1220
- all_fixups_one_target = nr_dangling == todo.nr && !mixed_target;
1221
- if (nr_dangling && !all_fixups_one_target) {
1222
- ret = error(_("cannot squash '%s': its target is not in the "
1223
- "range"), dangling_subject);
1224
- } else {
1225
- if (last_amend)
1226
- *msg_source = last_amend;
1227
- ret = 0;
1228
- }
1229
-
1230
-out:
1231
- free(dangling_subject);
1232
- free(dangling_target);
1233
- todo_list_release(&todo);
1234
- replay_opts_release(&opts);
1235
- reset_revision_walk();
1236
- release_revisions(&revs);
1237
- strvec_clear(&args);
1238
- return ret;
1239
-}
1240
-
1241
-struct interior_ref_cb {
1242
- const struct oidset *interior;
1243
- const char *name;
1244
-};
1245
-
1246
-static int find_interior_ref(const struct reference *ref, void *cb_data)
1247
-{
1248
- struct interior_ref_cb *data = cb_data;
1249
-
1250
- if (oidset_contains(data->interior, ref->oid)) {
1251
- data->name = xstrdup(ref->name);
1252
- return 1;
1253
- }
1254
-
1255
- return 0;
1256
-}
1257
-
1258
-static bool amend_replaces_target(struct todo_list *todo, int target)
1259
-{
1260
- int i;
1261
-
1262
- for (i = target + 1; i < todo->nr &&
1263
- todo->items[i].command != TODO_PICK; i++) {
1264
- if (todo->items[i].command == TODO_SQUASH)
1265
- return false;
1266
- if (todo->items[i].flags & TODO_REPLACE_FIXUP_MSG)
1267
- return true;
1268
- }
1269
- return false;
1270
-}
1271
-
1272
-static int build_squash_message(struct repository *repo,
1273
- struct commit *base,
1274
- struct commit *tip,
1275
- struct strbuf *out)
1276
-{
1277
- struct rev_info revs;
1278
- struct commit *commit;
1279
- struct strvec args = STRVEC_INIT;
1280
- struct todo_list todo = TODO_LIST_INIT;
1281
- struct replay_opts opts = REPLAY_OPTS_INIT;
1282
- int i, nr_commits, ret;
1283
-
1284
- repo_init_revisions(repo, &revs, NULL);
1285
- strvec_push(&args, "ignored");
1286
- strvec_push(&args, "--reverse");
1287
- strvec_push(&args, "--topo-order");
1288
- strvec_pushf(&args, "%s..%s", oid_to_hex(&base->object.oid),
1289
- oid_to_hex(&tip->object.oid));
1290
- setup_revisions_from_strvec(&args, &revs, NULL);
1291
-
1292
- if (prepare_revision_walk(&revs) < 0) {
1293
- ret = error(_("error preparing revisions"));
1294
- goto out;
1295
- }
1296
-
1297
- while ((commit = get_revision(&revs)))
1298
- strbuf_addf(&todo.buf, "pick %s\n",
1299
- oid_to_hex(&commit->object.oid));
1300
-
1301
- if (todo_list_parse_insn_buffer(repo, &opts, todo.buf.buf, &todo) < 0 ||
1302
- todo_list_rearrange_squash(&todo) < 0) {
1303
- ret = error(_("could not prepare the squash message"));
1304
- goto out;
1305
- }
1306
-
1307
- nr_commits = todo.nr;
1308
- for (i = 0; i < nr_commits; i++) {
1309
- struct todo_item *item = &todo.items[i];
1310
- const char *message, *body;
1311
- size_t commented_len;
1312
- bool skip, squashing;
1313
-
1314
- squashing = item->command == TODO_SQUASH ||
1315
- (item->flags & TODO_REPLACE_FIXUP_MSG);
1316
- if (item->command == TODO_PICK)
1317
- skip = amend_replaces_target(&todo, i);
1318
- else
1319
- skip = !squashing;
1320
-
1321
- message = repo_logmsg_reencode(repo, item->commit, NULL, NULL);
1322
- find_commit_subject(message, &body);
1323
-
1324
- if (skip)
1325
- commented_len = strlen(body);
1326
- else if (squashing)
1327
- commented_len = squash_subject_comment_len(body, 1);
1328
- else
1329
- commented_len = 0;
1330
-
1331
- if (!i)
1332
- add_squash_combination_header(out, nr_commits);
1333
- strbuf_addch(out, '\n');
1334
- add_squash_message_header(out, i + 1, skip);
1335
- strbuf_addstr(out, "\n\n");
1336
- strbuf_add_commented_lines(out, body, commented_len, comment_line_str);
1337
- strbuf_addstr(out, body + commented_len);
1338
- strbuf_complete_line(out);
1339
-
1340
- repo_unuse_commit_buffer(repo, item->commit, message);
1341
- }
1342
-
1343
- ret = 0;
1344
-
1345
-out:
1346
- todo_list_release(&todo);
1347
- replay_opts_release(&opts);
1348
- reset_revision_walk();
1349
- release_revisions(&revs);
1350
- strvec_clear(&args);
1351
- return ret;
1352
-}
1353
-
1354
-static int cmd_history_squash(int argc,
1355
- const char **argv,
1356
- const char *prefix,
1357
- struct repository *repo)
1358
-{
1359
- const char * const usage[] = {
1360
- GIT_HISTORY_SQUASH_USAGE,
1361
- NULL,
1362
- };
1363
- enum ref_action action = REF_ACTION_DEFAULT;
1364
- enum commit_tree_flags flags = 0;
1365
- int dry_run = 0;
1366
- struct option options[] = {
1367
- OPT_CALLBACK_F(0, "update-refs", &action, "(branches|head)",
1368
- N_("control which refs should be updated"),
1369
- PARSE_OPT_NONEG, parse_ref_action),
1370
- OPT_BOOL('n', "dry-run", &dry_run,
1371
- N_("perform a dry-run without updating any refs")),
1372
- OPT_BIT(0, "reedit-message", &flags,
1373
- N_("open an editor to modify the commit message"),
1374
- COMMIT_TREE_EDIT_MESSAGE),
1375
- OPT_END(),
1376
- };
1377
- struct strbuf reflog_msg = STRBUF_INIT;
1378
- struct strbuf message = STRBUF_INIT;
1379
- struct oidset interior = OIDSET_INIT;
1380
- struct commit *base, *oldest, *tip, *rewritten, *msg_source,
1381
- *amend_source;
1382
- const struct object_id *base_tree_oid, *tip_tree_oid;
1383
- const char *message_template = NULL;
1384
- struct commit_list *parents = NULL;
1385
- struct rev_info revs = { 0 };
1386
- int ret;
1387
-
1388
- argc = parse_options(argc, argv, prefix, options, usage,
1389
- PARSE_OPT_KEEP_UNKNOWN_OPT);
1390
- if (!argc) {
1391
- ret = error(_("command expects a revision range"));
1392
- goto out;
1393
- }
1394
- repo_config(repo, git_default_config, NULL);
1395
-
1396
- if (action == REF_ACTION_DEFAULT)
1397
- action = REF_ACTION_BRANCHES;
1398
-
1399
- ret = resolve_squash_range(repo, argv, &base, &oldest, &tip,
1400
- &interior);
1401
- if (ret < 0)
1402
- goto out;
1403
-
1404
- ret = reject_dangling_fixups(repo, base, tip, oldest, &msg_source,
1405
- &amend_source);
1406
- if (ret < 0)
1407
- goto out;
1408
- if (amend_source) {
1409
- const char *amend_message, *body;
1410
-
1411
- amend_message = repo_logmsg_reencode(repo, amend_source,
1412
- NULL, NULL);
1413
- find_commit_subject(amend_message, &body);
1414
- body = skip_blank_lines(body + commit_subject_length(body));
1415
- strbuf_addstr(&message, body);
1416
- message_template = message.buf;
1417
- repo_unuse_commit_buffer(repo, amend_source, amend_message);
1418
- }
1419
-
1420
- if (action == REF_ACTION_BRANCHES) {
1421
- struct interior_ref_cb cb = { .interior = &interior };
1422
-
1423
- refs_for_each_ref(get_main_ref_store(repo),
1424
- find_interior_ref, &cb);
1425
- if (cb.name) {
1426
- ret = error(_("'%s' points into the squashed range"),
1427
- cb.name);
1428
- advise_if_enabled(ADVICE_HISTORY_UPDATE_REFS,
1429
- _("Use --update-refs=head to rewrite only "
1430
- "the current branch and leave such refs "
1431
- "untouched."));
1432
- free((char *)cb.name);
1433
- goto out;
1434
- }
1435
- }
1436
-
1437
- if (flags & COMMIT_TREE_EDIT_MESSAGE) {
1438
- strbuf_reset(&message);
1439
- ret = build_squash_message(repo, base, tip, &message);
1440
- if (ret < 0)
1441
- goto out;
1442
- message_template = message.buf;
1443
- }
1444
-
1445
- ret = setup_revwalk(repo, action, tip, &revs);
1446
- if (ret < 0)
1447
- goto out;
1448
-
1449
- base_tree_oid = &repo_get_commit_tree(repo, base)->object.oid;
1450
- tip_tree_oid = &repo_get_commit_tree(repo, tip)->object.oid;
1451
- commit_list_append(base, &parents);
1452
-
1453
- ret = commit_tree_ext(repo, "squash", msg_source, message_template,
1454
- parents,
1455
- base_tree_oid, tip_tree_oid, &rewritten, flags);
1456
- if (ret < 0) {
1457
- ret = error(_("failed writing squashed commit"));
1458
- goto out;
1459
- }
1460
-
1461
- strbuf_addstr(&reflog_msg, "squash: updating ");
1462
- strbuf_join_argv(&reflog_msg, argc, argv, ' ');
1463
-
1464
- ret = handle_reference_updates(&revs, action, tip, rewritten,
1465
- reflog_msg.buf, dry_run,
1466
- REPLAY_EMPTY_COMMIT_ABORT);
1467
- if (ret < 0) {
1468
- ret = error(_("failed replaying descendants"));
1469
- goto out;
1470
- }
1471
-
1472
- ret = 0;
1473
-
1474
-out:
1475
- strbuf_release(&reflog_msg);
1476
- strbuf_release(&message);
1477
- oidset_clear(&interior);
1478
- commit_list_free(parents);
1479
- release_revisions(&revs);
1480
- return ret;
1481
-}
1482
-
1009
static int update_worktree(struct repository *repo,
1010
const struct commit *old_head,
1011
const struct commit *new_head,
@@ -1668,7 +1194,6 @@ int cmd_history(int argc,
1194
GIT_HISTORY_FIXUP_USAGE,
1195
GIT_HISTORY_REWORD_USAGE,
1196
GIT_HISTORY_SPLIT_USAGE,
1671
- GIT_HISTORY_SQUASH_USAGE,
1197
NULL,
1198
};
1199
parse_opt_subcommand_fn *fn = NULL;
@@ -1677,7 +1202,6 @@ int cmd_history(int argc,
1202
OPT_SUBCOMMAND("fixup", &fn, cmd_history_fixup),
1203
OPT_SUBCOMMAND("reword", &fn, cmd_history_reword),
1204
OPT_SUBCOMMAND("split", &fn, cmd_history_split),
1680
- OPT_SUBCOMMAND("squash", &fn, cmd_history_squash),
1205
OPT_END(),
1206
};
1207
sequencer.c
+31
-39
@@ -1880,38 +1880,18 @@ static int is_pick_or_similar(enum todo_command command)
1880
}
1881
}
1882
1883
+enum todo_item_flags {
1884
+ TODO_EDIT_MERGE_MSG = (1 << 0),
1885
+ TODO_REPLACE_FIXUP_MSG = (1 << 1),
1886
+ TODO_EDIT_FIXUP_MSG = (1 << 2),
1887
+};
1888
+
1889
static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
1890
static const char nth_commit_msg_fmt[] = N_("This is the commit message #%d:");
1891
static const char skip_first_commit_msg_str[] = N_("The 1st commit message will be skipped:");
1892
static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:");
1893
static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits.");
1894
1889
-void add_squash_combination_header(struct strbuf *buf, int n)
1890
-{
1891
- strbuf_addf(buf, "%s ", comment_line_str);
1892
- strbuf_addf(buf, _(combined_commit_msg_fmt), n);
1893
-}
1894
-
1895
-void add_squash_message_header(struct strbuf *buf, int n, int skip)
1896
-{
1897
- strbuf_addf(buf, "%s ", comment_line_str);
1898
- if (n == 1)
1899
- strbuf_addstr(buf, skip ? _(skip_first_commit_msg_str) :
1900
- _(first_commit_msg_str));
1901
- else
1902
- strbuf_addf(buf, skip ? _(skip_nth_commit_msg_fmt) :
1903
- _(nth_commit_msg_fmt), n);
1904
-}
1905
-
1906
-size_t squash_subject_comment_len(const char *body, int squashing)
1907
-{
1908
- if (starts_with(body, "amend!") ||
1909
- (squashing && (starts_with(body, "squash!") ||
1910
- starts_with(body, "fixup!"))))
1911
- return commit_subject_length(body);
1912
- return 0;
1913
-}
1914
-
1895
static int is_fixup_flag(enum todo_command command, unsigned flag)
1896
{
1897
return command == TODO_FIXUP && ((flag & TODO_REPLACE_FIXUP_MSG) ||
@@ -2032,13 +2012,20 @@ static int append_squash_message(struct strbuf *buf, const char *body,
2012
{
2013
struct replay_ctx *ctx = opts->ctx;
2014
const char *fixup_msg;
2035
- size_t commented_len, fixup_off;
2036
-
2037
- commented_len = squash_subject_comment_len(body,
2038
- command == TODO_SQUASH || seen_squash(ctx));
2015
+ size_t commented_len = 0, fixup_off;
2016
+ /*
2017
+ * amend is non-interactive and not normally used with fixup!
2018
+ * or squash! commits, so only comment out those subjects when
2019
+ * squashing commit messages.
2020
+ */
2021
+ if (starts_with(body, "amend!") ||
2022
+ ((command == TODO_SQUASH || seen_squash(ctx)) &&
2023
+ (starts_with(body, "squash!") || starts_with(body, "fixup!"))))
2024
+ commented_len = commit_subject_length(body);
2025
2040
- strbuf_addch(buf, '\n');
2041
- add_squash_message_header(buf, ++ctx->current_fixup_count + 1, 0);
2026
+ strbuf_addf(buf, "\n%s ", comment_line_str);
2027
+ strbuf_addf(buf, _(nth_commit_msg_fmt),
2028
+ ++ctx->current_fixup_count + 1);
2029
strbuf_addstr(buf, "\n\n");
2030
strbuf_add_commented_lines(buf, body, commented_len, comment_line_str);
2031
/* buf->buf may be reallocated so store an offset into the buffer */
@@ -2103,8 +2090,9 @@ static int update_squash_messages(struct repository *r,
2090
eol = !starts_with(buf.buf, comment_line_str) ?
2091
buf.buf : strchrnul(buf.buf, '\n');
2092
2106
- add_squash_combination_header(&header,
2107
- ctx->current_fixup_count + 2);
2093
+ strbuf_addf(&header, "%s ", comment_line_str);
2094
+ strbuf_addf(&header, _(combined_commit_msg_fmt),
2095
+ ctx->current_fixup_count + 2);
2096
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
2097
strbuf_release(&header);
2098
if (is_fixup_flag(command, flag) && !seen_squash(ctx))
@@ -2128,9 +2116,12 @@ static int update_squash_messages(struct repository *r,
2116
repo_unuse_commit_buffer(r, head_commit, head_message);
2117
return error(_("cannot write '%s'"), rebase_path_fixup_msg());
2118
}
2131
- add_squash_combination_header(&buf, 2);
2132
- strbuf_addch(&buf, '\n');
2133
- add_squash_message_header(&buf, 1, is_fixup_flag(command, flag));
2119
+ strbuf_addf(&buf, "%s ", comment_line_str);
2120
+ strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
2121
+ strbuf_addf(&buf, "\n%s ", comment_line_str);
2122
+ strbuf_addstr(&buf, is_fixup_flag(command, flag) ?
2123
+ _(skip_first_commit_msg_str) :
2124
+ _(first_commit_msg_str));
2125
strbuf_addstr(&buf, "\n\n");
2126
if (is_fixup_flag(command, flag))
2127
strbuf_add_commented_lines(&buf, body, strlen(body),
@@ -2149,8 +2140,9 @@ static int update_squash_messages(struct repository *r,
2140
if (command == TODO_SQUASH || is_fixup_flag(command, flag)) {
2141
res = append_squash_message(&buf, body, command, opts, flag);
2142
} else if (command == TODO_FIXUP) {
2152
- strbuf_addch(&buf, '\n');
2153
- add_squash_message_header(&buf, ++ctx->current_fixup_count + 1, 1);
2143
+ strbuf_addf(&buf, "\n%s ", comment_line_str);
2144
+ strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
2145
+ ++ctx->current_fixup_count + 1);
2146
strbuf_addstr(&buf, "\n\n");
2147
strbuf_add_commented_lines(&buf, body, strlen(body),
2148
comment_line_str);
sequencer.h
-30
@@ -119,13 +119,6 @@ enum todo_command {
119
TODO_COMMENT
120
};
121
122
-/* Bits for the "flags" member of struct todo_item */
123
-enum todo_item_flags {
124
- TODO_EDIT_MERGE_MSG = (1 << 0),
125
- TODO_REPLACE_FIXUP_MSG = (1 << 1),
126
- TODO_EDIT_FIXUP_MSG = (1 << 2),
127
-};
128
-
122
struct todo_item {
123
enum todo_command command;
124
struct commit *commit;
@@ -215,29 +208,6 @@ int todo_list_rearrange_squash(struct todo_list *todo_list);
208
*/
209
void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag);
210
218
-/*
219
- * Append the "This is a combination of N commits." banner that "git rebase
220
- * -i" writes at the top of a squashed commit's message, commented out with
221
- * the comment character.
222
- */
223
-void add_squash_combination_header(struct strbuf *buf, int n);
224
-
225
-/*
226
- * Append the header (1-based N) that "git rebase -i" writes above each message
227
- * when squashing, commented out with the comment character. With SKIP it reads
228
- * "The ... commit message will be skipped" for a message that is dropped (a
229
- * fixup), otherwise "This is the ... commit message".
230
- */
231
-void add_squash_message_header(struct strbuf *buf, int n, int skip);
232
-
233
-/*
234
- * Return the length of the leading subject of BODY when it should be commented
235
- * out in a squash message, or 0 otherwise. An "amend!" subject always
236
- * qualifies; "squash!" and "fixup!" subjects only when SQUASHING, since a
237
- * plain fixup chain keeps them.
238
- */
239
-size_t squash_subject_comment_len(const char *body, int squashing);
240
-
211
void append_conflicts_hint(struct index_state *istate,
212
struct strbuf *msgbuf, enum commit_msg_cleanup_mode cleanup_mode);
213
enum commit_msg_cleanup_mode get_cleanup_mode(const char *cleanup_arg,
t/meson.build
-1
@@ -406,7 +406,6 @@ integration_tests = [
406
't3452-history-split.sh',
407
't3453-history-fixup.sh',
408
't3454-history-drop.sh',
409
- 't3455-history-squash.sh',
409
't3500-cherry.sh',
410
't3501-revert-cherry-pick.sh',
411
't3502-cherry-pick-merge.sh',
t/t3455-history-squash.sh
deleted
-770
@@ -1,770 +0,0 @@
1
-#!/bin/sh
2
-
3
-test_description='tests for git-history squash subcommand'
4
-
5
-. ./test-lib.sh
6
-
7
-stage_file () {
8
- printf "%s\n" "$1" >file &&
9
- git add file
10
-}
11
-
12
-commit_with_message () {
13
- printf "%b" "$1" >msg &&
14
- git commit --allow-empty -qF msg
15
-}
16
-
17
-check_commit_count () {
18
- git rev-list --count "$1" >actual &&
19
- echo "$2" >expect &&
20
- test_cmp expect actual
21
-}
22
-
23
-check_log_subjects () {
24
- git log --format="%s" "$1" >actual &&
25
- cat >expect &&
26
- test_cmp expect actual
27
-}
28
-
29
-check_log_messages () {
30
- git log --format="%B" "$1" >actual &&
31
- cat >expect &&
32
- test_cmp expect actual
33
-}
34
-
35
-test_expect_success 'setup linear history touching two files' '
36
- test_commit base file a &&
37
- git tag start &&
38
- test_commit --no-tag one other x &&
39
- test_commit --no-tag two file c &&
40
- test_commit three file d
41
-'
42
-
43
-test_expect_success 'errors on missing range argument' '
44
- test_must_fail git history squash 2>err &&
45
- test_grep "expects a revision range" err
46
-'
47
-
48
-test_expect_success 'errors on an empty range' '
49
- test_must_fail git history squash HEAD..HEAD 2>err &&
50
- test_grep "the revision range is empty" err
51
-'
52
-
53
-test_expect_success 'errors on a single revision that is not a range' '
54
- test_must_fail git history squash HEAD 2>err &&
55
- test_grep "not a .*range" err &&
56
- test_must_fail git history squash HEAD~1 2>err &&
57
- test_grep "not a .*range" err
58
-'
59
-
60
-test_expect_success 'errors on a range holding a single commit' '
61
- git reset --hard three &&
62
- head_before=$(git rev-parse HEAD) &&
63
-
64
- test_must_fail git history squash "HEAD^!" 2>err &&
65
- test_grep "single commit; nothing to squash" err &&
66
- test_cmp_rev "$head_before" HEAD
67
-'
68
-
69
-test_expect_success 'accepts multiple revision arguments with an exclusion' '
70
- git reset --hard three &&
71
- git branch -f keep HEAD~2 &&
72
- tip_tree=$(git rev-parse HEAD^{tree}) &&
73
-
74
- git history squash start..HEAD ^keep &&
75
-
76
- git reflog -1 --format=%gs >actual &&
77
- echo "squash: updating start..HEAD ^keep" >expect &&
78
- test_cmp expect actual &&
79
-
80
- check_log_subjects start..HEAD <<-\EOF &&
81
- two
82
- one
83
- EOF
84
- test_cmp_rev keep HEAD~1 &&
85
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
86
-
87
- git branch -D keep
88
-'
89
-
90
-test_expect_success 'squashes a branch the current branch is not on' '
91
- git reset --hard three &&
92
- main=$(git symbolic-ref --short HEAD) &&
93
- head_before=$(git rev-parse HEAD) &&
94
- git checkout -b off-history start &&
95
- test_commit --no-tag off-one off a &&
96
- test_commit --no-tag off-two off b &&
97
- git checkout "$main" &&
98
-
99
- git history squash start..off-history &&
100
-
101
- check_commit_count start..off-history 1 &&
102
- test_cmp_rev "$head_before" HEAD &&
103
-
104
- git branch -D off-history
105
-'
106
-
107
-test_expect_success 'squashes a range into a single commit without changing the tree' '
108
- git reset --hard three &&
109
- head_before=$(git rev-parse HEAD) &&
110
- tip_tree=$(git rev-parse HEAD^{tree}) &&
111
-
112
- git history squash --dry-run start.. >out &&
113
- predicted=$(awk "/^update refs\/heads\// {print \$3}" out) &&
114
- test_cmp_rev "$head_before" HEAD &&
115
-
116
- git history squash start.. &&
117
-
118
- test "$predicted" = "$(git rev-parse HEAD)" &&
119
- check_commit_count start..HEAD 1 &&
120
- test_cmp_rev start HEAD^ &&
121
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
122
- check_log_subjects -1 <<-\EOF &&
123
- one
124
- EOF
125
- git reflog >reflog &&
126
- test_grep "squash: updating" reflog
127
-'
128
-
129
-test_expect_success 'sanitizes rev-list walk options, before and after --' '
130
- git reset --hard three &&
131
- tip_tree=$(git rev-parse HEAD^{tree}) &&
132
-
133
- git history squash --date-order start.. 2>err &&
134
- test_grep "ignoring rev-list options" err &&
135
- test_cmp_rev start HEAD^ &&
136
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
137
-
138
- git reset --hard three &&
139
- git history squash -- --reverse start.. 2>err &&
140
- test_grep "ignoring rev-list options" err &&
141
- test_cmp_rev start HEAD^ &&
142
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
143
-'
144
-
145
-test_expect_success 'squashes an interior range and replays descendants verbatim' '
146
- git reset --hard three &&
147
- final_tree=$(git rev-parse HEAD^{tree}) &&
148
-
149
- git history squash start..@~1 &&
150
-
151
- check_log_subjects start..HEAD <<-\EOF &&
152
- three
153
- one
154
- EOF
155
-
156
- test_cmp_rev start HEAD~2 &&
157
- test "$final_tree" = "$(git rev-parse HEAD^{tree})"
158
-'
159
-
160
-test_expect_success 'squashes when the base is the root commit' '
161
- git reset --hard three &&
162
- root=$(git rev-list --max-parents=0 HEAD) &&
163
- tip_tree=$(git rev-parse HEAD^{tree}) &&
164
-
165
- git history squash "$root.." &&
166
-
167
- check_commit_count "$root..HEAD" 1 &&
168
- test_cmp_rev "$root" HEAD^ &&
169
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
170
-'
171
-
172
-
173
-test_expect_success 'folds fixups whose target is in the range' '
174
- git reset --hard start &&
175
- test_commit --no-tag target file b &&
176
- git commit --allow-empty -m "fixup! target" &&
177
- git commit --allow-empty -m "fixup! target" &&
178
- test_commit --no-tag later file c &&
179
-
180
- git history squash start.. &&
181
-
182
- check_commit_count start..HEAD 1 &&
183
- check_log_subjects -1 <<-\EOF
184
- target
185
- EOF
186
-'
187
-
188
-test_expect_success 'refuses a below-range fixup! after an in-range commit' '
189
- git reset --hard start &&
190
- test_commit --no-tag inside file b &&
191
- test_commit --no-tag "fixup! outside" file c &&
192
- head_before=$(git rev-parse HEAD) &&
193
-
194
- test_must_fail git history squash start.. 2>err &&
195
- test_grep "target is not in the range" err &&
196
- test_cmp_rev "$head_before" HEAD
197
-'
198
-
199
-test_expect_success 'combines a run of fixups for one commit below the range' '
200
- git reset --hard start &&
201
- stage_file b && git commit -m "fixup! base" &&
202
- stage_file c && git commit -m "fixup! base" &&
203
-
204
- git history squash start.. &&
205
-
206
- check_commit_count start..HEAD 1 &&
207
- check_log_subjects -1 <<-\EOF
208
- fixup! base
209
- EOF
210
-'
211
-
212
-test_expect_success 'combining below-range fixups keeps the last amend! message' '
213
- git reset --hard start &&
214
- stage_file b && git commit -m "fixup! base" &&
215
- stage_file c &&
216
- commit_with_message "amend! base\n\namended body\n" &&
217
-
218
- git history squash start.. &&
219
-
220
- check_commit_count start..HEAD 1 &&
221
- check_log_messages -1 <<-\EOF
222
- amend! base
223
-
224
- amended body
225
-
226
- EOF
227
-'
228
-
229
-test_expect_success 'refuses fixups for two different commits below the range' '
230
- git reset --hard start &&
231
- stage_file b && git commit -m "fixup! aaa" &&
232
- stage_file c && git commit -m "fixup! bbb" &&
233
- head_before=$(git rev-parse HEAD) &&
234
-
235
- test_must_fail git history squash start.. 2>err &&
236
- test_grep "target is not in the range" err &&
237
- test_cmp_rev "$head_before" HEAD
238
-'
239
-
240
-test_expect_success 'the last amend! for the oldest commit replaces its message' '
241
- git reset --hard start &&
242
- test_commit --no-tag marker-oldest file b &&
243
- git commit --allow-empty -m "squash! marker-oldest" &&
244
- commit_with_message "amend! marker-oldest\n\nearlier message\n" &&
245
- commit_with_message \
246
- "amend! marker-oldest\n\namended subject\n\namended body\n" &&
247
- test_commit --no-tag marker-later file c &&
248
- commit_with_message "amend! marker-later\n\nwrong message\n" &&
249
-
250
- git history squash start.. &&
251
-
252
- check_commit_count start..HEAD 1 &&
253
- check_log_messages -1 <<-\EOF
254
- amended subject
255
-
256
- amended body
257
-
258
- EOF
259
-'
260
-
261
-test_expect_success 'preserves authorship of the oldest commit' '
262
- git reset --hard start &&
263
- GIT_AUTHOR_NAME=Squasher GIT_AUTHOR_EMAIL=squash@example.com \
264
- test_commit --no-tag oldest file b &&
265
- test_commit newest file c &&
266
-
267
- git history squash start.. &&
268
-
269
- git log -1 --format="%an <%ae>" >actual &&
270
- echo "Squasher <squash@example.com>" >expect &&
271
- test_cmp expect actual
272
-'
273
-
274
-test_expect_success '--reedit-message offers every folded-in message' '
275
- git reset --hard start &&
276
- stage_file b &&
277
- git commit -m "re-one subject" -m "re-one body line" &&
278
- test_commit --no-tag re-two file c &&
279
- test_commit re-three file d &&
280
-
281
- write_script editor <<-\EOF &&
282
- cat "$1" >edited &&
283
- echo combined >"$1"
284
- EOF
285
- test_set_editor "$(pwd)/editor" &&
286
- git history squash --reedit-message start.. &&
287
-
288
- cat >expect <<-EOF &&
289
- # This is a combination of 3 commits.
290
- # This is the 1st commit message:
291
-
292
- re-one subject
293
-
294
- re-one body line
295
-
296
- # This is the commit message #2:
297
-
298
- re-two
299
-
300
- # This is the commit message #3:
301
-
302
- re-three
303
-
304
- # Please enter the commit message for the squash changes. Lines starting
305
- # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
306
- # Changes to be committed:
307
- # modified: file
308
- #
309
- EOF
310
- test_cmp expect edited &&
311
- check_log_subjects -1 <<-\EOF
312
- combined
313
- EOF
314
-'
315
-
316
-test_expect_success '--reedit-message handles fixup!, squash! and amend! like rebase' '
317
- git reset --hard start &&
318
- test_commit --no-tag mark-base file b &&
319
- stage_file c &&
320
- commit_with_message "fixup! mark-base\n\nfixup body\n" &&
321
- stage_file d &&
322
- commit_with_message "squash! mark-base\n\nsquash remark\n" &&
323
- stage_file e &&
324
- commit_with_message "amend! mark-base\n\namended message\n" &&
325
-
326
- write_script editor <<-\EOF &&
327
- cat "$1" >edited
328
- EOF
329
- test_set_editor "$(pwd)/editor" &&
330
- git history squash --reedit-message start.. &&
331
-
332
- cat >expect <<-EOF &&
333
- # This is a combination of 4 commits.
334
- # This is the 1st commit message:
335
-
336
- mark-base
337
-
338
- # The commit message #2 will be skipped:
339
-
340
- # fixup! mark-base
341
- #
342
- # fixup body
343
-
344
- # This is the commit message #3:
345
-
346
- # squash! mark-base
347
-
348
- squash remark
349
-
350
- # This is the commit message #4:
351
-
352
- # amend! mark-base
353
-
354
- amended message
355
-
356
- # Please enter the commit message for the squash changes. Lines starting
357
- # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
358
- # Changes to be committed:
359
- # modified: file
360
- #
361
- EOF
362
- test_cmp expect edited &&
363
- check_log_messages -1 <<-\EOF
364
- mark-base
365
-
366
- squash remark
367
-
368
- amended message
369
-
370
- EOF
371
-'
372
-
373
-test_expect_success '--reedit-message groups fixups under their targets' '
374
- git reset --hard start &&
375
- test_commit --no-tag alpha file a1 &&
376
- test_commit --no-tag beta file b1 &&
377
- stage_file a2 &&
378
- commit_with_message "fixup! alpha\n" &&
379
- stage_file b2 &&
380
- commit_with_message "fixup! beta\n" &&
381
-
382
- write_script editor <<-\EOF &&
383
- cat "$1" >edited
384
- EOF
385
- test_set_editor "$(pwd)/editor" &&
386
- git history squash --reedit-message start.. &&
387
-
388
- cat >expect <<-EOF &&
389
- # This is a combination of 4 commits.
390
- # This is the 1st commit message:
391
-
392
- alpha
393
-
394
- # The commit message #2 will be skipped:
395
-
396
- # fixup! alpha
397
-
398
- # This is the commit message #3:
399
-
400
- beta
401
-
402
- # The commit message #4 will be skipped:
403
-
404
- # fixup! beta
405
-
406
- # Please enter the commit message for the squash changes. Lines starting
407
- # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
408
- # Changes to be committed:
409
- # modified: file
410
- #
411
- EOF
412
- test_cmp expect edited
413
-'
414
-
415
-test_expect_success '--reedit-message lets amend! replace its target message' '
416
- git reset --hard start &&
417
- test_commit --no-tag mark-base file b &&
418
- stage_file c &&
419
- commit_with_message "amend! mark-base\n\namended message\n" &&
420
- stage_file d &&
421
- commit_with_message "squash! mark-base\n\nsquash remark\n" &&
422
-
423
- write_script editor <<-\EOF &&
424
- cat "$1" >edited
425
- EOF
426
- test_set_editor "$(pwd)/editor" &&
427
- git history squash --reedit-message start.. &&
428
-
429
- cat >expect <<-EOF &&
430
- # This is a combination of 3 commits.
431
- # The 1st commit message will be skipped:
432
-
433
- # mark-base
434
-
435
- # This is the commit message #2:
436
-
437
- # amend! mark-base
438
-
439
- amended message
440
-
441
- # This is the commit message #3:
442
-
443
- # squash! mark-base
444
-
445
- squash remark
446
-
447
- # Please enter the commit message for the squash changes. Lines starting
448
- # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit.
449
- # Changes to be committed:
450
- # modified: file
451
- #
452
- EOF
453
- test_cmp expect edited &&
454
- check_log_messages -1 <<-\EOF
455
- amended message
456
-
457
- squash remark
458
-
459
- EOF
460
-'
461
-
462
-test_expect_success '--reedit-message aborts on an empty message' '
463
- git reset --hard three &&
464
- head_before=$(git rev-parse HEAD) &&
465
-
466
- write_script editor <<-\EOF &&
467
- >"$1"
468
- EOF
469
- test_set_editor "$(pwd)/editor" &&
470
- test_must_fail git history squash --reedit-message start.. &&
471
-
472
- test_cmp_rev "$head_before" HEAD
473
-'
474
-
475
-test_expect_success '--update-refs=head only moves HEAD' '
476
- git reset --hard three &&
477
- git branch -f other HEAD &&
478
- other_before=$(git rev-parse other) &&
479
-
480
- git history squash --update-refs=head start.. &&
481
-
482
- check_commit_count start..HEAD 1 &&
483
- test_cmp_rev "$other_before" other
484
-'
485
-
486
-test_expect_success 'refuses to fold a range a ref points into' '
487
- git reset --hard three &&
488
- git branch -f mid HEAD~1 &&
489
- head_before=$(git rev-parse HEAD) &&
490
-
491
- test_must_fail git history squash start.. 2>err &&
492
- test_grep "error: .* points into the squashed range" err &&
493
- test_grep "hint: .*--update-refs=head" err &&
494
- test_cmp_rev "$head_before" HEAD &&
495
-
496
- git branch -D mid
497
-'
498
-
499
-test_expect_success 'advice.historyUpdateRefs silences the hint' '
500
- git reset --hard three &&
501
- git branch -f mid HEAD~1 &&
502
- head_before=$(git rev-parse HEAD) &&
503
-
504
- test_must_fail git -c advice.historyUpdateRefs=false \
505
- history squash start.. 2>err &&
506
- test_grep "points into the squashed range" err &&
507
- test_grep ! "hint:" err &&
508
- test_cmp_rev "$head_before" HEAD &&
509
-
510
- git branch -D mid
511
-'
512
-
513
-test_expect_success '--update-refs=head folds past a ref pointing into the range' '
514
- git reset --hard three &&
515
- git branch -f mid HEAD~1 &&
516
- mid_before=$(git rev-parse mid) &&
517
-
518
- git history squash --update-refs=head start.. &&
519
-
520
- check_commit_count start..HEAD 1 &&
521
- test_cmp_rev "$mid_before" mid &&
522
-
523
- git branch -D mid
524
-'
525
-
526
-test_expect_success 'refuses to fold a range a tag points into' '
527
- git reset --hard three &&
528
- git tag -f mark HEAD~1 &&
529
- head_before=$(git rev-parse HEAD) &&
530
-
531
- test_must_fail git history squash start.. 2>err &&
532
- test_grep "refs/tags/mark" err &&
533
- test_grep "points into the squashed range" err &&
534
- test_cmp_rev "$head_before" HEAD &&
535
-
536
- git tag -d mark
537
-'
538
-
539
-test_expect_success 'squashes a range whose internal merge has a single base' '
540
- git reset --hard start &&
541
- main=$(git symbolic-ref --short HEAD) &&
542
- test_commit --no-tag before-side file b &&
543
- git checkout -b inner-side &&
544
- test_commit --no-tag on-inner-side inner x &&
545
- git checkout "$main" &&
546
- test_commit --no-tag after-side file c &&
547
- git merge --no-ff -m merge inner-side &&
548
- git branch -D inner-side &&
549
- test_commit --no-tag after-merge file d &&
550
- tip_tree=$(git rev-parse HEAD^{tree}) &&
551
-
552
- git history squash start.. &&
553
-
554
- check_commit_count start..HEAD 1 &&
555
- check_log_subjects -1 <<-\EOF &&
556
- before-side
557
- EOF
558
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
559
- test_path_is_file inner
560
-'
561
-
562
-test_expect_success 'folds a merge of a branch that forked at the base' '
563
- git reset --hard start &&
564
- main=$(git symbolic-ref --short HEAD) &&
565
- git checkout -b base-fork-side &&
566
- test_commit --no-tag base-fork-side side x &&
567
- git checkout "$main" &&
568
- test_commit --no-tag base-fork-main file b &&
569
- git merge --no-ff -m "merge base-fork-side" base-fork-side &&
570
- git branch -D base-fork-side &&
571
- test_commit --no-tag base-fork-tail file c &&
572
- tip_tree=$(git rev-parse HEAD^{tree}) &&
573
-
574
- git history squash start.. &&
575
-
576
- check_commit_count start..HEAD 1 &&
577
- test_cmp_rev start HEAD^ &&
578
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
579
- test_path_is_file side
580
-'
581
-
582
-test_expect_success 'refuses a merge whose other parent is outside the range' '
583
- git reset --hard start &&
584
- main=$(git symbolic-ref --short HEAD) &&
585
- git checkout -b outside-parent &&
586
- test_commit --no-tag outside-parent outside x &&
587
- git checkout "$main" &&
588
- test_commit --no-tag outside-main file b &&
589
- base=$(git rev-parse HEAD) &&
590
- test_commit --no-tag outside-mid file c &&
591
- git merge --no-ff -m "merge outside-parent" outside-parent &&
592
- git branch -D outside-parent &&
593
- merged=$(git rev-parse HEAD) &&
594
-
595
- test_must_fail git history squash "$base.." 2>err &&
596
- test_grep "more than one base" err &&
597
- test_cmp_rev "$merged" HEAD
598
-'
599
-
600
-test_expect_success 'folds a range whose tip is a merge commit' '
601
- git reset --hard start &&
602
- main=$(git symbolic-ref --short HEAD) &&
603
- test_commit --no-tag tipmerge-base file b &&
604
- git checkout -b tipmerge-side &&
605
- test_commit --no-tag tipmerge-side side x &&
606
- git checkout "$main" &&
607
- test_commit --no-tag tipmerge-main file c &&
608
- git merge --no-ff -m "merge tipmerge-side" tipmerge-side &&
609
- git branch -D tipmerge-side &&
610
- tip_tree=$(git rev-parse HEAD^{tree}) &&
611
-
612
- git history squash start.. &&
613
-
614
- check_commit_count start..HEAD 1 &&
615
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
616
- test_path_is_file side
617
-'
618
-
619
-test_expect_success 'folds a range whose base is a merge commit' '
620
- git reset --hard start &&
621
- main=$(git symbolic-ref --short HEAD) &&
622
- git checkout -b basemerge-side &&
623
- test_commit --no-tag basemerge-side side x &&
624
- git checkout "$main" &&
625
- test_commit --no-tag basemerge-main file b &&
626
- git merge --no-ff -m "merge basemerge-side" basemerge-side &&
627
- git branch -D basemerge-side &&
628
- base=$(git rev-parse HEAD) &&
629
- test_commit --no-tag basemerge-one file c &&
630
- test_commit --no-tag basemerge-two file d &&
631
- tip_tree=$(git rev-parse HEAD^{tree}) &&
632
-
633
- git history squash "$base.." &&
634
-
635
- check_commit_count "$base..HEAD" 1 &&
636
- test_cmp_rev "$base" HEAD^ &&
637
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})"
638
-'
639
-
640
-test_expect_success 'folds a range with two interior merges' '
641
- git reset --hard start &&
642
- main=$(git symbolic-ref --short HEAD) &&
643
- test_commit --no-tag two-merge-a file a1 &&
644
- git checkout -b two-merge-s1 &&
645
- test_commit --no-tag two-merge-s1 s1 x &&
646
- git checkout "$main" &&
647
- git merge --no-ff -m "merge s1" two-merge-s1 &&
648
- test_commit --no-tag two-merge-b file b1 &&
649
- git checkout -b two-merge-s2 &&
650
- test_commit --no-tag two-merge-s2 s2 y &&
651
- git checkout "$main" &&
652
- git merge --no-ff -m "merge s2" two-merge-s2 &&
653
- git branch -D two-merge-s1 two-merge-s2 &&
654
- tip_tree=$(git rev-parse HEAD^{tree}) &&
655
-
656
- git history squash start.. &&
657
-
658
- check_commit_count start..HEAD 1 &&
659
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
660
- test_path_is_file s1 &&
661
- test_path_is_file s2
662
-'
663
-
664
-test_expect_success 'folds a range with a nested merge' '
665
- git reset --hard start &&
666
- main=$(git symbolic-ref --short HEAD) &&
667
- git checkout -b nested-outer &&
668
- test_commit --no-tag nested-outer outer x &&
669
- git checkout -b nested-inner &&
670
- test_commit --no-tag nested-inner inner y &&
671
- git checkout nested-outer &&
672
- git merge --no-ff -m "merge inner" nested-inner &&
673
- git checkout "$main" &&
674
- test_commit --no-tag nested-main file b1 &&
675
- git merge --no-ff -m "merge outer" nested-outer &&
676
- git branch -D nested-outer nested-inner &&
677
- tip_tree=$(git rev-parse HEAD^{tree}) &&
678
-
679
- git history squash start.. &&
680
-
681
- check_commit_count start..HEAD 1 &&
682
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
683
- test_path_is_file outer &&
684
- test_path_is_file inner
685
-'
686
-
687
-test_expect_success 'folds a range with an octopus merge' '
688
- git reset --hard start &&
689
- main=$(git symbolic-ref --short HEAD) &&
690
- test_commit --no-tag octo-base file a1 &&
691
- git checkout -b octo-1 &&
692
- test_commit --no-tag octo-1 o1 x &&
693
- git checkout "$main" &&
694
- git checkout -b octo-2 &&
695
- test_commit --no-tag octo-2 o2 y &&
696
- git checkout "$main" &&
697
- git merge --no-ff -m octopus octo-1 octo-2 &&
698
- git branch -D octo-1 octo-2 &&
699
- tip_tree=$(git rev-parse HEAD^{tree}) &&
700
-
701
- git history squash start.. &&
702
-
703
- check_commit_count start..HEAD 1 &&
704
- test "$tip_tree" = "$(git rev-parse HEAD^{tree})" &&
705
- test_path_is_file o1 &&
706
- test_path_is_file o2
707
-'
708
-
709
-test_expect_success 'refuses an octopus merge with an arm forked before the base' '
710
- git reset --hard start &&
711
- main=$(git symbolic-ref --short HEAD) &&
712
- git checkout -b octo-pre &&
713
- test_commit octo-pre-side pside x &&
714
- git checkout "$main" &&
715
- test_commit octo-pre-main file b1 &&
716
- octo_base=$(git rev-parse HEAD) &&
717
- git checkout -b octo-within &&
718
- test_commit --no-tag octo-within wside y &&
719
- git checkout "$main" &&
720
- git merge --no-ff -m octopus octo-pre octo-within &&
721
- merged=$(git rev-parse HEAD) &&
722
- git branch -D octo-pre octo-within &&
723
-
724
- test_must_fail git history squash "$octo_base.." 2>err &&
725
- test_grep "more than one base" err &&
726
- test_cmp_rev "$merged" HEAD
727
-'
728
-
729
-test_expect_success 'refuses when a descendant above the range is a merge' '
730
- git reset --hard start &&
731
- main=$(git symbolic-ref --short HEAD) &&
732
- test_commit --no-tag desc-one file b &&
733
- test_commit --no-tag desc-two file c &&
734
- git tag desc-tip &&
735
- git checkout -b desc-above &&
736
- test_commit --no-tag desc-above above x &&
737
- git checkout "$main" &&
738
- test_commit --no-tag desc-main file d &&
739
- git merge --no-ff -m "merge desc-above" desc-above &&
740
- git branch -D desc-above &&
741
- head_before=$(git rev-parse HEAD) &&
742
-
743
- test_must_fail git history squash start..desc-tip 2>err &&
744
- test_grep "merge commits is not supported" err &&
745
- test_cmp_rev "$head_before" HEAD
746
-'
747
-
748
-test_expect_success 'refuses to fold a range a ref points into at a merge' '
749
- git reset --hard start &&
750
- main=$(git symbolic-ref --short HEAD) &&
751
- test_commit --no-tag refmerge-base file b &&
752
- git checkout -b refmerge-side &&
753
- test_commit --no-tag refmerge-side side x &&
754
- git checkout "$main" &&
755
- test_commit --no-tag refmerge-main file c &&
756
- git merge --no-ff -m "interior merge" refmerge-side &&
757
- git branch -D refmerge-side &&
758
- git branch at-merge HEAD &&
759
- test_commit --no-tag refmerge-tail file d &&
760
- head_before=$(git rev-parse HEAD) &&
761
-
762
- test_must_fail git history squash start.. 2>err &&
763
- test_grep "at-merge" err &&
764
- test_grep "points into the squashed range" err &&
765
- test_cmp_rev "$head_before" HEAD &&
766
-
767
- git branch -D at-merge
768
-'
769
-
770
-test_done