13
13
#include "refs.h"
14
14
#include "parse-options.h"
15
15
#include "run-command.h"
16
+ #include "tag.h"
16
17
17
18
static const char * const git_replace_usage [] = {
18
19
N_ ("git replace [-f] <object> <replacement>" ),
19
20
N_ ("git replace [-f] --edit <object>" ),
21
+ N_ ("git replace [-f] --graft <commit> [<parent>...]" ),
20
22
N_ ("git replace -d <object>..." ),
21
23
N_ ("git replace [--format=<format>] [-l [<pattern>]]" ),
22
24
NULL
@@ -299,6 +301,117 @@ static int edit_and_replace(const char *object_ref, int force, int raw)
299
301
return replace_object_sha1 (object_ref , old , "replacement" , new , force );
300
302
}
301
303
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
+
302
415
int cmd_replace (int argc , const char * * argv , const char * prefix )
303
416
{
304
417
int force = 0 ;
@@ -309,12 +422,14 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
309
422
MODE_LIST ,
310
423
MODE_DELETE ,
311
424
MODE_EDIT ,
425
+ MODE_GRAFT ,
312
426
MODE_REPLACE
313
427
} cmdmode = MODE_UNSPECIFIED ;
314
428
struct option options [] = {
315
429
OPT_CMDMODE ('l' , "list" , & cmdmode , N_ ("list replace refs" ), MODE_LIST ),
316
430
OPT_CMDMODE ('d' , "delete" , & cmdmode , N_ ("delete replace refs" ), MODE_DELETE ),
317
431
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 ),
318
433
OPT_BOOL ('f' , "force" , & force , N_ ("replace the ref if it exists" )),
319
434
OPT_BOOL (0 , "raw" , & raw , N_ ("do not pretty-print contents for --edit" )),
320
435
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)
332
447
usage_msg_opt ("--format cannot be used when not listing" ,
333
448
git_replace_usage , options );
334
449
335
- if (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT )
450
+ if (force &&
451
+ cmdmode != MODE_REPLACE &&
452
+ cmdmode != MODE_EDIT &&
453
+ cmdmode != MODE_GRAFT )
336
454
usage_msg_opt ("-f only makes sense when writing a replacement" ,
337
455
git_replace_usage , options );
338
456
@@ -359,6 +477,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
359
477
git_replace_usage , options );
360
478
return edit_and_replace (argv [0 ], force , raw );
361
479
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
+
362
486
case MODE_LIST :
363
487
if (argc > 1 )
364
488
usage_msg_opt ("only one pattern can be given with -l" ,
0 commit comments