Дополнительно

В заключительной части нашего руководства мы поделимся некоторыми дополнительными приемами, которые могут оказаться полезными.

1. Отслеживание изменений, сделанных в коммитах

Каждый коммит имеет уникальный идентификатор, представляющий собой строку из букв и цифр. Чтобы увидеть список всех коммитов с их идентификаторами, воспользуемся командой log:

$ git log
commit ba25c0ff30e1b2f0259157b42b9f8f5d174d80d7
Author: Tutorialzine
Date: Mon May 30 17:15:28 2016 +0300
New feature complete
commit b10cc1238e355c02a044ef9f9860811ff605c9b4
Author: Tutorialzine
Date: Mon May 30 16:30:04 2016 +0300
Added content to hello.txt
commit 09bd8cc171d7084e78e4d118a2346b7487dca059
Author: Tutorialzine
Date: Sat May 28 17:52:14 2016 +0300
Initial commit

Как видно, идентификаторы имеют значительную длину, но для работы достаточно использовать только первые несколько символов. Для просмотра изменений, внесенных в коммит, мы можем использовать команду show [commit]

$ git show b10cc123
commit b10cc1238e355c02a044ef9f9860811ff605c9b4
Author: Tutorialzine
Date: Mon May 30 16:30:04 2016 +0300
Added content to hello.txt
diff --git a/hello.txt b/hello.txt
index e69de29..b546a21 100644
--- a/hello.txt
+++ b/hello.txt
@@ -0,0 +1 @@
+Nice weather today, isn't it?

Для того чтобы сравнить изменения между двумя коммитами, нам пригодится команда diff (с указанием диапазона между коммитами):

$ git diff 09bd8cc..ba25c0ff
diff --git a/feature.txt b/feature.txt
new file mode 100644
index 0000000..e69de29
diff --git a/hello.txt b/hello.txt
index e69de29..b546a21 100644
--- a/hello.txt
+++ b/hello.txt
@@ -0,0 +1 @@
+Nice weather today, isn't it?

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

2. Возвращение файла к предыдущему состоянию

Git позволяет вернуть файл к состоянию в момент определенного коммита. Для этого достаточно использовать команду checkout, которую мы ранее применяли для переключения веток. Но мы также можем применять ее для работы с коммитами — такое использование команды достаточно характерно для Git, так как одна команда может выполнять разные задачи. В следующем примере мы вернем файл hello.txt к его первоначальному виду, используя идентификатор первого коммита и его путь:

$ git checkout 09bd8cc1 hello.txt

3. Исправление коммита

Если вы допустили ошибку в комментарии или забыли добавить файл и обнаружили это сразу после коммита, нет необходимости беспокоиться. Команда commit --amend быстренько решит эту проблему. Она переносит изменения последнего коммита в область подготовленных файлов и позволяет создать новый коммит, исправив при этом комментарий или добавив недостающие файлы. Однако для более сложных случаев, например, если правки вносятся не в последний коммит или уже были отправлены на сервер, мы воспользуемся командой revert. Она создает коммит, аннулирующий изменения, внесенные в указанный коммит. Использование алиаса HEAD позволяет обратиться к самому последнему коммиту:

$ git revert HEAD

Для остальных коммитов используйте их идентификаторы:

$ git revert b10cc123

Исправление старых коммитов может вызвать конфликты. Это происходит, если файл был изменен новыми коммитами, и Git не может найти строки для отката к предыдущему состоянию, поскольку они были удалены или изменены.

4. Разрешение конфликтов при слиянии

Конфликты могут возникать регулярно, например, при слиянии ветвей или отправке изменений в общую ветку. Иногда конфликты решаются автоматически, но чаще всего их необходимо разрешать вручную, выбирая, какой код стоит сохранить, а какой — удалить. Рассмотрим пример, где мы объединим две ветки под именами john_branch и tim_branch, где каждая из веток вносит изменения в одну и ту же функцию, отображающую элементы массива.
Джон предпочитает использовать цикл:

// Use a for loop to console.log contents.
for(var i=0; i<arr.length; i++) {
console.log(arr[i]);
}

Тим, в свою очередь, использует forEach:

// Use forEach to console.log contents.
arr.forEach(function(item) {
console.log(item);
});

Оба разработчика коммитят свои версии кода в свои ветки. Когда они пытаются объединить изменения, они сталкиваются с конфликтом:

$ git merge tim_branch
Auto-merging print_array.js
CONFLICT (content): Merge conflict in print_array.js
Automatic merge failed; fix conflicts and then commit the result.

Git не смог разрешить конфликт автоматически, и теперь это задача для разработчиков. Конфликтующие строки отмечены в файле:

<<<<<<< HEAD // Use a for loop to console.log contents. for(var i=0; i <arr.length; i++) { console.log(arr[i]); } ======= // Use forEach to console.log contents. arr.forEach(function(item) { console.log(item); }); >>>>>>> Tim's commit.

Верхняя часть отмеченного фрагмента (до разделителя =======) показывает версию (HEAD), а нижняя часть — конфликтующую. Это позволяет увидеть различия и определить, какую строку оставить. Или же, если необходимо, написать собственный вариант, который разрешит конфликт. В данном случае мы выберем третий путь и напишем что-то новое, убрав ненужные разделители. После этого сообщим Git, что работа завершена.

// Not using for loop or forEach.
// Use Array.toString() to console.log contents.
console.log(arr.toString());

Когда всё готово, останется лишь закоммитить изменения, чтобы завершить объединение:

$ git add -A
$ git commit -m "Array printing conflict resolved."

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

5. Настройка .gitignore

В большинстве проектов существуют файлы или каталоги, которые мы не хотим (и зачастую не должны) добавлять в репозиторий. Мы можем гарантировать их исключение из git add -A, используя файл .gitignore.

  1. Создайте в корне проекта файл с именем .gitignore.
  2. Список файлов и папок, которые должны игнорироваться, укажите в нем, пронумеровав их по одной строке.
  3. Как и любой другой файл проекта, .gitignore необходимо добавить в систему контроля версий, закоммитить и отправить на сервер.

Вот несколько примеров файлов, которые часто игнорируются:

  • Логи
  • Файлы, генерируемые системой сборки
  • Папки node_modules в проектах на Node.js
  • Папки, создаваемые IDE, такие как Netbeans или IntelliJ
  • Различные личные заметки разработчиков.

Пример файла .gitignore, содержащего перечисленные выше элементы, может выглядеть так:

*.log
build/
node_modules/
.idea/
my_notes.txt

Слэш в конце некоторых строк указывает на директорию и свидетельствует о том, что игнорируется всё её содержимое на всех уровнях. Звёздочка, как обычно, служит шаблоном.

Заключение

На этом мы завершаем наше руководство. Мы постарались собрать и изложить наиболее важную информацию максимально кратко и чётко.
Git — это обширный инструмент со множеством функций и возможностей. Для желающих углубиться в изучение Git мы рекомендуем следующие ресурсы:

  • Официальная документация, включающая книгу и обучающие видео – тут.
  • “Getting git right” – научно-популярный сайт компании Atlassian, содержащий руководство и статьи – тут.
  • Перечень клиентов с графическим интерфейсом – тут.
  • Инструмент для автоматической генерации .gitignore файлов – тут.

Вопросы для самопроверки:

  1. Какой командой можно увидеть список всех коммитов с их идентификаторами?
  2. Какую команду нужно использовать для просмотра изменений в конкретном коммите?
  3. Какой командой можно вернуть файл к состоянию определенного коммита?
  4. Что делает команда `commit --amend` в Git?
  5. Как решить конфликт при слиянии ветвей в Git?
  6. Какой файл используется для исключения определенных файлов и папок из системы контроля версий Git?