If you already looked into Git-enabled CTS (gCTS), you might know that new commits are created in the standard as soon as you release a transport request.
But what if this is not possible or not helpful in your case? It might be that many developers work on one transport request – each one stores changed objects in a task that is part of that transport request. But each developer might want to commit and test his changes when he feels it is a good point in time for doing so. He might not want to wait for all the others to finish their tasks so that the whole transport request can be released.
So how about committing on a task level to enable individual progress and testing for each developer?
It is possible. The solution is to implement the ‘CHECK_BEFORE_RELEASE’-method of BAdI CTS_REQUEST_CHECK.
In the following description on how to do this, you will find some sample coding that you can use but that we provide without any further warranty or official SAP support.
In the example given below, we will use the userID (so ‘SY-UNAME’) and the package name as conditions to decide whether a commit shall be created on task level or on request level. Other criteria are for sure possible. You could e.g. use just the package name(s) – without any dependency on the user – or even check whether a certain object is already part of a cloned repository, or you can completely leave out these conditions if you want to always use the commit option on task level.
In any case, make sure that the condition is met for every user, or for every transport task, or for whatever criterion that you choose – but nothing more, nothing less. So, it might happen that you have to adapt the condition from time to time in case new users join this working mode or additional packages shall make use of gCTS.
- Open transaction SE18 in your ABAP development system and search for BAdI ‘CTS_REQUEST_CHECK’.
- Choose Display, and switch to the tab Interfaces to view the details:
The method that we need to implement is ‘CHECK_BEFORE_RELEASE’. Other methods of the standard interface are not relevant in this case. If you already implemented this method or other ones of this BAdI, make sure that implementations do not contradict or overwrite each other.
- Choose Implementation -> Create.
- Enter an Implementation Name – e.g. Z_GCTS_REQUEST_CHECK.
Click on Continue (Enter) – the green check mark.
- On the next screen, enter an Implementation Short Text.
If you like, you can also change the Name of Implementing Class. In the example given in here, I did that:
Leave ABAP Code as Implementation Type.
Save your changes.
In the dialog popping up, you can add the implementation to a package. As I need the implementation only on my development system and don’t want to transport it, I chose to store it as local object.
- Double click on name of the implementing class:
- The class builder opens up.
Go to Local Definitions/Implementations. Click on Source Code Based.
- Copy the coding that you can find at the end of this blog into the editor.
Please note: The way the object list is used in the method get_objects_to_push with the implementation provided in the sample coding below only works for GitHub. If you use another collaborative version control software based on Git, please check the APIs provided by your vendor to find out how this method could be implemented. Adapt the coding to your needs. If you use it ‘just like it is’, your implementation will not work.
You can find some comments in the coding that will hopefully help you understand it and that explain what is required to be done if you want to commit on task level. The most important thing to do is that you adapt the basic conditions, when the commit on task level shall be executed:
- The line if sy-uname = ‘MY_USER (in the method if_ex_cts_request_check~check_before_release) restricts the option to release on task level to the user-ID MY_USER. Adapt this line to your needs. You can e.g. replace ‘MY_USER’ with the userID(s) that should commit on task level, or use another condition that fits to your needs, or remove the condition completely if you would like to enable commits on task level in any case (if so, remove the corresponding ‘endif’ as well).
- The line constants co_superpackage type string value ‘MY_TADIR_DEVC_PACKAGE‘ in the public section of the class definition restricts releasing on task level to the package MY_TADIR_DEVC_PACKAGE. Replace that by your package name or change the respective coding according to your needs.
- Check, save, and activate your coding.
- If everything is ok, click Back twice until you have reached the initial screen of your BAdI.
- In there, you should still see the Runtime Behavior ‘Implementation will not be called’. Click Activate business add-in Implementation as soon as you want to make use of the functionality.
You are done – try releasing a task where your conditions are met and check that a commit gets created.
Tips & Tricks
- When you release a task, a commit will now automatically be created. The commit message will contain a transport request number, name, and /or ID. You have most probably never seen this transport request number before. It is the number of a transport of copies that has been automatically created in the background to allow exporting the task. You can change the commit message to something more meaningful by maintaining the parameters CLIENT_VCS_COMMIT_DESCRIPTION and CLIENT_VCS_COMMIT_MESSAGE for your repository. For more details, please take a look at the SAP Help Portal at Configuration Parameters for Repositories
- If your implementation seems not to work as e.g. there is no commit visible in the commits list after you have released a task that fulfills your conditions, check the Log tab for your repository in the gCTS Fiori app.
- Commits and pushes are executed with the help of a job in transaction SM37. In case of issues, it might also be worth checking whether the job is there and (has been) executed: Choose the event SAP_TRIGGER_VCS_IMPORT and the ABAP Program Name SCTS_ABAP_VCS_IMPORT_OBSERVER and click Execute.You should get a list showing when the job was executed. If this is not the case, check your gCTS configuration. More details are provided on the SAP Help Portal under Enable Git-Enabled Change and Transport System in an ABAP System.
Please make sure that you copy the complete coding – the lines are longer than shown directly.
class zcl_im_cl_cts_git_forward definition public final create public . public section. types ty_tadir type standard table of tadir with default key. types ty_objects_to_push type table of if_cts_abap_vcs_transport_req=>ty_objects with default key. interfaces if_ex_cts_request_check . constants co_superpackage type string value 'MY_TADIR_DEVC_PACKAGE' ##NO_TEXT. protected section. private section. methods is_my_package_package importing !iv_package type devclass returning value(rv_found) type boolean . methods get_objects_to_push importing !iv_repository type if_cts_abap_vcs_repository=>ty_repository_json !it_object_list type ty_tadir returning value(rt_objects_to_push) type ty_objects_to_push . endclass. class zcl_im_cl_cts_git_forward implementation. method if_ex_cts_request_check~check_before_add_objects. " Not needed endmethod. method if_ex_cts_request_check~check_before_changing_owner. " Not needed endmethod. method if_ex_cts_request_check~check_before_creation. " Not needed endmethod. method if_ex_cts_request_check~check_before_release. data: ls_tadir type tadir, ls_e071 type e071, lt_e071 type table of e071 with default key, lt_object_list type ty_tadir, ls_address type bapiaddr3, lv_objname type sobj_name, lv_check type flag. " Execute just for a specific user (optional) if sy-uname = 'MY_USER'. " Execute just for certain TR type (e.g. task) " R - repair, S - development/correction, Q -customizing task if type = 'R' or type = 'S' or type = 'Q'. " loop over provided objects from the BAdI signature parameter loop at objects into data(ls_object). data(lv_pgmid) = ls_object-pgmid. data(lv_obj_type) = ls_object-object. lv_objname = ls_object-obj_name. " Find the super object for part objects which have not pgmid = R3TR " This is needed if that object will be committed for the very first time " Otherwise the repository will just contain, e.g. a single method, which cannot be deployed to a target system if ls_object-pgmid = 'LIMU'. call function 'TR_CHECK_TYPE' exporting wi_e071 = ls_object importing we_tadir = ls_tadir exceptions others = 1. " There was a TADIR entry for that object if sy-subrc = 0. lv_pgmid = ls_tadir-pgmid. lv_obj_type = ls_tadir-object. lv_objname = ls_tadir-obj_name. clear ls_tadir. else. continue. endif. endif. " Also keep sure that this is a 'real' object and not a generated one call function 'DEV_CHECK_OBJECT_EXISTS' exporting i_pgmid = lv_pgmid i_objtype = lv_obj_type i_objname = lv_objname importing e_exists = lv_check. if sy-subrc = 0. if lv_check = abap_true. " Double check that the related object could be transported by SE01/SE09 call function 'TR_TADIR_INTERFACE' exporting wi_tadir_pgmid = lv_pgmid wi_tadir_object = lv_obj_type wi_tadir_obj_name = lv_objname wi_read_only = 'X' importing new_tadir_entry = ls_tadir exceptions others = 1. " Evaluate that the current object is related to my DEVC package (optional) " Why do we need that? " It could be the case that we have a lot of different TR which will be transported trought our STMS landscape " In the case that gCTS will be used in parallel with common CTS we have to distinguish between the various object package relation " because of the fact that we probably just want to push objects which are related to our repository " For S/4HANA 2020: There is method which can be used in order to determine repository relation: " CL_CTS_ABAP_VCS_ORGANIZER_FAC=>check_objects if is_my_package_package( ls_tadir-devclass ) = abap_true and ls_tadir-genflag = abap_false. if not line_exists( lt_object_list[ obj_name = ls_tadir-obj_name ] ). append ls_tadir to lt_object_list. clear ls_tadir. endif. endif. else. continue. endif. else. " Handle Exception Here raise cancel. endif. endloop. " Get all exisiting repositories for current system data(lt_repositories) = cl_cts_abap_vcs_api_facade=>get_repositories( value #( ) ). " Was everything okay? if lt_repositories-exception is initial. loop at lt_repositories-result into data(ls_repository). " Compare provided object list with objects list of repository ... data(lt_objects_to_push) = get_objects_to_push( iv_repository = ls_repository it_object_list = lt_object_list ). if lt_objects_to_push is not initial. " Consume simplified facade method in order to commit&push objects to remote repository data(lv_response) = cl_cts_abap_vcs_trans_facade=>push_objects( value #( " Define Commit message desc = text " Define repository id for related objects repository = ls_repository-rid " Define repository related objects objects = lt_objects_to_push ) ). endif. endloop. endif. endif. endif. endmethod. method if_ex_cts_request_check~check_before_release_slin. endmethod. method is_my_package_package. " Is this our wanted package? if iv_package = co_superpackage. rv_found = abap_true. else. " Load package method information call method cl_package=>load_package exporting i_package_name = iv_package importing e_package = data(lo_package) exceptions others = 1. if lo_package is not initial. " is there a superpackage? " If not the process is over and this isn't our wanted package if lo_package->super_package_name is initial. rv_found = abap_false. elseif lo_package->super_package_name = co_superpackage . rv_found = abap_true. else. " Recursive call if there is a superpackage which also have to be evaluated rv_found = is_my_package_package( lo_package->super_package_name ). endif. else. "this isn't our wanted package rv_found = abap_false. endif. endif. endmethod. method get_objects_to_push. data: ls_single_object type if_cts_abap_vcs_repository=>ty_object. " Get all available objects for repository (Warning: this will just work with GitHub API repositories!) data(ls_objects_response) = cl_cts_abap_vcs_repo_facade=>get_objects( iv_repository-rid ). if ls_objects_response-objects is not initial and ls_objects_response-exception is initial. loop at it_object_list into data(ls_object). if line_exists( ls_objects_response-objects[ object = ls_object-devclass type = 'DEVC' ] ) or line_exists( ls_objects_response-objects[ object = co_superpackage type = 'DEVC' ] ). if line_exists( ls_objects_response-objects[ object = ls_object-obj_name type = ls_object-object ] ). ls_single_object = ls_objects_response-objects[ object = ls_object-obj_name type = ls_object-object ]. append value #( object = ls_single_object-object type = ls_single_object-type ) to rt_objects_to_push. else. append value #( object = ls_object-obj_name type = ls_object-object ) to rt_objects_to_push. endif. elseif line_exists( ls_objects_response-objects[ object = ls_object-obj_name type = ls_object-object ] ). ls_single_object = ls_objects_response-objects[ object = ls_object-obj_name type = ls_object-object ]. append value #( object = ls_single_object-object type = ls_single_object-type ) to rt_objects_to_push. endif. clear ls_single_object. endloop. endif. endmethod. endclass.
Hope that you find this useful. If you find issues or have questions or suggestions, feel free to post them as comments to this blog post.