为了账号安全,请及时绑定邮箱和手机立即绑定

重新安置分支机构,包括其所有子级

重新安置分支机构,包括其所有子级

Git
肥皂起泡泡 2019-10-06 11:09:28
我有以下Git存储库拓扑:A-B-F (master)   \   D (feature-a)    \ /     C (feature)      \       E (feature-b)通过重新feature分支,我希望重新构建整个子树(包括子分支):$ git rebase feature masterA-B-F (master)     \   D (feature-a)      \ /       C (feature)        \         E (feature-b)但是,这是实际结果:      C' (feature)     /A-B-F (master)   \   D (feature-a)    \ /     C      \       E (feature-b)我知道我可以通过执行以下操作轻松地手动修复它:$ git rebase --onto feature C feature-a$ git rebase --onto feature C feature-b但是,有没有一种方法可以自动为分支(包括其所有子代/后代)建立基础?
查看完整描述

3 回答

?
红颜莎娜

TA贡献1842条经验 获得超12个赞

几年前,我写了一些东西来处理这种事情。(当然,欢迎提出改进意见,但不要过多地评判-这是很久以前的事了!我什至还不了解Perl!)


它用于更多静态情况-您可以通过设置表单的config参数来配置它branch.<branch>.autorebaseparent。它不会碰到任何没有设置配置参数的分支。如果这不是您想要的,则可以轻松地将其入侵到所需位置。在过去的一两年中,我并没有真正使用过它,但是当我使用它时,它似乎总是非常安全和稳定的,因为大规模自动重新定级是可能的。


就是这样 通过保存成一个名为使用它git-auto-rebase在你的PATH。-n尝试使用空运行()选项也是一个好主意。它可能比您真正想要的要详细一些,但是它将向您显示将要尝试重新建立基础的内容以及要建立的内容。可能会节省您一些悲伤。


#!/bin/bash


CACHE_DIR=.git/auto-rebase

TODO=$CACHE_DIR/todo

TODO_BACKUP=$CACHE_DIR/todo.backup

COMPLETED=$CACHE_DIR/completed

ORIGINAL_BRANCH=$CACHE_DIR/original_branch

REF_NAMESPACE=refs/pre-auto-rebase


print_help() {

    echo "Usage:  git auto-rebase [opts]"

    echo "Options:"

    echo "    -n   dry run"

    echo "    -c   continue previous auto-rebase"

    echo "    -a   abort previous auto-rebase"

    echo "         (leaves completed rebases intact)"

}


cleanup_autorebase() {

    rm -rf $CACHE_DIR

    if [ -n "$dry_run" ]; then

        # The dry run should do nothing here. It doesn't create refs, and won't

        # run unless auto-rebase is empty. Leave this here to catch programming

        # errors, and for possible future -f option.

        git for-each-ref --format="%(refname)" $REF_NAMESPACE |

        while read ref; do

            echo git update-ref -d $ref

        done

    else

        git for-each-ref --format="%(refname)" $REF_NAMESPACE |

        while read ref; do

            git update-ref -d $ref

        done

    fi

}


# Get the rebase relationships from branch.*.autorebaseparent

get_config_relationships() {

    mkdir -p .git/auto-rebase

    # We cannot simply read the indicated parents and blindly follow their

    # instructions; they must form a directed acyclic graph (like git!) which

    # furthermore has no sources with two sinks (i.e. a branch may not be

    # rebased onto two others).

    # 

    # The awk script checks for cycles and double-parents, then sorts first by

    # depth of hierarchy (how many parents it takes to get to a top-level

    # parent), then by parent name. This means that all rebasing onto a given

    # parent happens in a row - convenient for removal of cached refs.

    IFS=$'\n'

    git config --get-regexp 'branch\..+\.autorebaseparent' | \

    awk '{

        child=$1

        sub("^branch[.]","",child)

        sub("[.]autorebaseparent$","",child)

        if (parent[child] != 0) {

            print "Error: branch "child" has more than one parent specified."

            error=1

            exit 1

        }

        parent[child]=$2

    }

    END {

        if ( error != 0 )

            exit error

        # check for cycles

        for (child in parent) {

            delete cache

            depth=0

            cache[child]=1

            cur=child

            while ( parent[cur] != 0 ) {

                depth++

                cur=parent[cur]

                if ( cache[cur] != 0 ) {

                    print "Error: cycle in branch."child".autorebaseparent hierarchy detected"

                    exit 1

                } else {

                    cache[cur]=1

                }

            }

            depths[child]=depth" "parent[child]" "child

        }

        n=asort(depths, children)

        for (i=1; i<=n; i++) {

            sub(".* ","",children[i])

        }

        for (i=1; i<=n; i++) {

            if (parent[children[i]] != 0)

                print parent[children[i]],children[i]

        }

    }' > $TODO


    # Check for any errors. If the awk script's good, this should really check

    # exit codes.

    if grep -q '^Error:' $TODO; then

        cat $TODO

        rm -rf $CACHE_DIR

        exit 1

    fi


    cp $TODO $TODO_BACKUP

}


# Get relationships from config, or if continuing, verify validity of cache

get_relationships() {

    if [ -n "$continue" ]; then

        if [ ! -d $CACHE_DIR ]; then

            echo "Error: You requested to continue a previous auto-rebase, but"

            echo "$CACHE_DIR does not exist."

            exit 1

        fi

        if [ -f $TODO -a -f $TODO_BACKUP -a -f $ORIGINAL_BRANCH ]; then

            if ! cat $COMPLETED $TODO | diff - $TODO_BACKUP; then

                echo "Error: You requested to continue a previous auto-rebase, but the cache appears"

                echo "to be invalid (completed rebases + todo rebases != planned rebases)."

                echo "You may attempt to manually continue from what is stored in $CACHE_DIR"

                echo "or remove it with \"git auto-rebase -a\""

                exit 1

            fi

        else

            echo "Error: You requested to continue a previous auto-rebase, but some cached files"

            echo "are missing."

            echo "You may attempt to manually continue from what is stored in $CACHE_DIR"

            echo "or remove it with \"git auto-rebase -a\""

            exit 1

        fi

    elif [ -d $CACHE_DIR ]; then

        echo "A previous auto-rebase appears to have been left unfinished."

        echo "Either continue it with \"git auto-rebase -c\" or remove the cache with"

        echo "\"git auto-rebase -a\""

        exit 1

    else

        get_config_relationships

    fi

}


# Verify that desired branches exist, and pre-refs do not.

check_ref_existence() {

    local parent child

    for pair in "${pairs[@]}"; do

        parent="${pair% *}"

        if ! git show-ref -q --verify "refs/heads/$parent" > /dev/null ; then

            if ! git show-ref -q --verify "refs/remotes/$parent" > /dev/null; then

                child="${pair#* }"

                echo "Error: specified parent branch $parent of branch $child does not exist"

                exit 1

            fi

        fi

        if [ -z "$continue" ]; then

            if git show-ref -q --verify "$REF_NAMESPACE/$parent" > /dev/null; then

                echo "Error: ref $REF_NAMESPACE/$parent already exists"

                echo "Most likely a previous git-auto-rebase did not complete; if you have fixed all"

                echo "necessary rebases, you may try again after removing it with:"

                echo

                echo "git update-ref -d $REF_NAMESPACE/$parent"

                echo

                exit 1

            fi

        else

            if ! git show-ref -q --verify "$REF_NAMESPACE/$parent" > /dev/null; then

                echo "Error: You requested to continue a previous auto-rebase, but the required"

                echo "cached ref $REF_NAMESPACE/$parent is missing."

                echo "You may attempt to manually continue from the contents of $CACHE_DIR"

                echo "and whatever refs in refs/$REF_NAMESPACE still exist, or abort the previous"

                echo "auto-rebase with \"git auto-rebase -a\""

                exit 1

            fi

        fi

    done

}


# Create the pre-refs, storing original position of rebased parents

create_pre_refs() {

    local parent prev_parent

    for pair in "${pairs[@]}"; do

        parent="${pair% *}"

        if [ "$prev_parent" != "$parent" ]; then

            if [ -n "$dry_run" ]; then

                echo git update-ref "$REF_NAMESPACE/$parent" "$parent" \"\"

            else

                if ! git update-ref "$REF_NAMESPACE/$parent" "$parent" ""; then

                    echo "Error: cannot create ref $REF_NAMESPACE/$parent"

                    exit 1

                fi

            fi

        fi


        prev_parent="$parent"

    done

}


# Perform the rebases, updating todo/completed as we go

perform_rebases() {

    local prev_parent parent child

    for pair in "${pairs[@]}"; do

        parent="${pair% *}"

        child="${pair#* }"


        # We do this *before* rebasing, assuming most likely any failures will be

        # fixed with rebase --continue, and therefore should not be attempted again

        head -n 1 $TODO >> $COMPLETED

        sed -i '1d' $TODO


        if [ -n "$dry_run" ]; then

            echo git rebase --onto "$parent" "$REF_NAMESPACE/$parent" "$child"

            echo "Successfully rebased $child onto $parent"

        else

            echo git rebase --onto "$parent" "$REF_NAMESPACE/$parent" "$child"

            if ( git merge-ff -q "$child" "$parent" 2> /dev/null && echo "Fast-forwarded $child to $parent." ) || \

                git rebase --onto "$parent" "$REF_NAMESPACE/$parent" "$child"; then

                echo "Successfully rebased $child onto $parent"

            else

                echo "Error rebasing $child onto $parent."

                echo 'You should either fix it (end with git rebase --continue) or abort it, then use'

                echo '"git auto-rebase -c" to continue. You may also use "git auto-rebase -a" to'

                echo 'abort the auto-rebase. Note that this will not undo already-completed rebases.'

                exit 1

            fi

        fi


        prev_parent="$parent"

    done

}


rebase_all_intelligent() {

    if ! git rev-parse --show-git-dir &> /dev/null; then

        echo "Error: git-auto-rebase must be run from inside a git repository"

        exit 1

    fi


    SUBDIRECTORY_OK=1

    . "$(git --exec-path | sed 's/:/\n/' | grep -m 1 git-core)"/git-sh-setup

    cd_to_toplevel



    # Figure out what we need to do (continue, or read from config)

    get_relationships


    # Read the resulting todo list

    OLDIFS="$IFS"

    IFS=$'\n'

    pairs=($(cat $TODO))

    IFS="$OLDIFS"


    # Store the original branch

    if [ -z "$continue" ]; then

        git symbolic-ref HEAD | sed 's@refs/heads/@@' > $ORIGINAL_BRANCH

    fi


    check_ref_existence

    # These three depend on the pairs array

    if [ -z "$continue" ]; then

        create_pre_refs

    fi

    perform_rebases


    echo "Returning to original branch"

    if [ -n "$dry_run" ]; then

        echo git checkout $(cat $ORIGINAL_BRANCH)

    else

        git checkout $(cat $ORIGINAL_BRANCH) > /dev/null

    fi


    if diff -q $COMPLETED $TODO_BACKUP ; then

        if [ "$(wc -l $TODO | cut -d" " -f1)" -eq 0 ]; then

            cleanup_autorebase

            echo "Auto-rebase complete"

        else

            echo "Error: todo-rebases not empty, but completed and planned rebases match."

            echo "This should not be possible, unless you hand-edited a cached file."

            echo "Examine $TODO, $TODO_BACKUP, and $COMPLETED to determine what went wrong."

            exit 1

        fi

    else

        echo "Error: completed rebases don't match planned rebases."

        echo "Examine $TODO_BACKUP and $COMPLETED to determine what went wrong."

        exit 1

    fi

}



while getopts "nca" opt; do

    case $opt in

        n ) dry_run=1;;

        c ) continue=1;;

        a ) abort=1;;

        * )

            echo "git-auto-rebase is too dangerous to run with invalid options; exiting"

            print_help

            exit 1

    esac

done

shift $((OPTIND-1))



case $# in

    0 )

        if [ -n "$abort" ]; then

            cleanup_autorebase

        else

            rebase_all_intelligent

        fi

        ;;


    * )

        print_help

        exit 1

        ;;

esac

自从我最初解决这个问题以来,我发现的一件事是有时答案是您实际上根本不想变基!首先要在正确的共同祖先启动主题分支,然后再尝试不推动它们前进,这是有话可说的。但这是在您和您的工作流程之间。


查看完整回答
反对 回复 2019-10-06
?
莫回无

TA贡献1865条经验 获得超7个赞

如果需要更新提交者日期,GIT_COMMITTER_DATE则可以使用环境变量(手动)。也可以使用--formatoption获得分支名称,而无需其他格式。


export GIT_COMMITTER_DATE=$( date -Iseconds )

git branch --format='%(refname)' --contains C | xargs -n 1 | git rebase -p --onto master C^

unset GIT_COMMITTER_DATE

# don't forget to unset this variable to avoid effect for the further work

注意:需要为--committer-date-is-author-date或设置GIT_COMMITTER_DATE相同的校验和C',或保证相同的校验Ca'和并Cb'提交(分别在重定基础功能,功能 -a 和功能-b上)。


查看完整回答
反对 回复 2019-10-06
  • 3 回答
  • 0 关注
  • 499 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信