Skip to content

Account git structure

Gordon Heydon edited this page Aug 24, 2026 · 2 revisions

The git structure of an account

A MultiValue account is a normal git repository — on UniData, UniVerse and MVX alike. On disk the working tree is the live native account (records in a hash/LMDB store or as directory files), but the git objects store the account in a portable, human-readable shape so it browses and diffs on GitHub and can move between MultiValue platforms.

The engine translates between the two at the git boundary: commit writes the portable shape from the live account; clone builds the live account straight from the git objects (never writing the portable shape to disk). A plain git user sees only ordinary files and directories.

Tree layout

For an account with a hash file ORDERS (one record, one indexed dict item) and a directory file PARTS:

.mv-account              ← account descriptor (see below)
ORDERS/O1                ← a data record: <file>/<id>, one blob per record
ORDERS.DICT/%FILE%       ← the file's class control (see below)
ORDERS.DICT/%INDEXES%    ← the list of indexed item names
ORDERS.DICT/WHO          ← a dictionary item (a D/I/PH descriptor), one blob each
PARTS/W1                 ← records of the directory file PARTS
PARTS.DICT/%FILE%
PARTS.DICT/NAME
README.md  build.sh  …   ← plain files, stored verbatim (with mode)
  • <file>/<id> — one blob per data record. Attribute marks (@AM, 0xFE) are stored as newlines, so a record is a legible list of attributes; value and subvalue marks (@VM/@SM) are kept as their bytes.
  • <file>.DICT/<id> — the file's dictionary, the same way. The dictionary is the schema: it travels with the account, so another system can rebuild the file's columns, conversions, and indexes.
  • <file>.DICT/%FILE% — the control that names the file's class. In the open account format it is just DIR or hash (portable); natively it is FILE⊽<driver>⊽<conn>.
  • <file>.DICT/%INDEXES% — the portable list of indexed item names. The index structures are derived and never committed; a clone rebuilds them.
  • .mv-account — marks the directory as an account and carries what a native build needs (name, version, openaccount). Natively this is .mvx; the open form renames it to .mv-account.
  • Plain files (README, scripts, a server/ subtree, a submodule) are stored and checked out verbatim, executable bit and all.
  • Never committed: the backend store itself (mvxdata.lmdb) and derived artifacts (CATALOG/, LIB/, index B-trees). Records are the source of truth, tracked as blobs.

Open format vs native

The portable shape is opt-in per account, the core.autocrlf analogue:

mvx-git config mvx.openaccount true      # or: mvx-git clone --open-account <url>
in git (open form) on disk (native)
.DICT/%FILE% = DIR / hash FILE⊽dir / FILE⊽lmdb
descriptor .mv-account descriptor .mvx
records/dicts as legible blobs records in the backend store

The open form carries nothing backend-specific, so udt-git (UniData) can rebuild the same account — including files that have no on-disk control record there (%FILE%, %INDEXES%), which it generates virtually.

Dictionaries across platforms

The dictionary is the schema, so it has to mean the same thing on the other side. Native layouts differ, and the committed form is canonical — the engine projects between them rather than storing whatever the local platform happens to use:

Native In git (canonical)
single/multi-value flag attribute 6 on UniData/UniVerse attribute 7
association attribute 7 attribute 6
I-type expression TRANS('STATES',STATE,'NAME','X') TRANS(STATES,5,NAME,X)

Also normalised so an account does not churn when it crosses:

  • @ID — a generated key item is not committed; it is regenerated on the other side (#96).
  • Account furniture — a stock account's own VOC records, the VOC dictionary and the platform's SQL catalogue files are not account content and do not travel (#95, #72).
  • Trailing newline — records committed from UniData end the way MVX writes them, so a cross-platform commit does not rewrite every record (#94).
  • The descriptor names a portable class, never a backend — hash, not lmdb (#91).

Both the CLI and the in-session verb apply this same projection, through one implementation. Before 2.0.0 they did not, which is why a freshly cloned account could read clean from the shell and dirty from a session (#108).

What each git command does

  • add / commit — read the live account and write the git objects: each file's records and dictionary as blobs, %FILE%DIR/hash, .mvx.mv-account. The working tree on disk is untouched (stays native).
  • clone / checkout — build the native account directly from the git objects into the backend: create each file with the backend its %FILE% names, write records to it, restore .mvx, then rebuild indexes and catalog. The open form never lands on disk.
  • status / diff / log / show — operate on records, translating the on-disk native form up to the open form for comparison.

Because the .git is an ordinary repository, GitHub and any git client read the history normally; only rebuilding a live account needs MVX (or, for a plain-git checkout, mvx-git-adopt).

Clone this wiki locally