現在のブランチにコミットされていない変更がある場合、別のブランチをチェックアウトします


349

ほとんどの場合、別の既存のブランチをチェックアウトしようとすると、現在のブランチにコミットされていない変更がある場合、Gitで許可されません。したがって、最初にそれらの変更をコミットまたは隠しておく必要があります。

ただし、Gitでは、変更をコミットしたり隠したりせずに別のブランチをチェックアウトできる場合があります。チェックアウトしたブランチに変更が反映されます。

ここでのルールは何ですか?変更がステージングされているか、ステージングされていないかは重要ですか?別のブランチに変更を加えても意味がありません。なぜgitがそれを許可するのですか?つまり、状況によっては役に立ちますか?

回答:


350

序論

ここでの観察は、作業を開始した後branch1branch2最初に別のブランチに切り替えるのが良いことを忘れているか、または気づいていない)、次のように実行することです。

git checkout branch2

時々、Gitは「OK、あなたはブランチ2にいます!」と言います。時々、Gitは「それはできない、あなたの変更の一部を失うだろう」と言います。

Gitで許可されない場合は、変更をコミットして永続的な場所に保存する必要があります。 あなたはgit stashそれらを保存するのに使いたいかもしれません。これは、その目的の1つです。 注ことgit stash savegit stash push、実際の手段は、「すべての変更をコミットしますが、まったくの枝の上に、その後、私は今の私どこからそれらを削除します。」これにより、切り替えが可能になります。進行中の変更はありません。その後git stash apply、切り替え後にそれらを使用できます。

サイドバー:git stash saveは古い構文です。git stash pushは、Gitバージョン2.13で導入されました。これは、への引数に関するいくつかの問題を修正しgit stash、新しいオプションを可能にするためです。基本的な方法で使用すると、どちらも同じことを行います。

必要に応じて、ここで読むのをやめることができます。

Gitがいる場合ではないだろう使用を:あなたは切り替えることができ、あなたはすでに救済策を持っていますgit stashgit commit。または、変更が簡単に再作成できる場合は、を使用git checkout -fして強制的に変更します。この答えは、変更を加え始めたにもかかわらず Gitがいつ許可するかについてのすべてgit checkout branch2です。なぜそれが時々機能し他の場合では機能しないですか?

ここでのルールは、ある意味では単純で、別の意味では複雑で説明が難しいものです。

上記の切り替えで変更を破棄する必要がない場合に限り、作業ツリーでコミットされていない変更を使用してブランチを切り替えることができます。

つまり、これはまだ簡略化されていることに注意してください。ステージングされたgit addsやgit rmsなどの非常に難しいコーナーケースがいくつかありますbranch1。A git checkout branch2はこれを行う必要があります。

  • すべてのファイルのためであるbranch1していないbranch21つのファイル削除。
  • すべてのファイルのためにあるbranch2していないbranch1、(適切な内容で)そのファイルを作成します。
  • 両方のブランチにあるすべてのファイルについて、バージョンbranch2が異なる場合は、作業ツリーのバージョンを更新します。

これらの各ステップでは、作業ツリーに何かが含まれる可能性があります。

  • 作業ツリーのバージョンがのコミットされたバージョンと同じ場合、ファイルの削除は「安全」ですbranch1。変更を加えた場合は「安全」ではありません。
  • ファイルがbranch2存在しない場合でも、ファイルの表示方法は「安全」です。2 現在は存在するが「内容が間違っている」場合、「危険」です。
  • そしてもちろん、作業ツリーバージョンが既ににコミットされてbranch1いる場合、ファイルの作業ツリーバージョンを別のバージョンに置き換えることは「安全」です。

新しいブランチ(git checkout -b newbranch)の作成は常に「安全」と見なされます。このプロセスの一部として作業ツリーでファイルが追加、削除、または変更されることはなく、インデックス/ステージング領域も変更されません。(注意:新しいブランチの開始点を変更せずに新しいブランチを作成する場合は安全です。ただし、たとえばに別の引数を追加するとgit checkout -b newbranch different-start-point、変更が必要にdifferent-start-pointなる場合があります。Gitは通常どおりチェックアウトの安全規則を適用します。 。)


1これは、ファイルが順番に単語の定義が必要です支店、であるためにそれが何を意味するのか定義する必要が枝を適切に。(も参照してください正確に、我々は「ブランチ」でどういう?)ここで、私は本当に平均で何コミットたへの分岐名解決します。そのパスのファイルであるであれば、ハッシュを生成します。代わりにエラーメッセージが表示される場合、そのファイルは存在しません。インデックスまたは作業ツリーにパスが存在するかどうかは、この特定の質問に答えるときには関係ありません。したがって、ここでの秘密は、それぞれの結果を調べることですP branch1git rev-parse branch1:Pbranch1Pgit rev-parsebranch-name:path。これは、ファイルが最大で1つのブランチに「ある」ために失敗するか、2つのハッシュIDを提供します。2つのハッシュIDが同じ場合、ファイルは両方のブランチで同じです。変更は必要ありません。ハッシュIDが異なる場合、ファイルは2つのブランチで異なり、ブランチを切り替えるために変更する必要があります。

ここで重要な概念は、コミット中のファイルは永久に凍結されるということです編集するファイルは明らかにフリーズされていません少なくとも最初は、2つの凍結されたコミット間の不一致のみを見ています。 残念ながら、我々 -またはGitは、また、ファイルに対処する必要がありませんあなたから離れて切り替えしようとしているコミットしているあなたにスイッチしようとしているコミットに。これにより、ファイルがインデックスや作業ツリーに存在する可能性があるため、残りの複雑化につながります。これらの作業している2つの特定の凍結されたコミットを存在させる必要はありません。

2 Gitが結局それを作成する必要がないように、「正しい内容」ですでに存在する場合、「安全な種類」と見なされる可能性があります。これを許可するGitの少なくともいくつかのバージョンを思い出しますが、テストしたところ、Git 1.8.5.4では「安全でない」と見なされていることがわかりました。同じことが、たまたま切り替え先ブランチに一致するように変更された変更済みファイルにも当てはまります。ここでも、1.8.5.4は「上書きされる」とだけ言っています。テクニカルノートの最後もご覧ください。バージョン1.5.somethingで最初にGitを使い始めてから、読み取りツリールールが変更されていないと思いますので、メモリに問題がある可能性があります。


変更がステージングされているか、ステージングされていないかは重要ですか?

はい、ある意味で。特に、変更をステージングしてから、作業ツリーファイルを「変更解除」できます。ここで違う二つの分岐内のファイル、だ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

この時点で、作業ツリーファイルはinboth1つに一致するbranch2私たちがしているにもかかわらず、branch1。この変更は、コミットの段階ではありません。これは、git status --shortここに示されているものです。

$ git status --short
 M inboth

space-then-Mは、「変更されたがステージングされていない」ことを意味します(より正確には、作業ツリーのコピーはステージングされた/インデックスのコピーとは異なります)。

$ 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

ここで行った変更は、ステージング領域から失われます(チェックアウトがステージング領域を介して書き込むため)。これはちょっとしたケースです。変更はなくなっていませんが、私がそれを上演したという事実なくなっています。

ブランチコピーとは異なる、ファイルの3番目のバリアントをステージングしてから、作業コピーを現在のブランチバージョンと一致するように設定します。

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

2 M秒ここ平均:からファイル異なり上演HEADファイル、および、段階的なファイルからファイル異なりツリー取り組んでいます。作業ツリーバージョンはbranch1(別名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それをインデックスに追加します。したがって、実際には各ファイルに3つの場所があります。現在のコミット、インデックス、作業ツリーです。

を実行するとgit checkout branch2、Gitがカバーの下で行うのは、現在のコミットとインデックスの両方にあるもののチップコミットを比較することですbranch2。現在のファイルと一致するファイルであれば、Gitはそのままにしておくことができます。それはすべてそのままです。両方のcommitで同じファイルであれば、Gitをそのままにしておくことできます。これらは、ブランチを切り替えるためのファイルです。

このインデックスがあるため、コミット切り替えを含むGitの多くは比較的高速です。実際にインデックスにあるのは、各ファイル自体ではなく、各ファイルのハッシュです。ファイル自体のコピーは、Gitがblobオブジェクトと呼ぶものとしてリポジトリに保存されます。これは、ファイルがコミットに保存される方法にも似ています。コミットには実際にはファイルが含まれず、Gitが各ファイルのハッシュIDにつながるだけです。そのため、GitはハッシュID(現在は160ビット長の文字列)を比較して、コミットXY同じファイルがあるかどうかを判断できます。次に、それらのハッシュIDをインデックス内のハッシュIDと比較することもできます。

これは、上記のすべての奇妙なコーナーケースにつながるものです。両方ともファイルを持つコミットXYpath/to/name.txtあり、のインデックスエントリがありpath/to/name.txtます。たぶん、3つのハッシュすべてが一致します。多分それらの2つは一致し、1つは一致しません。たぶん、3つすべてが異なるでしょう。そして、我々はまた、持っているかもしれないanother/file.txtことが唯一だXのみでYとであるか、今のインデックスではありません。XからYに切り替えるには、Git ファイルをコミットからインデックスにコピーするか、インデックスから削除する必要があるか?もしそうなら、それもする必要ありますファイルを作業ツリーにコピーするか、作業ツリーから削除します。場合とそれが「ケースをね、インデックスと作業ツリーのバージョンでは、より良い試合コミットさバージョンの少なくとも一つを持っていました。それ以外の場合、Gitは一部のデータを破壊します。

(これらすべての完全なルールは、git checkout予想されるgit read-treeドキュメントではなく「2つのツリーのマージ」というタイトルのセクションのドキュメントに記載されています。)


3
...もありgit checkout -m、ワークツリーとインデックスの変更を新しいチェックアウトにマージします。
jthill

1
この素晴らしい説明をありがとう!しかし、公式ドキュメントのどこに情報がありますか?それとも不完全ですか?もしそうなら、gitの信頼できるリファレンスは何ですか(うまくいけば、そのソースコード以外)?
2017

1
(1)できません。(2)ソースコード。主な問題は、Gitが常に進化していることです。たとえば、現在、SHA-256を使用して、またはSHA-256を支持して、SHA-1を増強または撤廃する大きな推進力があります。ただし、Gitのこの特定の部分は長い間安定しており、基礎となるメカニズムは単純です。Gitは現在のインデックスを現在のコミットとターゲットコミットと比較し、変更するファイル(ある場合)をターゲットコミットに基づいて決定します、次に、インデックスエントリを置き換える必要がある場合は、作業ツリーファイルの「クリーン度」をテストします。
torek

6
短い答え:ルールはありますが、平均的なユーザーが覚えていることは言うまでもなく、理解する希望がないので、ツールを理解して動作するのではなく、チェックアウトするときだけチェックアウトするという規則に従っている必要があります。現在のブランチはコミットされ、クリーンです。これがどのようにして未処理の変更を別のブランチに引き継ぐことが有益であるかという質問にどのように答えるかはわかりませんが、理解するのに苦労しているので見逃したかもしれません。
ニュートリノ2017

2
@HawkeyeParker:この回答は何度も編集されており、どれが大幅に改善されたのかはわかりませんが、「ブランチ内」にあるファイルの意味について何か追加してみます。結局のところ、ここでは「ブランチ」の概念が適切に定義されていないため、これは不安定になりますが、それはまた別の項目です。
torek

50

次の2つの選択肢があります。

git stash

その後、それらを取り戻すために:

git stash apply

または、ブランチに変更を加えて、リモートブランチを取得して、変更をブランチにマージできるようにします。これがgitの最大の特長の1つです。ブランチを作成してコミットし、他の変更を現在のブランチにフェッチできます。

あなたはそれは意味をなさないと言っていますが、あなたはそれをやっているだけなので、プルした後にそれらを自由にマージできます。明らかに、他の選択肢は、ブランチのコピーをコミットしてからプルすることです。あなたはそれをしたくない(その場合、私はあなたがブランチを望まないことに困惑しています)か、あなたは衝突を恐れているかのどちらかだと推定されています。


1
正しいコマンドではありませんgit stash applyか?ここにドキュメント。
Thomas8、2015

1
ちょうど私が探していたもの、一時的に別のブランチに切り替えて、何かを調べて、私が取り組んでいるブランチの同じ状態に戻るために。ロブありがとう!
Naishta 2016年

1
ええ、これはこれを行う正しい方法です。受け入れられた回答の詳細に感謝しますが、それは物事を必要以上に難しくしています。
Michael Leonard

5
また、隠し場所を維持する必要がない場合はgit stash pop、それを使用できます。適用が成功すると、隠し場所がリストから削除されます。
Michael Leonard

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はこの変更をマスターに適用しないので、マスターにはファイルに「hello world」と「そして戻ってきました」が含まれます
Xufeng

最初の例では、マスターは「hello world」のみをコミットしています。実験で 'hello world \ nさようなら世界'がコミットされました。ブランチの変更を行うには、file.txtを変更する必要があります。問題は、コミットされていない変更「hello world \ ngoodbye world \ n」に戻ったことです。
Gordolio 2014

1

正解は

git checkout -m origin/master

オリジンマスターブランチからの変更を、コミットされていないローカルの変更とマージします。


0

この変更をまったくコミットしたくない場合は、doを実行してください git reset --hard

次に、必要なブランチにチェックアウトできますが、コミットされていない変更は失われることに注意してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.