Git: коммиты, ветки и перемещение между ними


Вот мы и добрались, пожалуй, до самой важной процедуры — сохранения изменений или коммит. Для того чтобы понять, что такое коммит — нужно дать еще немного теории об объектах git. В git существуют четыре объекта:
- Blob — объект, содержащий длину содержимого файла и само содержимое;
- Tree — объект, содержащий данные о состоянии директорий и поддиректорий проекта;
- Commit — ссылка на корневое дерево. Так как объект Tree хранит в себе иерархию нижележащих объектов, корень проекта — содержит всю иерархию проекта, поэтому на него ссылается коммит;
- Annotated Tag — логическая разметка для маркирования состояний объектов в git.
У всех git-объектов есть уникальные хеш-номера, закодированные алгоритмом SHA1. Они служат в качестве ссылок, чтобы одни git-объекты могли ссылаться на другие git-объекты, формируя тем самым дерево. Git интерпретирует структуру директорий вашего проекта, как дерево состоящие из blob и tree объектов, связанных между собой хеш-линками, а коммит ссылает на корень дерева.
Каждый раз, когда вы сохраняете в репозитории состояние проекта — git отстраивает новое дерево с новыми объектами в нём с уникальными хеш-значениями. Вот пример git-структуры простого проекта, состоящего из двух директорий и пяти файлов:

Важно понимать, что git сохраняет объект целиком, не разность между содержанием версий, а всё содержание целиком, присваивая хеши и упаковывая в специальные pack-файлы. Такой подход, конечно, не экономичный по дисковому пространству, зато позволяет перемещаться между версиями файлов.
Внутренние устройство коммита
Давайте, более детально разберемся в том, из чего состоит коммит. Коммит состоит из шести элементов:

- Указатель коммита — SHA1-хеш, который идентифицирует коммит;
- Email и имя автора — данные вносятся автоматически на основании параметров git config;
- Описание коммита — здесь указываются изменения, которые были внесены, версия коммита и иные данные, которые помогут понять, что коммит содержит;
- Дерево — это хеш-ссылка на корневое дерево проекта, которое содержит структуру всего проекта;
- Родительский коммит — это хеш-ссылка на предыдущий коммит;
- HEAD — указатель на текущую версию проекта, которая находится в рабочей директории проекта.
Все коммиты связаны между собой хеш-ссылками и организуют цепочку, а смещение указателя HEAD позволяет изменять текущее состояние файлов рабочий директории. Строго говоря HEAD ссылается на ветку, а ветка ссылается на последний коммит в ней, но вы можете в ручном режиме смещать HEAD на конкретный коммит минуя ветку.
Итак, теперь, когда мы имеем представление о том, что такое коммит и как он работает — можно создавать первый коммит с помощью команды git commit -m “< message >» . Можно конечно и не указывать ключ -m, но тогда вам придётся работать с текстовым редактором в терминале — это не всегда удобно, поэтому проще использовать ключ -m.
После выполнения коммита в терминал будет выведен отчет о том, какие файлы были добавлены в репозиторий:

Просмотреть историю коммитов и сведений о них можно с помощью команды git log. Команда вернет список коммитов, их хеш-ссылки, описание коммитов и указатель HEAD:

По мере добавления новых коммитов, первый коммит (корневой) будет смещаться вниз списка, а указатель HEAD всегда будет ссылаться на ветку, которая в свою очередь ссылается на последний коммит:

Одна из основных фишек git — это перемещение между коммитами. Посмотрим, как это можно сделать.
Перемещение между коммитами
Напомним, что git сохраняет изменения в файле в репозитории целиком, что позволяет перемещаться между коммитами командой git checkout [commit hesh]. Узнать хеш коммита можно командой git log. Перемещаться между коммитами можно для просмотра удаленных частей кода или восстановления удаленных файлов.
Вообще, откреплять HEAD от ветки не рекомендуется, но это можно сделать для восстановления удаленных файлов. Откатываться назад и коммитить там — нельзя. Оно получится, но вы не увидите изменений в основной ветке. Перемещение между коммитами сделано только для ознакомления с состоянием файлов в прошлом, а вот перемещение между ветками — это базовая и очень полезная фишка git.
Познакомимся с концепцией ветвей в git.
Ветви

Ветка, в терминологии git, — это ссылка на последний коммит. Указатель HEAD ссылается на ветку, ветка ссылается на последний коммит. Ветки нужны для независимой работы разработчиков над своими зонами ответственности. Например, ваш проект имеет бэкенд и фронтенд части и, очевидно, над ними работают разные команды. В репозитории заводятся 2 разные ветки проекта: main и dev. Графически, эту структуру можно представить так:

Main — основная ветка проекта, в которой находится код рабочего (продакшн) проекта, а в ветке dev — ведется разработка нового функционала, чтобы разграничить команды, в ветке dev можно создать еще 2 ветки: frontend и backend. Теперь команды могут независимо друг от друга разрабатывать новый функционал, тестировать его, а после влить в основную ветку. Мы же сделали немного по другому: в ветке flask — мы будем разрабатывать web-часть нашего проекта, а в ветке cli — терминальную часть проекта.
Существуют общепринятые правила именования веток. Так, основная ветка проекта называется main. В gitHUB по умолчанию используется именно такое название, однако в git до сих пор по умолчанию основная ветка называется master. Исправить ситуацию можно двумя путями:
- Указать в конфигурационном файле git дефолтное название ветки. Сделать это можно командой git config —global init.defaultBranch main.
- Переименовать вручную ветку master в main, командой git branch -m master main.
Разница между этими двумя способами в том, что в первом случае все последующие репозитории на локальной машине будут использовать одну единую конфигурацию, второй способ работает только для одного конкретного репозитория. Ветку в которой ведется разработка обычно называют dev (development). Дочерние ветки обычно называют так, чтобы было понятно над чем в ней работают.
Создание новых веток
Отлично, мы переименовали ветку master в main командой git branch -m master main . Теперь создадим несколько новых веток согласно нашей схеме проекта с помощью команд git branch [branch name]: git branch flask , git branch cli . Теперь выполним команду git branch -a , чтобы просмотреть список доступных веток:

Из вывода команды git branch -a видно, что теперь у нас есть три ветки и мы сейчас находимся в ветке main, чтобы перейти в другую ветку нужно использовать команду git checkout [branch name] . Можно воспользоваться комбинированной командой git branch -b [branch name] для создания и автоматического перемещения в новую ветку.
Теперь с помощью команды git log посмотрим историю изменений. Мы увидим, что HEAD ссылается на ту ветку, где мы сейчас находимся, но также появились ещё две новые ветки: flask и cli, которые также связаны с последним коммитом:

Теперь, когда мы будем разрабатывать и тестировать web-часть нашего приложения — мы это будем делать в ветке flask, в основной ветке эти файлы отражаться не будут. После того как мы закончим разработку web-части нам необходимо слить ветки flask и main.
Слияние веток
Слияние веток или merge (мердж) — это создание коммита в основной ветке, который содержит ссылки на два родительских коммита: последний коммит в основной ветке и последний коммит в ветке, где разрабатывался функционал, который нужно интегрировать в основную ветку.
Графически процесс слияния веток можно представить так:

Технически тут происходит следующие: берутся все изменения веток flask и main, создается специальный мердж коммит в ветке main у которого прописаны два родительских коммита: коммит 9 из основной ветки и коммит 5 из ветки flask. Так при создании мердж коммита сохраняется история предыдущих коммитов в обеих ветках и при вызове команды git log вы будете видеть коммиты в обеих ветках.
Сливаются ветки командой git merge -m «commit text» [feature branch] , при этом вы должны находится в основной ветке, в которую нужно влить фиче-ветку. Если конфликтов слияния нет — вы увидите следующий вывод в терминале, в котором будет содержаться информация о стратегии слияния и файлах, которые были добавлены в репозиторий:

После успешного слияния можно выполнить команду git log и убедиться в том, что теперь коммиты фиче-ветки видны в основной ветке:

После слияния веток, фиче-ветка больше не нужна и её можно удалить командой git branch -d [branch name] . Вы можете столкнуться с проблемами слияния веток, когда изменения в один и тот же файлы вносились в разных ветках — тогда git выведет ошибку слияния и вам нужно будет вручную устранять расхождения в разных версиях файла в разных ветках.
Итак, мы подробно разобрались в том, что такое коммит, что такое ветка, и как с ними работать. Давайте, подведем итог по двум частям статьи и двинемся дальше — познакомимся с gitHUB и узнаем как загружать локальный репозиторий в gitHUB.
Базовые команды git и работа с его объектами

Напомним, что git — это система версионирования файлов, дающая возможность вести независимую разработку нескольким командам разработчиков в разных ветках проекта. Ветки в git — это просто указатели на последние коммиты в этих ветках. Коммит — это сущность git, указывающая на хеш корневого дерева проекта, корневое дерево в свою очередь содержит ссылки на хеши других деревьев (директорий проекта) и blob-файлов — сущностей хранящих изменения в файлах проекта.
Для работы с git на локальной машине используются терминал. Все действия с объектами git осуществляются через git-команды, которые условно можно разделить на несколько групп: команды для работы с репозиторием, команды для работы с ветками и команды настройки git. Вот список git-команд, которых будет достаточно для выполнения большинства базовых задач в git:
Команды работы с репозиторием:
- git init — инициализация репозитория;
- git status — просмотр актуального состояния репозитория git. Эта команда показывает, какие файлы были изменены, какие файлы находятся в индексе, а какие файлы вообще не трекаются гитом;
- git add [file name] — добавление файла или файлов в индекс, подготовка их для коммита. Если нужно добавить все файлы из директории используется оператор точка — git add .;
- git commit -m “[ message ]» — создать коммит и указать его описание;
- git log — просмотреть сделанные коммиты и информацию о них;
- git checkout [commit hash ] — перейти в нужную версию проекта или коммит по его хешу, который отражается в выводе команды git log
- git cat-file -t [hash git-object] — посмотреть тип git-объекта по хешу;
- git cat-file -p [hash git-object] — прочитать содержимое файлов git-объектов по хешу.
Команды работы с ветками:
- git branch [branch name] — создать ветку;
- git branch -a — просмотреть все доступные ветки;
- git branch -m [branch name] [branch name] — переименовать ветку;
- git checkout [branch name ] — перейти в другую ветку;
- git branch -b [branch name] — создать новую ветку и переместиться в неё;
- git branch -d [branch name] — удалить ветку по имени;
- git merge -m «commit text» [feature branch] — слить ветки, используя стандартную стратегию git-слияния.
Команды настройки git:
- git config —global init.defaultBranch [branch name] — задать название метки по умолчанию;
- git config —global user.name [имя пользователя] — задать имя пользователя глобально, для всех репозиториев на локальной машине;
- git config —global user.email [электронная почта пользователя] — задать электронную почту пользователя глобально, для всех репозиториев на локальной машине.
В следующей статье мы разберемся в том, что такое gitHUB, как с ним работать, как загружать в него код проекта и скачать его на локальную машину. Прочитать предыдущую статью про git можно по этой ссылке, а заказать бюджетный сервер для разработки можно по кнопке ниже.
Как в Git вернуться к последнему коммиту?
Решил посмотреть состояние проекта на пару коммитов назад. Дал команду:
git checkout хешСтарогоКоммита
Посмотрел, ничего не менял. Решил вернуться к последнему коммиту. Даю команду:
git checkout хешПоследнегоКоммита
Проект переключился на последний коммит. Но проект находится в ненормальном состоянии:
> git status HEAD detached at 9b93eec nothing to commit, working directory clean
Отчего так происходит? Как по-нормальному вернуться в последнее закоммиченное состояние проекта?
Но это по-моему слишком заковыристо для такого простого действия как «посмотреть что было и вернуться обратно».

Xintrea ★★★★★
13.08.15 13:12:42 MSK
git checkout master или git checkout HEAD
Deleted
( 13.08.15 13:18:45 MSK )

А что не так ? по ссылке все правильно.
joy4eg ★★★★★
( 13.08.15 13:19:27 MSK )

Возвращаться после однократного git checkout hash удобнее всего с помощью:
git checkout -
(Работает по аналогии с cd — .)
xaizek ★★★★★
( 13.08.15 13:24:56 MSK )
Последнее исправление: xaizek 13.08.15 13:28:55 MSK (всего исправлений: 1)
Ответ на: комментарий от Deleted 13.08.15 13:18:45 MSK

Часто вижу такую рекомендацию, но лично у меня эта команда правильно не работает. Git что ли из другой вселенной? Как был detached так и остается detached.
> git checkout HEAD > git status HEAD detached at 9b93eec nothing to commit, working directory clean
Сработала бессмысленная команда:
git checkout имяВеткиГдеВыНаходитесь
Вот она переносит проект в последнее состояние нормально.
Xintrea ★★★★★
( 13.08.15 13:26:19 MSK ) автор топика
Ответ на: комментарий от Deleted 13.08.15 13:18:45 MSK

Наверное, имелось в виду:
git checkout HEAD@
Так как восстановить сбитый HEAD переходом на него же не выйдёт.
xaizek ★★★★★
( 13.08.15 13:27:12 MSK )
Ответ на: комментарий от Xintrea 13.08.15 13:26:19 MSK
Часто вижу такую рекомендацию, но лично у меня эта команда правильно не работает.
HEAD names the commit on which you based the changes in the working tree
грубо говоря, этот shortcut контексто-зависим, и может означать много разных вещей.
это была правильная команда.
это не совсем точное название. в состоянии detached head, ты не находишься ни в какой ветке. git checkout branchname просто переключила ветку на ту что ты захотел.
waker ★★★★★
( 13.08.15 13:31:32 MSK )
Последнее исправление: waker 13.08.15 13:33:37 MSK (всего исправлений: 2)

git checkout ветка_с_которой_ты_работал
git reflog покажет тебе историю твоих перемещений по коммитам
Для того, чтобы «посмотреть состояние проекта на пару коммитов назад» не обязательно делать checkout, достаточно git show HEAD~2:путь_файла
annulen ★★★★★
( 13.08.15 13:33:23 MSK )

Вторую команду нужно заменить на git checkout имяИсходнойВетки .
2.3 Основы Git — Просмотр истории коммитов
После того, как вы создали несколько коммитов или же клонировали репозиторий с уже существующей историей коммитов, вероятно вам понадобится возможность посмотреть что было сделано — историю коммитов. Одним из основных и наиболее мощных инструментов для этого является команда git log .
Следующие несколько примеров используют очень простой проект «simplegit». Чтобы клонировать проект, используйте команду:
$ git clone https://github.com/schacon/simplegit-progit
Если вы запустите команду git log в каталоге клонированного проекта, вы увидите следующий вывод:
$ git log commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 Change version number commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 Remove unnecessary test commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon Date: Sat Mar 15 10:31:28 2008 -0700 Initial commit
По умолчанию (без аргументов) git log перечисляет коммиты, сделанные в репозитории в обратном к хронологическому порядке — последние коммиты находятся вверху. Из примера можно увидеть, что данная команда перечисляет коммиты с их SHA-1 контрольными суммами, именем и электронной почтой автора, датой создания и сообщением коммита.
Команда git log имеет очень большое количество опций для поиска коммитов по разным критериям. Рассмотрим наиболее популярные из них.
Одним из самых полезных аргументов является -p или —patch , который показывает разницу (выводит патч), внесённую в каждый коммит. Так же вы можете ограничить количество записей в выводе команды; используйте параметр -2 для вывода только двух записей:
$ git log -p -2 commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 Change version number diff --git a/Rakefile b/Rakefile index a874b73..8f94139 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ require 'rake/gempackagetask' spec = Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = "simplegit" - s.version = "0.1.0" + s.version = "0.1.1" s.author = "Scott Chacon" s.email = "schacon@gee-mail.com" s.summary = "A simple gem for using Git in Ruby code." commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 Remove unnecessary test diff --git a/lib/simplegit.rb b/lib/simplegit.rb index a0a60ae..47c6340 100644 --- a/lib/simplegit.rb +++ b/lib/simplegit.rb @@ -18,8 +18,3 @@ class SimpleGit end end - -if $0 == __FILE__ - git = SimpleGit.new - puts git.show -end
Эта опция отображает аналогичную информацию но содержит разницу для каждой записи. Очень удобно использовать данную опцию для код ревью или для быстрого просмотра серии внесённых изменений. Так же есть возможность использовать серию опций для обобщения. Например, если вы хотите увидеть сокращённую статистику для каждого коммита, вы можете использовать опцию —stat :
$ git log --stat commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon Date: Mon Mar 17 21:52:11 2008 -0700 Change version number Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40:33 2008 -0700 Remove unnecessary test lib/simplegit.rb | 5 ----- 1 file changed, 5 deletions(-) commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon Date: Sat Mar 15 10:31:28 2008 -0700 Initial commit README | 6 ++++++ Rakefile | 23 +++++++++++++++++++++++ lib/simplegit.rb | 25 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+)
Как вы видите, опция —stat печатает под каждым из коммитов список и количество изменённых файлов, а также сколько строк в каждом из файлов было добавлено и удалено. В конце можно увидеть суммарную таблицу изменений.
Следующей действительно полезной опцией является —pretty . Эта опция меняет формат вывода. Существует несколько встроенных вариантов отображения. Опция oneline выводит каждый коммит в одну строку, что может быть очень удобным если вы просматриваете большое количество коммитов. К тому же, опции short , full и fuller делают вывод приблизительно в том же формате, но с меньшим или большим количеством информации соответственно:
$ git log --pretty=oneline ca82a6dff817ec66f44342007202690a93763949 Change version number 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Remove unnecessary test a11bef06a3f659402fe7563abf99ad00de2209e6 Initial commit
Наиболее интересной опцией является format , которая позволяет указать формат для вывода информации. Особенно это может быть полезным когда вы хотите сгенерировать вывод для автоматического анализа — так как вы указываете формат явно, он не будет изменен даже после обновления Git:
$ git log --pretty=format:"%h - %an, %ar : %s" ca82a6d - Scott Chacon, 6 years ago : Change version number 085bb3b - Scott Chacon, 6 years ago : Remove unnecessary test a11bef0 - Scott Chacon, 6 years ago : Initial commit
Полезные опции для git log —pretty=format отображает наиболее полезные опции для изменения формата.
Как перейти на последний коммит в ветке?
Какой командой через консоль можно обновить текущие версии файлов в ветке до последнего коммита?
Документация предлагает использовать команду git pull — скачать все изменения из обновить файлы до последней версии.
Но проблема в том, что я использую jgit — это скрипт для Solaris (официальных пакетов для git нет), а в нем нет такой команды.
Есть команда fetch — но это только для скачивания из удаленного репозитория изменений.
Есть команда reset —hard — но для этого нужно знать хэш последнего коммита.
Какие ещё есть команды в git, кроме pull?
- Вопрос задан более трёх лет назад
- 14781 просмотр
Комментировать
Решения вопроса 1
git fetch git checkout git merge origin/
Ответ написан более трёх лет назад