Просунуте використання Гіта або як вийти на пенсію на півроку раніше?

 

Не знаю, якою мовою програмування ви пишете, але впевнений, що використовуєте Гіт при розробці. Інструментів для супроводу розробки стає все більше, але навіть самий маленький тестовий проект, я незмінно починаю з команди git init. А протягом робочого дня набираю в середньому ще 80 команд, звертаючись до цієї системи контролю версій.

 

Я витратив купу нервів, коли став переучуватися на десятипальцевий метод друку. У підсумку це стало самим правильним рішенням щодо поліпшення особистого робочого процесу. Серед наступних за важливістю оптимізацій варто поглиблене освоєння Гіта.

 

Взагалі написано багато статей про Гіті, але вони не йдуть далі офіційної документації, а спрощувати роботу автори пропонують самописними милицями. Я впевнений, що вивчати Гіт потрібно на конкретних прикладах завдань, а підвищувати ефективність роботи з ним – стандартизованими засобами.

 

Кому буде корисна ця стаття?

 

Ви вже освоїли джентльменський набір Гіта і готові рухатися далі? Існує 2 шляхи:

 

  1. Освоїти скорочені команди – аліаси. Вони майже завжди складені мнемонічно і легко запам’ятовуються. Забути оригінали команд проблематично, я легко їх набираю, коли це потрібно. Плюс не збиваюся з думки, перевіряючи щось в Гіті в процесі написання коду.
  2. Дізнатися про додаткові прапори до команд, а також їх об’єднання між собою. Я розумію, що хтось ненавидить скорочення. Для вас теж є цікавий матеріал в статті – як підвищити корисність і зручність виводу команд, а також як вирішувати не самі тривіальні, але часто зустрічні на практиці завдання.

 

Присвятіть описаним у статті експериментів пару годин сьогодні, і заощадьте за приблизними розрахунками півроку робочої життя.

 

Ласкаво просимо під кат!

 

Підготовка

 

Серед розробників стандарту альтернативи Bash є Zsh – просунута програмна оболонка, підтримуюча тонку настройку. А серед користувачів Zsh стандартом є використання Oh My Zsh – набору готових налаштувань для Zsh. Таким чином, встановивши цей комплект, ми з коробки отримаємо набір хаків, які роками збирала і напрацьовувало для нас співтовариство.

 

Дуже важливо відзначити, що Zsh є і для Linux, і для Mac, і навіть для Windows.

 

Установка Zsh і Oh My Zsh

 

Встановлюємо Zsh і Oh My Zsh по інструкції однією командою:

 

# macOS
brew install zsh zsh-completions && sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

# Ubuntu, Debian, ...
apt install zsh && sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

 

Оскільки завдання – оптимізувати взаємодію з Гітом, додамо до Zsh кілька плагінів. Відкрийте файл ~/.zshrc і додайте до списку plugins:

 

plugins=(git gitfast)

 

Разом:

 

  • git – набір аліасів і допоміжних функцій;
  • gitfast – покращене автодоповнення для Гіта.

 

Установка tig

 

І останній штрих – установка консольної утиліти tig:

 

# macOS
brew install tig

# Ubuntu, Debian, ...
# https://jonas.github.io/tig/INSTALL.html

 

Про неї поговоримо далі.

 

Біг на практиці

 

Розбиратися з Гітом краще всього на прикладах вирішення конкретних завдань. Далі розглянемо завдання з щоденної практики і варіанти їх зручного рішення. Для цього розглянемо якийсь репозиторій з текстовими файлами.

У жовтих блоках вказаний основний аліас для розв’язання задачі з розділу. Вивчіть тільки його, а все інше залиште для загального розвитку.

Перевіряємо стан робочої директорії

 

Почнемо з самої базової речі. Ми трохи попрацювали і тепер давайте подивимося, що відбувається в робочій директорії:

 

$ git status

On branch master
Changes to be committed:
 (use "git reset HEAD <file>..." to unstage)

 new file: e.md

Changes not staged for commit:
 (use "git add <file>..." to update what will be committed)
 (use "git checkout -- <file>..." to discard changes in working directory)

 modified: b.md

Untracked files:
 (use "git add <file>..." to include in what will be committed)

 d.md

 

Поточний стан всіх файлів описано дуже детально, дані додаткові керівництва до дії. Дуже корисно на перших порах використання Гіта, але для щоденної роботи дуже багато зайвого. Давайте знизимо рівень шуму додатковими ключами:

 

$ git status -sb

## master
 M b.md
A e.md
?? d.md

 

Ага, ми знаходимося в гілці master, змінили файл b.md (Modified) і створили два файли, додавши перший в індекс Гіта (Added), а другий залишивши поза індексом (??). Коротко і ясно.

 

Залишилося оптимізувати нескінченний введення цієї команди аліасом «git status with branch»:

Показати скорочений статус робочої директорії

$ gsb # git status -sb

 

Створюємо комміт

 

Продовжуємо.

 

Звичайно, ви вмієте створювати коміти. Але давайте спробуємо оптимізувати рішення і цієї простої задачі. Додаємо всі зміни в індекс аліасом «git add all»:

 

$ gaa # git add --all

 

Перевіряємо, що в індекс потрапило саме те, що нам потрібно з допомогою псевдонімів «git diff cached»:

 

$ gdca # git diff --cached

diff --git a/b.md b/b.md
index 698d533..cf20072 100644
--- a/b.md
+++ b/b.md
@@ -1,3 +1,3 @@
 # Beta

-Next step.
+Next step really hard.
diff --git a/d.md b/d.md
new file mode 100644
index 0000000..9e3752e
--- /dev/null
+++ b/d.md
@@ -0,0 +1,3 @@
+# Delta
+
+Body of article.

 

Читайте також  Архітектура складних чат-ботів

Хм, в один комміт повинні потрапляти зміни, які вирішують єдину задачу. Тут же зміни обох файлів ніяк не пов’язані між собою. Давайте виключимо файл d.md з індексу аліасом «git reset undo»:

 

$ gru d.md # git reset -- d.md

 

І створимо комміт аліасом «git commit»:

 

$ gc # git commit

 

Пишемо назву коміта і зберігаємо. А слідом створюємо ще один комміт для файлу d.md більш звичної командою з допомогою псевдонімів «git commit message»:

 

$ gaa # Вже знайомий аліас
$ gcmsg "Add new file" # git commit -m "Add new file"

 

А ще ми можемо…

 

… коммітити змінені файли з індексу однією командою:

 

$ gcam "Add changes" # git commit -a -m "Add changes"

 

… дивитися зміни за словами замість рядків (дуже корисно при роботі з текстом):

 

$ gdw # git diff --word-diff

 

… додавати файли по частинах (дуже корисно, коли потрібно додати в комміт тільки частину змін з файлу):

 

$ gapa # git add --patch

 

… додавати в індекс тільки файли, які вже перебувають під наглядом Гіта:

 

$ gau # git add --update

 

Разом:

Додати в індекс / Створити комміт

$ ga # git add
$ gc # git commit

 

Виправляємо комміт

 

Назва останнього коміта не пояснює зроблених нами змін. Давайте переформулюємо:

 

$ gc! # git commit -v --amend

 

І в контекстному текстовому редакторі назвемо його більш зрозуміло: "Add Delta article". Упевнений, ви ніколи не використовуєте ключ -v, хоча при редагуванні опису коміта він показує всі зроблені зміни, що допомагає краще зорієнтуватися.

 

А ще ми можемо…

 

… внести в комміт зміни файлів, але не чіпати опис:

 

$ gcn! # git commit -v --no-edit --amend

 

… внести всі зміни файлів відразу в комміт, без попереднього додавання індекс:

 

$ gca! # git commit -v -a --amend

 

… скомбінувати дві попередні команди:

 

$ gcan! # git commit -v -a --no-edit --amend

 

Ну і важливо ще раз відзначити, що замість набору повної регулярно використовується команди git commit -v --amend, ми пишемо всього три символи:

Змінити останній комміт

$ gc! # git commit -v --amend

 

Починаємо працювати над новою фичей

 

Створюємо нову гілку від поточної аліасом «git checkout branch»:

 

$ gcb erlang # git checkout --branch erlang

 

Хоча ні, краще напишемо статтю про більш сучасний мовний Еліксир аліасом «git branch з ключем move» (перейменування в Гіті робиться через move):

 

$ gb -m elixir # git branch -m elixir

 

Тут логічно було б використовувати псевдонім gbmv, але його, на жаль, ще не придумали. Хороший варіант для контрибьюта.

 

Вносимо зміни в репозиторій і створюємо комміт, як вже вміємо:

 

$ echo "# Еліксир — міць Ерланга з витонченістю Рубі." > e.md
$ gaa && gcmsg "Add article about Elixir"

 

І запам’ятовуємо:

Створити нову гілку

$ gcb # git checkout --branch

 

Зливаємо зміни

 

Тепер додаємо нашу нову статтю про Еліксир в master. Спочатку перемкнемося на основну гілку аліасом «git checkout master»:

 

$ gcm # git checkout master

 

Ні, серйозно. Одна з найбільш часто використовуваних команд у три легко запам’ятовуваних символи. Тепер мерджим зміни аліасом «git merge»:

 

$ gm elixir # git merge elixir

 

Упс, а в master хтось вже встиг внести свої зміни. І замість красивої лінійної історії, яка прийнята у нас в проекті, створився ненависний мердж-комміт.

Злити гілки

$ gm # git merge

 

Видаляємо останній комміт

 

Нічого страшного! Потрібно просто видалити останній комміт і спробувати злити зміни ще раз «git reset hhard»:

Видалити останній комміт

$ grhh HEAD~ # git reset --hard HEAD~

 

Вирішуємо конфлікти

 

Стандартна послідовність дій checkout – rebase – merge для підготовки лінійної історії змін виконується наступною послідовністю аліасів:

 

gco elixir # git checkout elixir
grbm # git rebase master
gcm # git checkout master
gm elixir # git merge elixir

 

Всі вони так часто використовуються вже відлітають від пальців, і роблячи подібні операції, немає необхідності замислюватися про те, який набір букв потрібно набирати. І не забувайте, що в Zsh можна доповнювати назви гілок клавішею Tab.

Зробити ребейз

$ grb # git rebase

 

Відправка змін на сервер

 

Спочатку додаємо origin аліасом «git remote add»:

 

$ gra origin git@github.com/... # git remote add origin git@github.com/...

 

А потім відправляємо зміни безпосередньо в поточну гілку репозиторію («gg» – подвоєне g на початку команди вказує на виконання дії в поточну гілку):

 

$ ggpush # git push origin git_current_branch

 

Ви також можете…

 

… відправити зміни на сервер з установкою upstream аліасом «git push set upstream»:

 

$ gpsup # git push --set-upstream origin $(git_current_branch)

Відправити зміни на сервері

$ gp # git push

 

Отримуємо зміни з сервера

 

Робота кипить. Ми встигли додати нову статтю f.md в master, а наші колеги змінити статтю a.md і відправити цю зміну на сервер. Ця ситуація теж вирішується дуже просто:

Читайте також  Інформаційна безпека банківських безготівкових платежів. Частина 1 — Економічні основи

 

$ gup # git pull --rebase

 

Після чого можна спокійно відправляти зміни на сервер. Конфлікт вичерпаний.

Отримати зміни з сервера

$ gl # git pull

 

Видаляємо злиті гілки

 

Отже, ми успішно влили в master кілька гілок, в тому числі і гілку elixir з попереднього прикладу. Вони нам більше не потрібні. Можна видаляти аліасом «git branch delete another»:

 

$ gbda # git branch --no-color --merged | command grep -v "^(*|s*(master|develop|dev)s*$)" | command xargs -n 1 git branch -d

 

Дуже гарна й хитра команда. Зазвичай я забуваю очищати гілки які втратили актуальність і ця витончена команда – справжній порятунок. Якщо не хочете використовувати псевдонім, просто скопіюйте повний варіант команди собі в нотатки, і виконуйте їх в міру необхідності.

 

Створюємо тимчасовий комміт

 

Робота над новою статтею h.md про Haskell йде повним ходом. Написана половина і потрібно отримати відгук від колеги. Недовго думаючи, набираємо аліас «git work in progress»:

 

$ gwip # git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit --no-verify -m "--wip-- [skip ci]"

 

І тут же створюється комміт з назвою Work in Progress, пропускає CI і видаляють зайві файли. Відправляємо гілку на сервер, говоримо про це колезі і чекаємо рев’ю.

 

Потім цей комміт можна скасувати і повернути файли в початковий стан:

 

$ gunwip # git log -n 1 | grep -q -c "--wip--" && git reset HEAD~1

 

А перевірити, чи є у вашій гілці WIP-коміти можна командою:

 

$ work_in_progress

 

Команда gwip – досить надійний аналог stash, коли потрібно перейти на сусідню гілку. Але в Zsh є багато аліасів і для самого stash.

Додати тимчасовий комміт / Скинути тимчасовий комміт

$ gwip
$ gunwip

 

Ховаємо зміни

 

З цією командою треба бути обережним. Файли можна сховати, а потім необережною дією видалити назовсім, благо є reflog, в якому можна спробувати знайти втрачені напрацювання.

 

Давайте сховаємо файли, над якими працюємо, аліасом «git stash all»:

 

$ gsta # git stash save

 

А потім повернемо їх назад аліасом «git stash pop»:

 

$ gstp # git stash pop

 

Або більш безпечним методом «git stash all apply»:

 

$ gstaa # git stash apply

 

Ви також можете …

 

… подивитися, що конкретно ми сховали:

 

gsts # git stash show --text

 

… скористатися скороченнями для пов’язаних команд:

 

gstc # git stash clear
gstd # git stash drop
gstl # git stash list

Сховати зміни / Дістати зміни

$ gsta
$ gstaa

 

Шукаємо баг

 

Інструмент git-bisect, який неодноразово рятував мені життя, теж має свої аліаси. Починаємо з запуску процедури «двійкового пошуку помилки» аліасом «git bisect start»:

 

$ gbss # git bisect start

 

Відзначаємо, що поточний, останній в гілці, комміт містить помилку, аліасом «git bisect bad»:

 

$ gbsb # git bisect bad

 

Тепер помічаємо комміт, що гарантує нам робочий стан програми «git bisect good»:

 

$ gbsg HEAD~20 # git bisect good HEAD~20

 

А тепер залишається продовжувати відповідати на питання Гіта фразами gbsb або gbsg, а після знаходження винуватця скинути процедуру:

 

$ gbsr # git bisect reset

 

І я дійсно пишу ці скорочення при використанні цього інструменту.

Пошук коміта з помилкою

$ gbss # git bisect start
$ gbsb # git bisect bad
$ gbsg # git bisect good
$ gbsr # git bisect reset

 

Шукаємо призвідника свавілля

 

Навіть з високим відсотком покриття коду тестами, ніхто не застрахований від ситуації, коли додаток падає і люб’язно вказує на конкретну позицію з помилкою. Або, наприклад, в нашому випадку ми хочемо дізнатися, хто допустив помилку в другій сходинці файлу a.md. Для цього виконайте команду:

 

$ gbl a.md -L 2 # git blame -b -w a.md -L 2

 

Бачите, контрибьюториOh My Zsh зробили аліас не просто на команду git blame, а додали в нього ключі, які спрощують пошук безпосередньо призвідника.

 

Bonus

 

Перегляд списку комітів

 

Для перегляду списку комітів використовується команда git log з додатковими ключами форматування виводу. Зазвичай цю команду разом з ключами заносять в кастомні аліаси Гіта. Нам з вами пощастило більше, у нас вже є готовий аліас з коробки: glog. А якщо ви встановили утиліту tig за порадою з початку статті, то ви абсолютний чемпіон.

 

Тепер, щоб повивчати історію комітів в консолі в дуже зручному вигляді, потрібно набрати слово git навпаки:

 

$ tig

 

Утиліта також дає пару корисних доповнень, яких немає в Гіті з коробки.

 

По-перше, команда для пошуку по вмісту історії:

 

$ tig grep

 

По-друге, перегляд списку всіх джерел, гілок, тегів разом з їх історією:

 

$ tig refs

 

Читайте також  Доказ наявності місць, де симетрії не можуть існувати

По-третє, можливо знайдете щось цікаве для себе самі:

 

$ tig --help

 

Випадково зробив git reset --hard

 

Ви працювали над гілкою elixir весь день:

 

$ glog

* 17cb385 (HEAD -> elixir) Refine Elixir article
* c14b4dc Add article about Elixir
* db84d54 (master) Initial commit

 

І під кінець випадково видалили всі:

 

$ grhh HEAD~2
HEAD is now at db84d54 Initial commit

 

Не треба панікувати. Найголовніше правило – перестаньте виконувати будь-які команди в Гіті і видихніть. Всі дії з локальним сховищем записуються в спеціальний журнал – reflog. З нього можна дістати хеш потрібного коміта і відновити його в робочому дереві.

 

Давайте заглянемо в рефлог, але не звичайним способом через git reflog, а більш цікавим з докладною розшифровкою записів:

 

$ glg -g

 

Знаходимо хеш потрібного коміта 17cb385 і відновлюємо його:

 

# Створюємо нову гілку з нашим комітом і перемикаємося на неї
$ gcb elixir-recover 17cb385

# Видаляємо стару гілку 
$ gbd elixir

# Перейменовуємо відновлену гілку назад
$ gb -m elixir

 

Випадково замість створення нового коміта вніс зміни до попереднього

 

Тут нам знову на допомогу приходить рефлог. Знаходимо хеш оригінального коміта 17cb385, якщо ми виробляємо скасування коміта відразу ж, то замість пошуку хеш можемо скористатися швидкої посиланням на нього HEAD@{1}. Слідом робимо м’який скидання, індекс при цьому не скидається:

 

# М'який скидання на оригінальний комміт
$ grh --soft HEAD@{1} # git reset -soft

# Коммитим правильно
$ gcmsg "Commit description"

 

Гілка занадто сильно застаріла

 

Буває починаєш працювати над фичей, але її реліз відкладається на невизначений термін. Робиш комміт і перемикаєшся на інші завдання. Разом з командою вносиш купу змін у майстер і через час повертаєшся до гілки з фичей. Пробуєш зробити ребейз, але він пропонує розібрати конфлікти в десятці комітів. Можна спробувати вирішити їх усі або зробити простіше.

 

Давайте розглянемо на прикладі гілки з фичей під назвою elixir:

 

# Перемикаємося на master
$ gcm # git checkout master

# Створюємо нову актуальну гілку для оригінальної фічі
$ gcb elixir-new # git checkout --branch elixir-new

# Переносимо єдиний комміт з фичей з застарілою гілки в нову
$ gcp elixir@{0} # git cherry-pick elixir@{0}

 

Ось так, замість спроби оновлення гілки, ми беремо і без проблем переносимо один єдиний комміт.

 

Видалення важливих даних репозиторію

 

Для видалення важливих даних з репозиторію, у мене збережений такий фрагмент:

 

$ git filter-branch ----force index-filter 'git rm --cached --ignore-unmatch <path to your file->' --prune-empty --tag-name-filter cat -- --all && git push origin --force --all

 

Виконання цієї команди поламає ваш stash. Перед її виконанням рекомендується дістати всі заховані зміни. Детальніше про це прийомі по посиланню.

 

Звернення до попередньої гілки

 

При виконанні деяких команд, які очікують на вхід назву гілки, ми можемо передати дефіс - в якості посилання на гілку, з якої ми прийшли. Особливо добре використовувати цей трюк для чекаута:

 

$ gco - # git checkout -
$ gm - # git merge -
$ grb - # git rebase -

 

Видалення всіх файлів, зазначених в .gitignore

 

Ще одна часта невдача – занадто пізно додати .gitignore якісь небажані файли або теки. Для того, щоб вичистити їх з репозиторію (і видалити з диска) вже є готові ключі для команди git clean:

 

$ gclean -X # git clean -Xfd

 

Будьте обережні!

 

Як правильно відслідкувати читайте далі.

 

Навіщо багатьом командам потрібен ключ --dry-run?

 

Ключ --dry-run потрібен як раз в якості обережності при завданнях видалення та оновлення. Наприклад, у попередньому розділі описаний спосіб видалення всього, що вказано в файлі .gitignore. Краще проявити обережність і скористатися ключем --dry-run, подивитися список всіх файлів до видалення, і тільки потім виконати команду без --dry-run.

 

Висновок

 

У статті показується точка для оптимізації трудової діяльності програміста. Запам’ятати 10-20 мнемонічних скорочень не становить праці, забути оригінальні команди практично неможливо. Аліаси стандартизовані, так що при переході всієї команди на Zsh + Oh My Zsh, ви зможете працювати з тими ж швидкістю і комфортом, навіть при парному програмуванні.

 

Куди рухатися далі?

 

Пропоную наступні варіанти:

 

  1. Нарешті-то розберіться, як Гіт влаштований всередині. Дуже допомагає розуміти, що ти робиш і чому те, що ти хочеш зробити не виходить.
  2. Не лінуйтеся зайвий раз заглянути в документацію до команд: git --help або ghh.
  3. Подивіться повний список аліасів по посиланню. Намагатися запам’ятати їх все – безумство, але використовувати перелік в якості збірки набору цікавих команд та ключів до них – гарна ідея.

 

Деякі аліаси зроблені нетривіально, але виявляються дуже корисними на практиці. Багато з представлених аліасів є не просто скороченнями, а невеликими функціями, які ще більше оптимізують роботу. Користуватися Гітом стало приємніше, якість комітів підвищилося.

 

Сподіваюся, матеріал виявився корисним, і ви змогли дізнатися для себе щось нове. А може бути вже почали активно впроваджувати новий підхід. Удачі!

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *