#git #git-revert
#git #git-revert
Вопрос:
Допустим, у меня есть только одна ветка, и история моих коммитов такова:
A — B — C — D
Если я нажимаю на C в графическом интерфейсе (например, GitKraken или Git для Windows) и делаю «отменить коммит», я получаю сообщение о конфликте файлов.
Два вопроса:
-
Это потому, что revert «отменяет» изменения, внесенные в C, и GIT теперь застрял с B и D, которые изменяют файл разными, несовместимыми способами? в этом причина?
-
Если да, означает ли это, что вы можете отменить только самый последний коммит? Можете ли вы когда-нибудь отменить коммит, отличный от самого последнего, без создания конфликта?
Обновить
Редактирую вопрос после очень полезных разъяснений от @RomainValeri. Я думаю, что я вносил слишком много путаницы между revert и reset. В приведенном ниже примере, выполняя
git revert B
приводит к переходу ветки из
A-B-C-D
Для
A-B-C-D-E
Если B был единственным коммитом, который изменился file2.txt и B не вносил никаких других изменений в другие файлы, тогда новый коммит E, созданный после возврата, сохранит все изменения, внесенные в C и D, но НЕ те, которые были сделаны в B. Это правильно?
Это потому, что технически возврат означает отмену любых изменений коммита — это правильно?
Также: предположим, что у меня есть только один файл в моем рабочем каталоге. Если B — единственный коммит, который изменяет функцию fun1() в моем файле, в то время как все остальные коммиты изменяют другие функции в том же файле, то возврат B, скорее всего, вызовет конфликт, потому что git мыслит в терминах строк в файле, а не в терминах функций внутри файла. Это правильно?
Итак, предположим, что B изменяет fun1 (), C изменяет fun2 () и D изменяет fun3 (). Затем я понимаю, что изменения, внесенные в B в fun1 (), неверны, и мне нужно их отменить. Если все эти функции находятся в одном файле, есть ли способ отменить изменения в B, сохранив изменения в C и D?
Если вместо этого каждая из этих 3 функций находится в отдельном файле, то гораздо проще отменить изменения одного коммита, не затрагивая остальные, верно?
Я полагаю, что это, конечно, одна из многих причин, почему один файл никогда не должен быть слишком большим или содержать слишком много функций, выполняющих разные функции, верно?
Комментарии:
1. Ну, вы всегда могли бы просто разрешить конфликт?
Ответ №1:
Короткий ответ: Да. Вы могли бы очень хорошо отменить данный коммит (даже старый) и не иметь конфликта.
Почему это так?
git revert
создает новый коммит поверх текущего кончика дерева, он не удаляет данный коммит и никак его не изменяет.
У вас возникает конфликт, когда что-то отличается в базе слияния между коммитом, который вы создаете с помощью revert и HEAD
.
Попробуйте сами с помощью этого
Тривиальный пример
Учитывая дерево репозитория,
Folder1
file1.txt
file2.txt
Folder2
file3.txt
и следуя истории,
A---B---C---D << HEAD
где
A
создает все файлы с их содержимым.
B
изменяет, file2.txt
C
модифицирует file1.txt
и file3.txt
D
далее модифицирует file1.txt
затем команда
git revert B
не создало бы никакого конфликта, просто новый коммит E
с новыми изменениями для file2.txt
.
A---B---C---D---E << HEAD
Содержимое нового коммита, созданного git revert
(добавлено после комментариев)
Изменения в E
являются точными обратными изменениями, которые B
были внесены.
С B
таким :
$ git show B
commit f3ba29d98c0998297126d686d03d33794cbc0f73
Author: Pythonista <some.name@some.isp>
Date: Sun Mar 24 20:41:49 2019 0100
Some change
diff --git a/javascript/functions.js b/javascript/functions.js
index 543c9cb..5f166d3 100644
--- a/javascript/functions.js
b/javascript/functions.js
@@ -3710,5 3710,6 @@ function dumpLists(isMixted) {
// end of process
createTrigger(window, 'load', START_EVERYTHING);
doStuff(param);
У вас был бы E
коммит, содержащий :
$ git show E
commit 2ff723970e81d64f3776c081a1f96242589774b6 (HEAD -> master)
Author: Pythonista <some.name@some.isp>
Date: Sun Mar 24 20:42:33 2019 0100
Revert "Some change"
This reverts commit f3ba29d98c0998297126d686d03d33794cbc0f73.
diff --git a/javascript/functions.js b/javascript/functions.js
index 5f166d3..543c9cb 100644
--- a/javascript/functions.js
b/javascript/functions.js
@@ -3710,6 3710,5 @@ function dumpLists(isMixted) {
// end of process
createTrigger(window, 'load', START_EVERYTHING);
-doStuff(param);
Обратите внимание на
или -
перед последней строкой diff, означающей «добавление» ( ) и «удаление» (-).
Наконец, сказать несколько слов о проблемах в конце вашего поста (которые я по некоторым причинам в основном пропустил) :
Я не думаю, что вы должны основывать выбор архитектуры для своего приложения на проблемах с управлением версиями. Git — довольно отличный инструмент, и он достаточно умен, чтобы предотвратить большую часть частых проблем, без которых мы неизбежно столкнулись бы. Выберите лучший способ сгруппировать ваш код по его собственной логике, независимо от git.
И, кстати, обнаружение конфликта происходит на уровне фрагмента, а не файла. Это означает, что один и тот же файл мог быть изменен двумя источниками без возникновения конфликта, если они происходят в разных несмежных частях. Я не мог бы точно сказать, где пороговое значение, но это не имеет отношения к делу.
Комментарии:
1. Позвольте мне посмотреть, правильно ли я понимаю: это конфликт, потому что, скажем, возвращая C, я возвращаюсь к B, который определяет x = y, в то время как D определяет x = z в той же строке кода. это пример? Аналогично, если все, что делает D, это добавляет еще одну строку кода в самом конце, например z = 1, это не создаст конфликт после того, как я вернусь, верно?
2. Я не уверен, что понимаю, что вы описываете. «возвращая C, я возвращаюсь к B» звучит очень неправильно, что я пропустил? Давайте не будем путать revert и reset здесь. Возврат
C
никуда не возвращается. Это просто создает новый коммит сверху. Мы согласны с этим?3. Я никогда не чувствовал себя таким глупым, как с тех пор, как пытался выучить git! Возврат не отменяет коммит? Я, мягко говоря, в замешательстве? Нет? Или возврат C создает новый коммит после D?
4.
Reset
устанавливает указатель перехода в другое место.Revert
создает новый коммит, содержащий точные изменения, обратные данному коммиту. Не злись так, ты не единственный, кто борется с этим, это не так просто понять с самого начала. Вы не кажетесь мне глупым человеком, это точно 🙂5. Итак, git revert B приводит к тому, что дерево переходит от A-B-C-D к A-B-C-D-B. Теперь все немного яснее. Спасибо! Жаль, что я не нашел этого простого однострочника среди буквально сотен страниц документации и руководств, которыми я был переполнен!
Ответ №2:
Почти все операции создания истории, отличные от прямой фиксации в Git, заканчиваются тем, что где-то задействовано слияние. Перебазирование, выбор вишни, возврат, всплывающее окно stash, слияние конечно, все они объединяются, просто при необходимости выбираются базы слияния. В checkout даже есть опция слияния, которая объединяет, используя вашу текущую проверку в качестве основы, новый коммит в качестве подсказки и ваше рабочее дерево / индекс в качестве другого подсказки, и сливается в новое рабочее дерево / индекс, что может быть приятно удобно.
Для возврата, скажем, A---B... * ... M master
и вас git revert B
, git просматривает изменения с B
на A
и изменения с B
на M
, это ваше слияние. Если в двух наборах изменений были затронуты смежные или перекрывающиеся строки, вероятность того, что кому-то понадобится исправить текст, резко возрастает, поэтому Git отказывается делать это молча. Тривиальные случаи в любом случае занимают секунды.
Комментарии:
1. По поводу
git checkout -m
(если я правильно понял, на что вы намекали в конце вашего первого абзаца), мне пришлось посмотреть его и еще ни разу им не пользовался, спасибо за совет 🙂