Senior: питання на співбесіді з теми «Командна робота»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
5 питань
Тег - постійне ім'я для конкретного коміту, зазвичай версії: v2.4.0. На відміну від гілки, тег не рухається.
- Легкий тег - просто вказівник на коміт, як гілка, що не рухається:
git tag v2.4.0
- Анотований тег - окремий об'єкт Git з автором, датою, повідомленням і, за бажанням, підписом GPG/SSH:
git tag -a v2.4.0 -m "Реліз 2.4.0: експорт рахунків"
git tag -s v2.4.0 -m "..." # підписаний
Для релізів використовують анотовані: git describe за замовчуванням бачить лише їх, і видно, хто й коли позначив реліз.
Теги треба відправляти явно - звичайний git push їх не передає:
git push origin v2.4.0
git push --follow-tags # анотовані теги, що вказують на відправлені коміти
Практики:
- Семантичне версіонування (
MAJOR.MINOR.PATCH): ламаюча зміна - major, нова функція - minor, виправлення - patch. Composer і npm розв'язують залежності саме за тегами. - Тег запускає реліз: CI на
pushтегу збирає артефакти, створює GitHub Release, публікує пакет. - Не переміщати опубліковані теги. Якщо в
v2.4.0помилка, випускаютьv2.4.1. Переписаний тег у когось уже завантажений, і в різних людей «v2.4.0» означатиме різний код. git describe --tagsдає версію для збирання на кшталтv2.4.0-12-ga1b2c3d- 12 комітів після тегу.
Перший крок - не чистка історії, а відкликання секрету. Щойно ключ потрапив у віддалений репозиторій, вважайте його скомпрометованим: його вже могли скопіювати клони, CI, форки, боти, що сканують GitHub. Чистка історії не поверне контроль.
Порядок дій:
- Відкликати й замінити секрет: новий API-ключ, новий пароль бази, ротація токенів. Оновити його в усіх місцях, де він використовується.
- Перевірити журнали сервісу на підозріле використання за час, поки ключ був відкритий.
- Прибрати з коду і перенести секрет туди, де він має жити:
.env(у.gitignore), менеджер секретів, змінні CI. - За потреби - переписати історію, щоб секрет не лишався в старих комітах:
git filter-repo(рекомендований інструмент) чи BFG. Потім force push і повідомити команду: усім потрібно переклонувати репозиторій, інакше старі коміти повернуться з чиєїсь копії. На GitHub ще може знадобитися звернутися в підтримку, щоб прибрати кешовані подання й посилання з pull request.
Як не допустити:
.env, ключі й дампи - в.gitignoreз самого початку; у репозиторії лише.env.exampleбез значень.- Сканування секретів: GitHub secret scanning з push protection,
gitleaksчиtrufflehogу pre-commit і CI. - Короткоживучі облікові дані замість вічних ключів (OIDC для CI, тимчасові токени).
Laravel-специфіка: витік APP_KEY дозволяє підробляти зашифровані cookie й підписані URL - його теж ротують, пам'ятаючи, що зашифровані старим ключем дані доведеться перешифрувати (APP_PREVIOUS_KEYS допомагає з переходом).
Докладніше в документації: Видалення чутливих даних з репозиторію
Коли одночасно підтримується кілька версій продукту (бібліотека з версіями 2.x і 3.x, застосунок, що встановлюється у клієнтів), кожна версія має релізну гілку:
main ── розробка наступної версії
release/3.x ── виправлення для 3.x, теги v3.4.1, v3.4.2
release/2.x ── лише критичні й безпекові виправлення
Два напрямки перенесення виправлень:
1. «Зверху вниз» (backport) - виправлення спершу в main:
git switch release/3.x
git cherry-pick -x a1b2c3d
mainгарантовано містить усі виправлення - регресія в наступній версії неможлива;-xзалишає в повідомленні посилання на оригінальний коміт;- конфлікти, якщо код у старій версії відрізняється.
2. «Знизу вгору» (merge-up) - виправлення в найстаршій підтримуваній гілці:
git switch release/2.x # виправлення тут
git switch release/3.x && git merge release/2.x
git switch main && git merge release/3.x
Так працює, наприклад, Laravel: виправлення потрапляють у гілку найстаршої підтримуваної версії, а потім зливаються вгору. Переваги - кожне виправлення має один коміт, і Git знає, що воно вже є у всіх новіших гілках. Недолік - злиття вгору тягне й те, що для новішої версії не потрібне, і його доводиться скасовувати при злитті.
Як зробити процес надійним:
- автоматизація backport: мітка
backport 3.xна pull request запускає бота (наприклад, GitHub Action), що робить cherry-pick і відкриває новий pull request - людина лише перевіряє; - тести в кожній релізній гілці - CI запускається на всіх підтримуваних гілках, а не лише на
main; - політика підтримки: скільки версій і як довго отримують виправлення (баги - остання версія, безпека - дві), записана й опублікована;
- захист релізних гілок - лише через pull request з рев'ю;
- changelog для кожної гілки - користувачі старої версії мають бачити, що виправлено саме в ній.
Чим менше підтримуваних версій, тим дешевше. Для веб-застосунку, що деплоїться з main, релізні гілки зазвичай зайві: виправлення йде в main і на продакшен звичайним деплоєм, а відкат - повторним деплоєм попередньої версії.
Автор коміту в Git нічим не підтверджений. Поля user.name і user.email будь-хто може встановити довільно:
git -c user.name="Taylor Otwell" -c user.email="taylor@laravel.com" commit -m "Totally legit"
GitHub покаже цей коміт з аватаркою власника пошти. Підпис - криптографічне підтвердження, що коміт створив власник ключа, і що вміст не змінювали після підписання.
Налаштування з SSH-ключем (простіше, ніж GPG, з Git 2.34):
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
Той самий публічний ключ додається на GitHub як Signing key (окремо від ключа автентифікації, навіть якщо файл той самий). Після цього коміти отримують позначку Verified.
Локальна перевірка підписів потребує файлу довірених ключів:
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
git log --show-signature
git verify-commit HEAD
GPG чи SSH:
| GPG | SSH | |
|---|---|---|
| налаштування | складніше: окремі ключі, агент, термін дії | ключ, який уже є |
| відкликання й термін дії | вбудовані | немає (лише видалити ключ з GitHub) |
| підтримка | старі версії Git | Git 2.34+ |
Що підпис дає команді:
- branch protection «Require signed commits» - у захищену гілку не потрапить непідписаний коміт;
- захист ланцюга постачання: зловмисник з викраденим токеном push не підробить підпис без приватного ключа;
- аудит у регульованих галузях.
Пастки:
- коміти, створені на GitHub (злиття через інтерфейс, редагування в браузері), підписує ключ GitHub - вони теж Verified;
- після rebase чи squash коміти перестворюються - їх підписує той, хто виконує операцію, з власним ключем;
- режим vigilant на GitHub позначає непідписані коміти з вашою поштою як Unverified, щоб підробку було помітно;
- ключ на ноутбуці без пароля - слабке місце; краще апаратний ключ (YubiKey) чи ключ у менеджері паролів з агентом SSH.
Докладніше в документації: GitHub: перевірка підпису комітів
Windows закінчує рядки двома символами CRLF (\r\n), macOS і Linux - одним LF (\n). Якщо редактор чи інструмент перетворює закінчення, кожен рядок файлу вважається зміненим: diff на весь файл, конфлікти на рівному місці, blame втрачає авторів.
Ще гірші наслідки для PHP-проєкту:
- shell-скрипти (
entrypoint.sh) зCRLFне запускаються в Linux-контейнері:/bin/sh^M: bad interpreter; CRLFу шаблонах чи тестах з очікуваним текстом ламає порівняння рядків.
Рішення - .gitattributes у репозиторії, а не налаштування кожного розробника:
# .gitattributes
* text=auto eol=lf
*.sh text eol=lf
*.bat text eol=crlf
*.png binary
*.jpg binary
*.pdf binary
text=auto- Git сам визначає текстові файли й зберігає їх у репозиторії зLF;eol=lf- у робочому каталозі тежLFна будь-якій ОС (сучасні редактори на Windows працюють зLFбез проблем);binary- жодних перетворень і текстових diff для бінарних файлів.
Чому не core.autocrlf: це локальне налаштування кожного розробника (true на Windows, input на macOS/Linux). Достатньо одному учаснику налаштувати неправильно - і в репозиторій потрапляють CRLF. .gitattributes комітиться й діє для всіх однаково, маючи пріоритет над core.autocrlf.
Нормалізація існуючого репозиторію після додавання .gitattributes:
git add --renormalize .
git commit -m "Normalize line endings"
Цей коміт змінить багато файлів - його варто додати в .git-blame-ignore-revs, щоб blame не показував його автором кожного рядка.
Додатково:
.editorconfigзend_of_line = lf- щоб редактори одразу створювали файли правильно;- перевірка у CI:
git ls-files --eolпоказує закінчення рядків у індексі й робочому каталозі для кожного файлу; - Pint і Prettier також нормалізують закінчення рядків у файлах, які форматують.
Докладніше в документації: gitattributes: перетворення закінчень рядків