1313#include "refs.h"
1414#include "parse-options.h"
1515#include "run-command.h"
16+ #include "tag.h"
1617
1718static 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+
302415int 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" ,
0 commit comments