Скоро мені знадобиться знайомити одного майбутнього молодого розробника з таїнством користування системами контролю версій (надалі VCS, version control system) і тому щоб трохи систематизувати те, що я збирався розповісти, вирішив написати цей допис. Він розрахований на зовсім базовий рівень роботи і тому тут багато розжовувань, які більш досвідченим особам наврядчи будуть цікаві. Знайомство одразу буду проводити на прикладі сучасних розподілених систем, в нашому випадку Mercurial.
Навіщо воно треба?
Для людей які хоч трохи займались програмуванням відповідь має бути очевидною, але на всяк випадок нагадаю: під час роботи над чимось ви сто відсотків будете проходити якісь віхи розробки і контроль версій дозволить отримувати стан проекту на певний момент. Плюс можна створювати гілки розробки. Уявіть, що при побудові будинку ви вирішите добудувати якийсь незапланований поверх, але не впевнені чи все триматиметься як слід. Ви віртуально дублюєте поточний будинок, будуєте свій поверх, поки інші будівельники тим часом працюють по запланованому графіку. Потім ви вирішуєте, що результат вашої роботи вас влаштовує ви плеском в долоні вставляєте його у існуючий будинок. А може будівельники десь помилились при розрахунках і набудували якусь фігню, то вони можуть так само швидко відкотитись до місця коли щось пішло не так.
До речі, місце де зберігаютсья всі стани проекту в термінах VCS називаєтсья репозитарій, а місце в якому ви вносите правки – відповідно робоча копія. Процес відправки набору змін з робочої копії до репозиторію – це операція commit. Репозитарії часто зберігають десь подалі, не на робочих машинах, щоб у випаку коли у вас, наприклад, здохне комп, весь код можна буде повіністю відновити. У мене був гіркий досвід збереження єдиного екземпляру коду одного сайту на ноуті, який згодом сперли… З тих пір я не розлучний -з VCS та бекап-системами типу Dropbox і зберігаю всі важливі дані в інтернеті. Плюс зручно, що можна з легкістю отримувати точні копії на будь-яку машину.
І нарешті ще одна зручна штука в користуванні VCS, хоч і похідна від нього – це можливість робити code review – огляд змін які зробив розробник між версійми коду. Принципироботи code review-систем чудово накладаються на принципи роботи VCS і тому вони часто бувають нерозлучні, коли якість коду має велике значення.
Розподілені VCS
Як писалося вище, існують поняття репозитарію та робочої копії. У VCS, які були популярні донедавна було чітке розподілення обов’язків: репозитарій був один і центральний, часто на виділеному сервері. Кожний commit відправляв дані з робочої копії до репозиторію. В певному сенсі це було не завжди зручно у випадку, коли ви хочете закинути кілька змін різними commit’ами на сервер, що знаходиться в інтернеті. По перше, процес зазвичай досить повільний, по-друге, за відсутності зв’язку з інтернетом ви взагалі не зможете зробити commit.
Тому щоб побороти подібні недоліки прийшли розподілені системи контролю версій: Mercurial, Git, Bazaar, тощо… Суть їх проста: кожна копія проекту є одночасно і репозитарієм і робочою копією. Тобто вся робота по суті виконуєтсья локально, але існує механізм синхронізації між самотніми репозитаріями. За такої організації вищезгадані проблеми з відсутністю зв’язку з інтернетом нівелюються – ви можете робити весь спектр операцій з VCS локально. Але як я вже казав зберігати дані локально – небезпечно, тому зазвичай в інтернеті відкривають віддалені репозитарії, які слугують таким собі хабом між людьми які працюють з даними. Тобто синхроніхація репозитаріїв іде не кожен-з-кожним (хоча і такий принцип організації можливий), а всі синхронізуютсья лише з віддаленим (але навіть за його недоступності робота може продовжуватись).
Розподілених VCS є кілька. Найбільш популярні наразі Git та Mercurial. Вибір між ними справа релігійна, але якщо цікаво порівняти, то ось гарний аналіз від Google (англ).
Працюємо з Mercurial
Як я вже говорив, робоча копія Mercurial фактично є заодно і репозиторієм, але всі зрізи версій зберігаються в каталозі .hg з мета-інформацією репозиторія. Поза цією папкою власне робоча копія. Не видаляйте папку, бо втратите репозиторій!
Варіантів для отримання локального репозитарію проекту два:
- якщо він існує на іншому комп’ютері чи сервері і тоді вам треба виконати команду clone: $ hg clone http://path/to/your/repository
вона створить копію репозитарію з вказоного URL в поточному каталозі
- якщо ви створюєте новий проект, то потрібно виконати команду init в папці з проектом: $ hg init
потім додати всі потрібні файли командою add:
$ hg add
Увага! У вас в каталозі можуть бути файли, які не варто зберігати: наприклад, “.obj”-файли C++ чи Пітонівські “.pyc”.
для цього в кореневій папці (на одному рівні з папкою “.hg”) треба створити файл .hgignore з вмістом типу:
glob:*.bak glob:*.obj glob:Debug glob:Release
і так далі… Можна також прописувати не лише файли, а і цілі каталоги (Debug та Release в прикладі вище).
Далі можна працювати з кодом. Якщо додавались нові файли – не забувати виконувати команду add. Коли потрібно залити зміни в репозитарій виконуємо команду commit:
$ hg commit
Тепер варто трохи поговорити про синхронізацію репозитаріїв, але для цього треба зробити невеликий ліричний відступ. Із-за розподіленої архітектури дерево ревізій в Mercurial утворює досить цікаві завитки. В же централізованих системах дерево ревізій представляє собою ланцюжок. Тобто робота виглядає наступним чином:
- була деяка початкова ревізія №1, яку для подальшої роботи стягнули два розробники
- обидва щось поправили і один був першим, хто закомітив зміни на сервер, і з’явилась ревізія №2
- другий спробував закомітитись, але сервер відмовив, вказавши що не може залити зміни поверх версії №1. Тому другий розробник має забрати собі версію №2 з репозиторія та об’єднати (або як часто кажуть “змерджити” від англомовного терміну merge) докупи зміни з ревізії №2 та свої власні правки. Часто це проходить автоматично, але буває, що треба ручна правка, коли зміни торкаються однакових фрагментів коду. Потім об’єднаний код можна комітити в репозиторій і так з’явиться ревізія №3
Таким чином у централізованих системах є лише одна “головна” ревізія, а самі ревізії йдуть одна за одною. Ось так це виглядає схематично:
У розподілених систем нема центрального сервера, а всі коміти йдуть локально, тому типовий приклад роботи з ними наступний:
- є центральний хаб з якого два розробники забирають єдину ревізію №1
- обидва роблять правки та локальні коміти, таким чином і у першого, і у другого на комп’ютері з’являється по дві ревізії: загальна №1 і у кожного власна №2
- вони обидва синхронізують свої копії зі спільним репозитарієм і в результаті на спільному репозиторії з’являється три ревізії: №1, №2 та №3. Таким чином утворилося дві “головні” ревізії №2 та №3.
- один з розробників знову синхронізує репозиторій і у нього з’являєтясь копія спільного. Він може оновити локальну копію до однієї з головних ревізій і продовжити роботу в цій “підгілці”, а може об’єднати (змержити) зміни з №2 та №3 і закомітити їх як ревізію №4: таким чином граф ревізій утворить ромб.
Синхронізація в Mercurial однонаправлена: тобто за одну операцію можна або залити свої ревізії у віддалений репозиторій (операція push), або отримати звідти ревізії у свій локальний (операція pull).
$ hg pull http://path/to/your/repository $ hg push http://path/to/your/repository
Поки що це вся теоретична частина. Якщо ви працюєте під ОС Windows, то життя собі можна значно полегшити, якщо поставити собі TortoiseHg – графічний фронт-енд для роботи з Меркуріал. Плюс роботу можна зробити зручнішою та розширити потужність системи поправивши конфігураційний файл Mercurial. Про це в наступній частині.