How to do a non-fast-forward git merge to a branch that isn't checked out?

前端 未结 5 1795
误落风尘
误落风尘 2021-01-04 11:31

I\'m on branch a. I want to merge branch b into branch c. The merge is not a fast-forward, but it also doesn\'t require manual resolut

相关标签:
5条回答
  • 2021-01-04 12:07

    If the merge doesn't involve files touched on both branches then I think you want git read-tree and git write-tree with a sideband GIT_INDEX_FILE. This should do it:

    #!/bin/sh
    export GIT_INDEX_FILE=.git/aux-merge-index
    trap 'rm -f '"'$GIT_INDEX_FILE'" 0 1 2 3 15
    set -e
    git read-tree -im `git merge-base $2 $1` $2 $1
    git write-tree \
    | xargs -i@ git commit-tree @ -p $2 -p $1 -m "Merge $1 into $2" \
    | xargs git update-ref -m"Merge $1 into $2" refs/heads/$2
    

    You could also use git mktree </dev/null instead of the merge-base to treat b and c as entirely unrelated branches and make the resulting merge combine the files in each rather than treating files missing in either as deletions.

    You say your merge isn't a fast-forward, so you're going to need to read the read-tree docs to make the sequence above do exactly what you want. --aggressive looks like it might be right, it depends on what the actual differences between your branches are.

    edit added the empty-tree base to handle unrelated trees edit 2 hoisting some payload I said or left implicit in a comment

    0 讨论(0)
  • 2021-01-04 12:15

    Additional to the very sophisticated git read-tree answer by @jthill, there is (nowadays) an IMHO easier way doing this utilising git worktree. This is the basic approach:

    $ git worktree add /tmp/wt c
    $ git -C /tmp/wt merge b
    $ git worktree remove /tmp/wt
    

    Below is a little Bash script which you can call like this:

    $ worktree-merge c b
    

    Script worktree-merge:

    #!/usr/bin/env bash
    
    log() {
        echo -n "LOG: "
        echo "$@" >&2
    }
    
    escape() {
        local string="$1"
        echo "${string//[. \/]/-}"
    }
    
    cleanup() {
        trap "" SIGINT
        log "Removing temporary worktree '$1' ..."
        git worktree remove --force "$1"
    }
    
    prepare_worktree() {
        local reference="$1"
        local worktree="/tmp/MERGE-INTO-`escape "$reference"`"
    
        log "Creating temporary worktree '$worktree' ..."
        trap "cleanup $worktree" EXIT
        git worktree add --force "$worktree" "$reference"
    }
    
    do_merge() {
        local reference="$1"
        local worktree="/tmp/MERGE-INTO-`escape "$reference"`"
        shift
    
        log "Merging ${@@Q} into ${reference@Q} ..."
        git -C "$worktree" merge "$@"
    }
    
    prepare_worktree "$1" &&
    do_merge "$@" &&
    true
    
    0 讨论(0)
  • 2021-01-04 12:18

    You can write a script even if your working directory is dirty. You will have to stash your changes first.

    git stash
    git checkout c
    git merge b
    git checkout a
    git stash pop
    
    0 讨论(0)
  • 2021-01-04 12:28

    Given your requirement to not have to clean your working directory, I assume you mean that you don't want to have to clean either your working tree or index, even via some scripting. In that case, you won't find a solution within the bounds of your current local repo. Git uses the index extensively when merging. I'm not sure about the working tree if there are no conflicts, but in general, merging is inextricably tied to the currently-checked-out branch.

    There's another way, though, that wouldn't require you to change anything in your current repo. It does, however, require you to have or create a clone of your repo. Basically, just clone your repo, then do your merge in the clone, and push it back to your original repo. Here's a brief example of how it would work.

    First, we need a sample repo to work with. The following sequence of commands will create one. You'll end up with master as your current branch and two other branches with changes ready to be merged named change-foo and change-bar.

    mkdir background-merge-example
    cd background-merge-example
    git init
    echo 'from master' > foo
    echo 'from master' > bar
    git add .
    git commit -m "add foo and bar in master" 
    git checkout -b change-foo
    echo 'from foo branch' >> foo
    git commit -am "update foo in foo branch"
    git checkout -b change-bar master
    echo 'from bar branch' >> bar
    git commit -am "update bar in bar branch"
    git checkout master
    

    Now, imagine that you're working on master, and you're wanting to merge change-bar into change-foo. Here's a semi-graphical depiction of where we are:

    $ git log --oneline --graph --all
    * c60fd41 update bar in bar branch
    | * e007aff update foo in foo branch
    |/  
    * 77484e1 add foo and bar in master
    

    The following sequence will accomplish the merge without interfering with the current master branch. Pack this into a script, and you've got a nice "background-merge" command:

    # clone with absolute instead of relative path, or the remote in the clone will
    # be wrong
    git clone file://`realpath .` tmp
    cd tmp
    # this checkout auto-creates a remote-tracking branch in newer versions of git
    # older versions will have to do it manually
    git checkout change-foo
    # creating a tracking branch for the other remote branch is optional
    # it just makes the commit message look nicer
    git branch --track change-bar origin/change-bar
    git merge change-bar
    git push origin change-foo
    cd ..
    rm -rf tmp
    

    Briefly, that will clone the current repo to a subdirectory, enter that directory, do the merge, then push it back to the original repo. It removes the subdirectory after it's done. In a large project, you might want to have a dedicated clone that's just kept up to date instead of making a fresh clone every time. After the merge and push, we end up at:

    $ git log --oneline --graph --all
    *   24f1916 Merge branch 'change-bar' into change-foo
    |\  
    | * d7375ac update bar in bar branch
    * | fed4757 update foo in foo branch
    |/  
    * 6880cd8 add foo and bar in master
    

    Questions?

    0 讨论(0)
  • 2021-01-04 12:29

    This answer explains a workaround that you can try.

    No, there is not. A checkout of the target branch is necessary to allow you to resolve conflicts, among other things (if Git is unable to automatically merge them).

    However, if the merge is one that would be fast-forward, you don't need to check out the target branch, because you don't actually need to merge anything - all you have to do is update the branch to point to the new head ref. You can do this with git branch -f:

    git branch -f branch-b branch-a
    Will update branch-b to point to the head of branch-a.

    0 讨论(0)
提交回复
热议问题