Documentation menu

Semantic git

Release. Everything on this page, and the matching VS Code features, arrives with ArcLang 4.6.0. The diff of two files described in Review and change is older; --from, --to, -f, history, merge, baseline and workbench --rev are not in the 4.5.0 binaries.

A model is text in git, but a line diff of a model says little: moving a block is a hundred changed lines, renaming an element is one, and two people adding two attributes to the same line conflict. ArcLang compares and merges by element identity, the stable UUID the compiler derives from each id. As a result:

  • a diff shows elements, not lines, and what the change does to the gate;
  • an element has a history;
  • two branches that touch different elements, or different attributes of one element, merge without a conflict;
  • an auditor gets a baseline they can verify.

Everything is computed by the compiler. The command line, a pipeline and the VS Code workbench give the same answers.

The examples below run in a real git repository built when this page is built. Its model is a braking chain with a 100 ms budget:

brake.arc
model Braking {
  requirements safety {
    req "REQ-001" "Braking reaction" { id: "REQ-001" description: "Brake within 100 ms of detection" }
  }
  system_analysis "Braking" {
    function "Detect obstacle" { id: "SF-DET" latency: 40 ms }
    function "Command braking" { id: "SF-BRK" latency: 50 ms }
    functional_chain "Detection to braking" {
      id: "FC-BRAKE"
      involves: ["SF-DET", "SF-BRK"]
      latency_budget: 100 ms
    }
  }
  architecture logical {
    component "Brake controller" { id: "LC-BRAKE" function "Command braking" }
  }
}
trace "LC-BRAKE" satisfies "REQ-001"
test_case "TC-001" { verifies: ["REQ-001"] method: "test" }

The first commit holds this model. In the working tree, the detection function has since been slowed from 40 ms to 80 ms.

Compare with a revision

$ arclang diff brake.arc --from HEAD
Semantic diff (by stable identity): HEAD -> WORKTREE
  Model: brake.arc

System Analysis (SA): +0 -0 ~1
  ~ modified SystemFunction 'Detect obstacle' [SF-DET]
      latency: "40 ms" -> "80 ms"

Consequences:
  Production gate: PASS (ISO26262) -> FAIL (0 blocker(s) -> 1)
    + blocker: functional_chain 'Detection to braking': sum of function latencies 130.0 ms exceeds the 100.0 ms budget
  Coverage: 1/1 satisfied, 1/1 verified (was 1/1, 1/1)
  Budget Detection to braking: 90 ms -> 130 ms (limit 100 ms), EXCEEDED
  Impact: 1 element(s) reached, 1 blocker(s)

  Total: 0 added, 0 removed, 1 modified (0 renamed, 0 moved); 0 relationship(s) added, 0 removed, 0 changed
Error: semantic differences found

The model is read from git archive of the revision, so imports are resolved at that revision. The report has three parts:

  • per element: added, removed, renamed (same UUID, new name), moved to another owner, and each changed attribute with its old and new value, by layer (OA, SA, LA, PA, EPBS, data, transverse);
  • relationships: exchanges, traces, allocations and deployments added, removed or retargeted;
  • consequences, from the impact engine's what-if: the production gate before and after with the blockers it gains or loses, requirements newly unsatisfied or unverified, constraints that flip, latency budgets, simulation verdicts, coverage, and how many elements the change reaches.

Here the 40 ms became 80 ms, and the report says what that costs: the chain now sums to 130 ms against a 100 ms budget, and the gate goes from PASS to FAIL. A line diff shows a number changing.

Two revisions are compared with --from main --to v1.0 (--to defaults to the working tree). -f text|json|md|html and -o file choose the format: the Markdown form is what a pull-request comment carries (see CI/CD). The exit code follows diff(1): 1 when the model differs, 0 otherwise. Changes of the <model>.layout.json arrangement file are listed apart and never counted as model changes (a layout-only change exits 0).

$ arclang diff brake.arc --from HEAD -f md
# Semantic diff: `HEAD` -> `WORKTREE`

Model: `brake.arc`

**0 added, 0 removed, 1 modified (0 renamed, 0 moved); 0 relationship(s) added, 0 removed, 0 changed**

## System Analysis (SA): +0 -0 ~1

### SystemFunction

- **modified** `SF-DET` Detect obstacle
  - latency: "40 ms" -> "80 ms"

## Consequences

- Production gate: **PASS (ISO26262) -> FAIL (0 blocker(s) -> 1)**
  - new blocker: functional_chain 'Detection to braking': sum of function latencies 130.0 ms exceeds the 100.0 ms budget
- Coverage: 1/1 satisfied, 1/1 verified (was 1/1, 1/1)
- Budget `Detection to braking`: 90 ms -> 130 ms (limit 100 ms)
- Impact: 1 element(s) reached, 1 blocker(s)
Error: semantic differences found

The history of an element

$ arclang history brake.arc SF-DET
History of 'Detect obstacle' [SF-DET] in brake.arc

4e8ab04 2026-01-20  ArcLang docs  modified
  Slower detection
      latency: "40 ms" -> "80 ms"

45c3d96 2026-01-12  ArcLang docs  added
  Initial braking model
      added other Involvement: FC-BRAKE -> SF-DET

arclang history <file> <element> [--limit N] [--json] lists the commits that changed one element, with what changed. They are found by the semantic diff between successive revisions of the model and of what it imports, not by line blame: a commit that only reformatted the file, or that did not touch the element, is not listed, and a rename is followed because the identity is the id.

Merge by element

Two branches changed the same line of the model: perception set the latency of SF-DET, ownership gave it an owner. Git's own merge cannot tell the two edits apart:

$ git merge perception
Auto-merging brake.arc
CONFLICT (content): Merge conflict in brake.arc
Automatic merge failed; fix conflicts and then commit the result.

arclang merge is a three-way merge that git can use as its merge driver. It reads the files as a tree of declarations and matches each by keyword#id. It is installed once per repository, after showing what it changes (the repository's .git/config and a .gitattributes):

arclang merge --install            # shows what it changes, asks, then writes
arclang merge --install --yes      # no question
arclang merge --install --dry-run  # shows only

It sets merge.arclang.driver to arclang merge %O %A %B -o %A --path %P in the repository's .git/config and adds two lines to .gitattributes. Nothing is written without confirmation, and running it twice changes nothing.

$ cat .gitattributes
*.arc merge=arclang
*.layout.json merge=arclang

Now the same merge succeeds:

$ git merge --no-edit perception
Auto-merging brake.arc
Merge made by the 'ort' strategy.
 brake.arc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ grep -n 'SF-DET' brake.arc
6:    function "Detect obstacle" { id: "SF-DET" owner: "Perception team" latency: 30 ms }
10:      involves: ["SF-DET", "SF-BRK"]

The result has both edits, and the file was compiled before git accepted it. What the driver does:

  • changes to different elements, or to different attributes of one element, merge without a conflict, wherever they are and even on the same line;
  • two branches adding different elements to the same container merge in both orders, so the result does not depend on who merges;
  • an element removed on one side and untouched on the other is removed;
  • comments, blank lines, indentation, line endings and the order of the declarations nobody touched are kept as written, and the comments above a declaration travel with it;
  • the merged text is compiled; if it does not compile the driver says why and fails.

A real conflict, the same attribute changed differently, an element changed on one side and removed on the other, or the same id added with different content, is written as markers around the whole element, with a header that names it and both values. Each side is a complete element, so the file stays well formed.

Here a third branch sets the same latency to 90 ms:

$ git merge --no-edit perception
Auto-merging brake.arc
CONFLICT (content): Merge conflict in brake.arc
Automatic merge failed; fix conflicts and then commit the result.
arclang merge: 1 conflict(s), written as markers around the element:
  function 'Detect obstacle' [SF-DET]: function "Detect obstacle" { id: "SF-DET" latency: 90 ms } (ours) vs function "Detect obstacle" { id: "SF-DET" latency: 30 ms } (theirs)
Error: 1 element conflict(s)
$ sed -n 5,10p brake.arc
  system_analysis "Braking" {
<<<<<<< ours function 'Detect obstacle' [SF-DET]: function "Detect obstacle" { id: "SF-DET" latency: 90 ms } (ours) vs function "Detect obstacle" { id: "SF-DET" latency: 30 ms } (theirs)
    function "Detect obstacle" { id: "SF-DET" latency: 90 ms }
=======
    function "Detect obstacle" { id: "SF-DET" latency: 30 ms }
>>>>>>> theirs function 'Detect obstacle' [SF-DET]

arclang merge --json describes the outcome for a script: the conflicts with their two sides, the notes, the compile error. The exit status is 0 when the merge is clean and 1 on a conflict or a result that does not compile.

The files *.layout.json, where the workbench keeps the arrangement of the boxes, are merged the same way by the element each entry places (a box position, the bends of an edge, a fold, a group). The same entry changed on both sides takes theirs, and the driver says so on standard error: a layout never changes what the model means.

Baselines

A baseline is the evidence an auditor (ISO 26262, DO-178C, IEC 61508) can check later: the gate verdict, the coverage, the exports and a manifest, bound to a git tag.

$ arclang baseline create v1.0 --standard iso26262 | sed -n 1,2p
Baseline 'v1.0' created (ISO26262).
  Gate: PASS (0 blocker(s), 1 warning(s)); requirements 1 total, 1 satisfied, 1 verified

baseline create refuses a tree that is not clean (tracked files, and untracked model or layout files), checks that the model compiles, runs the production gate, writes baselines/<name>/ and makes the annotated tag baseline/<name> on the commit. It never pushes and never overwrites a baseline. The gate verdict is recorded whatever it is; --require-pass refuses a failing gate.

File in baselines/<name>/ Content
gate_report.json, gate_report.md the gate verdict, findings and counts
requirements_coverage.csv the requirement coverage table
model.sysml, model.json the SysML v2 text and the compiled model
manifest.json format version, standard, time (UTC), compiler version, commit, branch and tag; the SHA-256 and size of every source file (the model, what it imports, its layout file); the identities of the model (id, UUID, kind and name of every element and relationship, with a SHA-256 of the list); the SHA-256 of every file of the baseline
$ arclang baseline verify v1.0 | tail -4
  ok   source brake.arc: the committed file matches
  ok   identities: 12 identities match

Baseline 'v1.0': VERIFIED

baseline verify checks every file against the manifest, that the tag exists, is annotated and points at the manifest's commit, that the sources as committed still match, and that the model compiled from that commit still has the same identities. Any difference is a failure (exit 1) that says which. baseline list shows the baselines and baseline diff <a> <b> is the semantic diff between the two commits with the two gate reports side by side.

In VS Code

  • Compare ▾ in the workbench tool row (also ArcLang: Compare the model with a branch, tag, baseline or HEAD…) takes HEAD, a branch, a tag, a remote or a baseline. The diagrams are drawn with the changes marked: added in green, modified in amber, removed ghosted (dashed red, struck through) where they were, moved and renamed with a badge, each status with a glyph as well as a color. The layer tabs count the changes. A side panel lists them by layer, with the gate, the new blockers, and the requirements newly unsatisfied or unverified; each change selects its element and offers Source, Diff (VS Code's diff against the revision) and History. The comparison is read-only and Exit returns to editing.
  • History… (context menu, Properties pane, Compare ▾) lists the commits of the element. Selecting one draws the model at that revision beside the current one.
  • Create baseline… asks for a name and a standard and shows the gate result before anything is written. Compare with a baseline… and Verify a baseline… complete it.
  • ArcLang: Install the semantic merge driver runs arclang merge --install after showing what it changes.
  • The Resolve conflicts view in the ArcLang side bar lists the element conflicts a merge left, by file and element, with Accept ours, Accept theirs and Edit. A message announces them.

Limits

  • Elements are matched by id. An element declared without an id: is matched by its declaration line, so renaming it is a removal and an addition. Give elements ids.
  • For the merge, moving an element to another container is a removal and an addition: if the other branch changed the element meanwhile, that is a conflict.
  • Attributes of one element changed differently conflict as a whole element, with both complete versions in the markers.
  • The merge keeps the text and does not reformat. import lines are ordinary lines: adding different imports at the same place conflicts.
  • arclang diff --from needs the file to be in a git repository, and the models at both revisions must compile (a revision that does not is named and refused).
  • history compiles one revision per commit: use --limit on a long history.
  • A baseline of a dirty tree is refused. --allow-dirty exists, but the baseline then describes files that are not in the tagged commit.
  • The layout merge takes theirs when the same entry changed on both sides, by design.
  • Not available in the browser playground: there is no git there, so no compare, history, baseline or merge.

Every ArcLang sample on this page is compiled, and every command is run, with the released compiler when the site is built.