Rebase git субмодуль и родительское репо - PullRequest
3 голосов
/ 29 февраля 2020

Моя ситуация: у вас есть ветвь функций, основанная на master, и кто-то обязуется master. Теперь ваша история выглядит следующим образом:

A - B - C - D (master)
         \
           E - F - G  (feature)

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

A - B - C - D (submodule:master)
         \
           E - F - G  (submodule:feature)
           *    *
           *     *
           X - Y - Z             (parent:feature)
              (asterisks represent references to submodule)

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

Есть ли способ сделать это и сохранить эти ссылки? (обе ветки 'feature' могут быть свободно переписаны).

Ответы [ 3 ]

3 голосов
/ 02 марта 2020

ссылки родителей на коммиты субмодуля будут недействительными.

Эти ссылки становятся недействительными только в том случае, если они были утеряны ребазом.

Все, что вам нужно сделать, это добавить ветку old_feature, где feature, перед функция перебазирования.
Не забудьте pu sh ветвь old_feature.

Затем, когда feature будет перебазирован и нажат, вы go для родительского репозитория убедитесь, что его подмодуль следует за веткой feature, и выполните дистанционное обновление :

git submodule update --remote

Коммит X и Z может сохранить свои старые ссылки на ветку old_feature, в то время как новый коммит сохранит ссылку на перебазированную ветку *1030* feature подмодуля.


As jthill добавляет в комментарии , reflog все еще там локально, в течение 90 дней, если вы уже перебазировали, не ссылаясь на старое состояние feature first:

у вас есть месяц, чтобы исправить упс в этом репо, git branch old-feature feature@{last.monday}

Затем pu sh в этой old-feature ветви, для того, чтобы ма Убедитесь, что ссылки на E и F сохраняются в удаленном репозитории, на коммиты ссылаются X и Z.

1 голос
/ 08 марта 2020
A - B - C - D (submodule:master)
         \
           E - F - G  (submodule:feature)
           *    *
           *     *
           X - Y - Z             (parent:feature)
              (asterisks represent references to submodule)

Чтобы перебазировать подмодуль (например, функцию на мастер) и обновить gitlinks родителя:

  1. Создать другую ветку в подсказке перебазировки подмодуля (например, 'old_feature' в 'feature').

  2. Выполните ребазинг в подмодуле.

  3. Обратите внимание, что вы можете изобразить отображение между старым субмодуль фиксирует (E -> G) и новый (E '-> G') и их идентификаторы. Они понадобятся вам позже.

  4. Вы также должны знать диапазон коммитов, которые нужно изменить в родительском репо (X -> Z), и которые указывают c коммиты, которые содержат ссылки на ссылки, которые нуждается в обновлении.

  5. Сделайте интерактивную ребаз в родительском при указанных коммитах. Вставьте команду 'break' после каждого, который должен быть изменен.

  6. Как git каждый раз падает в оболочку:

    1. Для старая ссылка gitlink текущего родительского коммита, извлеките в подмодуле соответствующий новый коммит.

    2. В родительском коммите подмодуль и git commit --amend. Это обновляет gitlink.

    3. Продолжить перебазирование. Если есть конфликты (их должно быть много), предпочтите те, у которых есть gitlinks, для коммитов old субмодуля, так как эти конфликты возникают до того, как мы их обновим. (когда я сделал это, это была опция «использовать удаленные изменения» в локальном и удаленном в git mergetool).

  7. Готово


Он довольно сложный (и потенциально медленный, если у вас много), и вопрос в том, считаете ли вы, что оно того стоит.

Более простой способ, за @ NicolasVoron

  1. Класси c, чистый способ: выполнить слияние вместо перебазировки

Выполнить слияние. E, F, G будут связаны с коммитом слияния. Таким образом, X, Y, Z будут оставаться навсегда и без изменений, даже если ветвь удалена (в этом случае будет использоваться только ссылка на ветвь. Но за счет нелинейной истории на ведущем устройстве). Когда вы будете pu sh родительская ветвь, X, Y, Z тоже будут выталкиваться.

1 голос
/ 02 марта 2020

Я часто сталкиваюсь с такой ситуацией в моей компании, и мы нашли 2 основных решения этой проблемы. Оба они используют способность git сохранять коммиты в истории в определенных ситуациях. Кстати, хорошей практикой является никогда не указывать ссылки на подмодули sh, которые указывают на удаляемые ветви (т.е. ветви функций).

Я предполагаю, что, как и я, вы не хотите использовать pu sh feature_branch, чтобы не загрязнять ваш удаленный репозиторий и быть устойчивыми к рабочим партнерам в режиме инквизитора, которые очищают все старые отправленные ветви функций

Проблема в том, что когда вы делаете ребаз, коммиты переигрываются поверх истории мастера. Это новые коммиты (скажем, X ', Y' и Z '), с новым ha sh, а голова ветки перемещается на вершину Z'. Таким образом, X, Y, Z останутся на мертвой ветке и будут удалены со временем (90 дней с настройками по умолчанию). В дополнение к этому, они не будут выдвигаться по умолчанию, когда вы сделаете sh вашу перебазированную ветвь.

  1. Classi c, чистый способ: сделайте слияние вместо ребазирования

Сделайте слияние. E, F, G будут связаны с коммитом слияния. Таким образом, X, Y, Z будут оставаться навсегда и без изменений, даже если ветвь удалена (в этом случае будет использоваться только ссылка на ветвь. Но за счет нелинейной истории на ведущем устройстве). Когда у вас будет pu sh родительская ветвь, X, Y, Z тоже будут выталкиваться.

Сохранять коммиты в истории с тегами

Одно из решений для сохранения коммитов X, Y, Z в истории - это тег Z (до перебазирования (тег доживет до него). ) или после (но вам придется заново найти его в истории с помощью reflog), это не имеет значения). Если этот тег затем нажать, X, Y, Z не будут очищены. Это довольно грязно и имеет побочные эффекты, но делает работу. Конечно, вам нужно будет сделать этот тег sh! (если честно, это довольно эквивалентно pu sh ветке. Разница в том, что вы можете назвать его конкретно, например, DONT_DELETE_THIS_FOOL;))

Вот небольшой скрипт, который вы можете запустить, чтобы что любая подмодульная зависимость не будет потеряна (пометив каждый подмодуль и pu sh их) (используйте -h для получения справки):

#!/bin/bash

check_tags () {
    slength=${#1}
    slength=$((slength+1))

    refmajor=`echo $2`
    refminor=`echo $3`
    refrevision=`echo $4`

    echo "Check for anterior tags in $5..."
    for tag in $(git tag -l -n "$1*.*.*" | cut -d" " -f1 | cut -c "$slength"- ); do
        temptagMajor=`echo $tag | cut -d. -f1`
        temptagMinor=`echo $tag | cut -d. -f2`
        temptagRevision=`echo $tag | cut -d. -f3`

        if [[ temptagMajor -gt tagMajor ]] || [[ temptagMajor -eq tagMajor && temptagMinor -gt tagMinor ]] || [[ temptagMajor -eq tagMajor && temptagMinor -eq tagMinor && temptagRevision -gt tagRevision ]]; then
            tagMajor=$temptagMajor
            tagMinor=$temptagMinor
            tagRevision=$temptagRevision
        fi
    done

    echo "Latest versioning tag found : $1$tagMajor.$tagMinor.$tagRevision (want to apply $1$refmajor.$refminor.$refrevision)"

    if [[ tagMajor -gt refmajor ]]; then
        echo "Cannot tag with $1$refmajor.$refminor.$refrevision : anterior tag with greater major revision number (found $tagMajor, wanted $refmajor). Use -f to ignore."
        return 1
    elif [[ tagMajor -eq refmajor ]] && [[ tagMinor -gt refminor ]]; then
        echo "Cannot tag with $1$refmajor.$refminor.$refrevision : anterior tag with equal major revision number but greater minor revision number (found $tagMinor, wanted $refminor). Use -f to ignore."
        return 1
    elif [[ tagMajor -eq refmajor ]] && [[ tagMinor -eq refminor ]] && [[ tagRevision -gt refrevision ]]; then
        echo "Cannot tag with $1$refmajor.$refminor.$refrevision : anterior tag with equal major and minor revision number, but greater revision number (found $tagRevision, wanted $refrevision). Use -f to ignore."
        return 1
    elif [[ tagMajor -eq refmajor ]] && [[ tagMinor -eq refminor ]] && [[ tagRevision -eq refrevision ]]; then
        echo "Cannot tag with $1$refmajor.$refminor.$refrevision : anterior tag with equal major, minor, and revision number (found $1$tagMajor.$tagMinor.$tagRevision, wanted $1$refmajor.$refminor.$refrevision). Use -f to ignore."      
        return 1
    else
        return 0
    fi
}

function subcheck() {  

  if [[ ! -d .git ]]; then
        echo "not a git repo"
        return 1
    fi;

    if ! [[ `git submodule status` ]]; then
        echo 'no submodule'
        return 1
    fi

    submodules=($(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'))

    currentDirectory=$(pwd)

    for submodule in "${submodules[@]}"
    do
        printf "\n\nEntering '$submodule'\n"
        cd "$currentDirectory/$submodule"

        check_tags $1 $2 $3 $4 $submodule

        if [[ $? -eq 1 ]]; then
            cd "$currentDirectory"
            return 1
        fi

    done

  cd "$currentDirectory"
}

#export -f check_tags

# Check arguments
while getopts v:hfti: option
do
case "${option}"
in
v) VERSION=${OPTARG};;
h) HELP='help';;
f) FORCE='force';;
t) TEST='test';;
i) INDEX=${OPTARG};;
esac
done

printf "Tag release script v0.1\n"

# Help
if [ "$HELP" != "" ]; then 
  echo 'GIT Release Script'
  echo "Options :"
  echo 'Use -v to specify version (mandatory). Ex : "-v 1.0.2"' 
  echo 'Use -t to run unit test of -v inputs' 
  echo 'Use -f to force tagging / skip anterior tag versions check' 
  echo 'Use -i to specify index (optional). Ex : "-v 1.0.2 -i A" for a indA + v1.0.2 double tag.'
  exit 1 
fi

# Tests for bad inputs
if [ "$HELP" != "" ]; then 
    array=( ".2.3" "1..3" "1.2." "A.2.3" "1.A.3" "1.2.A" "1A3.123.123" "123.1D3.123" "123.123.1A3" "nougatine" "1.3" )

    arr=(${array[*]})
    echo "Tested valudes : ${#arr[*]}"
    for ix in ${!arr[*]}
    do
        printf "   %s\n" "${arr[$ix]}"
        . release_script.sh -v ${arr[$ix]}
    done
fi

# Version
if [ "$VERSION" == "" ]; then 
  echo "Argument missing"
  echo "Run -h for help"
  exit 1 
fi

major=`echo $VERSION | cut -d. -f1`
minor=`echo $VERSION | cut -d. -f2`
revision=`echo $VERSION | cut -d. -f3`

if [ -n "$(printf '%s\n' "$major" | sed 's/[0-9]//g')" ] || [ "$major" == "" ]; then 
  echo "Invalid major version argument (was \"$major\")"
  echo "Run -h for help"
  exit 1 
fi

if [ -n "$(printf '%s\n' "$minor" | sed 's/[0-9]//g')" ] || [ "$minor" == "" ] ; then 
  echo "Invalid minor version argument (was \"$minor\")"
  echo "Run -h for help"
  exit 1 
fi

if [ -n "$(printf '%s\n' "$revision" | sed 's/[0-9]//g')" ] || [ "$revision" == "" ]; then 
  echo "Invalid revision version argument (was \"$revision\")"
  echo "Run -h for help"
  exit 1 
fi

# Fetching
git fetch --all
echo "Fetching tags"
git fetch --tag

tagMajor=0
tagMinor=0
tagRevision=0

versionLabel=v$VERSION
url=$(git config --get remote.origin.url)
basename=$(basename "$url" .git)

# Check previous available versions if no -f specified
if [ "$FORCE" == "" ]; then 

    if [ "$INDEX" != "" ]; then
        echo "Check for anterior index tag in $basename..."

        for tag in $(git tag -l -n "ind$INDEX" ); do

            if [[ "ind$INDEX" == $tag ]] ; then
                echo "\"ind$INDEX\" tag already exists in $basename. Use -f to ignore."
                exit 1
            fi
        done

        echo "No conflict found for "ind$INDEX" index tag"
    fi

    check_tags v "$major" "$minor" "$revision" "$basename"

    if [[ $? -eq 1 ]]; then
        #echo "Error found, won't tag"
        exit 1
    fi  

    subcheck ${basename}_v $major $minor $revision

    if [[ $? -eq 1 ]]; then
        exit 1
    fi
fi

# Release tag script
versionLabel=v$VERSION
url=$(git config --get remote.origin.url)
basename=$(basename "$url" .git)
echo "Tagging project $basename (\"$versionLabel\")"
git tag $versionLabel

#TODO : check index
if [ "$INDEX" != "" ]; then
    echo "Tagging index $INDEX"
    git tag "ind$INDEX"
fi
echo "Tagging submodules (\"${basename}_$versionLabel\")"
git submodule foreach "git tag ${basename}_$versionLabel || :"
echo "Pushing project tag"
git push --tags
echo "Pushing submodules tags"
git submodule foreach 'git push --tags || :'
...