gCTS (Git-enabled Change and Transport System) and CI/CD pipelines bring modern DevOps practices to the ABAP world. In this article, you’ll learn how to set up a complete DevOps pipeline for ABAP Cloud - from Git integration to automated deployment pipeline.
What is gCTS?
gCTS connects the classic SAP transport system with Git repositories. Instead of manually moving transport requests between systems, ABAP objects are stored as files in Git and synchronized via Git operations.
Core Concept
┌─────────────────────────────────────────────────────────────────────┐│ Development Landscape with gCTS ││ ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ DEV │ │ QAS │ │ PRD │ ││ │ System │ │ System │ │ System │ ││ │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │ ││ │ │ gCTS │ │ │ │ gCTS │ │ │ │ gCTS │ │ ││ │ │Client │ │ │ │Client │ │ │ │Client │ │ ││ │ └───┬───┘ │ │ └───┬───┘ │ │ └───┬───┘ │ ││ └──────│──────┘ └──────│──────┘ └──────│──────┘ ││ │ │ │ ││ └─────────┬────────┴────────┬─────────┘ ││ Push │ │ Pull ││ ▼ ▼ ││ ┌───────────────────────────────┐ ││ │ Git Repository │ ││ │ (GitHub / GitLab / Azure) │ ││ └───────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────┘Classic CTS vs. gCTS
| Aspect | Classic CTS | gCTS |
|---|---|---|
| Versioning | Transport requests | Git commits |
| Branching | Not possible | Full branching |
| Comparison | Limited | Diff between commits |
| Collaboration | Limited | Pull requests, code review |
| History | Transport logs | Complete Git history |
| Rollback | Manual | Git revert/reset |
| CI/CD | Difficult | Native integration |
| Tooling | SAP-specific | Standard Git tools |
gCTS vs. abapGit: When Do I Use Which?
A common question in the ABAP community: What’s the difference between gCTS and abapGit?
| Criterion | gCTS | abapGit |
|---|---|---|
| Origin | SAP product (official) | Open source (community) |
| Target audience | IT Operations & Enterprise | Developers & teams |
| Focus | Transport automation | Code versioning |
| Git integration | Push & pull | Full (bidirectional) |
| Cost | SAP license required | Free |
| Setup complexity | Higher (enterprise setup) | Lower (quick start) |
| CI/CD pipeline | Native SAP integration | Manually configurable |
| Transport integration | Fully integrated | None |
| SAP support | Yes, official support | Community support |
Recommendation
- Choose gCTS: Enterprise environments with productive system landscapes, when official SAP support is needed
- Choose abapGit: Open source projects, developer teams, quick start, maximum flexibility
- Combination: Both tools can be combined - abapGit for development, gCTS for enterprise deployments
For a detailed introduction to abapGit, see the abapGit Tutorial.
CI/CD Concepts for ABAP
Continuous Integration (CI) and Continuous Delivery (CD) bring proven DevOps practices to ABAP development:
| Phase | Without CI/CD | With CI/CD |
|---|---|---|
| Tests | Manual, often forgotten | Automatic with every commit |
| Code quality | Sporadic ATC checks | Continuous checking |
| Deployment | Manual transport | Automated & reproducible |
| Feedback | Delayed, after days | Immediate, in minutes |
| Risk | Large releases, high risk | Small changes, low risk |
| Rollback | Complex, manual | Simple, automated |
The CI/CD Pipeline for ABAP
┌─────────────────────────────────────────────────────────────────────────┐│ CI/CD Pipeline for ABAP Cloud ││ ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││ │ Code │ │ Build │ │ Test │ │ Quality │ │ Deploy │ ││ │ Push │──▶│ Check │──▶│ Stage │──▶│ Gate │──▶│ Stage │ ││ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ ││ │ │ │ │ │ ││ ▼ ▼ ▼ ▼ ▼ ││ Git Push Syntax & ABAP Unit ATC Checks gCTS Pull ││ Trigger Activation Tests Code Review Transport ││ Integration Coverage ││ Tests Security │└─────────────────────────────────────────────────────────────────────────┘Setting Up gCTS: Setup Guide
Prerequisites
- SAP S/4HANA or SAP BTP ABAP Environment
- Git repository (GitHub, GitLab, Azure DevOps)
- SSL certificates for Git server
- Authorizations: S_CTS_ADMI, S_CTS_SADM
Step 1: Import SSL Certificates
Transaction: STRUST
1. Open SSL Client (Standard)2. Import certificate: - GitHub: github.com - GitLab: gitlab.com (or custom domain)3. Update certificate list4. SaveStep 2: Create gCTS Repository
In transaction GCTS_MAINT or via the Fiori app:
┌──────────────────────────────────────────────────────────────┐│ Repository Configuration │├──────────────────────────────────────────────────────────────┤│ Repository Name: Z_MY_PROJECT ││ Remote URL: https://github.com/company/project.git ││ Branch: main ││ ││ Authentication: ││ ● Token-based (recommended) ││ ││ Username: github-service-user ││ Token: ghp_xxxxxxxxxxxx ││ ││ vSID (Virtual System ID): DEV │└──────────────────────────────────────────────────────────────┘Step 3: Create Repository via API (optional)
CLASS zcl_gcts_setup DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. METHODS create_repository IMPORTING iv_repository_name TYPE string iv_remote_url TYPE string iv_branch TYPE string DEFAULT 'main' RAISING cx_web_http_client_error.ENDCLASS.
CLASS zcl_gcts_setup IMPLEMENTATION. METHOD create_repository. DATA(lo_destination) = cl_http_destination_provider=>create_by_url( i_url = |http://localhost:50000/sap/bc/cts_abapvcs/repository| ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( i_destination = lo_destination ).
DATA: BEGIN OF ls_request, repository TYPE string, url TYPE string, branch TYPE string, role TYPE string VALUE 'SOURCE', END OF ls_request.
ls_request-repository = iv_repository_name. ls_request-url = iv_remote_url. ls_request-branch = iv_branch.
DATA(lv_json) = /ui2/cl_json=>serialize( data = ls_request compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).
DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_header_field( i_name = 'Content-Type' i_value = 'application/json' ). lo_request->set_text( lv_json ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>post ).
IF lo_response->get_status( )-code <> 201. RAISE EXCEPTION TYPE cx_web_http_client_error. ENDIF.
lo_client->close( ). ENDMETHOD.ENDCLASS.Step 4: Link ABAP Package to Repository
After creating the repository, the ABAP package must be linked:
" In SE80 or via ADT:" Package Z_MY_PACKAGE → Properties → Assign gCTS RepositoryGitHub Actions Pipeline for ABAP
GitHub Actions offers maximum flexibility for ABAP CI/CD. Here’s a complete, production-ready pipeline:
Complete Pipeline Configuration
name: ABAP CI/CD Pipeline
on: push: branches: [ main, develop, 'release/**' ] pull_request: branches: [ main, develop ] workflow_dispatch: inputs: deploy_target: description: 'Deploy to environment' required: true default: 'dev' type: choice options: - dev - qas - prd
env: ABAP_PACKAGE: ${{ vars.ABAP_PACKAGE }} ATC_VARIANT: ${{ vars.ATC_VARIANT || 'DEFAULT' }}
jobs: # Job 1: Syntax Check and Activation build: name: Build & Syntax Check runs-on: ubuntu-latest
steps: - name: Checkout Repository uses: actions/checkout@v4
- name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20'
- name: Verify ABAP Syntax run: | curl -X POST \ "${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/programs/checks" \ -u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \ -H "Content-Type: application/json" \ --fail
# Job 2: ABAP Unit Tests unit-tests: name: ABAP Unit Tests needs: build runs-on: ubuntu-latest
steps: - name: Run ABAP Unit Tests id: aunit run: | curl -X POST \ "${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/abapunit/testruns" \ -u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \ -H "Content-Type: application/vnd.sap.adt.abapunit.testruns.config.v4+xml" \ -H "Accept: application/vnd.sap.adt.abapunit.testruns.result.v1+xml" \ -d @- << 'EOF' > aunit_results.xml <?xml version="1.0" encoding="UTF-8"?> <aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit"> <external> <coverage active="true" branchCoverage="true"/> </external> <options> <uriType value="semantic"/> <testDeterminationStrategy sameProgram="true"/> <testRiskCoverage> <harmless active="true"/> <dangerous active="true"/> <critical active="true"/> </testRiskCoverage> <durationCoverage short="true" medium="true" long="true"/> </options> <adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core"> <objectSet kind="inclusive"> <adtcore:objectReferences> <adtcore:objectReference adtcore:uri="/sap/bc/adt/vit/wb/object_type/devck/object_name/${{ env.ABAP_PACKAGE }}"/> </adtcore:objectReferences> </objectSet> </adtcore:objectSets> </aunit:runConfiguration> EOF
- name: Parse Test Results run: | if grep -q 'alerts severity="error"' aunit_results.xml; then echo "::error::ABAP Unit Tests failed!" exit 1 fi echo "All ABAP Unit Tests passed"
- name: Upload Test Results uses: actions/upload-artifact@v4 with: name: aunit-results path: aunit_results.xml
# Job 3: ATC Code Quality Checks atc-checks: name: ATC Quality Checks needs: build runs-on: ubuntu-latest
steps: - name: Run ATC Checks id: atc run: | # Start ATC run RUN_ID=$(curl -X POST \ "${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/atc/runs" \ -u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \ -H "Content-Type: application/vnd.sap.atc.run.parameters.v1+xml" \ -d @- << EOF | grep -oP 'id="\K[^"]+' | head -1 <?xml version="1.0" encoding="UTF-8"?> <atc:run xmlns:atc="http://www.sap.com/adt/atc"> <checkVariant>${{ env.ATC_VARIANT }}</checkVariant> <objectSets> <objectSet name="run"> <softwareComponent name="${{ env.ABAP_PACKAGE }}"/> </objectSet> </objectSets> </atc:run> EOF )
echo "ATC Run ID: $RUN_ID" sleep 30
# Retrieve results curl -X GET \ "${{ secrets.ABAP_ENDPOINT }}/sap/bc/adt/atc/runs/$RUN_ID/results" \ -u "${{ secrets.ABAP_USER }}:${{ secrets.ABAP_PASSWORD }}" \ -H "Accept: application/vnd.sap.atc.checkresult.v1+xml" \ > atc_results.xml
- name: Evaluate ATC Results run: | ERRORS=$(grep -c 'priority="1"' atc_results.xml || true) WARNINGS=$(grep -c 'priority="2"' atc_results.xml || true)
echo "ATC Results: $ERRORS errors, $WARNINGS warnings"
if [ "$ERRORS" -gt 0 ]; then echo "::error::ATC found $ERRORS critical issues!" exit 1 fi echo "ATC checks passed"
- name: Upload ATC Results uses: actions/upload-artifact@v4 with: name: atc-results path: atc_results.xml
# Job 4: Deploy to DEV deploy-dev: name: Deploy to DEV needs: [unit-tests, atc-checks] runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' environment: development
steps: - name: Pull to DEV System via gCTS run: | curl -X POST \ "${{ secrets.DEV_ENDPOINT }}/sap/bc/cts_abapvcs/repository/${{ vars.GCTS_REPO }}/pull" \ -u "${{ secrets.DEPLOY_USER }}:${{ secrets.DEPLOY_PASSWORD }}" \ -H "Content-Type: application/json" \ -d '{"branch": "develop"}' echo "Deployed to DEV"
# Job 5: Deploy to QAS deploy-qas: name: Deploy to QAS needs: [unit-tests, atc-checks] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' environment: quality-assurance
steps: - name: Pull to QAS System via gCTS run: | curl -X POST \ "${{ secrets.QAS_ENDPOINT }}/sap/bc/cts_abapvcs/repository/${{ vars.GCTS_REPO }}/pull" \ -u "${{ secrets.DEPLOY_USER }}:${{ secrets.DEPLOY_PASSWORD }}" \ -H "Content-Type: application/json" \ -d '{"branch": "main"}' echo "Deployed to QAS"
# Job 6: Deploy to PRD (Manual Approval) deploy-prd: name: Deploy to PRD needs: deploy-qas runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' environment: production
steps: - name: Pull to PRD System via gCTS run: | curl -X POST \ "${{ secrets.PRD_ENDPOINT }}/sap/bc/cts_abapvcs/repository/${{ vars.GCTS_REPO }}/pull" \ -u "${{ secrets.DEPLOY_USER }}:${{ secrets.DEPLOY_PASSWORD }}" \ -H "Content-Type: application/json" \ -d '{"branch": "main", "tag": "${{ github.sha }}"}' echo "Deployed to PRD"Azure DevOps Pipeline Alternative
For Microsoft environments, Azure DevOps offers an excellent alternative:
trigger: branches: include: - main - develop
pool: vmImage: 'ubuntu-latest'
variables: ABAP_PACKAGE: 'Z_MY_PACKAGE' ATC_VARIANT: 'Z_CI_CHECKS'
stages: - stage: Test displayName: 'Test Stage' jobs: - job: ABAPUnitTests displayName: 'ABAP Unit Tests' steps: - script: | curl -X POST \ "$(ABAP_ENDPOINT)/sap/bc/adt/abapunit/testruns" \ -u "$(ABAP_USER):$(ABAP_PASSWORD)" \ -H "Content-Type: application/vnd.sap.adt.abapunit.testruns.config.v4+xml" \ -d '<aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit"> <options><uriType value="semantic"/></options> </aunit:runConfiguration>' \ --fail displayName: 'Run ABAP Unit Tests'
- job: ATCChecks displayName: 'ATC Quality Checks' steps: - script: | curl -X POST \ "$(ABAP_ENDPOINT)/sap/bc/adt/atc/runs" \ -u "$(ABAP_USER):$(ABAP_PASSWORD)" \ -H "Content-Type: application/vnd.sap.atc.run.parameters.v1+xml" \ --fail displayName: 'Run ATC Checks'
- stage: DeployDev displayName: 'Deploy to DEV' dependsOn: Test condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) jobs: - deployment: DeployDEV environment: 'development' strategy: runOnce: deploy: steps: - script: | curl -X POST \ "$(DEV_ENDPOINT)/sap/bc/cts_abapvcs/repository/$(GCTS_REPO)/pull" \ -u "$(DEPLOY_USER):$(DEPLOY_PASSWORD)" \ -d '{"branch": "develop"}' displayName: 'gCTS Pull to DEV'
- stage: DeployQAS displayName: 'Deploy to QAS' dependsOn: Test condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs: - deployment: DeployQAS environment: 'quality-assurance' strategy: runOnce: deploy: steps: - script: | curl -X POST \ "$(QAS_ENDPOINT)/sap/bc/cts_abapvcs/repository/$(GCTS_REPO)/pull" \ -u "$(DEPLOY_USER):$(DEPLOY_PASSWORD)" \ -d '{"branch": "main"}' displayName: 'gCTS Pull to QAS'Integrating Automated Tests
ABAP Unit Test Class for CI/CD
CLASS zcl_customer_test DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION. CLASS-DATA: mo_environment TYPE REF TO if_cds_test_environment.
CLASS-METHODS: class_setup, class_teardown.
METHODS: setup, teardown, test_create_customer_success FOR TESTING, test_create_customer_invalid_email FOR TESTING, test_update_customer_status FOR TESTING.ENDCLASS.
CLASS zcl_customer_test IMPLEMENTATION. METHOD class_setup. " CDS Test Double Framework for isolated tests mo_environment = cl_cds_test_environment=>create_for_multiple_cds( VALUE #( ( i_for_entity = 'ZI_CUSTOMER' ) ( i_for_entity = 'ZI_ORDER' ) ) ). ENDMETHOD.
METHOD class_teardown. mo_environment->destroy( ). ENDMETHOD.
METHOD setup. mo_environment->clear_doubles( ). " Insert mock data DATA(lt_customers) = VALUE zt_customer_t( ). mo_environment->insert_test_data( lt_customers ). ENDMETHOD.
METHOD teardown. ROLLBACK WORK. ENDMETHOD.
METHOD test_create_customer_success. " Given DATA(lo_cut) = NEW zcl_customer_service( ). DATA(ls_input) = VALUE zs_customer_create( name = 'New Customer' ).
" When DATA(ls_result) = lo_cut->create_customer( ls_input ).
" Then cl_abap_unit_assert=>assert_not_initial( act = ls_result-customer_id msg = 'Customer ID should be generated' ). ENDMETHOD.
METHOD test_create_customer_invalid_email. " Given DATA(lo_cut) = NEW zcl_customer_service( ). DATA(ls_input) = VALUE zs_customer_create( name = 'Invalid Customer' email = 'not-an-email' ).
" When / Then TRY. lo_cut->create_customer( ls_input ). cl_abap_unit_assert=>fail( 'Exception expected' ). CATCH zcx_customer_error INTO DATA(lx_error). cl_abap_unit_assert=>assert_equals( act = lx_error->error_code exp = 'INVALID_EMAIL' ). ENDTRY. ENDMETHOD.
METHOD test_update_customer_status. " Given DATA(lo_cut) = NEW zcl_customer_service( ).
" When lo_cut->set_customer_status( iv_customer_id = '1' iv_status = 'I' ).
" Then DATA(ls_customer) = lo_cut->get_customer( '1' ). cl_abap_unit_assert=>assert_equals( act = ls_customer-status exp = 'I' msg = 'Status should be updated' ). ENDMETHOD.ENDCLASS.Test Annotations for CI/CD Reporting
"! <p class="shorttext">Customer Service Test Coverage</p>"! @testing ZCL_CUSTOMER_SERVICECLASS zcl_customer_test DEFINITION FOR TESTING RISK LEVEL HARMLESS " Important: HARMLESS for CI/CD DURATION SHORT. " Important: SHORT for fast pipelinesCode Quality Checks: ATC Variant for CI/CD
Create Custom ATC Variant
Create a custom ATC variant for CI/CD checks:
┌────────────────────────────────────────────────────────────────┐│ ATC Check Variant: Z_CICD_CHECKS │├────────────────────────────────────────────────────────────────┤│ ││ Priority 1 (Blocker - Pipeline fails): ││ [x] Syntax Errors ││ [x] Security Vulnerabilities (SQL Injection, XSS) ││ [x] Performance Critical (SELECT without WHERE) ││ [x] Obsolete Statements (not cloud-ready) ││ ││ Priority 2 (Warning - Report only): ││ [x] Naming Conventions ││ [x] Code Style ││ [x] Missing Documentation ││ [x] Unused Variables ││ ││ Priority 3 (Info - Ignored in CI/CD): ││ [ ] Spelling Suggestions ││ [ ] Refactoring Opportunities ││ ││ Exemptions: ││ - Test Classes (RISK LEVEL, DURATION checks) ││ - Generated Code (SICF handlers) │└────────────────────────────────────────────────────────────────┘ATC Exemption in Code
METHOD call_legacy_function. " ATC Exemption for intentionally used legacy code "#EC CI_NOCHECK - Legacy integration, migration planned Q3 CALL FUNCTION 'Z_LEGACY_FUNCTION' EXPORTING iv_input = mv_input.ENDMETHOD.Branching Strategy for ABAP
Recommended GitFlow Model
main (Production)│├── release/1.0 ────────────────────────────► PRD│ │├── develop ─────────────────────────────────► QAS│ ││ ├── feature/US-001-create-customer ────────► DEV│ │ ││ │ └── Merge via Pull Request│ ││ └── feature/US-002-material-search ───────► DEV│└── hotfix/fix-calculation ──────────────────► Merge to main & developBranch Rules
| Branch | Purpose | Deployment |
|---|---|---|
main | Stable, tested releases | PRD |
develop | Integration of all features | QAS |
feature/* | One branch per user story | DEV |
bugfix/* | Quick bug fixes | DEV |
release/* | Release candidates | QAS → PRD |
hotfix/* | Critical production fixes | PRD (direct) |
Notifications and Reporting
Slack Integration in GitHub Actions
# Add at the end of the pipeline- name: Notify Slack on Failure if: failure() uses: slackapi/slack-github-action@v1 with: channel-id: 'C01234567' slack-message: | :x: *ABAP Pipeline Failed* Repository: ${{ github.repository }} Branch: ${{ github.ref_name }} Commit: ${{ github.sha }} <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details> env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- name: Notify Slack on Success if: success() uses: slackapi/slack-github-action@v1 with: channel-id: 'C01234567' slack-message: | :white_check_mark: *ABAP Pipeline Successful* Repository: ${{ github.repository }} Deployed to: ${{ matrix.environment }} env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}Troubleshooting
Common Problems and Solutions
| Problem | Cause | Solution |
|---|---|---|
| SSL certificate error | Certificate missing in STRUST | Import certificate |
| 401 Unauthorized | Token permissions | Create token with repo scope |
| Merge conflicts | Simultaneous changes | Resolve in Git repository, then pull |
| Object locks | User has lock | SM12: Release lock |
| ATC timeout | Too many objects | Split package or increase timeout |
Retrieve Logs
METHOD get_repository_logs. DATA(lo_client) = create_gcts_client( ).
DATA(lo_request) = lo_client->get_http_request( ). lo_request->set_uri_path( |/sap/bc/cts_abapvcs/repository/{ iv_repository }/log| ).
DATA(lo_response) = lo_client->execute( if_web_http_client=>get ).
IF lo_response->get_status( )-code = 200. rv_logs = lo_response->get_text( ). ENDIF.
lo_client->close( ).ENDMETHOD.Best Practices Summary
| Topic | Recommendation |
|---|---|
| Test isolation | CDS Test Environment for unit tests |
| Test data | Mock data in setup methods |
| ATC variant | Custom CI/CD variant with meaningful checks |
| Secrets | Use GitHub Secrets / Azure Key Vault |
| Parallelization | Run independent jobs in parallel |
| Environments | GitHub Environments for approval workflows |
| Commit messages | Conventional commits (feat:, fix:, etc.) |
| Code review | Pull requests before every merge |
| Tagging | Releases with semantic versioning |
| Monitoring | Slack/Teams integration for notifications |
Conclusion
gCTS and CI/CD transform ABAP development from manual transport processes to automated DevOps workflows:
- gCTS enables Git-based transport management with complete history and branching
- CI/CD pipelines automate tests, quality checks, and deployments
- ABAP Unit Tests in the pipeline ensure code quality with every commit
- ATC checks prevent security vulnerabilities or performance problems from reaching production
Next Steps:
- Set up gCTS repository for your ABAP project
- Configure GitHub Actions or Azure DevOps pipeline
- Write ABAP Unit Tests for critical business logic
- Create custom ATC variant for CI/CD
- Train team in Git workflows
Further Reading
- abapGit Tutorial - Version control for developers
- gCTS: Git-enabled CTS - Detailed gCTS reference
- CI/CD with ABAP Cloud - Advanced pipeline configurations
- ABAP Unit Testing - Test-driven development
- Clean ABAP: Rules and Best Practices - Code quality