Enhance diff class (#5044)

* Enhance diff class

- Refactor `zcl_abapgit_diff` to use `RS_CMP_COMPUTE_DELTA` instead of `SVRS_COMPUTE_DELTA_REPS` (needs less code in abapGit)
- Replace `abaptxt_255` with `rswsourcet` which removes 255 character per line limit. Now diffs on long lines are calculated properly.
- Add "ignore indent", "ignore comments", and "ignore case" options. These correspond to the options for the Split Screen Editor in SAP GUI and work the same way. The options are *not* exposed to the UI yet.

Same functionality, same performance.

* Lint

Co-authored-by: Lars Hvam <larshp@hotmail.com>
This commit is contained in:
Marc Bernard 2021-10-25 10:06:27 -04:00 committed by GitHub
parent 45806c5a95
commit c15b58400a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 237 additions and 149 deletions

View File

@ -1,37 +1,39 @@
CLASS zcl_abapgit_diff DEFINITION
PUBLIC
CREATE PUBLIC .
CREATE PUBLIC.
PUBLIC SECTION.
CONSTANTS co_starting_beacon TYPE i VALUE 1.
* assumes data is UTF8 based with newlines
* only works with lines up to 255 characters
METHODS constructor
IMPORTING
!iv_new TYPE xstring
!iv_old TYPE xstring .
!iv_new TYPE xstring
!iv_old TYPE xstring
!iv_ignore_indentation TYPE abap_bool DEFAULT abap_false
!iv_ignore_comments TYPE abap_bool DEFAULT abap_false
!iv_ignore_case TYPE abap_bool DEFAULT abap_false.
METHODS get
RETURNING
VALUE(rt_diff) TYPE zif_abapgit_definitions=>ty_diffs_tt .
VALUE(rt_diff) TYPE zif_abapgit_definitions=>ty_diffs_tt.
METHODS stats
RETURNING
VALUE(rs_count) TYPE zif_abapgit_definitions=>ty_count .
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 .
zcx_abapgit_exception.
METHODS set_patch_old
IMPORTING
!iv_line_old TYPE i
!iv_patch_flag TYPE abap_bool
RAISING
zcx_abapgit_exception .
zcx_abapgit_exception.
METHODS get_beacons
RETURNING
VALUE(rt_beacons) TYPE zif_abapgit_definitions=>ty_string_tt .
VALUE(rt_beacons) TYPE zif_abapgit_definitions=>ty_string_tt.
METHODS is_line_patched
IMPORTING
iv_index TYPE i
@ -47,38 +49,35 @@ CLASS zcl_abapgit_diff DEFINITION
PROTECTED SECTION.
PRIVATE SECTION.
TYPES ty_regexset_tt TYPE STANDARD TABLE OF REF TO cl_abap_regex WITH KEY table_line.
DATA mt_beacons TYPE zif_abapgit_definitions=>ty_string_tt .
DATA mt_diff TYPE zif_abapgit_definitions=>ty_diffs_tt .
DATA ms_stats TYPE zif_abapgit_definitions=>ty_count .
TYPES:
ty_regexset_tt TYPE STANDARD TABLE OF REF TO cl_abap_regex WITH KEY table_line.
CLASS-METHODS unpack
DATA mt_beacons TYPE zif_abapgit_definitions=>ty_string_tt.
DATA mt_diff TYPE zif_abapgit_definitions=>ty_diffs_tt.
DATA ms_stats TYPE zif_abapgit_definitions=>ty_count.
DATA mv_compare_mode TYPE c LENGTH 1.
DATA mv_ignore_case TYPE abap_bool.
METHODS unpack
IMPORTING
!iv_new TYPE xstring
!iv_old TYPE xstring
EXPORTING
!et_new TYPE abaptxt255_tab
!et_old TYPE abaptxt255_tab .
CLASS-METHODS render
IMPORTING
!it_new TYPE abaptxt255_tab
!it_old TYPE abaptxt255_tab
!it_delta TYPE vxabapt255_tab
RETURNING
VALUE(rt_diff) TYPE zif_abapgit_definitions=>ty_diffs_tt .
CLASS-METHODS compute
IMPORTING
!it_new TYPE abaptxt255_tab
!it_old TYPE abaptxt255_tab
RETURNING
VALUE(rt_delta) TYPE vxabapt255_tab .
METHODS calculate_line_num_and_stats .
METHODS map_beacons .
METHODS shortlist .
!et_new TYPE rswsourcet
!et_old TYPE rswsourcet.
METHODS map_beacons.
METHODS shortlist.
METHODS create_regex_set
RETURNING
VALUE(rt_regex_set) TYPE ty_regexset_tt.
METHODS compute_and_render
IMPORTING
!it_new TYPE rswsourcet
!it_old TYPE rswsourcet
RETURNING
VALUE(rt_diff) TYPE zif_abapgit_definitions=>ty_diffs_tt.
METHODS calculate_stats.
ENDCLASS.
@ -86,31 +85,12 @@ ENDCLASS.
CLASS zcl_abapgit_diff IMPLEMENTATION.
METHOD calculate_line_num_and_stats.
DATA: lv_new TYPE i VALUE 1,
lv_old TYPE i VALUE 1.
METHOD calculate_stats.
FIELD-SYMBOLS: <ls_diff> LIKE LINE OF mt_diff.
LOOP AT mt_diff ASSIGNING <ls_diff>.
<ls_diff>-new_num = lv_new.
<ls_diff>-old_num = lv_old.
CASE <ls_diff>-result. " Line nums
WHEN zif_abapgit_definitions=>c_diff-delete.
lv_old = lv_old + 1.
CLEAR <ls_diff>-new_num.
WHEN zif_abapgit_definitions=>c_diff-insert.
lv_new = lv_new + 1.
CLEAR <ls_diff>-old_num.
WHEN OTHERS.
lv_new = lv_new + 1.
lv_old = lv_old + 1.
ENDCASE.
CASE <ls_diff>-result. " Stats
CASE <ls_diff>-result.
WHEN zif_abapgit_definitions=>c_diff-insert.
ms_stats-insert = ms_stats-insert + 1.
WHEN zif_abapgit_definitions=>c_diff-delete.
@ -118,51 +98,103 @@ CLASS zcl_abapgit_diff IMPLEMENTATION.
WHEN zif_abapgit_definitions=>c_diff-update.
ms_stats-update = ms_stats-update + 1.
ENDCASE.
ENDLOOP.
ENDMETHOD.
METHOD compute.
METHOD compute_and_render.
DATA: lt_trdirtab_old TYPE TABLE OF trdir,
lt_trdirtab_new TYPE TABLE OF trdir,
lt_trdir_delta TYPE TABLE OF xtrdir.
DATA:
lv_i TYPE i,
ls_diff LIKE LINE OF rt_diff,
lt_delta TYPE STANDARD TABLE OF rsedcresul WITH DEFAULT KEY.
FIELD-SYMBOLS:
<ls_old> LIKE LINE OF it_old,
<ls_new> LIKE LINE OF it_new,
<ls_delta> LIKE LINE OF lt_delta.
CALL FUNCTION 'SVRS_COMPUTE_DELTA_REPS'
CALL FUNCTION 'RS_CMP_COMPUTE_DELTA'
EXPORTING
compare_mode = mv_compare_mode
ignore_case_differences = mv_ignore_case
TABLES
texttab_old = it_old
texttab_new = it_new
trdirtab_old = lt_trdirtab_old
trdirtab_new = lt_trdirtab_new
trdir_delta = lt_trdir_delta
text_delta = rt_delta.
text_tab1 = it_new
text_tab2 = it_old
text_tab_res = lt_delta
EXCEPTIONS
parameter_invalid = 1
difference_not_found = 2
OTHERS = 3.
IF sy-subrc = 0.
" Process delta
LOOP AT lt_delta ASSIGNING <ls_delta>.
CLEAR ls_diff.
IF <ls_delta>-line1 > 0.
ls_diff-old_num = lv_i = <ls_delta>-line1.
ls_diff-old = <ls_delta>-text1.
ENDIF.
IF <ls_delta>-line2 > 0.
ls_diff-new_num = lv_i = <ls_delta>-line2.
ls_diff-new = <ls_delta>-text2.
ENDIF.
IF <ls_delta>-flag1 = 'D'.
ls_diff-result = zif_abapgit_definitions=>c_diff-delete.
ELSEIF <ls_delta>-flag2 = 'I'.
ls_diff-result = zif_abapgit_definitions=>c_diff-insert.
ELSEIF <ls_delta>-flag1 = 'M' AND <ls_delta>-flag2 = 'M'.
ls_diff-result = zif_abapgit_definitions=>c_diff-update.
ELSEIF <ls_delta>-flag1 = '' AND <ls_delta>-flag2 = ''.
ls_diff-result = ''.
ELSE.
ASSERT 0 = 1. " unknown comparison result
ENDIF.
APPEND ls_diff TO rt_diff.
ENDLOOP.
ELSEIF sy-subrc = 2.
" Identical input
LOOP AT it_old ASSIGNING <ls_old>.
CLEAR ls_diff.
ls_diff-old_num = sy-tabix.
ls_diff-old = <ls_old>.
READ TABLE it_new ASSIGNING <ls_new> INDEX sy-tabix.
ASSERT sy-subrc = 0.
ls_diff-new_num = sy-tabix.
ls_diff-new = <ls_new>.
APPEND ls_diff TO rt_diff.
ENDLOOP.
ELSE.
ASSERT 0 = 1. " incorrect function call
ENDIF.
ENDMETHOD.
METHOD constructor.
DATA: lt_delta TYPE vxabapt255_tab,
lt_new TYPE abaptxt255_tab,
lt_old TYPE abaptxt255_tab.
DATA: lt_new TYPE rswsourcet,
lt_old TYPE rswsourcet.
mv_compare_mode = 1.
IF iv_ignore_indentation = abap_true.
mv_compare_mode = mv_compare_mode + 1.
ENDIF.
IF iv_ignore_comments = abap_true.
mv_compare_mode = mv_compare_mode + 2.
ENDIF.
mv_ignore_case = iv_ignore_case.
unpack( EXPORTING iv_new = iv_new
iv_old = iv_old
IMPORTING et_new = lt_new
et_old = lt_old ).
lt_delta = compute( it_new = lt_new
it_old = lt_old ).
mt_diff = compute_and_render( it_new = lt_new
it_old = lt_old ).
mt_diff = render( it_new = lt_new
it_old = lt_old
it_delta = lt_delta ).
calculate_line_num_and_stats( ).
calculate_stats( ).
map_beacons( ).
shortlist( ).
@ -272,75 +304,6 @@ CLASS zcl_abapgit_diff IMPLEMENTATION.
ENDMETHOD.
METHOD render.
DATA: lv_oindex TYPE i VALUE 1,
lv_nindex TYPE i VALUE 1,
ls_new LIKE LINE OF it_new,
ls_old LIKE LINE OF it_old,
ls_diff LIKE LINE OF rt_diff,
lt_delta LIKE it_delta,
ls_delta LIKE LINE OF it_delta.
lt_delta = it_delta.
DO.
READ TABLE lt_delta INTO ls_delta WITH KEY number = lv_oindex.
IF sy-subrc = 0.
DELETE lt_delta INDEX sy-tabix.
CASE ls_delta-vrsflag.
WHEN zif_abapgit_definitions=>c_diff-delete.
ls_diff-new = ''.
ls_diff-result = zif_abapgit_definitions=>c_diff-delete.
ls_diff-old = ls_delta-line.
lv_oindex = lv_oindex + 1.
WHEN zif_abapgit_definitions=>c_diff-insert.
ls_diff-new = ls_delta-line.
ls_diff-result = zif_abapgit_definitions=>c_diff-insert.
ls_diff-old = ''.
lv_nindex = lv_nindex + 1.
WHEN zif_abapgit_definitions=>c_diff-update.
CLEAR ls_new.
READ TABLE it_new INTO ls_new INDEX lv_nindex.
ASSERT sy-subrc = 0.
ls_diff-new = ls_new.
ls_diff-result = zif_abapgit_definitions=>c_diff-update.
ls_diff-old = ls_delta-line.
lv_nindex = lv_nindex + 1.
lv_oindex = lv_oindex + 1.
WHEN OTHERS.
ASSERT 0 = 1.
ENDCASE.
ELSE.
CLEAR ls_new.
READ TABLE it_new INTO ls_new INDEX lv_nindex. "#EC CI_SUBRC
lv_nindex = lv_nindex + 1.
CLEAR ls_old.
READ TABLE it_old INTO ls_old INDEX lv_oindex. "#EC CI_SUBRC
lv_oindex = lv_oindex + 1.
ls_diff-new = ls_new.
ls_diff-result = ''.
ls_diff-old = ls_old.
ENDIF.
APPEND ls_diff TO rt_diff.
CLEAR ls_diff.
IF lv_nindex > lines( it_new ) AND lv_oindex > lines( it_old ).
EXIT.
ENDIF.
ENDDO.
ENDMETHOD.
METHOD set_patch_by_old_diff.
FIELD-SYMBOLS: <ls_diff> TYPE zif_abapgit_definitions=>ty_diff.

View File

@ -18,7 +18,11 @@ CLASS ltcl_diff DEFINITION FOR TESTING
iv_old TYPE zif_abapgit_definitions=>ty_diff-old.
METHODS: setup.
METHODS: test.
METHODS: test
IMPORTING
!iv_ignore_indentation TYPE abap_bool DEFAULT abap_false
!iv_ignore_comments TYPE abap_bool DEFAULT abap_false
!iv_ignore_case TYPE abap_bool DEFAULT abap_false.
METHODS:
diff01 FOR TESTING,
@ -26,7 +30,10 @@ CLASS ltcl_diff DEFINITION FOR TESTING
diff03 FOR TESTING,
diff04 FOR TESTING,
diff05 FOR TESTING,
diff06 FOR TESTING.
diff06 FOR TESTING,
diff07 FOR TESTING,
diff08 FOR TESTING,
diff09 FOR TESTING.
ENDCLASS.
@ -85,8 +92,12 @@ CLASS ltcl_diff IMPLEMENTATION.
CREATE OBJECT lo_diff
EXPORTING
iv_new = lv_xnew
iv_old = lv_xold.
iv_new = lv_xnew
iv_old = lv_xold
iv_ignore_indentation = iv_ignore_indentation
iv_ignore_comments = iv_ignore_comments
iv_ignore_case = iv_ignore_case.
lt_diff = lo_diff->get( ).
@ -183,7 +194,7 @@ CLASS ltcl_diff IMPLEMENTATION.
METHOD diff06.
" mixed
add_new( iv_new = 'A' ).
add_new( iv_new = 'B' ).
add_new( iv_new = 'inserted' ).
@ -225,4 +236,118 @@ CLASS ltcl_diff IMPLEMENTATION.
ENDMETHOD.
METHOD diff07.
" ignore indentation
add_new( iv_new = 'A' ).
add_new( iv_new = ' B' ). " changed indent
add_new( iv_new = 'C' ).
add_new( iv_new = ' D' ). " changed indent
add_old( iv_old = 'A' ).
add_old( iv_old = 'B' ).
add_old( iv_old = 'C' ).
add_old( iv_old = 'D' ).
add_expected( iv_new_num = ' 1'
iv_new = 'A'
iv_result = ''
iv_old_num = ' 1'
iv_old = 'A' ).
add_expected( iv_new_num = ' 2'
iv_new = ' B'
iv_result = '' " no diff!
iv_old_num = ' 2'
iv_old = 'B' ).
add_expected( iv_new_num = ' 3'
iv_new = 'C'
iv_result = ''
iv_old_num = ' 3'
iv_old = 'C' ).
add_expected( iv_new_num = ' 4'
iv_new = ' D'
iv_result = '' " no diff!
iv_old_num = ' 4'
iv_old = 'D' ).
test( iv_ignore_indentation = abap_true ).
ENDMETHOD.
METHOD diff08.
" ignore comments
add_new( iv_new = 'A' ).
add_new( iv_new = '* X' ). " changed comment
add_new( iv_new = 'C' ).
add_new( iv_new = 'D " new' ). " changed comment
add_old( iv_old = 'A' ).
add_old( iv_old = '* B' ).
add_old( iv_old = 'C' ).
add_old( iv_old = 'D " old' ).
add_expected( iv_new_num = ' 1'
iv_new = 'A'
iv_result = ''
iv_old_num = ' 1'
iv_old = 'A' ).
add_expected( iv_new_num = ' 2'
iv_new = '* X'
iv_result = '' " no diff!
iv_old_num = ' 2'
iv_old = '* B' ).
add_expected( iv_new_num = ' 3'
iv_new = 'C'
iv_result = ''
iv_old_num = ' 3'
iv_old = 'C' ).
add_expected( iv_new_num = ' 4'
iv_new = 'D " new'
iv_result = '' " no diff!
iv_old_num = ' 4'
iv_old = 'D " old' ).
test( iv_ignore_comments = abap_true ).
ENDMETHOD.
METHOD diff09.
" ignore case
add_new( iv_new = 'A' ).
add_new( iv_new = 'b' ). " changed case
add_new( iv_new = 'c' ).
add_new( iv_new = 'D' ). " changed case
add_old( iv_old = 'A' ).
add_old( iv_old = 'B' ).
add_old( iv_old = 'c' ).
add_old( iv_old = 'd' ).
add_expected( iv_new_num = ' 1'
iv_new = 'A'
iv_result = ''
iv_old_num = ' 1'
iv_old = 'A' ).
add_expected( iv_new_num = ' 2'
iv_new = 'b'
iv_result = '' " no diff!
iv_old_num = ' 2'
iv_old = 'B' ).
add_expected( iv_new_num = ' 3'
iv_new = 'c'
iv_result = ''
iv_old_num = ' 3'
iv_old = 'c' ).
add_expected( iv_new_num = ' 4'
iv_new = 'D'
iv_result = '' " no diff!
iv_old_num = ' 4'
iv_old = 'd' ).
test( iv_ignore_case = abap_true ).
ENDMETHOD.
ENDCLASS.