Почему git записывает объекты в удаленный, прежде чем удаленный разрешает дельты - PullRequest
1 голос
/ 14 марта 2019

Короткий вопрос: я обнаружил, что git записывает объекты BLOB-объектов на удаленный компьютер до того, как он разрешает дельты во время git push --force, даже если те же объекты BLOB-объектов были записаны в тот же удаленный репозиторий некоторое время назад.

Я хочу спросить:

  1. Почему git записывает статические объекты BLOB-объектов на удаленный компьютер, даже если у последнего они есть
  2. Можно ли это сделать на стороне клиента или на стороне сервера?)

Более длинная история:

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

Весь кодфайлы находятся в ветви "history", и все статические файлы находятся в ветви "static", ветви "history" и "static" совместно используют общий начальный коммит, и они объединяются в ветвь "master", показанную ниже:

*   commit (HEAD -> master, origin/master)
|\  Merge:
| |
| |
| | 
| |     Merge branch 'static'
| | 
| * commit (static)
| |
| |
| | 
| |
| | 
* | commit (origin/history, history)
| |
| | 
| |
| |     
| |
| | 
* | commit
| |
| |
| | 
| |
| | 
* | commit
|/
|
|   
|
| 
* Initial commit

Каждый раз, когда происходит обновление кода, я фиксирую изменение в ветке «master», затем перебазирую коммит в ветку «history», затем извлекаю ветку «history» и снова появляюсь ветка «static», dВо время этого процесса ветки «history» (ускоренная перемотка вперед) и «master» (принудительное обновление) отправляются на удаленный сервер:

git rebase --onto history origin/master master
commit=`git rev-parse HEAD`
git checkout $history_branch
git reset --hard $commit

git push

git checkout master
git reset --hard history
git merge -m "Merge branch 'static'" static

git push --force

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

Когда есть изменения в статических файлах, я извлекаю ветку "static", фиксирую изменение, используя флаг --amend, затем извлекаю ветку "history" и ветвь слияния "static", принудительное обновление ветки "master" на удаленном компьютере в конце процесса:

git checkout static
git add .
git commit --amend -m 'Add static files'

# As torek pointed out, I made a mistake in this post
# The following "git push" command is not performed
# git push

git checkout master
git reset --hard history
git merge -m "Merge branch 'static'" static

# git push --force
git push --force origin static master

# torek's suggestion "What you can do about this, part 1"
# does not work out for me:
#
# $ git push --force origin static master
# Counting objects: 422, done.
# Compressing objects: 100% (407/407), done.
# Writing objects: 100% (422/422), 480.08 MiB | 1.05 MiB/s, done.
# Total 422 (delta 41), reused 0 (delta 0)
# remote: Resolving deltas: 100% (41/41), completed with 1 local object.
# To ...
#  + 3539524...6618427 master -> master (forced update)
#  + 6a1f0c0...ba60bb9 static -> static (forced update)


Последняя команда, однако, занимает много времени для завершения, и я обнаружил, что git записывает все объекты статических BLOB-объектов на удаленныйдо того, как пульт дистанционного управления разрешит дельты:

Counting objects: 422, done.
Compressing objects: 100% (407/407), done.
Writing objects: 100% (422/422), 480.08 MiB | 1.44 MiB/s, done.
Total 422 (delta 41), reused 0 (delta 0)
remote: Resolving deltas: 100% (41/41), completed with 1 local object.

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

Я использовал скрипт в how-do-gits-Transfer-protocol-work , чтобы перечислить все объекты в локальном хранилище до и после 2-го выполнения, иРезультаты показывают только 2 новых объекта после 2-го выполнения, объекты фиксации, созданные с помощью git commit --amend -m 'Add static files' и git merge -m "Merge branch 'static'" static, что означает, что новые объекты BLOB-объектов не создаются.

Дополнительная информация:

Здесьскрипт, который следит за рабочим процессом:

#!/bin/bash

master_branch=master
master_origin=origin/master
history_branch=history
static_branch=static


make_master() {
    git checkout $master_branch
    git reset --hard $history_branch
    git merge -m "Merge branch 'static'" $static_branch
}


extend_history() {
    git rebase --onto $history_branch $master_origin $master_branch
    local commit=`git rev-parse HEAD`
    git checkout $history_branch
    git reset --hard $commit
}


add_static() {
    git checkout $static_branch
    git add .
    git commit --amend -m 'Add static files'
}


case "$1" in
code)
    extend_history
    git push
    make_master
    git push --force
;;
asset)
    add_static
    make_master
    # git push --force
    git push --force origin $static_branch $master_branch
;;
*)
    echo "Unknown action \"$1\"" >&2
    exit 127
esac

версия клиента git: 2.17.1 версия клиента: 18.04.2 LTS (Bionic Beaver), x86_64, виртуальная машина внутри сервера виртуальной коробки Virtualbox 5.2.26 версия git: 2.11.0 серверная ОС: Debian GNU / Linux 9 (растяжка), x86_64

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


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

git checkout static
git subtree add -P code history
git checkout master
git reset --hard static
# Remove branch static and history, their tracking branches,
# and their counterparts in remote repository

Для сжатия статических коммитов:

code_commit=`git subtree split -P code`
git rm --quiet -r code
git checkout --orphan new
git commit --quiet -m 'Add static files'
git branch -M new master
git subtree add -P code $code_commit

1 Ответ

1 голос
/ 14 марта 2019

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

Git на самом деле не делает этого по уважительной причине.Ну, во всяком случае, «хорошо».Git спрашивает сервер, имеет ли он конкретные коммиты .Затем он делает некоторые разумные, но не обязательно точные предположения, основанные на результатах.Иногда это будет означать отправку объекта без необходимости.И, что не совсем случайно, ваш код, который достигает толчков, не выполняет то, что вы утверждаете, как это делается в вашем объяснении перед этим кодом.(Это, я думаю , источник проблемы, но я не проверял это.)

Тем не менее, есть некоторые вещи, которые вы можете сделать.Давайте сначала посмотрим, что делает Git.

Подробности

Когда происходит изменение в статических файлах, я извлекаю ветку "static", фиксирую изменение с помощью --amend флаг, затем извлечение ветки "history" и ветвь слияния "static", принудительное обновление ветки "master" в удаленном режиме в конце процесса:

git checkout static
git add .
git commit --amend -m 'Add static files'

На этом этапе в вашемв вашем собственном хранилище:

       R    [static@{1}]
      /
...--o--S   <-- static

(хотя на самом деле раздел ... пуст, а o - это коммит A ниже).

Commit R - этотот, который раньше был на кончике static;он был отодвинут в сторону, с S в качестве нового наконечника static.Обе фиксации существуют в вашем собственном хранилище.

git push

Вы не выполняете этот шаг. Таким образом, на сервере еще нет фиксации S,(Посмотрите на код для случая asset, который запускается add_static, затем make_master, затем git push --force. Шаг make_master устанавливает текущую ветвь на master, поэтому git push --force толкает только master. Поэтому вывод git log --graph не показывает origin/static.) Если бы вы это делали, вам нужно было бы git push --force здесь.

Теперь перейдем к:

git checkout master
git reset --hard history
git merge -m "Merge branch 'static'" static

git push --force

Давайте также нарисуем этот график, включая предыдущий master@{2} (это @{2}, потому что у нас есть два промежуточных события: сброс, затемслияние).Этот график, отражающий то, что находится в вашем хранилище, выглядит следующим образом:

  R--------M   <-- origin/master, master@{2}
 /        /
A--o--o--L   <-- history, origin/history, master@{1}
 \        \
  S--------N   <-- master

(commit R имеет метку static@{1}, а S имеет static и origin/static; я не включаю эти метки в чертеж из-за нехватки места.)

Сервер тем временем имеет следующее:

  R--------M   <-- master
 /        /
A--o--o--L   <-- history

Здесь все становится интереснее.Теперь клиент должен определить, какие объекты отправлять.Это делается путем инициирования разговора с сервером.Это начинается с: Я хотел бы отправить вам N;у вас есть N? Конечно, на сервере нет коммита N, так как вы только что сделали это.

Поскольку сервер говорит нет, клиент говорит: Тогда мне нужноу вас есть N родители L и S;у вас есть такие? Конечно, у них есть L, но не S.Теперь клиент знает, что нужно отправить N и S, и что на сервере есть все объекты, связанные с L - и, поскольку история на сервере не мелкая, все объекты, которыев цепочке, идущей от L обратно к A.

Теперь клиент спрашивает, есть ли у сервера S родитель A, или предполагает, что это так, поскольку A является предкомL;в любом случае он осознает, что на сервере действительно есть A.

. Теперь клиент делает предположение, что на сервере есть все объекты, которые находятся во всех коммитах, упомянутых сервером.Это не предполагает, что на сервере существует коммит R, так как не было упоминания R в обменах по протоколу иметь / хотеть.Таким образом, он упаковывает все объекты, которые находятся в S, и отправляет их.Сервер перепаковывает это, обнаруживает, что большинство больших двоичных объектов являются избыточными, и фактически игнорирует избыточные большие двоичные объекты.

Что вы можете сделать с этим, часть 1

Один из способов справиться с этим - пойти дальше и установить на сервере метку, соответствующую коммиту R (на предыдущем шаге). То есть, добавьте git push --force origin static, чтобы origin имел метку static, указывающую на R.

Затем, при отправке им нового коммита для master, обязательно попросите их обновить и static и master:

git push --force origin static master

или

git push origin +static:static +master:master

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

Теперь сервер будет иметь:

    ...........<-- static
   .
  R--------M   <-- master
 /        /
A--o--o--L   <-- history

и будет рекламировать тот факт, что его refs/heads/static обозначает коммит R. Клиенту нужна эта информация для своего предварительного push-хука (независимо от того, выполняет он или нет какой-либо предварительный push-хук) Поэтому, когда клиент отправляет отправлять новые коммиты, он предлагает отправить S (для обновления static и, поскольку он в истории обновленных master) и N (для обновления master), но , на этот раз он может сказать, что на сервере R. Он должен быть в состоянии отправить только один новый BLOB-объект.

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

Обратите внимание, что важно, чтобы вы выполнили оба эти действия вместе, потому что, как только сервер принимает S в качестве static и N в качестве master, он собирает мусор как M и R. (Обычно на серверах не включены повторные журналы, и все эти объекты находятся в файлах пакета и, следовательно, не подлежат 14-дневному льготному периоду для незакрепленных объектов.)

Что вы можете сделать с этим, часть 2

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

В некотором смысле, перезапись истории вызывает проблемы: клиент делает предположение , что на сервере нет ни одного из объектов static-assets , потому что каждый новый commit в этой ветке совершенно не связан ни с чем, кроме root commit A. Это предположение является «безопасным» в том смысле, что оно просто приводит к отправке дополнительных объектов. Это экономит много времени , потому что перечисление всех объектов дерева и блобов за каждым коммитом очень медленное - намного быстрее просто сказать: Ага, сервер имеет этот коммит, поэтому - за исключением осложнения, вызванные мелкими трансплантатами, которые мы здесь проигнорируем - у него есть все объекты, подразумеваемые этим коммитом, и его история. Клиенту вряд ли придется предлагать какие-либо хеш-идентификаторы, так как сервер вскоре отвечает Да Он у меня уже есть , и это завершает обход этой части графика. Если на сервере установлен L, у него есть все до L. Если у него есть R, у него есть все до R.

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

...