# Reviewing

otisk's review model is one commit per change. You amend it rather than
stacking fix-ups on top, and each push of it is kept as a revision, so nothing
you pushed is ever lost. [Concepts](concepts.md) explains why it is shaped
that way and how it differs from a pull request; this page is how to use it.

## A change

From inside a clone:

```sh
otisk change hook                    # new commits get a Change-Id trailer

echo "a first idea" > idea.md
git add idea.md && git commit -m "Write down an idea"
otisk change push                    # git push origin HEAD:refs/for/main
```

```
otisk: change 1 revision 1 -- Write down an idea
To ssh://git.otisk.dev/yuritomanek/notes.git
 * [new reference]   HEAD -> refs/changes/1/1
```

Revise it by amending, and push again:

```sh
echo "a better idea" > idea.md
git add idea.md && git commit --amend --no-edit
otisk change push                    # revision 2 of the same change

otisk change list                    # open changes
otisk change show 1                  # its revisions, and what each touches
otisk change diff 1                  # what changed between revisions 1 and 2
otisk change land 1                  # fast-forward main to revision 2
```

`show` says what each revision touches, which is usually the question:

```
change 1 (open) -> main
  Change-Id: I3f2a9c7e1b4d8a60f5c2e9b17d4a8c30e6f1b2d9
  r1  b71d4e05c3a9  2026-09-19 10:02  Write down an idea
      +  +2 -0       idea.md
  r2  8f1c0b2a9d44  2026-09-19 10:14  Write down an idea
      M  +1 -1       idea.md
```

The host works that out when a revision is pushed, so it answers from
anywhere: you need neither a clone nor the revision fetched to see what a
change contains.

The `Change-Id` trailer is what makes an amended commit the same change, so
keep it when you amend. Landing only ever fast-forwards: if `main` has moved
on, rebase onto it and push a new revision first.

## Several at once

A push may carry several commits, and each one becomes its own change, stacked
in order:

```sh
git commit -m "Add a lexer"        # on top of the change above
otisk change push                  # both commits, a change each
```

```
otisk: change 1: no new changes
otisk: change 2 revision 1 -- Add a lexer
```

The commits are reported bottom-up: the one already pushed has nothing new,
and the one on top became change 2.

Amend any of them and push again, and each change gains a revision; nothing
new means nothing recorded. Land them from the bottom up — a change whose
parent has not landed is refused. If any commit in the push is unusable, for
example because it has no `Change-Id`, the whole push is refused and nothing
is recorded, naming the commit at fault.

## Comments

Comments go on a change, or on a line of one of its revisions:

```sh
otisk change comment 1 --file idea.md --line 1 -m "is this the best idea?"
otisk change comment 1 -m "otherwise this looks fine"     # about the change
otisk change reply 1 1 -m "probably not, but it is mine"  # thread 1
otisk change comments 1
```

```
t1  open r2 idea.md:1
    yuritomanek  2026-09-18 10:02
      is this the best idea?
    yuritomanek  2026-09-18 10:05
      probably not, but it is mine
```

A comment names the revision and line it was written about, and stays there
when you push a new revision, so it always points at what was actually read.
Mark one dealt with, or bring it back:

```sh
otisk change resolve 1 1
otisk change comments 1 --all     # resolved threads too
otisk change reopen 1 1
```

`otisk change show 1` counts the threads still open, and `otisk change list`
marks changes that have any, so nothing is landed with a question outstanding
unless you mean it.

Comments live in the repository, as a commit per comment on
`refs/changes/<number>/meta`. They are stored, replicated, and restored exactly
as your code is, and you can read them with git alone:

```sh
git fetch origin refs/changes/1/meta:refs/otisk/meta && git log refs/otisk/meta
```

## All of it in one place

Everything above is also a terminal interface:

```sh
otisk ui          # in a clone; or otisk ui --repo yuritomanek/notes
```

```
┌─ 1 yuritomanek/notes — open changes ─────────┐┌─ 3 revisions ────────────────────────────────┐
│> 1  open  r2  main  (1)  Write down an idea  ││  change 1 (open) → main                      │
│                                              ││  Change-Id: I3f2a9c7e1b4d8a60f5c2e9b17d4a8c3…│
│                                              ││> r1  b71d4e05c3a9  Write down an idea        │
│                                              ││  r2  8f1c0b2a9d44  Write down an idea        │
│                                              ││                                              │
└──────────────────────────────────────────────┘└──────────────────────────────────────────────┘
┌─ 2 local work — origin/main..HEAD ───────────┐┌─ 4 comments ─────────────────────────────────┐
│> new change  Say why it is worth writing down││> t1  open r2 idea.md:1                       │
│                                              ││      yuritomanek  say why, not what          │
│                                              ││                                              │
│                                              ││                                              │
└──────────────────────────────────────────────┘└──────────────────────────────────────────────┘
```

Four panels: the repository's changes and the commits you have not pushed yet
on the left, the selected change's revisions and conversation on the right.
Moving in the change list changes what the right-hand panels show, so reading
a change never costs you sight of the others. `tab` moves to the next panel
and `1`–`4` straight to one — the number each panel wears. The line below
names the panel the keys are in and what it offers, and `?` shows every
panel's keys. Each panel scrolls when it holds more than fits, and says on its
border how much is out of sight.

**local work** is what `git log <remote>/<branch>..HEAD` holds, which is what
the panel is titled — the branch being the one the selected change targets, or
the repository's default, and fetched first so that landing something
elsewhere takes it off the list. Each commit is labelled with what pushing it
would do: start a new change, add a revision to change N, or nothing, because
it is already the latest revision, has landed, or has no `Change-Id`. `p`
pushes them for review, as one series, and what the host answers is what you
then see.

In **changes**, `a` lists every change, `l` lands, `x` abandons, and `c`
comments. In **revisions**, `d` shows the selected revision's diff and `D`
compares the last two — both in your pager, through git, returning when you
quit it. In **comments**, `R` replies and `s` resolves or reopens. Colour says
what a change's status is, and which threads are still open.

It does nothing the commands cannot: each keystroke is one of them, over one
reused ssh connection, so a refusal reads exactly as it does in the shell.
