Почему я не могу отправить это актуальное поддерево Git?

88

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

Я столкнулся с проблемой, когда git сообщает, что мое поддерево обновлено, но нажатие отклоняется. Например:

#! git subtree pull --prefix=public/shared project-shared master
From github.com:****
* branch            master     -> FETCH_HEAD
Already up-to-date.

Если я нажму, я должен получить сообщение, что толкать нечего ... Верно? ВЕРНО? :(

#! git subtree push --prefix=public/shared project-shared master
git push using:  project-shared master
To git@github.com:***
! [rejected]        72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:***'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Что могло быть причиной этого? Почему толкать не удается?

Матеуш
источник
почему вы не делаете то, что вам говорит git: git pull?
AlexWien
8
Git pull не имеет ничего общего с моим вопросом. Но да, git pull дает мне актуальное сообщение, поскольку в исходном репозитории нет ничего нового.
mateusz 07
1
Похоже, ветка, в которой вы находитесь, находится за тем местом, где находится главный HEAD - например, история ... -> A -> B -> C, где удаленный главный HEAD находится в C, но ваша текущая ветка находится в A (или какой-то потомок A, не связанный с C). Я бы посмотрел на свою историю git, чтобы убедиться.
Марк Лейтон Фишер,
У меня такая же проблема. Я использовал --rejoinдля ускорения выталкивания поддерева. Когда я вручную выполняю push-запуск subtree split --rejoinподдерева, ветвь разделенного поддерева имеет историю только до последнего повторного присоединения, когда обычно она содержит всю историю поддерева. Для меня это непосредственная причина ошибки, связанной с перемоткой вперед. Я все еще не уверен, почему разделение поддерева создает усеченную историю.
hurrymaplelad

Ответы:

99

Я нашел ответ в этом комментарии блога https://coderwall.com/p/ssxp5q

Если вы столкнулись с проблемой «Обновления были отклонены, потому что кончик вашей текущей ветки позади. Объедините удаленные изменения (например, 'git pull')», когда вы нажимаете (по какой-либо причине, особенно с использованием истории git) тогда вам нужно будет вложить команды git, чтобы вы могли принудительно нажать на heroku. например, учитывая приведенный выше пример:

git push heroku `git subtree split --prefix pythonapp master`:master --force
Эрик Вудрафф
источник
2
Выручил меня! Спасибо.
Майкл Форрест,
8
Как запустить эту команду в Windows? Я получаюerror: unknown option 'prefix'
плов
3
Удивительно, это просто спасло мне день :)
bpipat
2
Верно, но, вероятно, в большинстве случаев использование поддерева таким образом является односторонним продвижением к героку. Как правило, heroku не считается де-факто репозиторием git, из которого несколько пользователей могут нажимать и извлекать данные.
Эрик Вудрафф,
11
Мне было трудно определить, какие отношения herokuи pythonappесть в ответе. Перевод этого с языка, специфичного для Heroku:git push <your subtree's origin> `git subtree split --prefix=Path/to/subtree master`:master --force
aednichols
39

В Windows вложенная команда не работает:

git push heroku `git subtree split --prefix pythonapp master`:master --force

Вы можете сначала запустить вложенный бит:

git subtree split --prefix pythonapp master

Это (после большого количества чисел) вернет токен, например

157a66d050d7a6188f243243264c765f18bc85fb956

Используйте это в содержащей команду, например:

git push heroku 157a66d050d7a6188f243243264c765f18bc85fb956:master --force
Крис Джордан
источник
Это гораздо более чистое решение. Работает для меня!
Infinity
Когда я запускаю git subtree split --prefix subtreedirectoryname master, я получаю фатальный неоднозначный аргумент «мастер»: неизвестная ревизия или путь не в рабочем дереве
Demodave
Это отличное решение, я просто получал, git subtreeчто раньше это не команда. После использования вашего решения я смог развернуть папку на heroku вместо всего моего репо
pakman198 01
Отличное решение и экономия времени
Нишарг Шах
29

Используйте --ontoфлаг:

# DOESN'T WORK: git subtree push --prefix=public/shared project-shared master --onto=project-shared/master

[РЕДАКТИРОВАТЬ: к сожалению subtree push, не пересылается --ontoна нижележащий split, поэтому операция должна выполняться двумя командами! После этого я вижу, что мои команды идентичны командам в одном из других ответов, но объяснение другое, поэтому я все равно оставлю его здесь.]

git push project-shared $(git subtree split --prefix=public/shared --onto=project-shared/master):master

Или, если вы не используете bash:

git subtree split --prefix=public/shared --onto=project-shared/master
# This will print an ID, say 0123456789abcdef0123456789abcdef,
# which you then use as follows:
git push project-shared 01234567:master

Я часами просматривал исходный код git-subtree, чтобы понять это, так что надеюсь, вы это оцените;)

subtree pushначинается с запуска subtree split, который переписывает вашу историю коммитов в формат, который должен быть готов к отправке. Таким образом, он удаляет public/shared/переднюю часть любого пути, в котором он есть, и удаляет любую информацию о файлах, у которых его нет. Это означает, что даже если вы вытащите без сжатия, все коммиты суб-репозитория в восходящем направлении игнорируются, поскольку они называют файлы своими пустыми путями. (Коммиты, которые не касаются файлов вpublic/shared/, или коммиты слияния, которые идентичны родительским, также сворачиваются. [РЕДАКТИРОВАТЬ: Кроме того, с тех пор я обнаружил некоторое обнаружение сквоша, поэтому теперь я думаю, что это только в том случае, если вы вытащили несжатый, а затем упрощенное свертывание фиксации слияния, описанное в еще одном ответе, позволяет выбрать несжатый путь и отбросьте сжатый путь.]) В результате материал, который он пытается отправить, в конечном итоге содержит любую работу, которую кто-то привязал к текущему репозиторию хоста, из которого вы нажимаете, но не рабочие люди, привязанные непосредственно к суб-репозиторию или через другой хост репозиторий.

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

энте
источник
2
Я рекомендую этот ответ как тот, который помог мне уже дважды. И описание - это то, что мне нужно, чтобы понять, как работает разделение и отправка в нужное место. Спасибо.
Камил Возняк
2
Что такое «совместный проект»?
Colin D
1
@ColinD «Общий проект» - это имя пульта дистанционного управления. Это имя вы выберете при настройке места для получения и отправки. Вы можете получить список пультов с git remote -v.
Entheh
@entheh часы, которые вы потратили на это, просто сделали мой день. Благодарность!
июнь
1
Я обнаружил, что эта ошибка всегда возникает, когда ваш вспомогательный репозиторий имеет каталог, имя которого совпадает с префиксом, например, вы добавляете вспомогательный репозиторий как «libxxx» в основной репозиторий, а сам ваш вспомогательный репозиторий также имеет подкаталог с именем «libxxx» ". В этом случае git будет неправильно рассматривать коммиты в дополнительном репозитории как коммиты в основном репозитории и попытается разделить их. Эта --ontoопция помогает git идентифицировать коммиты, которые уже находятся во вспомогательном репозитории.
Cosyn
14

Для приложения типа «GitHub pages», где вы развертываете поддерево «dist» в ветке gh-pages, решение может выглядеть примерно так

git push origin `git subtree split --prefix dist master`:gh-pages --force

Я упоминаю об этом, поскольку он немного отличается от приведенных выше примеров heroku. Вы можете видеть, что моя папка "dist" существует в главной ветке моего репо, а затем я нажимаю ее как поддерево в ветку gh-pages, которая также находится в источнике.

Колин Д
источник
3

Я уже сталкивался с этой проблемой раньше, и вот как я ее решил.

Я обнаружил, что у меня есть ветка, которая не была прикреплена к локальной основной ветке. Эта ветка существует и просто висит в пустоте. В вашем случае это, вероятно, называется project-shared. Предполагая, что это так, и когда вы делаете это, git branchвы видите локальную project-sharedветку, тогда вы можете « добавлять » новые коммиты в свою существующую project-sharedветку, выполнив:

git subtree split --prefix=public/shared --onto public-shared --branch public-shared

Как я понял, я git subtreeначну создавать новую ветку --onto, в данном случае это локальная public-sharedветка. Затем ветвь означает создание ветки, которая просто заменяет старую public-sharedветку.

Это сохранит весь предыдущий SHA public-sharedветки. Наконец, вы можете сделать

git push project-shared project-shared:master

Предполагая, что у вас есть project-shared пульт; это подтолкнет локальное зависание в project-shared ветке void к masterветке удаленного project-shared.

snw
источник
3

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

Давайте посмотрим на пример (который вы можете легко воспроизвести), чтобы лучше понять, как это работает. Рассмотрим следующую историю (формат строки: коммит [дерево] тема):

% git log --graph --decorate --pretty=oneline --pretty="%h [%t] %s"
*   E [z] Merge branch 'master' into side-branch
|\
| * D [z] add dir/file2.txt
* | C [y] Revert "change dir/file1.txt"
* | B [x] change dir/file1.txt
|/
*   A [w] add dir/file1.txt

В этом примере мы разделяем dir. Коммиты Dи Eимеют то же дерево z, потому что у нас есть фиксация C, которая отменила фиксацию B, поэтому B-Cпоследовательность ничего не делает дляdir даже если в ней есть изменения.

Теперь займемся разделением. Сначала мы разбиваем на коммит C.

% git log `git subtree split -P dir C` ...
* C' [y'] Revert "change dir/file1.txt"
* B' [x'] change dir/file1.txt
* A' [w'] add dir/file1.txt

Затем мы разбиваем на коммит E.

% git log `git subtree split -P dir E` ...
* D' [z'] add dir/file2.txt
* A' [w'] add dir/file1.txt

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

Обычно вы можете допустить эту ошибку, используя push --force, поскольку отброшенные коммиты обычно не содержат в себе важной информации. В долгосрочной перспективе ошибку необходимо исправить, чтобы в истории разделения действительно были все коммиты, которые касаются dir, как и ожидалось. Я ожидаю, что исправление будет включать более глубокий анализ родительских коммитов на предмет скрытых зависимостей.

Для справки, вот часть исходного кода, отвечающая за поведение.

copy_or_skip()
  ...
  for parent in $newparents; do
      ptree=$(toptree_for_commit $parent) || exit $?
      [ -z "$ptree" ] && continue
      if [ "$ptree" = "$tree" ]; then
          # an identical parent could be used in place of this rev.
          identical="$parent"
      else
          nonidentical="$parent"
      fi
  ...
  if [ -n "$identical" ]; then
      echo $identical
  else
      copy_commit $rev $tree "$p" || exit $?
  fi
Климкин
источник
исправлена ​​ли ошибка по состоянию на 2020-05-09 (сб)? @klimkin
полумесяц
ошибка исправлена. см .: contrib / subtree: исправить ошибку пропущенного слияния "разделения поддерева". github.com/git/git/commit/…
половина месяца,
0

Ответ Эрика Вудраффа мне не помог, но помогло следующее:

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

Надо добавить, что раздавленная тяга не выявила конфликтов, все в порядке.

Жолт Сатмари
источник
0

Другое [более простое] решение - продвинуть головку пульта ДУ, сделав еще одну фиксацию, если можете. После того, как вы поместите эту расширенную голову в локальное поддерево, вы снова сможете вытолкнуть ее из нее.

9свэмпи
источник
1
Именно так я решил ту же проблему с удаленным репозиторием, не синхронизированным с локальным поддеревом.
Айдас Бендорайтис
0

Вы можете принудительно отправить локальные изменения в удаленное репозиторий поддерева

git push subtree_remote_address.git `git subtree split --prefix=subtree_folder`:refs/heads/branch --force
FunkyKat
источник
0

Быстрый PowerShell для пользователей Windows на основе решения Криса Джордана

$id = git subtree split --prefix pythonapp master
Write-Host "Id is: $id"
Invoke-Expression "git push heroku $id`:master --force"
Rtype
источник
0

Вот что я написал на основе того, что сказал @entheh.

for /f "delims=" %%b in ('git subtree split --prefix [name-of-your-directory-on-the-file-system-you-setup] -b [name-of-your-subtree-branch]') do @set token=%%b
git push [alias-for-your-subtree] %token%:[name-of-your-subtree-branch] --force

pause
Демодав
источник
0

Была и эта проблема. Вот что я сделал, основываясь на самых популярных ответах:

Дано:

#! git subtree push --prefix=public/shared project-shared master
git push using:  project-shared master
To git@github.com:***
! [rejected]        72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:***'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Введите это:

git push <origin> 72a6157733c4e0bf22f72b443e4ad3be0bc555ce:<branch> --force

Обратите внимание, что токен, который выводится командой «git subtree push», используется в «git push».

Амос
источник