Program Tip

체크 아웃을 사용하지 않고 Git 브랜치를 병합, 업데이트 및 가져 오기

programtip 2020. 10. 3. 11:31
반응형

체크 아웃을 사용하지 않고 Git 브랜치를 병합, 업데이트 및 가져 오기


저는 A와 B라는 두 개의 분기가있는 프로젝트에서 작업합니다. 저는 일반적으로 분기 A에서 작업하고 분기 B의 항목을 병합합니다. 병합을 위해 일반적으로 다음을 수행합니다.

git merge origin/branchB

그러나 분기 A와 먼저 병합하지 않고 가끔 분기를 확인할 수 있으므로 분기 B의 로컬 복사본도 유지하고 싶습니다.이를 위해 다음을 수행합니다.

git checkout branchB
git pull
git checkout branchA

분기를 앞뒤로 전환하지 않고도 하나의 명령으로 위의 작업을 수행 할 수있는 방법이 있습니까? 그것을 git update-ref위해 사용해야합니까? 어떻게?


짧은 답변

빨리 감기 병합을 수행하는 한 간단히 다음을 사용할 수 있습니다.

git fetch <remote> <sourceBranch>:<destinationBranch>

예 :

# Merge local branch foo into local branch master,
# without having to checkout master first.
# Here `.` means to use the local repository as the "remote":
git fetch . foo:master

# Merge remote branch origin/foo into local branch foo,
# without having to checkout foo first:
git fetch origin foo:foo

Amber의 대답 은 빨리 감기의 경우에도 작동 하지만 , git fetch대신 이런 식으로 사용하면 분기 참조를 강제로 이동하는 것보다 조금 더 안전 git fetch합니다. +에서 사용하지 않는 한 실수로 빨리 감기가 발생하는 것을 자동으로 방지하기 때문 입니다 . refspec.

긴 답변

빨리 감기가 아닌 병합이 발생하는 경우 먼저 A를 체크 아웃하지 않고 분기 B를 분기 A로 병합 할 수 없습니다. 잠재적 인 충돌을 해결하려면 작업 복사본이 필요하기 때문입니다.

그러나 빨리 감기 병합의 경우 정의에 따라 이러한 병합으로 인해 충돌이 발생하지 않기 때문에 가능 합니다. 분기를 먼저 확인하지 않고이를 수행하려면 git fetchrefspec과 함께 사용할 수 있습니다 .

다음 master은 다른 브랜치가 feature체크 아웃 된 경우 업데이트 (빨리 감기가 아닌 변경 허용 안 함) 의 예입니다 .

git fetch upstream master:master

이 사용 사례는 매우 일반적이므로 다음과 같이 git 구성 파일에서 별칭을 만들고 싶을 것입니다.

[alias]
    sync = !sh -c 'git checkout --quiet HEAD; git fetch upstream master:master; git checkout --quiet -'

이 별칭의 기능은 다음과 같습니다.

  1. git checkout HEAD: 이렇게하면 작업 복사본이 분리 된 헤드 상태가됩니다. master체크 아웃 한 상태 에서 업데이트하려는 경우 유용합니다 . 그렇지 않으면에 대한 브랜치 참조 master가 움직이지 않기 때문에와 관련이 필요하다고 생각합니다 .하지만 그것이 정말로 내 머리에서 바로 벗어난 것인지 기억이 나지 않습니다.

  2. git fetch upstream master:master: 이것은 당신의 지역을와 master같은 장소로 빨리 감습니다 upstream/master.

  3. git checkout -이전에 체크 아웃 한 브랜치를 체크 아웃합니다 ( -이 경우에서하는 일입니다).

git fetch(비) 빨리 감기 병합 구문

당신이 원하는 경우 fetch업데이트가 아닌 빨리 감기 인 경우 명령이 실패 당신은 단순히 형태의 refspec를 사용

git fetch <remote> <remoteBranch>:<localBranch>

비 빨리 감기 업데이트를 허용 +하려면 refspec 앞에 a 추가합니다 .

git fetch <remote> +<remoteBranch>:<localBranch>

다음을 사용하여 로컬 저장소를 "remote"매개 변수로 전달할 수 있습니다 ..

git fetch . <sourceBranch>:<destinationBranch>

문서

git fetch이 구문을 설명 하는 문서 에서 (강조 표시) :

<refspec>

<refspec>매개 변수 의 형식은 선택 사항 인 더하기 +, 소스 참조 <src>, 콜론 :, 대상 참조 순 <dst>입니다.

The remote ref that matches <src> is fetched, and if <dst> is not empty string, the local ref that matches it is fast-forwarded using <src>. If the optional plus + is used, the local ref is updated even if it does not result in a fast-forward update.

See Also

  1. Git checkout and merge without touching working tree

  2. Merging without changing the working directory


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.

The -f option stands for --force, which means you must be careful when using it.

Don't use it unless you are absolutely sure the merge will be fast-forward.


As Amber said, fast-forward merges are the only case in which you could conceivably do this. Any other merge conceivably needs to go through the whole three-way merge, applying patches, resolving conflicts deal - and that means there need to be files around.

I happen to have a script around I use for exactly this: doing fast-forward merges without touching the work tree (unless you're merging into HEAD). It's a little long, because it's at least a bit robust - it checks to make sure that the merge would be a fast-forward, then performs it without checking out the branch, but producing the same results as if you had - you see the diff --stat summary of changes, and the entry in the reflog is exactly like a fast forward merge, instead of the "reset" one you get if you use branch -f. If you name it git-merge-ff and drop it in your bin directory, you can call it as a git command: git merge-ff.

#!/bin/bash

_usage() {
    echo "Usage: git merge-ff <branch> <committish-to-merge>" 1>&2
    exit 1
}

_merge_ff() {
    branch="$1"
    commit="$2"

    branch_orig_hash="$(git show-ref -s --verify refs/heads/$branch 2> /dev/null)"
    if [ $? -ne 0 ]; then
        echo "Error: unknown branch $branch" 1>&2
        _usage
    fi

    commit_orig_hash="$(git rev-parse --verify $commit 2> /dev/null)"
    if [ $? -ne 0 ]; then
        echo "Error: unknown revision $commit" 1>&2
        _usage
    fi

    if [ "$(git symbolic-ref HEAD)" = "refs/heads/$branch" ]; then
        git merge $quiet --ff-only "$commit"
    else
        if [ "$(git merge-base $branch_orig_hash $commit_orig_hash)" != "$branch_orig_hash" ]; then
            echo "Error: merging $commit into $branch would not be a fast-forward" 1>&2
            exit 1
        fi
        echo "Updating ${branch_orig_hash:0:7}..${commit_orig_hash:0:7}"
        if git update-ref -m "merge $commit: Fast forward" "refs/heads/$branch" "$commit_orig_hash" "$branch_orig_hash"; then
            if [ -z $quiet ]; then
                echo "Fast forward"
                git diff --stat "$branch@{1}" "$branch"
            fi
        else
            echo "Error: fast forward using update-ref failed" 1>&2
        fi
    fi
}

while getopts "q" opt; do
    case $opt in
        q ) quiet="-q";;
        * ) ;;
    esac
done
shift $((OPTIND-1))

case $# in
    2 ) _merge_ff "$1" "$2";;
    * ) _usage
esac

P.S. If anyone sees any issues with that script, please comment! It was a write-and-forget job, but I'd be happy to improve it.


You can only do this if the merge is a fast-forward. If it's not, then git needs to have the files checked out so it can merge them!

To do it for a fast-forward only:

git fetch <branch that would be pulled for branchB>
git update-ref -m "merge <commit>: Fast forward" refs/heads/<branch> <commit>

where <commit> is the fetched commit, the one you want to fast-forward to. This is basically like using git branch -f to move the branch, except it also records it in the reflog as if you actually did the merge.

Please, please, please don't do this for something that's not a fast-forward, or you'll just be resetting your branch to the other commit. (To check, see if git merge-base <branch> <commit> gives the branch's SHA1.)


Another, admittedly pretty brute way is to just re-create the branch:

git fetch remote
git branch -f localbranch remote/remotebranch

This throws away the local outdated branch and re-creates one with the same name, so use with care ...


In your case you can use

git fetch origin branchB:branchB

which does what you want (assuming the merge is fast-forward). If the branch can't be updated because it requires a non-fast-forward merge, then this fails safely with a message.

This form of fetch has some more useful options too:

git fetch <remote> <sourceBranch>:<destinationBranch>

Note that <remote> can be a local repository, and <sourceBranch> can be a tracking branch. So you can update a local branch, even if it's not checked out, without accessing the network.

Currently, my upstream server access is via a slow VPN, so I periodically connect, git fetch to update all remotes, and then disconnect. Then if, say, the remote master has changed, I can do

git fetch . remotes/origin/master:master

to safely bring my local master up to date, even if I currently have some other branch checked out. No network access required.


You can clone the repo and do the merge in the new repo. On the same filesystem, this will hardlink rather than copy most of the data. Finish by pulling the results into the original repo.


Enter git-forward-merge:

Without needing to checkout destination, git-forward-merge <source> <destination> merges source into destination branch.

https://github.com/schuyler1d/git-forward-merge

Only works for automatic merges, if there are conflicts you need to use the regular merge.


For many cases (such as merging), you can just use the remote branch without having to update the local tracking branch. Adding a message in the reflog sounds like overkill and will stop it being quicker. To make it easier to recover, add the following into your git config

[core]
    logallrefupdates=true

Then type

git reflog show mybranch

to see the recent history for your branch


I wrote a shell function for a similar use case I encounter daily on projects. This is basically a shortcut for keeping local branches up to date with a common branch like develop before opening a PR, etc.

Posting this even though you don't want to use checkout, in case others don't mind that constraint.

glmh ("git pull and merge here") will automatically checkout branchB, pull the latest, re-checkout branchA, and merge branchB.

Doesn't address the need to keep a local copy of branchA, but could easily be modified to do so by adding a step before checking out branchB. Something like...

git branch ${branchA}-no-branchB ${branchA}

For simple fast-forward merges, this skips to the commit message prompt.

For non fast-forward merges, this places your branch in the conflict resolution state (you likely need to intervene).

To setup, add to .bashrc or .zshrc, etc:

glmh() {
    branchB=$1
    [ $# -eq 0 ] && { branchB="develop" }
    branchA="$(git branch | grep '*' | sed 's/* //g')"
    git checkout ${branchB} && git pull
    git checkout ${branchA} && git merge ${branchB} 
}

Usage:

# No argument given, will assume "develop"
> glmh

# Pass an argument to pull and merge a specific branch
> glmh your-other-branch

Note: This is not robust enough to hand-off of args beyond branch name to git merge


Another way to effectively do this is:

git fetch
git branch -d branchB
git branch -t branchB origin/branchB

Because it's a lower case -d, it will only delete it if the data will still exist somewhere. It's similar to @kkoehne's answer except it doesn't force. Because of the -t it will set up the remote again.

I had a slightly different need than OP, which was to create a new feature branch off develop (or master), after merging a pull request. That can be accomplished in a one-liner without force, but it doesn't update the local develop branch. It's just a matter of checking out a new branch and having it be based off origin/develop:

git checkout -b new-feature origin/develop

git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]

You can try git worktree to have two branches open side by side, this sounds like it might be what you what but very different than some of the other answers I've seen here.

In this way you can have two separate branches tracking in the same git repo so you only have to fetch once to get updates in both work trees (rather than having to git clone twice and git pull on each)

Worktree will create a new working directory for your code where you can have a different branch checked out simultaneously instead of swapping branches in place.

When you want to remove it you can clean up with

git worktree remove [-f] <worktree>

just to pull the master without checking out the master I use

git fetch origin master:master

참고URL : https://stackoverflow.com/questions/3216360/merge-update-and-pull-git-branches-without-using-checkouts

반응형