$ 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
?
@AndyJ Ни то, ни другое. Он просто повторно фиксирует извлеченный главный коммит с новой записью коммиттера (точнее, в этом сценарии он меняет значение поля «дата коммиттера»), тем самым создавая новый коммит и заставляя локальную ветвь и ее восходящий поток расходиться. Вот почему я опустил полный вывод git cat-file -p @
, но его поле committer
может выглядеть примерно так: committer user <[email protected]> 1719057871 +0300
, а затем что-то вроде committer user <[email protected]> 1719057877 +0300
после внесения изменений в коммит.
«Push (/fetch) только те объекты, которые не находятся в удаленном (/local) репозитории» — это то, как git работает по умолчанию. Он не передает весь репозиторий каждый раз, когда взаимодействует с удаленным устройством.
@larsks Отправка последовательных коммитов с новым деревом, содержащим несколько экземпляров 100_MiB_FILE
, действительно работает очень быстро. Таким образом, похоже, что это относится только к принудительным нажатиям. Я обновил вопрос.
Несмотря на то, что принудительная отправка занимает больше времени, поскольку на дальнем конце нужно выполнить больше работы, фактически отправляются только объекты, которых еще нет на удаленном конце. Возможно, потребуется проделать дополнительную работу, чтобы выяснить, какие объекты есть на удаленном устройстве относительно тех объектов, которые мы предлагали отправить, но фактическая отправка эффективна.
Я бы рекомендовал просмотреть сетевой трафик. Вы делаете предположение, основанное исключительно на разнице во времени, которую можно объяснить комментарием @matt. (Если вы можете доказать, что сделали ложное предположение, это все равно хороший вопрос с хорошим ответом.)
@TTT Не совсем. Я добавил полный сценарий с выводом git, основанный как на времени, так и на объектах, перечисленных до принудительного нажатия, при наличии двух расходящихся ветвей, указывающих на одно и то же дерево.
@matt Я обновил вопрос, теперь в нем есть две одинаковые по разности ветви, которые указывают на один и тот же объект дерева. Удаленный репозиторий явно знает об объекте дерева, поскольку этот объект дерева находится в другой ветке удаленного репозитория. Я ожидаю, что git отправит объект фиксации, исключая объект дерева.
@terrorrussia-keeps-killing Ах, да. Благодаря вашему редактированию я теперь вижу результаты последнего нажатия, показывающие, что на самом деле он отправляет тот же объект. Хороший вопрос!
Ни протокол push, ни протокол выборки не выходят за пределы передаваемой цепочки фиксации, чтобы определить, какие объекты следует отправить. Как только сторона определила, какие коммиты необходимо отправить, она отправляет все объекты, необходимые для покрытия различий с родительскими коммитами. Никогда не определяется, находится ли такой объект уже в пункте назначения.
Например, предположим, что изменение было внесено в один файл, затем зафиксировано и отправлено. Теперь в следующем коммите изменение было просто отменено. На этом этапе дерево HEAD идентично дереву родительского коммита и уже присутствует на сервере. Следующий push отправляет один коммит, но, тем не менее, также блоб и дерево, поскольку они составляют разницу с родительским коммитом.
Почему Git не смотрит дальше? Потому что нет предела, где остановить поиск. Нужно было бы перебрать все объекты в репозитории и для каждого определить, находится ли он уже по назначению. Клиент просто отказывается от всей этой работы.
Спасибо за ответ. Это все интересно, поскольку я думал, что клиент git должен спрашивать удаленный репозиторий (для сценария отправки), какие объекты отсутствуют для их отправки, а какие находятся в режиме ожидания во время отправки.
В вашем вопросе не упоминается, для чего используется
git commit --amend --no-edit
. Это изменить файл или добавить файл?