Gitでコミットする前にステージが必要なのはなぜですか?


102

私はバージョン管理に不慣れです。「コミット」とは、作業中の新しい「現在の」バージョンを更新しながら、基本的にバックアップを作成することを理解しています。

私が理解していないのは、ステージングが実際的な観点から何であるかです。ステージングは​​名前だけで存在するものですか、それとも目的を果たしますか?あなたがコミットすると、とにかくすべてをコミットするでしょう、そうですか?

編集:用語がわかりにくいかもしれません。「ステージング」ファイルは「追跡」ファイルと同じですか?


6
いいえ。追跡されたファイルは、(通常は以前のコミットから)リポジトリで認識されているファイルです。ステージングされたファイルは、インデックスに追加されたファイルで、後でコミットするために使用されます。
Mark Peters、

回答:


82

コミットすると、インデックス(「ステージングされた」ファイル)の変更のみがコミットされます。これには多くの用途がありますが、最も明白なのは、作業上の変更を小さな自己完結型の部分に分割することです。機能の実装中にバグを修正した可能性があります。あなたはできるgit addだけのファイル(またはことをgit add -p、ファイルの一部だけを追加するために!)とは、他のすべてをコミットする前にそのバグ修正をコミットします。あなたが使用しているgit commit -a場合はadd、コミットの直前にすべてのものを強制しています。-aステージングファイルを利用する場合は使用しないでください。

また、--cached多くのコマンドを使用して、ステージングされたファイルを中間作業コピーとして扱うこともできます。たとえば、git diff --cachedはステージがどのように異なるかを示しHEAD、他の実際の変更を混ぜずにコミットしようとしていることを確認できます。


25
もう1つの本当に一般的な使用法は、変更の一部をコミットしてはならない場合です。たとえば、良いものをステージングしてコミットし、で悪いものを吹き飛ばすことができますgit reset --hard
カスカベル

3
あなたの例では@BenJackson、ステージ+コミットと選択的コミットの違いは何ですか?違いはありません。
Eugenio

9
個人的には「ステージングのポイントは何ですか?」に対して満足のいく反応が得られませんでした。質問。率直に言って、それは単に不要です。私はすでにローカルブランチを使用しているので、ビルドが壊れるリスクはありません。変更に完全に満足するまでは、公開してプルリクエストを行いません。私はすでにコミットを論理的に分割することができます。中間ステップは必要ありません。そのため、それを使用することはありません。
mrplainswalker 2017

3
あなたが本当に質問に答えたとは思いません。「しかし、最も明白なのは、作業上の変更を小さな自己完結型の部分に分割することです。」なぜ誰かが変更を小さなものに分解したいのでしょうか?最初に変更しようとしていたコードを修正する前に、単一のバグ修正を追加してコミットする場合は、追加してコミットするのではなく、なぜそのバグ修正をコミットしないのですか?
kiwicomb123 2017

1
@ kiwicomb123通常、何か他の作業中にバグを発見し、独自のログメッセージを使用して独自のコミットで修正し、他の場所で修正するマージ/チェリーピック/リベースする柔軟性を持たせたいためです。
ベンジャクソン

26
  • ステージング領域は、コミットをより小さくするためのコントロールを提供します。コードに1つの論理的な変更を加え、変更されたファイルをステージング領域に追加し、最後に変更が悪い場合は、前のコミットにチェックアウトするか、変更をコミットします。これにより、タスクを小さなタスクに分割し、小さなコミットを行う柔軟性が得られます。変更。ステージング領域を使用すると、小さなタスクに集中しやすくなります。
  • また、休憩を取り、休憩前にどれだけの作業を行ったかを忘れるというオファーも提供します。1つの論理的な変更を行うために3つのファイルを変更する必要があり、最初のファイルを変更していて、他の変更を開始するまで長い休憩が必要であるとします。この時点ではコミットできず、どのファイルを使用したかを追跡したいので、戻ってきた後、どれだけの作業が行われたかを覚える必要はありません。ステージング領域にファイルを追加すると、作業が保存されます。戻ってきgit diff --stagedたら、変更したファイルと変更したファイルを確認し、他の変更を開始します。

13

ステージングの実用的な目的の1つは、ファイルコミットの論理的な分離です。

ステージングを使用すると、引き続きファイル/作業ディレクトリの編集を行うことができ、準備が整ったと思われるときに部分的にコミットすることができるため、論理的に関連のない編集には別々のステージを使用できます。

あなたが4ファイルがあるとしfileA.htmlfileB.htmlfileC.htmlfileD.html。あなたはすべての4つのファイルに変更を加え、コミットする準備ができているが、中に変更fileA.htmlし、fileB.html論理的に関連している(例えば、両方のファイルで同じ新機能の実装)しばらくの変化fileC.htmlfileD.html独立したファイルに、以前に論理的に無関係です。ファイルfileA.htmlを最初にステージングしfileB.htmlてコミットできます。

git add fileA.html
git add fileB.html
git commit -m "Implemented new feature XYZ"

次に、次のステップで残りの2つのファイルに変更をステージングしてコミットします。

git add fileC.html
git add fileD.html
git commit -m "Implemented another feature EFG"

6
この例では、ステージングが本当に必要かどうかはわかりません。4つのファイルすべてを編集した後、fileA.htmlとfileB.htmlをコミットするだけであれば、ステージングせずにコミットできます。コマンド: git commit -m "Implemented new feature XYZ" fileA.html fileB.html git addコマンドがなくても問題なく動作します。私はステージングがコンセプトではないSubversionの世界から来ているので、Gitステージングの有用性について確信がありません
Pavan

5

gitコマンドの使用と、Githubのリポジトリでログファイルが維持されていると想像するaddと、理解しやすいcommitでしょう。私の典型的なプロジェクトのログファイルは次のようになります。

---------------- Day 1 --------------------
Message: Complete Task A
Index of files changed: File1, File2

Message: Complete Task B
Index of files changed: File2, File3
-------------------------------------------

---------------- Day 2 --------------------
Message: Correct typos
Index of files changed: File3, File1
-------------------------------------------
...
...
...and so on

私は通常、1日をgit pullリクエストで開始し、リクエストで終了しgit pushます。したがって、1日のレコード内のすべてが、それらの間で発生することに対応します。毎日、いくつかのファイルを変更する必要がある1つ以上の論理的なタスクが完了しています。そのタスク中に編集されたファイルは、インデックスにリストされます。

これらの各サブタスク(ここではタスクAとタスクB)は個別のコミットです。このgit addコマンドは、「変更されたファイルのインデックス」リストにファイルを追加します。このプロセスは、ステージングとも呼ばれます。git commitコマンドレコードは/変更やカスタムメッセージとともに対応するインデックスリストを確定します。

変更するのはリポジトリのローカルコピーだけであり、Githubのローカルコピーは変更しないことに注意してください。この後、「git push」を実行した場合にのみ、これらの記録されたすべての変更を、コミットごとのインデックスファイルとともに、メインリポジトリ(Github)に記録します。

例として、架空のログファイルの2番目のエントリを取得するには、次のようにします。

git pull
# Make changes to these files
git add File3 File4
# Verify changes, run tests etc..
git commit -m 'Correct typos'
git push

一言で言えば、git addそしてgit commitあなたが体系的論理サブ変化にメインリポジトリに変更を打破することができます。他の回答やコメントが指摘しているように、もちろんそれらにはもっと多くの用途があります。ただし、これは最も一般的な使用法の1つであり、Svnのような他の一般的なものとは異なり、Gitが多段階のリビジョン管理システムであることの背後にある推進原則です。


2

ステージング領域は、より柔軟にコミットを作成するのに役立ちます。作成とは、コミットを論理ユニットに分割することを意味します。保守可能なソフトウェアが必要な場合、これは非常に重要です。これを達成する最も明白な方法:

単一の作業ディレクトリで複数の機能/バグに取り組みながら、意味のあるコミットを作成することができます。すべてのアクティブな作業を含む単一の作業ディレクトリがあることも非常に便利です。(これは、変更がファイルと重複しない限り、ステージング領域なしで実行できます。また、重複するかどうかを手動で追跡する責任も追加されます)

あなたはここでより多くの例を見つけることができます: インデックスの使用

そして最良の部分は、このワークフローのリストで利点が止まることはありません。独自のワークフローが登場した場合、ステージング領域が役立つことはほぼ確実です。


2

展開するにはベン・ジャクソンの答えは罰金、密接に元の質問で見てみましょうです。(わざわざタイプの質問をする理由については彼の回答を参照してください。これは何が起こっているかについての詳細です。)

私はバージョン管理に不慣れです。「コミット」とは、作業中の新しい「現在の」バージョンを更新しながら、基本的にバックアップを作成することを理解しています。

これではありませんかなり右。バックアップとバージョン管理は確かに関連しています。正確には、意見の問題であるいくつかの事柄にどれほど強く依存しているかですが、意図があったとしても、確かにいくつかの違いがあります。すべてのストレージメディアなどを含む建物全体)。バージョン管理は通常、よりきめの細かいやり取りのために設計されており、バックアップにはない機能を提供します。通常、バックアップはしばらく保存され、その後「古すぎる」として破棄されます。重要なのは、より新しいバックアップです。バージョン管理は通常、コミットされたすべてのバージョンを永久に保存します。

私が理解していないのは、ステージングが実際的な観点から何であるかです。ステージングは​​名前だけで存在するものですか、それとも目的を果たしますか?あなたがコミットすると、とにかくすべてをコミットするでしょう、そうですか?

はいといいえ。ここでのGitのデザインは多少独特です。個別のステージング手順を必要としないバージョン管理システムが存在します。たとえば、Mercurialは、それ以外の点では使用法がGitによく似ていますがhg add、まったく新しいファイルを導入する最初の手順以外に、別の手順必要ありません。Mercurialでは、hgいくつかのコミットを選択するコマンドを使用し、作業を行ってからを実行hg commitすれば完了です。Gitではgit checkout1を使用してから1を実行しgit add、次にを実行し、次にを実行しますgit commit。なぜ余分なgit addステップですか?

ここでの秘密は、Gitがさまざまに呼ぶ、インデックスステージング領域、またはときどき(ごく最近では)キャッシュです。これらはすべて同じものの名前です。

編集:用語がわかりにくいかもしれません。「ステージング」ファイルは「追跡」ファイルと同じですか?

いいえ、ただしこれらは関連しています。追跡されたファイルはGitリポジトリのインデックスに存在するものです。インデックスを正しく理解するには、コミットの理解から始めるのがよいでしょう。


Gitバージョン2.23以降、のgit switch代わりに使用できますgit checkout。この特定のケースでは、これら2つのコマンドはまったく同じことを行います。新しいコマンドはgit checkout、あまりにも多くのものでいっぱいになったために存在しています。彼らは、2つの別々のコマンドに出し分け、しまったgit switchgit restoreのGitを使用することが簡単かつ安全にするために、。


コミット

Gitでは、コミットにより、Gitが認識しているすべてのファイルの完全なスナップショットが保存さます。(Gitはどのファイルを知っていますか?次のセクションで説明します。)これらのスナップショットは、読み取り専用、Git専用、圧縮、および重複排除された特別な形式で保存され、通常はGit自体のみが読み取ることができます。 。(そこより多くのものがそれぞれよりもコミットしていただこのスナップショットが、それは我々がここでカバーするすべてです。)

重複除外はスペースの節約に役立ちます。通常、変更するのは数個のファイルのみで、その後新しいコミットを作成します。だから、ほとんどのファイルのコミット以前のファイルをコミットすると、ほとんど同じです。これらのファイルを直接再利用するだけで、Gitは多くのスペースを節約します。1つのファイルのみを変更した場合、新しいコミットは1つの新しいコピーのスペースのみを使用します。それでも圧縮されます(実際には後で発生しますが、場合によっては非常に圧縮.gitされます)。通常の日常のファイルに展開されると、ディレクトリは実際に含まれているファイルよりも小さくなります。コミットされたファイルは常にフリーズされるため、重複除外は安全です。誰も変更できないので、コミットがお互いのコピーに依存しても安全です。

ただし、保存されたファイルは、常に凍結されたGit専用の特別な形式であるため、Gitは各ファイルを通常の日常のコピーに展開する必要があります。この通常のコピーはGitのコピーではありません。それは、あなたのコピーであり、あなた意図したとおりに行うことができます。あなたがそうするように言ったとき、Gitはこれらに書き込むだけなので、あなたはあなたのコピーを扱うことができます。これらの使用可能なコピーは、作業ツリーまたは作業ツリーにあります。

これは、特定のコミットをチェックアウトすると、自動的に各ファイルの2つのコピーが存在することを意味します。

  • 現在のcommitには、Gitが常時フリーズされたGit化されたコピーがあります。このコピーは変更できません(もちろん、別のコミットを選択したり、新しいコミットを作成したりすることはできます)。

  • 作業ツリーに通常形式のコピーがあります。コンピューターのコマンドを使用して、これに対してやりたいことをすべて実行できます。

他のバージョン管理システム(上記のMercurialを含む)は、これらの2つのコピーとともにここで停止します。作業ツリーのコピーを変更してコミットするだけです。Git ...はしません。

インデックス

これら2つのコピーの間に、Git はすべてのファイルの3番目のコピー2を保存します。この3番目のコピーは凍結された形式ですが、コミットの凍結されたコピーとは異なり、変更できます。変更するには、を使用しますgit add

git add指令手段は、ファイルのインデックスコピーが作業ツリーのコピーを一致させます。つまり、Gitに次のように伝えています。更新された作業ツリーのコピーを圧縮し、重複を排除して、新しいコミットに凍結する準備をすることで、インデックスにある凍結フォーマットの重複排除されたコピーを置き換えます。 を使用しない場合git addでも、インデックスには現在のコミットからの固定形式のコピーが保持されます。

を実行するとgit commit、Gitはすぐにインデックスにあるものをパッケージ化して、新しいスナップショットとして使用します。すでにフリーズ形式であり、事前に重複が排除されているので、Gitは多くの追加作業を行う必要はありません。

これは、追跡されていないファイルのすべてについても説明します。追跡されていないファイルとは、作業ツリーにはあるが現在 Gitのインデックスにはないファイルです。この状態でファイルがどのように巻き取られるかは関係ありません。多分あなたはあなたのコンピュータの他の場所からあなたの作業ツリーにそれをコピーしました。たぶんここで作りたてだろう。Gitのインデックスにコピーがあった可能性あります、そのコピーをで削除しましたgit rm --cached。何らかの方法で、作業ツリーにここにコピーがありますが、Gitのインデックスにはコピーがありません。ここで新しいコミットを行うと、そのファイル新しいコミットには含まれません

git checkout最初に埋めあなたがチェックアウトコミットからGitリポジトリのインデックス。したがって、インデックスはコミットと一致し始めます。Gitは、この同じソースから作業ツリーにも入力します。したがって、最初は3つすべてが一致します。作業ツリーとgit addそれらのファイルを変更すると、インデックスと作業ツリーが一致します。次に実行するgit commitと、Gitはインデックスから新しいコミットを作成し、3つすべてが再び一致します。

Gitはインデックスから新しいコミットを作成するため、ように表現できます。Gitのインデックスは、作成する次のコミットを保持します。 これは、競合するマージ中にGitのインデックスが果たす拡張された役割を無視しますが、とりあえずそれを無視したいと思います。:-)

これですべてですが、それでもまだかなり複雑です!Gitのインデックスの内容を正確に確認する簡単な方法がないため、これは特にトリッキーです。3 しかし、そこにある何かはかなり便利だ方法で、起こっているのがわかりますGitのコマンドは、そのコマンドがありますgit status


2技術的には、これは実際にはまったくコピーではありません。代わりに、それはGit化されたファイルへの参照であり、事前重複排除されたものすべてです。ここには、モード、ファイル名、ステージング番号、Gitを高速化するためのいくつかのキャッシュデータなど、さらに多くのものがあります。しかし、あなたはGitリポジトリの低レベルcommands-のいくつかの作業に入る場合を除きgit ls-files --stage、およびgit update-index特定の-あなただけのコピーとして考えることができます。

3git ls-files --stageコマンドを使用すると、Gitリポジトリのインデックス内のすべてのファイルの名前とステージング番号が表示されますが、通常これは非常に便利な、とにかくではありません。


git status

このgit statusコマンドは、実際には2つの個別のgit diffコマンドを実行することで機能します(また、どのブランチにいるかを通知するなど、他の便利な機能も実行します)。

1つ目git diffは、現在のコミット(いつまでもフリーズされている)をGitのインデックスにあるものと比較します。同じファイルの場合、Gitは何もしません。異なるファイルの場合、Gitはこのファイルがコミット用にステージングされていることを通知します。これは、すべての新しいファイルを-場合は含まれコミットはありません。sub.pyそれには、しかし、インデックスがない持っているsub.pyことで、このファイルを追加、および任意の削除されたファイルされ、だった(とされる)そのコミットではなくではありませんもうインデックス(git rmおそらく)。

2番目git diffは、Gitのインデックス内のすべてのファイルを作業ツリー内のファイルと比較します。同じファイルの場合、Gitは何もしません。異なるファイルの場合、Gitはこのファイルがコミット用にステージングされていないことを通知します。最初のdiffとは異なり、この特定のリストにはまったく新しいファイル含まれません。ファイルuntrackedが作業ツリーに存在するが、Gitのインデックスには存在しない場合、Gitはそれを追跡されていないファイルのリストに追加するだけです4

最後に、これらの追跡されていないファイルをリストに蓄積すると、それらのファイルの名前もgit statusアナウンスされますが、特別な例外があります。ファイルの名前がファイルにリストされている場合、この最後のリストは抑制されます。 追跡されたファイル(Gitのインデックスにあるファイル)を一覧表示しても、ここでは効果がないことに注意してください。ファイルはインデックスに含まれているため、たとえに一覧表示されていても、比較され、コミットされます。無視ファイルは、「追跡されていないファイル」の問題を抑制するだけです。5.gitignore.gitignore.gitignore


4の短いバージョンを使用する場合git statusgit status -s—追跡されていないファイルはそれほど分離されていませんが、原理は同じです。このようにファイルを蓄積するとgit status、ディレクトリ名を出力するだけで、追跡されていない一連のファイルの名前を要約することもできます。完全なリストを取得するには、git status -uallまたはを使用しますgit status -u

5ファイルをリストすると、en-masse は追跡されていないファイルのような多くのファイル操作を追加しgit add .たり、git add *スキップしたりします。を使用git add --forceして、通常はスキップされるファイルを追加できるため、この部分は少し複雑になります。他のいくつかの通常はマイナーな特別なケースがあり、それらはすべてこれに追加されます。ファイル.gitignoreはより適切に呼び出される.git-do-not-complain-about-these-untracked-files-and-do-not-auto-add-themか、同様に扱いにくいかもしれません。しかし、.gitignoreそれはばかげているので、そうです。


git add -ugit commit -aなど

ここで知っておくと便利なショートカットがいくつかあります。

  • git add .更新されたすべてのファイルが現在のディレクトリとサブディレクトリに追加されます。これはを尊重.gitignoreするので、現在追跡されていないファイルがから不満がない場合、ファイルはgit status自動的に追加されません。

  • git add -u更新されたすべてのファイルを作業ツリーの任意の場所に自動追加します6 これは追跡されたファイルにのみ影響ます。作業ツリーのコピーを削除した場合は、インデックスコピーも削除れることに注意してください(git addこれにより、インデックスが作業ツリーと一致するようになります)。

  • git add -Agit add .作業ツリーの最上位から実行するようなものです(ただし脚注6を参照)。

これらに加えて、を実行することができますgit commit -a。これは、running and thenとほぼ同じ7です。つまり、Mercurialで便利なのと同じ動作が得られます。git add -ugit commit

私は通常、git commit -aパターンに反対することをお勧めします。git status頻繁に使用し、出力をよく見て、状況が期待したものと異なる場合は、その理由を理解します。を使用git commit -aすると、ファイルを誤って変更し、コミットするつもりがなかった変更をコミットするのは簡単です。しかし、これは主に好み/意見の問題です。


6 GitバージョンがGit 2.0より前のバージョンである場合は、ここで注意してください。git add -u現在のディレクトリとサブディレクトリでのみ機能するため、最初に作業ツリーの最上位レベルに上る必要があります。git add -Aオプションでは、同様の問題があります。

7実際には追加のインデックスを作成し、その別のインデックスを使用してコミットを実行するため、ほぼ同等と言えgit commit -aます。コミットが機能すれば、実行と同じ効果が得られますgit add -u && git commit。コミットが機能しない場合、つまりGitに多くの方法のいずれかでコミットをスキップさせた場合、git addGitが一時的な追加インデックスをスローしてメインインデックスの使用に戻るため、後でファイルが削除されません。。

git commit --onlyここで使用すると、さらに複雑な問題が発生します。この場合、Gitは3番目のインデックスを作成し、特にプリコミットフックを使用する場合は、非常に扱いにくいものになります。これは、別のgit add操作を使用するもう1つの理由です。


1

@Ben Jacksonと@Tapashee Tabassum Urmiによって言及されているように、ステージを使用してコミットを小さくすることのポイントがわかりますが、その目的で使用することもありますが、主にコミットを大きくするために使用します!これが私のポイントです:

いくつかの小さなステップを必要とする小さな機能を追加したいとします。小さなステップのために別のコミットをして、タイムラインをあふれさせても意味がありません。ただし、各ステップを保存し、必要に応じて戻りたいのですが、

小さなステップを重ねるだけで、コミットする価値があると感じたときにコミットします。このようにして、不要なコミットをタイムラインから削除し、最後のステップを元に戻す(チェックアウト)ことができます。

これを行う他の方法(git履歴を簡略化する)は、好みに応じて使用できると思います。

  1. git amend(これは最後のコミットを変更します)これはこの特定の目的のためにあなたが望むものではありません(私はそれを大抵悪いコミットをしてから修正しているように見えます)
  2. git rebaseは後付けであり、あなたやあなたのリポジトリを使用する他の人に深刻な問題を引き起こす可能性があります。
  3. 一時的なブランチを作成し、マージしてから後で削除します(これも良いオプションであり、より多くの手順が必要ですが、より細かく制御できます)

0

コミットするファイルを選択できるチェックボックスのようなものです。

たとえば、編集fileA.txtしてfileB.txt.ButをコミットしたいfileA.txtだけの場合。まだ終わっていないのでfileB.txt

使用git add fileA.txtしてコミットするだけでgit commit -m "changed fileA.txt"作業を続行fileB.txtでき、終了後もfileB.txt簡単にコミットできます

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