git

Форк
0
/
replace.c 
639 строк · 17.6 Кб
1
/*
2
 * Builtin "git replace"
3
 *
4
 * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org>
5
 *
6
 * Based on builtin/tag.c by Kristian Høgsberg <krh@redhat.com>
7
 * and Carlos Rica <jasampler@gmail.com> that was itself based on
8
 * git-tag.sh and mktag.c by Linus Torvalds.
9
 */
10

11
#include "builtin.h"
12
#include "config.h"
13
#include "editor.h"
14
#include "environment.h"
15
#include "gettext.h"
16
#include "hex.h"
17
#include "refs.h"
18
#include "parse-options.h"
19
#include "path.h"
20
#include "run-command.h"
21
#include "object-file.h"
22
#include "object-name.h"
23
#include "object-store-ll.h"
24
#include "replace-object.h"
25
#include "repository.h"
26
#include "tag.h"
27
#include "wildmatch.h"
28

29
static const char * const git_replace_usage[] = {
30
	N_("git replace [-f] <object> <replacement>"),
31
	N_("git replace [-f] --edit <object>"),
32
	N_("git replace [-f] --graft <commit> [<parent>...]"),
33
	"git replace [-f] --convert-graft-file",
34
	N_("git replace -d <object>..."),
35
	N_("git replace [--format=<format>] [-l [<pattern>]]"),
36
	NULL
37
};
38

39
enum replace_format {
40
	REPLACE_FORMAT_SHORT,
41
	REPLACE_FORMAT_MEDIUM,
42
	REPLACE_FORMAT_LONG
43
};
44

45
struct show_data {
46
	struct repository *repo;
47
	const char *pattern;
48
	enum replace_format format;
49
};
50

51
static int show_reference(const char *refname,
52
			  const char *referent UNUSED,
53
			  const struct object_id *oid,
54
			  int flag UNUSED, void *cb_data)
55
{
56
	struct show_data *data = cb_data;
57

58
	if (!wildmatch(data->pattern, refname, 0)) {
59
		if (data->format == REPLACE_FORMAT_SHORT)
60
			printf("%s\n", refname);
61
		else if (data->format == REPLACE_FORMAT_MEDIUM)
62
			printf("%s -> %s\n", refname, oid_to_hex(oid));
63
		else { /* data->format == REPLACE_FORMAT_LONG */
64
			struct object_id object;
65
			enum object_type obj_type, repl_type;
66

67
			if (repo_get_oid(data->repo, refname, &object))
68
				return error(_("failed to resolve '%s' as a valid ref"), refname);
69

70
			obj_type = oid_object_info(data->repo, &object, NULL);
71
			repl_type = oid_object_info(data->repo, oid, NULL);
72

73
			printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
74
			       oid_to_hex(oid), type_name(repl_type));
75
		}
76
	}
77

78
	return 0;
79
}
80

81
static int list_replace_refs(const char *pattern, const char *format)
82
{
83
	struct show_data data;
84

85
	data.repo = the_repository;
86
	if (!pattern)
87
		pattern = "*";
88
	data.pattern = pattern;
89

90
	if (format == NULL || *format == '\0' || !strcmp(format, "short"))
91
		data.format = REPLACE_FORMAT_SHORT;
92
	else if (!strcmp(format, "medium"))
93
		data.format = REPLACE_FORMAT_MEDIUM;
94
	else if (!strcmp(format, "long"))
95
		data.format = REPLACE_FORMAT_LONG;
96
	/*
97
	 * Please update _git_replace() in git-completion.bash when
98
	 * you add new format
99
	 */
100
	else
101
		return error(_("invalid replace format '%s'\n"
102
			       "valid formats are 'short', 'medium' and 'long'"),
103
			     format);
104

105
	refs_for_each_replace_ref(get_main_ref_store(the_repository),
106
				  show_reference, (void *)&data);
107

108
	return 0;
109
}
110

111
typedef int (*each_replace_name_fn)(const char *name, const char *ref,
112
				    const struct object_id *oid);
113

114
static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
115
{
116
	const char **p, *full_hex;
117
	struct strbuf ref = STRBUF_INIT;
118
	size_t base_len;
119
	int had_error = 0;
120
	struct object_id oid;
121
	const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
122

123
	strbuf_addstr(&ref, git_replace_ref_base);
124
	base_len = ref.len;
125

126
	for (p = argv; *p; p++) {
127
		if (repo_get_oid(the_repository, *p, &oid)) {
128
			error("failed to resolve '%s' as a valid ref", *p);
129
			had_error = 1;
130
			continue;
131
		}
132

133
		strbuf_setlen(&ref, base_len);
134
		strbuf_addstr(&ref, oid_to_hex(&oid));
135
		full_hex = ref.buf + base_len;
136

137
		if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &oid)) {
138
			error(_("replace ref '%s' not found"), full_hex);
139
			had_error = 1;
140
			continue;
141
		}
142
		if (fn(full_hex, ref.buf, &oid))
143
			had_error = 1;
144
	}
145
	strbuf_release(&ref);
146
	return had_error;
147
}
148

149
static int delete_replace_ref(const char *name, const char *ref,
150
			      const struct object_id *oid)
151
{
152
	if (refs_delete_ref(get_main_ref_store(the_repository), NULL, ref, oid, 0))
153
		return 1;
154
	printf_ln(_("Deleted replace ref '%s'"), name);
155
	return 0;
156
}
157

158
static int check_ref_valid(struct object_id *object,
159
			    struct object_id *prev,
160
			    struct strbuf *ref,
161
			    int force)
162
{
163
	const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;
164

165
	strbuf_reset(ref);
166
	strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object));
167
	if (check_refname_format(ref->buf, 0))
168
		return error(_("'%s' is not a valid ref name"), ref->buf);
169

170
	if (refs_read_ref(get_main_ref_store(the_repository), ref->buf, prev))
171
		oidclr(prev, the_repository->hash_algo);
172
	else if (!force)
173
		return error(_("replace ref '%s' already exists"), ref->buf);
174
	return 0;
175
}
176

177
static int replace_object_oid(const char *object_ref,
178
			       struct object_id *object,
179
			       const char *replace_ref,
180
			       struct object_id *repl,
181
			       int force)
182
{
183
	struct object_id prev;
184
	enum object_type obj_type, repl_type;
185
	struct strbuf ref = STRBUF_INIT;
186
	struct ref_transaction *transaction;
187
	struct strbuf err = STRBUF_INIT;
188
	int res = 0;
189

190
	obj_type = oid_object_info(the_repository, object, NULL);
191
	repl_type = oid_object_info(the_repository, repl, NULL);
192
	if (!force && obj_type != repl_type)
193
		return error(_("Objects must be of the same type.\n"
194
			       "'%s' points to a replaced object of type '%s'\n"
195
			       "while '%s' points to a replacement object of "
196
			       "type '%s'."),
197
			     object_ref, type_name(obj_type),
198
			     replace_ref, type_name(repl_type));
199

200
	if (check_ref_valid(object, &prev, &ref, force)) {
201
		strbuf_release(&ref);
202
		return -1;
203
	}
204

205
	transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
206
						  &err);
207
	if (!transaction ||
208
	    ref_transaction_update(transaction, ref.buf, repl, &prev,
209
				   NULL, NULL, 0, NULL, &err) ||
210
	    ref_transaction_commit(transaction, &err))
211
		res = error("%s", err.buf);
212

213
	ref_transaction_free(transaction);
214
	strbuf_release(&ref);
215
	return res;
216
}
217

218
static int replace_object(const char *object_ref, const char *replace_ref, int force)
219
{
220
	struct object_id object, repl;
221

222
	if (repo_get_oid(the_repository, object_ref, &object))
223
		return error(_("failed to resolve '%s' as a valid ref"),
224
			     object_ref);
225
	if (repo_get_oid(the_repository, replace_ref, &repl))
226
		return error(_("failed to resolve '%s' as a valid ref"),
227
			     replace_ref);
228

229
	return replace_object_oid(object_ref, &object, replace_ref, &repl, force);
230
}
231

232
/*
233
 * Write the contents of the object named by "sha1" to the file "filename".
234
 * If "raw" is true, then the object's raw contents are printed according to
235
 * "type". Otherwise, we pretty-print the contents for human editing.
236
 */
237
static int export_object(const struct object_id *oid, enum object_type type,
238
			  int raw, const char *filename)
239
{
240
	struct child_process cmd = CHILD_PROCESS_INIT;
241
	int fd;
242

243
	fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
244
	if (fd < 0)
245
		return error_errno(_("unable to open %s for writing"), filename);
246

247
	strvec_push(&cmd.args, "--no-replace-objects");
248
	strvec_push(&cmd.args, "cat-file");
249
	if (raw)
250
		strvec_push(&cmd.args, type_name(type));
251
	else
252
		strvec_push(&cmd.args, "-p");
253
	strvec_push(&cmd.args, oid_to_hex(oid));
254
	cmd.git_cmd = 1;
255
	cmd.out = fd;
256

257
	if (run_command(&cmd))
258
		return error(_("cat-file reported failure"));
259
	return 0;
260
}
261

262
/*
263
 * Read a previously-exported (and possibly edited) object back from "filename",
264
 * interpreting it as "type", and writing the result to the object database.
265
 * The sha1 of the written object is returned via sha1.
266
 */
267
static int import_object(struct object_id *oid, enum object_type type,
268
			  int raw, const char *filename)
269
{
270
	int fd;
271

272
	fd = open(filename, O_RDONLY);
273
	if (fd < 0)
274
		return error_errno(_("unable to open %s for reading"), filename);
275

276
	if (!raw && type == OBJ_TREE) {
277
		struct child_process cmd = CHILD_PROCESS_INIT;
278
		struct strbuf result = STRBUF_INIT;
279

280
		strvec_push(&cmd.args, "mktree");
281
		cmd.git_cmd = 1;
282
		cmd.in = fd;
283
		cmd.out = -1;
284

285
		if (start_command(&cmd)) {
286
			close(fd);
287
			return error(_("unable to spawn mktree"));
288
		}
289

290
		if (strbuf_read(&result, cmd.out, the_hash_algo->hexsz + 1) < 0) {
291
			error_errno(_("unable to read from mktree"));
292
			close(fd);
293
			close(cmd.out);
294
			return -1;
295
		}
296
		close(cmd.out);
297

298
		if (finish_command(&cmd)) {
299
			strbuf_release(&result);
300
			return error(_("mktree reported failure"));
301
		}
302
		if (get_oid_hex(result.buf, oid) < 0) {
303
			strbuf_release(&result);
304
			return error(_("mktree did not return an object name"));
305
		}
306

307
		strbuf_release(&result);
308
	} else {
309
		struct stat st;
310
		int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
311

312
		if (fstat(fd, &st) < 0) {
313
			error_errno(_("unable to fstat %s"), filename);
314
			close(fd);
315
			return -1;
316
		}
317
		if (index_fd(the_repository->index, oid, fd, &st, type, NULL, flags) < 0)
318
			return error(_("unable to write object to database"));
319
		/* index_fd close()s fd for us */
320
	}
321

322
	/*
323
	 * No need to close(fd) here; both run-command and index-fd
324
	 * will have done it for us.
325
	 */
326
	return 0;
327
}
328

329
static int edit_and_replace(const char *object_ref, int force, int raw)
330
{
331
	char *tmpfile;
332
	enum object_type type;
333
	struct object_id old_oid, new_oid, prev;
334
	struct strbuf ref = STRBUF_INIT;
335

336
	if (repo_get_oid(the_repository, object_ref, &old_oid) < 0)
337
		return error(_("not a valid object name: '%s'"), object_ref);
338

339
	type = oid_object_info(the_repository, &old_oid, NULL);
340
	if (type < 0)
341
		return error(_("unable to get object type for %s"),
342
			     oid_to_hex(&old_oid));
343

344
	if (check_ref_valid(&old_oid, &prev, &ref, force)) {
345
		strbuf_release(&ref);
346
		return -1;
347
	}
348
	strbuf_release(&ref);
349

350
	tmpfile = git_pathdup("REPLACE_EDITOBJ");
351
	if (export_object(&old_oid, type, raw, tmpfile)) {
352
		free(tmpfile);
353
		return -1;
354
	}
355
	if (launch_editor(tmpfile, NULL, NULL) < 0) {
356
		free(tmpfile);
357
		return error(_("editing object file failed"));
358
	}
359
	if (import_object(&new_oid, type, raw, tmpfile)) {
360
		free(tmpfile);
361
		return -1;
362
	}
363
	free(tmpfile);
364

365
	if (oideq(&old_oid, &new_oid))
366
		return error(_("new object is the same as the old one: '%s'"), oid_to_hex(&old_oid));
367

368
	return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force);
369
}
370

371
static int replace_parents(struct strbuf *buf, int argc, const char **argv)
372
{
373
	struct strbuf new_parents = STRBUF_INIT;
374
	const char *parent_start, *parent_end;
375
	int i;
376
	const unsigned hexsz = the_hash_algo->hexsz;
377

378
	/* find existing parents */
379
	parent_start = buf->buf;
380
	parent_start += hexsz + 6; /* "tree " + "hex sha1" + "\n" */
381
	parent_end = parent_start;
382

383
	while (starts_with(parent_end, "parent "))
384
		parent_end += hexsz + 8; /* "parent " + "hex sha1" + "\n" */
385

386
	/* prepare new parents */
387
	for (i = 0; i < argc; i++) {
388
		struct object_id oid;
389
		struct commit *commit;
390

391
		if (repo_get_oid(the_repository, argv[i], &oid) < 0) {
392
			strbuf_release(&new_parents);
393
			return error(_("not a valid object name: '%s'"),
394
				     argv[i]);
395
		}
396
		commit = lookup_commit_reference(the_repository, &oid);
397
		if (!commit) {
398
			strbuf_release(&new_parents);
399
			return error(_("could not parse %s as a commit"), argv[i]);
400
		}
401
		strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&commit->object.oid));
402
	}
403

404
	/* replace existing parents with new ones */
405
	strbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start,
406
		      new_parents.buf, new_parents.len);
407

408
	strbuf_release(&new_parents);
409
	return 0;
410
}
411

412
struct check_mergetag_data {
413
	int argc;
414
	const char **argv;
415
};
416

417
static int check_one_mergetag(struct commit *commit UNUSED,
418
			       struct commit_extra_header *extra,
419
			       void *data)
420
{
421
	struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data;
422
	const char *ref = mergetag_data->argv[0];
423
	struct object_id tag_oid;
424
	struct tag *tag;
425
	int i;
426

427
	hash_object_file(the_hash_algo, extra->value, extra->len,
428
			 OBJ_TAG, &tag_oid);
429
	tag = lookup_tag(the_repository, &tag_oid);
430
	if (!tag)
431
		return error(_("bad mergetag in commit '%s'"), ref);
432
	if (parse_tag_buffer(the_repository, tag, extra->value, extra->len))
433
		return error(_("malformed mergetag in commit '%s'"), ref);
434

435
	/* iterate over new parents */
436
	for (i = 1; i < mergetag_data->argc; i++) {
437
		struct object_id oid;
438
		if (repo_get_oid(the_repository, mergetag_data->argv[i], &oid) < 0)
439
			return error(_("not a valid object name: '%s'"),
440
				     mergetag_data->argv[i]);
441
		if (oideq(get_tagged_oid(tag), &oid))
442
			return 0; /* found */
443
	}
444

445
	return error(_("original commit '%s' contains mergetag '%s' that is "
446
		       "discarded; use --edit instead of --graft"), ref,
447
		     oid_to_hex(&tag_oid));
448
}
449

450
static int check_mergetags(struct commit *commit, int argc, const char **argv)
451
{
452
	struct check_mergetag_data mergetag_data;
453

454
	mergetag_data.argc = argc;
455
	mergetag_data.argv = argv;
456
	return for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
457
}
458

459
static int create_graft(int argc, const char **argv, int force, int gentle)
460
{
461
	struct object_id old_oid, new_oid;
462
	const char *old_ref = argv[0];
463
	struct commit *commit;
464
	struct strbuf buf = STRBUF_INIT;
465
	const char *buffer;
466
	unsigned long size;
467

468
	if (repo_get_oid(the_repository, old_ref, &old_oid) < 0)
469
		return error(_("not a valid object name: '%s'"), old_ref);
470
	commit = lookup_commit_reference(the_repository, &old_oid);
471
	if (!commit)
472
		return error(_("could not parse %s"), old_ref);
473

474
	buffer = repo_get_commit_buffer(the_repository, commit, &size);
475
	strbuf_add(&buf, buffer, size);
476
	repo_unuse_commit_buffer(the_repository, commit, buffer);
477

478
	if (replace_parents(&buf, argc - 1, &argv[1]) < 0) {
479
		strbuf_release(&buf);
480
		return -1;
481
	}
482

483
	if (remove_signature(&buf)) {
484
		warning(_("the original commit '%s' has a gpg signature"), old_ref);
485
		warning(_("the signature will be removed in the replacement commit!"));
486
	}
487

488
	if (check_mergetags(commit, argc, argv)) {
489
		strbuf_release(&buf);
490
		return -1;
491
	}
492

493
	if (write_object_file(buf.buf, buf.len, OBJ_COMMIT, &new_oid)) {
494
		strbuf_release(&buf);
495
		return error(_("could not write replacement commit for: '%s'"),
496
			     old_ref);
497
	}
498

499
	strbuf_release(&buf);
500

501
	if (oideq(&commit->object.oid, &new_oid)) {
502
		if (gentle) {
503
			warning(_("graft for '%s' unnecessary"),
504
				oid_to_hex(&commit->object.oid));
505
			return 0;
506
		}
507
		return error(_("new commit is the same as the old one: '%s'"),
508
			     oid_to_hex(&commit->object.oid));
509
	}
510

511
	return replace_object_oid(old_ref, &commit->object.oid,
512
				  "replacement", &new_oid, force);
513
}
514

515
static int convert_graft_file(int force)
516
{
517
	const char *graft_file = get_graft_file(the_repository);
518
	FILE *fp = fopen_or_warn(graft_file, "r");
519
	struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;
520
	struct strvec args = STRVEC_INIT;
521

522
	if (!fp)
523
		return -1;
524

525
	no_graft_file_deprecated_advice = 1;
526
	while (strbuf_getline(&buf, fp) != EOF) {
527
		if (*buf.buf == '#')
528
			continue;
529

530
		strvec_split(&args, buf.buf);
531
		if (args.nr && create_graft(args.nr, args.v, force, 1))
532
			strbuf_addf(&err, "\n\t%s", buf.buf);
533
		strvec_clear(&args);
534
	}
535
	fclose(fp);
536

537
	strbuf_release(&buf);
538

539
	if (!err.len)
540
		return unlink_or_warn(graft_file);
541

542
	warning(_("could not convert the following graft(s):\n%s"), err.buf);
543
	strbuf_release(&err);
544

545
	return -1;
546
}
547

548
int cmd_replace(int argc, const char **argv, const char *prefix)
549
{
550
	int force = 0;
551
	int raw = 0;
552
	const char *format = NULL;
553
	enum {
554
		MODE_UNSPECIFIED = 0,
555
		MODE_LIST,
556
		MODE_DELETE,
557
		MODE_EDIT,
558
		MODE_GRAFT,
559
		MODE_CONVERT_GRAFT_FILE,
560
		MODE_REPLACE
561
	} cmdmode = MODE_UNSPECIFIED;
562
	struct option options[] = {
563
		OPT_CMDMODE('l', "list", &cmdmode, N_("list replace refs"), MODE_LIST),
564
		OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE),
565
		OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT),
566
		OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
567
		OPT_CMDMODE(0, "convert-graft-file", &cmdmode, N_("convert existing graft file"), MODE_CONVERT_GRAFT_FILE),
568
		OPT_BOOL_F('f', "force", &force, N_("replace the ref if it exists"),
569
			   PARSE_OPT_NOCOMPLETE),
570
		OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")),
571
		OPT_STRING(0, "format", &format, N_("format"), N_("use this format")),
572
		OPT_END()
573
	};
574

575
	disable_replace_refs();
576
	git_config(git_default_config, NULL);
577

578
	argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
579

580
	if (!cmdmode)
581
		cmdmode = argc ? MODE_REPLACE : MODE_LIST;
582

583
	if (format && cmdmode != MODE_LIST)
584
		usage_msg_opt(_("--format cannot be used when not listing"),
585
			      git_replace_usage, options);
586

587
	if (force &&
588
	    cmdmode != MODE_REPLACE &&
589
	    cmdmode != MODE_EDIT &&
590
	    cmdmode != MODE_GRAFT &&
591
	    cmdmode != MODE_CONVERT_GRAFT_FILE)
592
		usage_msg_opt(_("-f only makes sense when writing a replacement"),
593
			      git_replace_usage, options);
594

595
	if (raw && cmdmode != MODE_EDIT)
596
		usage_msg_opt(_("--raw only makes sense with --edit"),
597
			      git_replace_usage, options);
598

599
	switch (cmdmode) {
600
	case MODE_DELETE:
601
		if (argc < 1)
602
			usage_msg_opt(_("-d needs at least one argument"),
603
				      git_replace_usage, options);
604
		return for_each_replace_name(argv, delete_replace_ref);
605

606
	case MODE_REPLACE:
607
		if (argc != 2)
608
			usage_msg_opt(_("bad number of arguments"),
609
				      git_replace_usage, options);
610
		return replace_object(argv[0], argv[1], force);
611

612
	case MODE_EDIT:
613
		if (argc != 1)
614
			usage_msg_opt(_("-e needs exactly one argument"),
615
				      git_replace_usage, options);
616
		return edit_and_replace(argv[0], force, raw);
617

618
	case MODE_GRAFT:
619
		if (argc < 1)
620
			usage_msg_opt(_("-g needs at least one argument"),
621
				      git_replace_usage, options);
622
		return create_graft(argc, argv, force, 0);
623

624
	case MODE_CONVERT_GRAFT_FILE:
625
		if (argc != 0)
626
			usage_msg_opt(_("--convert-graft-file takes no argument"),
627
				      git_replace_usage, options);
628
		return !!convert_graft_file(force);
629

630
	case MODE_LIST:
631
		if (argc > 1)
632
			usage_msg_opt(_("only one pattern can be given with -l"),
633
				      git_replace_usage, options);
634
		return list_replace_refs(argv[0], format);
635

636
	default:
637
		BUG("invalid cmdmode %d", (int)cmdmode);
638
	}
639
}
640

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.