Почему git повторно отправляет объекты, которые уже находятся в удаленном репозитории?

Подготовьте репозиторий

$ git init repo

$ cd repo

$ git config user.name username

$ git config user.email username@mail

$ head --bytes=100000000 < /dev/urandom > file

Создайте ветку удаленного мастера

$ git add file

$ git commit --message=initial\ commit
[master (root-commit) 6068e0d] initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file

$ git rev-parse master
6068e0d0071bc76f31065ddb1ddbad0d46c635b8

$ git cat-file -p master
tree f9862a0f570f7a910a01ab1fa743d66407452fdd
author username <username@mail> 1719130146 +0300
committer username <username@mail> 1719130146 +0300

initial commit

$ git cat-file -p f9862a0f570f7a910a01ab1fa743d66407452fdd
100644 blob bbb3bfdf995b0d2eea02b1fed8688e886da134de    file

$ git cat-file -s bbb3bfdf995b0d2eea02b1fed8688e886da134de
100000000

$ time git push git@hostname:username/reponame.git master:master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 20 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 95.40 MiB | 4.57 MiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To hostname:username/reponame.git
 * [new branch]      master -> master

real    0m22.503s
user    0m3.233s
sys 0m0.483s

Подготовьте ветку удаленной разработки

$ git checkout -b dev

$ git rev-parse dev
6068e0d0071bc76f31065ddb1ddbad0d46c635b8

$ git cat-file -p dev
tree f9862a0f570f7a910a01ab1fa743d66407452fdd
author username <username@mail> 1719130146 +0300
committer username <username@mail> 1719130146 +0300

initial commit

$ time git push git@hostname:username/reponame.git dev:dev
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To hostname:username/reponame.git
 * [new branch]      dev -> dev

real    0m1.671s
user    0m0.035s
sys 0m0.015s

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

Изменение первоначального коммита разработчика как локально, так и удаленно

$ git commit --amend --no-edit
[dev b15f0a6] initial commit
 Date: Sun Jun 23 11:09:06 2024 +0300
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file

$ git rev-parse dev
b15f0a6c36463e6e8b28b63473c464fb2fbcc326

$ git cat-file -p dev
tree f9862a0f570f7a910a01ab1fa743d66407452fdd
author username <username@mail> 1719130146 +0300
committer username <username@mail> 1719131177 +0300

initial commit

# it still points to the f9862a0f570f7a910a01ab1fa743d66407452fdd tree as expected

$ time git push --force git@hostname:username/reponame.git dev:dev
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 20 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 95.40 MiB | 4.44 MiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To hostname:username/reponame.git
 + 6068e0d...b15f0a6 dev -> dev (forced update)

real    0m23.326s
user    0m3.202s
sys 0m0.507s

Почему это занимает так много времени, если удаленный репозиторий уже знает о дереве f9862a0f570f7a910a01ab1fa743d66407452fdd и большом двоичном объекте bbb3bfdf995b0d2eea02b1fed8688e886da134de?

В вашем вопросе не упоминается, для чего используется git commit --amend --no-edit. Это изменить файл или добавить файл?

Andy J 22.06.2024 13:25

@AndyJ Ни то, ни другое. Он просто повторно фиксирует извлеченный главный коммит с новой записью коммиттера (точнее, в этом сценарии он меняет значение поля «дата коммиттера»), тем самым создавая новый коммит и заставляя локальную ветвь и ее восходящий поток расходиться. Вот почему я опустил полный вывод git cat-file -p @, но его поле committer может выглядеть примерно так: committer user <[email protected]> 1719057871 +0300, а затем что-то вроде committer user <[email protected]> 1719057877 +0300 после внесения изменений в коммит.

terrorrussia-keeps-killing 22.06.2024 14:12

«Push (/fetch) только те объекты, которые не находятся в удаленном (/local) репозитории» — это то, как git работает по умолчанию. Он не передает весь репозиторий каждый раз, когда взаимодействует с удаленным устройством.

larsks 22.06.2024 14:51

@larsks Отправка последовательных коммитов с новым деревом, содержащим несколько экземпляров 100_MiB_FILE, действительно работает очень быстро. Таким образом, похоже, что это относится только к принудительным нажатиям. Я обновил вопрос.

terrorrussia-keeps-killing 22.06.2024 15:02

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

matt 22.06.2024 16:55

Я бы рекомендовал просмотреть сетевой трафик. Вы делаете предположение, основанное исключительно на разнице во времени, которую можно объяснить комментарием @matt. (Если вы можете доказать, что сделали ложное предположение, это все равно хороший вопрос с хорошим ответом.)

TTT 23.06.2024 03:12

@TTT Не совсем. Я добавил полный сценарий с выводом git, основанный как на времени, так и на объектах, перечисленных до принудительного нажатия, при наличии двух расходящихся ветвей, указывающих на одно и то же дерево.

terrorrussia-keeps-killing 23.06.2024 10:42

@matt Я обновил вопрос, теперь в нем есть две одинаковые по разности ветви, которые указывают на один и тот же объект дерева. Удаленный репозиторий явно знает об объекте дерева, поскольку этот объект дерева находится в другой ветке удаленного репозитория. Я ожидаю, что git отправит объект фиксации, исключая объект дерева.

terrorrussia-keeps-killing 23.06.2024 10:47

@terrorrussia-keeps-killing Ах, да. Благодаря вашему редактированию я теперь вижу результаты последнего нажатия, показывающие, что на самом деле он отправляет тот же объект. Хороший вопрос!

TTT 23.06.2024 17:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
122
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

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

Спасибо за ответ. Это все интересно, поскольку я думал, что клиент git должен спрашивать удаленный репозиторий (для сценария отправки), какие объекты отсутствуют для их отправки, а какие находятся в режиме ожидания во время отправки.

terrorrussia-keeps-killing 24.06.2024 12:49

Другие вопросы по теме