Попередні примітки
Спостереження тут полягає в тому, що після того, як ви почнете працювати 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 add
s, git rm
s та подібними - припустимо, ви перебуваєте 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
branch1
git rev-parse branch1:P
branch1
P
git rev-parse
branch-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
Тут два M
s означають: поетапний файл відрізняється від 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
документації під розділом "Злиття двох дерев" .)
git checkout -m
об'єднання змін вашого робочого дерева та індексів у новий замовлення.