Git поддерево - поддерево обновлено, но не может

Я использую поддерево 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 [email protected]:***
! [rejected]        72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward)
error: failed to push some refs to '[email protected]:***'
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.

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

Ответ 1

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

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

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

Ответ 2

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

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

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

git subtree split --prefix pythonapp master

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

157a66d050d7a6188f243243264c765f18bc85fb956

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

git push heroku 157a66d050d7a6188f243243264c765f18bc85fb956:master --force

Ответ 3

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

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

[EDIT: к сожалению 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/, или слияния, которые идентичны родительскому, также рухнули. [EDIT: Кроме того, с тех пор я обнаружил некоторое обнаружение сквоша, так что теперь я думаю об этом только если вы вытащили без раздачи, а затем упрощенное свертывание слияния, описанное в еще одном ответе, умудряется выбрать неразрезанный путь и отбросить сжатый путь.]). Результат состоит в том, что материал, который он пытается оттолкнуть, заканчивается с любой работой кто-то, кто привязался к текущему репозиторию хоста, от которого вы нажимаете, но не работайте с людьми, переданными непосредственно в субрепозиторий или через другой репозиторий хостов.

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

Ответ 4

Для приложения типа "страницы GitHub", где вы развертываете поддерево "dist" на ветку gh-pages, решение может выглядеть примерно так:

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

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

Ответ 5

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

Что я узнал, у меня была ветка, которая не была прикреплена к локальной ветки мастера. Эта ветвь существует и ее просто висит в пустоте. В вашем случае его, вероятно, называют 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; это приведет к локальному зависанию в ветки void project-shared к ветки master удаленного project-shared.

Ответ 6

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

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

% 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. Commits D и E имеют одно и то же дерево z, потому что у нас есть commit C, который отменяет commit B, поэтому последовательность B-C ничего не делает для dir, даже если она имеет к ней изменения.

Теперь давайте разделим. Сначала мы разделим на commit 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

Затем мы разделим на commit 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

Ответ 7

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

Я обычно 'git поддерево pull' с параметром'ssquash '. Кажется, что это делало все труднее смириться, поэтому мне нужно было сделать поддерево, не раздавив на этот раз, разрешить некоторые конфликты, а затем нажать.

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

Ответ 8

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

Ответ 9

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

git push subtree_remote_address.git `git subtree split --prefix=subtree_folder`:refs/heads/branch --force

Ответ 10

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

$id = git subtree split --prefix pythonapp master
Write-Host "Id is: $id"
Invoke-Expression "git push heroku $id':master --force"

Ответ 11

Так что это то, что я написал, основываясь на том, что сказал @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