При написанні коду легко щось зламати чи зіпсувати. Щоб уникнути цього, розробники використовують системи контролю версій для зберігання історії коду. Одна з таких систем – Git. Система дає змогу відстежувати зміни у файлах, зберігати їх та за потреби повертатися до будь-якої версії.
На відміну від більшості систем контролю версій, Git фіксує інформацію у вигляді набору знімків, а не просто як перелік змін. Завдяки цьому розробник бачить ширшу картинку. Часто початківці зосереджуються лише на базовому функціоналі Git, тому втрачають у продуктивності й обтяжують себе зайвими кроками.
Як зробити використання Git ефективнішим, розкажуть експерти NIX – Lead Magento Developer Андрій Сидоренко та Lead Software Engineer Євген Бодня.
Налаштування Git
Популярність Git багато в чому обумовлена його простотою. Зі старту маємо все необхідне. Базові налаштування прості. Достатньо вказати креди (ім’я та емейл) для позначення автора нових комітів.
Зазвичай у робочих проєктах використовують спільні креди для авторства. Їх зберігають у файлі .gitconfig у профілі користувача. Інколи замовники просять встановити власні креди. Тоді вказуємо їх для конкретного проєкту. Докладніше про налаштування кредів читайте за посиланням.
Як працювати з Git далі: через консоль та команди або в UI на кшталт VS Code чи GitHub Desktop? Передусім ви маєте розуміти, що відбувається «під капотом». Не слід забувати, що UI може мати певні обмеження. Інколи неможливо додавати до команди наявні опції зі значеннями. У консолі ж легше побачити дрібниці, як-от відступи.
Серед розробників давно існує дискусія: відступ має бути Space чи Tab? Припустимо, у PhpStorm відмінностей між цими символами немає. У консолі для відступу є конкретні знаки.
Прихильникам консолі експерти радять спробувати Tig. Ця утиліта імітує UI. У лівій частині екрана зібрані коміти, у правій – їхній вміст. Завдяки цьому можна швидко подивитися історію гілок, комітів та файлів без необхідності входити до віддаленого репозиторію. Зручно це і на етапі знайомства з проєктом. Ви легко зробите базове рев’ю коду, щоб зрозуміти принципи оформлення й опису комітів, прийнятих у вашій команді.
Хоча для типових задач UI зручніший. Наприклад, у програмному забезпеченні від Jetbrains, у Webstorm він має ідеальну структуру для Git і кнопки для всіх основних команд: від push та pull до видалення гілки й зміни імені коміту.
Початок роботи з Git
Створюєте папку, заливаєте в неї базовий код та задаєте команду git init. Так запускаємо ініціалізацію локального репозиторію.
Якщо підключаєтесь до наявного проєкту з певними комітами, то клонуйте цей проєкт до себе. Для цього запустіть команду git clone (https://git.service.com/your-rep.git).
Щоб відправляти створені в локальному репозиторії зміни до віддаленого репозиторію, під’єднайте його за допомогою git remote add origin (https://git.service.com/your-rep.git).
Після цього вже можна відправляти зміни або пушити, використовуючи команду git push origin your-branch-name.
Як віддалений репозиторій можуть виступати GitHub, GitLab, Bitbucket тощо. Вони подібні за функціоналом та інтерфейсом. Але відмінності все-таки є.
Наприклад, у GitHub приватні репозиторії доступні тільки для користувачів, які мають спеціальні права на доступ (колаборатори). Для таких репозиторіїв можна створювати команди та вказувати конкретних користувачів для кожного рівня доступу.
GitLab також дає змогу керувати доступом до репозиторіїв через ролі, групи та проєкти. Доступ налаштовується за допомогою груп, які об’єднують користувачів і надають їм різні рівні прав доступу до відповідних репозиторіїв.
Якщо працюєте з наявним проєктом, після створення локального репозиторію знайдіть у папці конфігураційний файл .git. З його допомогою можна побачити, де знаходиться видалений репозиторій. Останню ж інформацію можна отримати, скориставшись командою tig і переглянувши попередні коміти.
Чому варто маскувати папку .git на проді? Залишаючи до неї відкритий доступ через браузер, зловмисник може запустити скрипт, який витягне з цього репозиторію код вебсайту. Надалі він зможе провести рев’ю коду і знайти вразливості, щоб зламати ресурс.
Після налаштування репозиторіїв залишається додати gitignore-файл. Переважно він стандартний. У типових проєктах його можна навіть копіювати зі старого. Також IDE (PHPStorm, WebStorm, VS Code) має шаблон із базовими в ігноруванні файлами та папками. Звісно, ніхто не заважає вам самостійно скласти список файлів, які не варто тримати ані віддалено, ані локально.
Робота с gitignore-файлом проста: достатньо вказати шлях до конкретних файлів. Їх може виявитись багато. Передусім треба ігнорувати файли з чутливою інформацією: логіни, паролі, токени, логи тощо. Якщо вони знадобляться на проді, створіть N-sample. Скопіюйте в нього тільки структуру файлу. І тоді під час деплою ви самі зможете заповнити на сервері потрібні параметри.
Немає сенсу копіювати між репозиторіями папки налаштувань IDE. Ці системи чи їхні конфігурації можуть відрізнятися у ваших колег по проєкту. Там, де використання JavaScript обов’язкове, варто додавати в ігнор node_modules. Ця папка може затягнути від 200 МБ до 1 ГБ.
Експерти зауважують про ризик додавання у проєкти білдів. Це може викликати конфлікти в Git, і потім буде досить важко об’єднати гілки. Білди краще створювати саме в момент деплою – самотужки на сервері або на основі інших сервісів відповідно до процесів CI/CD у вашій команді.
Базові команди Git
Коли ви тільки починаєте знайомитись із Git, у вас може з’явитися спокуса вивчити якомога більше команд. Не поспішайте опанувати все й одразу. Для роботи вистачить того, що використовується у 90% випадків.
До базових команд Git відносять:
- git add: додає зміни з робочого каталогу до індексації;
- git commit: створює коміт на основі проіндексованих даних;
- git checkout: перемикає репозиторій на інший коміт або гілку. Також може скасовувати зміни у незакомічених файлах;
- git push: закидує зміни з локального репозиторію до віддаленого;
- git pull: завантажує зміни з віддаленого репозиторію до локального;
- git branch: створює, перейменовує та видаляє гілки;
- git diff: порівнює між собою створені коміти/гілки або показує зміни у файлі;
- git status: показує список змінених каталогів та файлів;
- git log: демонструє наявні коміти у репозиторії.
Також існують прапорці, які можна додавати до команд. Наприклад, git checkoub -b your-branch-name дозволяє створити гілку на перемкнутися на неї. За допомогою git commit -m "Your commit message" можна написати опис до коміту. git checkout -p повертає зміни окремих частин файлу. Команда запитує, що вона має зробити із кожним фрагментом. Аналогічним чином діє git add -p, але вже у випадку додавання змін не всього файлу, а його частин.
Згодом почитайте про менш поширені команди, які теж можуть знадобитися на якомусь проєкті. Часом про них можуть запитати на співбесіді, якщо технічні експерти захочуть перевірити глибину ваших знань.
До прикладу, команда git fetch. Вона подібна до git pull. Зміни стягуються, але застосовуються не автоматично, а лише за потреби за допомогою git merge.
git fetch дає змогу отримувати оновлення із віддалених репозиторіїв без автоматичного об’єднання із локальними гілками. Розробник може переглядати зміни перед об’єднанням, забезпечуючи контроль та уникнення конфліктів. Навіть як рідкісний сценарій використання цієї команди важливе для гнучкості й ефективного управління розвитком проєктів.
Деякі маловідомі команди цілком корисні у щоденній роботі. Наприклад, git stash зберігає зміни без застосування. Фактично це ніби кеш.
Уявімо ситуацію: ви не дописали для коміту код, але потрібно терміново пофіксити баг в іншій гілці. Якщо перемкнутися, зміни втрачаються. Якщо комітити незавершену частину, колег це може заплутати. git stash apply дозволить обійтися без коміту та повернути дані, коли ви повернетесь до своєї гілки.
Цікава команда git cherry-pick. Дозволяє не копіювати файли в іншу гілку (наприклад, із Develop- до Main-гілки), а відразу надсилати коміт. У пул реквестах стане в пригоді squash merge – допомагає об’єднати всі зміни та полегшує підтримку історії комітів у репозиторії. Замість неорганізованого списку комітів буде один великий коміт.
Не обов’язково пам’ятати всі команди. Є один лайфхак – спробуйте конфігураційний скрипт bash completion. При натисканні Tab він показує перелік доступних команд. Ви можете доповнювати базу даних для автозаповнення, щоб оптимізувати все під власні потреби чи інструменти, якими часто користуєтесь. Схожі скрипти існують для будь-якої консольної утиліти.
Робота з комітами та гілками
Перший крок – визначитися із правилами неймінгу. Це проблема багатьох початківців. Через брак досвіду і командної роботи часто джуни не дотримуються стандартів найменування комітів і гілок. Пізніше це може плутати інших розробників і самого джуна. Принаймні слід погуглити кращі практики неймінгу.
Наприклад, гілки для виправлення помилок позначають fix чи bugfix, а для імплементації нових фіч – feature або feat.
Є й менш очевидні нюанси. Припустимо, як почати назву коміту: з великої літери чи з малої? А кілька слів при іменуванні гілки потрібно поєднувати за допомогою дефісу або через нижнє підкреслення? Насправді коректні всі варіанти. Головне – щоб це були єдині правила для всієї команди.
Принципи використання змін, комітів та гілок
Репозиторій у Git складається з комітів – порцій змін. Вони створюють гілки. Припустимо, ви маєте головну гілку Main. Вам дають завдання зробити фічу. Для цього на основі Main-гілки ви створюєте гілку для розробки нової частини.
Коли закінчите таск, треба зробити коміт та завантажити до віддаленого репозиторію, а потім змерджити новостворену гілку в Main-гілку. Так проєкт оновиться, і результат вашої роботи стане частиною продукту.
Якби ви працювали над застосунком самотужки, то могли б обійтися взагалі без усіляких гілок. Це ж робота в один потік. У розподіленій команді, де кілька фахівців працюють над проєктом паралельно, без такої системи було б складно.
Уявімо, що разом із вами над другою фічею працює інший розробник. Він теж створює власну гілку на основі Main-гілки. Проєкт починає розгалужуватися. До речі, є сервіси для навчання роботі з Git, де можна роботи коміти й одразу бачити результат у вигляді Git-дерева. Така візуалізація допомагає краще зрозуміти роботу системи контролю версій та її особливості.
Та головне інше – як поєднати зміни з різних гілок? Адже ви та колеги можете вливати коміти в різні моменти, поза чергою. Важливо регулярно оновлювати свій репозиторій, щоб підтягнути зміни інших розробників та уникнути конфлікту при мерджі.
Конфлікти під час мерджу
Конфлікти під час злиття гілок – велика окрема тема. Можна написати не одну статтю про те, як їх долати та як їх уникати! Але на базовому рівні розберемо простий приклад.
Припустимо, що один розробник створює на основі Main свою гілку A і змінює значення якогось рядка на «ABC», а інший розробник також від Main створює гілку B, де в тому ж саме рядку пише «123». Якщо злити всі ці зміни назад до Main, між ними з’явиться конфлікт. Система ж не знає, який із двох надісланих комітів правильний, яка гілка пріоритетна. Тут, як зазначено вище, на допомогу приходить система контролю версій.
Знову ж таки: при розв’язанні конфліктів ви можете працювати як із консоллю, так і з UI.
Як підкреслює Євген, в останньому випадку все дуже просто. Наприклад, у WebStorm по центру екрана показано ваш код, а зліва та справа його версії – і ви покроково стрілочками обираєте те, що треба залишити. Також кольором виокремлено проблемні місця, і безпосередньо в інтерфейсі можна переписати коміт та зберегти. Це буде вважатися як нові стягнуті зміни.
Схожим чином працюють й інші тулзи для бекенду і фронтенду: від VS Code до IntelliJ IDEA.
Щоб набути впевненості у подібній роботі, експерти радять потренуватися – створити під час мерджу та пофіксити конфлікти між власними гілками. Принцип вирішення, або резолву, конфліктів теж доволі простий. Ви стягуєте до локального репозиторію оновлену гілку, куди бажаєте замерджити зміни, де є модифікований файл. Через це у вас з’являється конфлікт злиття гілок.
Ви виправляєте все, як треба. А потім створюєте спеціальний коміт для вирішення конфліктів (там буде окремий прапорець для позначення змін). Цей коміт показує: конфлікт має бути вирішено у такий-то спосіб. Після надсилання цього коміту до віддаленого репозиторію система буде знати про подолання проблеми. А тому подальший мердж пройде успішно!
Причини виникнення конфліктів
Найчастіше конфлікти відбуваються через забудькуватість розробників. Забули оновити свою локальну Main-гілку і підтягнути ці зміни у робочу гілку. Або ж робили зміни в одній гілці, потім в іншій, яку влили в основну, але пропустили попередню. Чи помилково задали різні значення однієї функції в кількох місцях. Аналогічно може діяти й інший розробник.
Як вихід із ситуації – перевірте свої дії та, якщо все коректно, спитайте колегу про причини його дій у своїй частині коду. Може, він теж помилився. Або раніше за вас дізнався про нові настанови замовника, про які менеджери чи ліди вам чомусь не повідомили.
У будь-якому разі завжди вирішує спілкування з командою. Якщо ж конфлікт незначний, вистачить скріншоту та короткого повідомлення колезі у месенджері.
Якщо конфлікт викликаний різними технічними рішеннями двох розробників, до обговорення варто залучити техліда або менеджера проєкту. Обговоріть, яке виконання найкраще відповідає кінцевим завданням проєкту. У своїх доводах намагайтеся спиратися на джерела та перевірені поради. Як аргументи підійдуть фахові статті з Medium, зі збірників бест практіс, книг тощо.
Трапляються конфлікти й через особливості процесів. Із досвіду експерти згадують кілька таких історій. На одному проєкті через брак ресурсів QA-фахівці перевіряли фічі лише після того, як заливали їх в основну гілку. При виявленні багу доводилося створювати гілку з багфіксом і мерджити її. Це займало чимало часу та призводило до помилок у Main-гілці.
Інший приклад – де зміни зливались до окремої QA-гілки і тільки після перевірки тестувальниками відправлялись до головної гілки. Але якщо QA-спеціалісти не встигали з тасками, фічі не потрапляли вчасно до Main. Розробники не могли завантажити актуальний стан застосунку, не тягнучи неперевірені зміни. Джуни в подібних випадках нічого не вирішують, а просто слідують заданій у проєкті моделі роботи. Однак задля професійного розвитку варто знати про різні сценарії.
Оптимальним варіантом є не локальне злиття змін, а сервіси віддалених репозиторіїв. Це можуть бути пул реквести у GitHub та Azure або мердж реквести у GitLab. Це не зміни, а запит до них. Тобто команда отримує повідомлення про нові зміни у коді, завдяки чому інші розробники чи тестувальники можуть перевірити запропоновані зміни та обговорити їх.
Пул реквест виступає як додаткова нотифікація для команди: основна гілка змінюється, тож час оновитися. Так вдається не відставати у процесі розвитку та масштабування проєкту, а так мінімізувати ризики. Після злиття не виникатиме по 20 конфліктів, для розв’язання яких треба обійти всю команду. Щонайбільше буде кілька некритичних проблем.
Як уникнути труднощів при перевірці пул реквестів? Експерти NIX рекомендують при роботі над великими фічами розбивати їх на частини. На основі гілки, створеної під нову фічу, з’являтиметься ще декілька гілок для окремих частин. Ви зможете віддавати ці частини на перевірку одну за одною. Інакше в пул реквесті будуть відправлятися сотні файлів. На їхню перевірку може знадобитися кілька днів. Наявність малих пул реквестів дасть змогу не чекати зі злиттям змін іншого функціоналу, над яким паралельно працюють розробники.
Хорошою практикою є встановлення захисту у віддалених репозиторіях на головні гілки – Main, Staging, Develop. Тоді будь-які зміни будуть прийматися після розгляду пул реквесту, а це вбереже від критичних помилок у проєкті.
Видалення гілок і даних після мерджу
Після успішної заливки робочої гілки до Main-гілки першу треба видалити. Це можна зробити вручну або за допомогою автовидалення після мерджу, наприклад, до основної гілки (задається в налаштуваннях віддаленого репозиторію).
Інформація зі старої гілки може знадобиться пізніше. Наприклад, якщо колега захоче дізнатися про виконані вами зміни, набагато швидше звернутися до вас за уточненнями чи порадою. Тим паче, за певний час і кілька ітерацій змін попередня гілка могла втратити актуальність.
Вносити зміни у стару гілку не треба, бо всі нововведення чи виправлення мають відображатися у новій фікс-гілці. Та якщо не чистити першу, то згодом їх буде сотні, що ускладнить керування репозиторієм і може заплутати команду.
Після мерджу у репозиторії можуть зникати якісь файли та папки. Причин цього може бути чимало. Наприклад, хтось запушив до репозиторію видалення окремих файлів.
Часом це викликано новими вимогами, інколи – помилкою. У такому випадку в історії Git знайдіть автора останніх змін і командою git revert «відкотіть» коміт. Зручність Git тут полягає в тому, що в ньому нічого не загубиться. Саме тому з цією системою немає необхідності в детально закоментованих шматках коду, що нібито може знадобитися пізніше. Так полюбляють робити джуни. Однак який сенс зберігати старий код, якщо можна просто «відкотитись» до попередньої версії й все там роздивитись.
А ще інколи після мерджу можуть зникнути самі ж зміни, над якими ви працювали. Як згадує один з експертів, колись він рефакторив код майже вісім годин, але після злиття Develop-гілки у робочу з новими змінами все пропало. А сталося це через те, що зміни не були закомічені локально, тому їх не зафіксував Git. Тож варто комітити або використовувати вже згадану команду git stash.
Хоча на допомогу може прийти й ваша UI. Наприклад, у WebStorm доступна локальна історія. Якщо раптово зникли зміни, подивіться, яким був файл із кодом хвилину тому, до невдалого мерджу. Це власне копія файлу.
З якою б складністю ви не зіткнулися, пам’ятайте: завжди треба знаходити час, щоб розібратися у нюансах роботи Git. Це збереже вам море часу, зусиль і нервів.