Integrating GitLab with ABAP for File Management
GitLab provides a convenient API to interact with Git repositories without needing Git tools installed locally. This is particularly useful when accessing a Git repository directly from ABAP code.
For our Weblate integration, we required the ability to transfer translation files to and from GitLab. To achieve this, we implemented several GitLab API endpoints within an ABAP class.
⚙️ Prerequisites
Before interacting with the GitLab API through HTTP, you may need to configure the GitLab server using transaction SM59
. This allows you to avoid hardcoding server details in your ABAP code. We opted to work with multiple servers by using hostnames directly, without relying on SM59
.
To access the GitLab API anonymously, you must set up a Personal Access Token for use in the HTTP calls from ABAP.
🧱 ABAP Class Structure
We created an ABAP class to manage all interactions with the GitLab API. The class contains the following global fields:
1
2
3
4
5
6
CONSTANTS: gc_api_prefix TYPE string VALUE '/api/v4/'.
DATA: gv_host_name TYPE string,
gv_repository_name TYPE string,
gv_branch_name TYPE string,
gv_auth_token TYPE string.
These fields get initialized by the constructor call. For example:
1
2
3
4
DATA(lo_gitlab) = NEW zcl_gitlab( iv_host_name = <GITLAB_HOST>
iv_repository_name = <GITLAB_REPO>
iv_branch_name = 'master'
iv_auth_token = <GITLAB_AUTH_TOKEN> ).
📂 Listing Files in a Repository
To list all files in a repository, we use the List repository tree endpoint. Here’s an example method that calls this endpoint:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
METHOD get_file_listing.
TYPES: BEGIN OF t_file,
path TYPE string,
name TYPE string,
type TYPE string,
END OF t_file.
CONSTANTS: lc_service_path TYPE string VALUE 'projects/{id}/repository/tree?ref={branch}'.
DATA: lt_files TYPE TABLE OF t_file.
" prepare the service path for the given project
DATA(lv_service_path) = |{ gc_api_prefix }{ lc_service_path }|.
REPLACE ALL OCCURRENCES OF '{id}'
IN lv_service_path
WITH escape( val = gv_repository_name format = cl_abap_format=>e_url_full ).
REPLACE ALL OCCURRENCES OF '{branch}'
IN lv_service_path
WITH escape( val = gv_branch_name format = cl_abap_format=>e_url_full ).
IF iv_subfolder IS NOT INITIAL.
lv_service_path = |{ lv_service_path }&path={ escape( val = iv_subfolder format = cl_abap_format=>e_url_full ) }|.
ENDIF.
" create the http client
cl_http_client=>create( EXPORTING host = gv_host_name scheme = cl_http_client=>schemetype_https
IMPORTING client = lo_http_client
EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0. RETURN. ENDIF.
cl_http_utility=>set_request_uri( request = lo_http_client->request uri = lv_service_path ).
IF gv_auth_token IS NOT INITIAL.
lo_http_client->request->set_header_field( name = 'PRIVATE-TOKEN' value = gv_auth_token ).
ENDIF.
lo_http_client->send( EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0.
" get the error message
lo_http_client->get_last_error( IMPORTING message = DATA(lv_send_message) ).
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = lv_send_message ).
ENDIF.
lo_http_client->receive( EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0.
" get the error message
lo_http_client->get_last_error( IMPORTING message = DATA(lv_receive_message) ).
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = lv_receive_message ).
ENDIF.
DATA(lv_response) = lo_http_client->response->get_cdata( ).
lo_http_client->close( ).
" if the result contains "message": * there is a problem
IF lv_response CS '"message"'.
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = lv_response ).
ENDIF.
" now unmarshall the JSON response
/ui2/cl_json=>deserialize( EXPORTING json = lv_response
CHANGING data = lt_files ).
LOOP AT lt_files ASSIGNING FIELD-SYMBOL(<file>) WHERE type NE 'tree'.
APPEND INITIAL LINE TO rt_files
ASSIGNING FIELD-SYMBOL(<return>).
<return>-path_and_name = <file>-path.
<return>-file_name = <file>-name.
ENDLOOP.
ENDMETHOD.
📥 Receiving a File from GitLab
To retrieve a file from GitLab, we use the Get file from repository endpoint. Here’s the method for this process:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
METHOD receive_file.
TYPES: BEGIN OF t_file,
file_name TYPE string,
file_path TYPE string,
size TYPE i,
encoding TYPE string,
content TYPE string,
END OF t_file.
CONSTANTS: lc_service_path TYPE string VALUE 'projects/{id}/repository/files/{filename}?ref={branch}'.
DATA: ls_file TYPE t_file.
" prepare the service path for the given project
DATA(lv_service_path) = |{ gc_api_prefix }{ lc_service_path }|.
REPLACE ALL OCCURRENCES OF '{id}'
IN lv_service_path
WITH escape( val = gv_repository_name format = cl_abap_format=>e_url_full ).
REPLACE ALL OCCURRENCES OF '{filename}'
IN lv_service_path
WITH escape( val = iv_filename format = cl_abap_format=>e_url_full ).
REPLACE ALL OCCURRENCES OF '{branch}'
IN lv_service_path
WITH escape( val = gv_branch_name format = cl_abap_format=>e_url_full ).
" create the http client
cl_http_client=>create( EXPORTING host = gv_host_name scheme = cl_http_client=>schemetype_https
IMPORTING client = DATA(lo_http_client)
EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0. RETURN. ENDIF.
cl_http_utility=>set_request_uri( request = lo_http_client->request uri= lv_service_path ).
IF gv_auth_token IS NOT INITIAL.
lo_http_client->request->set_header_field( name = 'PRIVATE-TOKEN' value = gv_auth_token ).
ENDIF.
lo_http_client->send( EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0.
" get the error message
lo_http_client->get_last_error( IMPORTING message = DATA(lv_send_message) ).
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = lv_send_message ).
ENDIF.
lo_http_client->receive( EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0.
" get the error message
lo_http_client->get_last_error( IMPORTING message = DATA(lv_receive_message) ).
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = lv_receive_message ).
ENDIF.
DATA(lv_response) = lo_http_client->response->get_cdata( ).
lo_http_client->close( ).
" if the result contains "message": * there is a problem
IF lv_response CS '"message"'.
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = lv_response ).
ENDIF.
" now unmarshall the JSON response
/ui2/cl_json=>deserialize( EXPORTING json = lv_response
CHANGING data = ls_file ).
" only accept base64 encoded files
IF ls_file-encoding NE 'base64'.
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = 'no BASE64 encoded response' ).
ENDIF.
" decode the base64 encoded file
rv_file_content = cl_http_utility=>decode_x_base64( ls_file-content ).
ENDMETHOD.
📤 Sending Files to GitLab
Sending files is a bit more complex, as GitLab offers two separate endpoints: Create new file in repository and Update existing file in repository. Since there is no combined “create or update” endpoint, we first check if the file exists.
Here’s the method that checks for file existence:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
METHOD prepare_send_files.
DATA: lv_path TYPE filep.
DATA: ls_file TYPE t_file_send_internal.
DATA: lt_file_listing TYPE tt_file_listing,
lt_paths TYPE TABLE OF filep.
" prepare the given files
LOOP AT it_files ASSIGNING FIELD-SYMBOL(<import>) WHERE path_and_name IS NOT INITIAL
AND content IS NOT INITIAL.
ls_file-filename = <import>-path_and_name.
ls_file-content_base64 = cl_http_utility=>encode_x_base64( <import>-content ).
APPEND ls_file
TO rt_files.
" extract the path portion for the existing check later
CLEAR lv_path.
CALL FUNCTION 'CV120_SPLIT_PATH'
EXPORTING
pf_path = CONV char1024( <import>-path_and_name )
IMPORTING
pfx_path = lv_path.
APPEND lv_path
TO lt_paths.
ENDLOOP.
CHECK rt_files IS NOT INITIAL.
SORT lt_paths.
DELETE ADJACENT DUPLICATES FROM lt_paths.
" check whether the file already exists or not
LOOP AT lt_paths ASSIGNING FIELD-SYMBOL(<path>).
APPEND LINES OF get_file_listing( iv_subfolder = CONV #( <path> ) )
TO lt_file_listing.
ENDLOOP.
LOOP AT rt_files ASSIGNING FIELD-SYMBOL(<file>).
READ TABLE lt_file_listing WITH KEY path_and_name = <file>-filename
TRANSPORTING NO FIELDS.
IF sy-subrc EQ 0.
<file>-action = 'update'.
ELSE.
<file>-action = 'create'.
ENDIF.
ENDLOOP.
ENDMETHOD.
Having this helper method allows us to use a single method to send files to the GitLab repository:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
METHOD send_files.
CONSTANTS: lc_service_path TYPE string VALUE 'projects/{id}/repository/commits'.
TYPES: BEGIN OF t_action,
action TYPE string,
file_path TYPE string,
content TYPE string,
encoding TYPE string,
END OF t_action.
TYPES: tt_action TYPE STANDARD TABLE OF t_action
WITH DEFAULT KEY.
TYPES: BEGIN OF t_body,
branch TYPE string,
commit_message TYPE string,
actions TYPE tt_action,
END OF t_body.
DATA: lv_body TYPE string.
DATA: ls_body TYPE t_body,
ls_action TYPE t_action.
" prepare the files to be send
DATA(lt_files) = prepare_send_files( it_files ).
" build the POST body
ls_body-branch = gv_branch_name.
ls_body-commit_message = iv_commit_message.
LOOP AT lt_files ASSIGNING FIELD-SYMBOL(<file>).
CLEAR: ls_action.
ls_action-action = <file>-action.
ls_action-file_path = <file>-filename.
ls_action-content = <file>-content_base64.
ls_action-encoding = 'base64'.
APPEND ls_action
TO ls_body-actions.
ENDLOOP.
lv_body = /ui2/cl_json=>serialize( data = ls_body pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
" prepare the service path for the given project
DATA(lv_service_path) = |{ gc_api_prefix }{ lc_service_path }|.
REPLACE ALL OCCURRENCES OF '{id}'
IN lv_service_path
WITH escape( val = gv_repository_name format = cl_abap_format=>e_url_full ).
" create the http client
cl_http_client=>create( EXPORTING host = gv_host_name scheme = cl_http_client=>schemetype_https
IMPORTING client = DATA(lo_http_client)
EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0. RETURN. ENDIF.
cl_http_utility=>set_request_uri( request = lo_http_client->request uri = lv_service_path ).
IF gv_auth_token IS NOT INITIAL.
lo_http_client->request->set_header_field( name = 'PRIVATE-TOKEN' value = gv_auth_token ).
ENDIF.
lo_http_client->request->set_method( method = if_http_request=>co_request_method_post ).
lo_http_client->request->set_header_field( name = 'Content-Type' value = 'application/json' ).
lo_http_client->request->set_cdata( data = lv_body ).
lo_http_client->send( EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0.
" get the error message
lo_http_client->get_last_error( IMPORTING message = DATA(lv_send_message) ).
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = lv_send_message ).
ENDIF.
lo_http_client->receive( EXCEPTIONS OTHERS = 4 ).
IF sy-subrc NE 0.
" get the error message
lo_http_client->get_last_error( IMPORTING message = DATA(lv_receive_message) ).
RAISE EXCEPTION TYPE zcx_gitlab
EXPORTING
textid = VALUE #( msgid = '00' msgno = '001' attr1 = lv_receive_message ).
ENDIF.
DATA(lv_response) = lo_http_client->response->get_cdata( ).
lo_http_client->close( ).
ENDMETHOD.
With this class, you can now interact with GitLab to list, receive, and send files. You can modify the existing functionality as needed to handle more use cases.
🧠 Final Thoughts
Integrating GitLab with ABAP for file management opens up numerous possibilities for automating and streamlining processes, especially for tasks like translation file management. By leveraging the GitLab API, we’ve been able to seamlessly interact with Git repositories directly from ABAP, without needing local Git tools. This approach enhances flexibility and simplifies the management of repository files within SAP systems.
With the methods outlined in this article, you can easily extend the functionality to interact with GitLab repositories for tasks like listing files, receiving file contents, and updating or creating files. Whether you’re integrating for translation management or other use cases, this ABAP-GitLab integration provides a powerful solution for automating SAP-GitLab interactions.
By implementing this system, you can reduce manual intervention, streamline workflows, and improve the overall efficiency of managing repository data within your SAP environment.