Введение в Git/GitHub: базовые команды (ч. 8)

Введение
В предыдущей статье были рассмотрены основы систем контроля версий, их виды, а также выполнена установка и настройка Git, создан аккаунт на GitHub. В этой публикации мы продолжим изучения этих инструментов и подробно разберем базовые команды Git, которые используются в повседневной работе.
Начинающие программисты часто хватаются за голову от количества возможностей, которые предлагает Git и логики его работы. Да, понять все и сразу — задача сложно выполнимая. Но мы, чтобы все же с чего-то начать, пройдём для начала самые часто используемые команды. Это позволит вам поверить в себя и начать применять данный инструмент в своих проектах.

1. Основные термины Git

В Git используется своя терминология, которую я решил вынести в отдельный раздел, как глоссарий, чтобы вы могли к нему обращаться по мере необходимости.
Индекс — это часть локального репозитория (ЛР), хранящая не зафиксированные с помощью команды git add изменения файлов. Еще ее называют областью подготовки файлов. Эту область можно сравнить с черновиком (промежуточным этапом), из которого разработчик по завершению своей работы выбирает, какие изменения нужно зафиксировать (добавить в тот или иной коммит).
Индексирование — добавление изменений в индекс. После этой операции файлы попадают под версионный контроль Git (если они для него были незнакомы). Если файлы уже были под контролем Git, то их изменения просто попадают очередной раз в черновик, чтобы потом быть зафиксированными.
Коммит (commit) — фиксация изменений, взятых из индекса. Это действие заносит изменения в репозиторий Git. Его можно сравнить со слепком текущего состояния вашего ЛР. Вы берете из черновика (индекса) нужные изменения и коммитите текущее состояние файлов и папок. Это действие похоже на создание точки восстановления в Windows. Каждый коммит также является итогом вашей работы.
Закоммитить/коммитить — зафиксировать изменения, сделать слепок текущего состояния файлов и папок.
Клонирование репозитория (clone) — процесс скачивания копии УР к себе на компьютер. При этом создание ЛР происходит автоматически, без явного ввода git init.
Подтягивание изменений (pull) — работать с одним УР могут как разные люди, так и один разработчик с разных устройств. При такой ситуации, перед тем, как приступить к изменению файлов, необходимо начинать свою работу с получения всех коммитов, которые есть на УР. Коммиты в ЛР всегда должны совпадать с коммитами на УР, а иначе будут проблемы, решение которых мы рассмотрим в следующей статье.
Если упрощенно объединить все, что было сказано в этом разделе, то процесс работы с Git сводится к следующим шагам:
Ветка (branch) — это одно из направлений развития вашего проекта. Git позволяет вести параллельную работу (в разных ветках), в рамках одного репозитория, над одними и теми же файлами. У каждой ветки есть имя. Имя основной ветки — master (main).
Например, вам необходимо реализовать какую-то фичу (или исправить баг в коде) и чтобы не испортить уже работающую программу, которая находится в основной ветке, вы можете создать дополнительную, где будет вестись ее разработка. Таким образом, вы ничего не сломаете в основном коде, а если фича (или исправление ошибок) окажется удачной, то ваши наработки можно будут перенести в master (сделать слияние). Таких веток можно создавать сколько угодно. При необходимости удалять, объединять между собой.
Создавать новые ветки в данной статье мы пока не будем, т. к. эта тема требует отдельного рассмотрения.

2. Создание локального репозитория

В предыдущей статье, с помощью команды git init, мы создали ЛР — место на компьютере для хранения файлов и отслеживания в них любых изменений. Именно с этого шага начинается работа с Git.
Выполните команду git init в корне вашего проекта, как показано в образце ниже, если не делали этого ранее:
D:\Java\StartJava
> git init
Initialized empty Git repository in D:/Java/StartJava/.git/
В результате, в папке StartJava появится скрытая директория .git (с точкой в начале), где и будет в дальнейшем храниться информация обо всех изменениях, сделанных в репозитории.

3. Связывание удаленного и локального репозитория

На следующем шаге свяжем ЛР с УР, чтобы код, хранящийся на вашем компьютере, можно было размещать на GitHub (или брать с него).
URL на ваш УР скопируйте, зайдя на github.com и выбрав протокол, который вы настроили для взаимодействия с GitHub в прошлой статье.
В итоге команда будет выглядеть так (используйте ссылку на свой репозиторий!):
> git remote add origin https://github.com/ichimax/startjava2.git
Разберем ее по частям:
  • git — используется приложение git
  • remote — управляет удаленным репозиторием
  • add — добавляет информацию об УР в ЛР
  • origin — общепринятый псевдоним для URL УР
  • https://github.com/ichimax/startjava2.git — ссылка на УР
Эту длинную команду можно трактовать так: Git, добавь к себе информацию о связи между УР github.com/ichimax/startjava2.git, которому я хочу дать псевдоним origin, с ЛР.
Проверим, что получилось, введя git remote -v:
> git remote -v
origin  https://github.com/ichimax/startjava2.git (fetch)
origin  https://github.com/ichimax/startjava2.git (push)
Взаимосвязь установлена успешно: напротив псевдонима origin размещается ссылка на УР.

4. Состояние репозитория

Одной из самых популярных и простых команд, которая используется постоянно, является git status. Она показывает в каком состоянии в текущий момент находится ЛР (какие файлы были изменены, что нового добавилось и т. д.).
Файлы в репозитории могут находиться в следующих состояниях:
  • не отслеживаемые (untracked) — находящиеся в ЛР, но еще не добавленные в индекс (под версионный контроль). Это могут быть новые файлы
  • индексированные (staged) — новые файлы, добавленные впервые в индекс
  • зафиксированные (committed) — попавшие в коммит
  • измененные (modified) — модифицированные после коммита
Файлы все время меняют свое состояние. Их жизненный цикл повторяется снова и снова.
Введем в консоли git status:
D:\Java\StartJava (master)
> git status
On branch master

No commits yet

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

nothing added to commit but untracked files present (use "git add" to track)
Команда вывела сообщение, которое означает, что Git видит src и about.txt, но не отслеживает, вносимые в них изменения. В связи с этим, он предлагает добавить файлы в индекс. Тут следует запомнить одну важную деталь: добавленные в репозиторий новые файлы не попадают сразу под версионный контроль. Git их просто видит, но не фиксирует в них изменения.
Если бы файлы сразу попадали в индекс, то тогда любые случайные файлы (с настройками, class, временные и т. д.) попадали бы под версионный контроль — был бы хаос. Git защищает нас от этого, требуя, чтобы мы сами контролировали этот процесс.
А теперь разберем все, что вывела команда git status:
  1. On branch master — [находимся] в ветке master
  2. No commits yet — нет коммитов [которые можно запушить]
  3. Untracked files — [имеются] неотслеживаемые файлы
  4. (use "git add <file>..." to include in what will be committed) — используйте "git add <file> ..." для включения файлов в коммит, состояние [изменения] которых нужно зафиксировать.
    Git увидел, что в ЛР есть не занесенные в индекс и незафиксированные изменения. Т.к. он src и about.txt видит впервые, то для него они по умолчанию являются измененными. Поэтому Git предлагает закоммитить (зафиксировать) их текущее состояние, чтобы уже от него учитывать все последующие изменения.
  5. nothing added to commit but untracked files present (use "git add" to track) — ничего не добавлено в коммит, но присутствуют не отслеживаемые файлы (для отслеживания используйте "git add")
Для того чтобы существующие файлы попали под версионный контроль (чтобы Git начал отслеживать изменения), их нужно добавить в индекс Git.

5. Добавление файлов в индекс

Добавим файлы в индекс, запустив команду git add . (с точкой в конце), а затем снова выполним git status.
D:\Java\StartJava (master)
> git add .

D:\Java\StartJava (master)
> git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   about.txt
        new file:   src/MyFirstApp.java
Точка, идущая после add означает, что в индекс будут добавлены все изменения, которые были сделаны в текущей папки и ее подпапках.
Если вам требуется добавить вообще все изменения, которые были сделаны в репозитории, начиная с его корня, то для этого нужно использовать параметр -A вместо точки.
В выводимом сообщении произошли изменения: src и about.txt перестали быть untracked, а позеленели и теперь находятся в индексе.
Git предлагает зафиксировать их текущее состояние, т. е. сделать коммит. При этом подсказывает, что если файлы по ошибке были добавлены в индекс, их можно из него удалить (привет class-файлам), сделав снова untracked. Эта возможность бывает полезна, когда в индекс по ошибке добавляются файлы, которые Git должен игнорировать.
Ситуацию с игнорированием файлов мы разберем ниже, когда будем говорить про .gitignore.

6. Коммит файлов

Осталось зафиксировать изменения из индекса с помощью команды commit. Это можно сделать двумя основными способами.

6.1. Первый способ

Напишем в консоли команду git commit. Откроется указанный ранее при настройке (установке) текстовый редактор. В нашем случае откроется новая вкладка, содержащая следующий шаблонный текст:
При этом в консоли отобразится текст "hint: Waiting for your editor to close the file…" (благодаря параметру --wait, который мы установили в предыдущей статье).
Текст во вкладке COMMIT_EDITMSG частично напоминает то, что выводил git status. В начале каждой строки идет символ #, говорящий о том, что текст закомментирован и несет только информационную нагрузку. На него можно не обращать внимание (или удалить полностью), он все равно не попадет в коммит или его описание.
Осталось ввести сообщение, содержащее краткое описание тех изменений, которые были сделаны в файлах. Такое сообщение называется описание к коммиту. В качестве примера запишем многострочное описание:
В многострочном описании первая строка является заголовком, включающим в себя общий смысл проделанных изменений. Она не должна превышать 50 символов и не имеет точку в конце. Далее обязательно должна идти пустая строка. А затем уже идет перечисление конкретных изменений.
Из заголовка видно, что данный коммит является первым (root-commit), т. к. в нем присутствует слово «Инициализация». Далее указаны пункты с теми изменениями, которые мы внесли в файлы проекта.
Сохраните, а затем закройте вкладку. Git создаст коммит, добавит к нему описание и выведет о нем информацию в консоль:
D:\Java\StartJava\src (master)
> git commit
[master (root-commit) 1e36e0f] Инициализация проекта
 2 files changed, 6 insertions(+)
 create mode 100644 about.txt
 create mode 100644 src/MyFirstApp.java
Сообщение содержит следующие данные о коммите:
  • master — для какой ветки он был выполнен
  • root-commit — является корневым (первым) коммитом
  • 1e36e0f — уникальный идентификатор (контрольная сумма)
  • Инициализация проекта — описание (заголовок). При этом другие пункты — не отображаются
  • 2 files changed — количество измененных файлов
  • 6 insertions(+) — количество добавленных в файлах строк
  • create mode 100644 — права доступа к файлам в Unix-стиле

6.2. Второй способ

Есть и другой способ создания коммита. Если комментарий к коммиту короткий, то его можно набрать прямо в командной строке.
Изменим название файла about.txt на README.md («Прочитай меня») и внесем в него изменения. В этом файле принято размещать описание проекта, который находится в репозитории.
GitHub позволяет форматировать текст в файлах, используя облегчённый язык разметки Markdown. Маркдаун-файлы как раз имеют расширение md.
Изменим имя файла с помощью Git и отобразим результат:
D:\Java\StartJava (master)
> git mv about.txt README.md && ls
README.md  src/
Откроем README.md, написав в консоли его имя, и жмем Enter. Затем, используя маркдаун, внесем в него следующие изменения:
# [StartJava](https://topjava.ru/startjava) — курс на Java для начинающих

## Используемые на курсе инструменты и технологии

1. Java
1. Git/GitHub
1. Командная строка
1. Sublime Text
1. Intellij IDEA
1. SQL
1. PostgreSQL
1. psql
Отобразим данный файл в браузере. Для этого необходимо нажать Ctrl + Shift + P, написать preview и выбрать первую строку с надписью "MarkDown Preview: Preview in Browser" (при этом пакет Markdown Preview должен быть установлен):
Откроется страница в браузере с отформатированным текстом:
Далее проверим статус файла (вывод дан в сокращенном виде):
D:\Java\StartJava (master)
> git status
Changes to be committed:
        renamed:    about.txt -> README.md

Changes not staged for commit:
        modified:   README.md
Видим, что Git сам переименовал about.txt в README.md, а также пометил README.md как измененный.
Добавим все изменения в индекс, выполнив команду git add README.md с явным указанием имени измененного файла.
Перед тем как выполнить коммит, посмотрим, какие изменения мы хотим зафиксировать. Для этого пишем git status -v (вывод дан в сокращенном виде):
> git status -v

Changes to be committed:
        new file:   README.md
        deleted:    about.txt

diff --git a/README.md b/README.md
+++ b/README.md

+# [StartJava](https://topjava.ru/startjava) — курс на Java для начинающих
+
+## Используемые на курсе инструменты и технологии
+
+1. Java
+1. Git/GitHub
+1. Командная строка
+1. Sublime Text
+1. Intellij IDEA
+1. SQL
+1. PostgreSQL
+1. psql

diff --git a/about.txt b/about.txt
--- a/about.txt
-Интенсив StartJava - программирование на Java для начинающих!
На этот раз информации отобразилось значительно больше. В сообщении используется результат работы команды diff, которая сравнивает текущее состояние файла с последним коммитом и отображает все изменения.
Плюсом обозначаются добавленные в файл строки (выделены зеленым), а минусом удаленные (красные).
Сделаем коммит:
> git commit -m "Переименовал about.txt в README.md и внес в него описание проекта"
[master 39ba195] Переименовал about.txt в README.md и внес в него описание проекта
 2 files changed, 12 insertions(+), 1 deletion(-)
 create mode 100644 README.md
 delete mode 100644 about.txt
Очень важно запомнить, что текст к коммиту (описание) должен максимально точно и кратко отражать те изменения, которые были сделаны. Не пишите в него бессмыслицу, и не копируйте один и тот же текст из предыдущих коммитов.
В итоге мы имеем еще один коммит. В консоли отобразился уже знакомый нам текст. В нем новой для нас является информация об удаленном файле. Остальную часть сообщения мы уже разбирали.
В каких случаях и как часто необходимо делать коммит:
  • вы закончили создание нового функционала
  • вы завершили запланированное исправление ошибок в коде
  • в конце рабочего дня
Дополнительные правила создания коммита смотрите по ссылке.

7. Список коммитов

Чтобы посмотреть список (историю) всех имеющихся коммитов, воспользуемся командой git log (1, 2):
> git log
commit 39ba195cf96f555aaf2d9f0a2f3ffcfb37e0c536 (HEAD -> master)
Author: ichimax <myEmail>
Date:   Thu Sep 22 16:31:55 2022

    Переименовал about.txt в README.md и внес в него описание проекта

commit 1e36e0f6daf70d936627c444f8cb082bfffb9b17
Author: ichimax <myEmail>
Date:   Thu Sep 22 13:02:17 2022

    Инициализация проекта

    - добавил файл с описанием проекта
    - добавил реализацию первого класса
(END)
Команда отобразила информацию о каждом коммите, расположив их в обратном порядке: последний коммит всегда будет вверху.
Вывод заканчивается строкой с надписью (END), сообщающей, что список коммитов выведен в полном объеме. Для выхода из режима отображения — нажмите клавишу q (язык раскладки должен быть английским).
Информация к коммиту в себя включает следующие метаданные:
  • уникальный идентификатор коммита по которому можно его найти (длинный набор букв и цифр)
  • данные автора коммита, который его создал
  • дату создания коммита
  • краткое описание сделанных изменений
С помощью git log можно выводить информацию о конкретном количестве коммитов, а также видеть все изменения, содержащиеся в них:
> git log -p -1
Параметр -p позволяет отображать изменения (что добавили в файлы, что удалили), а -1 отвечает за количество отображаемых коммитов (в данном случае — один).
Если вам не требуется выводить подробную информацию по каждому коммиту, то можно воспользоваться сокращенным выводом с помощью опции --oneline, которая выводит каждый коммит в одну строку:
> git log --oneline
39ba195 (HEAD -> master) Переименовал about.txt в README.md и внес в него описание проекта
1e36e0f Инициализация проекта

8. Отправка коммитов на удаленный репозиторий

Сейчас оба наших коммита являются локальными — находятся в директории .git у вас на компьютере. Необходимо их загрузить (запушить) на УР на GitHub, используя команду push.
Предварительно введем git status:
> git status
On branch master
nothing to commit, working tree clean
Видим, что коммитить нам нечего, файлов, которые нужно добавить в индекс тоже нет. Выполним пуш, введя команду git push -u origin master:
> git push -u origin master
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 8 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (8/8), 1.17 KiB | 238.00 KiB/s, done.
Total 8 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/ichimax/startjava2.git
 * [new branch]      master -> master
branch 'master' set up to track 'origin/master'.
Данная команда помимо того, что отправит на удаленный репозиторий коммиты, установит связь (с помощью параметра -u) между локальной и удаленной веткой master. В будущем, чтобы сделать пуш, достаточно будет ввести git push. Все остальные параметры указывать уже не обязательно.
После пуша, зайдите на ваш репозиторий на GitHub и убедитесь, что src/MyFirstApp.java и README.md были загружены.

9. Подтягивание изменений с УР

А теперь представим ситуацию, что вы работаете с УР на разных компьютерах (на работе и дома). Внеся какие-то изменения в файлы и запушив их, вы идете домой, где планируете вечером еще немного поработать над проектом.
Или возможна другая ситуация, когда над проектом работает команда программистов, каждый из которых вносит изменения в файлы и пушит их на УР.
Дома, вы открываете свой проект и хотите продолжить над ним работу. И тут вам приходит в голову мысль, а как мне получить с УР все изменения, которые сделали как вы, так и другие программисты, чтобы ваш домашний ЛР был в актуальном состоянии?
Для этого в Git предусмотрена команда pull, которая скачивает с GitHub к вам на компьютер все коммиты, которых нет в вашем ЛР.
Для простоты эксперимента внесем изменения прямо на GitHub (да, это возможно) и подтянем их в свой ЛР.
Изменим код в файле MyFirstApp.java на следующий:
public class MyFirstApp {
    public static void main(String[] args) {
        System.out.print("Написано однажды, ");
        System.out.println("работает везде!");
    }
}
Пример того, как это можно сделать на GitHub, включая создание коммита:
Затем подтянем новый коммит в ЛР:
> git pull
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), 942 bytes | 40.00 KiB/s, done.
From https://github.com/ichimax/startjava2
   39ba195..b59d871  master     -> origin/master
Updating 39ba195..b59d871
Fast-forward
 src/MyFirstApp.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
Теперь количество коммитов в ЛР и УР совпадают — ЛР содержит данные в актуальном состоянии. Можно спокойно продолжить работу.
Выполняйте команду git pull всякий раз перед тем, как начинаете работать над своим проектом (пишите код, вносите какие-то изменения). Эта привычка защитит вас от множества проблем.

10. Клонирование репозитория

После того, как ваш (или чей-то) проект оказался на GitHub, любой желающий может скачать его копию с помощью git clone.
Для примера скачаем какой-нибудь другой репозиторий, но не в наш проект, а в любое место на жестком диске:
D:\Java
> git clone https://github.com/ichimax/Java-Style-Guide.git
Cloning into 'Java-Style-Guide'...
remote: Enumerating objects: 69, done.
remote: Counting objects: 100% (69/69), done.
remote: Compressing objects: 100% (66/66), done.
Receiving objects:  33% (23/69)
Receiving objects: 100% (69/69), 22.24 KiB | 1.71 MiB/s, done.
Resolving deltas: 100% (19/19), done.
При этом новый ЛР создастся автоматически с уже прописанным УР и будет содержать всю историю изменения проекта.
Зайдем в новый репозиторий и введем уже известные нам команды:
> cd Java-Style-Guide\ && ls -a && git status && git remote -v
./  ../  .git/  guide.md  README.md
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
origin  https://github.com/ichimax/Java-Style-Guide.git (fetch)
origin  https://github.com/ichimax/Java-Style-Guide.git (push)

11. Игнорирование файлов и папок

Обычно в любом проекте имеются файлы или целые директории, которые не несут никакого практического смысла. Зачастую их даже не безопасно помещать в коммит, а тем более загружать на УР на всеобщее обозрение. В таких случаях, чтобы исключить их случайное попадание под версионный контроль, в Git есть механизм их игнорирования с помощи специального файла .gitignore (с точкой в начале), который содержит список файлов и папок (каждый на новой строке), которые Git должен игнорировать и не добавлять под версионный контроль.
Давайте поэкспериментируем, создав условия для использования данного файла. Самое простое, что мы можем сделать — это скомпилировать MyFirstApp.java, чтобы в репозитории появился class-файл. Делать это мы уже умеем (если кто-то пропустил, то данная тема разбиралась ранее).
> javac MyFirstApp.java && ls
MyFirstApp.class  MyFirstApp.java
Нужный нам class-файл появился. С этого момента все class-файлы для нас являются «мусорными». Ни при каких обстоятельствах их нельзя включать в коммит, а тем более пушить в удаленный репозиторий.
На GitHub нужно хранить только разные версии ваших файлов к которым в будущем можно вернуться (откатиться). class-файлы нужны только для запуска программ. Для версионного контроля они не представляют никакой ценности.
Создадим .gitignore в корне нашего проекта:
D:\Java\StartJava (master -> origin)
> touch .gitignore
Отобразим все файлы и папки:
> tree /F
D:.
│   .gitignore
│   README.md
│
└───src
        MyFirstApp.class
        MyFirstApp.java
Не лишним будут посмотреть статус репозитория:
> git status
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        src/MyFirstApp.class
Репозиторий содержит два новых неотслеживаемых файла. Один из них нужно игнорировать (чтобы Git его даже не отображал).
Для этого откроем .gitignore и пропишем в нем правило (шаблон), которое позволит игнорировать любые class-файлы. Для этого воспользуемся маской и расширением *.class. Эта запись означает, что все файлы (не важно, как они названы), которые имеют расширение class, будут игнорироваться Git. Это максимально универсальный способ, который позволяет не писать явно имя каждого class-файла, который нужно игнорировать.

Если требуется добавить в него папку, которая находится в одной директории с .gitignore, достаточно написать ее имя. Например, .gitignore может содержать следующее:
*.class
*.DS_Store
*.iml
out/
lib/
.idea/
После имени директории можно указывать /, говорящий о том, что это директория, но так делать не обязательно.
В данный файл, по мере изучения Java, мы будем добавлять новые шаблоны. Не скачивайте из интернета универсальные шаблоны с множеством непонятных и не нужных вам записей. Добавляйте в .gitignore правила по мере необходимости.
Снова проверим статус репозитория:
> git status

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
Вуаля, class-файла, как не бывало (при этом он, конечно же, не удалился, а просто не отображается и игнорируется Git).
Осталось проделать привычные действия с новым файлом, которые уже не нуждаются в пояснении:
git add .
git commit -m "Добавил .gitignore с шаблоном *.class"
git push
git log --oneline

12. Список команд

Список команд, разобранных в статье, с кратким описанием.

Заключение

В данной статье мы рассмотрели самые популярные и часто используемые команды Git. Это очень мощный и многофункциональный инструмент, возможности которого нужно изучать по мере необходимости. Вам же остается не останавливаться в его познании самостоятельно углубляться во все его тонкости.
Автор: Чимаев Максим
Оцените статью, если она вам понравилась!