Gabungkan, perbarui, dan tarik cabang Git tanpa menggunakan checkout

629

Saya mengerjakan proyek yang memiliki 2 cabang, A dan B. Saya biasanya bekerja di cabang A, dan menggabungkan hal-hal dari cabang B. Untuk penggabungan, saya biasanya akan melakukan:

git merge origin/branchB

Namun, saya juga ingin menyimpan salinan cabang B lokal, karena saya kadang-kadang memeriksa cabang tanpa terlebih dahulu bergabung dengan cabang saya A. Untuk ini, saya akan melakukan:

git checkout branchB
git pull
git checkout branchA

Apakah ada cara untuk melakukan hal di atas dalam satu perintah, dan tanpa harus berganti cabang bolak-balik? Haruskah saya gunakan git update-refuntuk itu? Bagaimana?

charles
sumber
1
Jawaban Jakub untuk pertanyaan terkait pertama menjelaskan mengapa secara umum tidak mungkin. Penjelasan lain (a posteriori) adalah bahwa Anda tidak dapat menggabungkan repo kosong, jadi jelas itu membutuhkan pohon kerja.
Cascabel
3
@ Eric: Alasan umum adalah bahwa checkout memakan waktu lama untuk repo besar, dan mereka memperbarui cap waktu bahkan jika Anda kembali ke versi yang sama, jadi anggap semuanya perlu dibangun kembali.
Cascabel
Pertanyaan kedua yang saya tautkan adalah menanyakan tentang kasus yang tidak biasa - penggabungan yang bisa maju cepat, tetapi OP ingin menggabungkan menggunakan --no-ffopsi, yang menyebabkan komitmen gabungan dicatat pula. Jika Anda tertarik pada hal itu, jawaban saya di sana menunjukkan bagaimana Anda bisa melakukan itu - tidak sekuat jawaban saya di sini, tetapi kekuatan keduanya pasti bisa digabungkan.
Cascabel

Jawaban:

973

Jawaban Singkat

Selama Anda melakukan penggabungan maju-cepat , maka Anda bisa menggunakannya

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

Contoh:

# 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

Sementara jawaban Amber juga akan berfungsi dalam kasus fast-forward, menggunakan git fetchdengan cara ini sebagai gantinya sedikit lebih aman daripada hanya memindahkan-paksa referensi cabang, karena git fetchsecara otomatis akan mencegah non-fast-forward tanpa disengaja asalkan Anda tidak menggunakannya +dalam refspec.

Jawaban Panjang

Anda tidak dapat menggabungkan cabang B menjadi cabang A tanpa memeriksa A terlebih dahulu jika itu akan menghasilkan penggabungan non-maju cepat. Ini karena copy pekerjaan diperlukan untuk menyelesaikan potensi konflik.

Namun, dalam kasus penggabungan maju-cepat, ini dimungkinkan , karena penggabungan tersebut tidak pernah dapat mengakibatkan konflik, menurut definisi. Untuk melakukan ini tanpa memeriksa cabang terlebih dahulu, Anda dapat menggunakan git fetchdengan refspec.

Berikut adalah contoh pemutakhiran master(melarang perubahan tidak-maju-cepat) jika Anda memiliki cabang lain yang featurediperiksa:

git fetch upstream master:master

Casing penggunaan ini sangat umum, sehingga Anda mungkin ingin membuat alias untuknya di file konfigurasi git Anda, seperti ini:

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

Apa yang dilakukan alias ini adalah sebagai berikut:

  1. git checkout HEAD: ini membuat copy pekerjaan Anda menjadi kondisi kepala terpisah. Ini berguna jika Anda ingin memperbarui mastersementara Anda telah check-out. Saya pikir itu perlu dilakukan karena kalau tidak referensi cabang untuk mastertidak akan bergerak, tapi saya tidak ingat apakah itu benar-benar tepat di luar kepala saya.

  2. git fetch upstream master:master: ini memajukan lokal Anda masterke tempat yang sama dengan upstream/master.

  3. git checkout -periksa cabang yang sebelumnya Anda check-out (itulah yang -dilakukan dalam hal ini).

Sintaksis git fetchuntuk (tidak) penggabungan maju cepat

Jika Anda ingin fetchperintah gagal jika pembaruan tidak maju, maka Anda cukup menggunakan refspec dari formulir

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

Jika Anda ingin memperbolehkan pembaruan yang tidak maju, maka Anda menambahkan a di +bagian depan refspec:

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

Perhatikan bahwa Anda dapat meneruskan repo lokal Anda sebagai parameter "jarak jauh" menggunakan .:

git fetch . <sourceBranch>:<destinationBranch>

Dokumentasi

Dari git fetchdokumentasi yang menjelaskan sintaks ini (penekanan pada saya):

<refspec>

Format <refspec>parameter adalah plus opsional +, diikuti oleh ref sumber <src>, diikuti oleh titik dua :, diikuti oleh ref tujuan <dst>.

Referensi jarak jauh yang cocok <src>diambil, dan jika <dst>bukan string kosong, referensi lokal yang cocok akan diteruskan dengan cepat menggunakan<src> . Jika plus opsional+digunakan, ref lokal diperbarui walaupun itu tidak menghasilkan pembaruan maju cepat.

Lihat juga

  1. Dapatkan checkout dan bergabung tanpa menyentuh pohon yang berfungsi

  2. Penggabungan tanpa mengubah direktori kerja

Komunitas
sumber
3
git checkout --quiet HEADadalah git checkout --quiet --detachpada Git 1.7.5.
Rafa
6
Saya menemukan saya harus melakukan: git fetch . origin/foo:foountuk memperbarui foo lokal saya ke asal / foo lokal saya
weston
3
Apakah ada alasan bagian "git checkout HEAD --quiet" dan "git checkout --quiet -" termasuk dalam jawaban panjang, tetapi bukan jawaban singkat? Saya kira itu karena skrip dapat dijalankan ketika master Anda telah memeriksa, meskipun Anda hanya bisa melakukan git pull?
Sean
8
mengapa 'mengambil' perintah untuk melakukan 'bergabung' di sini ... itu sama sekali tidak masuk akal; Jika 'pull' adalah 'fetch' diikuti oleh 'merge', harus ada 'ekuivalen' merge 'fff' yang lebih logis yang akan memperbarui 'branch' dari 'origin / branch' secara lokal, mengingat bahwa 'fetch' memiliki sudah dijalankan.
Ed Randall
1
Terima kasih untuk git checkout -triknya! Semudah cd -.
Kuda
84

Tidak, tidak ada. Checkout cabang target diperlukan untuk memungkinkan Anda menyelesaikan konflik, antara lain (jika Git tidak dapat menggabungkannya secara otomatis).

Namun, jika penggabungan adalah salah satu yang akan maju cepat, Anda tidak perlu memeriksa cabang target, karena Anda sebenarnya tidak perlu menggabungkan apa pun - yang harus Anda lakukan hanyalah memperbarui cabang untuk menunjuk ke ref kepala baru. Anda dapat melakukan ini dengan git branch -f:

git branch -f branch-b branch-a

Akan memperbarui branch-bke titik ke kepala branch-a.

The -fpilihan singkatan --force, yang berarti Anda harus berhati-hati saat menggunakannya.

Jangan gunakan kecuali Anda benar-benar yakin penggabungan akan maju cepat.

Amber
sumber
46
Berhati-hatilah untuk tidak melakukannya kecuali Anda sudah memastikan penggabungan akan menjadi langkah maju! Tidak ingin disadari nanti Anda salah menempatkan komitmen.
Cascabel
@ FuadSaud Tidak persis. git resethanya berfungsi pada cabang yang saat ini ditutup.
Amber
9
Hasil yang sama (fast-forwarding) dicapai oleh git fetch upstream branch-b:branch-b(diambil dari jawaban ini ).
Oliver
6
Untuk memperluas komentar @ Oliver, Anda juga dapat melakukannya git fetch <remote> B:A, di mana B dan A adalah cabang yang benar-benar berbeda, tetapi B dapat maju cepat digabungkan menjadi A. Anda juga dapat meneruskan repositori lokal Anda sebagai "remote" menggunakan .sebagai alias jarak jauh: git fetch . B:A.
8
Pertanyaan OP membuatnya cukup jelas bahwa penggabungan memang akan maju cepat. Bagaimanapun, branch -fbisa berbahaya, seperti yang Anda tunjukkan. Jadi jangan gunakan itu! Gunakan fetch origin branchB:branchB, yang akan gagal dengan aman jika penggabungan tidak maju cepat.
Bennett McElwee
30

Seperti kata Amber, penggabungan maju cepat adalah satu-satunya kasus di mana Anda bisa melakukan ini. Penggabungan lain yang mungkin perlu melalui seluruh penggabungan tiga arah, menerapkan tambalan, menyelesaikan konflik - dan itu berarti perlu ada file di sekitar.

Saya kebetulan memiliki skrip di sekitar yang saya gunakan untuk persis ini: melakukan penggabungan maju cepat tanpa menyentuh pohon kerja (kecuali jika Anda menggabungkan ke HEAD). Agak panjang, karena setidaknya sedikit kuat - memeriksa untuk memastikan bahwa penggabungan akan menjadi maju cepat, kemudian melakukan itu tanpa memeriksa cabang, tetapi menghasilkan hasil yang sama seperti jika Anda punya - Anda melihat diff --statringkasan perubahan, dan entri di reflog persis seperti penggabungan maju cepat, alih-alih "reset" yang Anda dapatkan jika Anda gunakan branch -f. Jika Anda nama itu git-merge-ffdan menjatuhkannya dalam direktori bin Anda, Anda dapat menyebutnya sebagai perintah 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

PS Jika ada yang melihat masalah dengan skrip itu, silakan komentar! Itu adalah pekerjaan menulis dan melupakan, tetapi saya akan senang untuk memperbaikinya.

Cascabel
sumber
Anda mungkin tertarik dengan stackoverflow.com/a/5148202/717355 untuk perbandingan
Philip Oakley
@ PhilipOakley Dari pandangan sekilas, bahwa pada intinya melakukan hal yang persis sama seperti milik saya. Tapi milikku tidak dikodekan ke sepasang cabang, ia memiliki lebih banyak penanganan kesalahan, itu mensimulasikan output dari git-merge, dan itu melakukan apa yang Anda maksud jika Anda memanggilnya saat Anda benar-benar di cabang.
Cascabel
Saya memiliki milik Anda di direktori \ bin saya ;-) Saya baru saja melupakannya dan melihat sekeliling, melihat skrip itu dan itu membuat saya ingat! Salinan saya memang memiliki tautan ini dan # or git branch -f localbranch remote/remotebranchuntuk mengingatkan saya pada sumber dan opsinya. Berikan komentar Anda pada tautan lain, +1.
Philip Oakley
3
+1, dapat juga secara default "$branch@{u}"sebagai komite untuk bergabung untuk mendapatkan cabang upstream (dari kernel.org/pub/software/scm/git/docs/gitrevisions.html )
orip
Terima kasih! Apakah ada peluang Anda bisa memberikan contoh-penggunaan sederhana ke dalam jawabannya?
nmr
20

Anda hanya bisa melakukan ini jika penggabungan adalah maju cepat. Jika tidak, maka git perlu memeriksa file-nya agar dapat digabungkan!

Untuk melakukannya hanya untuk maju cepat :

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

di mana <commit>komit diambil, yang ingin Anda maju cepat. Ini pada dasarnya seperti menggunakan git branch -funtuk memindahkan cabang, kecuali itu juga mencatatnya di reflog seolah-olah Anda benar-benar melakukan penggabungan.

Tolong, tolong, tolong jangan lakukan ini untuk sesuatu yang bukan fast-forward, atau Anda hanya akan mengatur ulang cabang Anda ke komit lainnya. (Untuk memeriksa, lihat apakah git merge-base <branch> <commit>memberikan SHA1 cabang.)

Cascabel
sumber
4
Apakah ada cara untuk membuatnya gagal jika tidak bisa maju cepat?
gman
2
@man dapat Anda gunakan git merge-base --is-ancestor <A> <B>. "B" menjadi hal yang perlu digabung menjadi "A". Contohnya adalah A = master dan B = mengembangkan, memastikan pengembangan cepat-maju ke master. Catatan: Ada dengan 0 jika tidak ff-mampu, ada dengan 1 jika itu.
eddiemoya
1
Tidak didokumentasikan dalam git-scm, tetapi ada di kernal.org. kernel.org/pub/software/scm/git/docs/git-merge-base.html
eddiemoya
Membuat intisari cepat untuk menyelesaikan apa yang saya bicarakan (tidak benar-benar mengujinya, tetapi seharusnya membuat Anda sebagian besar di sana) gist.github.com/eddiemoya/ad4285b2d8a6bdabf432 ---- sebagai catatan, saya memiliki sebagian besar dari ini berguna, saya punya skrip yang memeriksa ff dan jika tidak memungkinkan Anda rebase cabang yang diinginkan terlebih dahulu - kemudian menggabungkan - semua tanpa memeriksa apa pun.
eddiemoya
12

Dalam kasus Anda, Anda dapat menggunakan

git fetch origin branchB:branchB

yang melakukan apa yang Anda inginkan (dengan asumsi penggabungan adalah maju cepat). Jika cabang tidak dapat diperbarui karena memerlukan penggabungan non-maju cepat, maka ini gagal dengan pesan.

Bentuk pengambilan ini juga memiliki beberapa opsi yang lebih berguna:

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

Catatan yang <remote> bisa menjadi repositori lokal , dan <sourceBranch>bisa menjadi cabang pelacakan. Jadi Anda dapat memperbarui cabang lokal, meskipun tidak dicentang, tanpa mengakses jaringan .

Saat ini, akses server hulu saya adalah melalui VPN yang lambat, jadi saya terhubung secara berkala, git fetchuntuk memperbarui semua remote, dan kemudian memutuskan sambungan. Kemudian jika, katakanlah, master jarak jauh telah berubah, saya bisa melakukannya

git fetch . remotes/origin/master:master

untuk memperbarui master lokal saya dengan aman, meskipun saya saat ini memiliki beberapa cabang lain yang sudah diperiksa. Tidak diperlukan akses jaringan.

Bennett McElwee
sumber
11

Cara lain, yang diakui cukup kasar adalah dengan hanya menciptakan kembali cabang:

git fetch remote
git branch -f localbranch remote/remotebranch

Ini membuang cabang yang sudah ketinggalan zaman lokal dan membuat ulang cabang dengan nama yang sama, jadi gunakan dengan hati-hati ...

kkoehne
sumber
Saya baru saja melihat bahwa jawaban asli sudah menyebutkan cabang -f ... Tetap saja, saya tidak melihat keuntungan memiliki penggabungan reflog untuk kasus penggunaan yang dijelaskan sebelumnya.
kkoehne
7

Anda dapat mengkloning repo dan melakukan penggabungan dalam repo baru. Pada sistem file yang sama, ini akan menggunakan hardlink daripada menyalin sebagian besar data. Selesai dengan menarik hasilnya ke dalam repo asli.

Wnoise
sumber
4

Masukkan git-forward-merge :

Tanpa perlu checkout tujuan, git-forward-merge <source> <destination>menggabungkan sumber ke cabang tujuan.

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

Hanya berfungsi untuk penggabungan otomatis, jika ada konflik Anda perlu menggunakan penggabungan biasa.

lkraider
sumber
2
Saya pikir ini lebih baik daripada git fetch <remote> <source>: <destination>, karena fast forward adalah operasi penggabungan yang tidak mengambil dan lebih mudah untuk menulis. Sayangnya, tidak ada di default git.
Binarian
4

Untuk banyak kasus (seperti penggabungan), Anda bisa menggunakan cabang jarak jauh tanpa harus memperbarui cabang pelacakan lokal. Menambahkan pesan di reflog terdengar seperti berlebihan dan akan menghentikannya lebih cepat. Untuk membuatnya lebih mudah untuk dipulihkan, tambahkan berikut ini ke konfigurasi git Anda

[core]
    logallrefupdates=true

Kemudian ketik

git reflog show mybranch

untuk melihat riwayat terbaru untuk cabang Anda

Casebash
sumber
Saya pikir bagian itu [core]tidak boleh [user]? (dan ini secara default aktif, untuk repo dengan ruang kerja (yaitu non-telanjang)
eckes
3

Saya menulis fungsi shell untuk use case serupa yang saya temui setiap hari pada proyek. Ini pada dasarnya adalah jalan pintas untuk memperbarui cabang lokal dengan cabang umum seperti pengembangan sebelum membuka PR, dll.

Posting ini meskipun Anda tidak ingin menggunakannya checkout, kalau-kalau orang lain tidak keberatan dengan kendala itu.

glmh("git pull dan gabung di sini") akan secara otomatis checkout branchB, pullyang terbaru, ulang checkout branchA, dan merge branchB.

Tidak membahas kebutuhan untuk menyimpan salinan branchA lokal, tetapi dapat dengan mudah dimodifikasi untuk melakukannya dengan menambahkan langkah sebelum memeriksa branchB. Sesuatu seperti...

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

Untuk penggabungan maju cepat sederhana, ini melompati ke prompt pesan komit.

Untuk penggabungan non-maju, ini menempatkan cabang Anda dalam keadaan resolusi konflik (Anda mungkin perlu melakukan intervensi).

Untuk mengatur, menambah .bashrcatau .zshrc, dll:

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

Pemakaian:

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

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

Catatan: Ini tidak cukup kuat untuk menyerahkan argumen di luar nama cabanggit merge

rkd
sumber
2

Cara lain untuk melakukan ini secara efektif adalah:

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

Karena ini adalah huruf kecil -d, itu hanya akan menghapusnya jika data masih ada di suatu tempat. Ini mirip dengan jawaban @ kkoehne kecuali itu tidak memaksa. Karena -titu akan mengatur remote lagi.

Saya memiliki kebutuhan yang sedikit berbeda dari OP, yaitu membuat cabang fitur baru mati develop(atau master), setelah menggabungkan permintaan tarik. Itu bisa dicapai dalam satu-liner tanpa kekuatan, tetapi itu tidak memperbarui developcabang lokal . Ini hanya masalah memeriksa cabang baru dan membuatnya didasarkan origin/develop:

git checkout -b new-feature origin/develop
Benjamin Atkin
sumber
1

hanya untuk menarik master tanpa memeriksa master yang saya gunakan

git fetch origin master:master

Sodhi saab
sumber
1

Sangat mungkin untuk melakukan penggabungan, bahkan penggabungan maju non-cepat, tanpa penggabungan git checkout. The worktreejawaban dengan @grego adalah petunjuk yang baik. Untuk memperluas itu:

cd local_repo
git worktree add _master_wt master
cd _master_wt
git pull origin master:master
git merge --no-ff -m "merging workbranch" my_work_branch
cd ..
git worktree remove _master_wt

Anda sekarang telah menggabungkan cabang kerja lokal ke mastercabang lokal tanpa mengalihkan checkout Anda.

Zanerock
sumber
1

Jika Anda ingin menyimpan pohon yang sama dengan salah satu cabang yang ingin Anda gabungkan (mis. Bukan "gabungan" yang sebenarnya), Anda dapat melakukannya seperti ini.

# Check if you can fast-forward
if git merge-base --is-ancestor a b; then
    git update-ref refs/heads/a refs/heads/b
    exit
fi

# Else, create a "merge" commit
commit="$(git commit-tree -p a -p b -m "merge b into a" "$(git show -s --pretty=format:%T b)")"
# And update the branch to point to that commit
git update-ref refs/heads/a "$commit"
phkb
sumber
1
git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]

Anda dapat mencoba git worktreemembuka dua cabang secara berdampingan, sepertinya ini yang Anda inginkan tetapi sangat berbeda dari beberapa jawaban lain yang pernah saya lihat di sini.

Dengan cara ini Anda dapat memiliki dua cabang terpisah yang melacak di repo git yang sama sehingga Anda hanya perlu mengambil satu kali untuk mendapatkan pembaruan di kedua pohon kerja (daripada harus git klon dua kali dan git tarik pada masing-masing)

Worktree akan membuat direktori kerja baru untuk kode Anda di mana Anda dapat memiliki cabang yang berbeda diperiksa secara bersamaan alih-alih bertukar cabang di tempat.

Saat Anda ingin menghapusnya, Anda dapat membersihkannya

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