Как вы перемещаете зафиксированные изменения из ветви в мастер как ожидающие изменения? - PullRequest
0 голосов
/ 03 января 2019

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

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

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

Ответы [ 3 ]

0 голосов
/ 03 января 2019

Сначала я дам команды, затем, что они на самом деле делают и почему это достигает того, чего вы хотите.Ниже я предполагаю, что ветвь функции просто называется 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 reset с HEAD в качестве коммита), чтобы скопировать коммит HEAD обратно виндекс, так что обычный git diff показывает вам все, а git status показывает все как , не подготовленный для фиксации .

Long

Важно отметить, что здесь, что на самом деле не существует такой вещи, как ожидающих изменений .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 коммит, который является реальным коммитом, содержащим все файлы в специальном Git-only, замороженном / только для чтенияform;
  • индекс, который по сути является предложенным следующим коммитом и содержит копию всех файлов, также в форме Git-only, но не совсем замороженной;
  • рабочее дерево, в котором ваши файлы находятся в их обычной полезной форме.

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

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

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

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

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

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

Реальное слияние, то есть git merge, которое выполняет полное трехстороннее слияние и, в конце концов, сделает новый коммит типа merge commit - сделает свой новый коммит с помощью двое родителей. Эти два родителя являются двумя из трех входов, которые были использованы для вычисления результата слияния. Третий вход подразумевается двумя другими; это база слияния . 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, что next commit - будь то из git merge --continue или git commit - должно иметь двух родителей , Это делает новый коммит merge commit , который свяжет две ветви:

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

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

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

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

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

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

0 голосов
/ 03 января 2019

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

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

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

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

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

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

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

0 голосов
/ 03 января 2019

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

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

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

...