DUICUO

Git: コマンドの各行が重要です。

[[429861]]

この記事はWeChat公式アカウント「sowhat1412」から転載されたものです。著者はsowhat1412です。転載の許可については、sowhat1412公式アカウントまでお問い合わせください。

Git マインドマップ

Git作業領域

日々の開発で実行する一連のGitコマンドの目的を説明するには、Gitの作業領域の概念を理解する必要があります。ほぼすべての一般的なGitコマンド操作は、作業領域を通して説明できます。

Git には 4 つのローカル作業領域があります。

  • 作業ディレクトリ: `git init` 後のローカル ファイル ディレクトリ。ここにコードを記述します。
  • ステージング/インデックス: コードを変更した後、変更がコミットされることを示すために、ステージング領域に変更を追加する必要があります。
  • ローカル リポジトリ: ローカル Git リポジトリは、すべての変更が保存される .git ファイルを含む隠しディレクトリです。
  • リモート リポジトリ: リモート Git リポジトリ。理論的にはローカル リポジトリと同等のステータスですが、主に複数の開発者がコードをプル/プッシュするために使用されます。

4つの作業領域

Gitファイルのステータス

次に、Git ファイルの状態を見てみましょう。

Gitファイルのステータス

  • 未追跡: このファイルはフォルダ内に存在しますが、Gitリポジトリに追加されておらず、バージョン管理されていません。`git add` を実行すると、ステータスが「ステージング済み」に変更されます。
  • UnModify: ファイルはリポジトリにコミット済みで、変更されていません。つまり、リポジトリ内のファイルスナップショットはフォルダの内容と完全に同一です。このタイプのファイルには2つの結果が考えられます。変更された場合は「Modified」になります。`git rm` を使用してリポジトリから削除された場合は、「UnTracked」ファイルになります。
  • 変更済み: ファイルは変更されていますが、その他の操作は実行されていません。このファイルの保存先は2つあります。`git add` を使用してステージング状態にするか、`git checkout` を使用して変更内容を破棄し、変更前の状態に戻すことができます。`git checkout` はリポジトリからファイルを取得し、現在の変更内容を上書きします。
  • ステージング済み: ファイルは一時的な状態です。`git commit` を実行するとリポジトリへの変更が同期され、リポジトリ内のファイルとローカルファイルの整合性が回復し、ファイルは「未変更」状態になります。`git reset` を実行するとフェーズがキャンセルされ、ファイルは「変更済み」状態になります。

Gitの基本コマンド

Gitの作業領域、ファイルの状態、ローカルリポジトリについて理解できたので、よく使われるコマンドについてより深く理解できたはずです。次に、よく使われるコマンドをいくつかまとめてみましょう。

  • `git clone`: リモートリポジトリをローカルマシンにクローンし、ローカルリポジトリを作成します。これにより、隠しファイル `.git` が作成されます。
  • `git status`: ステータスを確認します。どのファイルが変更され、どのファイルが追跡されていないかを確認できます。
  • `git diff`: このコマンドは、現在の作業ディレクトリとステージングされたディレクトリ内のファイル間の差分を表示します。
  • `git add`: 追跡されていない (新しく追加された) ファイルまたは変更されたファイルを作業ディレクトリからステージング領域に追加します。
  • `git commit`: `staged` ステージング領域の内容をローカル Git リポジトリにコミットします。
  • `git push`: ローカル Git リポジトリの内容をリモート Git リポジトリにコミットします。

一人で開発するプロジェクトであれば、これらのコマンドで十分でしょう。しかし、実際の開発では、より複雑なシナリオに直面することが多く、より複雑なコマンドが必要になります。それでは、続きを見ていきましょう。

git マージ

現在のブランチで `git merge master` を実行すると、master のコミットが現在のブランチにマージされ、実質的にはローカルブランチが更新されます。私たちの日常的な開発では、ローカルコードをリモートリポジトリにプッシュし、マージリクエストを作成して「マージ」ボタンをクリックすることで、実質的には開発ブランチが master ブランチにマージされます。

git フェッチ

`git fetch` はリモート ブランチをローカル マシンにプルできます。

`git fetch` はすべてのリモート ブランチをローカル マシンにプルします。

`git fetch origin sowhat1412` は、リモート `origin` リポジトリからの `sowhat1412` ブランチをローカル マシンに取得することを指定します。

git プル

ローカルブランチのコードを更新するには、リモート開発ブランチまたはリモートマスターブランチからコードをローカルマシンにプルし、現在の開発ブランチにマージする必要があります。つまり、`git pull` は `git fetch` + `git merge` と同じ意味になります。

現在の開発ブランチ sowhat1412 で、次のコマンドを実行します。

  1. git pull origin マスター
  2. これは、リモート マスター ブランチをローカル マシンにプルし、それを現在のブランチ sowhat1412 にマージすることを意味します。

git リベース

リベースは、現在のブランチのコミットを公開ブランチの末尾に配置するため、「リベース」と呼ばれます。これは、公開ブランチから新しいブランチをプルするようなものです。

例えば、master から機能ブランチを作成し、いくつかのコミットを行った後、誰かがその作業を master にマージすると、master にはブランチ作成時よりもいくつかのコミットが追加されます。この時点で master をリベースすると、現在のコミットはその人のコミットの後に配置されます。

git rebeae

マージは、パブリック ブランチと現在のコミットを結合して新しいコミットを形成します。

[[429865]]

git マージ

git チェリーピック

複数ブランチのコードベースでは、あるブランチから別のブランチにコードを移動することが一般的な要件です。

ここでは2つのシナリオが考えられます。1つは、別のブランチのすべてのコード変更が必要な場合で、この場合は `git merge` を使用します。もう1つは、一部のコード変更(数個のコミット)のみが必要な場合で、この場合は `Cherry pick` を使用します。

`git cherry-pick` コマンドの目的は、指定されたコミットを他のブランチに適用することです。

  1. $ git チェリーピック <コミットハッシュ>

上記のコマンドは、指定されたコミットハッシュを現在のブランチに適用します。これにより、現在のブランチに新しいコミットが作成されますが、そのハッシュ値は異なります。

たとえば、コード リポジトリには、master と feature の 2 つのブランチがあります。

  1. a - b - c - d マスター
  2. \
  3. e - f - g 機能

次に、f をマスター ブランチにコミットします。

  1. # マスターブランチに切り替える
  2. git チェックアウト マスター
  3.  
  4. # チェリーピック操作
  5. $ git チェリーピック f

上記の操作を完了すると、コードベースは次のようになります。

  1. a - b - c - d - f マスター
  2. \
  3. e - f - g 機能

上記のように、コミット「f」がマスター ブランチの最後に追加されました。

`git cherry-pick` コマンドの引数は、必ずしもコミットのハッシュである必要はありません。ブランチ名も許容され、そのブランチの最新のコミットを移動する必要があることを示します。

  1. $ git チェリーピック機能
  2. 上記のコードは、最新のコミットを機能ブランチから現在のブランチに転送することを意味します。

Cherry Pick は、複数のコミットを一度に転送することをサポートします。

  1. $ git チェリーピック <ハッシュA> <ハッシュB>

一連の連続したコミットを移動する場合は、次の簡略化された構文を使用できます。

  1. `git チェリーピック A..B`

上記のコマンドは、すべてのコミットを A から B に転送できます。コミットは正しい順序で配置する必要があります。コミット A はコミット B より前になければなりません。そうでない場合、コマンドは失敗しますが、エラーは報告されません。

上記のコマンドを使用すると、コミットAはCherry Pickに含まれないことに注意してください。コミットAを含めるには、次の構文を使用してください。

  1. `git チェリーピック A^..B`

git リセット

  • git コミットIDをリセットする
  1. コマンドを実行すると、HEAD ポインターは選択した commitId に移動し、HEAD と commitId の間で行われたすべての変更が作業ディレクトリに配置されるため、再度追加しコミットする必要があります
  • git リセット --soft コミット ID
  1. コマンドが実行されると、HEADポインタは選択されたcommitIdに移動し、HEADとcommitIdの間で行われたすべての変更がステージング領域に直接配置されます。つまり、以降の変更はコミット操作のみで実行できます。
  • git リセット --hard コミット ID
  1. --hardの部分は簡単に理解できます。これは、HEAD ポインターをロールバックするときに、HEAD と commitId の間のすべての変更を強制的に削除することを意味します。  
  2. なぜ引用符が必要なのでしょうか?先ほども述べたように、Git は(熟練したスキルがあれば)コミットを一切失うことはありません。その理由は後ほど分析します。

git reflog

git-test という名前の新しいディレクトリを作成し、リポジトリを初期化して、簡単なコミットを行います。

  1. mkdir git-test
  2. cd git-test/
  3. git 初期化
  4. エコー0 > テスト
  5. git を追加します
  6. git commit -am "init"    

過去数週間で 3 つの重要なコードコミットを行ったとします。

  1. linux-geek:/home/tmp/git-test # echo "重要なコード0" >> test
  2. linux-geek:/home/tmp/git-test # git commit -am "重要なコード0"    
  3. [マスター 03f85a1] 重要なコード0
  4. 1 ファイルが変更され、1 個の追加(+)、0 個の削除(-)
  5. linux-geek:/home/tmp/git-test # echo "重要なコード1" >> test
  6. linux-geek:/home/tmp/git-test # git commit -am "重要なコード1"    
  7. [マスター 83547ba] 重要なコード1
  8. 1 ファイルが変更され、1 個の追加(+)、0 個の削除(-)
  9. linux-geek:/home/tmp/git-test # echo "重要なコード2" >> test
  10. linux-geek:/home/tmp/git-test # git commit -am "重要なコード2"    
  11. [マスター b826ae5] 重要なコード2
  12. 1 ファイルが変更され、1 個の追加(+)、0 個の削除(-)
  13. linux-geek:/home/tmp/git-test # cat テスト
  14. 0
  15. 重要なコード0
  16. 重要なコード1
  17. 重要なコード2

この時点で、突然うっかりミスをして `git reset` コマンドを使用したとします。

  1. linux-geek:/home/tmp/git-test # git reset --hard HEAD~3  
  2. HEADは現在2e71cf6 initあります

コードを見てみましょう:

  1. linux-geek:/home/tmp/git-test # cat テスト
  2. 0

これを改善する方法はあるのでしょうか?答えは「はい」です!

git reflog を使って確認してみましょう。

  1. linux-geek:/home/tmp/git-test # git reflog
  2. 2e71cf6 HEAD@{0}: HEAD~3: HEADを更新中
  3. b826ae5 HEAD@{1}:コミット: 重要なコード2
  4. 83547ba HEAD@{2}:コミット: 重要なコード1
  5. 03f85a1 HEAD@{3}:コミット: 重要なコード0
  6. 2e71cf6 HEAD@{4}:コミット(初期): init

HEAD の動きとそのハッシュ値が毎回明確にわかるので、`git reset` を使ってみましょう。

  1. linux-geek:/home/tmp/git-test # git reset --hard HEAD@{1}  
  2. HEADは現在b826ae5の重要なコード2あります

コードを見てみましょう:

  1. linux-geek:/home/tmp/git-test # cat テスト
  2. 0
  3. 重要なコード0
  4. 重要なコード1
  5. 重要なコード2

この時点で、`git log` または `git log --pretty=oneline` を使用すると、履歴全体が復元されたことが示されます。

git を元に戻す

`revert` は、以前のコミットではなく、特定のコミットをロールバックします。`git revert` は、特定のバージョンで行われた変更を元に戻すために使用されます。例えば、3つのバージョン(バージョン1、バージョン2、バージョン3)をコミットした後、バージョン2に不具合(バグなど)があることが突然発見され、バージョン3の元に戻す処理に影響を与えずにバージョン2を元に戻したい場合、`git revert` コマンドを使用してバージョン2を元に戻し、新しいバージョン4を生成します。このバージョン4はバージョン3の変更を保持しますが、バージョン2の変更は元に戻されます。下の図を参照してください。

プロセスを元に戻すには、「git revert -n バージョン番号」コマンドを使用します。次のコマンドはバージョン番号8b89621を元に戻します。

  1. git を元に戻す -n 8b89621019c9adc6fc4d242cd41daeb13aeb9861

ここで競合が発生する可能性があるため、競合するファイルを手動で変更する必要があります。また、変更をコミットするには `git add filename` を使用する必要があります。

  1. git commit -m "元に戻す text.txt を追加"  

コミットを手動で整理する

コード量は少ない要件があるのに、コミットが複数回行われたため、マージリクエストには「更新」「バグ修正」「再修正」といった数十ものコミットIDが付けられてしまいます。見た目が醜いだけでなく、開発者の能力不足を印象づけてしまいます。100行のコードを完成させるのに、これほど多くのコミットが必要でした。(awkward.jpg)

この問題を解決するには、`git reset` を巧みに活用します。例えば、現在のコミットが A-1-2-3-4-5-6-7-8 で、最初のコミットが 1 の場合、以下のコマンドを実行します。

  1. `git reset --soft A` // ローカルブランチの HEAD ポインターをリセットします。  
  2. git commit -m "XXX ロジック開発"  
  3. `git push origin ywq_news_0702 -f` // リモートブランチにプッシュ

`-f` とはどういう意味でしょうか?これは「強制」を意味し、操作を必ず実行する必要があることを示します。`git reset` を実行すると、ローカルの HEAD ポインタが指すコミット ID はリモートの `origin` ブランチのコミット ID よりも後ろになり、直接プッシュは拒否されます。`-f` コマンドは、ローカルのコンテンツをリモートブランチに強制的にプッシュすることができます(注意!共同開発ブランチやリモートの `master` 操作の場合は、絶対に `-f` を追加しないでください!)。

`git reset --soft` を使用した後、マージ リクエストに commitId が含まれるようになり、送信された CR がより印象的なものになります。

Git stash 一時ストレージ

現在のブランチで機能を開発している際に、別の機能の統合テスト中に問題が発生し、別のブランチに切り替えて問題を解決する必要が生じました。どうすればよいでしょうか?

通常、ブランチを切り替える前に、現在のブランチの作業ディレクトリの内容を追加してコミットする必要があります。しかし、ここで問題があります。機能の開発がまだ途中であり、コミットを生成したくない場合はどうすればよいでしょうか?

心配しないでください。`git stash` コマンドを使えば、現在の作業ディレクトリの内容をスタッシュできます。その後、別のブランチに切り替えて問題を解決し、現在のブランチに戻って `git stash pop` で内容を取得できます。

  1. `git stash list` // 現在のスタッシュの内容を表示します。
  2. `git stash` // 現在の作業ディレクトリの内容をstashします。
  3. `git stash pop` // スタッシュスタックの先頭の要素をポップします。あるいは、順序に基づいてn番目の要素を直接取得することもできます。

Gitで変更されたファイルを復元する

変更されたファイルを復元するには、リポジトリからローカル作業領域(つまり、リポジトリ領域 -> ステージング領域 -> 作業領域)にファイルを移動する必要があります。

変更されたファイルには 2 つのシナリオがあります。

  • ファイルを変更しただけで、Git 操作は実行していません。
  1. ファイルは git 操作なしで単純に変更されただけなので、1 つのコマンドで変更を元に戻すことができます。
  2. `git checkout -- aaa.txt` # aaa.txtはファイル名です 
  • ファイルが変更され、ステージング領域にコミットされました。
  1. つまり、編集後に `git add` を使用しますが、 `git commit -m ...`は使用しません
  2. `$ git log --oneline` # これは省略できます。  
  3. `$ git reset HEAD` # 現在のバージョンに戻す
  4. `git checkout -- aaa.txt` # aaa.txtはファイル名です 
  • ファイルが変更され、リポジトリにコミットされました。
  1. つまり、編集後に `git add`と `git commit -m ...` を使用します。
  2. `$ git log --oneline` # これは省略できます。  
  3. `$ git reset HEAD^` # 以前のバージョンに戻す
  4. `git checkout -- aaa.txt` # aaa.txtはファイル名です 
  5. 参照する

参照する

Git ハードマップのトップ 10: https://zhuanlan.zhihu.com/p/132573100

Git ブランチガイドライン: https://zhuanlan.zhihu.com/p/108385922

Git マインドマップ: https://www.jianshu.com/p/e2f553942317