Об’єднайте, оновіть та потягніть гілки Git, не використовуючи каси


628

Я працюю над проектом, який має 2 гілки, A і B. Я, як правило, працюю над гілкою A і об'єдную речі з гілки B. Для об'єднання я б зазвичай робив:

git merge origin/branchB

Однак я також хотів би зберегти локальну копію гілки B, оскільки я можу періодично перевіряти гілку, не спочатку об'єднуючись із моєю гілкою A. Для цього я би зробив:

git checkout branchB
git pull
git checkout branchA

Чи є спосіб зробити вищезазначене в одній команді, і не потрібно перемикати гілку вперед і назад? Чи варто використовувати git update-refдля цього? Як?



1
Відповідь Якуба на перше пов'язане питання пояснює, чому це взагалі неможливо. Ще одне (posteriori) пояснення полягає в тому, що ви не можете злитися в голому репо, тому явно це вимагає робоче дерево.
Каскабель

3
@Eric: Поширені причини полягають у тому, що каси забирають багато часу для великих репостів, і вони оновлюють часові позначки, навіть якщо ви повертаєтесь до тієї ж версії, тому вважайте, що все потрібно відновити.
Каскабель

Друге питання, з яким я пов’язаний, - це запитання про незвичайний випадок - злиття, яке може бути швидким вперед, але яке ОП хотіло об'єднати за допомогою --no-ffопції, яка все одно записує зобов’язання щодо об'єднання. Якщо вас це зацікавить, моя відповідь там показує, як ви могли це зробити - не настільки надійний, як моя опублікована відповідь тут, але сильні сторони обох, безумовно, можна поєднувати.
Каскабель

Відповіді:


972

Короткий відповідь

Поки ви здійснюєте швидке злиття вперед , тоді ви можете просто використовувати

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

Хоча відповідь Бурштину також буде працювати у випадках швидкого перемотування вперед, git fetchнатомість використання цього способу трохи безпечніше, ніж просто переміщення посилань на гілку, оскільки git fetchавтоматично запобігає випадковому не швидкому переправленню до тих пір, поки ви не використовуєте +в респект.

Довга відповідь

Ви не можете об'єднати гілку B у гілку A, не спершу перевіривши 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команда вийшла з ладу, якщо оновлення не швидко переходить вперед, тоді ви просто використовуєте рефлекс форми

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

Якщо ви хочете дозволити оновлення, які не швидко перемотають вперед, тоді ви додасте a +на передню частину refspec:

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

Зауважте, що ви можете передавати локальне репо як "віддалений" параметр, використовуючи .:

git fetch . <sourceBranch>:<destinationBranch>

Документація

З git fetchдокументації, яка пояснює цей синтаксис (моє наголос):

<refspec>

Формат <refspec>параметра - необов'язковий плюс +, за ним - джерело ref <src>, за ним - двокрапка :, а потім - номер призначення <dst>.

Віддалене відбиття, яке відповідає <src>, отримується, і якщо <dst>це не порожній рядок, локальний коефіцієнт, який відповідає, швидко передається за допомогою<src> . Якщо використовується необов'язковий плюс+, локальна посилання оновлюється, навіть якщо це не призводить до швидкого оновлення вперед.

Дивись також

  1. Git чек і злиття, не торкаючись робочого дерева

  2. Об’єднання без зміни робочого каталогу


3
git checkout --quiet HEADстановить git checkout --quiet --detachGit 1.7.5.
Рафа

6
Я виявив, що мені потрібно зробити: git fetch . origin/foo:fooоновити місцеве місце на моє місцеве походження / foo
weston,

3
Чи є причина, що частини "git checkout HEAD --quiet" та "git checkout --quiet -" входять у довгу відповідь, але не в коротку відповідь? Я думаю, це тому, що сценарій може бути запущений, коли ви перевірили майстра, навіть якщо ви могли просто зробити git pull?
Шон

8
чому "вибирати" команду робити "злиття" тут ... це просто не має сенсу; Якщо "pull" - "fetch", а потім "merge", повинен бути більш логічний еквівалент "merge --ff-only", який би оновлював "гілку" від "origin / branch" локально, враховуючи, що "fetch" має вже запущено.
Ед Рендалл

1
Дякую за git checkout -трюк! Так само просто cd -.
Steed

84

Ні, немає. Оформлення цільової гілки необхідне для вирішення конфліктів, серед іншого (якщо Git не в змозі автоматично їх об'єднати).

Однак якщо злиття буде таким, яке було б швидким вперед, вам не потрібно перевіряти цільову гілку, тому що насправді нічого не потрібно зливати - все, що вам потрібно зробити, це оновити гілку, щоб вказати на нова голова реф. Це можна зробити за допомогою git branch -f:

git branch -f branch-b branch-a

Буде оновлено, branch-bщоб вказати на голову branch-a.

Цей -fпараметр означає --force, що ви повинні бути обережними при його використанні.

Не використовуйте його, якщо ви абсолютно впевнені, що злиття буде швидким вперед.


46
Просто будьте дуже обережні, щоб цього не робити, якщо ви абсолютно не впевнені, що злиття буде швидким вперед! Не хочете пізніше усвідомлювати, що ви неправомірно порушили свої зобов'язання.
Каскабель

@FuadSaud Не зовсім. git resetпрацює лише у відділенні, що наразі перевіряється.
Бурштин

9
Такого ж результату (швидке перемотування вперед) досягається шляхом git fetch upstream branch-b:branch-b(взятої з цієї відповіді ).
Олівер

6
Щоб розширити коментар @ Олівера, ви також можете зробити це git fetch <remote> B:A, коли B і A - це зовсім інші гілки, але B можна перемотати вперед в A. Ви також можете передати своє місцеве сховище як "віддалене", використовуючи .як віддалений псевдонім: git fetch . B:A.

8
З питання ОП досить чітко видно, що злиття дійсно буде швидким. Незважаючи на те, це branch -fможе бути небезпечним, як ви вказуєте. Тому не використовуйте його! Використовуйте fetch origin branchB:branchB, що не вдасться безпечно, якщо злиття не буде швидким вперед.
Bennett McElwee

30

Як сказала Ембер, швидкі злиття вперед - це єдиний випадок, коли можна це зробити. Будь-яке інше об'єднання, можливо, має пройти все тристороннє злиття, застосовуючи виправлення, вирішуючи конфліктну угоду - а це означає, що навколо мають бути файли.

У мене, мабуть, є сценарій, який я використовую саме для цього: виконуючи швидкі злиття вперед, не торкаючись робочого дерева (якщо ви не зливаєтеся в HEAD). Це трохи довго, тому що воно, принаймні, трохи надійне - воно перевіряє, щоб злиття було швидким вперед, потім виконує його, не перевіряючи гілку, але даючи ті самі результати, що і у вас, - ви бачите diff --statпідсумок змін і запис у рефлогу - це точно як швидке злиття вперед, замість "скидання", яке ви отримаєте, якщо використовуєте branch -f. Якщо ви називаєте це git-merge-ffі помістіть його в директорії бен, ви можете назвати його як мерзотник команди: 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 Якщо хтось бачить якісь проблеми з цим сценарієм, будь ласка, коментуйте! Це була робота з написанням і забуттям, але я б рада її вдосконалити.


вас може зацікавити stackoverflow.com/a/5148202/717355 для порівняння
Філіп Оуклі

@PhilipOakley Швидкий погляд, що в основі робиться точно те саме, що і я. Але мій не є жорстким кодом до однієї пари гілок, у нього набагато більше помилок, він імітує вихід git-merge, і він робить те, що ви маєте на увазі, якщо ви називаєте це, поки ви насправді на гілці.
Каскабель

У мене в моєму каталозі \ bin є ваше ;-) Я просто забув про це і озирнувся, побачив цей сценарій, і це спонукало моє відкликання! У моїй копії є посилання, # or git branch -f localbranch remote/remotebranchяке нагадує джерело та варіанти. Дайте свій коментар на іншому посиланні +1.
Філіп Оуклі

3
+1, також може бути за замовчуванням "$branch@{u}"об'єднатись, щоб отримати гілку вище за течією (від kernel.org/pub/software/scm/git/docs/gitreitions.html )
orip

Дякую! Будь-який шанс, що ви могли б кинути на відповідь простим прикладом?
nmr

20

Це можна зробити лише в тому випадку, якщо злиття є швидким вперед. Якщо це не так, тоді в 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для переміщення гілки, за винятком того, що вона також записує її в рефлог, як ніби ви насправді зробили злиття.

Будь ласка, будь ласка , не робіть цього для чогось, що не є швидким вперед, або ви просто перевстановите свою філію на іншу комісію. (Для перевірки див., Чи git merge-base <branch> <commit>дає SHA1 відділення.)


4
Чи є спосіб зробити це невдалим, якщо він не може швидко просунутися вперед?
gman

2
@gman ви можете використовувати git merge-base --is-ancestor <A> <B>. "B" - це річ, яку потрібно об'єднати в "A". Прикладом може бути A = master та B = development, переконайтеся, що розвиток швидко перетворюється на master. Примітка: вона існує з 0, якщо її неможливо, існує з 1, якщо вона є.
eddiemoya

1
Не задокументовано в git-scm, але воно є на kernal.org. kernel.org/pub/software/scm/git/docs/git-merge-base.html
eddiemoya

Зробив швидку суть, щоб закінчити те, про що я говорив (насправді не перевіряв це, але мусиш вас змусити там здебільшого). gist.github.com/eddiemoya/ad4285b2d8a6bdabf432 ---- як бічна примітка, я мав більшу частину цього зручного, у мене є сценарій, який перевіряє ff, а якщо не дозволяє спочатку відновити потрібну гілку - тоді ff зливається - все, не перевіряючи нічого.
eddiemoya

12

У вашому випадку ви можете використовувати

git fetch origin branchB:branchB

що робить те, що ви хочете (припускаючи, що злиття швидко просувається вперед). Якщо гілка не може бути оновлена, оскільки вона вимагає нешвидкого злиття вперед, тоді це повідомлення не вдається безпечно.

Ця форма отримання також має кілька корисних варіантів:

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

Зауважте, що це <remote> може бути локальне сховище та <sourceBranch>може бути гілкою відстеження. Таким чином, ви можете оновити локальну гілку, навіть якщо вона не перевірена, без доступу до мережі .

Наразі мій доступ до сервера за поточним сервером здійснюється через повільний VPN, тому я періодично підключаюся, git fetchоновлюю всі віддалені пристрої та відключаюсь. Тоді, якщо, скажімо, віддалений майстер змінився, я можу зробити

git fetch . remotes/origin/master:master

щоб безпечно довести свого місцевого господаря до кінця, навіть якщо я зараз перевірив якусь іншу гілку. Не потрібно доступу до мережі.


11

Ще один, безумовно, досить брутальний спосіб - це просто відтворити галузь:

git fetch remote
git branch -f localbranch remote/remotebranch

Це викидає локальну застарілу гілку та відтворює її знову з такою ж назвою, тому використовуйте обережно ...


Я щойно бачив, що в оригінальній відповіді вже згадується гілка -f ... Все-таки я не бачу переваги в об'єднанні в рефлог для спочатку описаного випадку використання.
kkoehne

7

Ви можете клонувати репо і виконати злиття в новому репо. У тій же файловій системі це буде жорстке посилання, а не копіювання більшості даних. Закінчіть, потягнувши результати в оригінальний репо.


4

Введіть git-forward-merge :

Не потрібно перевіряти місце призначення, git-forward-merge <source> <destination>об'єднує джерело у відділення призначення.

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

Працює лише для автоматичного злиття, якщо є конфлікти, вам потрібно використовувати звичайне злиття.


2
Я думаю, що це краще, ніж git fetch <remote> <source>: <destination>, тому що швидкий перемотка вперед - це операція злиття, а не отримання і простіше писати. Дуже погано, але це не в git за замовчуванням.
Бінаріан

4

У багатьох випадках (наприклад, об'єднання), ви можете просто використовувати віддалену гілку, не потребуючи оновлення локальної гілки відстеження. Додавання повідомлення до рефлогу звучить як надмірність, і це зупинить його швидше. Щоб полегшити відновлення, додайте наступне у свій git config

[core]
    logallrefupdates=true

Потім введіть

git reflog show mybranch

щоб побачити недавню історію для вашого відділення


Я думаю, що розділ повинен бути [core]не [user]? (і це за замовчуванням увімкнено, для репостів з робочою областю (тобто,
неоголених

3

Я написав функцію оболонки для подібного випадку використання, з яким щодня стикаюся на проектах. Це, в основному, комбінація клавіш для актуалізації місцевих відділень загальною галуззю, як, наприклад, розробка перед відкриттям PR та ін.

Опублікувати це, навіть якщо ви не хочете використовувати checkout, якщо інші не заперечують цього обмеження.

glmh("git pull and merge here") автоматично checkout branchB, pullнайновіше, повторно checkout branchAта merge branchB.

Не стосується необхідності зберігання локальної копії відділенняA, але його можна легко змінити, додавши крок перед тим, як перевірити відділенняB. Щось на зразок...

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

Ще один спосіб ефективно зробити це:

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

Оскільки це нижній регістр -d, він видалить його лише тоді, коли дані ще десь будуть існувати. Це схоже на відповідь @ kkoehne, за винятком того, що не примушує. Через-t він знову встановить пульт.

У мене була дещо інша потреба, ніж OP, яка полягала в створенні нової гілки функції develop(або master) після об'єднання запиту на витяг. Це можна здійснити в однокласснику без сили, але це не оновлює локальну developгілку. Це лише питання перевірити нову гілку та вимкнути її origin/develop:

git checkout -b new-feature origin/develop

1

просто витягнути майстра, не перевіряючи майстра, який я використовую

git fetch origin master:master


1

Абсолютно можливо здійснити будь-яке злиття, навіть нешвидкісне злиття вперед, без цього git checkout. worktreeВідповідь на @grego хороший натяк. Щоб розширити це:

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

Тепер ви об'єднали місцеву відділення роботи з місцевою masterвідділенням, не перемикаючи замовлення.


1

Якщо ви хочете зберегти те саме дерево, що і одна з гілок, яку ви хочете об'єднати (тобто не справжнє "злиття"), ви можете зробити це так.

# 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"

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

Ви можете спробувати git worktree відкрити дві гілки поруч, це здається, що це може бути те, що ви хочете, але дуже відрізняється від деяких інших відповідей, які я бачив тут.

Таким чином, ви можете мати дві окремі гілки відстеження в одному і тому ж git repo, тому вам доведеться лише один раз отримати, щоб отримати оновлення в обох робочих деревах (замість того, щоб два рази git клонувати і git тягнути на кожному)

Worktree створить новий робочий каталог для вашого коду, де ви можете одночасно перевіряти іншу гілку замість того, щоб міняти гілки на місці.

Коли ви хочете його видалити, ви можете почистити його

git worktree remove [-f] <worktree>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.