Полезные команды Git: безопасная отмена коммитов, добавление файла из другой ветки и другие

Рассказывает автор блога AlgoTech


Git — это мощный, хотя и незамысловато выглядящий, инструмент, который при неосторожном использовании может устроить большой беспорядок. Поэтому, прежде чем пробовать выполнять различные фокусы с Git, я рекомендую ознакомиться с его основными командами (init, clone, push, pull, status, log и т.д.) и концептами (репозиторий, ветвь, коммит, и т.д.).

Итак, если вы уверенно чувствуете себя при работе с Git, вот несколько полезных трюков, о которых я знаю.

Reflog. Отмена операций

Я помню, что в начале знакомства с Git я боялся запускать команды. Я думал, что всё испорчу. Например, первое использование git rebase вызвало у меня несколько восклицаний наподобие «Вот *****! Что я только что сделал?!» из-за дублировавшихся или вообще исчезнувших коммитов. Вскоре после этого я понял, что нужно идти дальше.

Лично я не люблю использовать инструмент, не зная, что именно он делает для достижения результата. Это как вождение машины без руля: всё прекрасно до тех пор, пока не доедешь до поворота. А что потом? Ну, вы меня поняли.

Итак, в нашем деле очень важно понимать концепты. Я много читал про Git, прошёл несколько обучающих курсов, и моё видение мира Git кардинально изменилось. Используя его, я стал чувствовать себя гораздо спокойнее, а что более важно, я познал его истинную мощь. И одной из самых мощных команд является git reflog.

Используя reflog, вы можете вернуться в прошлое, отменяя почти любые действия, сделанные в Git. Даже если это были rebase или reset. Кроме того, её легко использовать. Чтобы доказать это, приведу маленький пример.

$ git reflog
1234571 HEAD@{0}: checkout: moving from one-branch to another-branch
1234570 HEAD@{1}: merge origin/feature-branch: Fast-forward
1234569 HEAD@{2}: commit: The commit message goes here.
1234568 HEAD@{3}: reset: moving to 2234567
1234567 HEAD@{4}: rebase finished: returning to refs/heads/one-branch

Чтобы изменить нужное состояние, нужно запустить checkout, используя абсолютную ссылку:

$ git checkout 1234568

или относительную ссылку:

$ git checkout HEAD@{3}

и вы окажетесь в независимом (detached) состоянии HEAD, из которого можно создать новую ветвь.

Вот и всё. Все операции после вышеуказанного состояния отменены.

Revert. Отмена изменений коммита

Вы, должно быть, сталкивались с ситуацией, когда вам нужно было отменить некоторые изменения. На помощь придёт команда revert:

$ git revert <commit>

Что она делает? Она просто отменяет действия прошлых коммитов, создавая новый, содержащий все отменённые изменения. Зачем использовать её вместо других решений? Это — единственный безопасный способ, так как он не изменяет историю коммитов. Он обычно используется в публичных ветвях, где изменение истории нежелательно.

Давайте взглянем на другие команды, способные отменять действия.

Rebase

$ git rebase --interactive

Вы можете выполнить эту команду и просто убрать строки, относящиеся к ненужным коммитам. Это самое очевидное и простое решение, но у него есть недостаток, из-за которого его нельзя использовать в публичных ветвях: он изменяет историю коммитов. Поэтому после rebase’а у вас может возникнуть проблема с push’ем.

Reset

$ git reset --hard <commit>

Эта команда удаляет все коммиты выше указанного. Она лучше предыдущей тем, что не добавляет новую информацию в историю, но тем не менее тоже ее меняет.

Checkout

$ git checkout <commit> -- <filename>

Эта команда откатывает выбранный файл к состоянию в более ранней версии указанного коммита. Отменённые изменения будут в рабочей директории, и их можно будет закоммитить как угодно.

Вывод таков, что git revert — самая безопасная операция для отмены действий. Кроме того, нужно заметить, что эти команды не взаимозаменяемы в 100% случаев.

Log. Более приятное форматирование

С git log всё просто. Вы вводите команду и видите историю коммитов в хронологическом порядке, и каждый пункт включает в себя хэш коммита, автора, дату и прикреплённое сообщение.

Давайте взглянем на вид лога по умолчанию:

$ git log
commit 01ba45f789abcdef9876543210fedcba01234567
Author: First Last <email@domain.com>
Date: Wed Apr 13 17:00:01 2016 +0300

The last commit message goes here.

commit 01cf4a6789abcdef9876543210fedcba01234568
Author: First Last <email@domain.com>
Date: Wed Apr 12 16:24:03 2016 +0300

Another commit message goes here.

commit 01bf456789abcdef9876543210fedcba01234568
Author: First Last <email@domain.com>
Date: Wed Apr 11 15:30:33 2016 +0300

Another commit message goes here.

Я думаю, что могло бы быть и лучше.

Я предпочитаю простые вещи. Поэтому я использую две опции:

  • --oneline – показывает каждый коммит в одной строке. Кроме того, она показывает лишь префикс ID коммита.
  • --decorate – печатает все относительные имена показанных коммитов.

Вот как это выглядит:

$ git log --oneline --decorate
01ba45f (HEAD -> origin/feature-branch, feature-branch) The last commit message goes here.
01cf4a6 Another commit message goes here.
01bf456 (origin/dev, dev) Another commit message goes here.

Опция --oneline сокращает количество занятых строк, а опция --decorate добавляет относительные имена (локальные/удалённые ветви + HEAD). Используя эти две опции, мы получаем более компактный и осмысленный git log. Да, мы теряем некоторые вещи, но они не особо важны. Так гораздо лучше, да?

Тем не менее, иногда нам нужно знать дату и автора коммита. В таких случаях я использую следующее:

$ git log --color --pretty=format:"%C(yellow)%h%C(reset) %s%C(bold red)%d%C(reset) %C(green)%ad%C(reset) %C(blue)[%an]%C(reset)" --relative-date --decorate

Ничего себе! Давайте посмотрим, как это выглядит:

$ git log --color --pretty=format:"%C(yellow)%h%C(reset) %s%C(bold red)%d%C(reset) %C(green)%ad%C(reset) %C(blue)[%an]%C(reset)" --relative-date --decorate
01ba45f The last commit message goes here. (HEAD -> origin/feature-branch, feature-branch) 3 seconds ago [First Last]
01cf4a6 Another commit message goes here. 1 day ago [First Last]
01bf456 Another commit message goes here. (origin/dev, dev) 2 days ago [First Last]

Весьма неплохо.

И всё-таки вводить эти команды каждый раз достаточно неудобно, так? Давайте создадим алиасы. Я считаю, что использование алиасов очень сильно повышает продуктивность. Чтобы зарегистрировать их, добавим следующие строки в «~/.bash_profile» (конечно, если вы используете *nix OS):

alias gl='git log --oneline --decorate'
alias gh='git log --color --pretty=format:"%C(yellow)%h%C(reset) %s%C(bold red)%d%C(reset) %C(green)%ad%C(reset) %C(blue)[%an]%C(reset)" --relative-date --decorate'

Таким образом, мы получили не только хорошо выглядящую историю коммитов, но и упрощённый способ доступа к ней, используя команды gl и gh.

Еще один вариант украшения вывода лога приведен в этой статье.

Diff. Разница между коммитами

Простая команда, показывающая изменения между объектами в проекте — git diff. Один из основных сценариев её использования — сравнение двух коммитов:

$ git diff <commit1>..<commit2>

Другой — вывод всех изменений, не внесённых в индекс. Для этого нужно выполнить следующее:

$ git diff --cached

Branch. Проверка ветвей на слияние

Я думаю, вы уже знаете, что делает базовая команда git branch (без опций выводит список локальных веток — прим. перев.), поэтому перейдём к делу. Время от времени вам понадобится удалять старые или ненужные ветви из вашего «дерева» git.

Используя следующее:

$ git branch --merged

вы получите список всех веток, соединённых с текущей.

Можно и наоборот:

$ git branch --no-merged

Так вы получите список всех не соединённых с текущей веток.

Таким образом, вы всегда узнаете, безопасно ли удаление той или иной ветви.

Checkout. Получение файла из другой ветви

Вам наверняка приходилось искать конкретную версию файла, которая была в другой, ещё не присоединённой ветви. Помните команду checkout, приведённую выше? Она выглядит так:

$ git checkout <commit> -- <filename>

Указанный файл будет приведен к версии, указанной в заданном коммите. Раньше мы использовали это для отмены изменений файла. Удобно то, что этот коммит необязательно должен находиться в той же ветви, что и наш файл. Поэтому мы можем задать любому файлу любую версию из любого коммита или ветви.


Надеюсь, вы нашли здесь для себя что-то полезное. Прошу заметить, что все вышеуказанные моменты субъективны, поскольку я подгонял их под свои нужды.

Перевод статьи «Git Tips and Tricks»