Русский ▾ Topics ▾ Latest version ▾ git-read-tree last updated in 2.52.0

НАЗВАНИЕ

git-read-tree — Читает информацию о деревьях в индекс

ОБЗОР

git read-tree [(-m [--trivial] [--aggressive] | --reset | --prefix=<префикс>)
		[-u | -i]] [--index-output=<файл>] [--no-sparse-checkout]
		(--empty | <указатель-дерева-1> [<указатель-дерева-2> [<указатель-дерева-3>])

ОПИСАНИЕ

Считывает информацию о дереве, заданную <указатель-дерева>, в индекс, но фактически не обновляет никакие файлы, которые он «кэширует». (см.: git-checkout-index[1])

При необходимости с флагом -m он может выполнить слияние дерева с индексом, перемотку вперёд (т.е. двухстороннее слияние) или трёхстороннее слияние. При использовании с -m флаг -u заставляет его также обновлять файлы в рабочем каталоге результатом слияния.

Только тривиальные слияния выполняются самим git read-tree. При возврате git read-tree только конфликтующие пути будут находиться в неслитом (unmerged) состоянии.

ПАРАМЕТРЫ

-m

Выполнить слияние, а не просто чтение. Команда откажется выполняться, если в вашем файле индекса есть неслитые (unmerged) записи, указывающие на то, что вы не завершили ранее начатое слияние.

--reset

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

-u

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

-i

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

-n
--dry-run

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

-v

Показать ход переключения (checkout) файлов.

--trivial

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

--aggressive

Обычно трёхстороннее слияние git read-tree разрешает слияние для действительно тривиальных случаев, а остальные случаи оставляет неразрешёнными в индексе, чтобы высокоуровневые программы (porcelains) могли реализовывать различные стратегии слияния. Этот флаг заставляет команду разрешать ещё несколько случаев внутри:

  • когда одна сторона удаляет путь, а другая сторона оставляет путь неизменным. Разрешение состоит в удалении этого пути.

  • когда обе стороны удаляют путь. Разрешение состоит в удалении этого пути.

  • когда обе стороны добавляют путь идентично. Разрешение состоит в добавлении этого пути.

--prefix=<prefix>

Сохранить текущее содержимое индекса и прочитать содержимое указанного указателя-дерева в каталоге <префикс>. Команда откажется перезаписывать записи, которые уже существовали в исходном файле индекса.

--index-output=<file>

Вместо записи результатов в $GIT_INDEX_FILE записать результирующий индекс в указанный файл. Во время работы команды исходный файл индекса блокируется обычным механизмом. Файл должен допускать переименование (rename(2)) из временного файла, создаваемого рядом с обычным файлом индекса; обычно это означает, что он должен находиться в той же файловой системе, что и сам файл индекса, и у вас должны быть права на запись в каталоги, где расположены файл индекса и выходной файл индекса.

--recurse-submodules
--no-recurse-submodules

Использование --recurse-submodules обновит содержимое всех активных подмодулей в соответствии с коммитом, записанным в суперпроекте, путём рекурсивного вызова read-tree, а также установит HEAD подмодулей в отсоединённое состояние на этом коммите.

--no-sparse-checkout

Отключить поддержку частичного переключения (sparse checkout), даже если core.sparseCheckout имеет значение true.

--empty

Вместо чтения объектов-деревьев в индекс просто очистить его.

-q
--quiet

Тихий режим, подавлять информационные сообщения.

<указатель-дерева-№>

Идентификатор(ы) объекта(ов)-дерева для чтения/слияния.

СЛИЯНИЕ

Если указан -m, git read-tree может выполнить 3 вида слияния: слияние с одним деревом, если задано только 1 дерево, слияние с перемоткой вперёд с 2 деревьями или трёхстороннее слияние, если предоставлено 3 или более деревьев.

Слияние с одним деревом

Если указано только 1 дерево, git read-tree работает так, как если бы пользователь не указывал -m, за исключением того, что если в исходном индексе есть запись для данного пути и содержимое пути совпадает с читаемым деревом, используется информация stat из индекса. (Другими словами, данные stat() индекса имеют приоритет над данными объединённого дерева).

Это означает, что если вы выполните git read-tree -m <новоедерево>, а затем git checkout-index -f -u -a, то git checkout-index переключит только то, что действительно изменилось.

Это используется для предотвращения ненужных ложных срабатываний при запуске git diff-files после git read-tree.

Слияние с двумя деревьями

Обычно это вызывается как git read-tree -m $H $M, где $H — головной коммит текущего репозитория, а $M — голова внешнего дерева, которая просто опережает $H (т.е. мы находимся в ситуации перемотки вперёд).

Когда указаны два дерева, пользователь сообщает git read-tree следующее:

  1. Текущий индекс и рабочий каталог получены из $H, но с тех пор пользователь мог внести в них локальные изменения.

  2. Пользователь хочет выполнить перемотку вперёд до $M.

В этом случае команда git read-tree -m $H $M гарантирует, что ни одно локальное изменение не будет потеряно в результате этого «слияния». Вот правила «переноса вперёд», где «I» обозначает индекс, «clean» означает, что индекс и рабочий каталог совпадают, а «exists»/«nothing» относятся к наличию пути в указанном коммите:

	I                   H        M        Результат
       -------------------------------------------------------
     0  ничего             ничего  ничего  (не происходит)
     1  ничего             ничего  существует   использовать M
     2  ничего             существует   ничего  удалить путь из индекса
     3  ничего             существует   существует,  использовать M для "начального переключения",
				     H == M   в противном случае сохранить индекс
				     существует,  сбой
				     H != M

        clean I==H  I==M
       ------------------
     4  да    N/A   N/A     ничего  ничего  сохранить индекс
     5  нет   N/A   N/A     ничего  ничего  сохранить индекс

     6  да    N/A   да      ничего  существует   сохранить индекс
     7  нет   N/A   да      ничего  существует   сохранить индекс
     8  да    N/A   нет     ничего  существует   сбой
     9  нет   N/A   нет     ничего  существует   сбой

     10 да    да    N/A     существует   ничего  удалить путь из индекса
     11 нет   да    N/A     существует   ничего  сбой
     12 да    нет   N/A     существует   ничего  сбой
     13 нет   нет   N/A     существует   ничего  сбой

	clean (H==M)
       ------
     14 да                  существует   существует   сохранить индекс
     15 нет                 существует   существует   сохранить индекс

        clean I==H  I==M (H!=M)
       ------------------
     16 да    нет   нет     существует   существует   сбой
     17 нет   нет   нет     существует   существует   сбой
     18 да    нет   да      существует   существует   сохранить индекс
     19 нет   нет   да      существует   существует   сохранить индекс
     20 да    да    нет     существует   существует   использовать M
     21 нет   да    нет     существует   существует   сбой

Во всех случаях «сохранить индекс» запись индекса остаётся такой же, как в исходном файле индекса. Если запись не актуальна, git read-tree сохраняет копию в рабочем каталоге нетронутой при работе с флагом -u.

Когда эта форма git read-tree успешно возвращается, вы можете увидеть, какие из сделанных вами «локальных изменений» были перенесены вперёд, выполнив git diff-index --cached $M. Обратите внимание, что это не обязательно совпадает с тем, что git diff-index --cached $H показал бы до такого слияния двух деревьев. Это из-за случаев 18 и 19 — если у вас уже были изменения в $M (например, возможно, вы получили их по электронной почте в виде изменения), git diff-index --cached $H сообщил бы вам об изменении до этого слияния, но оно не отобразилось бы в выводе git diff-index --cached $M после слияния двух деревьев.

Случай 3 немного сложен и требует пояснения. Логически результатом этого правила должно быть удаление пути, если пользователь проиндексировал удаление пути, а затем переключился на новую ветку. Однако это предотвратит начальное переключение, поэтому правило изменено: использовать M (новое дерево) только тогда, когда содержимое индекса пусто. В противном случае удаление пути сохраняется до тех пор, пока $H и $M совпадают.

Трёхходовое слияние

Каждая запись «индекса» имеет два бита состояния «этапа» (stage). Этап 0 — это обычный, и это единственный, который вы увидите при любом обычном использовании.

Однако, когда вы выполняете git read-tree с тремя деревьями, «этап» начинается с 1.

Это означает, что вы можете выполнить

$ git read-tree -m <дерево1> <дерево2> <дерево3>

и в итоге вы получите индекс со всеми записями <дерево1> на «этапе1», всеми записями <дерево2> на «этапе2» и всеми записями <дерево3> на «этапе3». При выполнении слияния другой ветки в текущую ветку мы используем дерево общего предка как <дерево1>, голову текущей ветки как <дерево2> и голову другой ветки как <дерево3>.

Кроме того, git read-tree имеет специальную логику: если вы видите файл, полностью совпадающий в следующих состояниях, он «схлопывается» обратно в «этап0»:

  • этапы 2 и 3 одинаковы; взять один или другой (не имеет значения — одинаковая работа проделана в нашей ветке на этапе 2 и в их ветке на этапе 3)

  • этап 1 и этап 2 одинаковы, а этап 3 отличается; взять этап 3 (наша ветка на этапе 2 ничего не делала с предком на этапе 1, в то время как их ветка на этапе 3 работала над ним)

  • этап 1 и этап 3 одинаковы, а этап 2 отличается; взять этап 2 (мы что-то сделали, пока они ничего не делали)

Команда git write-tree отказывается записывать бессмысленное дерево и будет жаловаться на неслитые (unmerged) записи, если увидит хотя бы одну запись не на этапе 0.

Итак, всё это звучит как набор совершенно бессмысленных правил, но на самом деле это именно то, что нужно для быстрого слияния. Различные этапы представляют «результирующее дерево» (этап 0, также известный как «слитое»), исходное дерево (этап 1, также известный как «orig») и два дерева, которые вы пытаетесь слить (этапы 2 и 3 соответственно).

Порядок этапов 1, 2 и 3 (а следовательно, и порядок трёх аргументов командной строки <указатель-дерева>) имеет значение, когда вы начинаете трёхстороннее слияние с уже заполненным файлом индекса. Вот краткое описание того, как работает алгоритм:

  • если файл существует в идентичном формате во всех трёх деревьях, он автоматически схлопнется в состояние «слито» (merged) с помощью git read-tree.

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

  • файл индекса сохраняет и восстанавливает всю эту информацию, поэтому вы можете выполнять слияние поэтапно, но пока в нём есть записи на этапах 1/2/3 (т.е. «неслитые записи»), вы не можете записать результат. Итак, теперь алгоритм слияния становится действительно простым:

    • вы проходите по индексу по порядку и игнорируете все записи этапа 0, поскольку они уже готовы.

    • если вы находите «этап1», но нет соответствующего «этапа2» или «этапа3», вы знаете, что он был удалён из обоих деревьев (он существовал только в исходном дереве), и вы удаляете эту запись.

    • если вы находите соответствующие деревья на «этапе2» и «этапе3», вы удаляете одно из них и превращаете другое в запись «этапа0». Также удалите любую соответствующую запись «этапа1», если она существует. .. все обычные тривиальные правила ..

Обычно вы используете git merge-index с предоставленным git merge-one-file для выполнения этого последнего шага. Сценарий обновляет файлы в рабочем каталоге по мере слияния каждого пути и после успешного завершения слияния.

Когда вы начинаете трёхстороннее слияние с уже заполненным файлом индекса, предполагается, что он представляет состояние файлов в вашем рабочем каталоге, и у вас даже могут быть файлы с изменениями, не зарегистрированными в файле индекса. Далее предполагается, что это состояние «получено» из дерева этапа 2. Трёхстороннее слияние отказывается выполняться, если находит в исходном файле индекса запись, которая не соответствует этапу 2.

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

$ JC=`git rev-parse --verify "HEAD^0"`
$ git checkout-index -f -u -a $JC

Вы делаете случайные правки, не запуская git update-index. А затем вы замечаете, что верхушка вашего «вышестоящего» (upstream) дерева продвинулась с тех пор, как вы извлекли (pull) от него:

$ git fetch git://.... linus
$ LT=`git rev-parse FETCH_HEAD`

Ваш рабочий каталог по-прежнему основан на вашем HEAD ($JC), но с тех пор у вас есть некоторые правки. Трёхстороннее слияние гарантирует, что вы не добавляли и не изменяли записи индекса с момента $JC, и если нет, то делает правильные вещи. Итак, со следующей последовательностью:

$ git read-tree -m -u `git merge-base $JC $LT` $JC $LT
$ git merge-index git-merge-one-file -a
$ echo "Слияние с Linus" | \
  git commit-tree `git write-tree` -p $JC -p $LT

то, что вы закоммитите, будет чистым слиянием между $JC и $LT без ваших текущих изменений, и ваш рабочий каталог будет обновлён до результата слияния.

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

Другими словами, нет необходимости беспокоиться о том, что существует только в рабочем каталоге. Если у вас есть локальные изменения в части проекта, не затронутой слиянием, ваши изменения не мешают слиянию и сохраняются нетронутыми. Когда они действительно мешают, слияние даже не начинается (git read-tree громко жалуется и завершается сбоем, ничего не изменяя). В таком случае вы можете просто продолжить то, что делали, и когда ваш рабочий каталог будет готов (т.е. вы закончите свою текущую работу), повторите попытку слияния.

ЧАСТИЧНОЕ ПЕРЕКЛЮЧЕНИЕ

Примечание: Возможности skip-worktree в git-update-index[1] и read-tree существовали до появления git-sparse-checkout[1]. Пользователям рекомендуется использовать команду sparse-checkout вместо этих внутренних команд (plumbing) для нужд, связанных с частичным переключением/skip-worktree. Однако приведённая ниже информация может быть полезна пользователям, пытающимся понять стиль шаблонов, используемый в режиме non-cone команды sparse-checkout.

«Частичное переключение» (sparse checkout) позволяет заполнять рабочий каталог частично. Он использует бит skip-worktree (см. git-update-index[1]), чтобы сообщить Git, стоит ли обращать внимание на файл в рабочем каталоге.

git read-tree и другие команды на основе слияния (git merge, git checkout…​) могут помочь в поддержании битовой карты skip-worktree и обновлении рабочего каталога. $GIT_DIR/info/sparse-checkout используется для определения эталонной битовой карты skip-worktree. Когда git read-tree нужно обновить рабочий каталог, он сбрасывает бит skip-worktree в индексе на основе этого файла, который использует тот же синтаксис, что и файлы .gitignore. Если запись соответствует шаблону в этом файле или запись соответствует файлу, присутствующему в рабочем каталоге, то skip-worktree не будет установлен для этой записи. В противном случае будет установлен skip-worktree.

Затем он сравнивает новое значение skip-worktree с предыдущим. Если skip-worktree меняется с установленного на снятый, он добавит соответствующий файл обратно. Если он меняется со снятого на установленный, этот файл будет удалён.

Хотя $GIT_DIR/info/sparse-checkout обычно используется для указания того, какие файлы включать, вы также можете указать, какие файлы не включать, используя отрицательные шаблоны. Например, чтобы удалить файл unwanted:

/*
!unwanted

Ещё один хитрый момент — полное повторное заполнение рабочего каталога, когда вы больше не хотите использовать частичное переключение. Вы не можете просто отключить «частичное переключение», потому что биты skip-worktree всё ещё находятся в индексе, и ваш рабочий каталог по-прежнему заполнен частично. Вы должны повторно заполнить рабочий каталог содержимым файла $GIT_DIR/info/sparse-checkout следующим образом:

/*

Затем вы можете отключить частичное переключение. Поддержка частичного переключения в git read-tree и подобных командах по умолчанию отключена. Вам нужно включить core.sparseCheckout, чтобы получить поддержку частичного переключения.

GIT

Является частью пакета git[1]