src/lock/up.bend
ezx@1.11.0.0 · 1,291 lines · 140 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import Base
23import ezx@1.11.0.0/src/lock/lock.bend as L
24import ezx@1.11.0.0/src/ledger/manifest.bend as M
25import ezx@1.11.0.0/src/ledger/render.bend as Rend
26import ezx@1.11.0.0/src/ledger/upgrade.bend as U
27import ezx@1.11.0.0/src/ledger/ignore.bend as I
28import ezx@1.11.0.0/src/git/git.bend as Git
29import ezx@1.11.0.0/src/pkg/pkg.bend as K
30import ezx@1.11.0.0/src/pkg/path.bend as Path
31import ezx@1.11.0.0/src/hub/hub.bend as Web
32import ezx@1.11.0.0/src/share/sha.bend as Sha
33import ezx@1.11.0.0/src/share/say.bend as Say
34
35
36
37
38
39
40
41
42
43
44
45type Ask is Data:
46 Refs{url: String, ref: String}
47 Head{url: String}
48 Tags{url: String}
49 Above{url: String, pin: String, tip: String}
50 Tree{url: String, rev: String, entry: String}
51 Weigh{url: String, rev: String}
52 HubHas{hub: String, hash: String}
53 Ignore{}
54 Sources{}
55 Clone{url: String, rev: String}
56
57
58def ask.url(ask: Ask) -> String:
59 match ask:
60 case Refs{url, _ref}:
61 url
62 case Head{url}:
63 url
64 case Tags{url}:
65 url
66 case Above{url, _pin, _tip}:
67 url
68 case Tree{url, _rev, _entry}:
69 url
70 case Weigh{url, _rev}:
71 url
72 case HubHas{_hub, _hash}:
73 ""
74 case Ignore{}:
75 ""
76 case Sources{}:
77 ""
78 case Clone{url, _rev}:
79 url
80
81
82
83
84
85
86def anchor(+here: String, ask: Ask) -> Ask:
87 match ask:
88 case Refs{url, ref}:
89 Refs{Path.anchor(here, url), ref}
90 case Head{url}:
91 Head{Path.anchor(here, url)}
92 case Tags{url}:
93 Tags{Path.anchor(here, url)}
94 case Above{url, pin, tip}:
95 Above{Path.anchor(here, url), pin, tip}
96 case Tree{url, rev, entry}:
97 Tree{Path.anchor(here, url), rev, entry}
98 case Weigh{url, rev}:
99 Weigh{Path.anchor(here, url), rev}
100 case HubHas{hub, hash}:
101 HubHas{hub, hash}
102 case Ignore{}:
103 Ignore{}
104 case Sources{}:
105 Sources{}
106 case Clone{url, rev}:
107 Clone{Path.anchor(here, url), rev}
108
109
110type Found is Data:
111 Found{at: String, text: String}
112
113
114
115type Answer is Data:
116 Printed{ok: Bool, text: String}
117 Onward{yes: Bool}
118 Checkout{nar: String, root: String, manifest: String, srcs: List<&2, String>}
119 Weighed{nar: String}
120 HubSaid{got: Web.Got}
121 Plain{text: String}
122 Files{files: List<&2, Found>}
123 Cloned{nar: String, files: List<&2, K.Source>}
124 Miss{why: String}
125
126
127type Reply is Data:
128 Reply{ask: Ask, answer: Answer}
129
130
131
132type Laid is Data:
133 Laid{hash: String, nar: String, manifest: String, srcs: List<&2, String>}
134
135
136type Edit is Data:
137 Edit{at: String, text: String}
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153type Next is Data:
154 Next{model: M.Read, read: M.Read, stop: String, asks: List<&2, Ask>, trees: List<&2, Laid>,
155 vends: List<&2, String>, says: List<&2, String>, edits: List<&2, Edit>,
156 found: List<&2, Found>, drops: List<&2, String>, swaps: List<&2, U.Swap>}
157
158def next.model(nx: Next) -> M.Read:
159 Next{model, _read, _stop, _asks, _trees, _vends, _says, _edits, _found, _drops, _swaps} = nx
160 model
161
162def next.read(nx: Next) -> M.Read:
163 Next{_model, read, _stop, _asks, _trees, _vends, _says, _edits, _found, _drops, _swaps} = nx
164 read
165
166def next.stop(nx: Next) -> String:
167 Next{_model, _read, stop, _asks, _trees, _vends, _says, _edits, _found, _drops, _swaps} = nx
168 stop
169
170def next.asks(nx: Next) -> List<&2, Ask>:
171 Next{_model, _read, _stop, asks, _trees, _vends, _says, _edits, _found, _drops, _swaps} = nx
172 asks
173
174def next.trees(nx: Next) -> List<&2, Laid>:
175 Next{_model, _read, _stop, _asks, trees, _vends, _says, _edits, _found, _drops, _swaps} = nx
176 trees
177
178def next.vends(nx: Next) -> List<&2, String>:
179 Next{_model, _read, _stop, _asks, _trees, vends, _says, _edits, _found, _drops, _swaps} = nx
180 vends
181
182def next.says(nx: Next) -> List<&2, String>:
183 Next{_model, _read, _stop, _asks, _trees, _vends, says, _edits, _found, _drops, _swaps} = nx
184 says
185
186def next.edits(nx: Next) -> List<&2, Edit>:
187 Next{_model, _read, _stop, _asks, _trees, _vends, _says, edits, _found, _drops, _swaps} = nx
188 edits
189
190def next.found(nx: Next) -> List<&2, Found>:
191 Next{_model, _read, _stop, _asks, _trees, _vends, _says, _edits, found, _drops, _swaps} = nx
192 found
193
194def next.drops(nx: Next) -> List<&2, String>:
195 Next{_model, _read, _stop, _asks, _trees, _vends, _says, _edits, _found, drops, _swaps} = nx
196 drops
197
198def next.swaps(nx: Next) -> List<&2, U.Swap>:
199 Next{_model, _read, _stop, _asks, _trees, _vends, _says, _edits, _found, _drops, swaps} = nx
200 swaps
201
202
203def plain(+read: M.Read) -> Next:
204 Next{read, read, "", [], [], [], [], [], [], [], []}
205
206
207
208
209
210def halt.why(+why: String) -> String:
211 Bool.pick(String, String.is_empty(why), "ez: the upgrade stopped without a reason", why)
212
213
214
215def stopped(+read: M.Read, +why: String, asks: List<&2, Ask>) -> Next:
216 Next{read, read, halt.why(why), asks, [], [], [], [], [], [], []}
217
218
219
220
221
222
223def key(ask: Ask) -> String:
224 match ask:
225 case Refs{url, ref}:
226 "refs\n" ++ url ++ "\n" ++ ref
227 case Head{url}:
228 "head\n" ++ url
229 case Tags{url}:
230 "tags\n" ++ url
231 case Above{url, pin, tip}:
232 "above\n" ++ url ++ "\n" ++ pin ++ "\n" ++ tip
233 case Tree{url, rev, entry}:
234 "tree\n" ++ url ++ "\n" ++ rev ++ "\n" ++ entry
235 case Weigh{url, rev}:
236 "weigh\n" ++ url ++ "\n" ++ rev
237 case HubHas{hub, hash}:
238 "hub\n" ++ hub ++ "\n" ++ hash
239 case Ignore{}:
240 "ignore"
241 case Sources{}:
242 "sources"
243 case Clone{url, rev}:
244 "clone\n" ++ url ++ "\n" ++ rev
245
246
247type Heard is Data:
248 Heard{answer: Answer}
249 Open{}
250
251
252
253def look.step(hit: Bool, answer: Answer, rest: Unit -> Heard) -> Heard:
254 match hit:
255 case True{}:
256 Heard{answer}
257 case False{}:
258 rest(Unit{})
259
260
261def look(rs: List<&2, Reply>, +want: String) -> Heard:
262 match rs:
263 case []:
264 Open{}
265 case Reply{ask, answer} <> t:
266 look.step(String.eq(key(ask), want), answer, _u => look(t, want))
267
268
269def heard(rs: List<&2, Reply>, ask: Ask) -> Heard:
270 look(rs, key(ask))
271
272
273
274type Said is Data:
275 Said{text: String}
276 Unsaid{why: String}
277 Unasked{ask: Ask}
278
279def printed.ok(ok: Bool, +what: String, text: String) -> Said:
280 match ok:
281 case True{}:
282 Said{text}
283 case False{}:
284 Unsaid{"ez: git " ++ what ++ ": " ++ String.trim(text)}
285
286def printed.of(answer: Answer, +what: String) -> Said:
287 match answer:
288 case Printed{ok, text}:
289 printed.ok(ok, what, text)
290 case Miss{why}:
291 Unsaid{why}
292 case _:
293 Unsaid{"ez: git " ++ what ++ " was not answered with what git printed"}
294
295
296def printed.heard(got: Heard, ask: Ask, +what: String) -> Said:
297 match got:
298 case Open{}:
299 Unasked{ask}
300 case Heard{answer}:
301 printed.of(answer, what)
302
303
304def printed(rs: List<&2, Reply>, +ask: Ask, +what: String) -> Said:
305 printed.heard(heard(rs, ask), ask, what)
306
307
308def text.of(answer: Answer) -> Said:
309 match answer:
310 case Plain{text}:
311 Said{text}
312 case Miss{why}:
313 Unsaid{why}
314 case _:
315 Unsaid{"ez: a file was not answered with its text"}
316
317def text.said.heard(got: Heard, ask: Ask) -> Said:
318 match got:
319 case Open{}:
320 Unasked{ask}
321 case Heard{answer}:
322 text.of(answer)
323
324
325def text.said(rs: List<&2, Reply>, +ask: Ask) -> Said:
326 text.said.heard(heard(rs, ask), ask)
327
328
329type Co is Data:
330 Co{nar: String, root: String, manifest: String, srcs: List<&2, String>}
331 CoWait{ask: Ask}
332 CoNo{why: String}
333
334def checkout.of(answer: Answer) -> Co:
335 match answer:
336 case Checkout{nar, root, manifest, srcs}:
337 Co{nar, root, manifest, srcs}
338 case Miss{why}:
339 CoNo{why}
340 case _:
341 CoNo{"ez: a checkout was not answered with one"}
342
343
344def checkout.heard(got: Heard, ask: Ask) -> Co:
345 match got:
346 case Open{}:
347 CoWait{ask}
348 case Heard{answer}:
349 checkout.of(answer)
350
351
352def checkout(rs: List<&2, Reply>, +ask: Ask) -> Co:
353 checkout.heard(heard(rs, ask), ask)
354
355
356type Asc is Data:
357 Asc{yes: Bool}
358 AscWait{ask: Ask}
359 AscNo{why: String}
360
361def onward.of(answer: Answer) -> Asc:
362 match answer:
363 case Onward{yes}:
364 Asc{yes}
365 case Miss{why}:
366 AscNo{why}
367 case _:
368 AscNo{"ez: ancestry was not answered with whether it holds"}
369
370
371def onward.heard(got: Heard, ask: Ask) -> Asc:
372 match got:
373 case Open{}:
374 AscWait{ask}
375 case Heard{answer}:
376 onward.of(answer)
377
378
379def onward(rs: List<&2, Reply>, +ask: Ask) -> Asc:
380 onward.heard(heard(rs, ask), ask)
381
382
383type Wd is Data:
384 Wd{nar: String}
385 WdWait{ask: Ask}
386 WdNo{why: String}
387
388def weighed.of(answer: Answer) -> Wd:
389 match answer:
390 case Weighed{nar}:
391 Wd{nar}
392 case Miss{why}:
393 WdNo{why}
394 case _:
395 WdNo{"ez: a weighing was not answered with a NAR hash"}
396
397
398def weighed.heard(got: Heard, ask: Ask) -> Wd:
399 match got:
400 case Open{}:
401 WdWait{ask}
402 case Heard{answer}:
403 weighed.of(answer)
404
405
406def weighed(rs: List<&2, Reply>, +ask: Ask) -> Wd:
407 weighed.heard(heard(rs, ask), ask)
408
409
410
411type Hubbed is Data:
412 Clear{}
413 Clash{}
414 HubWait{ask: Ask}
415 HubNo{why: String}
416
417def hub.got(got: Web.Got) -> Hubbed:
418 match got:
419 case Web.Have{_body}:
420 Clear{}
421 case Web.Miss{why}:
422 Bool.pick(Hubbed, U.hub.bad(why), Clash{}, Clear{})
423
424def hub.of(answer: Answer) -> Hubbed:
425 match answer:
426 case HubSaid{got}:
427 hub.got(got)
428 case Miss{why}:
429 HubNo{why}
430 case _:
431 HubNo{"ez: the hub was not answered with what it served"}
432
433
434def hubbed.heard(got: Heard, ask: Ask) -> Hubbed:
435 match got:
436 case Open{}:
437 HubWait{ask}
438 case Heard{answer}:
439 hub.of(answer)
440
441
442def hubbed(rs: List<&2, Reply>, +ask: Ask) -> Hubbed:
443 hubbed.heard(heard(rs, ask), ask)
444
445
446type Srcs is Data:
447 Srcs{files: List<&2, Found>}
448 SrcsWait{ask: Ask}
449 SrcsNo{why: String}
450
451def sources.of(answer: Answer) -> Srcs:
452 match answer:
453 case Files{fs}:
454 Srcs{fs}
455 case Miss{why}:
456 SrcsNo{why}
457 case _:
458 SrcsNo{"ez: the sources were not answered with their texts"}
459
460
461def sources.heard(got: Heard, ask: Ask) -> Srcs:
462 match got:
463 case Open{}:
464 SrcsWait{ask}
465 case Heard{answer}:
466 sources.of(answer)
467
468
469def sources(rs: List<&2, Reply>, +ask: Ask) -> Srcs:
470 sources.heard(heard(rs, ask), ask)
471
472
473
474
475
476
477
478type Tip is Data:
479 At{rev: String, tag: String, branch: String}
480 TipWait{ask: Ask}
481 TipNo{why: String}
482
483
484
485def follow.rev(+rev: String, +url: String, +ref: String) -> Tip:
486 Bool.pick(Tip, String.is_empty(rev), TipNo{"ez: " ++ url ++ " has no ref named " ++ ref},
487 At{rev, ref, ""})
488
489def follow.said(said: Said, +url: String, +ref: String) -> Tip:
490 match said:
491 case Said{text}:
492 follow.rev(Git.exact(Git.rows(String.lines(text)), ref), url, ref)
493 case Unsaid{why}:
494 TipNo{why}
495 case Unasked{ask}:
496 TipWait{ask}
497
498def follow.ask(pinned: Bool, +rs: List<&2, Reply>, +url: String, +ref: String) -> Tip:
499 match pinned:
500 case True{}:
501 At{ref, "", ""}
502 case False{}:
503 follow.said(printed(rs, Refs{url, ref}, "ls-remote " ++ url), url, ref)
504
505
506
507def follow(+rs: List<&2, Reply>, +url: String, +ref: String) -> Tip:
508 follow.ask(Git.is_rev(ref), rs, url, ref)
509
510
511def forward.rev(+rev: String, branch: String, +url: String) -> Tip:
512 Bool.pick(Tip, String.is_empty(rev), TipNo{"ez: " ++ url ++ " has no HEAD"},
513 At{rev, "", branch})
514
515def forward.said(said: Said, +url: String) -> Tip:
516 match said:
517 case Said{text}:
518 +rows = Git.rows(String.lines(text))
519 forward.rev(Git.tip.rev(rows), Git.tip.branch(rows), url)
520 case Unsaid{why}:
521 TipNo{why}
522 case Unasked{ask}:
523 TipWait{ask}
524
525
526def forward(+rs: List<&2, Reply>, +url: String) -> Tip:
527 forward.said(printed(rs, Head{url}, "ls-remote " ++ url), url)
528
529
530def tip.of(+branch: String, +tip: String) -> String:
531 Bool.pick(String, String.is_empty(branch), tip, branch)
532
533
534
535def off.why(+name: String, +tag: String, +pin: String, +tip: String) -> String:
536 Bool.pick(String, String.is_empty(tag),
537 "ez: " ++ name ++ ": " ++ pin ++ " is not an ancestor of " ++ tip,
538 "ez: " ++ name ++ ": tag " ++ tag ++ " moved off " ++ pin)
539
540
541def branch.of(+label: String) -> String:
542 Bool.pick(String, String.is_empty(label), "", " (" ++ label ++ ")")
543
544
545def say.move(+name: String, +old: String, +new: String, +branch: String) -> String:
546 name ++ ": " ++ old ++ " -> " ++ new ++ branch.of(branch)
547
548
549def hash.of(+manifest: String) -> String:
550 "0x" ++ String.take(Sha.hex(manifest), 32n)
551
552
553def hash.kept.or(same: Bool, +bare: String, +full: String) -> String:
554 match same:
555 case True{}:
556 bare
557 case False{}:
558 full
559
560def hash.kept.pick(same: Bool, +pin: String, +full: String, +manifest: String) -> String:
561 match same:
562 case True{}:
563 full
564 case False{}:
565 +bare = hash.of(K.manifest.lines(K.bare(L.manifest.files(String.lines(manifest)))))
566 hash.kept.or(String.eq(bare, pin), bare, full)
567
568
569
570
571
572def hash.kept(+pin: String, +manifest: String) -> String:
573 +full = hash.of(manifest)
574 hash.kept.pick(String.eq(full, pin), pin, full, manifest)
575
576
577
578
579
580
581
582
583
584type Fate is Data:
585 Became{dep: M.Dep, old: String, new: String, prev: String, moved: Bool,
586 says: List<&2, String>, trees: List<&2, Laid>}
587 Waits{asks: List<&2, Ask>}
588 Halts{why: String}
589
590
591def stay(dep: M.Dep, says: List<&2, String>) -> Fate:
592 Became{dep, "", "", "", False{}, says, []}
593
594
595def swap.hash(same: Bool, +old: String) -> String:
596 match same:
597 case True{}:
598 ""
599 case False{}:
600 old
601
602
603def swap.of(vend: Bool, +old: String, +new: String) -> String:
604 match vend:
605 case False{}:
606 ""
607 case True{}:
608 swap.hash(String.eq(old, new), old)
609
610
611def line.if(say: Bool, +text: String) -> List<&2, String>:
612 Bool.pick(List<&2, String>, say, [text], [])
613
614
615
616
617def dep.moved(
618 +dep: M.Dep,
619 +hash: String,
620 +rev: String,
621 +root: String,
622 +nar: String,
623 manifest: String,
624 srcs: List<&2, String>,
625 +branch: String
626) -> Fate:
627 +src = M.source.dep(dep)
628 +name = M.dep.name(dep)
629 +old = M.dep.hash(dep)
630 +drop = swap.of(M.source.vendor(src), old, hash)
631 +prev = swap.hash(String.eq(old, hash), old)
632 Became{U.retarget(dep, rev, hash, root, nar), drop, hash, prev, True{},
633 say.move(name, M.source.rev(src), rev, branch) <>
634 List.append(&2, String,
635 line.if(Bool.not(String.is_empty(prev)), name ++ ": imports " ++ old ++ " -> " ++ hash),
636 line.if(Bool.not(String.is_empty(drop)), name ++ ": vendored .ez/lib/" ++ hash)),
637 [Laid{hash, nar, manifest, srcs}]}
638
639
640def dep.hub(
641 hubbed: Hubbed,
642 +dep: M.Dep,
643 +hash: String,
644 +rev: String,
645 +root: String,
646 +nar: String,
647 manifest: String,
648 srcs: List<&2, String>,
649 +branch: String
650) -> Fate:
651 match hubbed:
652 case Clear{}:
653 dep.moved(dep, hash, rev, root, nar, manifest, srcs, branch)
654 case Clash{}:
655 Halts{"ez: " ++ M.dep.name(dep) ++ ": hub digest does not match " ++ hash}
656 case HubWait{ask}:
657 Waits{[ask]}
658 case HubNo{why}:
659 Halts{why}
660
661
662
663def dep.advance(co: Co, +rs: List<&2, Reply>, +hub: String, +dep: M.Dep, +rev: String, +branch: String) -> Fate:
664 match co:
665 case Co{nar, root, +manifest, srcs}:
666 +hash = hash.of(manifest)
667 dep.hub(hubbed(rs, HubHas{hub, hash}), dep, hash, rev, root, nar, manifest, srcs,
668 branch)
669 case CoWait{ask}:
670 Waits{[ask]}
671 case CoNo{why}:
672 Halts{why}
673
674
675
676def dep.verdict(
677 verdict: U.Verdict,
678 +rs: List<&2, Reply>,
679 +hub: String,
680 +dep: M.Dep,
681 +tip: String,
682 +branch: String,
683 +seen: String
684) -> Fate:
685 match verdict:
686 case U.Keep{}:
687 stay(dep, [M.dep.name(dep) ++ ": unchanged " ++ M.source.rev(M.source.dep(dep))])
688 case U.Drift{}:
689 +src = M.source.dep(dep)
690 Halts{Say.drift(M.dep.name(dep), M.source.tag(src), M.source.rev(src),
691 M.dep.hash(dep), seen)}
692 case U.Off{}:
693 +src = M.source.dep(dep)
694 Halts{off.why(M.dep.name(dep), M.source.tag(src), M.source.rev(src),
695 tip.of(branch, tip))}
696 case U.Advance{+rev, _tag}:
697 dep.advance(checkout(rs, Tree{M.source.url(M.source.dep(dep)), rev, M.dep.entry(dep)}), rs, hub, dep, rev, branch)
698
699
700
701def dep.kept(co: Co, +rs: List<&2, Reply>, +hub: String, +dep: M.Dep, +tip: String, +tag: String) -> Fate:
702 match co:
703 case Co{nar, root, +manifest, _srcs}:
704 +src = M.source.dep(dep)
705 +hash = hash.kept(M.dep.hash(dep), manifest)
706 dep.verdict(U.judge(True{}, True{},
707 U.agree(M.dep.hash(dep), hash, M.source.nar(src), nar, M.source.root(src), root),
708 tip, tag), rs, hub, dep, tip, "", hash)
709 case CoWait{ask}:
710 Waits{[ask]}
711 case CoNo{why}:
712 Halts{why}
713
714
715
716def dep.onward(
717 asc: Asc,
718 +rs: List<&2, Reply>,
719 +hub: String,
720 +dep: M.Dep,
721 +tip: String,
722 +tag: String,
723 +branch: String
724) -> Fate:
725 match asc:
726 case Asc{yes}:
727 dep.verdict(U.judge(False{}, yes, False{}, tip, tag), rs, hub, dep, tip,
728 branch, "")
729 case AscWait{ask}:
730 Waits{[ask]}
731 case AscNo{why}:
732 Halts{why}
733
734
735
736def dep.same(
737 same: Bool,
738 +rs: List<&2, Reply>,
739 +hub: String,
740 +dep: M.Dep,
741 +tip: String,
742 +tag: String,
743 +branch: String
744) -> Fate:
745 match same:
746 case True{}:
747 +src = M.source.dep(dep)
748 dep.kept(checkout(rs, Tree{M.source.url(src), M.source.rev(src), M.dep.entry(dep)}), rs, hub, dep, tip, tag)
749 case False{}:
750 +src = M.source.dep(dep)
751 dep.onward(onward(rs, Above{M.source.url(src), M.source.rev(src), tip}), rs, hub, dep, tip, tag, branch)
752
753
754def dep.tip(tip: Tip, +rs: List<&2, Reply>, +hub: String, +dep: M.Dep) -> Fate:
755 match tip:
756 case At{+rev, tag, branch}:
757 dep.same(String.eq(M.source.rev(M.source.dep(dep)), rev), rs, hub, dep, rev,
758 tag, branch)
759 case TipWait{ask}:
760 Waits{[ask]}
761 case TipNo{why}:
762 Halts{why}
763
764
765
766def held.why(src: M.Source) -> String:
767 match src:
768 case M.Hub{_nv}:
769 ": pinned by its hash"
770 case M.Git{_url, _rev, _tag, _root, _nar, _vend}:
771 ": pinned by its hash"
772 case M.Member{_path, _nv}:
773 ": a workspace member, locked from its committed files"
774
775
776
777def dep.hold(sel: Bool, +dep: M.Dep) -> Fate:
778 stay(dep, line.if(sel, M.dep.name(dep) ++ held.why(M.source.dep(dep))))
779
780
781def dep.one(aim: U.Aim, +rs: List<&2, Reply>, +only: String, +hub: String, +dep: M.Dep) -> Fate:
782 match aim:
783 case U.Hold{}:
784 dep.hold(U.chosen(only, M.dep.name(dep)), dep)
785 case U.Follow{ref}:
786 dep.tip(follow(rs, M.source.url(M.source.dep(dep)), ref), rs, hub, dep)
787 case U.Forward{}:
788 dep.tip(forward(rs, M.source.url(M.source.dep(dep))), rs, hub, dep)
789
790
791
792
793
794type Deps is Data:
795 Deps{deps: List<&2, M.Dep>, swaps: List<&2, U.Swap>, edits: List<&2, U.Swap>,
796 moved: Bool, says: List<&2, String>, trees: List<&2, Laid>,
797 asks: List<&2, Ask>, bad: String}
798
799
800def swaps.put(empty: Bool, +old: String, +new: String, rest: List<&2, U.Swap>) -> List<&2, U.Swap>:
801 match empty:
802 case True{}:
803 rest
804 case False{}:
805 U.Swap{old, new} <> rest
806
807
808
809def deps.join(fate: Fate, rest: Deps) -> Deps:
810 match fate:
811 case Became{d, +old, +new, +prev, m, says, trees}:
812 Deps{ds, ss, es, any, sy, tr, asks, bad} = rest
813 Deps{d <> ds, swaps.put(String.is_empty(old), old, new, ss),
814 swaps.put(String.is_empty(prev), prev, new, es), Bool.or(m, any),
815 List.append(&2, String, says, sy), List.append(&2, Laid, trees, tr), asks, bad}
816 case Waits{more}:
817 Deps{ds, ss, es, any, sy, tr, asks, bad} = rest
818 Deps{ds, ss, es, any, sy, tr, List.append(&2, Ask, more, asks), bad}
819 case Halts{why}:
820 Deps{ds, ss, es, any, sy, tr, asks, _bad} = rest
821 Deps{ds, ss, es, any, sy, tr, asks, halt.why(why)}
822
823
824def deps.walk(+rs: List<&2, Reply>, +only: String, +hub: String, ds: List<&2, M.Dep>) -> Deps:
825 match ds:
826 case []:
827 Deps{[], [], [], False{}, [], [], [], ""}
828 case +h <> t:
829 deps.join(dep.one(U.aim(U.chosen(only, M.dep.name(h)), M.source.dep(h)), rs,
830 only, hub, h), deps.walk(rs, only, hub, t))
831
832
833
834
835
836
837type Pinned is Data:
838 Pinned{tool: M.Tool, moved: Bool, says: List<&2, String>}
839 PinWait{asks: List<&2, Ask>}
840 PinHalt{why: String}
841
842
843
844
845def set.go(
846 +name: String,
847 +entry: String,
848 +bin: String,
849 source: M.Source,
850 +rev: String,
851 +tag: String,
852 +nar: String
853) -> M.Tool:
854 match source:
855 case M.Hub{named}:
856 M.Tool{name, entry, bin, M.Hub{named}}
857 case M.Git{url, _rev, old, root, _nar, _vend}:
858 M.Tool{name, entry, bin, M.Git{url, rev,
859 Bool.pick(String, String.is_empty(tag), old, tag), M.root.or(root),
860 nar, False{}}}
861 case M.Member{path, named}:
862 M.Tool{name, entry, bin, M.Member{path, named}}
863
864
865def set(tool: M.Tool, +rev: String, +tag: String, +nar: String) -> M.Tool:
866 M.Tool{n, e, b, s} = tool
867 set.go(n, e, b, s, rev, tag, nar)
868
869
870
871
872def tool.pin(short: Bool, +tool: M.Tool, +rev: String, +tag: String, +nar: String, says: List<&2, String>) -> Pinned:
873 match short:
874 case True{}:
875 PinHalt{"ez: tools." ++ M.tool.name(tool) ++ ": weighing " ++ rev ++ " gave no narHash"}
876 case False{}:
877 Pinned{set(tool, rev, tag, nar), True{}, says}
878
879
880def tool.weighed(wd: Wd, +tool: M.Tool, +rev: String, +tag: String, says: List<&2, String>) -> Pinned:
881 match wd:
882 case Wd{+nar}:
883 tool.pin(Bool.or(String.is_empty(rev), String.is_empty(nar)), tool, rev, tag, nar, says)
884 case WdWait{ask}:
885 PinWait{[ask]}
886 case WdNo{why}:
887 PinHalt{why}
888
889
890
891def tool.resolved(tip: Tip, +rs: List<&2, Reply>, +tool: M.Tool, +ref: String) -> Pinned:
892 match tip:
893 case At{+rev, _tag, _branch}:
894 tool.weighed(weighed(rs, Weigh{M.source.url(M.tool.source(tool)), rev}), tool, rev, ref, [])
895 case TipWait{ask}:
896 PinWait{[ask]}
897 case TipNo{why}:
898 PinHalt{why}
899
900
901type Named is Data:
902 Named{ref: String}
903 NamedWait{ask: Ask}
904 NamedNo{why: String}
905
906
907def heads.branch(+name: String, +url: String) -> Named:
908 Bool.pick(Named, String.is_empty(name),
909 NamedNo{"ez: " ++ url ++
910 " has no semver-ish tag and its HEAD names no branch; name a ref to pin"},
911 Named{name})
912
913def heads.said(said: Said, +url: String) -> Named:
914 match said:
915 case Said{text}:
916 heads.branch(Git.tip.branch(Git.rows(String.lines(text))), url)
917 case Unsaid{why}:
918 NamedNo{why}
919 case Unasked{ask}:
920 NamedWait{ask}
921
922
923def tags.best(none: Bool, +best: String, +rs: List<&2, Reply>, +url: String) -> Named:
924 match none:
925 case False{}:
926 Named{best}
927 case True{}:
928 heads.said(printed(rs, Head{url}, "ls-remote " ++ url), url)
929
930def tags.said(said: Said, +rs: List<&2, Reply>, +url: String) -> Named:
931 match said:
932 case Said{text}:
933 +best = Git.choose(Git.tag.names(Git.rows(String.lines(text))))
934 tags.best(String.is_empty(best), best, rs, url)
935 case Unsaid{why}:
936 NamedNo{why}
937 case Unasked{ask}:
938 NamedWait{ask}
939
940
941
942def tool.default(+rs: List<&2, Reply>, +url: String) -> Named:
943 tags.said(printed(rs, Tags{url}, "ls-remote " ++ url), rs, url)
944
945
946def tool.ref(no_tag: Bool, +rs: List<&2, Reply>, +url: String, +tag: String) -> Named:
947 match no_tag:
948 case False{}:
949 Named{tag}
950 case True{}:
951 tool.default(rs, url)
952
953def tool.named(named: Named, +rs: List<&2, Reply>, +tool: M.Tool, +url: String) -> Pinned:
954 match named:
955 case Named{+ref}:
956 tool.resolved(follow(rs, url, ref), rs, tool, ref)
957 case NamedWait{ask}:
958 PinWait{[ask]}
959 case NamedNo{why}:
960 PinHalt{why}
961
962
963
964
965def tool.verdict(verdict: U.Verdict, +rs: List<&2, Reply>, +tool: M.Tool, +tip: String, +branch: String) -> Pinned:
966 match verdict:
967 case U.Keep{}:
968 Pinned{tool, False{}, [M.tool.name(tool) ++ ": unchanged " ++
969 M.source.rev(M.tool.source(tool))]}
970 case U.Drift{}:
971 PinHalt{"ez: " ++ M.tool.name(tool) ++ ": narHash drifted at " ++
972 M.source.rev(M.tool.source(tool))}
973 case U.Off{}:
974 +src = M.tool.source(tool)
975 PinHalt{off.why(M.tool.name(tool), M.source.tag(src), M.source.rev(src),
976 tip.of(branch, tip))}
977 case U.Advance{+rev, _tag}:
978 +src = M.tool.source(tool)
979 tool.weighed(weighed(rs, Weigh{M.source.url(src), rev}), tool, rev, "",
980 [say.move(M.tool.name(tool), M.source.rev(src), rev, branch)])
981
982
983
984def tool.kept.nar(fill: Bool, +rs: List<&2, Reply>, +tool: M.Tool, +tip: String, +tag: String, +nar: String) -> Pinned:
985 match fill:
986 case True{}:
987 tool.pin(Bool.or(String.is_empty(tip), String.is_empty(nar)), tool, tip, "", nar, [])
988 case False{}:
989 tool.verdict(U.judge(True{}, True{},
990 String.eq(M.source.nar(M.tool.source(tool)), nar), tip, tag), rs, tool, tip, "")
991
992
993def tool.kept(wd: Wd, +rs: List<&2, Reply>, +tool: M.Tool, +tip: String, +tag: String) -> Pinned:
994 match wd:
995 case Wd{+nar}:
996 tool.kept.nar(String.is_empty(M.source.nar(M.tool.source(tool))), rs, tool, tip, tag, nar)
997 case WdWait{ask}:
998 PinWait{[ask]}
999 case WdNo{why}:
1000 PinHalt{why}
1001
1002def tool.onward(asc: Asc, +rs: List<&2, Reply>, +tool: M.Tool, +tip: String, +tag: String, +branch: String) -> Pinned:
1003 match asc:
1004 case Asc{yes}:
1005 tool.verdict(U.judge(False{}, yes, False{}, tip, tag), rs, tool, tip, branch)
1006 case AscWait{ask}:
1007 PinWait{[ask]}
1008 case AscNo{why}:
1009 PinHalt{why}
1010
1011def tool.same(same: Bool, +rs: List<&2, Reply>, +tool: M.Tool, +tip: String, +tag: String, +branch: String) -> Pinned:
1012 match same:
1013 case True{}:
1014 +src = M.tool.source(tool)
1015 tool.kept(weighed(rs, Weigh{M.source.url(src), M.source.rev(src)}), rs, tool, tip, tag)
1016 case False{}:
1017 +src = M.tool.source(tool)
1018 tool.onward(onward(rs, Above{M.source.url(src), M.source.rev(src), tip}), rs, tool, tip, tag, branch)
1019
1020def tool.tip(tip: Tip, +rs: List<&2, Reply>, +tool: M.Tool) -> Pinned:
1021 match tip:
1022 case At{+rev, tag, branch}:
1023 tool.same(String.eq(M.source.rev(M.tool.source(tool)), rev), rs, tool, rev,
1024 tag, branch)
1025 case TipWait{ask}:
1026 PinWait{[ask]}
1027 case TipNo{why}:
1028 PinHalt{why}
1029
1030
1031def tool.aim(aim: U.Aim, +rs: List<&2, Reply>, +tool: M.Tool) -> Pinned:
1032 match aim:
1033 case U.Hold{}:
1034 Pinned{tool, False{}, []}
1035 case U.Follow{ref}:
1036 tool.tip(follow(rs, M.source.url(M.tool.source(tool)), ref), rs, tool)
1037 case U.Forward{}:
1038 tool.tip(forward(rs, M.source.url(M.tool.source(tool))), rs, tool)
1039
1040
1041
1042
1043
1044
1045def tool.fill(no_rev: Bool, +rs: List<&2, Reply>, +tool: M.Tool) -> Pinned:
1046 match no_rev:
1047 case True{}:
1048 +src = M.tool.source(tool)
1049 +url = M.source.url(src)
1050 tool.named(tool.ref(String.is_empty(M.source.tag(src)), rs, url,
1051 M.source.tag(src)), rs, tool, url)
1052 case False{}:
1053 tool.aim(U.aim(True{}, M.tool.source(tool)), rs, tool)
1054
1055
1056
1057def tool.short(short: Bool, +rs: List<&2, Reply>, +tool: M.Tool) -> Pinned:
1058 match short:
1059 case True{}:
1060 tool.fill(String.is_empty(M.source.rev(M.tool.source(tool))), rs, tool)
1061 case False{}:
1062 tool.aim(U.aim(True{}, M.tool.source(tool)), rs, tool)
1063
1064
1065def tool.git(git: Bool, +rs: List<&2, Reply>, +tool: M.Tool) -> Pinned:
1066 match git:
1067 case False{}:
1068 PinHalt{"ez: tools." ++ M.tool.name(tool) ++ " has no git"}
1069 case True{}:
1070 +src = M.tool.source(tool)
1071 tool.short(Bool.or(String.is_empty(M.source.rev(src)),
1072 String.is_empty(M.source.nar(src))), rs, tool)
1073
1074
1075def tool.sel(sel: Bool, +rs: List<&2, Reply>, +tool: M.Tool) -> Pinned:
1076 match sel:
1077 case False{}:
1078 Pinned{tool, False{}, []}
1079 case True{}:
1080 tool.git(M.source.is_git(M.tool.source(tool)), rs, tool)
1081
1082
1083type Tools is Data:
1084 Tools{tools: List<&2, M.Tool>, moved: Bool, says: List<&2, String>,
1085 asks: List<&2, Ask>, bad: String}
1086
1087def tools.join(pinned: Pinned, rest: Tools) -> Tools:
1088 match pinned:
1089 case Pinned{t, m, says}:
1090 Tools{ts, any, sy, asks, bad} = rest
1091 Tools{t <> ts, Bool.or(m, any), List.append(&2, String, says, sy), asks, bad}
1092 case PinWait{more}:
1093 Tools{ts, any, sy, asks, bad} = rest
1094 Tools{ts, any, sy, List.append(&2, Ask, more, asks), bad}
1095 case PinHalt{why}:
1096 Tools{ts, any, sy, asks, _bad} = rest
1097 Tools{ts, any, sy, asks, halt.why(why)}
1098
1099
1100def tools.walk(+rs: List<&2, Reply>, +only: String, ts: List<&2, M.Tool>) -> Tools:
1101 match ts:
1102 case []:
1103 Tools{[], False{}, [], [], ""}
1104 case +h <> t:
1105 tools.join(tool.sel(U.chosen(only, M.tool.name(h)), rs, h),
1106 tools.walk(rs, only, t))
1107
1108
1109
1110
1111
1112
1113def edit.toml(moved: Bool, +text: String, rest: List<&2, Edit>) -> List<&2, Edit>:
1114 match moved:
1115 case True{}:
1116 Edit{"ez.toml", text} <> rest
1117 case False{}:
1118 rest
1119
1120
1121def edit.if(same: Bool, +at: String, +text: String, rest: List<&2, Edit>) -> List<&2, Edit>:
1122 match same:
1123 case True{}:
1124 rest
1125 case False{}:
1126 Edit{at, text} <> rest
1127
1128
1129
1130def edit.sources(fs: List<&2, Found>, +es: List<&2, U.Swap>) -> List<&2, Edit>:
1131 match fs:
1132 case []:
1133 []
1134 case Found{+at, +text} <> t:
1135 +next = U.reimport.many(es, text)
1136 edit.if(String.eq(next, text), at, next, edit.sources(t, es))
1137
1138
1139def drops.of(ss: List<&2, U.Swap>) -> List<&2, String>:
1140 match ss:
1141 case []:
1142 []
1143 case U.Swap{old, _new} <> t:
1144 old <> drops.of(t)
1145
1146
1147
1148def read.next(moved: Bool, +text: String, old: M.Manifest) -> M.Read:
1149 match moved:
1150 case True{}:
1151 M.parse(text)
1152 case False{}:
1153 M.Good{old}
1154
1155
1156def model.next(moved: Bool, next: M.Manifest, old: M.Manifest) -> M.Read:
1157 match moved:
1158 case True{}:
1159 M.Good{next}
1160 case False{}:
1161 M.Good{old}
1162
1163
1164
1165
1166
1167def fit.why(moved: Bool, next: M.Manifest) -> String:
1168 match moved:
1169 case True{}:
1170 Bool.pick(String, Rend.renderable(next), "", Rend.unrenderable())
1171 case False{}:
1172 ""
1173
1174
1175def done(
1176 +old: M.Manifest,
1177 dd: Deps,
1178 tt: Tools,
1179 +ignore: String,
1180 fs: List<&2, Found>
1181) -> Next:
1182 M.Manifest{+name, +entry, +bin, +hub, +pubas, +pubver, _ds, _ts} = old
1183 Deps{+ds, swaps, +edits, dmoved, dsays, trees, _dasks, _dbad} = dd
1184 Tools{+ts, tmoved, tsays, _tasks, _tbad} = tt
1185 +moved = Bool.or(dmoved, tmoved)
1186 +text = Rend.show(M.Manifest{name, entry, bin, hub, pubas, pubver, ds, ts})
1187 +allow = I.sync(ds, ignore)
1188 Next{model.next(moved, M.Manifest{name, entry, bin, hub, pubas, pubver, ds, ts}, old),
1189 read.next(moved, text, old), fit.why(moved, M.Manifest{name, entry, bin, hub, pubas, pubver, ds, ts}), [],
1190 trees, I.vended(ds),
1191 List.append(&2, String, dsays, tsays),
1192 edit.toml(moved, text, edit.if(String.eq(allow, ignore), ".gitignore", allow, [])),
1193 fs, drops.of(swaps), edits}
1194
1195
1196def open.srcs(srcs: Srcs) -> List<&2, Ask>:
1197 match srcs:
1198 case SrcsWait{ask}:
1199 [ask]
1200 case _:
1201 []
1202
1203
1204def got.srcs(srcs: Srcs, +old: M.Manifest, dd: Deps, tt: Tools, +ignore: String) -> Next:
1205 match srcs:
1206 case Srcs{fs}:
1207 done(old, dd, tt, ignore, fs)
1208 case SrcsWait{ask}:
1209 stopped(M.Good{old}, "ez: the upgrade was not given the project's sources", [ask])
1210 case SrcsNo{why}:
1211 stopped(M.Good{old}, why, [])
1212
1213
1214def got.ignore(said: Said, +srcs: Srcs, +old: M.Manifest, dd: Deps, tt: Tools) -> Next:
1215 match said:
1216 case Said{ignore}:
1217 got.srcs(srcs, old, dd, tt, ignore)
1218 case Unasked{ask}:
1219 stopped(M.Good{old}, "ez: the upgrade was not given .gitignore",
1220 ask <> open.srcs(srcs))
1221 case Unsaid{why}:
1222 stopped(M.Good{old}, why, [])
1223
1224
1225def ask.srcs(none: Bool, +rs: List<&2, Reply>) -> Srcs:
1226 match none:
1227 case True{}:
1228 Srcs{[]}
1229 case False{}:
1230 sources(rs, Sources{})
1231
1232def deps.edits(dd: Deps) -> List<&2, U.Swap>:
1233 Deps{_ds, _ss, es, _m, _sy, _tr, _asks, _bad} = dd
1234 es
1235
1236
1237def settled(+rs: List<&2, Reply>, +old: M.Manifest, +dd: Deps, tt: Tools) -> Next:
1238 got.ignore(text.said(rs, Ignore{}),
1239 ask.srcs(List.is_empty(&2, U.Swap, deps.edits(dd)), rs), old, dd, tt)
1240
1241
1242def pins.asks(asks: List<&2, Ask>, +rs: List<&2, Reply>, +old: M.Manifest, dd: Deps, tt: Tools) -> Next:
1243 match asks:
1244 case []:
1245 settled(rs, old, dd, tt)
1246 case h <> t:
1247 stopped(M.Good{old}, "ez: the upgrade still has questions for the remote", h <> t)
1248
1249
1250def pins.bad(
1251 clean: Bool,
1252 +bad: String,
1253 asks: List<&2, Ask>,
1254 +rs: List<&2, Reply>,
1255 +old: M.Manifest,
1256 dd: Deps,
1257 tt: Tools
1258) -> Next:
1259 match clean:
1260 case True{}:
1261 pins.asks(asks, rs, old, dd, tt)
1262 case False{}:
1263 stopped(M.Good{old}, bad, [])
1264
1265
1266def pins(dd: Deps, tt: Tools, +rs: List<&2, Reply>, +old: M.Manifest) -> Next:
1267 Deps{ds, ss, es, dm, dsy, dtr, dasks, +dbad} = dd
1268 Tools{ts, tm, tsy, tasks, +tbad} = tt
1269 pins.bad(Bool.and(String.is_empty(dbad), String.is_empty(tbad)),
1270 Bool.pick(String, String.is_empty(dbad), tbad, dbad),
1271 List.append(&2, Ask, dasks, tasks), rs, old,
1272 Deps{ds, ss, es, dm, dsy, dtr, [], ""}, Tools{ts, tm, tsy, [], ""})
1273
1274
1275def known(ok: Bool, +only: String, +rs: List<&2, Reply>, +old: M.Manifest) -> Next:
1276 match ok:
1277 case False{}:
1278 stopped(M.Good{old}, "ez: no dependency or tool named " ++ only, [])
1279 case True{}:
1280 M.Manifest{_name, _entry, _bin, +hub, +pubas, +pubver, +ds, +ts} = old
1281 pins(deps.walk(rs, only, M.hub.or(hub), ds), tools.walk(rs, only, ts), rs, old)
1282
1283
1284
1285def next(+only: String, read: M.Read, +rs: List<&2, Reply>) -> Next:
1286 match read:
1287 case M.Bad{why}:
1288 plain(M.Bad{why})
1289 case M.Good{+m}:
1290 M.Manifest{_name, _entry, _bin, _hub, _pubas, _pubver, +ds, +ts} = m
1291 known(Bool.or(U.known(only, ds), M.tool.known(only, ts)), only, rs, m)