Как мне вернуть ветку в коммит и перевести формит коммиты в новую ветку? - PullRequest
0 голосов
/ 13 марта 2020

У меня git выглядит так, как для удаленного, так и для локального.

A---B---C---D master

Теперь я хочу, чтобы мой master был возвращен при B коммите, но я также не хотите потерять мои C и D коммит.

Есть ли способ заставить мой git выглядеть так как для удаленного, так и для локального?

      C---D new branch
     /
A---B master

Я знаю, что невозможно вернуться к предыдущему пункту, возможно, что-то подобное также помогает:

                          C---D new branch
                         /
A---B---C---D---revertToB master

Пожалуйста, дайте мне несколько советов.

1 Ответ

1 голос
/ 13 марта 2020

Вы можете получить это:

     C--D   <-- new-branch
    /
A--B   <-- master

, но для этого необходимо принудительно все Git репозитории, которые имеют ветвь с именем master, указывающая на существующий коммит D, чтобы убрать их master для указания на существующий коммит B. Это может быть сложно, поскольку Git «любит» добавить коммитов, но не «удалять» их.

Чтобы получить это локально, при условии, что у вас есть:

A--B--C--D   <-- master (HEAD)

пробег:

git branch new-branch master
git reset --hard HEAD~2

Теперь у вас есть:

     C--D   <-- new-branch
    /
A--B   <-- master (HEAD)

локально. К сожалению, в другом Git хранилище в origin все еще есть master, указывающее на коммит D. Любые клоны, сделанные из этого другого хранилища Git, также имеют их master, указывающие на фиксацию D. Вы должны заставить все эти репозитории делать одно и то же извлечение. Если есть только один репозиторий, и , у вас есть разрешение на использование git push --force для его отвода, теперь вы можете:

git push origin new-branch   # to create new-branch on origin
git push -f origin master    # to force them to retract their master

(Вы можете добавить -u к первый git push.)


Чтобы получить более безопасную вещь, которую вы ищете - добавление нового коммита к master, который сбрасывает его обратно в прежнее состояние B - Вы можете использовать:

git branch temp master

, который устанавливает следующее:

A--B--C--D   <-- master (HEAD), temp

Затем вы можете запустить git revert -n HEAD~2..HEAD; git commit или git read-tree -u HEAD~2; git commit, чтобы сделать новый коммит E, чей моментальный снимок соответствует B:

A--B--C--D   <-- temp
          \
           E   <-- master (HEAD)

Существующие коммиты C-D застряли там, где они находятся. Если вы хотите, чтобы new-branch был потомком master, вы должны создать его. Обратите внимание, что если вы в порядке с new-branch, указывающим на существующий коммит D, вы можете просто использовать имя new-branch вместо temp, и вы получите то, что хотели. Но если вам нужен новый коммит (назовем его F), который идет после E, который соответствует содержимому коммита D, вы можете создать новую ветку сейчас:

git checkout -b new-branch master

, давая:

A--B--C--D   <-- temp
          \
           E   <-- master, new-branch (HEAD)

Теперь вы можете:

  • отменить фиксацию возврата E или
  • git read-tree совершить D и зафиксировать, или
  • git cherry-pick фиксирует C и D.

Чтобы выполнить первое, запустите git revert master или git revert HEAD (достаточно), и вы получите:

A--B--C--D   <-- temp
          \
           E   <-- master
            \
             F   <-- new-branch (HEAD)

Чтобы выполнить второе, запустите git read-tree -u temp; git commit, чтобы сделать новый коммит F (вам нужно будет ввести новое сообщение о коммите).

Чтобы сделать третий, запустите git cherry-pick temp~2..temp; это даст:

A--B--C--D   <-- temp
          \
           E   <-- master
            \
             C'-D'  <-- new-branch (HEAD)

, где имена C' и D' указывают, что это выбранные вишней копии C и D соответственно. (Когда мы вернулись, чтобы сделать E, мы объединили оба этапа отмены, используя git revert -n, поэтому E - это не просто отмена D или C, а скорее отмена оба сразу.)

Теперь можно безопасно удалить имя temp, так как оно нам больше не нужно, чтобы более удобно находить C и D. (Нам не нужно было все это время, так как мы могли бы их найти в любом случае, но было удобно иметь имя для коммита D, чтобы коммиты C и B были temp~2 и temp~1 соответственно. )

Теперь безопасно (и не требует git push --force) до git push origin master new-branch.


В качестве альтернативы, вместо создания temp, вы можете сделать имя new-branch указывает на коммит D все время. То есть, начиная с:

A--B--C--D   <-- master (HEAD)

вы можете сделать:

git branch new-branch master

давая:

A--B--C--D   <-- master (HEAD), new-branch

Затем вы можете "отменить" коммиты C-D на master как и раньше, например, производя:

A--B--C--D   <-- new-branch
          \
           E   <-- master (HEAD)

В качестве следующего шага вы можете выполнить:

git checkout new-branch; git merge -s ours master

, чтобы Git создать новое слияние коммит F, чей снимок совпадает со снимком существующего коммита D:

A--B--C--D---F   <-- new-branch
          \ /
           E   <-- master (HEAD)

Фиксация F теперь опережает master, как и раньше, но при чтении в обратном направлении по прямой линии, через ссылки первого родителя, он никогда не посещает коммит E: Git возвращается от F к D до C и так далее. Опять же, это при условии , который вы используете --first-parent, когда git log или git rev-list перемещаетесь назад от F. По умолчанию обратный путь от F посещений обоих коммитов E и D (а так как по умолчанию go по порядку даты коммиттера, вы ' увидим E, который мы сделали позже D, перед тем как увидеть D).

...