== Гросмейстерство Git ==

Тепер ви вже повинні вміти орієнтуватися в сторінках *git help* і розуміти майже все. Однак точний вибір команди, необхідної для вирішення конкретної проблеми, може бути виснажливим. Можливо, я збережу вам трохи часу: нижче наведені рецепти, які знадобилися мені в минулому.

=== Релізи вихідних кодів ===

У моїх проектах Git управляє в точності тими файлами, які я збираюся архівувати і пускати в реліз. Щоб створити тарбол з вихідними кодами, я виконую:

 $ git archive --format=tar --prefix=proj-1.2.3/ HEAD

=== Комміт змін ===

У деяких проектах може бути трудомістким повідомляти Git про кожне додавання, видаленні та перейменування файлу. Замість цього ви можете виконати команди

 $ git add .
 $ git add -u

Git прогляне файли в поточному каталозі і сам подбає про деталі. Замість другої команди add, виконайте `git commit -a`, якщо ви збираєтеся відразу зробити комміт. Дивіться * git help ignore *, щоб дізнатися як вказати файли, які повинні ігноруватися.

Ви можете виконати все це одним махом:

 $ git ls-files -d -m -o -z | xargs -0 git update-index --add --remove

Опції *-z* і *-0* запобігають невірну обробку файлових імен, що містять спеціальні символи. Оскільки ця команда додає ігноровані файли, ви можливо захочете використовувати опції `-x` або `-X`.

=== Мій комміт занадто великий ===

Ви нехтували коммітамі занадто довго? Затято писали код і згадали про управління вихідними кодами тільки зараз? Внесли ряд незв'язаних змін, тому що це ваш стиль?

Немає причин для занепокоєння. Виконайте

 $ git add -p

Для кожної зробленої вами правки Git покаже змінену ділянку коду і запитає, чи повинна ця зміна потрапити в наступний комміт. Відповідайте "y" (так) або "n" (ні). У вас є й інші варіанти, наприклад відкласти вибір; введіть "?" Щоб дізнатися більше.

Коли закінчите, виконайте

 $ git commit

для внесення саме тих правок, що ви вибрали ('буферизованих' змін). Переконайтеся, що ви не вказали опцію *-a*, інакше Git закоммітить всі правки.

Що робити, якщо ви змінили безліч файлів в багатьох місцях? Перевірка кожної окремої зміни стає обтяжливою рутиною. У цьому випадку використовуйте *git add -i*. Її інтерфейс не такий простий, але більш гнучкий. У декілька натискань можна додати або прибрати з буфера кілька файлів одночасно, або переглянути і вибрати зміни лише в окремих файлах. Як варіант, запустіть *git commit \--interactive*, яка автоматично зробить комміт коли ви закінчите.

=== Індекс — буферна зона Git ===

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

Наприклад, *commit -a* насправді двоетапний процес. Спочатку зліпок поточного стану кожного з відстежуваних файлів поміщається в індекс. Потім зліпок, що знаходиться в індексі, записується в історію. Комміт без опції *-a* виконує тільки другий крок, і має сенс тільки після виконання команд, що змінюють індекс, таких як *git add*.

Зазвичай ми можемо не звертати уваги на індекс і робити вигляд, що взаємодіємо безпосередньо з історією. Але в даному випадку ми хочемо більш тонкого контролю, тому управляємо індексом. Ми поміщаємо зліпок деяких (але не всіх) наших змін в індекс, після чого остаточно записуємо цей акуратно сформований зліпок.

=== Не втрачай "голови" ===

Тег HEAD подібний курсору, який зазвичай вказує на останній комміт, просуваючись з кожним новим коммітом. Деякі команди Git дозволяють переміщати цей курсор. Наприклад,

 $ git reset HEAD~3

перемістить HEAD на три комміти назад. Тепер всі команди Git будуть працювати так, ніби ви не робили останніх трьох коммітів, хоча файли залишаться в поточному стані. У довідці описано кілька способів використання цього прийому.

Але як повернутися назад у майбутнє? Адже попередні комміти про нього нічого не знають.

Якщо у вас є SHA1 вихідної "голови", то:

 $ git reset 1b6d

Але припустимо, ви його не записували. Не турбуйтеся: для комнад такого роду Git зберігає оригінальну "голову" як тег під назвою ORIG_HEAD, і ви можете повернутися надійно і безпечно:

 $ git reset ORIG_HEAD

=== Полювання за "головами" ===

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

За замовчуванням Git зберігає комміти не менше двох тижнів, навіть якщо ви наказали знищити гілку, що їх містить. Проблема в знаходженні відповідного хешу. Ви можете проглянути всі значення хешів в `.git/objects` і методом проб та помилок знайти потрібний. Але є шлях значно легший.

Git записує кожен підрахований ним хеш комміта в `.git/logs`. У підкаталозі `refs` міститься повна історія активності на всіх гілках, а файл `HEAD` містить кожне значення хешу, яке коли-небудь приймав HEAD. Останній можна використовувати щоб ​​знайти хеши коммітів на випадково обрубаних гілках.

Команда reflog надає зручний інтерфейс роботи з цими журналами. Використовуйте

 $ git reflog

Замість копіювання хешів з reflog, спробуйте

 $ git checkout "@{10 minutes ago}"

Чи зробіть чекаут п'ятого з кінця з відвіданих коммітів за допомогою

 $ git checkout "@{5}"

Дивіться розділ ``Specifying Revisions'' в *git help rev-parse* для додаткової інформації.

Ви можете захотіти подовжити відстрочку для коммітів, приречених на видалення. Наприклад,

 $ git config gc.pruneexpire "30 days"

означає, що комміти, які видаляються, будуть остаточно зникати тільки після 30 днів і після запуску *git gc*.

Також ви можете захотіти відключити автоматичний виклик *git gc*:

 $ git config gc.auto 0

У цьому випадку комміти будуть видалятися тільки коли ви будете запускати *git gc* вручну.

=== Git як основа ===

Дизайн Git, в істинному дусі UNIX, дозволяє легко використовувати його як низькорівневий компонент інших програм: графічних та веб-інтерфейсів; альтернативних інтерфейсів командного рядка; інструментів управління патчами; засобів імпорту або конвертації, і так далі. Багато команд Git насправді - скрипти, які стоять на плечах гігантів. Невеликим доопрацюванням ви можете переробити Git на свій смак.

Найпростіший трюк — використання аліасів Git для скорочення часто використовуваних команд:

 $ git config --global alias.co checkout
 $ git config --global --get-regexp alias 	# відображає поточні аліаси
 alias.co checkout
 $ git co foo        # те ж саме, що і 'git checkout foo'

Інший приклад: можна виводити поточну гілку в запрошенні командного рядка або заголовку вікна терміналу. Запуск

 $ git symbolic-ref HEAD

виводить назву поточної гілки. На практиці ви швидше за все захочете прибрати "refs/heads/" і повідомлення про помилки:

 $ git symbolic-ref HEAD 2> /dev/null | cut -b 12-

Підкаталог +contrib+ — це ціла скарбниця інструментів, побудованих на Git. З часом деякі з них можуть ставати офіційними командами. В Debian та Ubuntu цей каталог знаходиться у +/usr/share/doc/git-core/contrib+.

Один популярний інструмент з цього каталогу — +workdir/git-new-workdir+. Цей скрипт створює за допомогою символічних посилань новий робочий каталог, який має спільну історію з оригінальним сховищем:

 $ git-new-workdir існуюче/сховище новий/каталог

Новий каталог і файли в ньому можна сприймати як клон, з тією різницею, що два дерева автоматично залишаються синхронізованими зважаючи на спільну історію. Немає необхідності в merge, push і pull.

=== Ризиковані трюки ===

Нинішній Git робить випадкове знищення даних дуже складним.
Але якщо ви знаєте, що робите, ви можете обійти захист для розповсюджених команд.

*Checkout*: Наявність незакомміченних змін перериває виконання checkout. Щоб перейти до потрібного комміту, навіть знищивши свої зміни, використовуйте прапор змушування (force) *-f*:

 $ git checkout -f HEAD^

З іншої сторони, якщо ви вкажете checkout конкретні шляхи, перевірки на безпеку не буде: вказані файли мовчки перезапишуть. Будьте обережні при такому використанні checkout.

*Reset*: скидання також переривається при наявності незакомміченних змін. Щоб змусити його спрацювати, запустіть

 $ git reset --hard 1b6d

*Branch*: Видалення гілки припиниться, якщо воно призвело б до втрати змін. Для примусового видалення введіть

 $ git branch -D мертва_гілка # замість -d

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

 $ git branch -M джерело ціль # замість -m

У відмінності від checkout і reset, ці дві команди дають відстрочку у видаленні даних. Зміни залишаються в каталозі.git і можуть бути повернуті відновленням потрібного хешу з `.git/logs` (дивіться вище розділ "Полювання за головами"). За замовчуванням вони будуть зберігатися принаймні два тижні.

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

 $ git clean -f -d

Наступного разу ця прикра команда спрацює!

=== Запобігаємо поганим коммітам ===

Дурні помилки забруднюють мої сховища. Найжахливіше це проблема відсутніх файлів, викликана забутим *git add*.

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

Якби я тільки поставив захист від дурня, використовуючи _хук_, який би попереджав мене про ці проблеми:

 $ cd .git/hooks
 $ cp pre-commit.sample pre-commit # В старих версіях Git: chmod +x pre-commit

Тепер Git скасує комміт, якщо виявить зайві пробіли або невирішені конфлікти.

Для цього керівництва я в кінці кінців додав наступне в початок хука *pre-commit*, щоб захиститися від своєї неуважності:

if git ls-files -o | grep '\.txt$'; then
 echo ПЕРЕРВАНО! Невідслідковувані .txt файли.
 exit 1
fi

Хукі підтримуються кількома різними операціями Git, дивіться *git help hooks*. Ми використовували приклад хука *post-update* раніше, при обговоренні використання Git через http. Він запускався при кожному переміщенні голови. Простий скрипт post-update оновлює файли, які потрібні Git для зв'язку через  засоби повідомлення такі як HTTP.
