web-dev-qa-db-ja.com

チェックアウトを使用せずにGitブランチをマージ、更新、およびプルする

私は、AとBの2つのブランチを持つプロジェクトに取り組んでいます。通常、ブランチAで作業し、ブランチBからのものをマージします。マージのために、通常、私は行います。

git merge Origin/branchB

ただし、最初にブランチAとマージせずにブランチをチェックアウトすることがあるので、ブランチBのローカルコピーも保持したいと思います。このために、次のようにします。

git checkout branchB
git pull
git checkout branchA

上記のことを1つのコマンドで実行する方法はありますか。分岐を前後に切り替える必要はありません。そのためにはgit update-refを使うべきですか?どうやって?

551
charles

短い答え

あなたが早送りマージをしている限り、あなたは単に使うことができます

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を使用する方がブランチ参照を強制移動するよりも少し安全です。 refspecでgit fetchを使用していない限り、+は、偶発的な早送りを自動的に防止します。

長い答え

早送りではないマージになる場合は、最初にAをチェックアウトせずにブランチBをブランチAにマージすることはできません。これは、潜在的な競合を解決するために作業コピーが必要だからです。

しかし、早送りマージの場合はが可能です。定義上、このようなマージでは競合が発生しないからです。最初にブランチをチェックアウトせずにこれを行うには、refspecとともにgit fetchを使用できます。

別のブランチ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:これはあなたのローカルのmasterupstream/masterと同じ場所に早送りします。

  3. git checkout -は以前にチェックアウトしたブランチをチェックアウトします(これが-の場合の動作です)。

(非)早送りマージのためのgit fetchの構文

更新が早送りでない場合にfetchコマンドを失敗させたい場合は、単に次の形式のrefspecを使用します。

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

早送り以外の更新を許可する場合は、refspecの先頭に+を追加します。

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

.を使用して、あなたのローカルレポを "remote"パラメータとして渡すことができることに注意してください。

git fetch . <sourceBranch>:<destinationBranch>

ドキュメンテーション

この構文を説明する git fetchのドキュメント から(強調表示)

<refspec>

<refspec>パラメーターのフォーマットは、オプションのプラス+、それに続くソース参照<src>、それに続くコロン:、およびそれに続く宛先参照<dst>です。

<src>に一致するリモート参照が取得され、<dst>が空の文字列でない場合は、それに一致するローカル参照が<src>を使用して転送されます。 。オプションのプラス+が使用されると、ローカル参照は早送り更新にならない場合でも更新されます。

参照

  1. 作業ツリーに触れずにチェックアウトしてマージします

  2. 作業ディレクトリを変更せずにマージする

843
user456814

いいえ、ありません。特にGitが自動的にそれらをマージすることができない場合、ターゲットブランチのチェックアウトはあなたが衝突を解決することを可能にするために必要です。

しかし、マージが早送りである場合は、実際には何もマージする必要がないため、ターゲットブランチをチェックアウトする必要はありません。ブランチを更新して、新しいヘッドリファレンスgit branch -fでこれを行うことができます。

git branch -f branch-b branch-a

branch-bbranch-aの先頭を指すように更新します。

-fオプションは--forceを表します。つまり、使用するときは注意が必要です。マージが早送りされることが確実でない限り、使用しないでください。

80
Amber

アンバーが言ったように、早送りマージはあなたがおそらくこれをすることができる唯一のケースです。他のマージでは、3方向のマージ、パッチの適用、競合の解決をすべて考慮する必要があると考えられます。つまり、ファイルが存在する必要があります。

私はまわりで私がまさにこれのために使用するスクリプトを持っている:(あなたがHEADにマージしているのでなければ)早送りマージを作業ツリーに触れることなく行う。少々長いので、少々堅牢です - マージが早送りになることを確認してから、ブランチをチェックアウトせずにそれを実行しますが、あなたが持っているのと同じ結果を生み出します - diff --statの変更の要約、およびreflogのエントリは、branch -fを使用した場合に「リセット」されるのではなく、早送りマージとまったく同じです。 git-merge-ffという名前を付けてbinディレクトリにドロップすると、gitコマンドとして呼び出すことができます。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誰かがそのスクリプトに関する問題を見た場合、コメントしてください!それは書き込み忘れの仕事でしたが、私はそれを改善して幸せです。

29
Cascabel

これが可能なのは、マージが早送りの場合だけです。そうでない場合は、gitはファイルをチェックアウトしてマージできるようにする必要があります。

早送りのみの場合は

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

<commit>はフェッチされたコミットで、早送りしたいものです。これは基本的にgit branch -fを使ってブランチを移動するのと似ていますが、あたかも実際にマージしたかのようにreflogにも記録されます。

お願いです、お願いです、お願いします早送りではない何かのためにこれをしないでください。 (確認するには、git merge-base <branch> <commit>がブランチのSHA1を与えるかどうかを確認してください。)

19
Cascabel

もう1つの、明らかにかなり野蛮な方法は、単にブランチを再作成することです。

git fetch remote
git branch -f localbranch remote/remotebranch

これはローカルの古いブランチを捨てて同じ名前のものを作り直すので、注意して使ってください...

10
kkoehne

あなたのケースではあなたが使用することができます

git fetch Origin branchB:branchB

これはあなたが望むことをする(マージが早送りであると仮定して)。早送り以外のマージが必要なためにブランチを更新できない場合は、メッセージが表示されて安全に失敗します。

この形式のフェッチには、さらに便利なオプションがいくつかあります。

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

<remote>をローカルリポジトリに、<sourceBranch>を追跡ブランチにすることができます。そのため、チェックアウトしていなくてもローカルブランチをネットワークにアクセスせずに更新できます

現在、私のアップストリームサーバーへのアクセスは遅いVPNを経由しているので、定期的に接続し、すべてのリモートを更新するためにgit fetchを接続してから切断します。それなら、もしリモートマスターが変わったら、私はそうすることができます

git fetch . remotes/Origin/master:master

現在他のブランチをチェックアウトしている場合でも、ローカルマスターを安全に最新の状態にするため。ネットワークアクセスは必要ありません。

9
Bennett McElwee

リポジトリを複製して、新しいリポジトリでマージを実行できます。同じファイルシステムでは、これはデータの大部分をコピーするのではなくハードリンクします。結果を元のリポジトリにプルして終了します。

6
wnoise

多くの場合(マージなど)、ローカルトラッキングブランチを更新しなくてもリモートブランチを使用できます。 reflogにメッセージを追加することはやり過ぎであるように聞こえ、速くなるのを止めます。回復しやすくするために、git configに以下を追加してください。

[core]
    logallrefupdates=true

それからタイプ

git reflog show mybranch

あなたのブランチの最近の歴史を見るために

3
Casebash

git-forward-mergeを入力してください。

宛先をチェックアウトする必要なしに、git-forward-merge <source> <destination>はソースを宛先ブランチにマージします。

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

通常のマージを使用する必要がある場合は、自動マージでのみ機能します。

3
lkraider

私は毎日プロジェクトで遭遇する同様のユースケースのためにシェル関数を書きました。これは基本的に、ローカルブランチをPRを開く前にDevelopのような共通のブランチで最新に保つための近道です。

他の人がその制約を気にしないのであれば、checkoutを使いたくないとしてもこれを投稿してください。

glmh( "git pull and merge here")は自動的にcheckout branchBpull、最新のもの、re -checkout branchA、そしてmerge branchBになります。

BranchAのローカルコピーを保持する必要性については言及していませんが、branchBをチェックアウトする前にステップを追加することによって、そうするように簡単に変更することができます。何かのようなもの...

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

単純な早送りマージの場合、これはコミットメッセージプロンプトにスキップします。

早送りでないマージでは、これはあなたのブランチを衝突解決状態にします(おそらく介入する必要があります)。

設定するには、.bashrcまたは.zshrcなどに追加します。

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} 
}

使用法:

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

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

注意:これはブランチ名を越えて引数をgit mergeに渡すのに十分なほど堅牢ではありません

2
rkd

これを効果的に行う別の方法は、次のとおりです。

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

-dは小文字なので、データがまだどこかに存在する場合にのみ削除します。それは強制しないことを除いて@ kkoehneの答えに似ています。 -tのために、それは再びリモートをセットアップします。

プルリクエストをマージした後、develop(またはmaster)から新しい機能ブランチを作成するというOPとは、多少異なるニーズがありました。これはワンライナーで無理なく達成できますが、ローカルのdevelopブランチは更新されません。新しいブランチをチェックアウトしてOrigin/developを基にしてもらうだけのことです。

git checkout -b new-feature Origin/develop
0
Benjamin Atkin

使用するマスターをチェックアウトせずにマスターをプルするだけです

git fetch Origin master:master

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

git worktree を試して、2つのブランチを並べて開くことができます。これは、あなたが何をしているように聞こえるかもしれませんが、私がここで見た他の回答のいくつかとは非常に異なっています。

この方法では、同じgitリポジトリで追跡する2つの別々のブランチを持つことができるため、両方の作業ツリーで更新を取得するために一度フェッチするだけです(git cloneを2回行い、それぞれをgit pullする必要はありません)

Worktreeは、コードの新しい作業ディレクトリを作成します。このディレクトリでは、ブランチを所定の場所にスワップする代わりに、異なるブランチを同時にチェックアウトできます。

あなたがそれを削除したいとき、あなたはでクリーンアップすることができます

git worktree remove [-f] <worktree>
0
grego