diff --git a/src/git/zcl_abapgit_git_add_patch.clas.abap b/src/git/zcl_abapgit_git_add_patch.clas.abap new file mode 100644 index 000000000..06b76cd9d --- /dev/null +++ b/src/git/zcl_abapgit_git_add_patch.clas.abap @@ -0,0 +1,118 @@ +CLASS zcl_abapgit_git_add_patch DEFINITION + PUBLIC + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + + METHODS: + constructor + IMPORTING + it_diff TYPE zif_abapgit_definitions=>ty_diffs_tt, + + get_patch + RETURNING + VALUE(rt_patch) TYPE stringtab + RAISING + zcx_abapgit_exception, + + get_patch_binary + RETURNING + VALUE(rv_patch_binary) TYPE xstring + RAISING + zcx_abapgit_exception. + + PRIVATE SECTION. + DATA: + mt_diff TYPE zif_abapgit_definitions=>ty_diffs_tt, + mt_patch TYPE stringtab. + + METHODS: + calculate_patch + RETURNING + VALUE(rt_patch) TYPE stringtab + RAISING + zcx_abapgit_exception. + +ENDCLASS. + + + +CLASS zcl_abapgit_git_add_patch IMPLEMENTATION. + + + METHOD calculate_patch. + + FIELD-SYMBOLS: TYPE zif_abapgit_definitions=>ty_diff. + + LOOP AT mt_diff ASSIGNING . + + CASE -result. + WHEN ' '. + + INSERT -new INTO TABLE rt_patch. + + WHEN zif_abapgit_definitions=>c_diff-insert. + + IF -patch_flag = abap_true. + INSERT -new INTO TABLE rt_patch. + ENDIF. + + WHEN zif_abapgit_definitions=>c_diff-delete. + + IF -patch_flag = abap_false. + INSERT -old INTO TABLE rt_patch. + ENDIF. + + WHEN zif_abapgit_definitions=>c_diff-update. + + IF -patch_flag = abap_true. + INSERT -new INTO TABLE rt_patch. + ELSE. + INSERT -old INTO TABLE rt_patch. + ENDIF. + + WHEN OTHERS. + + zcx_abapgit_exception=>raise( |Unknown result| ). + + ENDCASE. + + ENDLOOP. + + ENDMETHOD. + + + METHOD constructor. + + mt_diff = it_diff. + + ENDMETHOD. + + + METHOD get_patch. + + IF mt_patch IS INITIAL. + mt_patch = calculate_patch( ). + ENDIF. + + rt_patch = mt_patch. + + ENDMETHOD. + + + METHOD get_patch_binary. + + DATA: lv_string TYPE string. + + IF mt_patch IS INITIAL. + mt_patch = calculate_patch( ). + ENDIF. + + CONCATENATE LINES OF mt_patch INTO lv_string SEPARATED BY zif_abapgit_definitions=>c_newline. + lv_string = lv_string && zif_abapgit_definitions=>c_newline. + + rv_patch_binary = zcl_abapgit_convert=>string_to_xstring_utf8( lv_string ). + + ENDMETHOD. +ENDCLASS. diff --git a/src/git/zcl_abapgit_git_add_patch.clas.testclasses.abap b/src/git/zcl_abapgit_git_add_patch.clas.testclasses.abap new file mode 100644 index 000000000..f236c6858 --- /dev/null +++ b/src/git/zcl_abapgit_git_add_patch.clas.testclasses.abap @@ -0,0 +1,397 @@ +*"* use this source file for your ABAP unit test classes + +CLASS ltcl_calculate_patch DEFINITION FINAL FOR TESTING + DURATION SHORT + RISK LEVEL HARMLESS. + + PRIVATE SECTION. + METHODS: + single_insert FOR TESTING RAISING cx_static_check, + multiple_adjacent_insert FOR TESTING RAISING cx_static_check, + multiple_non_adjacent_insert FOR TESTING RAISING cx_static_check, + multiple_partial_insert FOR TESTING RAISING cx_static_check, + + single_delete FOR TESTING RAISING cx_static_check, + multiple_adjacend_delete FOR TESTING RAISING cx_static_check, + multiple_non_adjacent_delete FOR TESTING RAISING cx_static_check, + multiple_partial_delete FOR TESTING RAISING cx_static_check, + + single_update FOR TESTING RAISING cx_static_check, + multiple_adjacend_update FOR TESTING RAISING cx_static_check, + multiple_non_adjacent_update FOR TESTING RAISING cx_static_check, + multiple_partial_update FOR TESTING RAISING cx_static_check, + + mixed FOR TESTING RAISING cx_static_check, + + unknown_result_type FOR TESTING RAISING cx_static_check. + + METHODS: + setup, + + given_diff + IMPORTING + iv_patch_flag TYPE zif_abapgit_definitions=>ty_diff-patch_flag + iv_new_num TYPE zif_abapgit_definitions=>ty_diff-new_num + iv_new TYPE zif_abapgit_definitions=>ty_diff-new + iv_result TYPE zif_abapgit_definitions=>ty_diff-result + iv_old_num TYPE zif_abapgit_definitions=>ty_diff-old_num + iv_old TYPE zif_abapgit_definitions=>ty_diff-old + iv_short TYPE zif_abapgit_definitions=>ty_diff-short DEFAULT 'X' + iv_beacon TYPE zif_abapgit_definitions=>ty_diff-beacon DEFAULT 1, + + when_patch_is_calculated, + + then_patch_should_be + IMPORTING + iv_exp_patch TYPE string, + then_exception_is_raised. + + DATA: + mt_diff TYPE zif_abapgit_definitions=>ty_diffs_tt, + mt_patch TYPE stringtab, + mv_index TYPE sytabix, + mx_error TYPE REF TO zcx_abapgit_exception. + +ENDCLASS. + + +CLASS ltcl_calculate_patch IMPLEMENTATION. + + METHOD setup. + + mv_index = 0. + + ENDMETHOD. + + DEFINE given_diff. + + given_diff( iv_patch_flag = &1 + iv_new_num = &2 + iv_new = &3 + iv_result = &4 + iv_old_num = &5 + iv_old = &6 ). + + END-OF-DEFINITION. + + METHOD single_insert. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ', + 'X' ' 2' 'write: `Test`.' 'I' ' ' ' ', + ' ' ' 3' ' ' ' ' ' 2' ' '. + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Test`.' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_adjacent_insert. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ', + 'X' ' 2' 'write: `Test`.' 'I' ' ' ' ', + 'X' ' 3' 'write: `Hello world`.' 'I' ' ' ' ', + ' ' ' 4' ' ' ' ' ' 2' ' '. + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Test`.' ), + 'write: `Hello world`.' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_non_adjacent_insert. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ', + 'X' ' 2' 'write: `Test`.' 'I' ' ' ' ', + ' ' ' 3' ' ' ' ' ' 2' ' ', + 'X' ' 4' 'write: `Hello world`.' 'I' ' ' ' ', + ' ' ' 5' ' ' ' ' ' 3' ' '. + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Test`.' ), + ' ' ), + 'write: `Hello world`.' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_partial_insert. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ', + 'X' ' 2' 'write: `Test`.' 'I' ' ' ' ', + ' ' ' 3' ' ' ' ' ' 2' ' ', + ' ' ' 4' 'write: `Hello world`.' 'I' ' ' ' ', + ' ' ' 5' ' ' ' ' ' 3' ' '. + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Test`.' ), + ' ' ), + ' ' ). + + ENDMETHOD. + + METHOD single_delete. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' ' ' ' 'D' ' 2' 'write: `Test`.', + ' ' ' 2' ' ' ' ' ' 3' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_adjacend_delete. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' ' ' ' 'D' ' 2' 'write: `Test`.' , + 'X' ' ' ' ' 'D' ' 3' 'write: `Hello world`.', + ' ' ' 2' ' ' ' ' ' 4' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_non_adjacent_delete. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' ' ' ' 'D' ' 2' 'write: `Test`.' , + ' ' ' ' ' ' 'D' ' 3' 'write: `Hello world`.', + 'X' ' ' ' ' 'D' ' 4' 'write: `Hello 123`.' , + ' ' ' 2' ' ' ' ' ' 5' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Hello world`.' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_partial_delete. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' ' ' ' 'D' ' 2' 'write: `Test`.' , + ' ' ' ' ' ' 'D' ' 3' 'write: `Hello world`.', + ' ' ' ' ' ' 'D' ' 4' 'write: `Hello 123`.' , + 'X' ' ' ' ' 'D' ' 5' 'write: `Hello test`.' , + ' ' ' 2' ' ' ' ' ' 6' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be( ' ' ). + then_patch_should_be( 'write: `Hello world`.' ). + then_patch_should_be( 'write: `Hello 123`.' ). + then_patch_should_be( ' ' ). + + ENDMETHOD. + + METHOD single_update. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' 2' 'write: `Hello world`.' 'U' ' 2' 'write: `Test`.', + ' ' ' 3' ' ' ' ' ' 3' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Hello world`.' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_adjacend_update. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' 2' 'write: `Hello world`.' 'U' ' 2' 'write: `Test`.' , + 'X' ' 3' 'write: `Test`.' 'U' ' 3' 'write: `Hello world`.', + ' ' ' 4' ' ' ' ' ' 4' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Hello world`.' ), + 'write: `Test`.' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_non_adjacent_update. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' 2' 'write: `Hello world`.' 'U' ' 2' 'write: `Test`.' , + ' ' ' 3' ' ' ' ' ' 3' ' ' , + 'X' ' 4' 'write: `Test`.' 'U' ' 4' 'write: `Hello world`.', + ' ' ' 5' ' ' ' ' ' 5' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Hello world`.' ), + ' ' ), + 'write: `Test`.' ), + ' ' ). + + ENDMETHOD. + + METHOD multiple_partial_update. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' 2' 'write: `Hello world`.' 'U' ' 2' 'write: `Test`.' , + ' ' ' 3' ' ' ' ' ' 3' ' ' , + ' ' ' 4' 'write: `Test`.' 'U' ' 4' 'write: `Hello world`.', + ' ' ' 5' ' ' ' ' ' 5' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Hello world`.' ), + ' ' ), + 'write: `Hello world`.' ), + ' ' ). + + ENDMETHOD. + + METHOD mixed. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' ' ' ' 1' ' ' , + 'X' ' 2' 'write: `Hello world`.' 'U' ' 2' 'write: `Test`.' , + ' ' ' 3' ' ' ' ' ' 3' ' ' , + ' ' ' 4' 'write: `Test`.' 'U' ' 4' 'write: `Hello world`.', + ' ' ' 5' ' ' ' ' ' 5' ' ' , + 'X' ' 6' 'write: `newline`.' 'I' ' ' ' ' . + + when_patch_is_calculated( ). + + then_patch_should_be(: + ' ' ), + 'write: `Hello world`.' ), + ' ' ), + 'write: `Hello world`.' ), + ' ' ), + 'write: `newline`.' ). + + ENDMETHOD. + + METHOD unknown_result_type. + + given_diff: + " patch_flag new_num new result old_num old + ' ' ' 1' ' ' 'X' ' 1' ' '. + + when_patch_is_calculated( ). + + then_exception_is_raised( ). + + ENDMETHOD. + + METHOD given_diff. + + DATA: ls_diff LIKE LINE OF mt_diff. + + ls_diff-patch_flag = iv_patch_flag. + ls_diff-new_num = iv_new_num. + ls_diff-new = iv_new. + ls_diff-result = iv_result. + ls_diff-old_num = iv_old_num. + ls_diff-old = iv_old. + ls_diff-short = iv_short. + ls_diff-beacon = iv_beacon. + INSERT ls_diff INTO TABLE mt_diff. + + ENDMETHOD. + + + METHOD when_patch_is_calculated. + + DATA: lo_git_add_patch TYPE REF TO zcl_abapgit_git_add_patch. + + CREATE OBJECT lo_git_add_patch + EXPORTING + it_diff = mt_diff. + + TRY. + mt_patch = lo_git_add_patch->get_patch( ). + + CATCH zcx_abapgit_exception INTO mx_error. + ENDTRY. + + ENDMETHOD. + + + METHOD then_patch_should_be. + + FIELD-SYMBOLS: LIKE LINE OF mt_patch. + + mv_index = mv_index + 1. + + READ TABLE mt_patch INDEX mv_index + ASSIGNING . + + cl_abap_unit_assert=>assert_equals( + exp = iv_exp_patch + act = ). + + ENDMETHOD. + + + METHOD then_exception_is_raised. + + cl_abap_unit_assert=>assert_equals( + exp = |Unknown result| + act = mx_error->get_text( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/git/zcl_abapgit_git_add_patch.clas.xml b/src/git/zcl_abapgit_git_add_patch.clas.xml new file mode 100644 index 000000000..ee5426ef9 --- /dev/null +++ b/src/git/zcl_abapgit_git_add_patch.clas.xml @@ -0,0 +1,20 @@ + + + + + + ZCL_ABAPGIT_GIT_ADD_PATCH + 1 + E + git add -p + 2 + 1 + X + X + X + X + X + + + + diff --git a/src/ui/zcl_abapgit_gui_page_diff.clas.abap b/src/ui/zcl_abapgit_gui_page_diff.clas.abap index cdaa8eb4c..e16485758 100644 --- a/src/ui/zcl_abapgit_gui_page_diff.clas.abap +++ b/src/ui/zcl_abapgit_gui_page_diff.clas.abap @@ -1,52 +1,70 @@ CLASS zcl_abapgit_gui_page_diff DEFINITION PUBLIC + INHERITING FROM zcl_abapgit_gui_page FINAL - CREATE PUBLIC INHERITING FROM zcl_abapgit_gui_page. + CREATE PUBLIC . PUBLIC SECTION. - INTERFACES: zif_abapgit_gui_page_hotkey. + + INTERFACES zif_abapgit_gui_page_hotkey . + + TYPES: + BEGIN OF ty_file_diff, + path TYPE string, + filename TYPE string, + lstate TYPE char1, + rstate TYPE char1, + fstate TYPE char1, " FILE state - Abstraction for shorter ifs + o_diff TYPE REF TO zcl_abapgit_diff, + changed_by TYPE xubname, + type TYPE string, + END OF ty_file_diff . + TYPES: + tt_file_diff TYPE STANDARD TABLE OF ty_file_diff . CONSTANTS: BEGIN OF c_fstate, local TYPE char1 VALUE 'L', remote TYPE char1 VALUE 'R', both TYPE char1 VALUE 'B', - END OF c_fstate. + END OF c_fstate . - TYPES: BEGIN OF ty_file_diff, - path TYPE string, - filename TYPE string, - lstate TYPE char1, - rstate TYPE char1, - fstate TYPE char1, " FILE state - Abstraction for shorter ifs - o_diff TYPE REF TO zcl_abapgit_diff, - changed_by TYPE xubname, - type TYPE string, - END OF ty_file_diff, - tt_file_diff TYPE STANDARD TABLE OF ty_file_diff. + METHODS constructor + IMPORTING + !iv_key TYPE zif_abapgit_persistence=>ty_repo-key + !is_file TYPE zif_abapgit_definitions=>ty_file OPTIONAL + !is_object TYPE zif_abapgit_definitions=>ty_item OPTIONAL + !io_stage TYPE REF TO zcl_abapgit_stage OPTIONAL + !iv_patch_mode TYPE abap_bool DEFAULT abap_false + RAISING + zcx_abapgit_exception . - METHODS: - constructor - IMPORTING iv_key TYPE zif_abapgit_persistence=>ty_repo-key - is_file TYPE zif_abapgit_definitions=>ty_file OPTIONAL - is_object TYPE zif_abapgit_definitions=>ty_item OPTIONAL - RAISING zcx_abapgit_exception, - zif_abapgit_gui_page~on_event REDEFINITION. + METHODS zif_abapgit_gui_page~on_event + REDEFINITION . PROTECTED SECTION. METHODS: render_content REDEFINITION, scripts REDEFINITION. PRIVATE SECTION. + TYPES: ty_patch_action TYPE string. + CONSTANTS: BEGIN OF c_actions, + stage TYPE string VALUE 'patch_stage', toggle_unified TYPE string VALUE 'toggle_unified', - END OF c_actions. + END OF c_actions, + BEGIN OF c_patch_action, + add TYPE ty_patch_action VALUE 'add', + remove TYPE ty_patch_action VALUE 'remove', + END OF c_patch_action. DATA: mt_diff_files TYPE tt_file_diff, mt_delayed_lines TYPE zif_abapgit_definitions=>ty_diffs_tt, mv_unified TYPE abap_bool VALUE abap_true, mv_repo_key TYPE zif_abapgit_persistence=>ty_repo-key, - mv_seed TYPE string. " Unique page id to bind JS sessionStorage + mv_seed TYPE string, " Unique page id to bind JS sessionStorage + mv_patch_mode TYPE abap_bool, + mo_stage TYPE REF TO zcl_abapgit_stage. METHODS render_diff IMPORTING is_diff TYPE ty_file_diff @@ -55,6 +73,7 @@ CLASS zcl_abapgit_gui_page_diff DEFINITION IMPORTING is_diff TYPE ty_file_diff RETURNING VALUE(ro_html) TYPE REF TO zcl_abapgit_html. METHODS render_table_head + IMPORTING is_diff TYPE ty_file_diff RETURNING VALUE(ro_html) TYPE REF TO zcl_abapgit_html. METHODS render_lines IMPORTING is_diff TYPE ty_file_diff @@ -65,7 +84,9 @@ CLASS zcl_abapgit_gui_page_diff DEFINITION RETURNING VALUE(ro_html) TYPE REF TO zcl_abapgit_html. METHODS render_line_split IMPORTING is_diff_line TYPE zif_abapgit_definitions=>ty_diff + iv_filename TYPE string iv_fstate TYPE char1 + iv_index TYPE sytabix RETURNING VALUE(ro_html) TYPE REF TO zcl_abapgit_html. METHODS render_line_unified IMPORTING is_diff_line TYPE zif_abapgit_definitions=>ty_diff OPTIONAL @@ -81,6 +102,63 @@ CLASS zcl_abapgit_gui_page_diff DEFINITION IMPORTING iv_d1 TYPE xstring iv_d2 TYPE xstring RETURNING VALUE(rv_yes) TYPE abap_bool. + METHODS add_to_stage + RAISING + zcx_abapgit_exception. + METHODS render_patch + IMPORTING + io_html TYPE REF TO zcl_abapgit_html + iv_patch_line_possible TYPE abap_bool + iv_filename TYPE string + is_diff_line TYPE zif_abapgit_definitions=>ty_diff + iv_index TYPE sytabix. + METHODS start_staging + IMPORTING + it_postdata TYPE cnht_post_data_tab + RAISING + zcx_abapgit_exception . + METHODS apply_patch_all + IMPORTING + iv_action TYPE ty_patch_action + iv_patch TYPE string + iv_patch_flag TYPE abap_bool + RAISING + zcx_abapgit_exception. + METHODS render_patch_head + IMPORTING + io_html TYPE REF TO zcl_abapgit_html + is_diff TYPE zcl_abapgit_gui_page_diff=>ty_file_diff. + METHODS apply_patch_for + IMPORTING + iv_filename TYPE string + iv_line_index TYPE string + iv_patch_flag TYPE abap_bool + RAISING + zcx_abapgit_exception. + METHODS get_diff_object + IMPORTING + iv_filename TYPE string + RETURNING + VALUE(ro_diff) TYPE REF TO zcl_abapgit_diff + RAISING + zcx_abapgit_exception. + METHODS get_diff_line + IMPORTING + io_diff TYPE REF TO zcl_abapgit_diff + iv_line_index TYPE string + RETURNING + VALUE(rs_diff) TYPE zif_abapgit_definitions=>ty_diff + RAISING + zcx_abapgit_exception. + CLASS-METHODS get_patch_data + IMPORTING + iv_patch TYPE string + iv_action TYPE string + EXPORTING + ev_filename TYPE string + ev_line_index TYPE string + RAISING + zcx_abapgit_exception. ENDCLASS. @@ -88,6 +166,50 @@ ENDCLASS. CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. + METHOD add_to_stage. + + DATA: lo_repo TYPE REF TO zcl_abapgit_repo_online, + lt_local TYPE zif_abapgit_definitions=>ty_files_item_tt, + lt_diff TYPE zif_abapgit_definitions=>ty_diffs_tt, + lv_something_patched TYPE abap_bool, + lv_patch TYPE xstring, + lo_git_add_patch TYPE REF TO zcl_abapgit_git_add_patch. + + FIELD-SYMBOLS: TYPE zcl_abapgit_gui_page_diff=>ty_file_diff. + + lo_repo ?= zcl_abapgit_repo_srv=>get_instance( )->get( mv_repo_key ). + lt_local = lo_repo->get_files_local( ). + + LOOP AT mt_diff_files ASSIGNING . + + lt_diff = -o_diff->get( ). + + READ TABLE lt_diff TRANSPORTING NO FIELDS + WITH KEY patch_flag = abap_true. + CHECK sy-subrc = 0. + + lv_something_patched = abap_true. + + CREATE OBJECT lo_git_add_patch + EXPORTING + it_diff = -o_diff->get( ). + + lv_patch = lo_git_add_patch->get_patch_binary( ). + + mo_stage->add( + iv_path = -path + iv_filename = -filename + iv_data = lv_patch ). + + ENDLOOP. + + IF lv_something_patched = abap_false. + zcx_abapgit_exception=>raise( |Nothing added| ). + ENDIF. + + ENDMETHOD. + + METHOD append_diff. DATA: @@ -173,6 +295,67 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. ENDMETHOD. + METHOD apply_patch_all. + + DATA: lv_filename TYPE string, + lt_patch TYPE string_table, + lv_line_index TYPE string. + + FIELD-SYMBOLS: TYPE LINE OF string_table. + + SPLIT iv_patch AT ',' INTO TABLE lt_patch. + + LOOP AT lt_patch ASSIGNING . + + get_patch_data( + EXPORTING + iv_patch = + iv_action = iv_action + IMPORTING + ev_filename = lv_filename + ev_line_index = lv_line_index ). + + apply_patch_for( iv_filename = lv_filename + iv_line_index = lv_line_index + iv_patch_flag = iv_patch_flag ). + + ENDLOOP. + + ENDMETHOD. + + + METHOD apply_patch_for. + + DATA: lo_diff TYPE REF TO zcl_abapgit_diff, + ls_diff_line TYPE zif_abapgit_definitions=>ty_diff, + lv_line TYPE i. + + lo_diff = get_diff_object( iv_filename ). + + ls_diff_line = get_diff_line( io_diff = lo_diff + iv_line_index = iv_line_index ). + + CASE ls_diff_line-result. + WHEN zif_abapgit_definitions=>c_diff-update + OR zif_abapgit_definitions=>c_diff-insert. + + lv_line = ls_diff_line-new_num. + + lo_diff->set_patch_new( iv_line_new = lv_line + iv_patch_flag = iv_patch_flag ). + + WHEN zif_abapgit_definitions=>c_diff-delete. + + lv_line = ls_diff_line-old_num. + + lo_diff->set_patch_old( iv_line_old = lv_line + iv_patch_flag = iv_patch_flag ). + + ENDCASE. + + ENDMETHOD. + + METHOD build_menu. DATA: lo_sub TYPE REF TO zcl_abapgit_html_toolbar, @@ -222,6 +405,14 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. io_sub = lo_sub ) ##NO_TEXT. ENDIF. + IF mv_patch_mode = abap_true. + ro_menu->add( iv_txt = 'Stage' + iv_act = c_actions-stage + iv_id = 'stage' + iv_typ = zif_abapgit_definitions=>c_action_type-dummy + ) ##NO_TEXT. + ENDIF. + ro_menu->add( iv_txt = 'Split/Unified view' iv_act = c_actions-toggle_unified ) ##NO_TEXT. @@ -242,6 +433,13 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. ms_control-page_title = 'DIFF'. mv_unified = zcl_abapgit_persistence_user=>get_instance( )->get_diff_unified( ). mv_repo_key = iv_key. + mv_patch_mode = iv_patch_mode. + mo_stage = io_stage. + + IF mv_patch_mode = abap_true. + " While patching we always want to be in split mode + CLEAR: mv_unified. + ENDIF. GET TIME STAMP FIELD lv_ts. mv_seed = |diff{ lv_ts }|. " Generate based on time @@ -292,6 +490,25 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. ENDMETHOD. + METHOD get_patch_data. + + CLEAR: ev_filename, ev_line_index. + + IF iv_action <> c_patch_action-add + AND iv_action <> c_patch_action-remove. + zcx_abapgit_exception=>raise( |Invalid action { iv_action }| ). + ENDIF. + + FIND FIRST OCCURRENCE OF REGEX iv_action && `_patch_(.*)_(\d+)` + IN iv_patch + SUBMATCHES ev_filename ev_line_index. + IF sy-subrc <> 0. + zcx_abapgit_exception=>raise( |Invalid patch| ). + ENDIF. + + ENDMETHOD. + + METHOD is_binary. DATA: lv_len TYPE i, @@ -399,8 +616,8 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. " Content IF is_diff-type <> 'binary'. ro_html->add( '
' ). "#EC NOTEXT - ro_html->add( '' ). "#EC NOTEXT - ro_html->add( render_table_head( ) ). + ro_html->add( |
| ). "#EC NOTEXT + ro_html->add( render_table_head( is_diff ) ). ro_html->add( render_lines( is_diff ) ). ro_html->add( '
' ). "#EC NOTEXT ELSE. @@ -457,7 +674,8 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. DATA: lo_highlighter TYPE REF TO zcl_abapgit_syntax_highlighter, lt_diffs TYPE zif_abapgit_definitions=>ty_diffs_tt, - lv_insert_nav TYPE abap_bool. + lv_insert_nav TYPE abap_bool, + lv_tabix TYPE syst-tabix. FIELD-SYMBOLS LIKE LINE OF lt_diffs. @@ -467,6 +685,9 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. lt_diffs = is_diff-o_diff->get( ). LOOP AT lt_diffs ASSIGNING . + + lv_tabix = sy-tabix. + IF -short = abap_false. lv_insert_nav = abap_true. CONTINUE. @@ -492,7 +713,9 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. ro_html->add( render_line_unified( is_diff_line = ) ). ELSE. ro_html->add( render_line_split( is_diff_line = - iv_fstate = is_diff-fstate ) ). + iv_filename = is_diff-filename + iv_fstate = is_diff-fstate + iv_index = lv_tabix ) ). ENDIF. ENDLOOP. @@ -506,10 +729,11 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. METHOD render_line_split. - DATA: lv_new TYPE string, - lv_old TYPE string, - lv_mark TYPE string, - lv_bg TYPE string. + DATA: lv_new TYPE string, + lv_old TYPE string, + lv_mark TYPE string, + lv_bg TYPE string, + lv_patch_line_possible TYPE abap_bool. CREATE OBJECT ro_html. @@ -525,6 +749,10 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. lv_new = || && |{ lv_mark }{ is_diff_line-new }|. + IF lv_mark <> ` `. + lv_patch_line_possible = abap_true. + ENDIF. + " Old line CLEAR lv_bg. lv_mark = ` `. @@ -538,8 +766,23 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. lv_old = || && |{ lv_mark }{ is_diff_line-old }|. + IF lv_mark <> ` `. + lv_patch_line_possible = abap_true. + ENDIF. + " render line, inverse sides if remote is newer ro_html->add( '' ). "#EC NOTEXT + + IF mv_patch_mode = abap_true. + + render_patch( io_html = ro_html + iv_patch_line_possible = lv_patch_line_possible + iv_filename = iv_filename + is_diff_line = is_diff_line + iv_index = iv_index ). + + ENDIF. + IF iv_fstate = c_fstate-remote. " Remote file leading changes ro_html->add( lv_old ). " local ro_html->add( lv_new ). " remote @@ -547,6 +790,7 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. ro_html->add( lv_new ). " local ro_html->add( lv_old ). " remote ENDIF. + ro_html->add( '' ). "#EC NOTEXT ENDMETHOD. @@ -599,6 +843,82 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. ENDMETHOD. + METHOD render_patch. + + CONSTANTS: + BEGIN OF c_css_class, + patch_active TYPE string VALUE `patch-active` ##NO_TEXT, + patch TYPE string VALUE `patch` ##NO_TEXT, + END OF c_css_class. + + DATA: lv_id TYPE string, + lv_left_class TYPE string, + lv_right_class TYPE string, + lv_object TYPE string. + + lv_object = iv_filename. + + IF iv_patch_line_possible = abap_true. + + lv_id = |patch_{ lv_object }_{ iv_index }|. + + io_html->add( || ). + + lv_left_class = |{ c_patch_action-add } |. + lv_right_class = |{ c_patch_action-remove } |. + + IF is_diff_line-patch_flag = abap_true. + + lv_left_class = lv_left_class && |{ c_css_class-patch_active }|. + + ELSE. + + lv_right_class = lv_right_class && |{ c_css_class-patch_active }|. + + ENDIF. + + io_html->add_a( iv_txt = |{ c_patch_action-add }| + iv_act = || + iv_id = |{ c_patch_action-add }_{ lv_id }| + iv_typ = zif_abapgit_definitions=>c_action_type-dummy + iv_class = lv_left_class ). + io_html->add_a( iv_txt = |{ c_patch_action-remove }| + iv_act = || + iv_id = |{ c_patch_action-remove }_{ lv_id }| + iv_typ = zif_abapgit_definitions=>c_action_type-dummy + iv_class = lv_right_class ). + + io_html->add( || ). + + ELSE. + + io_html->add( || ). + io_html->add( || ). + + ENDIF. + + ENDMETHOD. + + + METHOD render_patch_head. + + io_html->add( || ). + + io_html->add_a( iv_txt = |{ c_patch_action-add }| + iv_act = |patch_add_all('{ is_diff-filename }')| + iv_id = |patch_add_all| + iv_typ = zif_abapgit_definitions=>c_action_type-dummy ). + + io_html->add_a( iv_txt = |{ c_patch_action-remove }| + iv_act = |patch_remove_all('{ is_diff-filename }')| + iv_id = |patch_remove_all| + iv_typ = zif_abapgit_definitions=>c_action_type-dummy ). + + io_html->add( '' ). + + ENDMETHOD. + + METHOD render_table_head. CREATE OBJECT ro_html. @@ -611,10 +931,19 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. ro_html->add( 'new' ). "#EC NOTEXT ro_html->add( 'code' ). "#EC NOTEXT ELSE. + + IF mv_patch_mode = abap_true. + + render_patch_head( io_html = ro_html + is_diff = is_diff ). + + ENDIF. + ro_html->add( '' ). "#EC NOTEXT ro_html->add( 'LOCAL' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ro_html->add( 'REMOTE' ). "#EC NOTEXT + ENDIF. ro_html->add( '' ). "#EC NOTEXT @@ -635,22 +964,113 @@ CLASS zcl_abapgit_gui_page_diff IMPLEMENTATION. ro_html->add( ' }' ). ro_html->add( '});' ). + IF mv_patch_mode = abap_true. + ro_html->add( 'preparePatch();' ). + ro_html->add( 'registerStagePatch();' ). + ENDIF. + ENDMETHOD. - METHOD zif_abapgit_gui_page~on_event. + METHOD start_staging. - CASE iv_action. - WHEN c_actions-toggle_unified. " Toggle file diplay - mv_unified = zcl_abapgit_persistence_user=>get_instance( )->toggle_diff_unified( ). - ev_state = zif_abapgit_definitions=>c_event_state-re_render. - ENDCASE. + DATA: lv_string TYPE string, + lt_fields TYPE tihttpnvp, + lv_add TYPE string, + lv_remove TYPE string. + + CONCATENATE LINES OF it_postdata INTO lv_string. + lt_fields = zcl_abapgit_html_action_utils=>parse_fields( lv_string ). + + zcl_abapgit_html_action_utils=>get_field( EXPORTING iv_name = c_patch_action-add + it_field = lt_fields + CHANGING cg_field = lv_add ). + + zcl_abapgit_html_action_utils=>get_field( EXPORTING iv_name = c_patch_action-remove + it_field = lt_fields + CHANGING cg_field = lv_remove ). + + apply_patch_all( iv_action = c_patch_action-add + iv_patch = lv_add + iv_patch_flag = abap_true ). + + apply_patch_all( iv_action = c_patch_action-remove + iv_patch = lv_remove + iv_patch_flag = abap_false ). + + add_to_stage( ). ENDMETHOD. METHOD zif_abapgit_gui_page_hotkey~get_hotkey_actions. + DATA: ls_hotkey_action LIKE LINE OF rt_hotkey_actions. + + ls_hotkey_action-name = |Diff: Stage|. + ls_hotkey_action-action = |stagePatch|. + ls_hotkey_action-default_hotkey = |s|. + INSERT ls_hotkey_action INTO TABLE rt_hotkey_actions. + + ENDMETHOD. + + + METHOD zif_abapgit_gui_page~on_event. + + DATA: lo_repo TYPE REF TO zcl_abapgit_repo_online. + + CASE iv_action. + WHEN c_actions-toggle_unified. " Toggle file diplay + + mv_unified = zcl_abapgit_persistence_user=>get_instance( )->toggle_diff_unified( ). + ev_state = zif_abapgit_definitions=>c_event_state-re_render. + + WHEN c_actions-stage. + + start_staging( it_postdata ). + + lo_repo ?= zcl_abapgit_repo_srv=>get_instance( )->get( mv_repo_key ). + CREATE OBJECT ei_page TYPE zcl_abapgit_gui_page_commit + EXPORTING + io_repo = lo_repo + io_stage = mo_stage. + ev_state = zif_abapgit_definitions=>c_event_state-new_page. + + ENDCASE. + + ENDMETHOD. + + METHOD get_diff_object. + + FIELD-SYMBOLS: LIKE LINE OF mt_diff_files. + + READ TABLE mt_diff_files ASSIGNING + WITH KEY filename = iv_filename. + IF sy-subrc <> 0. + zcx_abapgit_exception=>raise( |Invalid filename { iv_filename }| ). + ENDIF. + + ro_diff = -o_diff. + + ENDMETHOD. + + + METHOD get_diff_line. + + DATA: lt_diff TYPE zif_abapgit_definitions=>ty_diffs_tt, + lv_line_index TYPE sytabix. + + FIELD-SYMBOLS: LIKE LINE OF lt_diff. + + lv_line_index = iv_line_index. + lt_diff = io_diff->get( ). + + READ TABLE lt_diff INTO rs_diff + INDEX lv_line_index. + IF sy-subrc <> 0. + zcx_abapgit_exception=>raise( |Invalid line index { lv_line_index }| ). + ENDIF. + ENDMETHOD. ENDCLASS. diff --git a/src/ui/zcl_abapgit_gui_page_diff.clas.testclasses.abap b/src/ui/zcl_abapgit_gui_page_diff.clas.testclasses.abap new file mode 100644 index 000000000..fccdfdee4 --- /dev/null +++ b/src/ui/zcl_abapgit_gui_page_diff.clas.testclasses.abap @@ -0,0 +1,142 @@ +*"* use this source file for your ABAP unit test classes + +CLASS ltcl_patch DEFINITION FINAL FOR TESTING + DURATION SHORT + RISK LEVEL HARMLESS. + + PRIVATE SECTION. + METHODS: + get_patch_data_add FOR TESTING RAISING cx_static_check, + get_patch_data_remove FOR TESTING RAISING cx_static_check, + invalid_action FOR TESTING RAISING cx_static_check, + invalid_patch_missing_file FOR TESTING RAISING cx_static_check, + invalid_patch_missing_index FOR TESTING RAISING cx_static_check. + +ENDCLASS. + +CLASS zcl_abapgit_gui_page_diff DEFINITION LOCAL FRIENDS ltcl_patch. + +CLASS ltcl_patch IMPLEMENTATION. + + METHOD get_patch_data_add. + + DATA: lv_file_name TYPE string, + lv_line_index TYPE string. + + zcl_abapgit_gui_page_diff=>get_patch_data( + EXPORTING + iv_patch = |add_patch_zcl_test_git_add_p.clas.abap_19| + iv_action = |add| + IMPORTING + ev_filename = lv_file_name + ev_line_index = lv_line_index ). + + cl_abap_unit_assert=>assert_equals( + exp = |zcl_test_git_add_p.clas.abap| + act = lv_file_name ). + + cl_abap_unit_assert=>assert_equals( + exp = |19| + act = lv_line_index ). + + ENDMETHOD. + + METHOD get_patch_data_remove. + + DATA: lv_file_name TYPE string, + lv_line_index TYPE string. + + zcl_abapgit_gui_page_diff=>get_patch_data( + EXPORTING + iv_patch = |remove_patch_ztest_patch.prog.abap_39| + iv_action = |remove| + IMPORTING + ev_filename = lv_file_name + ev_line_index = lv_line_index ). + + cl_abap_unit_assert=>assert_equals( + exp = |ztest_patch.prog.abap| + act = lv_file_name ). + + cl_abap_unit_assert=>assert_equals( + exp = |39| + act = lv_line_index ). + + ENDMETHOD. + + METHOD invalid_action. + + DATA: lv_file_name TYPE string, + lv_line_index TYPE string, + lx_error TYPE REF TO zcx_abapgit_exception. + + TRY. + zcl_abapgit_gui_page_diff=>get_patch_data( + EXPORTING + iv_patch = |remove_patch_ztest_patch.prog.abap_39| + iv_action = |mix| + IMPORTING + ev_filename = lv_file_name + ev_line_index = lv_line_index ). + + cl_abap_unit_assert=>fail( ). + + CATCH zcx_abapgit_exception INTO lx_error. + cl_abap_unit_assert=>assert_equals( + exp = |Invalid action mix| + act = lx_error->get_text( ) ). + ENDTRY. + + ENDMETHOD. + + METHOD invalid_patch_missing_file. + + DATA: lv_file_name TYPE string, + lv_line_index TYPE string, + lx_error TYPE REF TO zcx_abapgit_exception. + + TRY. + zcl_abapgit_gui_page_diff=>get_patch_data( + EXPORTING + iv_patch = |add_patch_39| + iv_action = |add| + IMPORTING + ev_filename = lv_file_name + ev_line_index = lv_line_index ). + + cl_abap_unit_assert=>fail( ). + + CATCH zcx_abapgit_exception INTO lx_error. + cl_abap_unit_assert=>assert_equals( + exp = |Invalid patch| + act = lx_error->get_text( ) ). + ENDTRY. + + ENDMETHOD. + + METHOD invalid_patch_missing_index. + + DATA: lv_file_name TYPE string, + lv_line_index TYPE string, + lx_error TYPE REF TO zcx_abapgit_exception. + + TRY. + zcl_abapgit_gui_page_diff=>get_patch_data( + EXPORTING + iv_patch = |remove_patch_ztest_patch.prog.abap| + iv_action = |remove| + IMPORTING + ev_filename = lv_file_name + ev_line_index = lv_line_index ). + + cl_abap_unit_assert=>fail( ). + + CATCH zcx_abapgit_exception INTO lx_error. + cl_abap_unit_assert=>assert_equals( + exp = |Invalid patch| + act = lx_error->get_text( ) ). + ENDTRY. + + ENDMETHOD. + +ENDCLASS. diff --git a/src/ui/zcl_abapgit_gui_page_diff.clas.xml b/src/ui/zcl_abapgit_gui_page_diff.clas.xml index 8b30d88f5..0daa57dfa 100644 --- a/src/ui/zcl_abapgit_gui_page_diff.clas.xml +++ b/src/ui/zcl_abapgit_gui_page_diff.clas.xml @@ -13,6 +13,7 @@ X X X + X diff --git a/src/ui/zcl_abapgit_gui_page_stage.clas.abap b/src/ui/zcl_abapgit_gui_page_stage.clas.abap index 76a8ee2c4..fc7166ae1 100644 --- a/src/ui/zcl_abapgit_gui_page_stage.clas.abap +++ b/src/ui/zcl_abapgit_gui_page_stage.clas.abap @@ -66,11 +66,16 @@ CLASS zcl_abapgit_gui_page_stage DEFINITION METHODS build_menu RETURNING VALUE(ro_menu) TYPE REF TO zcl_abapgit_html_toolbar . + METHODS get_page_patch + IMPORTING iv_getdata TYPE clike + iv_prev_page TYPE clike + RETURNING VALUE(ri_page) TYPE REF TO zif_abapgit_gui_page + RAISING zcx_abapgit_exception. ENDCLASS. -CLASS ZCL_ABAPGIT_GUI_PAGE_STAGE IMPLEMENTATION. +CLASS zcl_abapgit_gui_page_stage IMPLEMENTATION. METHOD build_menu. @@ -182,7 +187,9 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_STAGE IMPLEMENTATION. METHOD render_actions. DATA: lv_local_count TYPE i, - lv_add_all_txt TYPE string. + lv_add_all_txt TYPE string, + lv_param TYPE string, + ls_file TYPE zif_abapgit_definitions=>ty_file. CREATE OBJECT ro_html. lv_local_count = lines( ms_files-local ). @@ -204,6 +211,17 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_STAGE IMPLEMENTATION. ro_html->add_a( iv_act = |{ c_action-stage_all }| iv_id = 'commitAllButton' iv_txt = lv_add_all_txt ) ##NO_TEXT. + + lv_param = zcl_abapgit_html_action_utils=>file_encode( iv_key = mo_repo->get_key( ) + ig_file = ls_file ). + + + ro_html->add( '' ). + + ro_html->add( '' ). + ro_html->add_a( + iv_txt = |Patch| + iv_act = |{ zif_abapgit_definitions=>c_action-go_patch }?{ lv_param }| ). ro_html->add( '' ). " Filter bar @@ -212,7 +230,8 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_STAGE IMPLEMENTATION. ' type="search" placeholder="Filter objects">' ). ro_html->add( '' ). - ro_html->add( '' ). + ro_html->add( '' ). + ro_html->add( '' ). ENDMETHOD. "render_actions @@ -268,6 +287,7 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_STAGE IMPLEMENTATION. ro_html->add( |?| ). ro_html->add( '' ). " Command added in JS + ro_html->add( '' ). ENDMETHOD. "render_file @@ -367,6 +387,13 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_STAGE IMPLEMENTATION. METHOD zif_abapgit_gui_page_hotkey~get_hotkey_actions. + DATA: ls_hotkey_action TYPE zif_abapgit_gui_page_hotkey=>ty_hotkey_action. + + ls_hotkey_action-name = |Stage: Patch|. + ls_hotkey_action-action = zif_abapgit_definitions=>c_action-go_patch. + ls_hotkey_action-default_hotkey = |p|. + INSERT ls_hotkey_action INTO TABLE rt_hotkey_actions. + ENDMETHOD. @@ -383,23 +410,72 @@ CLASS ZCL_ABAPGIT_GUI_PAGE_STAGE IMPLEMENTATION. CASE iv_action. WHEN c_action-stage_all. + LOOP AT ms_files-local ASSIGNING . lo_stage->add( iv_path = -file-path iv_filename = -file-filename iv_data = -file-data ). ENDLOOP. + + CREATE OBJECT ei_page TYPE zcl_abapgit_gui_page_commit + EXPORTING + io_repo = mo_repo + io_stage = lo_stage. + ev_state = zif_abapgit_definitions=>c_event_state-new_page. + + ev_state = zif_abapgit_definitions=>c_event_state-new_page. + WHEN c_action-stage_commit. + process_stage_list( it_postdata = it_postdata io_stage = lo_stage ). + + CREATE OBJECT ei_page TYPE zcl_abapgit_gui_page_commit + EXPORTING + io_repo = mo_repo + io_stage = lo_stage. + ev_state = zif_abapgit_definitions=>c_event_state-new_page. + + WHEN zif_abapgit_definitions=>c_action-go_patch. " Go Patch page + + ei_page = get_page_patch( + iv_getdata = iv_getdata + iv_prev_page = iv_prev_page ). + ev_state = zif_abapgit_definitions=>c_event_state-new_page. + WHEN OTHERS. RETURN. ENDCASE. - CREATE OBJECT ei_page TYPE zcl_abapgit_gui_page_commit - EXPORTING - io_repo = mo_repo - io_stage = lo_stage. + ENDMETHOD. - ev_state = zif_abapgit_definitions=>c_event_state-new_page. + + METHOD get_page_patch. + + DATA: lo_page TYPE REF TO zcl_abapgit_gui_page_diff, + lv_key TYPE zif_abapgit_persistence=>ty_repo-key, + ls_file TYPE zif_abapgit_definitions=>ty_file, + ls_object TYPE zif_abapgit_definitions=>ty_item, + lo_stage TYPE REF TO zcl_abapgit_stage. + + zcl_abapgit_html_action_utils=>file_obj_decode( + EXPORTING + iv_string = iv_getdata + IMPORTING + ev_key = lv_key + eg_file = ls_file + eg_object = ls_object ). + + CREATE OBJECT lo_stage. + + CREATE OBJECT lo_page + EXPORTING + iv_key = lv_key + iv_patch_mode = abap_true + io_stage = lo_stage. + + ri_page = lo_page. ENDMETHOD. + + ENDCLASS. diff --git a/src/ui/zcl_abapgit_gui_router.clas.abap b/src/ui/zcl_abapgit_gui_router.clas.abap index db1559b0c..acd144794 100644 --- a/src/ui/zcl_abapgit_gui_router.clas.abap +++ b/src/ui/zcl_abapgit_gui_router.clas.abap @@ -44,7 +44,7 @@ ENDCLASS. -CLASS ZCL_ABAPGIT_GUI_ROUTER IMPLEMENTATION. +CLASS zcl_abapgit_gui_router IMPLEMENTATION. METHOD get_page_background. diff --git a/src/utils/zcl_abapgit_diff.clas.abap b/src/utils/zcl_abapgit_diff.clas.abap index 614fda38f..1ea8f92c5 100644 --- a/src/utils/zcl_abapgit_diff.clas.abap +++ b/src/utils/zcl_abapgit_diff.clas.abap @@ -16,6 +16,21 @@ CLASS zcl_abapgit_diff DEFINITION METHODS stats RETURNING VALUE(rs_count) TYPE zif_abapgit_definitions=>ty_count. + + METHODS set_patch_new + IMPORTING + iv_line_new TYPE i + iv_patch_flag TYPE abap_bool + RAISING + zcx_abapgit_exception. + + METHODS set_patch_old + IMPORTING + iv_line_old TYPE i + iv_patch_flag TYPE abap_bool + RAISING + zcx_abapgit_exception. + PRIVATE SECTION. DATA mt_diff TYPE zif_abapgit_definitions=>ty_diffs_tt. DATA ms_stats TYPE zif_abapgit_definitions=>ty_count. @@ -258,6 +273,54 @@ CLASS ZCL_ABAPGIT_DIFF IMPLEMENTATION. ENDMETHOD. + METHOD set_patch_new. + + DATA: lv_new_num TYPE i. + FIELD-SYMBOLS: TYPE zif_abapgit_definitions=>ty_diff. + + LOOP AT mt_diff ASSIGNING . + + lv_new_num = -new_num. + + IF lv_new_num = iv_line_new. + EXIT. + ENDIF. + + ENDLOOP. + + IF sy-subrc <> 0. + zcx_abapgit_exception=>raise( |Invalid new line number { iv_line_new }| ). + ENDIF. + + -patch_flag = iv_patch_flag. + + ENDMETHOD. + + + METHOD set_patch_old. + + DATA: lv_old_num TYPE i. + FIELD-SYMBOLS: TYPE zif_abapgit_definitions=>ty_diff. + + LOOP AT mt_diff ASSIGNING . + + lv_old_num = -old_num. + + IF lv_old_num = iv_line_old. + EXIT. + ENDIF. + + ENDLOOP. + + IF sy-subrc <> 0. + zcx_abapgit_exception=>raise( |Invalid old line number { iv_line_old }| ). + ENDIF. + + -patch_flag = iv_patch_flag. + + ENDMETHOD. + + METHOD shortlist. DATA: lv_index TYPE i. diff --git a/src/zabapgit_css_common.w3mi.data.css b/src/zabapgit_css_common.w3mi.data.css index 9f75545e2..f860c645f 100644 --- a/src/zabapgit_css_common.w3mi.data.css +++ b/src/zabapgit_css_common.w3mi.data.css @@ -503,6 +503,18 @@ table.diff_tab td.num, th.num { -ms-user-select: none; user-select: none; } +table.diff_tab td.patch, th.patch { + width: 1%; + min-width: 6em; + padding-right: 8px; + padding-left: 8px; + text-align: right !important; + color: #ccc; + border-left: 1px solid #eee; + border-right: 1px solid #eee; + -ms-user-select: none; + user-select: none; +} table.diff_tab td.num::before { content: attr(line-num); } @@ -882,3 +894,8 @@ div.news .update { border-style: solid; border-color: #555 transparent transparent transparent; } + +/* diff-patch */ +.patch-active { + color: lightgrey !important; +} diff --git a/src/zabapgit_js_common.w3mi.data.js b/src/zabapgit_js_common.w3mi.data.js index b012cde17..b67e83640 100644 --- a/src/zabapgit_js_common.w3mi.data.js +++ b/src/zabapgit_js_common.w3mi.data.js @@ -289,7 +289,7 @@ StageHelper.prototype.onTableClick = function (event) { } this.updateMenu(); -} +}; // Search object StageHelper.prototype.onSearch = function (e) { @@ -682,7 +682,7 @@ function enableArrowListNavigation() { document.addEventListener('keydown', oKeyNavigation.onkeydown.bind(oKeyNavigation)); -}; +} function LinkHints(sLinkHintKey, sColor){ this.sLinkHintKey = sLinkHintKey; @@ -723,13 +723,12 @@ LinkHints.prototype.fnRenderTooltips = function () { // we stick to numeric values and just increment them. var - iTooltipCounter = this.getTooltipStartValue(this.aTooltipElements.length), - that = this; + iTooltipCounter = this.getTooltipStartValue(this.aTooltipElements.length); [].forEach.call(this.aTooltipElements, function(oTooltip){ iTooltipCounter += 1; this.fnRenderTooltip(oTooltip, iTooltipCounter) - }.bind(that)); + }.bind(this)); }; @@ -754,8 +753,6 @@ LinkHints.prototype.fnRemoveAllTooltips = function () { LinkHints.prototype.fnFilterTooltips = function (sPending) { - var that = this; - Object .keys(this.oTooltipMap) .forEach(function (sKey) { @@ -772,7 +769,7 @@ LinkHints.prototype.fnFilterTooltips = function (sPending) { oTooltip.classList.add('hidden'); } - }.bind(that)); + }.bind(this)); }; @@ -844,26 +841,30 @@ function setLinkHints(sLinkHintKey, sColor) { function Hotkeys(oKeyMap){ - var that = this; this.oKeyMap = oKeyMap || {}; // these are the hotkeys provided by the backend Object.keys(this.oKeyMap).forEach(function(sKey){ - var action = that.oKeyMap[sKey]; + var action = this.oKeyMap[sKey]; // We replace the actions with callback functions to unify // the hotkey execution - that.oKeyMap[sKey] = function(oEvent) { + this.oKeyMap[sKey] = function(oEvent) { - // We have either a js function - if (that[action]) { - that[action].call(that); + // We have either a js function on this + if (this[action]) { + this[action].call(this); return; } + + // Or a global function + if (window[action]) { + window[action].call(this); + } // Or a SAP event - var sUiSapEvent = that.getSapEvent(action); + var sUiSapEvent = this.getSapEvent(action); if (sUiSapEvent) { submitSapeventForm({}, sUiSapEvent, "post"); oEvent.preventDefault(); @@ -872,7 +873,7 @@ function Hotkeys(oKeyMap){ } - }); + }.bind(this)); } @@ -936,3 +937,191 @@ function setKeyBindings(oKeyMap){ document.addEventListener('keydown', oHotkeys.onkeydown.bind(oHotkeys)); } + +/* + Patch / git add -p + */ + +function CSSPatchClassCombination(sClassLinkClicked, sClassCorrespondingLink){ + this.sClassLinkClicked = sClassLinkClicked; + this.sClassCorrespondingLink = sClassCorrespondingLink; +} + +function Patch() { + + this.CSS_CLASS = { + ADD: 'add', + REMOVE: 'remove', + PATCH: 'patch', + PATCH_ACTIVE: 'patch-active' + } + + this.ID = { + STAGE: 'stage', + PATCH_ADD_ALL: 'patch_add_all', + PATCH_REMOVE_ALL: 'patch_remove_all' + } + + this.ACTION = { + PATCH_STAGE: 'patch_stage' + } + + this.ADD_REMOVE = new CSSPatchClassCombination(this.CSS_CLASS.ADD, this.CSS_CLASS.REMOVE); + this.REMOVE_ADD = new CSSPatchClassCombination(this.CSS_CLASS.REMOVE, this.CSS_CLASS.ADD); + +} + +Patch.prototype.preparePatch = function(){ + + this.registerClickHandlerSingleLine(); + this.registerClickHandlerAllFile(); + +} + +Patch.prototype.registerClickHandlerSingleLine = function(){ + + // registers the link handlers for add and remove single lines + + this.registerClickHandlerForPatchLink(this.ADD_REMOVE); + this.registerClickHandlerForPatchLink(this.REMOVE_ADD); + +} + +Patch.prototype.registerClickHandlerAllFile = function(){ + + // registers the link handlers for add and remove all changes for a file + + this.registerClickHandlerForPatchLinkAll('#' + this.ID.PATCH_ADD_ALL, this.ADD_REMOVE); + this.registerClickHandlerForPatchLinkAll('#' + this.ID.PATCH_REMOVE_ALL, this.REMOVE_ADD); + +} + +Patch.prototype.registerClickHandlerForPatchLink = function(oClassCombination) { + // register onclick handler. When a link is clicked it is + // deactivated and its corresponding link gets active + // + // e.g. if you click on 'add' add is deactivated and 'remove' + // is activated. + + var elLinkAll = document.querySelectorAll('.' + this.CSS_CLASS.PATCH + ' a.' + oClassCombination.sClassLinkClicked); + + [].forEach.call(elLinkAll,function(elLink){ + + elLink.addEventListener('click',function(oEvent){ + this.togglePatchActiveForClassLink(oEvent, elLink, oClassCombination); + }.bind(this)); + + }.bind(this)); + +} + +Patch.prototype.togglePatchActive = function(oEvent, elClicked, elCorrespondingLink){ + + if (!elClicked.classList.contains(this.CSS_CLASS.PATCH_ACTIVE)){ + elClicked.classList.toggle(this.CSS_CLASS.PATCH_ACTIVE); + elCorrespondingLink.classList.toggle(this.CSS_CLASS.PATCH_ACTIVE); + } + + oEvent.preventDefault(); +} + + +Patch.prototype.togglePatchActiveForClassLink = function(oEvent, elClicked, oClassCombination) { + + var sCorrespondingLinkId = this.getCorrespodingLinkId(elClicked.id, oClassCombination); + var elCorrespondingLink = document.querySelector('#' + this.escapeDots(sCorrespondingLinkId)); + + this.togglePatchActive(oEvent, elClicked, elCorrespondingLink); +} + +Patch.prototype.getCorrespodingLinkId = function(sClickedLinkId, oClassCombination){ + + // e.g. + // + // add_patch_z_test_git_add_p.prog.abap_28 => remove_patch_z_test_git_add_p.prog.abap_28 + // + // and vice versa + + var oRegexPatchClassPrefix = new RegExp('^' + oClassCombination.sClassLinkClicked ); + return sClickedLinkId.replace(oRegexPatchClassPrefix, oClassCombination.sClassCorrespondingLink); + +} + +Patch.prototype.escapeDots = function(sFileName){ + return sFileName.replace(/\./g,'\\.'); +} + +Patch.prototype.patchLinkClickAll = function(oClassCombination) { + return function(oEvent) { + + var sTableId = oEvent.srcElement.parentElement.parentElement.parentElement.parentElement.id; + var elAddAll = document.querySelectorAll('#' + this.escapeDots(sTableId) + ' a.' + oClassCombination.sClassLinkClicked); + + [].forEach.call(elAddAll,function(elem){ + this.togglePatchActiveForClassLink(oEvent, elem, oClassCombination); + }.bind(this)); + + oEvent.preventDefault(); + + } +} + +Patch.prototype.registerClickHandlerForPatchLinkAll = function(sSelector, oClassCombination){ + + var elAll = document.querySelectorAll(sSelector); + + [].forEach.call(elAll, function(elem){ + elem.addEventListener('click', this.patchLinkClickAll(oClassCombination).bind(this)); + }.bind(this)); + +} + +Patch.prototype.registerStagePatch = function registerStagePatch(){ + + var elStage = document.querySelector('#' + this.ID.STAGE); + elStage.addEventListener('click', this.stagePatch.bind(this)); + + // for hotkeys + window.stagePatch = function(){ + this.stagePatch(); + }.bind(this); + +} + +Patch.prototype.stagePatch = function() { + + // Collect add and remove info and submit to backend + + var aAddPatch = this.collectActiveElementsForSelector('.' + this.CSS_CLASS.PATCH +' a.' + this.CSS_CLASS.ADD); + var aRemovePatch = this.collectActiveElementsForSelector('.' + this.CSS_CLASS.PATCH + ' a.' + this.CSS_CLASS.REMOVE); + + submitSapeventForm({'add': aAddPatch, 'remove': aRemovePatch}, this.ACTION.PATCH_STAGE, "post"); + +} + +Patch.prototype.collectActiveElementsForSelector = function(sSelector){ + + return [].slice.call(document.querySelectorAll(sSelector)) + .filter(function(elem){ + return elem.classList.contains(this.CSS_CLASS.PATCH_ACTIVE) + }.bind(this)) + .map(function(elem){ + return elem.id; + }); + +}; + + +function preparePatch(){ + + var oPatch = new Patch(); + oPatch.preparePatch(); + +} + +function registerStagePatch(){ + + var oPatch = new Patch(); + oPatch.registerStagePatch(); + +} diff --git a/src/zcl_abapgit_stage.clas.abap b/src/zcl_abapgit_stage.clas.abap index 859c2f0c2..3c11c8d55 100644 --- a/src/zcl_abapgit_stage.clas.abap +++ b/src/zcl_abapgit_stage.clas.abap @@ -84,14 +84,16 @@ ENDCLASS. -CLASS ZCL_ABAPGIT_STAGE IMPLEMENTATION. +CLASS zcl_abapgit_stage IMPLEMENTATION. METHOD add. + append( iv_path = iv_path iv_filename = iv_filename iv_method = c_method-add iv_data = iv_data ). + ENDMETHOD. "add diff --git a/src/zif_abapgit_definitions.intf.abap b/src/zif_abapgit_definitions.intf.abap index 4442e94c1..765f62975 100644 --- a/src/zif_abapgit_definitions.intf.abap +++ b/src/zif_abapgit_definitions.intf.abap @@ -263,13 +263,14 @@ INTERFACE zif_abapgit_definitions PUBLIC. END OF c_diff. TYPES: BEGIN OF ty_diff, - new_num TYPE c LENGTH 6, - new TYPE string, - result TYPE c LENGTH 1, - old_num TYPE c LENGTH 6, - old TYPE string, - short TYPE abap_bool, - beacon TYPE i, + patch_flag TYPE abap_bool, + new_num TYPE c LENGTH 6, + new TYPE string, + result TYPE c LENGTH 1, + old_num TYPE c LENGTH 6, + old TYPE string, + short TYPE abap_bool, + beacon TYPE i, END OF ty_diff. TYPES: ty_diffs_tt TYPE STANDARD TABLE OF ty_diff WITH DEFAULT KEY. @@ -347,6 +348,15 @@ INTERFACE zif_abapgit_definitions PUBLIC. TYPES: tty_dokil TYPE STANDARD TABLE OF dokil WITH NON-UNIQUE DEFAULT KEY. + TYPES: tty_lines TYPE STANDARD TABLE OF i + WITH NON-UNIQUE DEFAULT KEY, + BEGIN OF ty_patch, + filename TYPE string, + lines_new TYPE tty_lines, + lines_old TYPE tty_lines, + END OF ty_patch, + tty_patch TYPE HASHED TABLE OF ty_patch + WITH UNIQUE KEY filename. CONSTANTS: BEGIN OF c_type, @@ -456,6 +466,7 @@ INTERFACE zif_abapgit_definitions PUBLIC. go_debuginfo TYPE string VALUE 'go_debuginfo', go_settings TYPE string VALUE 'go_settings', go_tutorial TYPE string VALUE 'go_tutorial', + go_patch TYPE string VALUE 'go_patch', jump TYPE string VALUE 'jump', jump_pkg TYPE string VALUE 'jump_pkg',