Spec-Zone.ru › Git

gitcore-tutorial

Имя

gitcore-tutorial - Руководство по работе с ядром Git для разработчиков

Синопсис

git *

Описание

В этом руководстве объясняется, как использовать команды Git «ядра» для настройки и работы с репозиторием Git.

Если вам просто нужно использовать Git как систему управления версиями, вы можете начать с «Введения в Git» (gittutorial[7]) или Справочника пользователя Git.

Однако понимание этих низкоуровневых инструментов может быть полезным, если вы хотите понять внутреннее устройство Git.

Ядро Git часто называют «сантехникой», а более удобные пользовательские интерфейсы, которые на ней основаны, — «фарфором». Возможно, вам не нужно часто пользоваться непосредственно сантехникой, но полезно знать, что делает сантехника, когда фарфор не функционирует.

Когда этот документ был первоначально написан, многие команды фарфора были скриптами оболочки. Для простоты в качестве примеров по-прежнему используются скрипты оболочки, чтобы проиллюстрировать, как сантехника объединяется в команды фарфора. В дереве исходных кодов некоторые из этих скриптов есть в contrib/examples/ для справки. Хотя они больше не реализованы как скрипты оболочки, описание того, что делают команды уровня сантехники, все еще актуально.

Примечание
Более подробные технические детали часто помечаются как Примечания, которые вы можете пропустить при первом чтении.

Создание репозитория Git

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

В нашем первом примере мы создадим совершенно новый репозиторий с нуля, без предварительно существующих файлов, и назовем его git-tutorial. Для начала создайте подкаталог для него, перейдите в этот подкаталог и инициализируйте инфраструктуру Git с помощью git init:

$ mkdir git-tutorial
$ cd git-tutorial
$ git init

на что Git ответит:

Initialized empty Git repository in .git/

что просто означает, что вы ничего странного не делаете, и что он создаст локальный .git каталог, настроенный для вашего нового проекта. У вас теперь будет каталог .git, и вы можете проверить его с помощью ls. Для вашего нового пустого проекта он должен показать вам три записи, помимо прочего:

  • файл под названием HEAD, содержащий ref: refs/heads/master. Он похож на символическую ссылку и указывает на refs/heads/master относительно файла HEAD.

    Не беспокойтесь о том, что файл, на который указывает ссылка HEAD, еще не существует — вы еще не создали коммит, который запустит ветвь разработки HEAD.

  • подкаталог под названием objects, который будет содержать все объекты вашего проекта. У вас никогда не должно быть реальной причины смотреть на объекты напрямую, но вы можете знать, что эти объекты содержат все реальные data в вашем репозитории.

  • подкаталог под названием refs, который содержит ссылки на объекты.

В частности, подкаталог refs будет содержать два других подкаталога, названные соответственно heads и tags. Они делают именно то, что подразумевают их названия: они содержат ссылки на любое количество различных heads разработки (также называемые branches) и на любые tags ветви, которые вы создали для именования определенных версий в вашем репозитории.

Примечание: специальная ветвь master является ветвью по умолчанию, поэтому файл .git/HEAD указывает на нее, даже если она еще не существует. По сути, ссылка HEAD должна всегда указывать на ветвь, на которой вы работаете прямо сейчас, и вы всегда начинаете работать на ветви master.

Однако это всего лишь соглашение, и вы можете назвать свои ветви как угодно и даже не обязаны создавать ветвь master. Многие инструменты Git будут считать, что .git/HEAD действительны.

Примечание
Объект идентифицируется по своему 160-битному хэшу SHA-1, также называемому именем объекта, а ссылка на объект всегда представляет собой 40-байтовый шестнадцатеричный код этого имени SHA-1. Файлы в подкаталоге refs должны содержать эти шестнадцатеричные ссылки (обычно с окончанием \n), и поэтому вы должны ожидать увидеть ряд файлов размером 41 байт, содержащих эти ссылки в этих подкаталогах refs при фактическом заполнении вашего дерева.
Примечание
Более опытный пользователь может ознакомиться с gitrepository-layout[5] после завершения этого руководства.

Вы создали свой первый репозиторий Git. Конечно, поскольку он пустой, это не очень полезно, поэтому давайте начнем заполнять его данными.

Заполнение репозитория Git

Мы сделаем это просто и понятно, поэтому начнем с заполнения нескольких тривиальных файлов, чтобы понять принцип работы.

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

$ echo "Hello World" >hello
$ echo "Silly example" >example

Теперь у вас есть два файла в вашей рабочей области (также известной как working directory), но чтобы действительно зафиксировать свою работу, вам нужно выполнить два шага:

  • Заполните файл index (также известный как cache) информацией о состоянии вашей рабочей области.

  • Зафиксируйте этот файл индекса как объект.

Первый шаг тривиален: когда вы хотите сообщить Git о любых изменениях в вашей рабочей области, вы используете программу git update-index. Эта программа обычно принимает список имен файлов, которые нужно обновить, но чтобы избежать тривиальных ошибок, она отказывается добавлять новые записи в индекс (или удалять существующие), если вы не укажете явно, что добавляете новую запись с флагом --add (или удаляете запись с флагом --remove)).

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

$ git update-index --add hello example

и теперь вы сообщили Git о необходимости отслеживания этих двух файлов.

В действительности, если вы теперь посмотрите в свой каталог объектов, вы заметите, что Git добавит два новых объекта в базу данных объектов. Если вы выполнили все шаги выше, вы теперь можете сделать

$ ls .git/objects/??/*

и увидеть два файла:

.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
.git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962

которые соответствуют объектам с именами 557db... и f24c7... соответственно.

Если хотите, можете использовать git cat-file для просмотра этих объектов, но вам нужно использовать имя объекта, а не имя файла объекта:

$ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238

где -t сообщает git cat-file сообщить вам, какой тип объекта. Git сообщит вам, что у вас есть объект "blob" (то есть обычный файл), и вы можете увидеть его содержимое с помощью

$ git cat-file blob 557db03

что выведет "Hello World". Объект 557db03 — это просто содержимое вашего файла hello.

Примечание
Не путайте этот объект с самим файлом hello. Объект — это буквально это конкретное содержимое файла, и как бы вы ни изменяли содержимое файла hello в дальнейшем, объект, который мы только что рассмотрели, никогда не изменится. Объекты неизменны.
Примечание
Второй пример демонстрирует, что вы можете сократить имя объекта до первых нескольких шестнадцатеричных цифр в большинстве мест.

В любом случае, как мы упоминали ранее, вы обычно не смотрите сами объекты, и вводить длинные 40-значные шестнадцатеричные имена — это не то, что вы обычно хотите делать. Это отступление было просто для демонстрации того, что git update-index выполнило нечто магическое и фактически сохранило содержимое ваших файлов в базе данных объектов Git.

Обновление индекса также выполнило еще одну задачу: оно создало файл .git/index. Это индекс, описывающий вашу текущую рабочую область, и вы должны быть с этим очень осторожны. Опять же, вы обычно не беспокоитесь о самом файле индекса, но вы должны знать, что до сих пор вы не действительно "зафиксировали" свои файлы в Git, а только сообщили Git об их существовании.

Однако, поскольку Git знает о них, вы теперь можете начать использовать некоторые из самых основных команд Git для управления файлами или просмотра их состояния.

В частности, давайте не будем еще фиксировать эти два файла в Git, а сначала добавим еще одну строку в hello:

$ echo "It's a new day for git" >>hello

Теперь, поскольку вы сообщили Git о предыдущем состоянии hello, вы можете попросить Git показать, что изменилось в дереве по сравнению с вашим старым индексом, используя команду git diff-files.

$ git diff-files

Ошибочка. Это не очень читабельно. Это просто выводит свою собственную внутреннюю версию diff, но эта внутренняя версия в основном говорит вам, что она заметила, что "hello" был изменен, и что старое содержимое объекта было заменено чем-то другим.

Чтобы сделать вывод читабельным, мы можем сказать git diff-files вывести различия в виде патча, используя флаг -p:

$ git diff-files -p
diff --git a/hello b/hello
index 557db03..263414f 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
 Hello World
+It's a new day for git

т. е. разница в изменении, которое мы сделали, добавив еще одну строку в hello.

Другими словами, git diff-files всегда показывает нам разницу между тем, что записано в индексе, и тем, что сейчас находится в рабочей области. Это очень полезно.

Удобное сокращение для git diff-files -p — это просто git diff, что сделает то же самое.

$ git diff
diff --git a/hello b/hello
index 557db03..263414f 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
 Hello World
+It's a new day for git

Фиксация состояния git

Теперь мы переходим к следующему этапу в Git: взятие файлов, о которых Git знает в индексе, и фиксирование их как реального дерева. Это делается в два этапа: создание объекта tree и фиксирование этого объекта tree как объекта commit вместе с объяснением, что такое дерево, а также информацией о том, как мы пришли к этому состоянию.

Создание объекта дерева тривиально и выполняется с помощью git write-tree. Нет параметров или другого ввода: git write-tree возьмёт текущее состояние индекса и запишет объект, описывающий весь индекс. Другими словами, мы теперь связываем все разные имена файлов с их содержимым (и их разрешениями) и создаём эквивалент объекта "каталога" Git:

$ git write-tree

и это выведет имя получившегося дерева, в этом случае (если вы сделали всё точно так, как описано), это должно быть

8988da15d077d4829fc51d8544c097def6644dbb

что является ещё одним непонятным именем объекта. Опять же, если хотите, можете использовать git cat-file -t 8988d... , чтобы увидеть, что на этот раз объект — не объект "blob", а объект "tree" (вы также можете использовать git cat-file для фактического вывода содержимого объекта, но увидите в основном двоичный мусор, поэтому это менее интересно).

Однако, обычно вы не используете git write-tree в одиночку, потому что обычно вы всегда фиксируете дерево в объект коммита с помощью команды git commit-tree. На самом деле, проще вообще не использовать git write-tree в одиночку, а просто передать его результат в качестве аргумента команде git commit-tree.

git commit-tree обычно принимает несколько аргументов — он хочет знать, каков parent коммита, но поскольку это первый коммит в этом новом репозитории и у него нет родителей, нам нужно только передать имя объекта дерева. Однако git commit-tree также хочет получить сообщение о коммите со стандартного ввода и выведет имя получившегося объекта коммита в стандартный вывод.

И здесь мы создаём файл .git/refs/heads/master, на который ссылается HEAD. Этот файл должен содержать ссылку на вершину дерева ветви master, и поскольку git commit-tree выводит именно это, мы можем сделать всё это последовательностью простых команд оболочки:

$ tree=$(git write-tree)
$ commit=$(echo 'Initial commit' | git commit-tree $tree)
$ git update-ref HEAD $commit

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

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

Внесение изменений

Помните, как мы выполнили git update-index в файле hello, а затем изменили hello после этого, и могли сравнить новое состояние hello с состоянием, которое мы сохранили в файле индекса?

Кроме того, помните, как я сказал, что git write-tree записывает содержимое файла индекса в дерево, и поэтому то, что мы только что зафиксировали, на самом деле было исходным содержимым файла hello, а не новым. Мы сделали это намеренно, чтобы показать разницу между состоянием индекса и состоянием рабочей области и то, что они не обязательно должны совпадать, даже когда мы фиксируем изменения.

Как и раньше, если мы выполним git diff-files -p в нашем проекте git-tutorial, мы по-прежнему увидим ту же разницу, что и в прошлый раз: файл индекса не изменился в результате фиксации каких-либо изменений. Однако теперь, когда мы что-то зафиксировали, мы также можем научиться использовать новую команду: git diff-index.

В отличие от git diff-files, которая показывала разницу между файлом индекса и рабочей областью, git diff-index показывает различия между зафиксированным деревом и файлом индекса или рабочей областью. Другими словами, git diff-index требует дерева для сравнения, и до фиксации мы не могли этого сделать, потому что у нас не было ничего для сравнения.

Но теперь мы можем сделать

$ git diff-index -p HEAD

(где -p имеет то же значение, что и в git diff-files), и это покажет нам ту же разницу, но по совершенно другой причине. Теперь мы сравниваем рабочую область не с файлом индекса, а с деревом, которое мы только что записали. Просто так получается, что эти два явно совпадают, поэтому мы получаем тот же результат.

Опять же, поскольку это распространенная операция, вы также можете использовать сокращённую запись

$ git diff HEAD

которая выполняет все вышеперечисленное за вас.

Другими словами, git diff-index обычно сравнивает дерево с рабочей областью, но при использовании флага --cached он сравнивает его только с содержимым кэша индекса и полностью игнорирует состояние текущей рабочей области. Поскольку мы только что записали файл индекса в HEAD, выполнение git diff-index --cached -p HEAD должно вернуть пустой набор различий, и именно это оно и делает.

Примечание

git diff-index всегда использует индекс для сравнений, и утверждение, что он сравнивает дерево с рабочей областью, поэтому не совсем точно. В частности, список файлов для сравнения ("метаданные") всегда берется из файла индекса, независимо от того, используется флаг --cached или нет. Флаг --cached определяет только то, поступает ли содержимое файла для сравнения из рабочей области или нет.

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

Однако, наш следующий шаг — зафиксировать внесённое изменение. И снова, чтобы понять, что происходит, имейте в виду разницу между "содержимым рабочей области", "файлом индекса" и "зафиксированным деревом". У нас есть изменения в рабочей области, которые мы хотим зафиксировать, и мы всегда должны работать через файл индекса, поэтому первое, что нам нужно сделать, это обновить кэш индекса:

$ git update-index hello

(обратите внимание, что на этот раз нам не потребовался флаг --add, так как Git уже знал об этом файле).

Обратите внимание на то, что происходит с различными git diff-* версиями здесь. После обновления hello в индексе, git diff-files -p теперь не показывает различий, но git diff-index -p HEAD показывает, что текущее состояние отличается от состояния, которое мы зафиксировали. На самом деле, теперь git diff-index показывает ту же разницу, используем ли мы флаг --cached или нет, поскольку теперь индекс согласован с рабочей областью.

Теперь, поскольку мы обновили hello в индексе, мы можем зафиксировать новую версию. Мы могли бы это сделать, написав дерево вручную снова и зафиксировав дерево (на этот раз нам пришлось бы использовать флаг -p HEAD чтобы сказать commit, что HEAD был предком нового коммита, и что это больше не начальный коммит), но вы уже делали это один раз, поэтому давайте в этот раз просто воспользуемся полезной утилитой:

$ git commit

которая запускает редактор для записи сообщения о коммите и рассказывает вам немного о том, что вы сделали.

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

Вы теперь сделали свой первый реальный Git коммит. И если вас интересует, что именно git commit делает, не стесняйтесь изучить: это несколько очень простых скриптов оболочки для генерации полезных (?) заголовков сообщения о коммите и несколько однострочных команд, которые на самом деле выполняют сам коммит (git commit).

Просмотр изменений

Хотя создание изменений полезно, ещё более полезно уметь определить, что изменилось позднее. Наиболее полезной командой для этого является другая команда из семейства diff, а именно git diff-tree.

git diff-tree может получить два произвольных дерева, и она покажет различия между ними. Однако ещё чаще вы можете передать ей только один объект коммита, и она сама определит родительский коммит и покажет разницу непосредственно. Таким образом, чтобы получить тот же diff, который мы уже видели несколько раз, мы теперь можем сделать

$ git diff-tree -p HEAD

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

Примечание

Вот ASCII-арт Джона Лоелигера, иллюстрирующий, как различные команды diff-* сравнивают вещи.

            diff-tree
             +----+
             |    |
             |    |
             V    V
          +-----------+
          | Object DB |
          |  Backing  |
          |   Store   |
          +-----------+
            ^    ^
            |    |
            |    |  diff-index --cached
            |    |
diff-index  |    V
            |  +-----------+
            |  |   Index   |
            |  |  "cache"  |
            |  +-----------+
            |    ^
            |    |
            |    |  diff-files
            |    |
            V    V
          +-----------+
          |  Working  |
          | Directory |
          +-----------+

Более интересно, вы также можете передать git diff-tree флаг --pretty, который укажет ей показать сообщение о коммите, автора и дату коммита, а также показать всю цепочку диффов. Или вы можете сделать её "беззвучной", чтобы она не показывала диффы вообще, а только само сообщение о коммите.

Фактически, вместе с программой git rev-list (которая генерирует список ревизий), git diff-tree оказывается настоящим источником изменений. Вы можете эмулировать git log, git log -p, и т.д. с помощью тривиального скрипта, который передаёт вывод git rev-list в git diff-tree --stdin, что было именно так реализованы ранние версии git log.

Разметка версии

В Git есть два типа тегов: "лёгкий" и "аннотированный".

Лёгкий тег по сути является веткой, но мы помещаем её в подкаталог .git/refs/tags/ вместо того, чтобы называть её head. Таким образом, самый простой способ создания тега заключается в следующем:

$ git tag my-first-tag

Это просто записывает текущий HEAD в файл .git/refs/tags/my-first-tag, после чего вы можете использовать это символическое имя для этого конкретного состояния. Например, вы можете сделать

$ git diff my-first-tag

для сравнения вашего текущего состояния с этим тегом, что в данный момент будет пустым diff'ом, но если вы будете продолжать развивать проект и делать коммиты, вы можете использовать ваш тег как "точку отсчёта" для просмотра изменений, внесённых с момента его создания.

Аннотированный тег — это фактически реальный объект Git, который содержит не только указатель на состояние, которое вы хотите пометить, но также небольшое имя и сообщение тега, а также необязательную цифровую подпись PGP, подтверждающую, что вы действительно создали этот тег. Вы создаёте аннотированные теги, используя флаги -a или -s к git tag:

$ git tag -s <tagname>

что подпишет текущий HEAD (но вы также можете указать другой аргумент, который указывает на то, что именно нужно пометить, например, вы могли бы пометить текущую mybranch точку с помощью git tag <tagname> mybranch).

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

Копирование репозиториев

Репозитории Git обычно полностью самодостаточны и переносимы. В отличие от CVS, например, нет отдельного понятия «репозитория» и «рабочей директории». Репозиторий Git обычно является рабочей директорией, при этом локальная информация Git скрыта в поддиректории .git. Больше ничего нет. То, что вы видите, — это всё.

Примечание
Вы можете указать Git разделить внутреннюю информацию Git от директории, которую он отслеживает, но мы пока проигнорируем это: так обычно не работают обычные проекты, и это действительно предназначено только для специальных случаев. Таким образом, ментальная модель «информация Git всегда напрямую связана с рабочей директорией, которую она описывает», может быть не технически на 100% точной, но это хорошая модель для всех обычных случаев.

Это имеет два следствия:

  • если вы устали от созданного вами репозитория (или допустили ошибку и хотите начать всё заново), вы можете просто выполнить

    $ rm -rf git-tutorial

    и он исчезнет. Внешнего репозитория нет, и нет истории вне созданного вами проекта.

  • если вы хотите переместить или дублировать репозиторий Git, вы можете это сделать. Существует git clone команда, но если всё, что вам нужно, это создать копию вашего репозитория (со всей полной историей, которая с ним связана), вы можете сделать это с помощью обычной cp -a git-tutorial new-git-tutorial.

    Обратите внимание, что при перемещении или копировании репозитория Git файл индекса Git (который кэширует различную информацию, в частности, некоторые данные «stat» для вовлечённых файлов) вероятно потребуется обновить. Поэтому после выполнения cp -a для создания новой копии, вы захотите выполнить

    $ git update-index --refresh

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

Обратите внимание, что второй пункт справедлив и между машинами. Вы можете дублировать удалённый репозиторий Git с помощью любого обычного механизма копирования, будь то scp, rsync или wget.

При копировании удалённого репозитория вам нужно будет как минимум обновить кэш индекса при этом, и особенно с репозиториями других людей, вам часто нужно убедиться, что кэш индекса находится в некотором известном состоянии (вы не знаете, что они сделали и ещё не зафиксировали), поэтому обычно перед git update-index вы будете выполнять

$ git read-tree --reset HEAD
$ git update-index --refresh

что принудительно перестроит индекс из дерева, на которое указывает HEAD. Это сбросит содержимое индекса до HEAD, а затем git update-index позаботится о сопоставлении всех записей индекса с проверенными файлами. Если в рабочей директории исходного репозитория были незафиксированные изменения, git update-index --refresh заметит их и сообщит, что их нужно обновить.

Вышесказанное также можно записать просто как

$ git reset

и на самом деле многие распространённые комбинации команд Git могут быть запрограммированы с помощью git xyz интерфейсов. Вы можете узнать, просто взглянув на то, что делают различные скрипты git. Например, git reset раньше представлял собой две вышеупомянутые строки, реализованные в git reset, но некоторые вещи, например, git status и git commit — это немного более сложные скрипты вокруг основных команд Git.

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

Чтобы создать свою собственную локальную живую копию такого «сырого» репозитория Git, вы сначала создадите свою собственную поддиректорию для проекта, а затем скопируете содержимое сырого репозитория в директорию .git. Например, чтобы создать собственную копию репозитория Git, вы выполните следующее

$ mkdir my-git
$ cd my-git
$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git

за которым следует

$ git read-tree HEAD

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

$ git checkout-index -u -a

где флаг -u означает, что вы хотите, чтобы проверка сохранила индекс обновлённым (чтобы вам не пришлось обновлять его впоследствии), а флаг -a означает «проверить все файлы» (если у вас есть устаревшая копия или более старая версия проверочного дерева, вам может потребоваться добавить флаг -f вначале, чтобы сказать git checkout-index принудительно перезаписать любые старые файлы).

Опять же, всё это можно упростить с помощью

$ git clone git://git.kernel.org/pub/scm/git/git.git/ my-git
$ cd my-git
$ git checkout

что в итоге сделает всё вышеперечисленное за вас.

Теперь вы успешно скопировали удалённый репозиторий другого человека (мой), и проверили его.

Создание новой ветки

Ветки в Git — это всего лишь указатели в базу данных объектов Git изнутри поддиректории .git/refs/, и как мы уже обсуждали, ветвь HEAD — это всего лишь символическая ссылка на один из этих указателей объектов.

Вы можете в любое время создать новую ветку, просто выбрав произвольную точку в истории проекта и записав имя SHA-1 этого объекта в файл в .git/refs/heads/. Вы можете использовать любое имя файла (и, действительно, поддиректории), но принято называть «нормальную» ветку master. Однако это просто соглашение, и ничего его не навязывает.

Чтобы показать это на примере, вернёмся к репозиторию git-tutorial, который мы использовали ранее, и создадим в нём ветку. Вы делаете это, просто указав, что хотите проверить новую ветку:

$ git switch -c mybranch

создаст новую ветку, основанную на текущей HEAD позиции, и переключится на неё.

Примечание

Если вы решите начать свою новую ветку в какой-то другой точке истории, чем текущая HEAD, вы можете сделать это, просто указав git switch, чем будет являться базовая точка проверки. Другими словами, если у вас есть более ранняя метка или ветка, вы просто выполните

$ git switch -c mybranch earlier-commit

и это создаст новую ветку mybranch в более раннем коммите и проверит состояние на тот момент.

Вы всегда можете вернуться к исходной ветке master, выполнив

$ git switch master

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

$ cat .git/HEAD

скажет вам, на что она указывает. Чтобы получить список веток, вы можете сказать

$ git branch

что раньше было всего лишь простым скриптом вокруг ls .git/refs/heads. Рядом с веткой, на которой вы сейчас находитесь, будет стоять звёздочка.

Иногда вы можете захотеть создать новую ветку without фактически проверив её и переключившись на неё. В этом случае просто используйте команду

$ git branch <branchname> [startingpoint]

что просто create ветку, но не выполнит никаких дальнейших действий. Затем позже — когда вы решите, что хотите фактически разрабатывать на этой ветке — переключитесь на эту ветку с помощью обычной git switch с именем ветки в качестве аргумента.

Слияние двух веток

Одна из идей ветвления состоит в том, что вы выполняете некоторую (возможно, экспериментальную) работу в ветви, а затем объединяете её обратно в основную ветвь. Итак, предположим, что вы создали вышеприведённую mybranch ветвь, которая изначально была такой же, как исходная master ветвь, давайте убедимся, что мы находимся в этой ветви, и выполним там некоторую работу.

$ git switch mybranch
$ echo "Work, work, work" >>hello
$ git commit -m "Some work." -i hello

Здесь мы просто добавили еще одну строку в hello, и мы использовали сокращение для выполнения как git update-index hello, так и git commit, просто указав имя файла напрямую в git commit, с флагом -i (он сообщает Git, что необходимо include этот файл в дополнение к тому, что вы сделали с файлом индекса до этого момента при создании коммита). Флаг -m служит для ввода сообщения к коммиту с командной строки.

Теперь, чтобы сделать всё более интересным, предположим, что кто-то другой выполнил некоторую работу в исходной ветви, и смоделируем это, вернувшись в ветвь master и изменив тот же файл по-другому:

$ git switch master

Здесь потратьте немного времени, чтобы посмотреть содержимое hello, и обратите внимание, что оно не содержит работы, которую мы только что выполнили в mybranch — потому что эта работа вообще не произошла в ветви master. Затем выполните

$ echo "Play, play, play" >>hello
$ echo "Lots of fun" >>example
$ git commit -m "Some fun." -i hello example

поскольку ветвь master, очевидно, сейчас в лучшем настроении.

Теперь у вас есть две ветви, и вы решаете объединить выполненную работу. Прежде чем мы это сделаем, давайте представим мощный графический инструмент, который поможет вам просмотреть происходящее:

$ gitk --all

покажет вам графически обе ваши ветви (вот что означает --all: обычно он просто показывает вашу текущую HEAD) и их историю. Вы также можете увидеть, как они были получены из общего источника.

В любом случае, давайте выйдем из gitk (^Q или из меню «Файл») и решим, что хотим объединить работу, которую мы выполнили в ветви mybranch в ветвь master (которая в настоящее время также является нашей HEAD). Для этого есть удобная команда git merge, которая хочет знать, какие ветви вы хотите разрешить и о чём идёт речь при объединении:

$ git merge -m "Merge work in mybranch" mybranch

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

Теперь, в данном случае мы намеренно создали ситуацию, когда объединение потребует ручного исправления, поэтому Git выполнит как можно больше автоматически (в данном случае это просто объединение файла example, в котором не было различий в ветви mybranch), и скажет:

        Auto-merging hello
        CONFLICT (content): Merge conflict in hello
        Automatic merge failed; fix conflicts and then commit the result.

Это говорит вам, что был выполнен «Автоматическое объединение», который потерпел неудачу из-за конфликтов в hello.

Не волнуйтесь. Конфликт (тривиальный) в hello остался в той же форме, к которой вы уже привыкли, если когда-либо работали с CVS, поэтому давайте просто откроем hello в нашем редакторе (любом), и исправим его. Я бы предложил просто сделать так, чтобы hello содержало все четыре строки:

Hello World
It's a new day for git
Play, play, play
Work, work, work

и после того, как вы будете удовлетворены своим ручным объединением, просто выполните

$ git commit -i hello

что громко предупредит вас о том, что вы сейчас создаёте коммит объединения (что правильно, поэтому не волнуйтесь), и вы сможете написать небольшое сообщение об объединении о своих приключениях в git merge-земле.

После завершения, запустите gitk --all чтобы увидеть, как выглядит история графически. Обратите внимание, что mybranch всё ещё существует, и вы можете переключаться на него и продолжать работать с ним, если захотите. Ветвь mybranch не будет содержать объединение, но в следующий раз, когда вы объедините её с ветвью master, Git будет знать, как вы выполнили объединение, поэтому вам не придётся делать that объединение ещё раз.

Ещё один полезный инструмент, особенно если вы не всегда работаете в среде X-Window, — git show-branch.

$ git show-branch --topo-order --more=1 master mybranch
* [master] Merge work in mybranch
 ! [mybranch] Some work.
--
-  [master] Merge work in mybranch
*+ [mybranch] Some work.
*  [master^] Some fun.

Первые две строки указывают, что он показывает две ветви с названиями их коммитов на вершине дерева, вы находитесь в ветви master (обратите внимание на символ звёздочки *), и первый столбец для последующих строк вывода используется для отображения коммитов, содержащихся в ветви master, а второй столбец для ветви mybranch. Показаны три коммита вместе с их названиями. Все они имеют непустые символы в первом столбце (* показывает обычный коммит в текущей ветви, - — коммит объединения), что означает, что они теперь являются частью ветви master. Только коммит «Некоторое количество работ» имеет символ плюса + во втором столбце, потому что mybranch не была объединена, чтобы включить эти коммиты из ветви master. Строка в скобках перед сообщением коммита — это короткое имя, которое можно использовать для именования коммита. В приведённом примере master и mybranch являются головными ветвями. master^ является первым родителем master ветви.

Замечание
Без опции --more=1, git show-branch не выведет коммит [master^], так как коммит [mybranch] является общим предком обоих концов master и mybranch. Подробности см. в gitrevisions[7].
Замечание
Если после объединения было больше коммитов в ветви master, сам коммит объединения по умолчанию не будет показан командой git show-branch. Вам потребуется предоставить опцию --sparse чтобы показать коммит объединения в этом случае.

Теперь предположим, что вы сделали всю работу в mybranch, и плоды вашей упорной работы, наконец, были объединены с ветвью master. Вернёмся в mybranch, и выполним git merge, чтобы получить «изменения от источника» обратно в вашу ветвь.

$ git switch mybranch
$ git merge -m "Merge upstream changes." master

Это выведет что-то вроде этого (фактические имена объектов коммитов будут другими)

Updating from ae3a2da... to a80b4aa....
Fast-forward (no commit created; -m option ignored)
 example | 1 +
 hello   | 1 +
 2 files changed, 2 insertions(+)

Поскольку ваша ветвь не содержала ничего, кроме того, что уже было объединено в ветвь master, операция объединения фактически не выполнила объединение. Вместо этого она просто обновила вершину дерева вашей ветви на вершину дерева ветви master. Это часто называют fast-forward объединением.

Вы можете снова выполнить gitk --all, чтобы увидеть, как выглядит родословная коммитов, или выполнить show-branch, которая об этом сообщает.

$ git show-branch master mybranch
! [master] Merge work in mybranch
 * [mybranch] Merge work in mybranch
--
-- [master] Merge work in mybranch

Объединение внешней работы

Обычно вы гораздо чаще сливаетесь с другими, чем со своими собственными ветками, поэтому стоит отметить, что Git делает это очень легко, и, фактически, это не сильно отличается от выполнения git merge. Фактически, удалённое слияние сводится к «получению работы из удалённого репозитория в временный тег» и последующему git merge.

Получение из удалённого репозитория выполняется, как нетрудно догадаться, git fetch:

$ git fetch <remote-repository>

Для указания репозитория для скачивания можно использовать один из следующих транспортов:

SSH

remote.machine:/path/to/repo.git/ или

ssh://remote.machine/path/to/repo.git/

Этот транспорт может использоваться как для загрузки, так и для скачивания и требует от вас права доступа к ssh удалённой машины. Он определяет набор объектов, которых нет на другой стороне, обмениваясь данными о коммитах с обеих сторон и передаёт (практически) минимальный набор объектов. Это, безусловно, наиболее эффективный способ обмена объектами Git между репозиториями.

Локальный каталог

/path/to/repo.git/

Этот транспорт аналогичен SSH-транспорту, но использует sh для запуска обеих сторон на локальной машине вместо запуска другой стороны на удалённой машине с помощью ssh.

Git Native

git://remote.machine/path/to/repo.git/

Этот транспорт предназначен для анонимного скачивания. Как и SSH-транспорт, он определяет набор объектов, которых нет у стороны получателя, и передаёт (практически) минимальный набор объектов.

HTTP(S)

http://remote.machine/path/to/repo.git/

Загрузчик с http- и https-адресов сначала получает имя объекта самого верхнего коммита с удалённого сайта, посмотрев на указанное имя ссылки в каталоге repo.git/refs/, а затем пытается получить объект коммита, загрузив его с repo.git/objects/xx/xxx... с использованием имени объекта этого коммита. Затем он считывает объект коммита, чтобы определить его родительские коммиты и ассоциированный объект дерева; он повторяет этот процесс, пока не получит все необходимые объекты. Из-за этого поведения их иногда также называют commit walkers.

Эти commit walkers иногда также называют dumb transports, потому что они не требуют наличия интеллектуального сервера Git, как это делает транспорт Git Native. Любой обычный HTTP-сервер, который даже не поддерживает индексацию каталогов, вполне подойдёт. Но вы должны подготовить свой репозиторий с git update-server-info для помощи простым загрузчикам транспортов.

После получения из удалённого репозитория вы merge это со своей текущей веткой.

Однако — настолько часто бывает, что fetch и сразу же merge , что это называется git pull, и вы можете просто сделать

$ git pull <remote-repository>

и необязательно указать имя ветки для удалённой стороны как второй аргумент.

Примечание
Вы можете обойтись без использования ветвей вообще, сохраняя столько локальных репозиториев, сколько вам нужно ветвей, и выполняя слияние между ними с помощью git pull, точно так же, как вы сливаете между ветвями. Преимущество этого подхода заключается в том, что вы можете сохранять набор файлов для каждой branch и, возможно, вам будет легче переключаться туда и обратно, если вы одновременно работаете с несколькими линиями разработки. Конечно, вы заплатите за это больше места на диске для хранения нескольких рабочих деревьев, но дисковое пространство в наши дни недорого.

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

$ git config remote.linus.url https://git.kernel.org/pub/scm/git/git.git/

и использовать ключевое слово «linus» с git pull вместо полного URL.

Примеры.

  1. git pull linus

  2. git pull linus tag v0.99.1

вышеприведённые примеры эквивалентны:

  1. git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD

  2. git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.99.1

Как работает слияние?

Мы сказали, что этот учебник показывает, как утилиты помогают вам справиться с «порцеляном», который не смывается, но пока не говорили о том, как на самом деле работает слияние. Если вы проходите этот учебник впервые, я бы посоветовал перейти к разделу «Опубликование вашей работы» и вернуться сюда позже.

Хорошо, ещё со мной? Чтобы наглядно продемонстрировать пример, давайте вернёмся к предыдущему репозиторию с файлами «hello» и «example» и вернёмся к состоянию до слияния:

$ git show-branch --more=2 master mybranch
! [master] Merge work in mybranch
 * [mybranch] Merge work in mybranch
--
-- [master] Merge work in mybranch
+* [master^2] Some work.
+* [master^] Some fun.

Помните, что перед запуском git merge, наша master головка находилась на коммите «Some fun», а наша mybranch головка находилась на коммите «Some work».

$ git switch -C mybranch master^2
$ git switch master
$ git reset --hard master^

После отката структура коммитов должна выглядеть так:

$ git show-branch
* [master] Some fun.
 ! [mybranch] Some work.
--
*  [master] Some fun.
 + [mybranch] Some work.
*+ [master^] Initial commit

Теперь мы готовы провести эксперимент по ручному слиянию.

Команда git merge, при слиянии двух ветвей, использует трёхсторонний алгоритм слияния. Сначала она находит общего предка между ними. Используемая команда: git merge-base:

$ mb=$(git merge-base HEAD mybranch)

Команда записывает имя объекта коммита общего предка в стандартный вывод, поэтому мы сохранили его вывод в переменной, так как будем использовать его на следующем шаге. Кстати, общий предок в этом случае — коммит «Initial commit». Вы можете узнать об этом, выполнив:

$ git name-rev --name-only --tags $mb
my-first-tag

После того, как общий предок коммита найден, следующий шаг — это:

$ git read-tree -m -u $mb HEAD mybranch

Это та же команда git read-tree, которую мы уже видели, но она принимает три дерева, в отличие от предыдущих примеров. Она считывает содержимое каждого дерева в разные stage в файле индекса (первое дерево попадает в стадию 1, второе — в стадию 2 и т.д.). После считывания трёх деревьев в три стадии пути, которые одинаковы во всех трёх стадиях, collapsed в стадию 0. Также пути, которые одинаковы в двух из трёх стадий, сводятся в стадию 0, принимая SHA-1 из стадии 2 или 3, которая отличается от стадии 1 (т. е. только одна сторона изменилась с момента общего предка).

После collapsing операции пути, которые отличаются в трёх деревьях, остаются в ненулевых стадиях. На этом этапе вы можете проверить файл индекса с помощью этой команды:

$ git ls-files --stage
100644 7f8b141b65fdcee47321e399a2598a235a032422 0        example
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1        hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2        hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3        hello

В нашем примере с двумя файлами у нас не было неизменённых файлов, поэтому только example привело к сворачиванию. Но в реальных проектах с большим количеством файлов, когда в одном коммите изменяется небольшое количество файлов, это collapsing имеет тенденцию к тривиальному слиянию большинства путей довольно быстро, оставляя лишь небольшое количество реальных изменений в ненулевых стадиях.

Чтобы посмотреть только ненулевые стадии, используйте флаг --unmerged:

$ git ls-files --unmerged
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1        hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2        hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3        hello

Следующий этап слияния — слияние этих трёх версий файла с помощью трёхстороннего слияния. Это делается путём ввода git merge-one-file команды в качестве одного из аргументов команды git merge-index:

$ git merge-index git-merge-one-file hello
Auto-merging hello
ERROR: Merge conflict in hello
fatal: merge program failed

Скрипт git merge-one-file вызывается с параметрами для описания этих трёх версий и отвечает за оставление результатов слияния в рабочей директории. Это довольно простой shell-скрипт, и в конечном итоге он вызывает программу merge из пакета RCS для выполнения трёхстороннего слияния на уровне файлов. В этом случае merge обнаруживает конфликты, а результат слияния с отметками конфликтов оставляется в рабочей директории. Это можно увидеть, если вы выполните ls-files --stage ещё раз на этом этапе:

$ git ls-files --stage
100644 7f8b141b65fdcee47321e399a2598a235a032422 0        example
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1        hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2        hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3        hello

Это состояние файла индекса и файла рабочей области после git merge возвращает управление вам, оставляя вам разрешение конфликта слияния. Обратите внимание, что путь hello всё ещё не слился, и то, что вы видите с помощью git diff на этом этапе, представляет собой отличия со стадии 2 (т. е. вашей версии).

Опубликование вашей работы

Итак, мы можем использовать чужую работу из удалённого репозитория, но как подготовить репозиторий, чтобы другие люди могли из него брать изменения?

Вы выполняете свою работу в рабочей ветви, в которой ваш основной репозиторий находится в виде подкаталога .git. Вы можете сделать этот репозиторий доступным удалённо и попросить людей из него брать изменения, но на практике так обычно не делается. Рекомендуемый способ — иметь публичный репозиторий, сделать его доступным для других людей и, когда изменения, которые вы внесли в свою основную рабочую ветвь, будут в надлежащем состоянии, обновить публичный репозиторий из неё. Это часто называется pushing.

Примечание
Этот публичный репозиторий может быть дополнительно дублирован, и именно так управляются репозитории Git на kernel.org.

Опубликование изменений из вашего локального (частного) репозитория в удалённый (публичный) репозиторий требует права записи на удалённой машине. Вам необходимо иметь там учётную запись SSH для выполнения одной команды: git-receive-pack.

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

Примечание
git push использует пару команд: git send-pack на вашей локальной машине и git-receive-pack на удалённой машине. Взаимодействие между ними по сети внутри использует соединение SSH.

Директория Git вашего частного репозитория обычно .git, но ваш публичный репозиторий часто называется по имени проекта, например, <project>.git. Давайте создадим такой публичный репозиторий для проекта my-git. После входа в систему на удалённой машине создайте пустую директорию:

$ mkdir my-git.git

Затем преобразуйте эту директорию в репозиторий Git, выполнив команду git init, но на этот раз, поскольку её имя не является стандартным .git, мы делаем это немного по-другому:

$ GIT_DIR=my-git.git git init

Убедитесь, что эта директория доступна для других пользователей, чтобы они могли брать ваши изменения с помощью выбранного вами способа передачи. Также вам нужно убедиться, что у вас есть программа git-receive-pack на $PATH.

Примечание
Многие установки sshd не запускают вашу оболочку в качестве оболочки входа, когда вы непосредственно запускаете программы; это означает, что если ваша оболочка входа — bash, то только .bashrc будет прочитана, а не .bash_profile. В качестве обходного решения убедитесь, что .bashrc настраивает $PATH, чтобы вы могли запустить программу git-receive-pack.
Примечание
Если вы планируете опубликовать этот репозиторий для доступа через http, вы должны сделать mv my-git.git/hooks/post-update.sample my-git.git/hooks/post-update на данном этапе. Это гарантирует, что каждый раз, когда вы отправляете изменения в этот репозиторий, git update-server-info будет выполняться.

Ваш «публичный репозиторий» теперь готов принимать ваши изменения. Вернитесь к машине, на которой у вас находится частный репозиторий. Оттуда выполните эту команду:

$ git push <public-host>:/path/to/my-git.git master

Это синхронизирует ваш публичный репозиторий, чтобы он соответствовал имени ветви (например, master в данном случае) и объектам, доступным из неё в вашем текущем репозитории.

В качестве реального примера, вот как я обновляю свой публичный репозиторий Git. Сеть зеркал Kernel.org позаботится о распространении на другие публично доступные машины:

$ git push master.kernel.org:/pub/scm/git/git.git/

Упаковка вашего репозитория

Ранее мы видели, что для каждого созданного вами Git-объекта хранится один файл в каталоге .git/objects/??/. Такое представление эффективно для создания атомарно и безопасно, но не очень удобно для передачи по сети. Поскольку Git-объекты неизменяемы после создания, есть способ оптимизировать хранение, «упаковав» их вместе. Команда

$ git repack

сделает это за вас. Если вы следовали примерам из руководства, к настоящему моменту вы накопили около 17 объектов в каталогах .git/objects/??/. git repack показывает, сколько объектов было упаковано, и сохраняет упакованный файл в каталоге .git/objects/pack.

Примечание
Вы увидите два файла, pack-*.pack и pack-*.idx, в каталоге .git/objects/pack. Они тесно связаны друг с другом, и если вы когда-либо скопируете их вручную в другой репозиторий по какой-либо причине, вы должны убедиться, что копируете их вместе. Первый содержит все данные из объектов в пакете, а второй — индекс для произвольного доступа.

Если вы параноик, команда git verify-pack обнаружит, повреждён ли пакет, но не беспокойтесь слишком сильно. Наши программы всегда идеальны ;-).

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

$ git prune-packed

удалит их для вас.

Вы можете попробовать запустить команду find .git/objects -type f до и после запуска git prune-packed, если вам интересно. Также git count-objects покажет, сколько разархивированных объектов находится в вашем репозитории и сколько места они занимают.

Примечание
git pull несколько неудобно для HTTP-передачи, поскольку упакованный репозиторий может содержать относительно небольшое количество объектов в относительно большом пакете. Если вы ожидаете много HTTP-загрузок из вашего публичного репозитория, вам может потребоваться часто переупаковывать и удалять ненужные файлы или вообще не делать этого.

Если вы снова запустите git repack, она скажет «Ничего нового для упаковки». После продолжения разработки и накопления изменений запуск git repack снова создаст новый пакет, содержащий объекты, созданные с момента последней упаковки вашего репозитория. Мы рекомендуем упаковать проект вскоре после первоначального импорта (если вы не начинаете проект с нуля), а затем периодически запускать git repack в зависимости от активности вашего проекта.

Когда репозиторий синхронизируется с помощью git push и git pull объекты, упакованные в исходном репозитории, обычно хранятся разархивированными в конечном.

Работа с другими пользователями

Хотя Git — это действительно распределённая система, часто удобно организовывать свой проект с неформальной иерархией разработчиков. Разработка ядра Linux происходит именно так. В презентации Рэнди Данлапа есть хорошее иллюстративное описание (страница 17, «Слияния в основную ветку») в презентации Рэнди Данлапа.

Следует подчеркнуть, что эта иерархия является чисто неформальной. В Git нет ничего фундаментального, что навязывает «цепь потока исправлений», подразумеваемую этой иерархией. Вам не нужно брать изменения только из одного удалённого репозитория.

Рекомендуемый рабочий процесс для «руководителя проекта» выглядит следующим образом:

  1. Подготовьте свой основной репозиторий на локальном компьютере. Ваша работа ведётся там.

  2. Подготовьте публичный репозиторий, доступный другим.

    Если другие люди получают изменения из вашего репозитория через простые протоколы передачи (HTTP), вам необходимо поддерживать этот репозиторий dumb transport friendly. После git init, $GIT_DIR/hooks/post-update.sample из стандартных шаблонов будет содержать вызов git update-server-info, но вам нужно вручную включить хук с помощью mv post-update.sample post-update. Это гарантирует, что git update-server-info сохранит необходимые файлы в актуальном состоянии.

  3. Опубликуйте в публичном репозитории из вашего основного репозитория.

  4. git repack публичный репозиторий. Это создаёт большой пакет, содержащий начальный набор объектов в качестве базовой точки отсчёта, и, возможно, git prune если используемый протокол для получения изменений из вашего репозитория поддерживает упакованные репозитории.

  5. Продолжайте работать в своём основном репозитории. Ваши изменения включают ваши собственные модификации, исправления, полученные по электронной почте, и слияния, полученные при получении изменений из публичных репозиториев ваших «подсистеменных администраторов».

    Вы можете переупаковывать этот частный репозиторий, когда захотите.

  6. Опубликуйте ваши изменения в публичном репозитории и сообщите об этом публично.

  7. Время от времени git repack публичный репозиторий. Возвращайтесь к шагу 5 и продолжайте работу.

Рекомендуемый рабочий цикл для «подсистемного администратора», работающего над этим проектом и имеющего собственный «публичный репозиторий», выглядит следующим образом:

  1. Подготовьте свой рабочий репозиторий, выполнив git clone в публичном репозитории «руководителя проекта». URL, используемый для первоначального клонирования, хранится в переменной конфигурации remote.origin.url.

  2. Подготовьте публичный репозиторий, доступный другим, так же, как и «руководитель проекта».

  3. Скопируйте упакованные файлы из публичного репозитория «руководителя проекта» в свой публичный репозиторий, если репозиторий «руководителя проекта» не находится на том же компьютере, что и ваш. В последнем случае вы можете использовать файл objects/info/alternates для указания репозитория, из которого вы берёте изменения.

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

  5. Продолжайте работать в своём основном репозитории. Ваши изменения включают ваши собственные модификации, исправления, полученные по электронной почте, и слияния, полученные при получении изменений из публичных репозиториев «руководителя проекта» и, возможно, ваших «под-подсистеменных администраторов».

    Вы можете переупаковывать этот частный репозиторий, когда захотите.

  6. Опубликуйте ваши изменения в свой публичный репозиторий и попросите «руководителя проекта» и, возможно, ваших «под-подсистеменных администраторов» получить эти изменения.

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

Рекомендуемый рабочий цикл для «индивидуального разработчика», у которого нет «публичного» репозитория, немного отличается. Он выглядит следующим образом:

  1. Подготовьте свой рабочий репозиторий, git clone публичный репозиторий «руководителя проекта» (или «подсистеменного администратора», если вы работаете над подсистемой). URL, используемый для первоначального клонирования, хранится в переменной конфигурации remote.origin.url.

  2. Выполняйте свою работу в своём репозитории на master ветке.

  3. Время от времени выполните git fetch origin из публичного репозитория вашего источника. Это выполняет только первую половину git pull, но не выполняет слияние. Голова публичного репозитория хранится в .git/refs/remotes/origin/master.

  4. Используйте git cherry origin чтобы увидеть, какие из ваших исправлений были приняты, и/или используйте git rebase origin чтобы перенести неслитые изменения в обновлённый исходный код.

  5. Используйте git format-patch origin чтобы подготовить исправления для отправки по электронной почте вашему источнику и отправьте их. Вернитесь к шагу 2 и продолжайте работу.

Работа с другими, стиль общих репозиториев

Если вы приходите из среды CVS, стиль сотрудничества, предложенный в предыдущем разделе, может быть новым для вас. Вам не нужно беспокоиться. Git также поддерживает стиль сотрудничества с «общим публичным репозиторием», с которым вы, вероятно, более знакомы.

Подробности см. в gitcvs-migration[7].

Объединение вашей работы

Вероятно, вы будете работать над несколькими задачами одновременно. Управлять этими более или менее независимыми задачами с помощью Git легко.

Мы уже видели, как работают ветки ранее, с примером «развлечения и работа» с использованием двух веток. Идея та же, если веток больше двух. Предположим, вы начали с головы «master» и имеете некоторый новый код в ветке «master» и два независимых исправления в ветках «commit-fix» и «diff-fix»:

$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Release candidate #1
---
 +  [diff-fix] Fix rename detection.
 +  [diff-fix~1] Better common substring algorithm.
+   [commit-fix] Fix commit message normalization.
  * [master] Release candidate #1
++* [diff-fix~2] Pretty-print messages.

Оба исправления хорошо протестированы, и на этом этапе вы хотите слить их оба. Вы можете сначала слить diff-fix, а затем commit-fix следующим образом:

$ git merge -m "Merge fix in diff-fix" diff-fix
$ git merge -m "Merge fix in commit-fix" commit-fix

Что привело бы к:

$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Merge fix in commit-fix
---
  - [master] Merge fix in commit-fix
+ * [commit-fix] Fix commit message normalization.
  - [master~1] Merge fix in diff-fix
 +* [diff-fix] Fix rename detection.
 +* [diff-fix~1] Better common substring algorithm.
  * [master~2] Release candidate #1
++* [master~3] Pretty-print messages.

Однако нет особой причины сливать ветки по одной, когда у вас есть набор действительно независимых изменений (если порядок имел значение, то они не независимы по определению). Вместо этого вы можете сразу слить эти две ветки в текущую ветку. Сначала вернёмся к тому, что мы только что сделали, и начнём заново. Нам нужно получить ветку master до этих двух слияний, сбросив её до master~2:

$ git reset --hard master~2

Вы можете убедиться, что git show-branch соответствует состоянию до этих двух git merge что вы только что сделали. Затем, вместо того, чтобы запускать две команды git merge подряд, вы объедините эти две головы веток (это известно как making an Octopus):

$ git merge commit-fix diff-fix
$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
---
  - [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
+ * [commit-fix] Fix commit message normalization.
 +* [diff-fix] Fix rename detection.
 +* [diff-fix~1] Better common substring algorithm.
  * [master~1] Release candidate #1
++* [master~2] Pretty-print messages.

Обратите внимание, что вам не следует использовать Octopus просто потому, что это возможно. Octopus — это допустимая операция и часто упрощает просмотр истории коммитов, если вы объединяете более двух независимых изменений одновременно. Однако, если у вас есть конфликты при слиянии любой из веток, и вам нужно их вручную разрешить, это указывает на то, что разработка в этих ветках не была независимой всё-таки, и вы должны слить две по одной, задокументировав, как вы разрешили конфликты, и почему вы предпочитали изменения с одной стороны по сравнению с другой. В противном случае это затруднит, а не упростит, отслеживание истории проекта.

См. также

gittutorial[7], gittutorial-2[7], gitcvs-migration[7], git-help[1], giteveryday[7], Руководство пользователя Git

gitcore-tutorial

© 2005–2026 Linus Torvalds and others
Licensed under the GNU General Public License version 2.
https://git-scm.com/docs/gitcore-tutorial

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API