Системи контролю версій і модель Git
Зміст
Уяви теку з проєктом, де тести лежать поряд у файлах report_final.spec.ts, report_final_v2.spec.ts, report_final_РЕАЛЬНО_final.spec.ts. Хто останній їх чіпав? Що саме змінилося між другою й третьою версією? Як повернути стан «до того, як ми все зламали»? Без відповіді на ці питання команда з двох людей уже сповзає в хаос, а команда з десяти — просто не працює. Система контролю версій (version control system, VCS) — це інструмент, який відповідає на всі три питання автоматично: хто, коли, що і навіщо змінив, і як відкотитися.
Для AQA це не «інструмент розробників, який мене не стосується». Твої автотести живуть у git-репозиторії — часто в тому самому, що й код застосунку. Ти щодня робиш коміти, розводиш гілки, розв'язуєш конфлікти, читаєш git blame, щоб зрозуміти, хто зламав локатор. CI-пайплайн запускається на кожен push. І на співбесіді на AQA питання про Git — майже гарантовані, бо без нього автоматизація не існує. Ця глава — модель світу: не команди (їх розберемо в главі «Базовий цикл Git»), а те, як Git думає. Зрозумієш модель — команди стануть очевидними; завчиш команди без моделі — зламаєшся на першому ж нестандартному випадку.
Що таке контроль версій і навіщо він QA
Контроль версій — це система, яка запам'ятовує стан набору файлів у часі й дозволяє повернутися до будь-якого збереженого стану. Кожен зафіксований стан називають комітом (commit) — це знімок проєкту плюс метадані: автор, дата, повідомлення й посилання на попередній коміт. Ланцюжок комітів утворює історію (history).
Що це дає на практиці:
- Історія й відповідальність. Видно, хто, коли й навіщо змінив кожен рядок. Тест почав падати після вчорашнього мержу — за хвилину знаходиш винний коміт.
- Відкат. Зламали — повертаєшся до робочого стану без «пам'ятаєш, як воно виглядало?».
- Паралельна робота. Кілька людей правлять один проєкт одночасно, кожен у своїй гілці (branch), а система зводить зміни докупи, а не затирає чужу роботу.
- Експерименти без страху. Спробувати ризиковану переробку в окремій гілці й викинути її, якщо не спрацювало, — дешево.
Для тестувальника додається свій кут: коміт — це одиниця, яку перевіряє CI; гілка — це те, що ревʼюять перед мержем; історія — це доказова база при розслідуванні регресій. У контексті процесів QA контроль версій — частина керування конфігурацією (configuration management): кожен тестовий артефакт має бути ідентифікований і версійований, щоб результат прогону можна було звʼязати з конкретною версією і коду, і тестів.
Централізовані проти розподілених
VCS історично бувають двох архітектур, і різницю між ними питають чи не найчастіше.
Централізована (centralized VCS, CVCS) — Subversion (SVN), CVS, Perforce. Є один центральний сервер, який тримає всю історію. Розробники беруть із нього робочу копію (working copy) — зазвичай лише останній стан файлів, без історії. Щоб зробити коміт, подивитися лог чи створити гілку, потрібне зʼєднання із сервером. Сервер — єдина точка правди й водночас єдина точка відмови: він ліг — команда не комітить; диск на ньому вмер без бекапу — історія втрачена назавжди.
Розподілена (distributed VCS, DVCS) — Git, Mercurial. Тут немає «головного» й «підлеглих» копій на рівні технології. Коли ти робиш git clone, ти отримуєш повну копію репозиторію разом з усією історією. Комітити, дивитися лог, розводити й зливати гілки, порівнювати версії можна повністю офлайн — усе це локальні операції з даними на твоєму диску. Сервер (його називають remote) потрібен лише для обміну змінами з іншими, і він — питання домовленості, а не архітектури: будь-який клон може стати новим центром.
| Аспект | Централізована | Розподілена (Git) |
|---|---|---|
| Що є в тебе локально | Робоча копія (часто лише остання версія) | Повний репозиторій з усією історією |
| Коміт, лог, гілки | Потрібен сервер | Локально, офлайн |
| Точка відмови | Сервер — критична | Будь-який клон містить усе |
| Швидкість операцій з історією | Обмежена мережею | Миттєва (локальний диск) |
| Приклади | SVN, CVS, Perforce | Git, Mercurial |
Практичний наслідок для AQA: Git-операції над історією миттєві, бо не ходять у мережу. Коли git bisect за секунди перебирає сотні комітів у пошуках того, що зламав тест, — це працює саме тому, що вся історія лежить локально.
Git думає знімками, а не діфами
Це головна відмінність Git на рівні внутрішньої моделі, і саме вона відрізняє того, хто «вивчив команди», від того, хто розуміє інструмент.
Більшість старих VCS зберігають дані як набір змін (діфів, deltas): базова версія файлу плюс список правок поверх неї. Щоб отримати актуальний стан, система бере базу й накочує всі діфи один за одним.
Git влаштований інакше. Він думає про дані як про послідовність знімків (snapshots). При кожному коміті Git фактично фотографує, як виглядають усі файли в цей момент, і зберігає посилання на цей знімок. Це не означає, що Git дублює кожен файл у кожному коміті: якщо файл не змінився, Git не зберігає його копію, а кладе посилання на вже наявний ідентичний обʼєкт. Тобто знімок — це не «купа копій», а набір посилань на вміст, і незмінене перевикористовується.
Технічно вміст файлу зберігається як обʼєкт, адресований хешем свого вмісту (за замовчуванням SHA-1). Однаковий вміст — однаковий хеш — зберігається один раз. Через це Git ловить будь-яке пошкодження: якщо байт у файлі змінився, а хеш ні — цілісність порушена, і Git це побачить.
Чому це важить на практиці:
- Коміт — це повний стан, а не «різниця». Тому переключитися на будь-який коміт — швидко: Git не «відкочує діфи», а розкладає готовий знімок.
- Гілки дешеві. Гілка — це просто вказівник на коміт (детально — у главі «Гілки та злиття»). Створення гілки не копіює файли, тому їх заводять щедро.
- Дифи — це подання, а не сховище. Коли ти дивишся
git diff, Git обчислює різницю між двома знімками на льоту для твоєї зручності; зберігає він знімки.
Три зони: робоча директорія, staging, репозиторій
Найважливіша щоденна модель Git — це три зони, через які проходить кожна зміна. Плутанина тут — джерело половини «магічних» проблем новачка.
- Робоча директорія (working directory / working tree) — звичайні файли на диску, які ти відкриваєш у редакторі й правиш. Це єдина зона, яку видно у файловому менеджері.
- Staging area (індекс, index / staging area) — проміжна зона, «чернетка наступного коміту». Сюди ти явно складаєш ті зміни, які хочеш зафіксувати. Командою
git addфайл (точніше, його поточний стан) переноситься з робочої директорії в staging. - Локальний репозиторій (.git) — прихована тека
.gitу корені проєкту, де лежить уся історія комітів. Командоюgit commitвміст staging перетворюється на новий постійний коміт.
Навіщо взагалі окрема зона staging — адже можна було б комітити прямо з робочої директорії? Бо вона дає контроль над тим, що саме увійде в коміт. Ти правив три файли, але логічно завершена лише одна зміна — складаєш у staging тільки її й комітиш окремо, а решту лишаєш у роботі. Це основа охайної, читабельної історії, де кожен коміт — одна осмислена зміна, а не «зберіг усе, що назбиралось».
Відповідно, файл у Git буває в одному з трьох станів: змінений (modified) — правлений у робочій директорії, але ще не в staging; підготовлений (staged) — доданий у staging під наступний коміт; зафіксований (committed) — уже в історії. Команда git status показує, який файл у якому стані, і читати її треба саме через цю модель. Повернути файл між зонами (прибрати зі staging, відкинути правки в робочій директорії) — тема глави «Скасування змін».
Локальний і віддалений: Git ≠ GitHub
Тепер розширимо модель на командну роботу — і одразу знімемо найпоширенішу плутанину.
Git — це інструмент: консольна програма контролю версій, яку створив Лінус Торвальдс у 2005 році для розробки ядра Linux. Git працює повністю на твоїй машині й нікому нічого не винен: можна вести репозиторій роками, жодного разу не виходячи в інтернет.
GitHub, GitLab, Bitbucket — це сервіси-хостинги, побудовані навколо Git. Вони тримають віддалену копію репозиторію й додають зверху те, чого в самому Git немає: вебінтерфейс, pull request-и й код-рев'ю, трекер задач, керування доступами, вбудований CI/CD. Git може існувати без них (локально або на власному сервері через SSH); вони без Git — безглузді.
Аналогія: Git — це двигун, GitHub — одна з машин із цим двигуном. Можна знати двигун і не знати конкретну машину; знати машину без двигуна — не можна.
Звідси — робота з віддаленим репозиторієм. Твій локальний репозиторій (.git) — повноцінний і самодостатній. Remote — це іменоване посилання на віддалену копію (за замовчуванням його називають origin). Обмін між ними — окремі, свідомі дії: git push відправляє твої локальні коміти на remote, git pull забирає чужі. Ключовий наслідок, який ловить кожного новачка: git commit нічого нікуди не відправляє. Коміт живе тільки на твоєму диску, поки ти не зробиш push. «Я ж закомітив» і «мої зміни на GitHub» — це два різні факти. Механіку push/pull/fetch детально розбираємо в главі «Базовий цикл Git», а pull request-и й стратегії гілкування — у главі «Pull Request».
Для AQA ця межа принципова ще й тому, що CI/CD живе на боці сервісу: пайплайн запускає твої тести у відповідь на подію (push, відкриття PR) на GitHub чи GitLab — на події самого Git він не реагує, бо про твої локальні коміти ще нічого не знає.
.gitignore: що не потрапляє в історію
Не все, що лежить у теці проєкту, має бути в репозиторії. Згенеровані артефакти, залежності, локальні налаштування, а тим паче секрети — усе це або роздуває історію, або взагалі небезпечне. Щоб Git їх ігнорував, у проєкт кладуть файл .gitignore зі списком шаблонів.
Типовий .gitignore для проєкту автотестів:
# залежності — ставляться з package-lock.json, у репо не потрібні
node_modules/
# артефакти прогонів
test-results/
playwright-report/
allure-results/
# секрети та локальна конфігурація
.env
.env.local
# службове ОС і редактора
.DS_Store
.idea/
Найважливіша й найчастіше незрозуміла деталь: .gitignore діє лише на файли, які Git ще не відстежує (untracked). Якщо файл уже потрапив у історію (його колись закомітили), додавання його в .gitignore нічого не змінить — Git продовжить відстежувати зміни в ньому. Класична пастка: розробник комітить .env із паролем, аж потім згадує про .gitignore — а файл уже в історії. Прибрати з відстеження (лишивши на диску) треба явно:
git rm --cached .env
І окреме застереження, критичне для QA: якщо секрет уже опинився в історії комітів, самого git rm замало — він лишається в старих комітах, і будь-хто з доступом до репозиторію його дістане. Секрет треба вважати скомпрометованим і ротувати (згенерувати новий, старий відкликати). Гігієна секретів у репозиторії й пайплайні — окрема тема (розділ про секрети та конфігурацію в CI), але правило номер один просте: паролі, токени й ключі в git-історії не місце.
git config: хто ти для Git
Git потребує мінімального налаштування, і одне з них — обовʼязкове. Кожен коміт підписаний іменем і поштою автора, тож перш ніж комітити, їх треба задати:
git config --global user.name "Olena Tester"
git config --global user.email "olena@example.com"
Конфігурація існує на трьох рівнях, і це варто розуміти, бо на співбесіді про це питають:
- system (
--system) — для всіх користувачів машини (файл рівня системи); - global (
--global) — для твого користувача, усі твої репозиторії (файл~/.gitconfig); - local (
--local) — лише поточний репозиторій (файл.git/config).
Правило пріоритету: чим вужчий рівень, тим сильніший. Local перекриває global, global перекриває system. Це зручно, коли, наприклад, у робочих проєктах треба комітити з корпоративною поштою, а в пет-проєктах — з особистою: ставиш різний user.email локально в конкретному репозиторії.
Тут — ще одна пастка, яку плутають навіть досвідчені. user.email у git config — це не логін і не автентифікація. Це просто підпис, який Git вшиває в коміт як текст; поставити туди можна будь-що. А от право push-ити у віддалений репозиторій сервіс перевіряє окремо — через SSH-ключ або токен доступу. Тому «я поставив правильний email, а push відхиляють» — нормальна ситуація: email і доступ — це різні речі.
Ще одне налаштування, яке реально псує життя тестам, — закінчення рядків (line endings). Windows традиційно завершує рядок парою символів CRLF, Unix і macOS — одним LF. Параметр core.autocrlf керує тим, як Git конвертує їх при коміті й вивантаженні. Розсинхрон тут дає «фантомні» зміни (весь файл раптом позначений як змінений, хоча в ньому «нічого не чіпали») і крихкі порівняння в тестах, чутливих до вмісту файлів. Для крос-платформних команд це узгоджують один раз на весь проєкт (часто через файл .gitattributes), щоб історія не залежала від того, хто на якій ОС працює.
Типові помилки
- «Я закомітив — значить, зміни в команди / на GitHub». Насправді
commit— локальна дія; поки немаєpush, зміни бачиш лише ти. Колеги нічого не побачать, і CI не запуститься. - «
git addзберігає зміни». Насправдіaddлише кладе поточний стан у staging — чернетку наступного коміту. Зберігає в історію тількиcommit. Забувaddпісля правки — закомітиш стару версію файлу. - «Git зберігає різницю між версіями». Насправді концептуальна модель Git — знімки цілих станів проєкту з дедуплікацією незмінених файлів, а не ланцюжок діфів. Дифи він обчислює на льоту для показу.
- «Клон — це як робоча копія в SVN, лише останні файли». Насправді
git cloneтягне повний репозиторій з усією історією; майже всі операції потім локальні й офлайн. - «Додав файл у
.gitignore— і його секрет зник». Насправді.gitignoreдіє лише на untracked-файли; уже закомічений файл лишається в історії, доки його не прибрати явно (git rm --cached), а секрет із минулих комітів треба ротувати. - «
user.emailу config — це мій логін до GitHub». Насправді це лише текстовий підпис автора в коміті; автентифікацію сервіс робить через SSH-ключ або токен, і це різні механізми. - «Git і GitHub — одне й те саме». Насправді Git — інструмент контролю версій, GitHub — один із хостинг-сервісів навколо нього; Git працює й без жодного з них.
Підсумок
- Git розподілений: кожен клон містить повну історію, і майже всі операції (коміт, лог, гілки, дифи) — локальні й миттєві, бо не ходять у мережу.
- Git думає знімками цілих станів, а не діфами; незмінений вміст зберігається один раз і адресується хешем.
- Кожна зміна проходить три зони: робоча директорія → (
git add) → staging → (git commit) → локальний репозиторій.addіcommit— різні кроки, і staging дає контроль над тим, що саме увійде в коміт. - Коміт живе локально, поки не
push; Git ≠ GitHub — інструмент і сервіс-хостинг навколо нього — це різні речі. .gitignoreдіє лише на untracked-файли, аuser.name/user.emailу config — це підпис автора, а не автентифікація.
Що питають на співбесіді
- «Чим розподілена система контролю версій відрізняється від централізованої?» Інтервʼюер перевіряє, чи розумієш ти головне: у Git кожен клон — повна копія з історією, робота офлайн і відсутність єдиної критичної точки. Слабка відповідь зводить усе до «Git новіший»; сильна — називає локальність операцій і наслідки (швидкість, надійність).
- «Що таке staging area і навіщо вона потрібна?» Дивляться, чи не плутаєш ти
addіcommit. Хороша відповідь: staging — чернетка наступного коміту, вона дає зібрати в один коміт саме потрібні зміни, а не все підряд, і тримати історію охайною. - «Git — це те саме, що GitHub?» Питання-фільтр на базове розуміння. Правильно: Git — інструмент контролю версій, GitHub/GitLab/Bitbucket — сервіси-хостинги з вебінтерфейсом, PR, задачами й CI поверх Git; Git працює й без них.
- «Git зберігає діфи чи знімки?» Перевіряють глибину моделі. Знімки цілих станів з дедуплікацією незмінених файлів; дифи — обчислюване подання.
- «Випадково закомітив файл із паролем — що робиш?» Тут інтервʼюер дивиться на інстинкт безпеки. Ключове:
.gitignoreзаднім числом не рятує, файл лишається в історії; секрет треба вважати скомпрометованим і ротувати, а не просто видалити наступним комітом.
Джерела
- Pro Git (Scott Chacon, Ben Straub) — розділ 1: Getting Started — канонічний підручник: контроль версій, централізовані vs розподілені, знімки.
- Pro Git — розділ 1.3: What is Git? — знімки замість діфів, три стани файлу, три зони.
- Git Reference: gitignore — синтаксис шаблонів і правило untracked-файлів.
- Git Reference: git config — рівні конфігурації та їх пріоритет.
- ISTQB CTFL 4.0 Syllabus, розділ 5.4 «Configuration Management» — контроль версій як частина керування конфігурацією тестових артефактів.
Навіщо тестувальнику контроль версій, якщо це «інструмент розробників»?
Бо автотести — це теж код, і живе він у тому самому репозиторії, часто поруч із кодом застосунку. Щодня AQA робить коміти, заводить гілки під нові сценарії, розв'язує конфлікти після мержу, дивиться через git blame, хто і коли переписав локатор, що раптово перестав знаходитись. CI-пайплайн стартує на кожен push — без системи контролю версій (version control system, VCS) автоматизація просто не має де запускатися. Плюс історія комітів — це доказова база при розслідуванні регресій: коли тест почав червоніти, перший крок — знайти зміну, після якої це сталося. Тому питання про Git на співбесіді на AQA практично неминучі.
Що таке коміт і що всередині нього, крім самих файлів?
Коміт — це зафіксований знімок стану проєкту в конкретний момент плюс метадані про цю фіксацію. До метаданих входять автор, дата, текстове повідомлення (навіщо зроблено зміну) і посилання на попередній коміт — саме воно зшиває окремі коміти в ланцюжок історії. Завдяки посиланню на батька Git завжди знає, який стан був до цього, і може показати різницю чи відкотитися. Для QA важливо, що коміт — це неподільна одиниця, яку перевіряє CI: пайплайн запускається на конкретний коміт, і результат прогону прив'язується саме до нього. Тобто «зелений білд» — це завжди зелений білд на певному знімку коду й тестів.
Чим розподілена система контролю версій відрізняється від централізованої?
Головне — місце зберігання історії й потреба в мережі. У централізованих системах (SVN, CVS, Perforce) повна історія лежить на одному сервері, а в розробника локально — тільки робоча копія файлів без історії; будь-яка дія (фіксація, перегляд логу, нова гілка) вимагає зв'язку із сервером. Git і Mercurial влаштовані навпаки: під час клонування ти забираєш увесь репозиторій разом з історією, тож фіксувати, дивитись лог, галузити й порівнювати версії можна прямо на диску, без мережі. Звідси дві переваги: такі дії відпрацьовують без мережевої затримки, а сервер перестає бути єдиним вузлом, чия втрата вбиває історію, — копія є в кожного. На співбесіді слабко звучить «Git просто новіший»; сильно — коли називаєш локальність дій і те, що з неї випливає: швидкість і відмовостійкість.
Git зберігає різницю між версіями чи щось інше?
Концептуально Git тримає не діфи, а послідовність знімків (snapshots) цілих станів проєкту. Багато старих VCS зберігають базовий файл і чергу правок над ним, а щоб отримати актуальний вміст, ці правки накочують одну за одною. Git на кожному коміті робить інакше: він мовби фотографує всі файли такими, якими вони є цієї миті, і запам'ятовує покажчик на цю фотографію. Дублювання при цьому немає — файл, що не мінявся, не копіюється вдруге, а адресується наявним ідентичним об'єктом, тобто незмінене йде в перевикористання. Той git diff, який ти бачиш на екрані, обчислюється на льоту між двома знімками заради зручності показу, а не відображає спосіб зберігання. Наслідок для роботи: стрибок на будь-який коміт швидкий, адже Git викладає готовий знімок, а не відмотує чергу правок.
Що таке три зони Git і чому не можна комітити прямо з робочої директорії?
Кожна зміна проходить через три зони: робочу директорію (working directory) — звичайні файли на диску, які ти правиш у редакторі; staging area, вона ж індекс (index) — «чернетку наступного коміту»; і локальний репозиторій — приховану теку .git, де лежить уся історія. Командою git add поточний стан файлу переноситься з робочої директорії в staging, командою git commit вміст staging стає новим постійним комітом. Технічно можна було б фіксувати прямо з робочої директорії, але проміжна зона дає владу над складом коміту: якщо з трьох правлених файлів завершена лише одна логічна зміна, у staging кладеш саме її, а решту лишаєш дозрівати. Так тримається читабельна історія, де коміт відповідає одній завершеній думці, а не звалищу випадкових правок. Плутанина в цих зонах — джерело половини «магічних» проблем новачка.
У чому різниця між git add і git commit?
Це два різні кроки, і плутати їх — класична помилка. git add лише кладе поточний стан файлу в staging — готує чернетку наступного коміту, але нічого не зберігає в історію назавжди. git commit бере те, що зараз у staging, і перетворює на постійний коміт в історії. Практична пастка звідси: якщо ти правиш файл, робиш add, потім знову правиш той самий файл і одразу комітиш без повторного add, — у коміт піде та версія, яку ти застейджив раніше, а не остання з диска. Тобто add фотографує стан файлу на момент виклику, а не «підписує файл на всі майбутні зміни». На співбесіді питанням про staging перевіряють саме те, чи не злиплися в тебе ці дві дії в одну.
Які стани буває файл у Git і як їх читати?
Відстежуваний файл перебуває в одному з трьох станів. Modified — ти його правив, і правки висять у робочій директорії поза staging. Staged — стан файлу вже відкладено під найближчий коміт. Committed — зміна вже осіла в історії. Поза цією трійкою є ще untracked-файли, яких Git узагалі не бачить, бо їх ніколи не додавали. Вивід git status — це і є розкладка файлів по цих станах: окремо те, що потрапить у коміт, окремо змінене-але-недодане, окремо невідстежуване. Прочитавши його через модель зон, ти наперед бачиш, що саме зафіксується, а що просто загубиться, якщо закомітити цю ж мить.
Git — це те саме, що GitHub?
Ні — Git і GitHub лежать на різних рівнях: одне інструмент, друге сервіс поверх нього. Git — це консольна утиліта контролю версій авторства Лінуса Торвальдса (2005 рік, задля розробки ядра Linux), і вона цілком автономна: репозиторій можна вести хоч роками без жодного виходу в мережу. Хостинги (GitHub, GitLab, Bitbucket) тримають у себе віддалену копію й нашаровують те, чого Git сам по собі не має: браузерний інтерфейс, механізм pull request-ів із рев'ю, баг-трекер, ролі та права, конвеєр CI/CD. Прибери хостинг — Git житиме далі (локально або на своєму сервері через SSH); прибери Git — від хостингу нічого не лишиться. Образно: Git — це мотор, а GitHub — конкретний автомобіль на ньому; розібратись у моторі, не знаючи саме цієї моделі авто, можна, а навпаки — ні. Питання просте, але добре відсіює тих, хто цієї межі не бачить.
Що таке remote і чому git commit не відправляє зміни колегам?
Remote — це названий покажчик на віддалену копію репозиторію; головному за традицією дають ім'я origin. Локальний .git самодостатній, а синхронізація з remote відбувається лише тоді, коли ти сам її запускаєш: git push виштовхує твої коміти на сервер, git pull затягує зроблене іншими. І ось де спотикається майже кожен новачок: сам по собі git commit нічого за межі диска не виносить — доки не буде push, коміт існує тільки в тебе. Через це «я закомітив» і «моє вже на GitHub» — зовсім не одне й те саме: без push ані колеги нічого не побачать, ані CI не стартує, адже про локальні коміти хостинг просто не в курсі. Тому й конвеєр CI/CD чіпляється за подію на стороні сервісу (push, відкриття PR), а не за коміт як такий.
Як працює .gitignore і чому він «не прибрав» уже закомічений файл?
.gitignore — це файл зі списком шаблонів, за якими Git не бере файли до уваги: залежності (node_modules/), артефакти прогонів (test-results/, playwright-report/), локальну конфігурацію й секрети (.env). Критична деталь, яку найчастіше не розуміють: .gitignore діє лише на невідстежувані (untracked) файли. Якщо файл уже колись закомітили, він відстежується, і дописати його у .gitignore після факту вже нічого не дасть — Git і далі стежитиме за змінами в ньому. Типовий провал: .env уже закомічено, і аж тоді про нього згадують у .gitignore — та пізно, він давно в історії. Щоб зняти файл з відстеження, не стираючи його з диска, це роблять руками — git rm --cached .env, і лише після цього правило в .gitignore почне діяти.
Випадково закомітив файл із паролем — що робиш?
Спершу треба зрозуміти головне: .gitignore заднім числом не рятує, бо файл уже потрапив в історію. Просто видалити його наступним комітом теж замало — старі коміти з паролем нікуди не зникають, і кожен, хто має доступ до репозиторію, витягне секрет із історії. Тому єдина правильна реакція — вважати секрет скомпрометованим і ротувати його: згенерувати новий, а старий відкликати. Прибрати сам файл з відстеження (git rm --cached) і додати в .gitignore — теж потрібно, але це вже гігієна, а не порятунок. Тут інтерв'юер дивиться саме на інстинкт безпеки: слабка відповідь — «видалю й закомічу», сильна — «ротую секрет, бо в історії він уже засвітився».
Які рівні має git config і як працює пріоритет між ними?
Конфігурація Git існує на трьох рівнях. System (--system) — для всіх користувачів машини. Global (--global) — для твого користувача, спільна для всіх твоїх репозиторіїв (файл ~/.gitconfig). Local (--local) — лише для поточного репозиторію (файл .git/config). Пріоритет вибудовано так: що конкретніший рівень, то він головніший — налаштування репозиторію б'є твоє користувацьке, а користувацьке б'є системне. Навіщо це на практиці: на роботі коміти мають іти з корпоративною адресою, а у власних забавках — з приватною, тож user.email задаєш окремо всередині потрібного репозиторію, глобальний лишаючи як є. Про це регулярно питають, бо саме нерозуміння пріоритету дає коміти «не з тієї пошти».
user.email у git config — це мій логін до GitHub?
Ні, це лише текстовий підпис автора, який Git вшиває в коміт, і поставити туди можна будь-що. А чи маєш ти право заливати коміти на віддалений сервер — це хостинг з'ясовує зовсім іншим шляхом: за SSH-ключем або токеном доступу. Ось чому картина «email правильний, а push усе одно летить у відмову» цілком буденна: підпис і доступ — різні механізми, які нічого не знають один про одного. І навпаки: правильний ключ пустить тебе пушити, навіть якщо в user.email вписана дурниця, — просто коміти будуть підписані кимось стороннім. Плутають це навіть досвідчені, тож на співбесіді відповідь «email — це підпис, а не автентифікація» показує зрілість.
Що таке core.autocrlf і як закінчення рядків ламають тести?
На Windows кінець рядка історично позначають двома символами (CRLF), а на Unix і macOS — одним (LF), і саме конвертацією між цими варіантами при коміті та вивантаженні керує core.autocrlf. Якщо в команді це не звірено, вилазять «примарні» зміни: файл цілком помічений як правлений, хоча по суті в ньому переклацнулись лише символи переходу рядка. Тестам від цього подвійно кисло — сміття в діфах і рев'ю плюс зламані порівняння там, де тест чутливий до точного байтового вмісту (звірка з еталонним файлом, наприклад). Тому в командах на різних ОС питання закривають раз і назавжди на рівні проєкту, зазвичай через .gitattributes, аби вміст в історії не залежав від операційки автора.
Чому Git адресує вміст хешем і що це дає на практиці?
Вміст файлу Git зберігає як об'єкт, адресований хешем свого ж вмісту (за замовчуванням SHA-1). Це означає, що однаковий вміст завжди дає однаковий хеш і зберігається один раз — звідси природна дедуплікація незмінених файлів між комітами. Друга властивість важливіша для надійності: якщо хоч байт у файлі зміниться, зміниться і його хеш, тож будь-яке пошкодження даних Git одразу помітить — вміст більше не відповідатиме адресі, під якою лежить. Тобто хеш служить водночас і адресою для зберігання, і контрольною сумою цілісності. Саме на цій адресації вмістом тримається вся модель знімків: коміт посилається на дерево об'єктів за їхніми хешами, а не тягне копії файлів.
Чому в Git гілки називають «дешевими»?
Бо гілка в Git — це просто рухомий вказівник на коміт, а не копія файлів. Створення гілки не дублює проєкт і не займає місця пропорційно розміру коду — це фактично запис одного хеша, тому операція миттєва. Через це в Git прийнято заводити гілки щедро: під кожну задачу, експеримент, ризиковану переробку — і викидати непотрібні без жалю. У централізованих системах гілкування історично було важчою й повільнішою операцією, тому там гілок уникали; у Git дешевизна гілок прямо змінює робочий процес — pull request-и, короткоживучі feature-гілки, ізоляція кожної зміни стають нормою. Для AQA це означає, що кожен новий набір тестів чи фікс локатора природно живе у своїй гілці, доки не пройде рев'ю й не змержиться.
Три ситуації з робочого дня AQA, де все впирається в модель Git: як прочитати git status через три зони, як правильно відреагувати на секрет, що вже потрапив в історію, і як за допомогою історії знайти коміт, після якого посипались тести. Скрізь — не набір команд, а що відбувається під ними й чому.
Кейс 1. Читаємо git status через модель трьох зон
Новачок дивиться на git status як на суцільний шум і комітить «наче все». Досвідчений читає його як карту трьох зон: що зафіксується просто зараз, що загубиться, а про що Git ще не знає. Уяви, що ти правив автотести й ось вивід:
On branch feature/login-tests
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: tests/login.spec.ts
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
modified: tests/login.spec.ts
modified: pages/LoginPage.ts
Untracked files:
(use "git add <file>..." to include what will be committed)
.env
Розбір по зонах — і чому кожен рядок важливий:
Changes to be committed— це staging. Сюди вже потрапитьlogin.spec.ts, але у тій версії, яку ти застейджив раніше. Якщо післяgit addти ще раз правив цей файл, у коміт піде стара версія, а не остання з диска.- Той самий
login.spec.tsводночас уChanges not staged. Це не помилка виводу: у staging лежить один знімок файлу, а в робочій директорії — новіший. Git чесно показує обидва стани; поки не зробиш повторнийgit add, свіжі правки в коміт не ввійдуть. LoginPage.tsтільки вnot staged. Логічно зв'язана зміна локатора лишиться поза комітом, якщо ти її не додаси. Ось так народжується «червоний тест після мержу»: спек оновлено, а сторінковий об'єкт забули застейджити..envвUntracked— тривожний дзвінок. Git його ще не відстежує, тобтоgit add .затягне секрет у коміт. Тут його місце — у.gitignore, а не в історії (див. кейс 2).
Мораль: перед комітом дивись не «наче все змінилось», а конкретно — чи всі логічно зв'язані файли в зоні to be committed і чи не заповзло туди зайве.
Кейс 2. Секрет у комітах: чому .gitignore заднім числом не рятує
Найгірший сценарій гігієни репозиторію: у коміт потрапив .env із реальним токеном, і це помітили не одразу. Інстинктивна реакція «додам у .gitignore й видалю файл» тут не працює — і треба розуміти чому.
# 1. Прибрати файл з відстеження, лишивши копію на диску
git rm --cached .env
# 2. Занести шаблон у .gitignore, щоб Git більше його не бачив
# (рядок ".env" всередині .gitignore)
# 3. Закомітити прибирання
git commit -m "Stop tracking .env"
Що тут відбувається і де пастка:
.gitignoreдіє лише на untracked-файли. Файл, який уже колись закомітили, Git відстежує далі, скільки б шаблонів ти не дописав. Тому крок 1 —git rm --cached— обов'язковий: він каже Git «забудь цей файл», не чіпаючи його на диску.- Але навіть після цього секрет нікуди не зник. Кроки 1–3 прибирають файл із майбутніх комітів, а в старих він лишається — знімок того коміту досі містить токен, і кожен, кому відкрито репозиторій, дістане його з історії.
- Єдина справжня реакція — ротація. Токен треба вважати скомпрометованим: згенерувати новий, старий відкликати. Це не «на всяк випадок», а обов'язковий крок, бо історію застосунок міг уже роздати клонами й дзеркалами.
Коротке правило для рев'ю: побачив .env, ключ чи пароль у діфі — блокуй мерж і сигналь про ротацію, а не проси «просто прибрати наступним комітом».
Кейс 3. Тест почав падати: знаходимо винний коміт історією
Локатор getByTestId('submit') раптом нічого не знаходить, хоча вчора все було зелене. Замість гадати, історія Git дає точну відповідь — і робить це локально й миттєво, бо вся вона лежить у тебе на диску. Ось таблиця «симптом → що робимо → що шукаємо»:
| Ситуація | Команда | Що вона дає |
|---|---|---|
| Хочу побачити останні зміни файлу з локатором | git log --oneline -- pages/LoginPage.ts | Список комітів, що чіпали саме цей файл, від свіжих до старих |
| Треба зрозуміти, хто і в якому коміті переписав конкретний рядок | git blame pages/LoginPage.ts | Для кожного рядка — коміт, автор і дата останньої зміни |
| Тест ще недавно був зелений, а зараз червоний, але коміт-винуватець невідомий | git bisect start | Двійковий пошук: Git сам перебирає коміти між «добрим» і «поганим» |
Як це виглядає для git bisect — і чому воно швидке:
git bisect start
git bisect bad # поточний стан зламаний
git bisect good v2.3.0 # на цьому релізі тест ще проходив
# Git переключає на середній коміт — ти проганяєш тест і кажеш вердикт:
git bisect good # або git bisect bad
# ...кілька ітерацій, і Git називає перший поганий коміт
git bisect reset # повернутися до вихідного стану
Що дивитися і чому:
bisect— це двійковий пошук по історії. Замість перебирати сотні комітів поспіль, він щоразу ділить діапазон навпіл, тож знаходить винуватця заlog2(N)кроків — для 500 комітів це близько дев'яти перевірок, а не п'ятисот.- Працює це саме тому, що історія локальна. Переключення між комітами не ходить у мережу — Git розкладає готовий знімок кожного стану, а не відкочує ланцюжок діфів, тому кожна ітерація майже миттєва.
blameвідповідає на «хто зламав локатор». Але обережно з висновками:blameпоказує останню зміну рядка, а не обов'язково автора баги — іноді рядок лише переформатували. Це відправна точка розслідування, а не вирок.- Спершу історія, потім здогади. Формулювання «тест ліг після коміту abc123,
blameвказує на зміну селектора» коштує рівно стільки, скільки під ним доказів з історії, — і це набагато сильніше за «здається, щось зламали на фронті».
Навіщо контроль версій QA
- Можу пояснити, що коміт фіксує авторство й намір зміни — хто її зробив, коли, у чому вона й задля чого, — і відкриває шлях до відкоту.
- Розумію, що автотести живуть у git-репозиторії, CI стартує на кожен
push, а історія — доказова база при розслідуванні регресій.
Централізовані проти розподілених
- Знаю різницю між централізованою (SVN, CVS, Perforce) і розподіленою (Git, Mercurial) моделлю: де лежить історія і чи потрібне з'єднання для коміту.
- Можу пояснити, що
git cloneтягне повну копію з усією історією, тому коміт, лог, гілки, дифи — локальні й офлайн. - Розумію два наслідки розподіленості: миттєвість операцій над історією (немає мережі) і відсутність єдиної критичної точки відмови.
Модель Git: знімки і хеші
- Знаю, що Git концептуально зберігає знімки цілих станів, а не ланцюжок діфів; незмінений вміст перевикористовується, а не копіюється.
- Розумію, що
git diff— це обчислене на льоту подання різниці, а не спосіб зберігання даних. - Можу пояснити, чому вміст адресується хешем (SHA-1): дедуплікація однакового вмісту плюс контроль цілісності.
Три зони і стани файлу
- Знаю маршрут зміни: робоча директорія → staging/index (крок
git add) → локальний репозиторій.git(крокgit commit). - Можу пояснити, навіщо окрема staging: вона визначає точний склад коміту й дає охайну історію з одного осмисленого коміту.
- Знаю різницю між
git addіgit commit: перший кладе поточний стан у чернетку, другий фіксує чернетку в історію. - Читаю
git statusчерез модель зон: розрізняю staged, modified і untracked.
Git vs GitHub, локальне й віддалене
- Знаю різницю між Git (інструмент) і GitHub/GitLab/Bitbucket (хостинг-сервіси з PR, задачами, CI поверх Git).
- Розумію, що remote (
origin) — це посилання на віддалену копію, аpush/pull— окремі свідомі дії обміну. - Пам'ятаю ключове:
git commitнічого не відправляє; доpushзміни бачу лише я, і CI не запуститься.
.gitignore і секрети
- Знаю, що
.gitignoreфільтрує тільки untracked; уже закомічений файл він не прибере — його треба зняти з відстеження явно черезgit rm --cached(файл лишається на диску). - Розумію, що засвічений в історії секрет підлягає ротації як скомпрометований, а не тихому видаленню наступним комітом.
git config
- Знаю три рівні конфігурації (system / global / local) і правило: що вужчий рівень, то він сильніший.
- Розумію, що
user.email— це підпис автора в коміті, а не логін; автентифікацію сервіс робить через SSH-ключ чи токен. - Можу пояснити, як
core.autocrlfі розсинхрон line endings дають «фантомні» зміни й крихкі порівняння в тестах.
Чим розподілена система контролю версій відрізняється від централізованої?
Питання
Що таке коміт і що в ньому, крім файлів?