Checkin & Batch Lock Session
adt checkin <dir> is the inverse of adt import package: it pushes a
local abapGit / gCTS-formatted directory back into SAP. Because SAP
has no transactional "save N objects atomically" API, we approximate it
with a batch lock session plus dependency-ordered application.
This page documents how that orchestration works and what its limitations are.
Why SAP has no atomic save
The SAP ADT surface is per-object:
POST /adtlock/POST /adtlock?actionType=unlockare one-at-a-time.PUT /oo/classes/{n}is one-at-a-time.- There is no "begin transaction / commit" endpoint — each PUT is immediately persisted on success.
If we PUT three classes and the third fails mid-way, the first two are already live. True rollback is impossible; the best we can do is fail fast before any PUT and surface the remaining failures clearly.
BatchLockSession
@abapify/adt-locks exposes a BatchLockSession primitive:
const batch = await createBatchLockSession(client, {
objects: [
{ uri: '/sap/bc/adt/oo/classes/ZCL_A' },
{ uri: '/sap/bc/adt/oo/classes/ZCL_B' },
{ uri: '/sap/bc/adt/packages/ZMYPKG', transport: 'DEVK900042' },
],
});
try {
// use batch.handles.get(uri) to retrieve each lock handle
} finally {
await batch.releaseAll();
}
Behaviour:
- Acquires locks sequentially. On any failure, releases every previously-acquired lock and throws.
- All locks share the same security session / CSRF token — SAP only issues one security session per user.
- Never holds locks across an await boundary the caller doesn't own —
release is synchronous with the end of
checkin.
Pre-flight validation, not execution
Our CheckinService.apply() uses BatchLockSession primarily as a
validation primitive:
1. Build change plan (diff local vs. remote).
2. Acquire BatchLockSession over every object in the plan.
3. Immediately release it. ← validation only
4. Apply each entry via ADK save({mode: 'upsert'}) — which re-locks
per object and releases on completion.
Why the double-lock?
- Fail fast. If any object is locked by someone else, we learn in step 2, before any PUT runs.
- ADK already locks.
ADK.save()acquires its own lock (and handles ETag refresh / retry / transport binding). Using ADK for step 4 means we get all of that for free. - The CSRF token survives. Because the security session is stable across lock + unlock + relock, the double cycle is cheap.
When ADK exposes a way to thread pre-acquired lock handles into
save(), the two cycles can collapse into one. That's a known
follow-up (see Roadmap → Future).
Dependency-tier ordering
PUTs run in a deterministic order so that referenced objects exist before their referents:
tier 0: packages (DEVC)
tier 1: DDIC (DOMA, DTEL, TABL, TTYP, VIEW, …)
tier 2: application types (CLAS, INTF, PROG, FUGR, FUNC, INCL, …)
tier 3: CDS (DDLS, DCLS, DDLX, BDEF, SRVD, SRVB)
Within a tier, order is stable (by object name). This is "good enough" for realistic abapGit packages — full semantic sort would require a symbol resolver we don't yet have.
Best-effort rollback
If step 4 fails part-way, CheckinService.apply():
- Stops further PUTs.
- Releases any locks still held.
- Returns a summary with one entry per object:
created/updated/unchanged/failed, plus the error message for failures. - Logs "manual cleanup may be needed" for partially-applied changes.
It does not attempt to un-do successful PUTs — there is no SAP API to do so, and a naive "PUT the old content back" would either fail (ETag mismatch) or land yet another transport entry.
Known limitations
- No per-field diff (yet).
diffObject()today treats "remote exists + local had pending sources" asupdateand "remote 404" ascreate. True file-level diffs will come whenFormatPlugin.diff()lands (E05 follow-up — see Format Plugins). - Double lock cycle. See "Pre-flight validation" above.
- No
.gitignore-style excludes.checkinprocesses every serialised object in the directory. - gCTS-hosted repos use
adt gcts commit, notcheckin. When the target system has gCTS enabled and the repo is gCTS-tracked, commit + push through gCTS' own endpoints.checkinis the raw ADT path.