Перейти к содержимому

Git head что это

  • автор:

7.7 Инструменты Git — Раскрытие тайн reset

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

Три дерева

Разобраться с командами reset и checkout будет проще, если считать, что Git управляет содержимым трёх различных деревьев. Здесь под «деревом» мы понимаем «набор файлов», а не специальную структуру данных. (В некоторых случаях индекс ведет себя не совсем так, как дерево, но для наших текущих целей его проще представлять именно таким.)

В своих обычных операциях Git управляет тремя деревьями:

Снимок последнего коммита, родитель следующего

Снимок следующего намеченного коммита

Указатель HEAD

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

На самом деле, довольно легко увидеть, что представляет из себя этот снимок. Ниже приведён пример получения содержимого каталога и контрольных сумм для каждого файла в HEAD:

$ git cat-file -p HEAD tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf author Scott Chacon 1301511835 -0700 committer Scott Chacon 1301511835 -0700 initial commit $ git ls-tree -r HEAD 100644 blob a906cb2a4a904a152. README 100644 blob 8f94139338f9404f2. Rakefile 040000 tree 99f1a6d12cb4b6f19. lib

Команды cat-file и ls-tree являются «служебными» (plumbing) командами, которые используются внутри системы и не требуются при ежедневной работе, но они помогают нам разобраться, что же происходит на самом деле.

Индекс

Индекс — это ваш следующий намеченный коммит. Мы также упоминали это понятие как «область подготовленных изменений» Git — то, что Git просматривает, когда вы выполняете git commit .

Git заполняет индекс списком изначального содержимого всех файлов, выгруженных в последний раз в ваш рабочий каталог. Затем вы заменяете некоторые из таких файлов их новыми версиями и команда git commit преобразует изменения в дерево для нового коммита.

$ git ls-files -s 100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README 100644 8f94139338f9404f26296befa88755fc2598c289 0 Rakefile 100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0 lib/simplegit.rb

Повторим, здесь мы используем служебную команду ls-files , которая показывает вам, как выглядит сейчас ваш индекс.

Технически, индекс не является древовидной структурой, на самом деле, он реализован как сжатый список (flattened manifest) — но для наших целей такого представления будет достаточно.

Рабочий Каталог

Наконец, у вас есть рабочий каталог. Два других дерева сохраняют свое содержимое эффективным, но неудобным способом внутри каталога .git . Рабочий Каталог распаковывает их в настоящие файлы, что упрощает для вас их редактирование. Считайте Рабочий Каталог песочницей, где вы можете опробовать изменения перед их коммитом в индекс (область подготовленных изменений) и затем в историю.

$ tree . ├── README ├── Rakefile └── lib └── simplegit.rb 1 directory, 3 files

Технологический процесс

Основное предназначение Git — это сохранение снимков последовательно улучшающихся состояний вашего проекта, путём управления этими тремя деревьями.

reset workflow

Давайте рассмотрим этот процесс: пусть вы перешли в новый каталог, содержащий один файл. Данную версию этого файла будем называть v1 и изображать голубым цветом. Выполним команду git init , которая создаст Git-репозиторий, у которого ссылка HEAD будет указывать на ещё несуществующую ветку ( master пока не существует).

reset ex1

На данном этапе только дерево Рабочего Каталога содержит данные.

Теперь мы хотим закоммитить этот файл, поэтому мы используем git add для копирования содержимого Рабочего Каталога в Индекс.

reset ex2

Затем, мы выполняем команду git commit , которая сохраняет содержимое Индекса как неизменяемый снимок, создает объект коммита, который указывает на этот снимок, и обновляет master так, чтобы он тоже указывал на этот коммит.

reset ex3

Если сейчас выполнить git status , то мы не увидим никаких изменений, так как все три дерева одинаковые.

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

reset ex4

Если сейчас мы выполним git status , то увидим, что файл выделен красным в разделе «Изменения, не подготовленные к коммиту», так как его представления в Индексе и Рабочем Каталоге различны. Затем мы выполним git add для этого файла, чтобы поместить его в Индекс.

reset ex5

Если сейчас мы выполним git status , то увидим, что этот файл выделен зелёным цветом в разделе «Изменения, которые будут закоммичены», так как Индекс и HEAD различны — то есть, наш следующий намеченный коммит сейчас отличается от нашего последнего коммита. Наконец, мы выполним git commit , чтобы окончательно совершить коммит.

reset ex6

Сейчас команда git status не показывает ничего, так как снова все три дерева одинаковые.

Переключение веток и клонирование проходят через похожий процесс. Когда вы переключаетесь (checkout) на ветку, HEAD начинает также указывать на новую ветку, ваш Индекс замещается снимком коммита этой ветки, и затем содержимое Индекса копируется в ваш Рабочий Каталог.

Назначение команды reset

Команда reset становится более понятной, если рассмотреть её с учётом вышеизложенного.

В следующих примерах предположим, что мы снова изменили файл file.txt и закоммитили его в третий раз. Так что наша история теперь выглядит так:

reset start

Давайте теперь внимательно проследим, что именно происходит при вызове reset . Эта команда простым и предсказуемым способом управляет тремя деревьями, существующими в Git. Она выполняет три основных операции.

Шаг 1: Перемещение указателя HEAD

Первое, что сделает reset — переместит то, на что указывает HEAD. Обратите внимание, изменяется не сам HEAD (что происходит при выполнении команды checkout ); reset перемещает ветку, на которую указывает HEAD. Таким образом, если HEAD указывает на ветку master (то есть вы сейчас работаете с веткой master ), выполнение команды git reset 9e5e6a4 сделает так, что master будет указывать на 9e5e6a4 .

reset soft

Не важно с какими опциями вы вызвали команду reset с указанием коммита ( reset также можно вызывать с указанием пути), она всегда будет пытаться сперва сделать данный шаг. При вызове reset —soft на этом выполнение команды и остановится.

Теперь взгляните на диаграмму и постарайтесь разобраться, что случилось: фактически была отменена последняя команда git commit . Когда вы выполняете git commit , Git создает новый коммит и перемещает на него ветку, на которую указывает HEAD. Если вы выполняете reset на HEAD~ (родителя HEAD), то вы перемещаете ветку туда, где она была раньше, не изменяя при этом ни Индекс, ни Рабочий Каталог. Вы можете обновить Индекс и снова выполнить git commit , таким образом добиваясь того же, что делает команда git commit —amend (смотрите Изменение последнего коммита).

Шаг 2: Обновление Индекса (—mixed)

Заметьте, если сейчас вы выполните git status , то увидите отмеченные зелёным цветом изменения между Индексом и новым HEAD.

Следующим, что сделает reset , будет обновление Индекса содержимым того снимка, на который указывает HEAD.

reset mixed

Если вы указали опцию —mixed , выполнение reset остановится на этом шаге. Такое поведение также используется по умолчанию, поэтому если вы не указали совсем никаких опций (в нашем случае git reset HEAD~ ), выполнение команды также остановится на этом шаге.

Снова взгляните на диаграмму и постарайтесь разобраться, что произошло: отменен не только ваш последний commit , но также и добавление в индекс всех файлов. Вы откатились назад до момента выполнения команд git add и git commit .

Шаг 3: Обновление Рабочего Каталога (—hard)

Третье, что сделает reset — это приведение вашего Рабочего Каталога к тому же виду, что и Индекс. Если вы используете опцию —hard , то выполнение команды будет продолжено до этого шага.

reset hard

Давайте разберемся, что сейчас случилось. Вы отменили ваш последний коммит, результаты выполнения команд git add и git commit , а также все изменения, которые вы сделали в рабочем каталоге.

Важно отметить, что только указание этого флага ( —hard ) делает команду reset опасной, это один из немногих случаев, когда Git действительно удаляет данные. Все остальные вызовы reset легко отменить, но при указании опции —hard команда принудительно перезаписывает файлы в Рабочем Каталоге. В данном конкретном случае, версия v3 нашего файла всё ещё остаётся в коммите внутри базы данных Git и мы можем вернуть её, просматривая наш reflog , но если вы не коммитили эту версию, Git перезапишет файл и её уже нельзя будет восстановить.

Резюме

Команда reset в заранее определённом порядке перезаписывает три дерева Git, останавливаясь тогда, когда вы ей скажете:

  1. Перемещает ветку, на которую указывает HEAD (останавливается на этом, если указана опция —soft )
  2. Делает Индекс таким же как и HEAD (останавливается на этом, если не указана опция —hard )
  3. Делает Рабочий Каталог таким же как и Индекс.

Reset с указанием пути

Основной форме команды reset (без опций —soft и —hard ) вы также можете передавать путь, с которым она будет оперировать. В этом случае, reset пропустит первый шаг, а на остальных будет работать только с указанным файлом или набором файлов. Первый шаг пропускается, так как HEAD является указателем и не может ссылаться частично на один коммит, а частично на другой. Но Индекс и Рабочий Каталог могут быть изменены частично, поэтому reset выполняет шаги 2 и 3.

Итак, предположим вы выполнили команду git reset file.txt . Эта форма записи (так как вы не указали ни SHA-1 коммита, ни ветку, ни опций —soft или —hard ) является сокращением для git reset —mixed HEAD file.txt , которая:

  1. Перемещает ветку, на которую указывает HEAD (будет пропущено)
  2. Делает Индекс таким же как и HEAD (остановится здесь)

То есть, фактически, она копирует файл file.txt из HEAD в Индекс.

reset path1

Это создает эффект отмены индексации файла. Если вы посмотрите на диаграммы этой команды и команды git add , то увидите, что их действия прямо противоположные.

reset path2

Именно поэтому в выводе git status предлагается использовать такую команду для отмены индексации файла. (Смотрите подробности в Отмена индексации файла.)

Мы легко можем заставить Git «брать данные не из HEAD», указав коммит, из которого нужно взять версию этого файла. Для этого мы должны выполнить следующее git reset eb43bf file.txt .

reset path3

Можно считать, что, фактически, мы в Рабочем Каталоге вернули содержимое файла к версии v1, выполнили для него git add , а затем вернули содержимое обратно к версии v3 (в действительности все эти шаги не выполняются). Если сейчас мы выполним git commit , то будут сохранены изменения, которые возвращают файл к версии v1, но при этом файл в Рабочем Каталоге никогда не возвращался к такой версии.

Заметим, что как и команде git add , reset можно указывать опцию —patch для отмены индексации части содержимого. Таким способом вы можете избирательно отменять индексацию или откатывать изменения.

Слияние коммитов

Давайте посмотрим, как, используя вышеизложенное, сделать кое-что интересное — слияние коммитов.

Допустим, у вас есть последовательность коммитов с сообщениями вида «упс.», «В работе» и «позабыл этот файл». Вы можете использовать reset для того, чтобы просто и быстро слить их в один. (В разделе Объединение коммитов главы 7 представлен другой способ сделать то же самое, но в данном примере проще воспользоваться reset .)

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

reset squash r1

Вы можете выполнить git reset —soft HEAD~2 , чтобы вернуть ветку HEAD на какой-то из предыдущих коммитов (на первый коммит, который вы хотите оставить):

reset squash r2

Затем просто снова выполните git commit :

reset squash r3

Теперь вы можете видеть, что ваша «достижимая» история (история, которую вы впоследствии отправите на сервер), сейчас выглядит так — у вас есть первый коммит с файлом file-a.txt версии v1, и второй, который изменяет файл file-a.txt до версии v3 и добавляет file-b.txt . Коммита, который содержал файл версии v2 не осталось в истории.

Сравнение с checkout

Наконец, вы можете задаться вопросом, в чем же состоит отличие между checkout и reset . Как и reset , команда checkout управляет тремя деревьями Git, и также её поведение зависит от того указали ли вы путь до файла или нет.

Без указания пути

Команда git checkout [branch] очень похожа на git reset —hard [branch] , в процессе их выполнения все три дерева изменяются так, чтобы выглядеть как [branch] . Но между этими командами есть два важных отличия.

Во-первых, в отличие от reset —hard , команда checkout бережно относится к рабочему каталогу, и проверяет, что она не трогает файлы, в которых есть изменения. В действительности, эта команда поступает немного умнее — она пытается выполнить в Рабочем Каталоге простые слияния так, чтобы все файлы, которые вы не изменяли, были обновлены. С другой стороны, команда reset —hard просто заменяет всё целиком, не выполняя проверок.

Второе важное отличие заключается в том, как эти команды обновляют HEAD. В то время как reset перемещает ветку, на которую указывает HEAD, команда checkout перемещает сам HEAD так, чтобы он указывал на другую ветку.

Например, пусть у нас есть ветки master и develop , которые указывают на разные коммиты и мы сейчас находимся на ветке develop (то есть HEAD указывает на неё). Если мы выполним git reset master , сама ветка develop станет ссылаться на тот же коммит, что и master . Если мы выполним git checkout master , то develop не изменится, но изменится HEAD. Он станет указывать на master .

Итак, в обоих случаях мы перемещаем HEAD на коммит A, но важное отличие состоит в том, как мы это делаем. Команда reset переместит также и ветку, на которую указывает HEAD, а checkout перемещает только сам HEAD.

reset checkout

С указанием пути

Другой способ выполнить checkout состоит в том, чтобы указать путь до файла. В этом случае, как и для команды reset , HEAD не перемещается. Эта команда как и git reset [branch] file обновляет файл в индексе версией из коммита, но дополнительно она обновляет и файл в рабочем каталоге. То же самое сделала бы команда git reset —hard [branch] file (если бы reset можно было бы так запускать) — это небезопасно для рабочего каталога и не перемещает HEAD.

Также как git reset и git add , команда checkout принимает опцию —patch для того, чтобы позволить вам избирательно откатить изменения содержимого файла по частям.

Заключение

Надеюсь, вы разобрались с командой reset и можете её спокойно использовать. Но, возможно, вы всё ещё немного путаетесь, чем именно она отличается от checkout , и не запомнили всех правил, используемых в различных вариантах вызова.

Ниже приведена памятка того, как эти команды воздействуют на каждое из деревьев. В столбце «HEAD» указывается «REF» если эта команда перемещает ссылку (ветку), на которую HEAD указывает, и «HEAD» если перемещается только сам HEAD. Обратите особое внимание на столбец «Сохранность рабочего каталога?» — если в нём указано НЕТ, то хорошенько подумайте прежде чем выполнить эту команду.

На уровне коммитов

Git: наглядная справка

Если вы не видите иллюстраций, попробуйте переключиться на версию со стандартными картинками (без SVG).

SVG изображения были отключены. (Включить SVG изображения)

На этой странице представлена краткая наглядная справка для наиболее часто используемых команд git. Если у вас уже есть небольшой опыт работы с git, эта страница поможет вам закрепить ваши знания. Если вам интересно, как был создан этот сайт, загляните на мой репозиторий на GitHub.

Основные команды

Следующие четыре команды предназначены для копирования файлов между рабочей директорией, сценой, также известной как «индекс», и историей, представленной в форме коммитов.

  • git add файлы копирует файлы в их текущем состоянии на сцену.
  • git commit сохраняет снимок сцены в виде коммита.
  • git reset — файлы восстанавливает файлы на сцене, а именно копирует файлы из последнего коммита на сцену. Используйте эту команду для отмены изменений, внесённых командой git add файлы . Вы также можете выполнить git reset , чтобы восстановить все файлы на сцене.
  • git checkout — файлы копирует файлы со сцены в рабочую директорию. Эту команду удобно использовать, чтобы сбросить нежелательные изменения в рабочей директории.

Вы можете использовать git reset -p , git checkout -p , и git add -p вместо имён файлов или вместе с ними, чтобы в интерактивном режиме выбирать, какие именно изменения будут скопированы.

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

  • git commit -a аналогичен запуску двух команд: git add для всех файлов, которые существовали в предыдущем коммите, и git commit.
  • git commit файлы создаёт новый коммит, в основе которого лежат уже существующие файлы, добавляя изменения только для указанных файлов. Одновременно, указанные файлы будут скопированы на сцену.
  • git checkout HEAD — файлы копирует файлы из текущего коммита и на сцену, и в рабочую директорию.

Соглашения

Иллюстрации в этой справке выдержаны в единой цветовой схеме.

Коммиты раскрашены зелёным цветом и подписаны 5-ти буквенными идентификаторами. Каждый коммит указывает на своего родителя зелёной стрелочкой. Ветки раскрашены оранжевым цветом; ветки указывают на коммиты. Специальная ссылка HEAD указывает на текущую ветку. На иллюстрации вы можете увидеть последние пять коммитов. Самый последний коммит имеет хеш ed489. main (текущая ветка) указывает на этот коммит, stable (другая ветка) указывает на предка main-ового коммита.

Подробно о командах

Diff

Есть много способов посмотреть изменения между коммитами. Ниже вы увидите несколько простых примеров. К каждой из этих команд можно добавить имена файлов в качестве дополнительного аргумента. Так мы выведем информацию об изменениях только для перечисленных файлов.

Commit

Когда вы делаете коммит, git создаёт новый объект коммита, используя файлы со сцены, а текущей коммит становится родителем для нового. После этого указатель текущей ветки перемещается на новый коммит. Вы это видите на картинке, где main — это текущая ветка. До совершения коммита main указывал на коммит ed489. После добавления нового коммита f0cec, родителем которого стал ed489, указатель ветки main был перемещён на новый коммит.

То же самое происходит, если одна ветка является предком другой ветки. Ниже показан пример нового коммита 1800b в ветке stable, которая является предком ветки main. После этого ветка stable уже больше не является предком ветки main. И в случае необходимости объединения работы, проделанной в этих разделённых ветках, вам следует воспользоваться командой merge (что более предпочтительно) или rebase.

Если вы сделали ошибку в последнем коммите, её легко исправить с помощью команды git commit —amend . Эта команда создаёт новый коммит, родителем которого будет родитель ошибочного коммита. Старый ошибочный коммит будет отброшен, конечно же если только на него не будет ещё каких-либо других ссылок, что маловероятно.

Четвертый случай коммита из состояния «detached HEAD» будет рассмотрен далее.

Checkout

Команда checkout используется для копирования файлов из истории или сцены в рабочую директорию. Также она может использоваться для переключения между ветками.

Когда вы указываете имя файла (и/или ключ -p ), git копирует эти файлы из указанного коммита на сцену и в рабочую директорию. Например, git checkout HEAD~ foo.c копирует файл foo.c из коммита HEAD~ (предка текущего коммита) в рабочую директорию и на сцену. Если имя коммита не указано, то файл будет скопирован со сцены в рабочую директорию. Обратите внимание на то, что при выполнении команды checkout позиция указателя текущей ветки (HEAD) остаётся прежней, указатель никуда не перемещается.

В том случае, если мы не указываем имя файла, но указываем имя локальной ветки, то указатель HEAD будет перемещён на эту ветку, то есть мы переключимся на эту ветку. При этом сцена и рабочая директория будут приведены в соответствие с этим коммитом. Любой файл, который присутствует в новом коммите (a47c3 ниже), будет скопирован из истории; любой файл, который был в старом коммите (ed489), но отсутствует в новом, будет удалён; любой файл, который не записан ни в одном коммите, будет проигнорирован.

В том случае, если мы не указываем имя файла, и не указываем имя локальной ветки, а указываем тег, дистанционную (remote) ветку, SHA-1 хеш коммита или что-то вроде main~3, то мы получаем безымянную ветку, называемую «Detached HEAD» (оторванная голова). Это очень полезная штука, если нам надо осмотреться в истории коммитов. К примеру, вам захочется скомпилировать git версии 1.6.6.1. Вы можете набрать git checkout v1.6.6.1 (это тег, не ветка), скомпилировать, установить, а затем вернуться в другую ветку, скажем git checkout main . Тем не менее, коммиты из состояния «Detached HEAD» происходят по своим особым важным правилам, и мы рассмотрим их ниже.

Коммит из состояния «Detached HEAD»

Когда мы находимся в состоянии оторванной головы (Detached HEAD), коммит совершается по тем же правилам, что и обычно, за исключением одной маленькой особенности: ни один указатель ветки не будет изменён или добавлен к новому коммиту. Вы можете представить эту ситуацию как работу с анонимной веткой.

Если после такого коммита вы переключитесь в ветку main, то коммит 2eecb, совершённый из состояния «Detached HEAD», потеряется и попросту будет уничтожен очередной сборкой мусора только потому, что нет ни одного объекта, который бы на него ссылался: ни ветки, ни тега.

В том случае, если вы хотите сохранить этот коммит на будущее, вы можете создать на основе него новую ветку командой git checkout -b new .

Reset

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

Если коммит указан без имён файлов, указатель ветки будет перемещён на этот коммит, а затем сцена приведётся в соответствие с этим коммитом. Если мы используем ключ —soft , то сцена не будет изменена. Если мы используем ключ —hard , то будет обновлена и сцена, и рабочая директория.

Если имя коммита не будет указано, по умолчанию оно будет HEAD. В этом случае указатель ветки не будет перемещён, но сцена (а также и рабочая директория, если был использован ключ —hard ) будет приведена к состоянию последнего коммита.

Если в команде указано имя файла (и/или ключ -p ), то команда работает так же, как checkout с именем файла, за исключением того, что только сцена (но не рабочая директория) будет изменена. Если вы подставите имя коммита на место двойной черты, вы сможете получить состояние файла из этого коммита, тогда как в случае с двойной чертой вы получите состояние файла из коммита, на который указывает HEAD.

Merge

Команда merge (слияние) создает новый коммит на основе текущего коммита, применяя изменения других коммитов. Перед слиянием сцена должна быть приведена в соответствие с текущим коммитом. Самый простой случай слияния — это когда другой коммит является предком текущего коммита: в этом случае ничего не происходит. Другой простой случай слияния — когда текущий коммит является предком другого коммита: в этом случае происходит быстрая перемотка (fast-forward). Ссылка текущей ветки будет просто перемещена на новый коммит, а сцена и рабочая директория будут приведены в соответствие с новым коммитом.

Во всех других случаях выполняется «настоящее» слияние. Вы можете изменить стратегию слияния, но по умолчанию будет выполнено «рекурсивное» слияние, для которого будет взят текущий коммит (ed489 ниже на схеме), другой коммит (33104) и их общий предок (b325c); и для этих трех коммитов будет выполнено трёхстороннее слияние. Результат этого слияния будет записан в рабочую директорию и на сцену, и будет добавлен результирующий коммит со вторым родителем (33104).

Cherry Pick

Команда cherry-pick («вишенка в тортике») создаёт новый коммит на основе только одного сладкого «коммита-вишенки», применив все его изменения и сообщение.

Rebase

Перебазирование (rebase) — это альтернатива слиянию для задач объединения нескольких веток. Если слияние создаёт новый коммит с двумя родителями, оставляя нелинейную историю, то перебазирование применяет все коммиты один за одним из одной ветки в другую, оставляя за собой линейную историю коммитов. По сути это автоматическое выполнение нескольких команд cherry-pick подряд.

На схеме выше вы видите как команда берёт все коммиты, которые есть в ветке topic, но отсутствуют в ветке main (коммиты 169a6 and 2c33a), и воспроизводит их в ветке main. Затем указатель ветки перемещается на новое место. Следует заметить, что старые коммиты будут уничтожены сборщиком мусора, если на них уже ничего не будет ссылаться.

Используйте ключ —onto чтобы ограничить глубину захвата объединяемой ветки. На следующей схеме вы можете увидеть как в ветку main приходят лишь последние коммиты из текущей ветки, а именно коммиты после (но не включая) 169a6, т. е. 2c33a.

Технические заметки

Содержание файлов не хранится в индексе (.git/index) или в объектах коммитов. Правильнее было бы сказать, что каждый файл хранится в базе данных объектов (.git/objects) в двоичном представлении; найти этот файл можно по его SHA-1 хешу. В файле индекса записаны имена файлов, их хеши и дополнительная информация. В информации о коммитах вы встретите тип данных дерево, для идентификации которого также используется SHA-1 хеш. Деревья описывают директории в рабочей директории, а также содержат информацию о других деревьях и файлах, принадлежащих обозначенному дереву. Каждый коммит хранит идентификатор своего верхнего дерева, которое содержит все файлы и другие деревья, изменённые в этом коммите.

Если вы делаете коммит из состояния «оторванной головы» (detached HEAD), то на этот коммит будет ссылаться ссылка истории HEAD. Но рано или поздно время хранения этой ссылки истечёт, и этот коммит будет уничтожен сборщиком мусора точно так же, как это делается при выполнении команд git commit —amend и git rebase .

Copyright © 2010, Mark Lodato. Russian translation © 2012, Alex Sychev.

Это произведение доступно по лицензии Creative Commons Attribution-NonCommercial-ShareAlike (Атрибуция — Некоммерческое использование — С сохранением условий) 3.0 США.

Основные команды и определения

Коммит в git-репозитории хранит снимок всех файлов в директории. Коммит — это набор изменений или «дельта» между текущей версией и предыдущей.

Код Описание
git commit Коммит

Ветвления

Ветки — это просто ссылки на определённый коммит — ничего более.

Код Описание
git branch [name] Создание ветки
git checkout [name] Переключение ветки

Слияния

Слияния в Git создают особый вид коммита, который имеет сразу двух родителей. Коммит с двумя родителями обычно означает, что мы хотим объединить изменения из одного коммита с другим коммитом и всеми их родительскими коммитами.

Код Описание
git merge [name] Слияние веток

Git Rebase

При rebase Git по сути копирует набор коммитов и переносит их в другое место. Преимущество rebase в том, что c его помощью можно делать чистые и красивые линейные последовательности коммитов. История коммитов будет чище, если вы применяете rebase.

Код Описание
git rebase [name] Rebase

HEAD

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

Отделение (detaching) HEAD

Отделение (detaching) HEAD означает лишь присвоение его не ветке, а конкретному коммиту.

Код Описание
HEAD -> master -> C1
git checkout C1
HEAD -> C1 Отделение (detaching) HEAD

Относительные ссылки

С относительными ссылками можно начать с какого-либо удобного места (например, с ветки или от HEAD) и двигаться от него.

Код Описание
git checkout [name]^ Перемещение на один коммит назад
git checkout [name]~[num] Перемещение на num коммитов назад

Перемещение ветки (branch forcing)

Одна из наиболее распространённых целей, для которых используются относительные ссылки — это перемещение веток. Можно напрямую прикрепить ветку к коммиту при помощи опции -f .

Код Описание
git branch -f master HEAD~3 Переместить (принудительно) ветку master на три родителя назад от HEAD.

Отмена изменений в Git

Есть много путей для отмены изменений в Git. Так же как и коммит, отмена изменений в Git возможна и на низком уровне (добавление в коммит отдельных файлов и наборов строк), и на высоком (как изменения реально отменяются). Сейчас сфокусируемся на высокоуровневой части.

Есть два основных способа отмены изменений в Git.

git revert [name]

Git Reset

Git reset отменяет изменения, перенося ссылку на ветку назад, на более старый коммит. Это своего рода «переписывание истории»; git reset перенесёт ветку назад, как будто некоторых коммитов вовсе и не было.

Код Описание
git reset HEAD~1 Git просто перенёс ссылку на master обратно на коммит C1. Теперь наш локальный репозиторий в состоянии, как будто C2 никогда не существовал.

Git Revert

Git reset отлично работает на локальных ветках, в локальных репозиториях. Но этот метод переписывания истории не сработает на удалённых ветках, которые используют другие пользователи.

Чтобы отменить изменения и поделиться отменёнными изменениями с остальными, надо использовать git revert .

Код Описание
Git revert HEAD Новый коммит C2′ просто содержит изменения, полностью противоположные тем, что сделаны в коммите C2.

Git Cherry-pick

Это очень простой и прямолинейный способ сказать, что ты хочешь копировать несколько коммитов на место, где сейчас находишься (HEAD).

Код Код
git cherry-pick [commit1] [commit2] [. ] git cherry-pick C2 C4

Git Interactive Rebase

Git cherry-pick применяется, когда точно известно, какие коммиты нужны (и известны их точные хеши).

Но, в случае, когда точно не известно какие коммиты нужны можно использовать интерактивный rebase — лучший способ отобрать набор коммитов для rebase .

Код Код Описание
git rebase -i [name] git rebase -i HEAD~4 Rebase ordering C3, C4

results matching » «

No results matching » «

Что такое remotes/origin/HEAD?

Я так понял, что HEAD может указывать только на ветки удаленного репазитория. Но почему тогда после переключения на удаленную ветку дев стока HEAD все равно указывает на мастер??

remotes/origin/HEAD -> origin/master 

Вот согласно статье на хабре

текущее состояние не изменённых файлов, находящихся под контролем версий, есть тот коммит, на который указывает HEAD

ниче не понятно, HEAD в итоге указывает на ту ветку где ты находится или на ветку в которую ты сделал последний коммит?

Отслеживать
задан 10 июл 2016 в 9:05
10.9k 17 17 золотых знаков 62 62 серебряных знака 127 127 бронзовых знаков
head — это указатель на текущее состояние. См. habrahabr.ru/post/157175
10 июл 2016 в 10:02
зачем вы постоянно ставите метки github и gitlab?
10 июл 2016 в 10:40
@Etki ну мне так показалось как будто этот вопрос относится к ним.
10 июл 2016 в 10:41
gitlab и github — всего лишь хранилища, никак не изменяющие интерфейс гита.
10 июл 2016 в 11:19

4 ответа 4

Сортировка: Сброс на вариант по умолчанию

ветка (branch) в git — это (плавающий) указатель на commit.

HEAD / .git/HEAD (технически) — это файл, содержащий указатель либо на текущую (для репозитория) ветку (например, файл может содержать такой текст ref: refs/heads/master ), либо на текущий commit (т.н. detached head, файл содержит строку с хэш-суммой этого commit-а).

удалённый репозиторий ничем не «хуже» вашего локального, и в нём тоже есть такой файл, и информация, выдаваемая командой branch :

remotes/origin/HEAD -> origin/master 

сообщает вам о том, что этот файл в удалённом репозитории в момент клонирования содержал ссылку на ветку master (в том же, удалённом репозитории).

Я так понял, что head это та ветка в которую я делаю пуш

push вы делаете в ту ветку, которую сами и указали. либо явно, например так:

$ git push репозиторий ветка 

либо неявно, «привязав» вашу локальную ветку к (произвольной) ветке в удалённом репозитории, и вызывая git push без дополнительных параметров.

посмотреть «привязки» веток можно, например, командой remote show репозиторий :

$ git remote show origin . Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (up to date) 

по поводу дополнения к вопросу

во-первых, я уже ответил практически на тот же вопрос: Почему получаю detached head?.

во-вторых, вынесу (и дополню) сюда основное:

    эта строка ( remotes/origin/HEAD -> origin/master ) в выдаче команды branch появляется благодаря наличию в вашем локальном хранилище файла refs/remotes/origin/HEAD , содержащего в вашем случае:

$ cat .git/refs/remotes/origin/HEAD ref: refs/remotes/origin/master 

Я так понял, что HEAD может указывать только на ветки удаленного репазитория.

  • либо указатель на локальную ветку (т.е., содержать что-то вроде ref: refs/heads/master )
  • либо хэш коммита (т.н. «состояние detached head»)

Но почему тогда после переключения на удаленную ветку дев стока HEAD все равно указывает на мастер?

remotes/origin/HEAD -> origin/master 

Вот согласно статье на хабре текущее состояние не изменённых файлов, находящихся под контролем версий, есть тот коммит, на который указывает HEAD

  • если файл .git/HEAD содержит указатель на локальную ветку (т.е., содержит что-то вроде ref: refs/heads/master ), то, значит, у вас в рабочем каталоге распакован коммит, на который указывает локальная ветка master . хэш этого коммита хранится в файле .git/refs/heads/master
  • если же файл .git/HEAD содержит хэш коммита (т.н. «состояние detached head»), то именно этот коммит сейчас и распакован в вашем рабочем каталоге.

Отслеживать
ответ дан 10 июл 2016 в 10:30
aleksandr barakin aleksandr barakin
67.9k 216 216 золотых знаков 77 77 серебряных знаков 219 219 бронзовых знаков

Я вот такой ветки у себя ни разу не видел. Может ли вопрос автора быть связан с тем, что его удаленный репозиторий имеет рабочую копию (не является bare)?

10 июл 2016 в 10:34

Кроме того, не понятно почему HEAD удаленного репозитория вообще оказалась вытянута. Я почему-то всегда думал, что «вытягиваются» только refs/heads/* , а HEAD под этот шаблон не попадает же.

10 июл 2016 в 10:37

@PavelMayorov, Может ли вопрос автора быть связан с тем, что его удаленный репозиторий имеет рабочую копию (не является bare)? — скорее всего, так и есть. по крайней мере у меня в тестах такая строка в выдаче branch -r присутствует только в такой ситуации.

10 июл 2016 в 10:43

@PavelMayorov, «вытягиваются» только refs/heads/* — «вытягивается» то, что указано в директиве fetch в файле config. по умолчанию это +refs/heads/*:refs/remotes/origin/* . вот в refs/remotes/origin и попадает файл HEAD . и, насколько вижу, он не изменяется и остаётся таким же, каким создан при клонировании.

10 июл 2016 в 10:44
@PavelMayorov, stackoverflow.com/q/12613793/4827341
10 июл 2016 в 10:59

head это то состояния версии в которой вы в данный момент находитесь. это значит, если вы переключились на другую ветку и у вас хед позади других коммитов, значит вам нужно сделать pull , чтобы получить свежую версию этой ветки.

И для переключения между ветками лучше использовать git checkout , а для просмотра удаленных веток git remote .

Отслеживать
ответ дан 10 июл 2016 в 9:11
2,904 19 19 серебряных знаков 44 44 бронзовых знака

remotes/origin/master 

Это ветка, предназначенная «только для чтения». Её положение обозначает последнее известное (после последнего push / pull / fetch ) положение ветки master на сервере origin .

remotes/origin/HEAD -> origin/master 

HEAD в Git это «текущая верхушка». Локальный HEAD обычно показывает место на графе коммитов, в котором сейчас находится рабочее дерево [0] (например, сделан checkout ).

Но здесь другой случай. это не просто HEAD , а из origin . Как и все remote-ветки, предназначен только для чтения, но на что он указывает?

На практике origin/HEAD обычно указывает ветку по умолчанию для удалённого репозитория. Практических последствий у этого почти никаких, разве что при клонировании репозитория локальный HEAD будет там же, где на момент клонирования был HEAD на сервере. Когда вы работаете у себя на машине, origin/HEAD вам не нужен.

Ситуация становится чуть интереснее, когда к удалённому репозиторию есть интерфейс доступа. Для него HEAD может означать что-то ещё. Например, на GitHub origin/HEAD или, как у них называется, default branch можно выбрать в настройках, и прочитать пояснение о том, для чего это надо:

The default branch is considered the “base” branch in your repository, against which all pull requests and code commits are automatically made, unless you specify a different branch.

(«Ветка по умолчанию считается «основной» веткой в вашем репозитории, в которую делаются все pull-request’ы и коммиты, если не выбирать другую ветку явно.» прим. пер.)

Почему origin/* только для чтения? Эти ветки являются «отражением» [1] того, что есть на сервере origin . Поэтому эти ветки меняются только тогда, когда ваши действия меняют репозиторий в origin . Скажем, в процессе push Git передаёт серверу коммиты, дожидается его подтверждения о получении и сдвигает «отражение» на новую точку. Любые изменения в обход этого механизма рискуют сломать саму суть origin/* как отражений.

Если переместиться в ветку-отражение (посредством checkout ), Git заметит, что её нельзя [2] менять, поэтому HEAD перейдёт не в ветку, а на коммит, на котором она находится, отделившись от веток (отсюда detached HEAD ). И с этого момента надо быть осторожным.

текущее состояние не изменённых файлов, находящихся под контролем версий, есть тот коммит, на который указывает HEAD

Надеюсь, из текста выше понятно, что у вас есть HEAD (ваш) и origin/HEAD (удалённый). В статье о первом. А в списке веток есть только последний.

[0] на самом деле скорее наоборот, рабочее дерево само по себе, а HEAD нужен, чтобы вычислить и записать внесённые изменения, это «отправная точка»
[1] этот термин честно выдуман мной, употребляйте осторожно
[2] можно всё, но многие вещи ломают настолько много, что оно того не стоит

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *