Как обновить git shallow клон?

Фон

(для tl;dr см. # вопросы ниже)

У меня есть несколько git-репозиторий мелких клонов. Я использую неглубокие клоны, потому что они намного меньше по сравнению с грязными клонами. Каждый клонируется git clone --single-branch --depth 1 <git-repo-url> <dir-name>,

Это отлично работает, за исключением того, что я не вижу, как его обновить.

Когда я клонирую по тегу, обновление не имеет смысла, так как тег является замороженным моментом времени (насколько я понимаю). В этом случае, если я хочу обновить, это означает, что я хочу клонировать другим тегом, поэтому я просто rm -rf <dir-name> и снова клонировать.

Ситуация усложняется, когда я клонирую HEAD главной ветви, а затем хочу обновить ее.

Я старался git pull --depth 1 но хотя я и не собираюсь ничего выдвигать в удаленный репозиторий, он жалуется, что не знает, кто я.

Я старался git fetch --depth 1, но, кажется, что-то обновляет, я проверил, что это не актуально (некоторые файлы в удаленном репозитории имеют содержимое, отличное от того, что есть в моем клоне).

После /questions/9170357/kak-obnovit-neglubokij-klonirovannyij-submodul-bez-uvelicheniya-razmera-osnovnogo-repo/9170363#9170363 я попробовал git fetch --depth 1; git reset --hard origin/master, но две вещи: во-первых, я не понимаю, почему git reset во-вторых, хотя файлы, кажется, обновлены, некоторые старые файлы остаются, и git clean -df не удаляет эти файлы.

Вопросы

Пусть клон создан с git clone --single-branch --depth 1 <git-repo-url> <dir-name>, Как обновить его, чтобы достичь того же результата, что и rm -rf <dir-name>; git clone --single-branch --depth 1 <git-repo-url> <dir-name>? Или rm -rf <dir-name> и снова клонировать единственный путь?

Заметка

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

2 ответа

Решение

[немного переписано и отформатировано] Учитывая клон, созданный с git clone --single-branch --depth 1 url directory Как я могу обновить его, чтобы достичь того же результата, что и rm -rf directory; git clone --single-branch --depth 1 url directory?

Обратите внимание, что --single-branch по умолчанию при использовании --depth 1, (Одна) ветка - это та, с которой вы даете -b, Здесь есть много вопросов об использовании -b с тегами, но я оставлю это на потом. Если вы не используете -b, ваш Git спрашивает Git "вверх по течению" - Git at url - какую ветку он извлек, и делает вид, что вы использовали -b thatbranch, Это означает, что при использовании важно соблюдать осторожность. --single-branch без -b чтобы убедиться, что текущая ветка этого репозитория имеет смысл, и, конечно, когда вы используете -b, чтобы удостовериться, что аргумент ответвления, который вы даете, действительно дает имя ветви, а не тегу.

Простой ответ в основном такой, с двумя небольшими изменениями:

После /questions/9170357/kak-obnovit-neglubokij-klonirovannyij-submodul-bez-uvelicheniya-razmera-osnovnogo-repo/9170363#9170363 я попробовал git fetch --depth 1; git reset --hard origin/master, но две вещи: во-первых, я не понимаю, почему git reset во-вторых, хотя файлы, кажется, обновлены, некоторые старые файлы остаются, и git clean -df не удаляет эти файлы.

Два небольших изменения: убедитесь, что вы используете origin/branchname вместо этого и добавить -x (git clean -d -f -x или же git clean -dfx) к git clean шаг. Что касается того, почему это становится немного сложнее.

В чем дело

Без --depth 1, git fetch step вызывает другой Git и получает от него список имен веток и соответствующих хеш-идентификаторов коммитов. Таким образом, он находит список всех ветвей upstream и их текущих коммитов. Тогда, потому что у вас есть --single-branch репозиторий, ваш Git выбрасывает все, кроме одной ветви, и переносит все, что нужно Git для подключения текущего коммита, к коммиту (ам), которые у вас уже есть в вашем репозитории.

С --depth 1 ваш Git вообще не связывает новый коммит со старыми историческими коммитами. Вместо этого он получает только один коммит и другие объекты Git, необходимые для завершения этого коммита. Затем он записывает дополнительную запись "мелкий трансплантат", чтобы пометить этот коммит как новый псевдокорневый коммит.

Обычный (неглубокий) клон и выборка

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

История хранилища формируется из коммитов. Каждый коммит называет свой родительский коммит (или для слияний, родительских коммитов, множественного числа) в цепочке, которая переходит от "последнего коммита" к предыдущему, к некоторому более наследственному коммиту и так далее. Цепочка в конце концов останавливается, когда она достигает коммита, у которого нет родителя, такого как первый коммит, когда-либо сделанный в репозитории. Этот вид коммита является корневым коммитом.

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

o <- o <- o <- o   <-- master

Имя master указывает на четвертый и последний коммит, который указывает на третий, который указывает на второй, который указывает на первый.

Каждый коммит содержит полный снимок всех файлов, которые входят в этот коммит. Файлы, которые вообще не изменяются, распределяются между этими коммитами: четвертый коммит просто "заимствует" неизмененную версию у третьего коммита, который "заимствует" ее у второго, и так далее. Следовательно, каждый коммит называет все "объекты Git", в которых он нуждается, и Git либо находит эти объекты локально - потому что он уже имеет их - либо использует fetch протокол, чтобы перенести их из другого, восходящего Git. Существует формат сжатия, называемый "упаковкой", и специальный вариант для передачи по сети, называемый "тонкими пакетами", который позволяет Git делать это еще лучше / интереснее, но принцип прост: Git нужны все и только те объекты, которые идут с новыми коммитами он набирает обороты. Ваш Git решает, есть ли у него эти объекты, и если нет, получает их из своего Git.

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

        o--o   <-- feature/tall
       /
o--o--o---o    <-- master
    \    /
     o--o      <-- bug/short

Здесь ветка bug/short сливается обратно в master В то время как филиал feature/tall находится в стадии разработки. Имя bug/short может (вероятно) теперь быть полностью удалено: оно нам больше не нужно, если мы сделали коммиты на него. Совершить на кончике master имена двух предыдущих коммитов, включая коммит на кончике bug/short, так что выбирая master мы заберем bug/short совершает.

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

 o--o
     \
o--o--o   <-- master

или этот:

 o--o     <-- orphan

o--o      <-- master

На самом деле, только один master было, вероятно, сделано путем слияния orphan в master затем удалив имя orphan,

Прививки и замены

У Git в течение долгого времени была (возможно, шаткая) поддержка для трансплантатов, которая была заменена (намного лучше, фактически надежной) поддержкой общих замен. Чтобы понять их конкретно, нам нужно добавить к вышесказанному, что каждый коммит имеет свой уникальный идентификатор. Эти идентификаторы - большие уродливые 40-символьные хэши SHA-1, face0ff... и так далее. Фактически, каждый объект Git имеет уникальный идентификатор, хотя для целей графа все, что нам нужно, - это коммиты.

Для рисования графиков эти большие хеш-идентификаторы слишком болезненны для использования, поэтому мы можем использовать однобуквенные имена A через Z вместо. Давайте снова воспользуемся этим графиком, но введем однобуквенные имена:

        E--H   <-- feature/tall
       /
A--B--D---G    <-- master
    \    /
     C--F      <-- bug/short

совершить H ссылается на фиксацию E (E является H родитель). совершить G, который является коммитом слияния - имея в виду, что у него есть по крайней мере два родителя - относится к обоим D а также F, и так далее.

Обратите внимание, что имена филиалов, feature/tall, master, а также bug/short каждый указывает на один коммит. Имя bug/short указывает на фиксацию F, Вот почему совершают F находится на ветке bug/short... но так же совершить C, совершить C находится на bug/short потому что это достижимо от имени. Имя заставляет нас F, а также F заставляет нас C, так C находится на ветке bug/short,

Обратите внимание, однако, что совершить G кончик master заставляет нас совершать F, Это означает, что совершать F также на ветке master, Это ключевая концепция в Git: коммиты могут быть на одной, многих или даже не ветвях. Имя ветки - это просто способ начать работу в графе коммитов. Есть и другие способы, такие как имена тегов, refs/stash (который возвращает вас к текущему тайнику: каждый тайник на самом деле представляет собой пару коммитов) и reflogs (которые обычно скрыты от глаз, поскольку обычно они просто беспорядочные).

Это также, однако, заставляет нас к прививкам и заменам. Трансплантат является лишь ограниченным видом замены, и мелкие репозитории используют ограниченную форму трансплантата. 1 Я не буду описывать замены полностью здесь, поскольку они немного более сложны, но в целом Git делает для всего этого использование трансплантата или замены как "вместо". Для конкретного случая коммитов мы хотим, чтобы здесь была возможность изменить - или, по крайней мере, притвориться измененным - родительский идентификатор или идентификаторы любого коммита... и для мелких репозиториев, мы хотим иметь возможность притворяться, что у данного коммита нет родителей.


1 Способ, которым мелкие репозитории используют код пересадки, не шаток. Для более общего случая я рекомендовал использовать git replace вместо этого, поскольку это также было и не шатко. Единственное рекомендуемое использование для трансплантатов - или, по крайней мере, было много лет назад, - чтобы поставить их на место достаточно долго, чтобы запустить git filter-branch скопировать измененную - привитую - историю, после чего вам следует просто полностью отказаться от привитой истории. Ты можешь использовать git replace для этой цели, но в отличие от трансплантатов, вы можете использовать git replace постоянно или полупостоянно, без необходимости git filter-branch,


Создание мелкого клона

Чтобы сделать неглубокий клон глубины 1 текущего состояния вышестоящего репозитория, мы выберем одно из трех имен веток: feature/tall, master, или же bug/short - и перевести его в идентификатор фиксации. Затем мы напишем специальную запись трансплантата, которая гласит: "Когда вы видите этот коммит, представьте, что он не имеет родительских коммитов, то есть является корневым коммитом".

Допустим, мы выбираем master, Имя master указывает на фиксацию G чтобы сделать неглубокий клон коммита G мы получаем коммит G как обычно, из верхнего Git, но затем напишите специальную запись взяточничества, в которой утверждается, что commit G не имеет родителей Мы поместили это в наш репозиторий, и теперь наш график выглядит так:

G   <-- master, origin/master

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

Обновление мелкого клона, которое мы сделали ранее

Но что, если у нас уже есть клон (глубина 1) и мы хотим обновить его? Ну, это на самом деле не проблема. Допустим, мы сделали неглубокий клон восходящего потока, когда master указал совершить B перед новыми ветками и исправлением ошибки. Это означает, что у нас в настоящее время есть это:

B   <-- master, origin/master

В то время как B настоящий родитель A у нас есть мелкий клон, который говорит: "притворись B это корневой коммит ". Теперь мы git fetch --depth 1, который смотрит вверх по течению master - то, что мы называем origin/master - и видит коммит G, Мы берем совершить G от верхнего по течению, вместе с его объектами, но сознательно не захватывать коммиты D а также F, Затем мы обновляем наши записи прививки с мелким клоном, говоря: G Корневой коммит тоже ":

B   <-- master

G   <-- origin/master

Наш репозиторий теперь имеет две корневые коммиты: имя master (еще) указывает на фиксацию B, чьи родители мы (до сих пор) притворяемся, не существует, и имя origin/master указывает на G, чьи родители мы притворяемся, не существует.

Вот почему вам нужно git reset

В обычном хранилище вы можете использовать git pull что на самом деле git fetch с последующим git merge, Но git merge требует истории, а у нас ее нет: мы подделали Git с притворными корневыми коммитами, и у них нет истории за ними. Поэтому мы должны использовать git reset вместо.

Какие git reset делать это немного сложно, потому что это может повлиять на три разные вещи: имя ветви, индекс и рабочее дерево. Мы уже видели, как называются ветви: они просто указывают на (один, конкретный) коммит, который мы называем вершиной ветви. Это оставляет индекс и рабочее дерево.

Рабочее дерево легко объяснить: там находятся все ваши файлы. Вот и все: не больше и не меньше. Это сделано для того, чтобы вы могли использовать Git: Git предназначен для хранения всех когда-либо сделанных коммитов, чтобы их можно было извлечь. Но они в формате, бесполезном для простых смертных. Чтобы быть использованным, файл - или, как правило, файл целого коммита - должен быть извлечен в его обычный формат. Рабочее дерево - то, где это происходит, и затем вы можете работать над ним и делать новые коммиты, используя его тоже.

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

Какие git reset --hard Это влияет на все три: имя ветви, индекс и рабочее дерево. Он перемещает имя ветки так, что он указывает на (возможно, другой) коммит. Затем он обновляет индекс в соответствии с этим подтверждением и обновляет рабочее дерево в соответствии с новым индексом.

Следовательно:

git reset --hard origin/master

говорит Git посмотреть origin/master, Так как мы побежали git fetch что сейчас указывает на совершение G, Затем Git заставляет нашего мастера - нашу текущую (и единственную) ветвь - также указывать на коммит G, а затем обновляет наш индекс и рабочее дерево. Наш график теперь выглядит так:

B   [abandoned - but see below]

G   <-- master, origin/master

Сейчас master а также origin/master оба имени фиксируют G и совершить G это проверенный в рабочем дереве.

Зачем тебе git clean -dfx

Ответ здесь немного сложен, но обычно это "вы не делаете" (нужно git clean).

Когда вам нужно git clean это потому, что вы - или что-то, что вы запустили - добавили файлы в ваше рабочее дерево, о которых вы не сказали Git. Это неотслеживаемые и / или игнорируемые файлы. С помощью git clean -df удалит неотслеживаемые файлы (и пустые каталоги); добавление -x также удалит проигнорированные файлы.

Подробнее о разнице между "неотслеживаемым" и "игнорируемым" см. В этом ответе.

Почему тебе не нужно git clean: индекс

Я упоминал выше, что вам обычно не нужно бежать git clean, Это из-за индекса. Как я уже говорил ранее, индекс Git в основном "следующий коммит". Если вы никогда не добавляете свои собственные файлы - если вы просто используете git checkout чтобы проверить различные существующие коммиты, которые у вас были все время, или которые вы добавили с git fetch; или если вы используете git reset --hard переместить имя ветки, а также переключить индекс и рабочее дерево на другой коммит - тогда все, что находится в индексе сейчас, есть, потому что ранее git checkout (или же git reset) поместите его в указатель, а также в дерево работ.

Другими словами, у индекса есть краткая и быстрая для доступа к Git сводка или манифест, описывающий текущее рабочее дерево. Git использует это, чтобы знать, что сейчас находится в рабочем дереве. Когда вы просите Git переключиться на другой коммит, через git checkout или же git reset --hard Git может быстро сравнить существующий индекс с новым коммитом. Любые файлы, которые изменились, Git должен извлечь из нового коммита (и обновить индекс). Любые файлы, которые недавно добавлены, Git также должен извлечь (и обновить индекс). Любые файлы, которые ушли, - которые находятся в существующем индексе, но не в новом коммите - Git должен удалить... и это то, что делает Git. Git обновляет, добавляет и удаляет эти файлы в рабочем дереве в соответствии со сравнением текущего индекса и нового коммита.

Это означает, что если вам нужно git clean Вы, должно быть, сделали что-то за пределами Git, чтобы добавить файлы. Эти добавленные файлы отсутствуют в индексе, поэтому по определению они не отслеживаются и / или игнорируются. Если они просто не отслежены, git clean -f удалил бы их, но если они игнорируются, только git clean -fx удалит их. (Ты хочешь -d просто удалить каталоги, которые становятся или становятся пустыми во время очистки.)

Заброшенные коммиты и сборка мусора

Я упомянул и нарисовал обновленный мелкий график, что когда мы git fetch --depth 1 а потом git reset --hard мы отказываемся от предыдущего поверхностного коммита глубины-1. (На графике я нарисовал, это было зафиксировать B.) Однако в Git заброшенные коммиты редко бывают действительно заброшенными - по крайней мере, не сразу. Вместо этого некоторые специальные имена, такие как ORIG_HEAD держитесь за них какое-то время, и каждая ссылка - ветви и теги - это формы ссылок - несет в себе журнал "предыдущих значений".

Вы можете отобразить каждый reflog с git reflog refname, Например, git reflog master показывает не только какой совершить master имена сейчас, но также и то, что фиксирует это имя в прошлом. Существует также рефлог для HEAD сам, что к чему git reflog показывает по умолчанию.

Записи журнала в конце концов истекают. Их точная продолжительность варьируется, но по умолчанию они имеют право на истечение срока действия через 30 дней в некоторых случаях и 90 дней в других. После истечения срока действия эти записи reflog больше не защищают оставленные коммиты (или, для аннотированных ссылок на теги, аннотированный объект тега - теги не должны перемещаться, поэтому этот случай не должен возникать, но если это произойдет - если вы принудительно) Git для перемещения тега - он обрабатывается так же, как и все остальные ссылки).

Как только любой объект Git - коммит, аннотированный тег, "дерево" или "блоб" (файл) - действительно не имеет ссылок, Git может удалить его по-настоящему. 2 Только в этот момент исходные данные репозитория для коммитов и файлов исчезают. Даже тогда, это происходит только тогда, когда что-то работает git gc, Таким образом, мелкий репозиторий обновляется git fetch --depth 1 не совсем так же, как свежий клон с --depth 1: мелкий репозиторий, вероятно, имеет несколько длительных имен для исходных коммитов и не будет удалять дополнительные объекты репозитория до тех пор, пока эти имена не истекут или не будут удалены другим способом.


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


Особые соображения для --single-branch и / или мелкие клоны

Я отметил выше, что имя, которое вы даете git clone -b может ссылаться на тег. Для нормальных клонов (не мелкие и не с одной ветвью) это работает так же, как и следовало ожидать: вы получаете обычный клон, а затем Git делает git checkout по имени тега. Результат - обычная отдельная ГОЛОВА в совершенно обычном клоне.

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

Во-первых, если вы используете --single-branch Git меняет нормальный fetch Конфигурация в новом репозитории. Нормальный fetch Конфигурация зависит от имени, которое вы выбираете для пульта, но по умолчанию origin так что я просто буду использовать origin Вот. Это читает:

fetch = +refs/heads/*:refs/remotes/origin/*

Опять же, это нормальная конфигурация для нормального (не одношагового) клона. Эта конфигурация говорит git fetch что выбрать, что есть "все ветви". Когда вы используете --single-branch однако вместо этого вы получаете строку выборки, которая относится только к одной ветви:

fetch = +refs/heads/zorg:refs/remotes/origin/zorg

если вы клонируете zorg ветка.

Какую бы ветку вы ни клонировали, это та, которая входит в fetch линия. Каждое будущее git fetch будет подчиняться этой линии, 3, так что вы не будете получать другие ветви. Если вы хотите получить другие ветви позже, вам придется изменить эту строку или добавить дополнительные строки.

Во-вторых, если вы используете --single-branch а то, что вы клонируете - это тег, Git поместит довольно странно fetch линия. Например, с git clone --single-branch -b v2.1 ... Я получил:

fetch = +refs/tags/v2.1:refs/tags/v2.1

Это означает, что вы не получите веток, и если кто-то не переместил тег, 4 git fetch ничего не сделаю!

В-третьих, поведение тега по умолчанию немного странно из-за git clone а также git fetch получить теги. Помните, что теги - это просто ссылка на один конкретный коммит, как ветки и все другие ссылки. Между ветвями и тегами есть два ключевых различия: ветви должны двигаться (а теги нет), а ветви переименовываются (а теги нет).

Помните, что во всем вышеизложенном, мы продолжаем обнаруживать, что другие (вверх по течению) Git's master становится нашим origin/master, и так далее. Это пример процесса переименования. Мы также вкратце увидели, как именно это переименование работает через fetch = линия: наш Git берет их refs/heads/master и меняет его на наш refs/remotes/origin/master, Это имя не только выглядит иначе (origin/master), но буквально не может быть таким же, как любая из наших веток. Если мы создаем ветку с именем origin/master, 5 "полное имя" этой ветви на самом деле refs/heads/origin/master который отличается от другого полного имени refs/remotes/origin/master, Только когда Git использует более короткое имя, у нас есть одна (обычная, локальная) ветка с именем origin/master и другой другой (удаленный трекинг) филиал с именем origin/master, (Это очень похоже на участие в группе, где всех зовут Брюс.)

Теги не проходят через все это. Тег v2.1 только что назван refs/tags/v2.1, Это означает, что нет никакого способа отделить "свой" тег от "вашего" тега. Вы можете иметь либо свой тег, либо их тег. Пока никто не перемещает тег, это не имеет значения: если у вас обоих есть тег, он должен указывать на один и тот же объект. (Если кто-то начинает перемещать теги, все становится ужасно.)

В любом случае, Git реализует "нормальную" выборку тегов по простому правилу: 6, когда Git уже имеет коммит, если некоторые имена тегов фиксируют, Git также копирует тег. В обычных клонах первый клон получает все теги, а затем последующие git fetch Операции получают новые теги. Однако неглубокий клон по определению пропускает некоторые коммиты, а именно все, что находится ниже любой точки пересадки на графе. Эти коммиты не подберут теги. Они не могут: чтобы иметь теги, вам нужно иметь коммиты. Git не разрешается (кроме как через мелкие трансплантаты) иметь идентификатор коммита без фактического наличия коммита.


3 Вы можете дать git fetch некоторые refspec(s) в командной строке, и они переопределяют значения по умолчанию. Это относится только к выборке по умолчанию. Вы также можете использовать несколько fetch = строк в конфигурации, например, для выборки только определенного набора ветвей, хотя нормальный способ "де-ограничить" изначально клон с одной ветвью - вернуть обычную +refs/heads/*:refs/remotes/origin/* получить строку.

4 Так как теги не должны двигаться, мы можем просто сказать "это ничего не делает". Если они двигаются, + в refspec представляет флаг силы, поэтому тег перемещается.

5 Не делай этого. Это сбивает с толку. Git прекрасно с этим справится - локальная ветвь находится в локальном пространстве имен, а ветвь удаленного отслеживания - в пространстве имен удаленного отслеживания - но это действительно сбивает с толку.

6 Это правило не соответствует документации. Я тестировал против Git версии 2.10.1; старые Gits могут использовать другой метод.

О самом процессе обновления мелкого клона, см. Commit 649b0c3 форму Git 2.12 (1 квартал 2017 года).
Этот коммит является частью:

Фиксация 649b0c3, фиксация f2386c6, фиксация 6bc3d8c, фиксация 0afd307 (06 декабря 2016 г.) от Nguyán Thái Ngọc Duy ( pclouds ) См. Коммит 1127b3c, коммит 381aa8e (06 декабря 2016 г.) Расмуса Вильмоса ( ravi-prevas ) (Объединено Юнио С Хамано - gitster - в коммите 3c9979b, 21 декабря 2016 г.)

shallow.c

это paint_down() является частью шага 6 из 58babff (shallow.c: 8 шагов для выбора новых коммитов для.git / shallow - 2013-12-05).
Когда мы выбираем из мелкого репозитория, нам нужно знать, нужно ли одному из новых / обновленных ссылок новые "мелкие коммиты" в .git/shallow (потому что у нас недостаточно истории этих рефери) и какой.

Вопрос на шаге 6 заключается в том, какие (новые) мелкие коммиты требуются в других, чтобы поддерживать достижимость по всему хранилищу, не сокращая нашу историю?
Чтобы ответить, мы помечаем все коммиты, доступные из существующих ссылок, с помощью UNINTERESTING (" rev-list --not --all "), помечайте мелкие коммиты с помощью BOTTOM, затем для каждой новой / обновленной ссылки проходите по графику фиксации, пока мы не нажмем UNINTERESTING или BOTTOM, отмечая ссылку на фиксацию во время ходьбы.

После того как все прогулки пройдены, мы проверяем новые мелкие коммиты. Если мы не увидели никаких новых ссылок, помеченных на новом мелком коммите, мы знаем, что все новые / обновленные ссылки доступны с использованием только нашей истории и .git/shallow,
Рассматриваемый мелкий коммит не нужен и может быть отброшен.

Итак, код.

Цикл здесь (чтобы пройти через коммиты) в основном:

  1. получить один коммит из очереди
  2. игнорировать, если он виден или неинтересен
  3. отметьте это
  4. пройти через всех родителей и..
    • 5.a отметьте это, если это никогда не отмечалось прежде
    • 5.b поставить его обратно в очередь

В этом патче мы выпадаем на шаге 5а, потому что в этом нет необходимости.
Коммит, отмеченный 5a, помещается обратно в очередь и будет отмечен на шаге 3 на следующей итерации. Единственный случай, когда он не будет помечен, это когда коммит уже помечен как UNINTERESTING (5a не проверяет это), что будет игнорироваться на шаге 2.

Если целью было обновить неглубокий клон без получения всей истории (но позволяя получить короткую историю), то альтернативные подходы с использованием современных версий git (>= 2.11.1) могут работать с:

  • --shallow-since=... получать только коммиты старше указанной даты
  • --shallow-exclude=... для получения без получения фиксации, которая является предком данной фиксации
Другие вопросы по тегам