OXYGENHUB

Changes and Dependencies

Andy McCall edited this page · 1 revision

Changes and Dependencies

What a change holds

classDiagram
    class Change {
        version
        change_id
        deps: [ChangeHash]
        author: KeyId
        hlc: hybrid logical clock
        message
        tree_ops: [TreeOp]
        edits: [Edit]
        kind: normal, resolution or working-copy
    }
    class TreeOp {
        AddNode, Move, Delete, SetMode
    }
    class Edit {
        Insert lines between two atoms
        Delete a range of atoms
        Replace a whole opaque file
    }
    Change *-- TreeOp
    Change *-- Edit
  • Tree operations add, move, delete and change the mode of files and directories, each identified by a NodeId.
  • Edits change file content. A text file is edited line by line; an opaque file (binary) is replaced whole.

A file is a graph of lines

Each line is an atom, named by the change that introduced it and an index. A file's content is a graph: an edge from atom A to atom B means "B comes right after A". Deleting a line marks the atom deleted; it stays in the graph as a tombstone.

flowchart LR
    START((START)) --> a1["c1:0 fn main() {"]
    a1 --> a2["c1:1 println!(hi)"]
    a2 --> a3["c1:2 }"]
    a3 --> END((END))
    a1 --> b1["c2:0 let x = 1;"]
    b1 --> a2
    a2 -.-> d1["c3 deletes c1:1"]

Change c1 wrote three lines. Change c2 inserted a line between c1:0 and c1:1. Change c3 deleted c1:1. The graph is the union of what every change added, so it does not matter in which order the changes arrive: the result is the same.

Dependencies

A change depends on the changes that introduced anything it touches: every line it deletes, every line it inserts next to, every file it edits or moves.

flowchart BT
    c1["c1: create main.rs"]
    c2["c2: insert a line in main.rs"] --> c1
    c3["c3: delete a line of c1"] --> c1
    c4["c4: create README"]
    c5["c5: edit a line c2 wrote"] --> c2
  • c2 and c3 both depend on c1, but not on each other.
  • c4 depends on nothing.
  • c5 depends on c2, and so, through it, on c1.

Commutation

Two changes commute when neither depends on the other, directly or through others. Commuting changes can be applied in either order and give exactly the same result.

flowchart LR
    S[State with c1] -->|apply c2, then c3| R[Same result]
    S -->|apply c3, then c2| R

This is why Oxygen needs no merge commits. Pulling someone's work adds their changes to the set; anything that did not touch your work simply sits beside it.

Explicit dependencies

Changes can be logically coupled without sharing any lines: an API change in one file and the update of its caller in another. oxy commit --depends-on CHANGE adds a dependency so the two can never be separated. Use it sparingly: every extra dependency turns history back towards a straight line.

Rewriting

Amending, rebasing, splitting or squashing makes a new change with the same ChangeId. Changes that depended on the old one are rewritten automatically, their references translated to the new change. Where a line they referred to no longer exists, the reference moves to the nearest surviving line and oxy tells you.

flowchart BT
    subgraph before[Before amending C]
        C1[C #aaa] 
        D1[D #ddd] --> C1
    end
    subgraph after[After]
        C2[C #bbb same ChangeId] 
        D2[D #eee same ChangeId] --> C2
    end
    before ==> after