Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit 1673744

Browse files
committed
Merge branch 'cc/replace-graft'
"git replace" learned a "--graft" option to rewrite parents of a commit. * cc/replace-graft: replace: add test for --graft with a mergetag replace: check mergetags when using --graft replace: add test for --graft with signed commit replace: remove signature when using --graft contrib: add convert-grafts-to-replace-refs.sh Documentation: replace: add --graft option replace: add test for --graft replace: add --graft option replace: cleanup redirection style in tests
2 parents 4799593 + 3fa1025 commit 1673744

File tree

6 files changed

+313
-26
lines changed

6 files changed

+313
-26
lines changed

Documentation/git-replace.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ SYNOPSIS
1010
[verse]
1111
'git replace' [-f] <object> <replacement>
1212
'git replace' [-f] --edit <object>
13+
'git replace' [-f] --graft <commit> [<parent>...]
1314
'git replace' -d <object>...
1415
'git replace' [--format=<format>] [-l [<pattern>]]
1516

@@ -81,6 +82,15 @@ OPTIONS
8182
cannot be pretty-printed. Note that you may need to configure
8283
your editor to cleanly read and write binary data.
8384

85+
--graft <commit> [<parent>...]::
86+
Create a graft commit. A new commit is created with the same
87+
content as <commit> except that its parents will be
88+
[<parent>...] instead of <commit>'s parents. A replacement ref
89+
is then created to replace <commit> with the newly created
90+
commit. See contrib/convert-grafts-to-replace-refs.sh for an
91+
example script based on this option that can convert grafts to
92+
replace refs.
93+
8494
-l <pattern>::
8595
--list <pattern>::
8696
List replace refs for objects that match the given pattern (or

builtin/replace.c

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
#include "refs.h"
1414
#include "parse-options.h"
1515
#include "run-command.h"
16+
#include "tag.h"
1617

1718
static const char * const git_replace_usage[] = {
1819
N_("git replace [-f] <object> <replacement>"),
1920
N_("git replace [-f] --edit <object>"),
21+
N_("git replace [-f] --graft <commit> [<parent>...]"),
2022
N_("git replace -d <object>..."),
2123
N_("git replace [--format=<format>] [-l [<pattern>]]"),
2224
NULL
@@ -299,6 +301,117 @@ static int edit_and_replace(const char *object_ref, int force, int raw)
299301
return replace_object_sha1(object_ref, old, "replacement", new, force);
300302
}
301303

304+
static void replace_parents(struct strbuf *buf, int argc, const char **argv)
305+
{
306+
struct strbuf new_parents = STRBUF_INIT;
307+
const char *parent_start, *parent_end;
308+
int i;
309+
310+
/* find existing parents */
311+
parent_start = buf->buf;
312+
parent_start += 46; /* "tree " + "hex sha1" + "\n" */
313+
parent_end = parent_start;
314+
315+
while (starts_with(parent_end, "parent "))
316+
parent_end += 48; /* "parent " + "hex sha1" + "\n" */
317+
318+
/* prepare new parents */
319+
for (i = 0; i < argc; i++) {
320+
unsigned char sha1[20];
321+
if (get_sha1(argv[i], sha1) < 0)
322+
die(_("Not a valid object name: '%s'"), argv[i]);
323+
lookup_commit_or_die(sha1, argv[i]);
324+
strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1));
325+
}
326+
327+
/* replace existing parents with new ones */
328+
strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start,
329+
new_parents.buf, new_parents.len);
330+
331+
strbuf_release(&new_parents);
332+
}
333+
334+
struct check_mergetag_data {
335+
int argc;
336+
const char **argv;
337+
};
338+
339+
static void check_one_mergetag(struct commit *commit,
340+
struct commit_extra_header *extra,
341+
void *data)
342+
{
343+
struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data;
344+
const char *ref = mergetag_data->argv[0];
345+
unsigned char tag_sha1[20];
346+
struct tag *tag;
347+
int i;
348+
349+
hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1);
350+
tag = lookup_tag(tag_sha1);
351+
if (!tag)
352+
die(_("bad mergetag in commit '%s'"), ref);
353+
if (parse_tag_buffer(tag, extra->value, extra->len))
354+
die(_("malformed mergetag in commit '%s'"), ref);
355+
356+
/* iterate over new parents */
357+
for (i = 1; i < mergetag_data->argc; i++) {
358+
unsigned char sha1[20];
359+
if (get_sha1(mergetag_data->argv[i], sha1) < 0)
360+
die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]);
361+
if (!hashcmp(tag->tagged->sha1, sha1))
362+
return; /* found */
363+
}
364+
365+
die(_("original commit '%s' contains mergetag '%s' that is discarded; "
366+
"use --edit instead of --graft"), ref, sha1_to_hex(tag_sha1));
367+
}
368+
369+
static void check_mergetags(struct commit *commit, int argc, const char **argv)
370+
{
371+
struct check_mergetag_data mergetag_data;
372+
373+
mergetag_data.argc = argc;
374+
mergetag_data.argv = argv;
375+
for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
376+
}
377+
378+
static int create_graft(int argc, const char **argv, int force)
379+
{
380+
unsigned char old[20], new[20];
381+
const char *old_ref = argv[0];
382+
struct commit *commit;
383+
struct strbuf buf = STRBUF_INIT;
384+
const char *buffer;
385+
unsigned long size;
386+
387+
if (get_sha1(old_ref, old) < 0)
388+
die(_("Not a valid object name: '%s'"), old_ref);
389+
commit = lookup_commit_or_die(old, old_ref);
390+
391+
buffer = get_commit_buffer(commit, &size);
392+
strbuf_add(&buf, buffer, size);
393+
unuse_commit_buffer(commit, buffer);
394+
395+
replace_parents(&buf, argc - 1, &argv[1]);
396+
397+
if (remove_signature(&buf)) {
398+
warning(_("the original commit '%s' has a gpg signature."), old_ref);
399+
warning(_("the signature will be removed in the replacement commit!"));
400+
}
401+
402+
check_mergetags(commit, argc, argv);
403+
404+
if (write_sha1_file(buf.buf, buf.len, commit_type, new))
405+
die(_("could not write replacement commit for: '%s'"), old_ref);
406+
407+
strbuf_release(&buf);
408+
409+
if (!hashcmp(old, new))
410+
return error("new commit is the same as the old one: '%s'", sha1_to_hex(old));
411+
412+
return replace_object_sha1(old_ref, old, "replacement", new, force);
413+
}
414+
302415
int cmd_replace(int argc, const char **argv, const char *prefix)
303416
{
304417
int force = 0;
@@ -309,12 +422,14 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
309422
MODE_LIST,
310423
MODE_DELETE,
311424
MODE_EDIT,
425+
MODE_GRAFT,
312426
MODE_REPLACE
313427
} cmdmode = MODE_UNSPECIFIED;
314428
struct option options[] = {
315429
OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
316430
OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
317431
OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
432+
OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
318433
OPT_BOOL('f', "force", &force, N_("replace the ref if it exists")),
319434
OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")),
320435
OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
@@ -332,7 +447,10 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
332447
usage_msg_opt("--format cannot be used when not listing",
333448
git_replace_usage, options);
334449

335-
if (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT)
450+
if (force &&
451+
cmdmode != MODE_REPLACE &&
452+
cmdmode != MODE_EDIT &&
453+
cmdmode != MODE_GRAFT)
336454
usage_msg_opt("-f only makes sense when writing a replacement",
337455
git_replace_usage, options);
338456

@@ -359,6 +477,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
359477
git_replace_usage, options);
360478
return edit_and_replace(argv[0], force, raw);
361479

480+
case MODE_GRAFT:
481+
if (argc < 1)
482+
usage_msg_opt("-g needs at least one argument",
483+
git_replace_usage, options);
484+
return create_graft(argc, argv, force);
485+
362486
case MODE_LIST:
363487
if (argc > 1)
364488
usage_msg_opt("only one pattern can be given with -l",

commit.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,40 @@ int parse_signed_commit(const struct commit *commit,
11461146
return saw_signature;
11471147
}
11481148

1149+
int remove_signature(struct strbuf *buf)
1150+
{
1151+
const char *line = buf->buf;
1152+
const char *tail = buf->buf + buf->len;
1153+
int in_signature = 0;
1154+
const char *sig_start = NULL;
1155+
const char *sig_end = NULL;
1156+
1157+
while (line < tail) {
1158+
const char *next = memchr(line, '\n', tail - line);
1159+
next = next ? next + 1 : tail;
1160+
1161+
if (in_signature && line[0] == ' ')
1162+
sig_end = next;
1163+
else if (starts_with(line, gpg_sig_header) &&
1164+
line[gpg_sig_header_len] == ' ') {
1165+
sig_start = line;
1166+
sig_end = next;
1167+
in_signature = 1;
1168+
} else {
1169+
if (*line == '\n')
1170+
/* dump the whole remainder of the buffer */
1171+
next = tail;
1172+
in_signature = 0;
1173+
}
1174+
line = next;
1175+
}
1176+
1177+
if (sig_start)
1178+
strbuf_remove(buf, sig_start - buf->buf, sig_end - sig_start);
1179+
1180+
return sig_start != NULL;
1181+
}
1182+
11491183
static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
11501184
{
11511185
struct merge_remote_desc *desc;

commit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ struct commit *get_merge_parent(const char *name);
333333

334334
extern int parse_signed_commit(const struct commit *commit,
335335
struct strbuf *message, struct strbuf *signature);
336+
extern int remove_signature(struct strbuf *buf);
337+
336338
extern void print_commit_list(struct commit_list *list,
337339
const char *format_cur,
338340
const char *format_last);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/sh
2+
3+
# You should execute this script in the repository where you
4+
# want to convert grafts to replace refs.
5+
6+
GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts"
7+
8+
. $(git --exec-path)/git-sh-setup
9+
10+
test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'"
11+
12+
grep '^[^# ]' "$GRAFTS_FILE" |
13+
while read definition
14+
do
15+
if test -n "$definition"
16+
then
17+
echo "Converting: $definition"
18+
git replace --graft $definition ||
19+
die "Conversion failed for: $definition"
20+
fi
21+
done
22+
23+
mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" ||
24+
die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'"
25+
26+
echo "Success!"
27+
echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!"
28+
echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"

0 commit comments

Comments
 (0)