1010#include <git2/sys/odb_backend.h>
1111#include <git2/sys/refdb_backend.h>
1212#include <git2/refs.h>
13+ #include <git2/apply.h>
1314
1415extern VALUE rb_mRugged ;
1516extern VALUE rb_eRuggedError ;
@@ -18,6 +19,7 @@ extern VALUE rb_cRuggedConfig;
1819extern VALUE rb_cRuggedBackend ;
1920extern VALUE rb_cRuggedRemote ;
2021extern VALUE rb_cRuggedCommit ;
22+ extern VALUE rb_cRuggedDiff ;
2123extern VALUE rb_cRuggedTag ;
2224extern VALUE rb_cRuggedTree ;
2325extern VALUE rb_cRuggedReference ;
@@ -396,6 +398,98 @@ static VALUE rb_git_repo_init_at(int argc, VALUE *argv, VALUE klass)
396398 return rugged_repo_new (klass , repo );
397399}
398400
401+ static int apply_cb_result (int exception , VALUE result )
402+ {
403+ if (exception || result == Qnil ) {
404+ return GIT_EAPPLYFAIL ;
405+ } else {
406+ if (RTEST (result )) {
407+ return 0 ;
408+ } else {
409+ return 1 ;
410+ }
411+ }
412+ }
413+
414+ static int apply_delta_cb (const git_diff_delta * delta , void * data )
415+ {
416+ struct rugged_apply_cb_payload * payload = data ;
417+ VALUE args = rb_ary_new2 (2 );
418+ VALUE result ;
419+
420+ if (NIL_P (payload -> delta_cb ))
421+ return 0 ;
422+
423+ VALUE rb_delta = rugged_diff_delta_new (Qnil , delta );
424+
425+ rb_ary_push (args , payload -> delta_cb );
426+ rb_ary_push (args , rb_delta );
427+
428+ result = rb_protect (rugged__block_yield_splat , args , & payload -> exception );
429+
430+ return apply_cb_result (payload -> exception , result );
431+ }
432+
433+ static int apply_hunk_cb (const git_diff_hunk * hunk , void * data )
434+ {
435+ struct rugged_apply_cb_payload * payload = data ;
436+ VALUE args = rb_ary_new2 (2 );
437+ VALUE result ;
438+
439+ if (NIL_P (payload -> hunk_cb ))
440+ return 0 ;
441+
442+ VALUE rb_hunk = rugged_diff_hunk_new (Qnil , 0 , hunk , 0 );
443+
444+ rb_ary_push (args , payload -> hunk_cb );
445+ rb_ary_push (args , rb_hunk );
446+
447+ result = rb_protect (rugged__block_yield_splat , args , & payload -> exception );
448+
449+ return apply_cb_result (payload -> exception , result );
450+ }
451+
452+ static void rugged_parse_apply_options (git_apply_options * opts , git_apply_location_t * location , VALUE rb_options , struct rugged_apply_cb_payload * payload )
453+ {
454+ if (!NIL_P (rb_options )) {
455+ VALUE rb_value ;
456+ Check_Type (rb_options , T_HASH );
457+
458+ rb_value = rb_hash_aref (rb_options , CSTR2SYM ("location" ));
459+ if (!NIL_P (rb_value )) {
460+ ID id_location ;
461+
462+ Check_Type (rb_value , T_SYMBOL );
463+ id_location = SYM2ID (rb_value );
464+
465+ if (id_location == rb_intern ("both" )) {
466+ * location = GIT_APPLY_LOCATION_BOTH ;
467+ } else if (id_location == rb_intern ("index" )) {
468+ * location = GIT_APPLY_LOCATION_INDEX ;
469+ } else if (id_location == rb_intern ("workdir" )) {
470+ * location = GIT_APPLY_LOCATION_WORKDIR ;
471+ } else {
472+ rb_raise (rb_eTypeError ,
473+ "Invalid location. Expected `:both`, `:index`, or `:workdir`" );
474+ }
475+ }
476+
477+ opts -> payload = payload ;
478+
479+ payload -> delta_cb = rb_hash_aref (rb_options , CSTR2SYM ("delta_callback" ));
480+ if (!NIL_P (payload -> delta_cb )) {
481+ CALLABLE_OR_RAISE (payload -> delta_cb , "delta_callback" );
482+ opts -> delta_cb = apply_delta_cb ;
483+ }
484+
485+ payload -> hunk_cb = rb_hash_aref (rb_options , CSTR2SYM ("hunk_callback" ));
486+ if (!NIL_P (payload -> hunk_cb )) {
487+ CALLABLE_OR_RAISE (payload -> hunk_cb , "hunk_callback" );
488+ opts -> hunk_cb = apply_hunk_cb ;
489+ }
490+ }
491+ }
492+
399493static void parse_clone_options (git_clone_options * ret , VALUE rb_options , struct rugged_remote_cb_payload * remote_payload )
400494{
401495 VALUE val ;
@@ -868,6 +962,69 @@ static VALUE rb_git_repo_revert_commit(int argc, VALUE *argv, VALUE self)
868962 return rugged_index_new (rb_cRuggedIndex , self , index );
869963}
870964
965+ /*
966+ * call-seq:
967+ * repo.apply(diff, options = {}) -> true or false
968+ *
969+ * Applies the given diff to the repository.
970+ * The following options can be passed in the +options+ Hash:
971+ *
972+ * :location ::
973+ * Whether to apply the changes to the workdir (default for non-bare),
974+ * the index (default for bare) or both. Valid values: +:index+, +:workdir+,
975+ * +:both+.
976+ *
977+ * :delta_callback ::
978+ * While applying the patch, this callback will be executed per delta (file).
979+ * The current +delta+ will be passed to the block. The block's return value
980+ * determines further behavior. When the block evaluates to:
981+ * - +true+: the hunk will be applied and the apply process will continue.
982+ * - +false+: the hunk will be skipped, but the apply process continues.
983+ * - +nil+: the hunk is not applied, and the apply process is aborted.
984+ *
985+ * :hunk_callback ::
986+ * While applying the patch, this callback will be executed per hunk.
987+ * The current +hunk+ will be passed to the block. The block's return value
988+ * determines further behavior, as per +:delta_callback+.
989+ *
990+ */
991+ static VALUE rb_git_repo_apply (int argc , VALUE * argv , VALUE self )
992+ {
993+ VALUE rb_diff , rb_options ;
994+ git_diff * diff ;
995+ git_repository * repo ;
996+ git_apply_options opts = GIT_APPLY_OPTIONS_INIT ;
997+ git_apply_location_t location ;
998+ struct rugged_apply_cb_payload payload = { Qnil , Qnil , 0 };
999+ int error ;
1000+
1001+ Data_Get_Struct (self , git_repository , repo );
1002+ if (git_repository_is_bare (repo )) {
1003+ location = GIT_APPLY_LOCATION_INDEX ;
1004+ } else {
1005+ location = GIT_APPLY_LOCATION_WORKDIR ;
1006+ }
1007+
1008+ rb_scan_args (argc , argv , "11" , & rb_diff , & rb_options );
1009+
1010+ if (!rb_obj_is_kind_of (rb_diff , rb_cRuggedDiff )) {
1011+ rb_raise (rb_eArgError , "Expected a Rugged::Diff." );
1012+ }
1013+
1014+ if (!NIL_P (rb_options )) {
1015+ Check_Type (rb_options , T_HASH );
1016+ rugged_parse_apply_options (& opts , & location , rb_options , & payload );
1017+ }
1018+
1019+ Data_Get_Struct (rb_diff , git_diff , diff );
1020+
1021+ error = git_apply (repo , diff , location , & opts );
1022+
1023+ rugged_exception_check (error );
1024+
1025+ return Qtrue ;
1026+ }
1027+
8711028/*
8721029 * call-seq:
8731030 * repo.merge_commits(our_commit, their_commit, options = {}) -> index
@@ -2608,6 +2765,8 @@ void Init_rugged_repo(void)
26082765 rb_define_method (rb_cRuggedRepo , "merge_analysis" , rb_git_repo_merge_analysis , -1 );
26092766 rb_define_method (rb_cRuggedRepo , "merge_commits" , rb_git_repo_merge_commits , -1 );
26102767
2768+ rb_define_method (rb_cRuggedRepo , "apply" , rb_git_repo_apply , -1 );
2769+
26112770 rb_define_method (rb_cRuggedRepo , "revert_commit" , rb_git_repo_revert_commit , -1 );
26122771
26132772 rb_define_method (rb_cRuggedRepo , "path_ignored?" , rb_git_repo_is_path_ignored , 1 );
0 commit comments