Добавить / зафиксировать файл во всех ветках - PullRequest
0 голосов
/ 30 августа 2018

Скажите, что я на ветке, а индекс грязный. Я внес изменения в файл x, и у меня есть и другие изменения.

Есть ли способ добавить файл x во все существующие ветки? Примерно так:

    #!/usr/bin/env bash
    current_branch="$(git rev-parse --abbrev-ref HEAD)"
    git add .
    git commit -am "added x"
    git fetch origin
    git for-each-ref --format='%(refname)' refs/heads  | while read b ; do
       git checkout "$b" 
       git checkout "$current_branch" -- x
    done
    git checkout "$current_branch";  # finally, check out the original branch again

так что в основном он проверяет все ветви, а затем проверяет файл x..так мне нужно зафиксировать в каждой ветви b или нет? почему нет?

Ответы [ 2 ]

0 голосов
/ 29 июля 2019
for remote in git branch -r; do git checkout  —track $remote ; <make your changes> ;  git add . ; git commit -a -m “change commit” ; git push origin $remote ; done
0 голосов
/ 30 августа 2018

так что в основном [мой цикл] проверяет все ветви, а затем проверяет файл x..так мне нужно фиксировать в каждой ветви b или нет? почему нет?

Ответ и нет, и да. Вы почти наверняка нуждаетесь в некоторых коммитах.

Помните, ветвь имя в действительности является указателем , указывающим на один конкретный коммит. Одно из этих имен имеет имя HEAD, прикрепленное к it, 1 , а все остальные имена указывают на некоторый коммит tip. Давайте нарисуем эти коммиты и имена. Допустим, есть четыре имени и три таких коммита с ветвлением:

  T  <-U  <-X   <--name1 (HEAD)
 /
...  <-V  <-Y   <--name2, name3
  \
   W  <-Z   <-- name4

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


1 Точнее, не более к одному имени прикреплено HEAD. Если HEAD является отсоединенным , это указывает непосредственно на некоторый коммит. В этом случае git rev-parse --abbrev-ref HEAD будет просто печатать HEAD. Часто бывает полезно проверить это, хотя, если вы делаете это по ходу работы и точно знаете, что у вас нет отдельного ГОЛОВКИ, в этом нет необходимости.


Ваши первые шаги:

current_branch="$(git rev-parse --abbrev-ref HEAD)"
git add .
git commit -am "added x"

Ваша текущая ветвь равна name1, что указывает на фиксацию X. Первая строка устанавливает current_branch в name1. Ваш текущий коммит является коммитом X: файлы этого коммита существуют в вашем индексе и в вашем рабочем дереве , потому что вы запустили git checkout name1 в какой-то момент в недавнем прошлом, который заполнил и индекс, и рабочее дерево из коммита X.

Вы используете git add . для копирования всех файлов в текущем каталоге или любом подкаталоге в индекс, 2 , чтобы они могли быть зафиксированы. Это включает в себя этот новый файл x, который вы только что создали в своем рабочем дереве. Ваш индекс готов к фиксации.


2 Точнее, он копирует в индекс из рабочего дерева все такие файлы, которые (а) уже есть в индексе или (б) не игнорируются. Часть (a) здесь подразумевается частью (b) - файл, который находится в индексе, по определению не игнорируется - но это стоит подчеркнуть.


Затем третья строка делает git commit. (Непонятно, почему вы используете git commit -a вместе с git add ., но -a добавит файлы в некоторые другие каталоги, если это необходимо. Вы могли бы также запустить git add --all, предполагая Git 2.0 или более позднюю версию, и пропустить -a.) Предполагая, что это успешно, 3 теперь есть новый дополнительный коммит после X, и name1 указывает на этот новый коммит, поэтому картинка теперь должна выглядеть следующим образом:

  T--U--X--α   <-- name1 (HEAD)
 /
...--V--Y   <-- name2, name3
  \
   W--Z   <-- name4

(у меня закончились латинские буквы, так что это коммит альфа.)


3 Все это время мы предполагаем, что все работает, или что если команда терпит неудачу, этот сбой хорош.


Кажется, ваша следующая команда в вашем скрипте не имеет здесь никакой функции:

git fetch origin

Это получит новые коммиты, которые есть у Git на origin, которых у вас нет, и обновит ваши origin/* имена для удаленного отслеживания, но вы не будете использовать имена для удаленного отслеживания после этой точки, поэтому зачем обновлять их на данный момент?

Проблемные части встречаются здесь, в цикле:

git for-each-ref --format='%(refname)' refs/heads  | while read b ; do
   git checkout "$b" 
   git checkout "$current_branch" -- x
   # proposed: git commit -m "some message"
done

Сначала вывод %(refname) будет читать refs/heads/name1, refs/heads/name2 и так далее. git checkout проверит каждый из них как отдельную ГОЛОВУ , что не то, что вы хотите. Это легко исправить с помощью %(refname:short), в котором пропущена часть refs/heads/.

В нашем гипотетическом примере вы получите имена name1, name2, name3 и name4. Итак, вы начнете с того, что попросите Git снова извлечь commit α - который идет очень быстро, так как он уже там - и затем использовать имя name1 для извлечения файла x в индекс и рабочее дерево. Те тоже уже есть.

ThПредложение добавить git commit. Этот конкретный git commit будет завершаться с ошибкой с ошибкой, говорящей о том, что нечего коммитить, что в данном конкретном случае, вероятно, то, что вам нужно: уже есть файл x с правильным содержанием в подсказке коммит ветки name1, т.е. в коммите α.

Затем цикл переходит на git checkout name2, то есть фиксирует Y. Это заменит ваш индекс и содержимое рабочего дерева теми, что извлечены из commit Y, и присоединит HEAD к имени name2. Строка git checkout name1 -- x извлечет файл x из коммита α в индекс и рабочее дерево, а предложенный git commit сделает новый коммит и, следовательно, заставит имя name2 двигаться вперед, указывая на это новый коммит. Обратите внимание, что имя name3 продолжает указывать на коммит Y. Давайте нарисуем новый коммит, который мы можем назвать β (бета):

    U--X--α   <-- name1 (HEAD)
   /
  T       β    <-- name2
 /       /
...--V--Y   <-- name3
  \
   W--Z   <-- name4

Теперь ваш цикл переходит на name3, который все еще указывает на фиксацию Y, поэтому Git вернет индекс и рабочее дерево к тому, что они были минуту назад, когда вы проверяли коммит Y через имя name2. Git теперь извлечет файл x из коммита α, как и раньше, и сделает еще один новый коммит.

Здесь все становится очень интересно! Новый коммит имеет то же дерево , что и коммит β. Он также имеет того же автора и коммиттера. В зависимости от того, как вы создаете ваше сообщение -m, оно может иметь то же сообщение журнала , что и commit β. Если Git делает этот коммит в ту же метку времени в секунду , которую он использовал при совершении коммита β, новый коммит фактически равен существующим коммитом β и все в порядке.

С другой стороны, если Git требуется достаточно времени, чтобы новый коммит получил другую метку времени, новый коммит отличается от коммита β. Давайте предположим, что это происходит, и мы получаем commit γ (гамма):

    U--X--α   <-- name1 (HEAD)
   /
  T       β    <-- name2
 /       /
...--V--Y--γ   <-- name3
  \
   W--Z   <-- name4

Наконец, цикл будет повторять этот процесс еще раз для name4, который в настоящее время указывает на фиксацию Z, но в итоге будет указывать на новый коммит δ (delta):

    U--X--α   <-- name1 (HEAD)
   /
  T       β    <-- name2
 /       /
...--V--Y--γ   <-- name3
  \
   W--Z--δ   <-- name4

Общие проблемы

Здесь возникает одна проблема, когда несколько имен указывают на один и тот же базовый коммит. В этом случае вы должны решить, хотите ли вы откорректировать все имен таким же образом - то есть, чтобы name2 и name3 оба продвинулись, чтобы указать на фиксацию β - или вы не хотите этого:

  • Если вы делаете этого хотите, вы должны убедиться, что вы используете какую-либо операцию обновления имени ветви (git branch -f, git merge --ff-only и т. Д.) обновить все имена, которые указывают на конкретную фиксацию. В противном случае вы рассчитываете на то, что все коммиты будут выполнены в течение одной секунды, поэтому все метки времени будут совпадать.

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

Если вы уверены, что все ваши имена указывают на разные коммиты, эта проблема исчезнет.

Другие вещи, о которых стоит подумать:

  • Указывает ли какое-либо из имен какой-либо существующий коммит, что имеет файл с именем x? Если это так, вы перезапишете this x из x, который мы извлекаем из коммита α (первый коммит, который мы делаем в текущей ветви, в начале всего процесса.)

  • Если у каких-либо имен do есть x - кто-то, безусловно, считает, что, будучи той ветвью, на которой мы были в первую очередь, - тогда это x соответствует тот, что в коммите α? Если это так, предложенный git commit потерпит неудачу, если мы не добавим --allow-empty. Но в нашем конкретном случае здесь это, вероятно, хорошая вещь, поскольку это означает, что мы можем избежать специального случая для проверки соответствия $b 1256 *.

  • У вас на самом деле есть имена ветвей для всех ... ну, не понятно, как называть эти вещи. Смотрите Что именно мы подразумеваем под "ветвью"? для деталей. Назовем их линиями развития . У вас есть (локальное) название ветви для каждой такой строки? Это может быть причиной того, что у вас есть git fetch origin здесь: чтобы вы могли накапливать обновления для всех ваших origin/* имен удаленного отслеживания.

    Если у вас есть origin/feature1 и origin/feature2, на этом этапе вы можете создать (локальные) имена ветвей feature1 и feature2, чтобы добавить файл x до кончика коммитов этих двух веток. Вам понадобятся имена веток, чтобы запомнить вновь созданные коммиты. Но простое использование git for-each-ref сверх refs/heads не приведет к желаемому результату: вы можете использовать git for-each-ref сверх refs/remotes/origin, накапливать имена минус часть origin/ в наборе вместе со всеми именами refs/heads (минус refs/heads/ части конечно), и используйте эти в качестве имен ветвей.

...