CLASS zcl_abapgit_branch_overview DEFINITION PUBLIC FINAL CREATE PRIVATE GLOBAL FRIENDS zcl_abapgit_factory . PUBLIC SECTION. INTERFACES zif_abapgit_branch_overview . CONSTANTS c_deleted_branch_name_prefix TYPE string VALUE '__DELETED_BRANCH_' ##NO_TEXT. METHODS constructor IMPORTING io_repo TYPE REF TO zcl_abapgit_repo_online RAISING zcx_abapgit_exception . PROTECTED SECTION. PRIVATE SECTION. TYPES: tyt_commit_sha1_range TYPE RANGE OF zif_abapgit_definitions=>ty_sha1 . DATA mt_branches TYPE zif_abapgit_definitions=>ty_git_branch_list_tt . DATA mt_commits TYPE zif_abapgit_definitions=>ty_commit_tt . DATA mt_tags TYPE zif_abapgit_definitions=>ty_git_tag_list_tt . METHODS compress_internal IMPORTING !iv_name TYPE string CHANGING !ct_temp TYPE zif_abapgit_definitions=>ty_commit_tt !ct_commits TYPE zif_abapgit_definitions=>ty_commit_tt . CLASS-METHODS parse_commits IMPORTING !it_objects TYPE zif_abapgit_definitions=>ty_objects_tt RETURNING VALUE(rt_commits) TYPE zif_abapgit_definitions=>ty_commit_tt RAISING zcx_abapgit_exception . METHODS parse_annotated_tags IMPORTING !it_objects TYPE zif_abapgit_definitions=>ty_objects_tt RAISING zcx_abapgit_exception . METHODS determine_branch RAISING zcx_abapgit_exception . METHODS determine_merges RAISING zcx_abapgit_exception . METHODS fixes RAISING zcx_abapgit_exception . METHODS get_git_objects IMPORTING !io_repo TYPE REF TO zcl_abapgit_repo_online RETURNING VALUE(rt_objects) TYPE zif_abapgit_definitions=>ty_objects_tt RAISING zcx_abapgit_exception . METHODS determine_tags RAISING zcx_abapgit_exception . METHODS _sort_commits CHANGING !ct_commits TYPE zif_abapgit_definitions=>ty_commit_tt . METHODS _get_1st_child_commit IMPORTING !it_commit_sha1s TYPE tyt_commit_sha1_range EXPORTING !et_commit_sha1s TYPE tyt_commit_sha1_range !es_1st_commit TYPE zif_abapgit_definitions=>ty_commit CHANGING !ct_commits TYPE zif_abapgit_definitions=>ty_commit_tt . METHODS _reverse_sort_order CHANGING !ct_commits TYPE zif_abapgit_definitions=>ty_commit_tt . ENDCLASS. CLASS ZCL_ABAPGIT_BRANCH_OVERVIEW IMPLEMENTATION. METHOD compress_internal. FIELD-SYMBOLS: LIKE LINE OF ct_temp, LIKE LINE OF ct_commits, LIKE LINE OF ct_temp. IF lines( ct_temp ) >= 10. READ TABLE ct_temp ASSIGNING INDEX 1. ASSERT sy-subrc = 0. READ TABLE ct_temp ASSIGNING INDEX lines( ct_temp ). ASSERT sy-subrc = 0. APPEND INITIAL LINE TO ct_commits ASSIGNING . -sha1 = -sha1. -parent1 = -parent1. -time = -time. -message = |Compressed, { lines( ct_temp ) } commits|. -branch = iv_name. -compressed = abap_true. ELSE. APPEND LINES OF ct_temp TO ct_commits. ENDIF. CLEAR ct_temp. ENDMETHOD. METHOD constructor. DATA: lt_objects TYPE zif_abapgit_definitions=>ty_objects_tt. lt_objects = get_git_objects( io_repo ). mt_commits = parse_commits( lt_objects ). _sort_commits( CHANGING ct_commits = mt_commits ). parse_annotated_tags( lt_objects ). CLEAR lt_objects. determine_branch( ). determine_merges( ). determine_tags( ). fixes( ). ENDMETHOD. METHOD determine_branch. CONSTANTS: lc_head TYPE string VALUE 'HEAD'. TYPES: BEGIN OF ty_branch_with_time, time TYPE string, name TYPE string, sha1 TYPE zif_abapgit_definitions=>ty_sha1, END OF ty_branch_with_time. DATA: lt_branches_sorted_by_time TYPE SORTED TABLE OF ty_branch_with_time WITH NON-UNIQUE KEY time, ls_branches_with_time TYPE ty_branch_with_time. FIELD-SYMBOLS: LIKE LINE OF mt_branches, LIKE LINE OF lt_branches_sorted_by_time, LIKE LINE OF mt_branches, LIKE LINE OF mt_commits, LIKE LINE OF -create. * Exchange HEAD, and make sure the branch determination starts with the HEAD branch READ TABLE mt_branches ASSIGNING WITH KEY name = lc_head. ASSERT sy-subrc = 0. LOOP AT mt_branches ASSIGNING WHERE sha1 = -sha1 AND name <> lc_head. -name = -name. DELETE mt_branches INDEX sy-tabix. EXIT. ENDLOOP. * Sort Branches by Commit Time LOOP AT mt_branches ASSIGNING . READ TABLE mt_commits ASSIGNING WITH KEY sha1 = -sha1. IF sy-subrc = 0. ls_branches_with_time-name = -name+11. ls_branches_with_time-sha1 = -sha1. IF -is_head = abap_true. ls_branches_with_time-time = '0000000000'. "Force HEAD to be the first one ELSE. ls_branches_with_time-time = -time. ENDIF. INSERT ls_branches_with_time INTO TABLE lt_branches_sorted_by_time. CLEAR ls_branches_with_time. ENDIF. ENDLOOP. LOOP AT lt_branches_sorted_by_time ASSIGNING . READ TABLE mt_commits ASSIGNING WITH KEY sha1 = -sha1. ASSERT sy-subrc = 0. DO. IF -branch IS INITIAL. -branch = -name. ELSE. APPEND INITIAL LINE TO -create ASSIGNING . -name = -name. -parent = -branch. EXIT. ENDIF. IF -parent1 IS INITIAL. EXIT. ELSE. READ TABLE mt_commits ASSIGNING WITH KEY sha1 = -parent1. ASSERT sy-subrc = 0. ENDIF. ENDDO. ENDLOOP. ENDMETHOD. METHOD determine_merges. DATA: BEGIN OF ls_deleted_branch_info, created TYPE abap_bool, index TYPE string, name TYPE string, END OF ls_deleted_branch_info. FIELD-SYMBOLS: TYPE zif_abapgit_definitions=>ty_commit, TYPE zif_abapgit_definitions=>ty_commit, TYPE zif_abapgit_definitions=>ty_commit, TYPE zif_abapgit_definitions=>ty_create. * we need latest first here: latest -> initial _reverse_sort_order( CHANGING ct_commits = mt_commits ). LOOP AT mt_commits ASSIGNING WHERE NOT parent2 IS INITIAL. ASSERT NOT -branch IS INITIAL. READ TABLE mt_commits ASSIGNING WITH KEY sha1 = -parent2. IF sy-subrc = 0. -merge = -branch. * orphaned, branch has been deleted after merge ls_deleted_branch_info-created = abap_false. WHILE -branch IS INITIAL. IF ls_deleted_branch_info-created = abap_false. ls_deleted_branch_info-created = abap_true. ls_deleted_branch_info-index = ls_deleted_branch_info-index + 1. ls_deleted_branch_info-name = c_deleted_branch_name_prefix && ls_deleted_branch_info-index && '__'. CONDENSE ls_deleted_branch_info-name NO-GAPS. -merge = ls_deleted_branch_info-name. ENDIF. -branch = ls_deleted_branch_info-name. READ TABLE mt_commits ASSIGNING WITH KEY sha1 = -parent1. IF sy-subrc <> 0. EXIT. ELSE. ASSIGN TO . ENDIF. ENDWHILE. IF IS ASSIGNED. APPEND INITIAL LINE TO -create ASSIGNING . -name = ls_deleted_branch_info-name. -parent = -branch. ENDIF. ENDIF. ENDLOOP. " switch back to initial -> latest _reverse_sort_order( CHANGING ct_commits = mt_commits ). ENDMETHOD. METHOD determine_tags. DATA: lv_tag TYPE LINE OF zif_abapgit_definitions=>ty_commit-tags. FIELD-SYMBOLS: LIKE LINE OF mt_tags, LIKE LINE OF mt_commits. LOOP AT mt_tags ASSIGNING . IF -type = zif_abapgit_definitions=>c_git_branch_type-lightweight_tag. READ TABLE mt_commits WITH KEY sha1 = -sha1 ASSIGNING . "#EC CI_SUBRC ELSEIF -type = zif_abapgit_definitions=>c_git_branch_type-annotated_tag. READ TABLE mt_commits WITH KEY sha1 = -object ASSIGNING . ENDIF. CHECK sy-subrc = 0. lv_tag = zcl_abapgit_git_tag=>remove_tag_prefix( -name ). INSERT lv_tag INTO TABLE -tags. ENDLOOP. ENDMETHOD. METHOD fixes. FIELD-SYMBOLS: LIKE LINE OF mt_commits. LOOP AT mt_commits ASSIGNING WHERE NOT merge IS INITIAL. * commits from old branches IF -branch = -merge. CLEAR -merge. ENDIF. ENDLOOP. ENDMETHOD. METHOD get_git_objects. DATA: lo_branch_list TYPE REF TO zcl_abapgit_git_branch_list, li_progress TYPE REF TO zif_abapgit_progress, lt_branches_and_tags TYPE zif_abapgit_definitions=>ty_git_branch_list_tt, lt_tags TYPE zif_abapgit_definitions=>ty_git_branch_list_tt, ls_tag LIKE LINE OF mt_tags. FIELD-SYMBOLS: LIKE LINE OF lt_tags. li_progress = zcl_abapgit_progress=>get_instance( 1 ). li_progress->show( iv_current = 1 iv_text = |Get git objects { io_repo->get_name( ) }| ). * get objects directly from git, mo_repo only contains a shallow clone of only * the selected branch "TODO refactor lo_branch_list = zcl_abapgit_git_transport=>branches( io_repo->get_url( ) ). mt_branches = lo_branch_list->get_branches_only( ). INSERT LINES OF mt_branches INTO TABLE lt_branches_and_tags. lt_tags = lo_branch_list->get_tags_only( ). INSERT LINES OF lt_tags INTO TABLE lt_branches_and_tags. LOOP AT lt_tags ASSIGNING . IF -name CP '*^{}'. CONTINUE. ENDIF. MOVE-CORRESPONDING TO ls_tag. INSERT ls_tag INTO TABLE mt_tags. ENDLOOP. zcl_abapgit_git_transport=>upload_pack_by_branch( EXPORTING iv_url = io_repo->get_url( ) iv_branch_name = io_repo->get_branch_name( ) iv_deepen_level = 0 it_branches = lt_branches_and_tags IMPORTING et_objects = rt_objects ). DELETE rt_objects WHERE type = zif_abapgit_definitions=>c_type-blob. ENDMETHOD. METHOD parse_annotated_tags. DATA: ls_raw TYPE zcl_abapgit_git_pack=>ty_tag. FIELD-SYMBOLS: LIKE LINE OF it_objects, LIKE LINE OF mt_tags. LOOP AT it_objects ASSIGNING USING KEY type WHERE type = zif_abapgit_definitions=>c_type-tag. ls_raw = zcl_abapgit_git_pack=>decode_tag( -data ). READ TABLE mt_tags ASSIGNING WITH KEY sha1 = -sha1. ASSERT sy-subrc = 0. -name = zif_abapgit_definitions=>c_git_branch-tags_prefix && ls_raw-tag. -sha1 = -sha1. -object = ls_raw-object. -type = zif_abapgit_definitions=>c_git_branch_type-annotated_tag. -display_name = ls_raw-tag. -tagger_name = ls_raw-tagger_name. -tagger_email = ls_raw-tagger_email. -message = ls_raw-message. -body = ls_raw-body. ENDLOOP. ENDMETHOD. METHOD parse_commits. DATA: ls_commit LIKE LINE OF mt_commits, lt_body TYPE STANDARD TABLE OF string WITH DEFAULT KEY, ls_raw TYPE zcl_abapgit_git_pack=>ty_commit. FIELD-SYMBOLS: LIKE LINE OF it_objects, TYPE string. LOOP AT it_objects ASSIGNING USING KEY type WHERE type = zif_abapgit_definitions=>c_type-commit. ls_raw = zcl_abapgit_git_pack=>decode_commit( -data ). CLEAR ls_commit. ls_commit-sha1 = -sha1. ls_commit-parent1 = ls_raw-parent. ls_commit-parent2 = ls_raw-parent2. SPLIT ls_raw-body AT zif_abapgit_definitions=>c_newline INTO TABLE lt_body. READ TABLE lt_body WITH KEY table_line = ' -----END PGP SIGNATURE-----' TRANSPORTING NO FIELDS. IF sy-subrc = 0. DELETE lt_body TO sy-tabix. DELETE lt_body TO 2. ENDIF. READ TABLE lt_body INDEX 1 INTO ls_commit-message. "#EC CI_SUBRC " The second line is always empty. Therefore we omit it. LOOP AT lt_body ASSIGNING FROM 3. INSERT INTO TABLE ls_commit-body. ENDLOOP. zcl_abapgit_utils=>extract_author_data( EXPORTING iv_author = ls_raw-author IMPORTING ev_author = ls_commit-author ev_email = ls_commit-email ev_time = ls_commit-time ). APPEND ls_commit TO rt_commits. ENDLOOP. ENDMETHOD. METHOD zif_abapgit_branch_overview~compress. DATA: lv_previous TYPE i, lv_index TYPE i, lv_name TYPE string, lt_temp LIKE it_commits. FIELD-SYMBOLS: LIKE LINE OF mt_branches, LIKE LINE OF it_commits. LOOP AT mt_branches ASSIGNING . CLEAR lt_temp. lv_name = -name+11. LOOP AT it_commits ASSIGNING WHERE branch = lv_name. lv_index = sy-tabix. IF NOT -merge IS INITIAL OR NOT -create IS INITIAL. * always show these vertices lv_previous = -1. ENDIF. IF lv_previous + 1 <> sy-tabix. compress_internal( EXPORTING iv_name = lv_name CHANGING ct_temp = lt_temp ct_commits = rt_commits ). ENDIF. lv_previous = lv_index. APPEND TO lt_temp. ENDLOOP. compress_internal( EXPORTING iv_name = lv_name CHANGING ct_temp = lt_temp ct_commits = rt_commits ). ENDLOOP. _sort_commits( CHANGING ct_commits = rt_commits ). ENDMETHOD. METHOD zif_abapgit_branch_overview~get_branches. rt_branches = mt_branches. ENDMETHOD. METHOD zif_abapgit_branch_overview~get_commits. rt_commits = mt_commits. ENDMETHOD. METHOD zif_abapgit_branch_overview~get_tags. rt_tags = mt_tags. ENDMETHOD. METHOD _get_1st_child_commit. DATA: lt_1stchild_commits TYPE zif_abapgit_definitions=>ty_commit_tt, ls_parent LIKE LINE OF it_commit_sha1s, lt_commit_sha1s LIKE it_commit_sha1s. FIELD-SYMBOLS: TYPE zif_abapgit_definitions=>ty_commit. CLEAR: es_1st_commit. * get all reachable next commits lt_commit_sha1s = it_commit_sha1s. LOOP AT ct_commits ASSIGNING WHERE parent1 IN lt_commit_sha1s OR parent2 IN lt_commit_sha1s. INSERT INTO TABLE lt_1stchild_commits. ENDLOOP. * return oldest one SORT lt_1stchild_commits BY time ASCENDING. READ TABLE lt_1stchild_commits INTO es_1st_commit INDEX 1. * remove from available commits DELETE ct_commits WHERE sha1 = es_1st_commit-sha1. * set relevant parent commit sha1s IF lines( lt_1stchild_commits ) = 1. CLEAR et_commit_sha1s. ELSE. et_commit_sha1s = it_commit_sha1s. ENDIF. ls_parent-sign = 'I'. ls_parent-option = 'EQ'. ls_parent-low = es_1st_commit-sha1. INSERT ls_parent INTO TABLE et_commit_sha1s. ENDMETHOD. METHOD _reverse_sort_order. DATA: lt_commits TYPE zif_abapgit_definitions=>ty_commit_tt. FIELD-SYMBOLS: TYPE zif_abapgit_definitions=>ty_commit. LOOP AT ct_commits ASSIGNING . INSERT INTO lt_commits INDEX 1. ENDLOOP. ct_commits = lt_commits. FREE lt_commits. ENDMETHOD. METHOD _sort_commits. DATA: lt_sorted_commits TYPE zif_abapgit_definitions=>ty_commit_tt, ls_next_commit TYPE zif_abapgit_definitions=>ty_commit, lt_parents TYPE tyt_commit_sha1_range, ls_parent LIKE LINE OF lt_parents. FIELD-SYMBOLS: TYPE zif_abapgit_definitions=>ty_commit. * find initial commit READ TABLE ct_commits ASSIGNING WITH KEY parent1 = space. IF sy-subrc = 0. ls_parent-sign = 'I'. ls_parent-option = 'EQ'. ls_parent-low = -sha1. INSERT ls_parent INTO TABLE lt_parents. * first commit INSERT INTO TABLE lt_sorted_commits. * remove from available commits DELETE ct_commits WHERE sha1 = -sha1. DO. _get_1st_child_commit( EXPORTING it_commit_sha1s = lt_parents IMPORTING et_commit_sha1s = lt_parents es_1st_commit = ls_next_commit CHANGING ct_commits = ct_commits ). IF ls_next_commit IS INITIAL. EXIT. "DO ENDIF. INSERT ls_next_commit INTO TABLE lt_sorted_commits. ENDDO. ENDIF. ct_commits = lt_sorted_commits. ENDMETHOD. ENDCLASS.