Вопрос в том, как предотвратить перезапись JenkinsFile при любом слиянии? Я хочу, чтобы JenkinsFile оставался нетронутым и не подвергался никакому слиянию. Есть ли способ «заблокировать» эти файлы?
номер
Существует совершенно другой способ решения этой проблемы, который обходит всю проблему. На самом деле, есть несколько способов, но я покажу только один. Существует неприятная проблема с точки зрения получения от до состояния, в котором все работает так, как нужно, но как только вы доберетесь, у вас все хорошо. Конечная цель здесь состоит в том, чтобы , а не , иметь фиксированный файл с именем Jenkinsfile
(или JenkinsFile
, но я использовал нижнее регистр-F ниже), содержимое которого зависит от ветви. Вместо этого просто получите незафиксированный файл только для рабочего дерева, имя которого Jenkins[Ff]ile
и содержимое которого зависит от ветви. Сделайте так, чтобы commit файлы имели другие имена.
Фон
По сути, git merge
работает с помощью , объединяя проделанную работу , то есть объединяя изменения в некоторых файлах с некоторой общей начальной точки. Но Git не хранит изменений; Git хранит снимки . Это создает проблему для git merge
, и решение требует, чтобы вы понимали, как работает граф коммитов Git .
Почти каждый коммит в репозитории Git имеет по крайней мере один родительский коммит, который является непосредственным предшественником этого коммита. У большинства точно один родитель; коммитов типа «слияние» имеют как минимум два, а обычно ровно два. Фактически, наличие более чем одного родителя - это то, что определяет коммит как коммит слияния. Другой распространенный особый случай - это то, что самый первый коммит в репозитории имеет нет родителя, потому что он не может иметь его, потому что это был первый коммит. (Комитеты с тремя или более родителями называются слияниями осьминога , но они не делают ничего, что вы не можете делать с обычными слияниями, поэтому они в основном используются для демонстрации. :-))
Эти ссылки, в которых коммит сохраняет хэш-идентификатор своего родителя (ей) - помните, что каждый коммит находится по уникальному хеш-идентификатору, который Git назначил для коммита, когда вы сделали коммит - образуют обратные цепочки. Эти обратные цепи являются историей в хранилище. История совершает; коммиты это история. Имя ветви просто идентифицирует (единственный) последний коммит, который мы хотим объявить частью этой ветви:
... <-F <-G <-H <--master
Здесь, вместо реальных хеш-идентификаторов, я нарисовал заглавными буквами, обозначающими каждый коммит. Имя master
содержит фактический хэш-идентификатор коммита H
. Мы говорим, что master
указывает на H
. H
содержит идентификатор хеша родительского элемента G
, поэтому H
указывает на G
, что указывает на F
и т. Д., Обратно вниз по линии.
Ничто внутри любого коммита не может измениться, поэтому нам не нужны внутренние стрелки, мы просто должны помнить, что они идут назад. На самом деле в Git очень трудно идти вперед: почти все операции начинаются в конце (ах) и работают в обратном направлении. Если у нас есть более одной ветви, это дает изображение, которое выглядит так:
G--H <-- master
/
...--E--F
\
I--J <-- develop
\
K <-- test
К git checkout
ветвь означает * извлечь снимок из коммита наконечника этой ветви . So
мастер проверки git extracts the snapshot from commit
H , while
разработка git checkout or
проверка проверки git extracts those snapshots in turn. Also, doing a
проверка git of some branch name attaches the special name
HEAD` в эту ветку. Вот как Git знает , какая ветвь и коммит - это текущая единица.
Когда вы запускаете git merge
, вы даете Git имя какого-то другого коммита. Это не обязательно должно быть имя ветки - любое имя для коммита будет служить - но присвоение ему имени ветки работает нормально, так как оно называет коммит наконечника этой ветви. Поэтому, если вы git checkout master
и затем запустите git merge develop
, вы начнете с:
G--H <-- master (HEAD)
/
...--E--F
\
I--J <-- develop
\
K <-- test
и Git находит коммит J
. Затем Git работает в обратном направлении как от текущего коммита H
, так и от названного коммита J
, чтобы найти базу слияния этих двух коммитов.
ЯОснова rge - это первый коммит, который мы получаем из обоих советов. Это коммит в обеих ветвях, и в этом случае это, очевидно, коммит F
. Идея создания базы слияния имеет решающее значение для понимания того, как работает слияние. Поскольку целью слияния является объединение работы , и эту работу можно найти, сравнив снимок в коммите F
, одно сравнение за раз, каждому из двух коммитов подсказок H
и J
:
git diff --find-renames <hash-of-F> <hash-of-H> # what we changed
git diff --find-renames <hash-of-F> <hash-of-J> # what they changed
Чтобы объединить изменения, Git начинает со всех файлов из F
и смотрит, какие файлы мы изменили и какие они изменили. Если мы оба изменили разные файлы, Git примет наши или их файлы соответствующим образом. Если мы оба изменили один и тот же файл - , это в конечном итоге поднимает философскую проблему , к которой мы вскоре вернемся - Git пытается разбить наши изменения вместе с их изменениями, если предположить, что если мы коснулись какой-либо строки источника, а они этого не сделали, то это займет нашу, а если они коснулись какой-то строки источника, а мы нет, то и их тоже. Если мы оба коснулись одинаковых строк одного и того же файла, то либо мы проделали ту же самую вещь до этих строк - в этом случае Git берет одна копия этого изменения - или есть конфликт.
Если нет конфликтов, Git применяет эти объединенные изменения к снимку в базе слияния - здесь F
и использует полученные файлы для записи нового снимка. Этот новый снимок является коммитом типа merge commit , имеющим двух родителей. Первый родитель - это коммит, на котором мы были раньше, H
, а второй - тот, который мы назвали с помощью нашего аргумента J
, поэтому слияние выглядит так:
G--H
/ \
...--E--F L <-- master (HEAD)
\ /
I--J <-- develop
\
K <-- test
Обратите внимание, что ничего не происходит ни с существующим коммитом, ни с любым другим именем ветки. Только наше собственное имя ветви, master
(к которому прикреплен HEAD
), перемещается; master
теперь указывает на новый коммит слияния, который только что сделал Git.
Если слияние происходит плохо из-за конфликтов слияния, Git оставит беспорядок. index , в который я не буду здесь вдаваться, будет содержать все конфликтующие входные файлы, а рабочее дерево будет содержать попытку Git слияния, а также маркеры конфликта. Ваша задача - навести порядок, исправить индекс и завершить объединение (с помощью git merge --continue
или git commit
- --continue
просто запускает commit
) вручную.
Ваша проблема: Jenkinsfile
Предположим, что в коммите F
, базе слияния, есть файл с именем Jenkinsfile
. Этот же файл с тем же именем появляется в коммитах H
и J
. Копии в H
и J
различаются - вы сказали, что они делают, поэтому мы предположим, что они делают. Следовательно, хотя бы один отличается от F
и, возможно, оба отличаются от F
.
Git будет предполагать, что файл с именем Jenkinsfile
в обеих подсказках ветки является тем же файлом с именем Jenkinsfile
в F
. Очевидно, что это не совсем один и тот же файл - его содержимое отличается - но Git примет , что это так, и что вы пытаетесь объединить проделанную работу над ним.
Итак, Git будет отличать версию Jenkinsfile
в F
от версии в H
, а затем снова дифференцировать с версией в J
. Там будет некоторые изменения. Если в подсказках обеих веток есть изменения, Git объединит их (или объявит конфликт). Результат: плохо . В противном случае Git примет версию файла с какой бы «стороны» она не менялась. Это та сторона, которую вы хотите? Если это так, результат: хорошо . Если нет, результат: плохо .
В итоге для этого сценария возможны три результата:
- База против HEAD - единственное изменение: результат в порядке.
- База против их единственное изменение: результат плохой.
- База против HEAD и база против их обоих имеют изменения: результат, вероятно, плохой.
Это конечноВозможно, базовый коммит слияния F
имеет нет файл с именем Jenkinsfile
. И, возможно, что один или оба commit не имеют такого файла. В этом случае становится немного сложнее. Мы вернемся к этому через мгновение.
Решение (и некоторые проблемы, возникающие там)
Решение здесь состоит в том, чтобы избежать использования одного файла с фиксированным именем, такого как Jenkinsfile
, во всех коммитах, когда этот файл предназначен для зависимости от ветви. Предположим, вместо этого, что коммит F
содержит Jenkinsfile.master
и Jenkinsfile.develop
и Jenkinsfile.test
. Тогда коммит H
будет иметь также Jenkinsfile.master
и Jenkinsfile.develop
и Jenkinsfile.test
, а изменит с F
на H
в Jenkinsfile.master
и будет тем, который вы хотите сохранить , Поскольку commit J
находится в ветке develop
, он всегда должен иметь либо одинаковые изменения - импортированные из master
в какой-то момент, либо без изменений . Следовательно, слияние Git сделает правильную вещь в обоих случаях.
Та же логика применяется к каждому из других таких файлов. Обратите внимание, что в этот момент коммиты, идентифицированные всеми подсказками ветвей, должны иметь файл no с именем Jenkinsfile
(без суффикса) вообще. Это, конечно, идеализированное целевое состояние: чтобы попасть туда, вы должны фактически сделать новые коммиты в каждой ветви, переименовав существующий Jenkinsfile
. Но это не будет иметь никакого эффекта на любых существующих коммитах. Вся эта история в вашем хранилище заморожена на все времена. Это означает, что в какой-то момент вы запустите git merge
и git merge
найдет базовый коммит слияния, который имеет только Jenkinsfile
, а не Jenkinsfile.master
, а не Jenkinsfile.develop
или любой другой суффикс.
Давайте теперь предположим, что в H
и J
вы уже сделали это переименование, но в базе слияния F
вы этого не сделали - очевидно, поскольку это исторический коммит. Таким образом, F
имеет Jenkinsfile
и не имеет переименованных файлов, в то время как H
и J
имеют нет Jenkinsfile
, но имеют переименованные файлы.
Теперь запомните выше, где мы показали git diff
s, которые запускает git merge
, чтобы выяснить, что изменилось со времени базы слияния. Одним из аргументов является --find-renames
. Это указывает Git предположить , является ли файл Jenkinsfile
в F
"тем же" файлом, что и Jenkinsfile.master
в H
, при сравнении F
и H
. То же самое касается сравнения F
против J
: старый Jenkinsfile
совпадает с новым Jenkinsfile.develop
?
Если вы перейдете по ссылке на https://en.wikipedia.org/wiki/Ship_of_Theseus, вы увидите, что нет философского правильного ответа на вопрос об идентичности во времени. Но у Git есть правильный ответ , а именно: Если файл имеет индекс сходства 50% или лучше, это тот же файл. Здесь не нужно беспокоиться о том, как Git вычисляет этот индекс сходства (это немного сложно); очень вероятно, что Git обнаружит переименование в обоих случаях.
На практике это означает , что первый первый раз, когда вы запускаете этот git merge
, Git немедленно объявит конфликт слияния типа, который мне нравится называть высокий уровень конфликт. То есть Git скажет, что Jenkinsfile
было переименовано в обе ветви, но с двумя разными именами. Git не знает, использовать ли мастер-версию, или версию для разработки, или обе, или ни то, ни другое. Это просто остановится конфликтом слияний. Это нормально , потому что это дает вам возможность разрешить конфликт, что вы должны сделать, выбрав файл Jenkinsfile.master
, как он появляется в ветке master
или --ours
, и выбирая файл Jenkinsfile.develop
в том виде, в каком он появляется в ветви develop
или --theirs
, в качестве результатов объединения. Поместите эти два файла в указатель, удаляя исходное имя:
git rm --cached Jenkinsfile
git checkout --ours Jenkinsfile.master
git checkout --theirs Jenkinsfile.develop
git add Jenkinsfile.master Jenkinsfile.develop
Теперь вы решили конфликт, решив сохранить оба файла в том виде, в каком они отображаются в подсказках обеих ветвей. Теперь вы можете зафиксировать результат.
Каждый раз, когда вы выполняете объединение, которое использует одну из исторических однократных Jenkinsfile
фиксаций, вам нужно будет проверить правильность результата объединения или разрешить любые конфликты. (Если это не правильно, сразу после слияния вы можете исправить это на месте и использовать git commit --amend
, чтобы отодвинуть исходное слияние в сторону и выбрать новый результат в качестве коммита слияния. Если вы не заметите плохое слияние, это немного более болезненным, но восстановление в конце концов аналогично. Вспомните, как Git выполняет слияния, и проработайте два git diff
s, чтобы увидеть, как правильное использование результата в any commit коммит приведет вас туда, куда вы надо идти.)
Последнее, теперь нет Jenkinsfile
Теперь, когда нет файла с именем Jenkinsfile
, вам придется перенаправить любое программное обеспечение, которое хочет использовать такой файл. Существует несколько решений (в зависимости от программного обеспечения и вашей ОС), в том числе создание символической ссылки с Jenkinsfile
до правильной проверки для каждой ветви. (Убедитесь, что символическая ссылка не зафиксирована, или вы сразу же вернетесь к той же проблеме слияния, когда Git попытается объединить два потенциальных изменения цели символической ссылки.)