Lock-файл
ketch.lock — це набір утиліт однієї машини, закріплених на точних релізах. Запишіть його,
закомітьте поруч зі своїми dotfiles, і будь-яка інша машина зможе відтворити той самий
набір:
ketch lock # записати ./ketch.lock за тим, що встановлено
ketch lock --check # чи не розійшлося дерево з ним?
ketch sync # встановити те, що названо в lock-файлі, у цих версіях
Його JSON Schema, згенерована з типів, у які ketch його читає, — lock.schema.json.
Це не те блокування, яке ketch бере під час роботи, — те є м'ютексом над деревом встановлення, утримується на час виконання команди й звільняється наприкінці.
Який він має вигляд
# ketch.lock — generated by `ketch lock`. Do not edit it by hand:
# run `ketch lock` again instead. Commit it.
version = 1
[[package]]
name = "ripgrep"
source = "github:BurntSushi/ripgrep"
version = "14.1.1"
tag = "14.1.1"
target = "macos-aarch64"
asset = "ripgrep-14.1.1-aarch64-apple-darwin.tar.gz"
sha256 = "4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e"
[[package]]
name = "fd"
source = "github:sharkdp/fd"
version = "10.2.0"
tag = "v10.2.0"
target = "macos-aarch64"
asset = "fd-v10.2.0-aarch64-apple-darwin.tar.gz"
sha256 = "d3f0e1a5b3ee2b6d0a1a6d0dd6c72fd0e69f6bb7c5b3a2a94f0e5b9a2f8b8c11"
pinned = true
Пакети записуються відсортованими за назвою, тому файл стабільний, а його дифи
читабельні. pinned записується лише тоді, коли він дорівнює true, а bin — лише тоді, коли
вибір було зроблено.
| Ключ | Що це |
|---|---|
name | назва, під якою пакет встановлено |
source | owner/repo зі схемою — стабільна ідентичність пакета |
version | для людей; насправді розв'язується tag |
tag | точний реліз, який запитує ketch sync |
target | машина, на якій було знято цей запис |
asset | файл релізу, взятий на цьому target |
sha256 | цього файлу |
pinned | чи був пакет закріплений на цій версії |
bin | бінарник, вибраний, коли реліз містить кілька файлів із назвою пакета, у вигляді назви файлу (rtok-cli); ketch sync повторює цей вибір, не питаючи, — див. розділ bin у MANIFESTS.md |
published | для пакета на плаваючому тезі, який не є версією (nightly), — коли було опубліковано зафіксовану збірку; пишеться лише для таких пакетів |
Що відтворюване, а що ні
Тег відтворюваний. Кожна машина, що розв'язує той самий тег, отримує той самий реліз. Заради цього його й записують.
Асет і його геш відтворювані лише на тому самому target. Lock-файл,
записаний на Apple Silicon, називає tarball aarch64, який машина
x86_64 Linux запустити не може. Тому:
- На машині зі збіжним
targetsyncзвіряє завантаження із записанимsha256і відхиляє його ще до розпакування будь-чого, якщо вони не збігаються. Реліз, підмінений під уже опублікованим тегом, — це саме те, що lock-файл і має виловлювати. - На будь-якій іншій машині
syncобирає асет, що пасує хосту, і перевіряє його за контрольною сумою, яку публікує джерело, як під час звичайного встановлення. Удавати, що записаний геш досі чинний, означало б дати гарантію, яка непомітно нею не є. - Плаваючий тег (
nightly,dev) перепубліковують під тією самою назвою, тому сам тег збірку не втримує. На target, що записав lock-файл, її вже фіксує геш. На будь-якій іншій машиніsyncпорівнюєpublishedз тим, коли було опубліковано збірку, на яку тег указує зараз, і відхиляє пізнішу ще до завантаження; щоб прийняти її, знову запустітьketch lock. Якщо джерело не повідомляє, коли опубліковано реліз, лишається тільки геш на target, що записав lock-файл.
Пакет local: відтворюваний лише там, де є його шлях. Lock-файл записує
абсолютний шлях, звідки пакет встановлено, і геш того, що там лежало, — байтів
файлу, а для бандла .app — дайджест кожного файлу і символьного посилання
дерева. Кожен запис обрамлено видом, шляхом, бітами прав і або байтами файлу,
або власною ціллю символьного посилання, тож два розташування не можуть
поділити один дайджест, а перенацілювання посилання чи зняття +x його
змінює. sync перевстановлює з цього шляху й відхиляє його ще до розміщення
будь-чого, якщо вміст змінився відтоді, як записано lock-файл. Lock-файл,
записаний до цього обрамлення, не збігається з незмінним бандлом; запустіть
ketch lock ще раз, щоб записати новий дайджест. На машині без цього шляху
запис падає, як будь-яке інше недоступне джерело.
ketch sync
ketch sync # встановити відсутнє або те, що стоїть не в тій версії
ketch sync --dry-run # показати план, нічого не змінюючи
ketch sync --prune # також видалити пакети, не названі в lock-файлі
ketch sync --file <FILE> # lock-файл не в ./ketch.lock, а деінде
План читається як диф:
+ jq 1.7.1
~ ripgrep 14.1.0 -> 14.1.1
- httpie not in the lockfile
2 already match
Пакет, не згаданий у lock-файлі, сам по собі не є розбіжністю — lock-файл
записує те, що ви хочете, а не обов'язково все, що у вас є, — тому
ketch lock --check ігнорує зайве, і видаляє його лише --prune. Видалення
може знищити чиюсь роботу, тому спершу ставиться запитання, якщо не передано --yes.
pinned відновлюється після встановлення, тож пакет, записаний у lock-файлі як закріплений,
повертається закріпленим, а не тихо оновлюваним.
Що перевіряє ketch
Lock-файл — це файл, який міг написати хтось інший; саме це й означає спільний
репозиторій dotfiles. Тому ніщо в ньому не має права обирати шлях у файловій
системі. sync запитує джерело на тезі, встановлює його під записаною name
— яка має пройти першу перевірку нижче, — і дає звичайному розв'язанню маніфестів
вирішити, які бінарники ставити й куди; записаний bin лише вирішує,
який із кількох бінарників із назвою пакета отримає посилання, після будь-якого вибору,
який уже запам'ятав state.json. Lock-файл закріплює, який реліз, але ніколи — куди він
потрапить.
| Відхиляється | Чому |
|---|---|
name, яку не можна використати дослівно як один компонент шляху | вона зіставляється зі встановленими пакетами й показується вам; назва, яку довелося б переписати, означає не те, що в ній написано |
| той самий пакет двічі | встановити можна було б лише один із них |
джерело github:, що не є коректним owner/repo | воно перетворюється на URL |
| будь-яке інше джерело з порожнім id | розв'язувати нічого |
sha256, що не складається з 64 шістнадцяткових символів | його порівнюють зі справжнім дайджестом |
bin, порожній або непридатний дослівно як одна назва файлу | він називає файл у релізі; значення з роздільником шляху, .. або керівними символами не може цього робити |
порожній tag | розв'язувати нічого |
published, порожній або з чимось, окрім друкованого ASCII | його виводять у помилці, що називає зсунутий тег |
target, який ketch не розпізнає | він мовчки перетворює запис на кросплатформний, тож записані асет і геш перестають діяти, а геш, що змінився під тегом, виглядає чистим |
| невідомий ключ | ключ з одруківкою, який мовчки ігнорується, закріплює не те, що ви написали |
version, новіша, ніж розуміє цей ketch | оновіться через ketch self upgrade |
Один поганий запис провалює весь файл, а не пропускається. На відміну від реєстру, де часткова відповідь краща за жодну, lock-файл, що встановив більшу частину себе, зовсім не був би lock-файлом.
Яка назва розв'язується
sync спершу запитує пакет за name, бо саме так його
спочатку знайшли, а те, який рівень маніфестів відповість, вирішує, що отримає посилання й під
якими назвами. Розв'язання безпосередньо з github:BurntSushi/ripgrep провалилося б
до виведення з репозиторію й могло б дати інші бінарники, ніж запис реєстру,
який ви насправді встановили.
Назва використовується лише доти, доки вона означає той самий проєкт. Якщо тепер вона розв'язується
в інше джерело, перемагає source з lock-файлу — назва, що змінила
власника, не повинна тихо встановити щось інше.
Хай там як, пакет встановлюється під name з lock-файлу. Пакет,
встановлений через ketch install --name, повертається під цією назвою, а не під тією, яку вивело б
його джерело, тож ketch lock --check узгоджується з машиною, яку щойно налаштував sync.