Каков наиболее эффективный способ объединить исходные изменения в рабочую копию с помощью Git?

Очень распространенный сценарий, возникающий, когда люди пишут код вместе, заключается в том, чтобы себя (скажем, функциональной ветки, над которой вы работаете) обновляться. Это может привести к "конфликту" одного и того же файла, который был изменен как в основной ветке, так и в вашем рабочем дереве. У меня вопрос: какой самый прямой способ решить этот тип конфликта с Git.

В частности, я ищу что-то краткое и обеспечивающее хорошую видимость (т. е. знание того, что происходит, потому что иногда конфликты выходят за рамки автоматического разрешения, и хотелось бы знать это до слияния). Что-то лучше этого:

# I'm on a branch, have changes in working tree,
  # have overlapping change in remote; for simplicity,
  # assume no local (unpushed) commits have been made (clean HEAD)

git fetch
git difftool origin/<branchName> HEAD   # visually examine incoming changes, to understand them; conclude that the changes are automatically merge-able

#git merge              # fails: "Your local changes to the following files would be overwritten by merge:"
#git merge -s resolve   # fails: same
#git merge -s recursive # fails: same
#git mergetool          # no-op: "No files need merging" ?!

git stash
git pull
git stash pop

git difftool HEAD    # visually examine outcome - it worked, but does stashing really need to be involved?

Другие ответы включали фиксацию после выборки, что для меня нет, мне нечего фиксировать на данном этапе. Чего я хочу, так это привести себя (т. е. мое рабочее дерево) в соответствие с апстримом.


person haelix    schedule 29.09.2018    source источник


Ответы (2)


Я думаю, что отказ от слияния — плохая идея:

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

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

Если у вас есть огромная ветка, в которой будет действительно много конфликтов, я бы посоветовал завершить исправления, над которыми вы работаете, перед слиянием, чтобы у вас не было «черновиков» исправлений в вашей истории, когда вам нужно будет размещайте свои материалы в общем репозитории git.

Я лично использую вариант 1, даже если у меня есть много ожидающих исправлений. Это позволяет мне перепроверять каждый патч при выполнении перебазирования, а также предотвращает «злые слияния», которые мне не нравятся (имхо, сделать git виноватым).

В заключение, я думаю, вам следует пересмотреть тот факт, что вы не хотите совершать временные действия; это дает вам больше безопасности, чем тайники, и позволяет вам использовать слияние так, как вы, кажется, уже знаете.

person OznOg    schedule 29.09.2018
comment
Вы затрагиваете суть проблемы — почему мы обсуждаем, как быстро/часто я должен делать коммиты, какое это имеет отношение к тому, чтобы быть в курсе? --› Я не отказываюсь от слияния, даже наоборот, хочу слить, но не коммитить. - person haelix; 29.09.2018
comment
Что ж, git — это инструмент, предназначенный для частой фиксации и выполнения такого рода операций в качестве обычного рабочего процесса. Если вы считаете, что не хотите фиксировать, возможно, вы ищете SVN (в какой-то другой системе управления версиями) - person OznOg; 30.09.2018
comment
Когда вы говорите часто, что вы имеете в виду? Как часто это часто? Перефразируя вопрос, как вы разграничиваете свои коммиты с помощью git? Как разграничить коммиты с другими инструментами VCS? (если используется) - person haelix; 01.10.2018
comment
по сравнению с SVN (например), фиксация не означает распространение изменений, это просто набор изменений. Всякий раз, когда вы хотите/нужно, вы все равно можете изменить свои коммиты, чтобы они лучше отражали то, что вы хотели иметь. ТОГДА вы нажимаете их, чтобы вы могли поделиться ими (это упрощенно, но это идея). Чтобы ответить на вопрос, как часто, я бы ответил: всякий раз, когда у вас есть что-то значимое для вас, например, когда код компилируется. Позже вы все еще можете внести изменения, чтобы исправить код, который, например, не прошел модульные тесты. Я лично не боюсь совершать 10 раз в одной и той же строке - person OznOg; 01.10.2018

Если у вас есть изменения, т. е. грязное рабочее дерево, вам нужно что-то зафиксировать. В лучшем случае у вас есть что-то, что вы хотите зафиксировать временно.

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

Вы упоминаете начальный сценарий, для которого у меня есть рабочий шаблон, который я считаю полезным:

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

Допустим, вы работаете над feature-X, который в итоге будет помещен в dev (ветвь разработки). Другие разработчики работали над функциями Y и Z, и одна из них завершена, а dev теперь обновлена, поэтому вы запускаете:

$ git fetch

и видим, что ваш dev теперь позади origin/dev. То есть теперь у вас есть:

...--C--D--H   <-- master
         \
          \         I--J   <-- origin/dev
           \       /
            E--F--G   <-- dev, feature-X (HEAD)

в вашем репозитории. У вас также есть некоторые вещи в вашем рабочем дереве, которые отличаются от файлов в коммите G. (Возможно, у вас еще нет ветки с именем feature-X, и вместо этого HEAD присоединена к dev. В этом случае вы можете просто создать ее сейчас с git checkout -b feature-X, и теперь вы соответствуете картинке.)

Что нужно сделать на этом этапе, так это зафиксировать материал, над которым вы работаете в любом случае. Это делает один новый коммит K:

...--C--D--H   <-- master
         \
          \         I--J   <-- origin/dev
           \       /
            E--F--G   <-- dev
                   \
                    K feature-X (HEAD)

Теперь вы можете перемотать свои собственные файлы с dev на origin/dev. Основной метод команды:

$ git checkout dev                 # safe, since your work is committed
$ git merge --ff-only origin/dev   # or `git pull` if you really insist

Теперь рисунок выглядит так:

...--C--D--H   <-- master
         \
          \
           \
            E--F--G--I--J   <-- dev (HEAD), origin/dev
                   \
                    K   <-- feature-X (HEAD)

Здесь большинство людей просто запускают git checkout feature-X; git rebase dev, и это нормально, и вы можете свободно использовать этот метод. (Я часто так делаю. Подумайте о том, чтобы сделать это с последующим приемом git reset HEAD^, описанным ниже.) Но иногда я просто переименовываю feature-X в feature-X.0, а затем создаю новый< /em> feature-X с git checkout -b feature-X:

...--C--D--H   <-- master
         \
          \
           \
            E--F--G--I--J   <-- dev, origin/dev, feature-X (HEAD)
                   \
                    K   <-- feature-X.0

Теперь я готов снова начать работу над feature-X, и на данный момент я просто выбираю все коммиты feature-X.0:

$ git cherry-pick dev..feature-X.0

который создает коммит K', который является копией K:

...--C--D--H   <-- master
         \
          \               K'  <-- feature-X (HEAD)
           \             /
            E--F--G--I--J   <-- dev, origin/dev
                   \
                    K   <-- feature-X.0

Это работает, даже если на feature-X.0 есть несколько коммитов:

...--C--D--H   <-- master
         \
          \               K'-L'  <-- feature-X (HEAD)
           \             /
            E--F--G--I--J   <-- dev, origin/dev
                   \
                    K--L   <-- feature-X.0

Если последняя фиксация этого нового feature-X (L' в этой версии, K' в той, в которой была только одна фиксация) действительно, серьезно еще не готова к фиксации, на данный момент я просто используйте git reset HEAD^ (вы можете написать это HEAD~, если это проще, как, по-видимому, в Windows), чтобы переместить имя ветки на один шаг назад. Это удаляет самый последний коммит из текущей ветки, давая:

...--C--D--H   <-- master
         \
          \               K'  <-- feature-X (HEAD)
           \             /
            E--F--G--I--J   <-- dev, origin/dev
                   \
                    K--L   <-- feature-X.0

и оставляет рабочее дерево "грязным" именно таким, каким оно было до того, как я начал весь этот процесс. (В общем, частичная фиксация — это нормально, так как я буду использовать git rebase -i позже, чтобы очистить все, когда feature-X будет в основном готов.)

Если по какой-то причине мне нужно повторить процесс, я переименовываю текущий незавершенный feature-X в feature-X.1, или feature-X.2, или как-то еще. Я выращиваю небольшую коллекцию feature-X-ов и время от времени выхожу в сад с ветками и обрезаю самые сорные. Самый последний и самый лучший по-прежнему называется feature-X, но все мои предыдущие работы доступны по мере необходимости, пока я их не отсею. Это лучше, чем либо перебазировать или заначка, потому что, если код сложный и я что-то упускаю, у меня остается старая версия, под узнаваемым именем, а не какой-то непонятный хеш ID в рефлоге, и не заначка, неотличимая от десятка других заначек.

person torek    schedule 29.09.2018
comment
Я начал читать ваш пост с интересом, но остановился на том месте, где вы упомянули функцию-X.0, где у меня возникли сомнения, что вы серьезно предлагаете это как ответ на мой вопрос. Мой вопрос касался одной ветки, чрезвычайно распространенного сценария, который решается одной концептуально простой операцией. Как и в другом ответе, вы также пытаетесь заставить меня совершить, как будто я хочу, но не знаю об этом. Так сколько же веток и операций требуется Git для этого? - person haelix; 29.09.2018
comment
В конечном счете, если у вас есть изменения в тех же файлах, что и некоторые входящие коммиты, вы должны выполнить слияние (как в глаголе слить). безопасный способ сделать это — сначала выполнить фиксацию. У Git нет внешнего интерфейса, чтобы сделать это небезопасно. Итак, вам нужна фиксация. Вы можете использовать git stash, который делает коммиты. Я не рекомендую этот метод, потому что, когда что-то пойдет не так (а они случаются), вам будет гораздо лучше, если у вас будет ветка. Но вы можете запустить git stash, ввести новые коммиты и запустить git stash apply, проверить, работает ли он, и git stash drop, если он работает. Вы даже можете [продолжение] - person torek; 30.09.2018
comment
... можно даже сократить это до git stash && git pull && git stash pop (но см. stackoverflow.com/q/52568548/1256452) или git pull --autostash --rebase (но см. вопрос, который вызвал этот вопрос). - person torek; 30.09.2018
comment
В конечном счете, если у вас есть изменения в тех же файлах, что и некоторые входящие коммиты, вы должны выполнить слияние (как в глаголе, слить). Безопасный способ сделать это — зафиксировать сначала. Я полностью согласен с первой частью, мы согласны с семантикой слияния. Но зачем совершать? Вы выделили слово безопасно. Почему коммит обеспечивает безопасность слияния? В конечном счете, суть вопроса в том, почему я должен выполнять слияние внутри HEAD, а не внутри рабочего каталога, где я могу естественным образом скорректировать результат ошибочного слияния? (Рабочий каталог означает, что над ним работают нет?) - person haelix; 01.10.2018
comment
Причина фиксации первой заключается в том, что если вы этого не сделаете, слияние уничтожит ваше текущее состояние. Рассмотрим, как работает слияние (глагол): у вас есть три входа: база слияния и две подсказки. У вас есть один результат, который является либо новой фиксацией, либо лучшими усилиями Git, оставленными в вашем рабочем дереве. Обычно эти три входа являются фиксациями, доступными только для чтения и такими же постоянными, как и сами фиксации. Вы можете (используя git merge-recursive напрямую) рассматривать рабочее дерево как один из входных данных, но поскольку оно перезаписывает рабочее дерево для получения результата, что происходит с вашим ввод, если слияние не удалось? - person torek; 01.10.2018