cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / docs / files / git.md
git.md 6.6K rendered
1---2title: "Diff and merge in git"3sidebar_position: 74---56# Diff and merge in git78`012 diff` compares two workbooks cell by cell, and `012 merge-driver`9merges them, so sheets kept in git are reviewed and merged like code.1011## 012 diff1213```sh14012 diff budget.012 budget-v2.01215```1617```18sheet Q4 renamed from Q319sheet Notes added20Q4!B7 input =SUM(A1:A5) → =SUM(A1:A6)21Q4!B7 value 15 → 2122Q4!C2 value 15 → 2123Q4!D9 input - 4224Q4!A12 input + Total25Q4!B3 format currency, decimals 2 → currency, bold, decimals 226Q4!B3 note check → checked27Q4 widths C 10 → 1428Notebook cell 2 source ls → ls -a29name Sales range Q3!B2:B20 → Q4!B2:B2130```3132A line says where, what about it changed, and `old → new`, `+ new` for33something added or `- old` for something removed. On a terminal (and in34git's pager) the old side is red and the new green; `--color never` or35`NO_COLOR` turns that off, `--color always` on.3637What it compares:3839| Change | Lines |40|---|---|41| Sheets | `added`, `removed`, `renamed from`, `moved from 3 to 1`. A sheet under a new name that holds at least half the cells of one gone is that sheet renamed |42| Cells | `input` (what was typed: a formula or an entry), `value` (what a formula computes, or an array spilled), `format` (the cell's formatting, by its fields in the file), `note` |43| Notebooks | Each [notebook](../nushell/notebooks.md) cell, lined up by its source: `added`, `removed`, or which field changed (`source`, `output`, `error`) |44| Regions | Each [linked file](following.md) or notebook output sent to a sheet, by name: added, removed, or which field changed |45| Tables | Each [table](../sheets/tables.md), by name, the same way: `Q3 table Sales range A1:C9 → A1:C10` |46| Layout | Every other field of a sheet, by its name in [the file](format.md): `widths`, `heights` and `lines` key by key, the rest (`charts`, `conditionalFormats`, `freeze`) whole |47| Workbook | Named ranges, macros, the locale and decimal arithmetic |4849A value is shown only for formulas and cells an array filled, since a50typed entry's value is its input. Values that change on every51recalculation (`NOW()`, `RAND()` and what reads them, on any sheet)52aren't compared, and neither is anything that needs a notebook cell or53JEV run: `012 diff` only reads the two files, whose notebook outputs are54compared as they were saved. The sheet shown, the55file's version, the notebook prompt's history and which computer56trusted its commands aren't changes either.5758`--format json` and `--format nuon` write the changes as a table with59the columns `kind`, `sheet`, `item`, `field`, `old` and `new`, values60with their types:6162```nu63012 diff a.012 b.012 --format nuon | from nuon | where kind == cell and field == value64```6566The exit status is diff(1)'s: 0 when the workbooks are the same, 1 when67they differ, 2 on trouble (a file that can't be read).6869## Git7071Tell git which files are workbooks, in `.gitattributes` at the top of72the repository (committed, so everyone's clone knows):7374```75*.012 diff=012 merge=01276```7778and what `012` means for them, in each clone or in your global git79config (git doesn't take commands from a repository):8081```sh82git config diff.012.command '012 diff'83git config diff.012.textconv '012 diff --textconv'84git config merge.012.name '012 cell by cell'85git config merge.012.driver '012 merge-driver %O %A %B %P'86```8788- **`diff.012.command`** makes `git diff`, `git show` and `git log -p`89 show 012's diff for each workbook, under a `diff --012 a/budget.01290 b/budget.012` header. Git calls it with its own seven arguments, which91 `012 diff` recognizes; it exits 0 then, as git expects.92- **`diff.012.textconv`** is for what doesn't use the external diff:93 `git diff --no-ext-diff`, `git log -p --no-ext-diff`, `git blame`,94 `git grep` and hosting sites that honor textconv. `012 diff --textconv95 file` writes the workbook as one line per cell and field, each naming96 its sheet (`Q3!B7 =SUM(A1:A6) = 21 [currency, decimals 2]`), and git97 diffs those lines. Either can be set alone.98- **`merge.012.driver`** merges workbooks cell by cell, below.99100## Merging101102`012 merge-driver base ours theirs [path]` merges theirs into ours from103their common base and writes the result over ours, which is what git104asks of a merge driver (`%O %A %B %P`). Each part of a workbook merges105on its own:106107```mermaid108flowchart TD109 part["A part: a cell's input, format or note; a sheet's name; a column's width; a region; a table; a notebook's cells; a named range"]110 part --> same{"Did both sides change it?"}111 same -- "One side, or neither" --> take["Take the side that changed it"]112 same -- "Both, alike" --> take113 same -- "Both, differently" --> conflict["Conflict: keep ours, note what theirs had"]114```115116So two people editing different cells of one sheet, or one formatting a117cell while the other retypes it, merge without conflicts. Sheets are118matched by name, or by their cells when renamed (as `012 diff` matches119them): a sheet renamed on one side and edited on the other merges both.120A sheet removed on one side goes, unless the other side changed it,121which is a conflict: ours stays (kept when ours changed it, gone when122ours removed it). Sheets added on both sides under one name merge cell123by cell. A notebook's cells are one part, their sources compared: the124side that changed them brings its cells and their outputs, and a125notebook run on both sides with the same cells keeps ours' outputs.126127On conflicts the merge keeps ours for each, adds to the cell's note what128theirs had (`Merge conflict, kept ours; theirs had input 11`), so the129cells show the note's mark in the grid, lists them on standard error and130exits 1:131132```133012 merge-driver: 1 conflict in budget.012, where ours is kept; a cell's note says what theirs had:134 Sheet1!B2 input: ours 12, theirs 11, base 10135```136137Git then reports the file as conflicted. Open it in 012, fix the cells138and delete the notes, then `git add` it; or take one side whole with139`git checkout --ours` or `--theirs`. A merge that can't be made at all140(a side that isn't a workbook, or a result the engine refuses, such as a141named range left on a sheet the other side removed) leaves ours as it142was and exits 2, which git also takes as a conflict.143144Two limits:145146- Inserting or deleting rows or columns moves every cell below or to147 the right, which a merge by address can't tell from edits to those148 cells. Merge such a change in 012 instead: open both versions and copy149 across.150- Trust in a file's commands follows [macros' rule](../sheets/macros.md#macros-from-other-computers):151 the merged file keeps ours' trust only when its macros, notebook cells152 and linked files are ours', or theirs were trusted on the same153 computer. Otherwise the next time the file runs one, 012 asks first.