Как вы перемещаете зафиксированные изменения из ветви в мастер как ожидающие изменения?

Я реализовывал новую функцию, для которой я создал новую ветку, сделал несколько коммитов и подтолкнул. Теперь я хочу продолжить работу над этой функцией в мастере в виде ожидающих / незафиксированных изменений. В ожидании, потому что нужно больше работы, прежде чем я совершу. Как мне это сделать?

Когда я наконец фиксирую, история изменений мастера должна просто указать один коммит, как если бы я никогда не создавал другую ветку и не делал промежуточные коммиты. Как мне это сделать?

Ручной способ - создать второе рабочее пространство git, открыть мастер в одном и ветвь в другом и скопировать и вставить каждый измененный файл. Есть ли автоматизированный способ сделать это?

3 ответа

Решение

Сначала я дам команды, затем то, что они действительно делают и почему это достигает того, чего вы хотите. Ниже я предполагаю, что ветвь функции просто называется branch - изменить имя, данное git merge если у него есть какое-то другое имя.

git status               # and make sure everything is committed on your branch
                         # do any extra commits here if required

git checkout master      # change the current branch/commit, index, and work-tree
git merge --squash --no-commit branch

git merge, --squash подразумевает --no-commit в любом случае, вы можете опустить второй аргумент флага здесь. Я включил это больше для иллюстрации. Более того, --squash означает выполнить операцию слияния в индексе и рабочем дереве, но не делать коммит слияния.)

Вы можете, в зависимости от ваших инструментов, захотеть сделать git reset (который по умолчанию --mixed сбросить с HEAD как обязательство), чтобы скопировать HEAD зафиксировать обратно в индекс, так что простой git diff показывает вам все, и git status показывает все как не организовано для коммита.

Долго

Здесь важно отметить, что на самом деле не существует такой вещи, как ожидающие изменения. Git не хранит изменения вообще. Git хранит коммиты, и каждый коммит представляет собой полный снимок всех файлов. Когда вы спрашиваете Git, что изменилось, вы должны сообщить Git о двух коммитах или, по крайней мере, о двух деревьях, полных файлов. Затем из этих двух входов Git выясняет, что отличается в снимках A и B. Затем Git сообщает вам об этих различиях, но A и B остаются полными снимками. В некотором смысле ничего из этого не имеет значения - вы видите изменения, когда хотите, и снимки, когда хотите. Но если ваша ментальная модель соответствует тому, что на самом деле делает Git, у вас будет меньше загадок: ваша собственная работа будет легче. Самый важный способ, которым это важно, - помнить: чтобы увидеть изменения или различия, вы должны предоставить два входа. Иногда подразумевается один из этих входов. Иногда оба они подразумеваются!

Этот последний случай с простым git diff или же git diff --cached / git diff --staged: Git сравнивает индекс с рабочим деревом (обычный git diff), или HEAD зафиксировать индекс (--staged или же --cached). Опция, если таковая имеется, определяет два входа. То есть Git берет две из трех вещей из этого списка:

  • HEAD commit, который является реальным коммитом, содержащим все файлы в специальной Git-только, замороженной / только для чтения форме;
  • индекс, который по сути является предлагаемым следующим коммитом и содержит копию всех файлов, также в форме Git-only, но не совсем замороженной;
  • рабочее дерево, в котором ваши файлы находятся в их обычной полезной форме.

Имея две из этих вещей в руках, Git сравнивает их и говорит вам, что отличается. git status Команда работает, как часть своей работы, оба из этих двух git diff s (с определенными параметрами, всегда установленными, такими как --name-status а также --find-renames=50%) и суммирует результаты: выход из git diff --staged внесены изменения для фиксации и вывода из git diff изменения не установлены для фиксации.

Вы также можете запустить вручную git diff HEAD, Это выбирает HEAD и ваше рабочее дерево как два входа: вы явно назвали один из них, HEAD в аргументах подразумевается и другое. Это сравнивает замороженные HEAD содержимое для незамерзшего, нормального формата, содержимого рабочего дерева и показывает разницу. Опять же, Git сравнивает полные снимки: второй - это живое рабочее дерево, которое снимается в процессе диффузии.

(Кроме того: то, что вы хотите сделать, на мой взгляд, вероятно, является неправильным способом решения этой проблемы. Зачастую вы можете упростить свою работу, совершая ее рано и часто. Выполнение различий - хорошая идея, но вы можете сделать это из base-commit для tip-commit, т. е. через промежуток коммитов, для оценки общего результата. Затем вы можете использовать интерактивное перебазирование или - мой предпочтительный метод - дополнительные ветки, чтобы реконструировать меньшие коммиты в более разумную серию коммиты. Но многое из этого является личным предпочтением. Похоже, ваш инструмент XCode может работать намного лучше, чем сейчас; то, как вы его используете, не является неправильным, оно просто ужасно ограничивает.)

Как и почему это работает

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

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

Настоящее слияние, то есть git merge он выполняет полное трехстороннее слияние и, в конце концов, создает новый коммит типа коммит слияния - создает новый коммит с двумя родителями. Эти два родителя являются двумя из трех входов, которые были использованы для вычисления результата слияния. Третий вход подразумевается двумя другими; это база слияния. Git находит базу слияния самостоятельно, используя граф коммитов. Затем Git сравнивает базу слияния с --ours а также --theirs подсказки ветви, как будто используя два git diff --find-renames команды: одна от базы до нашей, одна от базы до их.

Игнорирование таких проблем, как добавление, удаление или переименование файлов, сравнение этих трех коммитов дает список тех, кто изменил какие файлы. Если мы изменили какой-то файл, который они не изменили, или они изменили какой-то файл, который мы не изменили, объединить их легко: Git может просто взять наш файл или их файл. Если никто из нас не изменил файл вообще, Git может взять любую из трех версий файла (объединить базу, нашу или их).

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

Случай конфликта слияния является самым грязным. Здесь Git оставляет все три входных файла в индексе и записывает конфликтующее слияние в рабочее дерево. Ваша работа, как человека, бегущего git merge, чтобы придумать правильное слияние, как вам нравится. Вы можете использовать три индексные копии файла, или копию рабочего дерева, или все эти. Вы решаете слияние самостоятельно, а затем используете git add записать правильный результат в указатель, заменив три не объединенные копии одной объединенной копией. Это решает этот конфликт; как только вы разрешили все конфликты, вы можете завершить слияние.

Если вы бежите git merge с --no-commit Git сам сделает столько слияний, сколько сможет - а может быть и все - и затем остановится, так же, как и в случае конфликта слияний. Если не было никаких конфликтов, это оставляет и индекс, и рабочее дерево в состоянии готовности к фиксации. (Если были конфликты, --no-commit опция не имеет никакого эффекта: Git уже собирался остановиться, оставив беспорядок позади, и все еще останавливаясь, оставив беспорядок для вас.)

Однако во всех этих случаях Git оставляет файл в .git директории, сообщающей Git, что следующий коммит - либо из git merge --continue или же git commit - должно быть двое родителей. Это делает новый коммит коммитом слияния, который свяжет две ветви:

             I--J   [master pointed to J when we started]
            /    \
...--F--G--H      M   <-- master (HEAD)
            \    /
             K--L   <-- branch

Это не то, что вы хотите. Итак, мы используем git merge --squash, который говорит Git: делайте слияние как обычно, но вместо того, чтобы делать коммит слияния, организуйте следующий коммит как обычный коммит. То есть, если Git сделать новый коммит M это будет выглядеть так:

             I--J--M   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- branch

Без особой уважительной причины, --squash блокирует коммит, даже если слияние проходит хорошо. Так после git merge --squash, индекс и рабочее дерево обновляются, но вы можете внести дополнительные изменения в рабочее дерево, используйте git add скопировать обновленные файлы в индекс, и только когда вы закончите, используйте git commit сделать новый коммит.

(Для более простого слияния, когда Git по умолчанию выполняет ускоренную перемотку вперед, --squash блокирует режим ускоренной перемотки вперед, как если бы git merge --no-ff, Так --squash без --no-ff и без --no-commit, хватит.)

Финал git reset, если используется и нужен, просто копирует HEAD совершить обратно в индекс. То есть вы, вероятно, хотите, чтобы ваш инструмент сравнивал HEAD зафиксировать на рабочем дереве, как если бы вы работали git diff HEAD, Если инструмент сравнивает индекс с рабочим деревом, вы можете получить тот же эффект, де-обновив индекс для соответствия HEAD, после --squash операция обновила его.

Рассматривали ли вы сделать слияние в master а затем с помощью предпочитаемого вами инструмента diff для сравнения истории коммитов?

Слияние в master будет коммит. Но пока ты не push изменения в удаленном (например, "происхождение") вы всегда можете сбросить слияние, используя git reset HEAD~2 отказаться от слияния совершает.

Еще одно примечание, "HEAD~2", является примером количества коммитов, к которым необходимо вернуться обратно в истории коммитов. Если ваш коммит-слияние поместит вас на "x" количество коммитов перед вашим пультом, вы замените "2" на количество коммитов a git status сообщает, что вы впереди вашего пульта.

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

cd $the-root-of-your-project
git checkout feature-branch
git format-patch <the-commit-since-you-began-the-feature>

Это создаст один файл патча на коммит. (Подсказка: коммит, который необходимо ввести в последней команде, - это тот, который был перед первым, который вы хотите перенести. См. since а также revision range в https://git-scm.com/docs/git-format-patch для деталей.)

Во-вторых, я предлагаю вам cat все это *.patch файлы в один.

В-третьих, внесите изменения этого накопленного файла патча в индекс master:

git checkout master
patch -p1 < accumulated.patch
git diff
git add .
...

patch это тот из Баш и -p1 необходимо удалить префикс пути, специфичный для git, в файле патча.

Другие вопросы по тегам