Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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. Чистка історії не поверне контроль.

Порядок дій:

  1. Відкликати й замінити секрет: новий API-ключ, новий пароль бази, ротація токенів. Оновити його в усіх місцях, де він використовується.
  2. Перевірити журнали сервісу на підозріле використання за час, поки ключ був відкритий.
  3. Прибрати з коду і перенести секрет туди, де він має жити: .env (у .gitignore), менеджер секретів, змінні CI.
  4. За потреби - переписати історію, щоб секрет не лишався в старих комітах: 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 і на продакшен звичайним деплоєм, а відкат - повторним деплоєм попередньої версії.

Докладніше в документації: Pro Git: супровід проєкту

Автор коміту в 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: перетворення закінчень рядків