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

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

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

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

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 и выбрав нужный протокол авторизации (HTTP или SSH — мы их настраивали в прошлой статье).
В итоге команда будет выглядеть так (используйте ссылку на свой репозиторий!):

// Для HTTPS
> git remote add origin https://github.com/ichimax/startjava2.git

// Для SSH
> git remote add origin git@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)
Взаимосвязь установлена успешно: напротив псевдонима размещается ссылка на УР. Она позволяет не писать в процессе работы с Git длинный адрес до УР, заменяя его одним слово — 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 — [имеются] неотслеживаемые файлы (папка src и файл about.txt)
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 начал отслеживать изменения), их нужно проиндексировать.

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 означает, что в индекс будут добавлены все изменения, которые были сделаны в текущей папки и ее подпапках.
В выводимом сообщении произошли изменения: src и about.txt перестали быть untracked, а позеленели и теперь находятся в индексе в состоянии staged.
Git предлагает зафиксировать их текущее состояние, т. е. сделать коммит. При этом подсказывает, что если файлы по ошибке были добавлены в индекс, их можно из него удалить (привет class-файлам), сделав снова untracked. Эта возможность бывает полезна, когда в индекс по ошибке добавляются файлы, которые Git должен игнорировать.
Ситуацию с игнорированием файлов мы разберем ниже, когда будем говорить про .gitignore.

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

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

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

Напишите в консоли команду git commit, нажав Enter. Откроется редактор, который мы указали при настройке Git, содержащий следующий текст:
При этом в консоли отобразится сообщение "hint: Waiting for your editor to close the file…" (благодаря параметру --wait, установленному в предыдущей статье).
Текст во вкладке COMMIT_EDITMSG частично напоминает то, что выводил git status. В начале каждой строки идет символ #, говорящий, что текст закомментирован и несет только информационную нагрузку. На него можно не обращать внимание (или удалить полностью), он все равно не попадет в коммит или его описание.
Осталось ввести сообщение, содержащее краткое описание тех изменений, которые были сделаны в файлах. Такое сообщение называется описание к коммиту. В качестве примера запишем многострочное описание:
В многострочном описании первая строка является заголовком, включающим в себя общий смысл проделанных изменений. Она не должна превышать 50 символов и не имеет точку в конце. Далее обязательно должна идти пустая строка. А затем уже идет перечисление конкретных изменений.
После ввода описания, сохраните, а затем закройте вкладку. 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 («Прочитай меня») с помощью Git и отобразим результат:

D:\Java\StartJava (master)
> git mv about.txt README.md && ls
README.md  src/
GitHub позволяет форматировать текст в файлах с расширением md, используя облегчённый язык разметки Markdown.
Откройте README.md (в файле с таким именем принято размещать описание проекта), написав в консоли его имя, и нажав Enter. Затем, используя Markdown, внесите в него следующий текст:

# [StartJava](https://topjava.ru/startjava) — курс на Java для начинающих

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

1. Java
1. Git/GitHub
1. Командная строка
1. Sublime Text
1. Checkstyle
1. Intellij IDEA
1. SQL
1. PostgreSQL
1. psql
Для отображения данного файла в браузере:
  • установите в Sublime Text пакет Markdown Preview
  • нажмите Ctrl + Shift + P
  • напишите preview
  • кликните по первой строке с надписью "MarkDown Preview: Preview in Browser"
Откроется страница в браузере с отформатированным текстом:
Далее проверим статус файла (вывод дан в сокращенном виде):

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
Важно запомнить, что текст к коммиту (описание) должен максимально точно и кратко отражать те изменения, которые были сделаны. Не пишите в него бессмыслицу, и не копируйте один и тот же текст из предыдущих коммитов.
В итоге мы имеем еще один коммит. В консоли отобразился уже знакомый текст. В нем новой для нас является информация об удаленном файле. Остальную часть сообщения мы уже разбирали.
Mr. X
В каких случаях и как часто необходимо делать коммит?
Max
  • вы закончили создание нового функционала
  • вы завершили запланированное исправление ошибок в коде
  • в конце рабочего дня
Дополнительные правила создания коммита смотрите по ссылке.

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)
Команда отобразила информацию о каждом коммите, расположив их в обратном порядке: последний коммит всегда будет вверху.
Для выхода из режима отображения нажмите клавишу 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.
Если вы впервые взаимодействуете через консоль с УР, то отобразится окно авторизации.
Воспользуйтесь, например, вкладкой Token, введя в ней токен, который мы создали в прошлой статье. Больше это окно вы не увидите. Введенные вами данные отныне хранятся в Диспетчере учетных данных.
Если авторизация прошла благополучно, то выполнится push.

> 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 всякий раз перед тем, как начинаете работать над своим проектом, но только при условии, что над ним работают разные люди или только вы, но с разных устройств. В противном случае просто так выполнять git pull смысла не имеет.

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

После того, как ваш (или чей-то) проект оказался на GitHub, любой желающий может скачать его копию с помощью git clone. При условии, что он публичный.
Для тренеровки скачайте следующий репозиторий в любое место на жестком диске, выполнив команду клонирования:
git clone https://github.com/ichimax/Java-Style-Guide.git

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 (с точкой в начале).
Давайте поэкспериментируем, создав условия для использования .gitignore. Самое простое, что мы можем сделать — это скомпилировать 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, будут игнорироваться Git. Это максимально универсальный способ, позволяющий не писать явно имя каждого class-файла, который требуется игнорировать.

Если нужно добавить для игнорирования папку, которая находится в одной директории с .gitignore, достаточно написать ее имя. Например, .gitignore может содержать следующие правила:

.idea/
lib/
out/
*.DS_Store
*.iml
После имени директории можно указывать /, говорящий о том, что это директория, но так делать не обязательно.
Добавляйте в .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

Заключение

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