on master help sign in
j/config.j
671 lines 31 kB history raw
1-- config.j — everything j knows.
2--
3-- j reads this file on every run. An expression can use a name only if this
4-- file declares it (a builtin, implemented by j) or defines it (written here,
5-- in the language). Nothing else exists.
6--
7-- Conventions in this file:
8-- Name = … a shape: a record layout, matched by its field names
9-- name : Type a signature. Alone, it declares a builtin implemented
10-- by j. Before a definition, it documents that definition.
11-- Either way j checks arguments and results against it at
12-- each call, one level deep: kind, list-ness, field names.
13-- name = expr a definition. Functions are lambdas: name = \x -> …
14--
15-- Edits. Most definitions are Edits: functions from a Repo to a Repo. On the
16-- command line a function result is applied to the current repository; a Repo
17-- result is persisted. Anything else is printed. `e2 . e1` runs e1 then e2;
18-- `e or id` runs e and leaves things alone if it crashes.
19--
20-- Four definitions here are read by j itself: `user` (who commits), `tree`
21-- (how a Repo is printed), `immutable` (which commits may never be
22-- rewritten), and `labelled` (what a %name literal means).
25------------------------------------------------------------------------------
26-- You
27------------------------------------------------------------------------------
29user : { name : Text, email : Text }
30user = { name = "Your Name", email = "you@example.com" }
33------------------------------------------------------------------------------
34-- Shapes
35------------------------------------------------------------------------------
37Path = [Text] -- a file path as its components; written ./a/b
38Label = Text -- a bookmark name on the remote
40Entry = { path : Path, content : Blob }
41Snapshot = [Entry] -- the files of one commit; paths unique;
42 -- a deleted, resolved path has no entry
43Change = { from : Snapshot, to : Snapshot } -- the files before and after
45Commit = { files : Snapshot
46 , message : Text
47 , labels : [Label] -- the remote's names for this commit;
48 -- read-only: only fetch and push change them
49 , id : Id } -- each id is on exactly one commit
51Subtree = { root : Commit, children : [Subtree] }
53-- A Repo is the whole history, seen from one commit: the focused subtree plus
54-- the path back to the top. Each Frame is one ancestor with a hole where the
55-- focused subtree was; left and right are the siblings on either side. Sibling
56-- order is presentation only.
57Frame = { parent : Commit, left : [Subtree], right : [Subtree] }
58Repo = { root : Commit, children : [Subtree], context : [Frame] }
59 -- context = [] at the top of history
61Detached = { subtree : Subtree, rest : Repo }
62Rebase = { from : Snapshot, onto : Snapshot } -- the base a subtree was written on, and the new one
63Meta = { hash : Text, author : Text, email : Text, time : Int }
64 -- what jj records about a commit beyond the model
66Edit = Repo -> Repo -- a command; may crash
67Fileset = Path -> Bool -- which paths a command applies to
68Revset = Repo -> [Id] -- a set of commits, computed from a repo
70Push = { id : Id, name : Label } -- set the remote's bookmark `name` to `id`
71Drop = { delete : Label } -- delete the remote's bookmark
74------------------------------------------------------------------------------
75-- Builtins: functions and control
76------------------------------------------------------------------------------
78(.) : (b -> c) -> (a -> b) -> a -> c -- composition, right to left
79-- Return a value unchanged. As a repository command, `j id` records the
80-- snapshotted working-copy edits in the focused commit. `j save` is the
81-- descriptive alias in this reference config; inspections such as `j status`
82-- and `j tree` do not record those edits.
83id : a -> a
84const : a -> b -> a
85crash : Text -> a -- abort the program with a message
87-- `a or b` is a keyword, not a builtin: the value of a; or, if evaluating a
88-- crashed, the value of b. When a is a function and b is one too, the result
89-- is pointwise, (f or g) x = f x or g x, so `edit or id` tries an edit and
90-- keeps the repository if it fails. Edits compose with `.`: `tree . squash`
91-- squashes, then renders.
93------------------------------------------------------------------------------
94-- Builtins: comparison, booleans, integers
95------------------------------------------------------------------------------
97(==) : a -> a -> Bool -- structural; functions crash
98(/=) : a -> a -> Bool
99(&&) : Bool -> Bool -> Bool -- short-circuit
100(||) : Bool -> Bool -> Bool -- short-circuit
101not : Bool -> Bool
102(+) : Int -> Int -> Int
103(-) : Int -> Int -> Int
104(*) : Int -> Int -> Int
105(<) : Int -> Int -> Bool
106(<=) : Int -> Int -> Bool
107(>) : Int -> Int -> Bool
108(>=) : Int -> Int -> Bool
109show : a -> Text -- any value, in this language's syntax
111------------------------------------------------------------------------------
112-- Builtins: lists and text
113------------------------------------------------------------------------------
115(::) : a -> [a] -> [a] -- cons
116map : (a -> b) -> [a] -> [b]
117filter : (a -> Bool) -> [a] -> [a]
118length : [a] -> Int
119null : [a] -> Bool
120head : [a] -> a -- crash on []
121tail : [a] -> [a] -- crash on []
122last : [a] -> a -- crash on []
123nth : Int -> [a] -> a -- 0-based; crash out of range
124take : Int -> [a] -> [a]
125drop : Int -> [a] -> [a]
126member : a -> [a] -> Bool
127range : Int -> Int -> [Int] -- range a b = [a (a+1) … (b-1)]
128foldl : (b -> a -> b) -> b -> [a] -> b -- foldl f z [x y] = f (f z x) y
130-- ++ and concat work on lists and on text. concat [] crashes: supply a
131-- default with or, e.g. concat xs or []
132(++) : m -> m -> m
133concat : [m] -> m
135startsWith : Text -> Text -> Bool
136endsWith : Text -> Text -> Bool
137splitOn : Text -> Text -> [Text] -- splitOn "/" "a/b" = ["a" "b"]
139------------------------------------------------------------------------------
140-- Builtins: the repository
141------------------------------------------------------------------------------
143-- Replay a change onto a snapshot: "onto, with the change from `from` to `to`
144-- applied." Where the change collides with what is already there, the path's
145-- blob is unresolved rather than an error, except that a collision making a
146-- path a file on one side and a directory on another, as adding ./a/b onto a
147-- file ./a would, crashes, naming the path (§7.3).
148replay : Snapshot -> Change -> Snapshot
149unresolved : Blob -> Bool
151-- Paths edited in the working copy since the last recorded repository state.
152-- `j status` includes this list; `j save` records it in the focused commit.
153-- This measures the working copy, even when composed after an in-memory edit.
154unrecorded : Repo -> [Path]
155touchedPaths : Change -> [Path] -- paths whose content differs, one pass;
156 -- blobs compared by content id, bytes never read
157blob : Text -> Blob -- a resolved regular file
158blobHex : Text -> Text -> Blob -- kind (regular/executable/symlink), hex bytes
159text : Blob -> Text -- its content (markers if unresolved)
160by : Id -> Repo -> Repo -- focus the commit with this id; crash if absent
161lookupCommit : Id -> Repo -> Commit -- indexed commit lookup without moving the focus
162lookupContent : Path -> Snapshot -> Blob -- indexed file content; an empty blob if absent
163subtreeCommits : a -> [Commit] -- every commit of a tree in preorder, in one
164 -- pass: what `commits` (below) is
165meta : Id -> Meta -- git hash, author, email, committer time;
166 -- crash for a commit not yet persisted
167validate : Repo -> Repo -- the repo unchanged, or the crash persisting
168 -- it would produce (shape, root, labels, immutability,
169 -- focus). `tree . validate . rebase trunk` is an
170 -- honest dry run.
171diff : Blob -> Blob -> Text -- unified diff from the first to the second
172difft : Path -> Blob -> Blob -> Text -- difftastic when difft is on PATH; otherwise
173 -- unified diff with path headers and 3 context lines,
174 -- or a binary-files summary. DFT_* configures difft.
175treeWith : { detail : Int, margin : Bool, elide : Bool, icons : Bool, color : Text
176 , lanes : Int, author : Bool, date : Bool, files : Bool }
177 -> Repo -> Text -- the history as a tree; see the spec
179-- Every subvalue of a given shape, however deeply nested. A shape is a type
180-- name used as a value: extract Commit x, extract Id x, extract Push ys.
181extract : Shape -> a -> [b]
183-- Ids: an existing commit is written @kpqx (any unique prefix; checked before
184-- the program runs). A bare @ is a new id, distinct each time it is evaluated.
185-- A label is written %main and is a Revset: the commit the remote calls "main".
188------------------------------------------------------------------------------
189-- Zipper. A Repo is focused on one commit; these move or detach the focus.
190-- Commands never select a commit by position: use ids, labels, or predicates.
191------------------------------------------------------------------------------
193-- To the parent. Crashes at the top.
194up : Edit
195up = \repo ->
196 let f = head repo.context
197 in { root = f.parent
198 , children = f.left ++ [{ root = repo.root, children = repo.children }] ++ f.right
199 , context = tail repo.context }
201-- Detach the focused subtree; the focus moves to the parent. Crashes at the top.
202detach : Repo -> Detached
203detach = \repo ->
204 let f = head repo.context
205 in { subtree = { root = repo.root, children = repo.children }
206 , rest = { root = f.parent, children = f.left ++ f.right, context = tail repo.context } }
208-- To the top of history.
209top : Edit
210top = \repo -> top (up repo) or repo
212-- Every commit of a subtree (or of the focused subtree of a Repo), in preorder.
213-- Takes anything with `root` and `children`, so the signature is left open.
214-- The builtin is \t -> t.root :: (concat (map commits t.children) or [])
215-- in one pass; written that way it copies the list below every commit, and
216-- so takes time quadratic in the length of the history.
217commits : a -> [Commit]
218commits = subtreeCommits
221------------------------------------------------------------------------------
222-- Structure. Editing at the focus.
223------------------------------------------------------------------------------
225mapRoot : (Commit -> Commit) -> Edit
226mapRoot = \f repo -> repo { root = f repo.root }
228mapChildren : ([Subtree] -> [Subtree]) -> Edit
229mapChildren = \f repo -> repo { children = f repo.children }
231-- Attach a subtree as a new child of the focus; the focus does not move.
232attach : Subtree -> Edit
233attach = \t -> mapChildren (++ [t])
235-- Remove the focused commit; its children join the parent. Focus to the parent.
236remove : Edit
237remove = \repo -> let x = detach repo
238 in x.rest { children = x.rest.children ++ x.subtree.children }
240-- Attach a commit as a new child of the focus, and move to it.
241addChild : Commit -> Edit
242addChild = \c repo -> by c.id (attach ({ root = c, children = [] }) repo)
244-- A new commit with these files, no message, no labels, and a new id.
245newCommit : Snapshot -> Commit
246newCommit = \files -> { files = files, message = "", labels = [], id = @ }
249------------------------------------------------------------------------------
250-- Snapshots, changes, and filesets
251------------------------------------------------------------------------------
253-- A path is written ./src/lexer.rs, which is ["src" "lexer.rs"]; ./ alone is
254-- the root, []. For names the literal cannot spell, splitOn "/" "…" builds one.
256restrict : Fileset -> Snapshot -> Snapshot -- entries matching m
257restrict = \m -> filter (\e -> m e.path)
259select : Fileset -> Snapshot -> Snapshot -> Snapshot -- m from a, the rest from b
260select = \m a b -> restrict m a ++ restrict (neg m) b
262-- Unresolved paths in a snapshot. For the focus, inspect `j status` or
263-- `j conflicted . files`, edit the listed files to remove conflict markers,
264-- then run `j save` and inspect `j status` again. See `j help conflicts`.
265conflicted : Snapshot -> [Path]
266conflicted = \s -> map (.path) (filter (\e -> unresolved e.content) s)
268-- What the focus changes: its parent's files, and its own.
269changeOf : Repo -> Change
270changeOf = \repo -> { from = (up repo).root.files or [], to = repo.root.files }
272-- The same change, backwards.
273invert : Change -> Change
274invert = \ch -> { from = ch.to, to = ch.from }
276everything : Fileset
277everything = const true
279neg : Fileset -> Fileset
280neg = \m -> not . m
282both : Fileset -> Fileset -> Fileset
283both = \m n p -> m p && n p
285either : Fileset -> Fileset -> Fileset
286either = \m n p -> m p || n p
288under : Path -> Fileset -- inside directory d
289under = \d p -> take (length d) p == d
291ext : Text -> Fileset -- file extension e
292ext = \e p -> endsWith ("." ++ e) (last p)
295------------------------------------------------------------------------------
296-- Revsets. A revset is a function from a Repo to a list of ids, so it can be
297-- relative to the focus (parents, kids) or absolute (all, %main).
298------------------------------------------------------------------------------
300here : Revset
301here = \repo -> [repo.root.id]
303parents : Revset
304parents = \repo -> [(up repo).root.id] or []
306kids : Revset
307kids = \repo -> map (\t -> t.root.id) repo.children
309descendants : Revset -- includes the focus
310descendants = \repo -> map (.id) (commits repo)
312ancestors : Revset -- includes the focus
313ancestors = \repo -> repo.root.id :: map (\f -> f.parent.id) repo.context
315siblings : Revset
316siblings = \repo -> filter (\i -> i /= repo.root.id) (kids (up repo)) or []
318all : Revset
319all = \repo -> descendants (top repo)
321stack : Revset -- the line through the focus
322stack = union ancestors descendants
324labelled : Label -> Revset -- %main is labelled "main"
325labelled = \n repo -> map (.id) (filter (\c -> member n c.labels) (commits (top repo)))
327-- Evaluate f starting from each commit of rs, and collect.
328from : Revset -> Revset -> Revset
329from = \rs f repo -> concat (map (\i -> f (by i repo)) (rs repo)) or []
331descendantsOf : Revset -> Revset
332descendantsOf = \rs -> from rs descendants
334ancestorsOf : Revset -> Revset
335ancestorsOf = \rs -> from rs ancestors
337union : Revset -> Revset -> Revset
338union = \a b repo -> let xs = a repo in xs ++ filter (\i -> not (member i xs)) (b repo)
340intersect : Revset -> Revset -> Revset
341intersect = \a b repo -> let ys = b repo in filter (\i -> member i ys) (a repo)
343minus : Revset -> Revset -> Revset
344minus = \a b repo -> let ys = b repo in filter (\i -> not (member i ys)) (a repo)
346matching : (Commit -> Bool) -> Revset -> Revset
347matching = \p rs repo -> filter (\i -> p (commitAt i repo)) (rs repo)
349-- Find every commit with unresolved files. `j conflicts` lists candidates;
350-- use `j by @ID` to focus a mutable one, then `j status` to find its paths.
351-- Edit each conflicted file, remove the conflict markers, and run `j save`.
352-- Check `j status` again; `j clean` means the focus has no unresolved files,
353-- not that the working copy has no unrecorded edits. `j review` shows the diff.
354conflicts : Revset
355conflicts = matching (\c -> not (null (conflicted c.files))) all
357-- The first of several revsets that is non-empty.
358firstOf : [Revset] -> Revset
359firstOf = \rss repo -> foldl (\acc rs -> if null acc then rs repo else acc) [] rss
361-- The main line: whichever of these names the remote has, first one wins.
362-- Empty when no fetched remote label is named main, master, or trunk;
363-- nothing is then immutable. Configure `j remote URL`, run `j fetch`, and
364-- inspect `j tree` to see remote labels. A remote with another default label
365-- needs this definition adjusted. Start work with j 'new . goto trunk'.
366trunk : Revset
367trunk = firstOf [%main %master %trunk]
369-- Commits that may never be rewritten, abandoned, or focused. j evaluates this
370-- before persisting anything and refuses to change what it names. Merge
371-- commits and their ancestors are immutable regardless. The focus must always
372-- be mutable, so it is `new . goto trunk`, never `goto trunk` alone.
373immutable : Revset
374immutable = ancestorsOf trunk
377------------------------------------------------------------------------------
378-- Focusing. `goto` is the only place a revset must have exactly one member.
379------------------------------------------------------------------------------
381-- Move to the single commit selected by a revset.
382-- Example: j 'goto %feature'; use j 'new . goto trunk' to start on the main line.
383goto : Revset -> Edit
384goto = \rs repo ->
385 let is = rs repo
386 candidate = \i ->
387 let c = commitAt i repo
388 message = head (splitOn "\n" c.message)
389 in " " ++ show i ++ " " ++ (if message == "" then "(no description)" else message) ++ "\n"
390 in if length is == 1
391 then by (head is) repo
392 else if null is
393 then crash "expected one revision, got 0; no matching revision. Run `j tree` to inspect available revisions. For a missing remote label such as %main or trunk, configure `j remote URL`, run `j fetch`, and check the label names in `j tree`."
394 else crash ("expected one revision, got " ++ show (length is) ++ "\nCandidates:\n" ++ concat (map candidate (take 8 is)) ++ (if length is > 8 then " More candidates: run `j tree`.\n" else "") ++ "Choose a mutable commit with `j by @ID`, or start a child with `j 'new . by @ID'`; replace @ID with a candidate above.")
396commitAt : Id -> Repo -> Commit
397commitAt = lookupCommit
399prev : Edit -- the parent
400prev = goto parents
402-- Move to the only child. At a tip, `j new` starts a child; when there are
403-- several children, choose one of the candidates with `j by @ID`.
404next : Edit
405next = \repo -> if null (kids repo)
406 then crash "next: this commit has no children; run `j new` to start a new child, or `j tree` to inspect the history"
407 else goto kids repo
409tip : Edit -- end of the line of only-children
410tip = \repo -> tip (next repo) or repo
412into : Id -> Edit -- a child, by id
413into = \i repo -> if member i (kids repo) then by i repo
414 else crash "into: not a child of the focus"
416child : (Commit -> Bool) -> Edit -- the one child satisfying p
417child = \p -> goto (matching p kids)
419-- Run an edit somewhere else, then come back (by id, so the edit may move things).
420at : Revset -> Edit -> Edit
421at = \rs e repo -> let me = repo.root.id in by me (e (goto rs repo))
423-- Run an edit at every commit of a revset, returning to the focus.
424forEach : Revset -> Edit -> Edit
425forEach = \rs e repo -> foldl (\acc i -> at (const [i]) e acc) repo (rs repo)
427eachChild : Edit -> Edit
428eachChild = forEach kids
430guard : (Repo -> Bool) -> Edit -- crash unless p holds
431guard = \p repo -> if p repo then repo else crash "guard"
433-- Whether the focused commit has no unresolved files. This says nothing
434-- about unrecorded working-copy edits; inspect those with `j unrecorded`.
435clean : Repo -> Bool
436clean = \repo -> null (conflicted repo.root.files)
439------------------------------------------------------------------------------
440-- Rebasing
441------------------------------------------------------------------------------
443-- Rewrite a subtree as if it had been written on `onto` instead of `from`.
444-- Each commit's change is replayed onto the new base; its children are then
445-- replayed from its old files onto its new ones.
446replayTree : Rebase -> Subtree -> Subtree
447replayTree = \rb t ->
448 let c = t.root
449 c' = c { files = replay rb.onto ({ from = rb.from, to = c.files }) }
450 in { root = c', children = map (replayTree ({ from = c.files, onto = c'.files })) t.children }
452-- Replay the focused subtree onto the commit newP, in place. The subtree stays
453-- attached where it is; only its files change. Move it afterwards with detach
454-- and attach. Crashes at the top.
455replayOnto : Commit -> Edit
456replayOnto = \newP repo ->
457 let t = replayTree ({ from = (up repo).root.files, onto = newP.files })
458 ({ root = repo.root, children = repo.children })
459 in repo { root = t.root, children = t.children }
461-- Replace the focused commit with f applied to it, replaying its children.
462rewrite : (Commit -> Commit) -> Edit
463rewrite = \f repo ->
464 let c = repo.root
465 c' = f c
466 in (eachChild (replayOnto c') repo) { root = c' }
469------------------------------------------------------------------------------
470-- Commands
471------------------------------------------------------------------------------
473-- Record the current working-copy edits in the focused commit.
474-- Example: j save; use j status first to inspect unrecorded paths.
475-- This is id restricted to a Repo: the final Repo result triggers recording.
476-- It does not publish anything. `j new` starts a child; `j id` records too.
477save : Edit
478save = id
480-- Preview an edit and validate that its result could be recorded, then show
481-- the resulting tree. This returns Text, so it records nothing.
482-- Examples: j 'preview squash'; j 'preview (rebase trunk)'.
483-- This helper is in the reference config. Existing custom configs are not
484-- upgraded automatically; add it there or use `tree . validate . EDIT`.
485preview : Edit -> Repo -> Text
486preview = \edit -> tree . validate . edit
488-- Set the message of the focused commit.
489-- Example: j 'describe "fix login"'
490describe : Text -> Edit
491describe = \m -> mapRoot (\c -> c { message = m })
493-- Start an empty child of the focus and move to it.
494-- Examples: j new; j 'describe "next change" . new'
495new : Edit
496new = \repo -> addChild (newCommit repo.root.files) repo
498-- Remove the focused commit. Its children are rebased onto its parent, and a
499-- new empty commit on that parent becomes the focus (the parent may be
500-- immutable, and the focus never is). Refused for a commit the remote has a
501-- name for: push something else under that name first, or forget the name.
502-- Example: j abandon; validate a preview first with j 'preview abandon'.
503abandon : Edit
504abandon = \repo ->
505 let c = repo.root
506 p = (up repo).root
507 in if not (null c.labels)
508 then crash ("abandon: the remote names this commit " ++ show c.labels)
509 else new (remove (eachChild (replayOnto p) repo))
511-- Push the files matching m from the focus down into its parent.
512-- Example: j 'contract (ext "rs")'
513contract : Fileset -> Edit
514contract = \m repo -> let a = repo.root
515 in at parents (rewrite (\q -> q { files = select m a.files q.files })) repo
517-- Fold the focus entirely into its parent; a new empty commit becomes the focus.
518-- Example: j squash; preview first with j 'preview squash'.
519squash : Edit
520squash = abandon . contract everything
522-- Push the files matching m from the focus up into a new child, which becomes
523-- the focus and inherits the old focus's children. Labels stay with the
524-- original commit, since they are the remote's.
525-- Example: j 'split (ext "rs")'; use (under ./src) to select a directory.
526split : Fileset -> Edit
527split = \m repo ->
528 let a = repo.root
529 p = (up repo).root
530 a' = a { files = select m p.files a.files }
531 x = newCommit a.files
532 in by x.id (repo { root = a', children = [{ root = x, children = repo.children }] })
534-- Move the focused subtree onto another commit. Returns to the moved commit.
535-- Example: j 'rebase trunk'; preview with j 'preview (rebase trunk)'.
536rebase : Revset -> Edit
537rebase = \rs repo ->
538 let d = (goto rs repo).root
539 in if member d.id (descendants repo)
540 then crash "rebase: the destination is inside the subtree being moved"
541 else let me = repo.root.id
542 x = detach (replayOnto d repo)
543 in by me (attach x.subtree (by d.id x.rest))
545-- Apply the focused commit's change onto another commit, as a new child there.
546-- Example: j 'pick %main'
547pick : Revset -> Edit
548pick = \rs repo ->
549 let ch = changeOf repo
550 t = (goto rs repo).root
551 in at rs (addChild (newCommit (replay t.files ch))) repo
553-- A new child of the focus that undoes the change of the given commit.
554-- Example: j 'backout %feature'
555backout : Revset -> Edit
556backout = \rs repo ->
557 let ch = changeOf (goto rs repo)
558 in addChild (newCommit (replay repo.root.files (invert ch))) repo
561------------------------------------------------------------------------------
562-- Pushing. `j push EXPR` evaluates EXPR to a list of Push and Drop records.
563------------------------------------------------------------------------------
565-- Label every commit of a revset. `j 'push (label "feature" here)'` labels @.
566label : Label -> Revset -> Repo -> [Push]
567label = \n rs repo -> map (\i -> { id = i, name = n }) (rs repo)
569-- Remove a label on the remote. `j 'push (unlabel "feature")'` after the merge.
570unlabel : Label -> [Drop]
571unlabel = \n -> [{ delete = n }]
573-- Move a label: `j 'push (rename "old" "new")'`.
574rename : Label -> Label -> Repo -> [b]
575rename = \old new repo -> label new (labelled old) repo ++ unlabel old
577-- Every label the remote already has, at its current local commit.
578-- `j push relabel` after rewriting a stack moves the remote's bookmarks along.
579relabel : Repo -> [Push]
580relabel = \repo -> concat (map (\c -> map (\n -> { id = c.id, name = n }) c.labels)
581 (extract Commit repo)) or []
584------------------------------------------------------------------------------
585-- Inspection. These return printable values rather than a Repo.
586------------------------------------------------------------------------------
588focus : Repo -> Commit -- the focused commit
589focus = \repo -> repo.root
591files : Repo -> Snapshot -- its files
592files = \repo -> repo.root.files
594-- The entry at path p in snapshot s, as a list: [] if absent.
595entryAt : Path -> Snapshot -> [Entry]
596entryAt = \p s -> filter (\e -> e.path == p) s
598-- The content at path p, or an empty file if absent.
599contentAt : Path -> Snapshot -> Blob
600contentAt = lookupContent
602-- The paths a change touches.
603touched : Change -> [Path]
604touched = touchedPaths
606-- Paths changed by the focused commit compared with its parent, including
607-- the working-copy snapshot. These can remain nonempty after `j save`;
608-- `j unrecorded` shows only working-copy edits not yet recorded.
609-- Example: j changed
610changed : Repo -> [Path]
611changed = \repo -> touched (changeOf repo)
613-- What the focus changes, one record per changed path. Uses difftastic when
614-- installed; a missing difft falls back to a unified diff with path headers
615-- and 3 context lines, or a binary-files summary.
616diffs : Repo -> [{ path : Path, diff : Text }]
617diffs = \repo ->
618 let ch = changeOf repo
619 in map (\p -> { path = p, diff = difft p (contentAt p ch.from) (contentAt p ch.to) }) (touched ch)
621-- The same, as one page of text. A Text result prints raw, so `j review` is
622-- readable in the terminal. A focus that changes nothing reads "no changes";
623-- missing difft uses the built-in unified diff (path headers, 3 context lines)
624-- or a binary-files summary. Other execution errors remain visible; an
625-- installed difft's output, even nothing, is the page.
626-- Example: j review; difftastic is optional.
627review : Repo -> Text
628review = \repo ->
629 let ds = diffs repo
630 in if null ds then "no changes\n" else concat (map (\d -> d.diff) ds)
632-- A summary of the focus. `changed` lists changes in the commit compared
633-- with its parent; `unrecorded` lists working-copy edits not yet recorded.
634-- Inspection does not record anything. Run `j save` to record those edits.
635-- Example: j status
636status : Repo -> { id : Id, message : Text, labels : [Label], changed : [Path], conflicts : [Path], unrecorded : [Path] }
637status = \repo ->
638 { id = repo.root.id
639 , message = repo.root.message
640 , labels = repo.root.labels
641 , changed = changed repo
642 , conflicts = conflicted repo.root.files
643 , unrecorded = unrecorded repo }
645-- A summary of every commit, top first, in preorder.
646-- Example: j log
647log : Repo -> [{ id : Id, message : Text, labels : [Label], focus : Bool }]
648log = \repo ->
649 map (\c -> { id = c.id
650 , message = c.message
651 , labels = c.labels
652 , focus = c.id == repo.root.id })
653 (commits (top repo))
655-- The history as a tree. Edit these records to change what `j tree` shows.
656-- (The parentheses are required: `f { … }` would be a record update of f.)
657-- The default `tree` shows every commit, sorted by time (parents always above
658-- their children); `treeCompact` folds uninteresting runs and far subtrees.
659-- Examples: j tree; j 'tree . squash' previews an edit without saving it.
660tree : Repo -> Text
661tree = treeWith ({ detail = 1, margin = false, elide = false, icons = false, color = "auto", lanes = 4, author = false, date = false, files = false })
663treeCompact : Repo -> Text
664treeCompact = treeWith ({ detail = 1, margin = false, elide = true, icons = false, color = "auto", lanes = 4, author = false, date = false, files = false })
666treeFull : Repo -> Text
667treeFull = treeWith ({ detail = 2, margin = true, elide = false, icons = false, color = "auto", lanes = 4, author = false, date = false, files = false })
669-- The full tree plus the extra data columns: date, files changed, author.
670treeData : Repo -> Text
671treeData = treeWith ({ detail = 2, margin = true, elide = false, icons = false, color = "auto", lanes = 4, author = true, date = true, files = true })