Введение в Git/GitHub: базовые команды

Введение
В предыдущей статье были рассмотрены основы систем контроля версий, их виды, а также выполнена установка и настройка 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
В выводимом сообщении произошли изменения: 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 частично напоминает то, что выводил status. В начале каждой строки идет символ #, говорящий о том, что текст закомментирован и несет только информационную нагрузку. На него можно не обращать внимание (или удалить полностью), он все равно не попадет в коммит или его описание.
Осталось ввести сообщение, содержащее краткое описание тех изменений, которые были сделаны в файлах. Такое сообщение называется описание к коммиту. В первой строке файла напишите следующее:
Сохраните, а затем закройте вкладку. Git создаст коммит, добавит к нему описание и выведет о нем информацию в консоль.
D:\Java\StartJava\src (master)
> git commit
[master (root-commit) 21781bf] Мой первый коммит! Ура!
 2 files changed, 6 insertions(+)
 create mode 100644 about.txt
 create mode 100644 src/MyFirstApp.java
Полученный токен необходимо использовать в командной строке вместо пароля при выполнении операций Git через HTTPS.
  • master — для какой ветки он был выполнен
  • root-commit — является корневым (первым) коммитом
  • 21781bf — уникальный идентификатор (контрольная сумма)
  • Мой первый коммит! Ура! — описание
  • 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
1. 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 должен быть установлен):
Откроется страница в браузере с отформатированным текстом.
Далее проверим статус файла (вывод дан в сокращенном виде):
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:
  (use "git restore --staged <file>..." to unstage)
        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
+1. 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 d800731] Переименовал about.txt в README.md и внес в него описание проекта
 2 files changed, 14 insertions(+), 1 deletion(-)
 create mode 100644 README.md
 delete mode 100644 about.txt
Очень важно запомнить, что текст к коммиту (описание) должен максимально точно и кратко отражать те изменения, которые были сделаны. Не пишите в него бессмыслицу, и не копируй один и тот же текст из предыдущих коммитов.
В итоге мы имеем еще один коммит. В консоли отобразился уже знакомый нам текст. В нем новой для нас является информация об удаленном файле. Остальную часть сообщения мы уже разбирали.
В каких случаях и как часто необходимо делать коммит:
  • вы закончили создание нового функционала
  • вы завершили запланированное исправление ошибок в коде
  • в конце рабочего дня

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

Чтобы посмотреть список (историю) всех имеющихся коммитов, воспользуемся командой git log.
> git log
commit d800731d214fdb075cb16d75029844e1131a856f (HEAD -> master)
Author: ichimax <email>
Date:   Fri Mar 4 18:51:33 2022

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

commit 1c2d982ee972ebafe483bb89924331b8adb70cb7
Author: ichimax <email>
Date:   Tue Mar 1 10:06:23 2022

    Мой первый коммит! Ура!
Команда отобразила информацию о каждом коммите, расположив их в обратном порядке: последний коммит всегда будет вверху.
Информация к коммиту в себя включает следующие метаданные:
  • уникальный идентификатор коммита по которому можно его найти (длинный набор букв и цифр)
  • данные автора коммита, который его создал
  • дату создания коммита
  • краткое описание сделанных изменений

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

Сейчас оба наших коммита являются локальными — находятся в директории .git у вас на компьютере. Необходимо их загрузить (запушить) на УР на GitHub, используя команду push.
Предварительно введем git status.
> git status
On branch master
nothing to commit, working tree clean
Видим, что коммитить нам нечего, файлов, которые нужно добавить в индекс тоже нет. Выполним пуш, введя команду git push -u origin master.
Данная команда помимо того, что отправит на удаленный репозиторий коммиты, установит связь (с помощью параметра -u) между локальной и удаленной веткой master. В будущем, чтобы сделать пуш, достаточно будет ввести git push. Все остальные параметры указывать уже не обязательно.
Зайдем на УР и убедимся, что 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), 946 bytes | 45.00 KiB/s, done.
From https://github.com/ichimax/startjava2
   d800731..dd6b784  master     -> origin/master
Updating d800731..dd6b784
Fast-forward
 src/MyFirstApp.java | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
Теперь количество коммитов в ЛР и УР совпадают — данные актуализированы и находятся в ЛР в актуальном состоянии. Можно спокойно продолжить работу.

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

nothing added to commit but untracked files present (use "git add" to track)
Репозиторий содержит два новых неотслеживаемых файла. Один из них нужно игнорировать (чтобы Git его даже не отображал).
Для этого откроем. gitignore и пропишем в нем правило (шаблон), которое позволит игнорировать любые class-файлы. Для этого воспользуемся маской и расширением *.class. Эта запись означает, что все файлы (не важно, как они названы), которые имеют расширение class, будут игнорироваться Git. Это максимально универсальный способ, который позволяет не писать явно имя каждого class-файла, который нужно игнорировать/
В данный файл, по мере изучения Java, мы будем добавлять новые шаблоны.
Снова проверим статус репозитория:
> 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

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

Список команд, разобранных в статье, с кратким описанием.
Автор: Чимаев Максим
Оцените статью, если она вам понравилась!