This document covers the research behind ifc-commit's approach to storing git provenance inside IFC files: the mechanisms surveyed, the tradeoffs considered, and how the implementation was designed.
A survey of IFC mechanisms for storing git commit metadata.
Every IfcRoot-derived entity (walls, spaces, products, etc.) carries an optional IfcOwnerHistory attribute. It is the closest thing IFC has to built-in change tracking.
Fields:
| Field | Type | Notes |
|---|---|---|
OwningUser |
IfcPersonAndOrganization |
Who created the element |
OwningApplication |
IfcApplication |
Software used |
State |
IfcStateEnum |
READWRITE, READONLY, LOCKED |
ChangeAction |
IfcChangeActionEnum |
ADDED, MODIFIED, DELETED, NOCHANGE |
LastModifiedDate |
IfcTimeStamp |
Unix timestamp |
LastModifyingUser |
IfcPersonAndOrganization |
|
LastModifyingApplication |
IfcApplication |
|
CreationDate |
IfcTimeStamp |
Unix timestamp |
Raw IFC line from samples/duplex.ifc:
#33=IFCOWNERHISTORY(#32,#2,$,.NOCHANGE.,$,$,$,0);
Limitations:
ChangeAction is a coarse enum; no room for a commit hash, message, or branchIfcApplication.Version is a short string, not designed for structured dataVerdict: Good for standard compliance and timestamping. Not sufficient alone for git metadata.
Custom property sets (Pset_*) are the standard IFC way to attach arbitrary key-value metadata to any IfcObject. They survive round-trips through most IFC-aware tools (unknown Psets are ignored, not discarded).
Schema —
Pset_GitCommit
| Property | Type | Example |
|---|---|---|
CommitHash |
IfcLabel |
a1b2c3d4f5e6c7b8 |
CommitMessage |
IfcText |
Fix wall thickness |
CommitAuthor |
IfcLabel |
alice <***@***> |
CommitDate |
IfcLabel |
2026-03-24T14:30:00Z |
CommitBranch |
IfcLabel |
main |
OperationName |
IfcLabel |
Merge |
ifcopenshell snippet — writing:
pset = ifcopenshell.api.pset.add_pset(model, product=element, name="Pset_GitCommit")
ifcopenshell.api.pset.edit_pset(model, pset=pset, properties={
"CommitHash": commit_hash,
"CommitMessage": commit_message,
"CommitAuthor": commit_author,
"CommitDate": commit_date,
"CommitBranch": branch,
"OperationName": operation_name,
})
Reading back:
for rel in element.IsDefinedBy or []:
if rel.is_a("IfcRelDefinesByProperties"):
pset = rel.RelatingPropertyDefinition
if pset.Name == "Pset_GitCommit":
props = {p.Name: p.NominalValue.wrappedValue for p in pset.HasProperties}
Verdict: Best fit for per-element traceability. Flexible, queryable, spec-compliant.
IfcDocumentInformation + IfcRelAssociatesDocument lets you attach a document reference (URL, identifier, description) to any IfcRoot entity. It can carry a git commit URL back to the forge.
doc = ifcopenshell.api.document.add_information(model)
ifcopenshell.api.document.edit_information(model, information=doc, attributes={
"Identification": commit_hash[:8],
"Name": commit_message,
"Location": f"https://gitaec.org/rvba/ifc-commit/commit/{commit_hash}",
})
ref = ifcopenshell.api.document.add_reference(model, information=doc)
ifcopenshell.api.document.assign_document(model, products=[element], document=ref)
Verdict: Useful for linking elements to a hosted commit URL. More verbose than a Pset. Better suited for file-level "source revision" than per-element tracking.
IfcApplication is referenced by every IfcOwnerHistory. Its Version field can carry the current commit hash as a lightweight file-level stamp.
app = ifcopenshell.api.owner.add_application(model)
ifcopenshell.api.owner.edit_application(model, application=app, attributes={
"ApplicationIdentifier": "ifc-commit",
"Version": commit_hash,
"Name": "ifc-commit",
})
Verdict: Zero overhead. Limited to one hash per file. Good as a quick "what commit produced this file" marker.
| Mechanism | Granularity | Stores hash/message | IFC compliance | Overhead |
|---|---|---|---|---|
IfcOwnerHistory |
per-element | No | Native | Minimal |
Pset_GitCommit |
per-element | Yes (all fields) | Standard extension | Medium |
IfcDocumentInformation |
per-element | Yes (via Location) | Standard | High |
IfcApplication.Version |
per-file | Hash only | Native | Minimal |
A two-layer design:
File level — IfcApplication.Version is set to the commit hash. Every tool that reads IfcOwnerHistory exposes this with no extra work.
Element level — On elements touched by an operation, a Pset_GitCommit property set is written with the full commit metadata. IfcOwnerHistory.ChangeAction is updated to ADDED or MODIFIED accordingly.
This keeps standard IFC compliance intact while making full git provenance queryable directly from the model.
The following section describes how the history mechanism is integrated into the pipeline and webapp.
The history command in ifc-commit operates in two modes:
write_psets: true): at the end of a pipeline run, stamps Pset_GitCommit on all elements in every operation's output IFC, using the current HEAD commit.input present): opens an IFC file, collects all Pset_GitCommit records, and writes them to a JSON file for the webapp.Example pipeline declaration:
operations:
- name: Extract
command: extract
input: ifc/duplex.ifc
output: ifc/duplex_extract.ifc
type: IfcSpace
id: A102
- name: Modify
command: modify
input: ifc/duplex_extract.ifc
output: ifc/duplex_modified.ifc
element: "168381"
x: 2
- name: Merge
command: merge
base: ifc/duplex.ifc
space: A102
part: ifc/duplex_modified.ifc
output: ifc/duplex_merge.ifc
- name: WriteHistory
command: history
write_psets: true
- name: ReadHistory
command: history
input: ifc/duplex_merge.ifc
output: ifc/history.json
After a pipeline run, the webapp calls /api/ifc-history to surface the per-element commit metadata, with links back to the corresponding commits on the forge.
Each element that was touched by the pipeline carries:
Schema — per-element provenance
| Property | Example |
|---|---|
CommitHash |
a1b2c3d4… |
CommitMessage |
move table |
CommitAuthor |
rvba <***@***> |
CommitDate |
2026-03-24T14:30:00Z |
CommitBranch |
main |
OperationName |
Modify |
The element history panel links each commit hash to its page on the forge, making the full modification trail navigable directly from the webapp.