Отримайте іншу гілку, коли в поточній гілці є неспроможні зміни


353

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

Однак, інколи Git дозволяє мені оформити іншу гілку, не вносячи і не приховавши ці зміни, і він внесе ці зміни до гілки, яку я оформив.

Яке правило тут? Чи має значення те, чи зміни є поетапними або нестандартними? Внесення змін до іншої гілки не має для мене сенсу, чому git дозволяє це іноді? Тобто чи корисна вона в деяких ситуаціях?

Відповіді:


355

Попередні примітки

Спостереження тут полягає в тому, що після того, як ви почнете працювати branch1(забувши або не розуміючи, що було б спочатку перейти на іншу гілку branch2), ви запускаєте:

git checkout branch2

Іноді Гіт каже "Гаразд, ти зараз на гілці2!" Іноді Гіт каже: "Я не можу цього зробити, я втратив би деякі ваші зміни".

Якщо Git не дозволить вам це зробити, вам доведеться зробити свої зміни, щоб зберегти їх десь постійно. Ви можете використовувати їх git stashдля збереження; це одна з речей, для якої призначена. Зауважте, що git stash saveабо git stash pushнасправді означає "Внести всі зміни, але взагалі немає жодної гілки, а потім видаліть їх там, де я зараз". Це дає можливість перемикатися: у вас зараз немає змін, що тривають. Потім ви можете git stash applyпісля переключення.

Бічна панель: git stash saveце старий синтаксис; git stash pushбула введена у Git версії 2.13, щоб виправити деякі проблеми з аргументами git stashта дозволити нові варіанти. Обидва роблять те саме, коли їх використовують основними способами.

Ви можете перестати читати тут, якщо вам подобається!

Якщо Git не дозволить вам переключитися, у вас уже є засіб захисту: використання git stashабо git commit; або, якщо ваші зміни є тривіальними для їх повторного створення, використовуйте git checkout -fдля примушування. Ця відповідь стосується того, коли Git дозволить вам, git checkout branch2навіть якщо ви почали робити якісь зміни. Чому це працює іноді , а не в інші часи?

Правило тут просте в один спосіб, а складне / важко пояснити іншим:

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

Це - і зауважте, що це все ще спрощено; є декілька надзвичайно складних кутових випадків із поетапними git adds, git rms та подібними - припустимо, ви перебуваєте branch1. A git checkout branch2повинен був би зробити це:

  • Для кожного файлу , який знаходиться в branch1і НЕ в branch2, 1 видалити цей файл.
  • Для кожного файлу , який знаходиться в branch2і НЕ в branch1, створити файл (з відповідним змістом).
  • Для кожного файлу, що знаходиться в обох гілках, якщо версія в branch2іншому, оновіть версію робочого дерева.

Кожен із цих кроків може щось виправити на вашому робочому дереві:

  • Видалення файлу є "безпечним", якщо версія в дереві робіт є такою ж, як і в допущеній версії branch1; це "небезпечно", якщо ви внесли зміни.
  • Створення файлу таким, яким він відображається, branch2є "безпечним", якщо його зараз немає. 2 Це "небезпечно", якщо він існує зараз, але має "неправильний" вміст.
  • І звичайно, заміна версії файлу робочого дерева на іншу версію є "безпечною", якщо версія робочого дерева вже прихильна branch1.

Створення нової гілки ( git checkout -b newbranch) завжди вважається "безпечною": жодні файли не будуть додані, видалені або змінені в робочому дереві в рамках цього процесу, а область індексу / інсценізації також не стосується. (Caveat: це безпечно при створенні нової гілки без зміни початкової точки нової гілки; але якщо ви додасте інший аргумент, наприклад git checkout -b newbranch different-start-point, це, можливо, доведеться змінити, щоб перейти до different-start-point. Git буде застосовувати правила безпеки каси, як зазвичай .)


1 Це вимагає, щоб ми визначили, що означає, щоб файл знаходився у гілці, а це, в свою чергу, вимагає правильного визначення слова гілка . (Дивіться також Що саме ми маємо на увазі під «гілку»? ) Ось, що я на самом деле означає це Комміт , до якого ім'я-гілки розсмоктується: файл , чий шлях є в разі виробляє хеш. Цей файл не знаходиться, якщо ви отримаєте повідомлення про помилку. Існування шляху у вашому індексі чи на робочому дереві не має значення при відповіді на це конкретне запитання. Таким чином, секрет тут полягає у вивченні результатів по кожномуP branch1git rev-parse branch1:Pbranch1Pgit rev-parsebranch-name:path. Це або не вдається, оскільки файл "в" не більше однієї гілки, або дає два ідентифікатори хеша. Якщо два ідентифікатора хеша однакові , файл однаковий в обох гілках. Ніяких змін не потрібно. Якщо ідентифікатори хешу відрізняються, файл у двох гілках відрізняється, і його потрібно змінити, щоб переключити гілки.

Ключовим поняттям тут є те, що файли у комітах заморожуються назавжди. Файли, які ви будете редагувати, очевидно, не заморожені. Ми, принаймні спочатку, дивимось лише на невідповідність двох заморожених комітетів. На жаль, ми - або Git - також маємо мати справу з файлами, які не належать до комісії, з якої ви збираєтесь перейти, і є комітетом, на який ви збираєтесь перейти. Це призводить до залишків ускладнень, оскільки файли можуть також існувати в індексі та / або в дереві роботи, не маючи існування цих двох заморожених комітетів, з якими ми працюємо.

2 Це може вважатися "таким чином безпечним", якщо воно вже існує з "правильним вмістом", так що Git не повинен його створювати. Я пригадую принаймні деякі версії Git, що це дозволяють, але тестування лише зараз показує, що він вважається "небезпечним" у Git 1.8.5.4. Цей же аргумент застосовуватиметься до модифікованого файлу, який, можливо, буде модифікований так, щоб відповідати гілці, що перемикається. Знову ж таки, 1.8.5.4 говорить просто "було б переписано". Дивіться також кінець технічних зауважень: моя пам’ять може бути несправною, оскільки я не думаю, що правила дерева дерева читання змінилися, оскільки я вперше почав використовувати Git у версії 1.5.


Чи має значення те, чи зміни є поетапними або нестандартними?

Так, певним чином. Зокрема, ви можете поетапно змінити, а потім "де-змінити" файл робочого дерева. Ось файл у двох гілках, що відрізняється branch1та branch2:

$ git show branch1:inboth
this file is in both branches
$ git show branch2:inboth
this file is in both branches
but it has more stuff in branch2 now
$ git checkout branch1
Switched to branch 'branch1'
$ echo 'but it has more stuff in branch2 now' >> inboth

На даний момент файл робочого дерева відповідає файлу inbothв branch2, хоч ми і перебуваємо branch1. Ця зміна не є поетапною для здійснення фіксації, що git status --shortтут показано:

$ git status --short
 M inboth

Пробіл тоді-М означає "модифікований, але не поетапний" (а точніше, копія робочого дерева відрізняється від поетапної / індексної копії).

$ git checkout branch2
error: Your local changes ...

Гаразд, тепер давайте поставимо копію робочого дерева, яка, як ми вже знаємо, також відповідає копії branch2.

$ git add inboth
$ git status --short
M  inboth
$ git checkout branch2
Switched to branch 'branch2'

Тут постановочні та робочі копії обидва відповідали тому, що було branch2, тому замовлення було дозволено.

Спробуємо ще один крок:

$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches

Зміни, які я здійснив, втрачаються з області постановки зараз (оскільки каса пише через область постановки). Це трохи кутовий випадок. Зміна не було, але той факт , що я поставив його, як зник.

Давайте поставимо третій варіант файлу, який відрізняється від будь-якої гілки-копії, а потім встановимо робочу копію відповідно до поточної версії гілки:

$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth

Тут два Ms означають: поетапний файл відрізняється від HEADфайла, а файл робочого дерева відрізняється від поетапного файлу. Версія робочого дерева відповідає версії branch1(aka HEAD):

$ git diff HEAD
$

Але замовлення git checkoutне дозволить:

$ git checkout branch2
error: Your local changes ...

Давайте встановимо branch2версію як робочу версію:

$ git show branch2:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
diff --git a/inboth b/inboth
index ecb07f7..aee20fb 100644
--- a/inboth
+++ b/inboth
@@ -1 +1,2 @@
 this file is in both branches
+but it has more stuff in branch2 now
$ git diff branch2 -- inboth
$ git checkout branch2
error: Your local changes ...

Навіть незважаючи на те, що поточна робоча копія відповідає тій branch2, що введена, поетапний файл цього немає, тож git checkoutця копія втратить, а git checkoutвідхилена.

Технічні записки - лише для шалено цікавих :-)

Основним механізмом впровадження цього є індекс Git . Індекс, який також називають "зоною постановки", - це те, де ви будуєте наступну комісію: вона починається відповідно до поточної фіксації, тобто що б ви не перевірили зараз, а потім кожного разу, коли ви git addстворюєте файл, ви замінюєте версію індексу з тим, що ви маєте на своєму робочому дереві.

Пам'ятайте, дерево роботи - це те, де ви працюєте над своїми файлами. Тут вони мають свою нормальну форму, а не якусь особливу єдину корисну для Git форму, як це роблять у комітах та в індексі. Отже, ви витягаєте файл з коміту, через індекс, а потім у робоче дерево. Змінивши його, ви git addйого в індекс. Таким чином, насправді є три місця для кожного файлу: поточний фіксатор, індекс та дерево роботи.

Під час запуску git checkout branch2те, що Git робить під обкладинками, - це порівняння підказки з branch2тим, що є як у поточній комісії, так і в індексі зараз. Будь-який файл, який відповідає тому, що є зараз, Git може залишити в спокої. Це все недоторкано. Будь-який файл, який однаковий в обох комітах , Git також може залишити в спокої - і це ті, які дозволяють вам перемикати гілки.

Значна частина Git, включаючи комутацію комутацій, відносно швидка завдяки цьому індексу. Фактично в індексі - це не кожен файл, а хеш кожного файлу . Сама копія файлу зберігається як те, що Git називає об'єктом blob , у сховищі. Це подібно до того, як файли зберігаються у комітах: комісії насправді не містять файли , вони просто призводять Git до ідентифікаційного хешу кожного файлу. Таким чином, Git може порівнювати хеш-ідентифікатори (зараз це рядки довжиною 160 біт), щоб вирішити, чи мають у комітетів X і Y один і той же файл чи ні. Потім він може порівняти ці хеш-ідентифікатори з хеш-ідентифікатором також в індексі.

Це те, що призводить до всіх випадків дивних куточків вище. У нас є коміти X і Y, які мають обоє файли path/to/name.txt, і у нас є запис в індексі path/to/name.txt. Можливо, всі три хеші збігаються. Можливо, дві з них відповідають, а одна ні. Можливо, всі троє різні. І ми можемо також мати another/file.txtце лише в X або лише в Y , або зараз є або немає в індексі. Кожен з цих різних випадків потребує окремого розгляду: чи потрібно Git скопіювати файл з фіксації в індекс або видалити його з індексу, щоб перейти з X на Y ? Якщо так, то це теж повинноскопіюйте файл на робоче дерево або видаліть його з робочого дерева. І якщо це так, версії індексу та дерева дерев повинні краще відповідати принаймні одній із допущених версій; інакше Git буде приховувати деякі дані.

(Повні правила для всього цього описані не в git checkoutдокументації, як ви могли очікувати, а в git read-treeдокументації під розділом "Злиття двох дерев" .)


3
... також є git checkout -mоб'єднання змін вашого робочого дерева та індексів у новий замовлення.
jthill

1
Дякую за це чудове пояснення! Але де я можу знайти інформацію в офіційних документах? Або вони неповні? Якщо так, то яка авторитетна посилання на git (сподіваємось, інший, ніж його вихідний код)?
макс

1
(1) ви не можете, і (2) вихідний код. Основна проблема полягає в тому, що Git постійно розвивається. Наприклад, зараз існує великий поштовх до збільшення або канавки SHA-1 з використанням або на користь SHA-256. Ця конкретна частина Git вже досить давно стабільна, і основний механізм є простим: Git порівнює поточний індекс з поточним та цільовим комісіями та вирішує, які файли змінити (якщо такі є) на основі цільової комісії , потім тестує "чистоту" файлів робочого дерева, якщо введення індексу потрібно буде замінити.
торек

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

2
@HawkeyeParker: ця відповідь зазнала численних змін, і я не впевнений, що хтось із них її значно покращив, але я спробую додати щось про те, що означає, щоб файл знаходився "у гілці". Зрештою, це буде хиткою, оскільки поняття "гілка" тут не визначено в першу чергу належним чином, але це ще один пункт.
торек

51

У вас є два варіанти: приховуйте зміни:

git stash

потім пізніше, щоб повернути їх:

git stash apply

або помістіть свої зміни на гілку, щоб ви могли отримати віддалену гілку, а потім об'єднати зміни до неї. Це одна з найбільших речей щодо git: ви можете зробити гілку, взяти на себе зобов’язання, а потім отримати інші зміни до гілки, на якій ви знаходилися.

Ви кажете, що це не має ніякого сенсу, але ви це робите лише для того, щоб ви могли їх об'єднати за бажанням після того, як ви потягнете. Очевидно, ваш інший вибір - взяти на себе копію відділення, а потім зробити тягнення. Презумпція полягає в тому, що ви або не хочете цього робити (в такому випадку я здивований, що ви не хочете відділення) або боїтеся конфліктів.


1
Не правильна команда git stash apply? тут документи.
Thomas8

1
Тільки те, що я шукав, тимчасово переходити на різні гілки, щось шукати і повертатися до того самого стану гілки, над яким я працюю. Дякую Роб!
Найшта

1
Так, це правильний спосіб зробити це. Я ціную деталі у прийнятій відповіді, але це робить речі складнішими, ніж вони повинні бути.
Майкл Леонард

5
Крім того, якщо у вас немає ніякої необхідності зберігати сховище, ви можете скористатися, git stash popі воно скине копію зі списку, якщо вона успішно застосовується.
Майкл Леонард

1
краще використовувати git stash pop, якщо ви не маєте наміру вести журнал підписок у своїй історії репо
Damilola Olowookere

14

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

Приклад:

$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "adding file.txt"

$ git checkout -b experiment
$ echo 'goodbye world' >> file.txt
$ git add file.txt
$ git commit -m "added text"
     # experiment now contains changes that master doesn't have
     # any future changes to this file will keep you from changing branches
     # until the changes are stashed or committed

$ echo "and we're back" >> file.txt  # making additional changes
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
    file.txt
Please, commit your changes or stash them before you can switch branches.
Aborting

Це стосується як відслідковуваних файлів, так і відстежених файлів. Ось приклад файлу, що не відслідковується.

Приклад:

$ git checkout -b experimental  # creates new branch 'experimental'
$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "added file.txt"

$ git checkout master # master does not have file.txt
$ echo 'goodbye world' > file.txt
$ git checkout experimental
error: The following untracked working tree files would be overwritten by checkout:
    file.txt
Please move or remove them before you can switch branches.
Aborting

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

$ echo 'experimental change' >> file.txt # change to existing tracked file
   # I want to save these, but not on master

$ git checkout -b experiment
M       file.txt
Switched to branch 'experiment'
$ git add file.txt
$ git commit -m "possible modification for file.txt"

Насправді я все ще не дуже розумію. У вашому першому прикладі, після того, як ви додали "і ми повернулися", він говорить, що локальна зміна буде перезаписана, що локальна зміна саме? "і ми повернулися"? Чому git не переносить цю зміну в master так, щоб у файлі master
містилися

У першому прикладі майстер лише здійснив "привіт світ". експеримент здійснив "привіт світ \ nгудній світ". Для того, щоб відбулася зміна гілки, файл.txt потрібно змінити, проблема полягає в тому, що є невмілі зміни "привіт, світ \ ngoodbye world \ nnd we back".
Гордоліо

1

Правильна відповідь

git checkout -m origin/master

Він об'єднує зміни з відділення головного походження з вашими локальними навіть неприйнятими змінами.


0

Якщо ви не хочете, щоб ці зміни взагалі були здійснені git reset --hard.

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

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