Gitを使うなら覚えておきたい便利なコマンド
ファイル管理において、バージョン管理はとても便利です。
とりわけ Git はバージョン管理の主流と言えます。
しかし、fetch、pull、commit、push、mergeしか使っていない人も多いのではないでしょうか?
今回はそんなGit初心者を脱出するため、とても便利なコマンドを紹介していきます。
目次
はじめに
今回の記事の内容ですが、対象は Gitを利用したことがある人 で、最低限のコマンドが使える人です。
まったくの初心者の方は最低限のコマンドを覚えてからご覧ください。
特定のコミットをブランチを取り込む / git cherry-pick
特定のコミットを別のブランチから取り込むときに使用します。
例えばこんな状況です。
前提として dev1 ブランチにあるコミットCを、dev2 ブランチに取り込むとします。
(下記の図を参考)
この時 取り込み先のブランチをチェックアウトし git cherry-pick を実行 すればOKです。
1 2 3 4 5 6 7 8 |
$ git checkout dev2 $ git cherry-pick [コミットCのコミットハッシュ値] Finished one cyerry-pick. Created commit XXXXXXX: Update daat2 1 files changed, 1 insertions(+), - deletions(-) |
git cherry-pick は ある目的でブランチを切って作業していた際、特定の機能(コミット)だけを取り込む場合 などに便利です。
コミット単位でブランチに適用できるので
git merge コマンドと使い分けて開発スタイルに幅が出ます。
(なんでもmergeするのはやめましょう)
cherry-pick に失敗する場合
git cherry-pick は あるコミットを取り出して、対象ブランチへ適用する という特性のため、ワークツリーにコミットされていないファイルがある場合、失敗してしまいます。
1 2 3 4 5 6 |
$ git cherry-pick [コミットのコミットハッシュ値] error: Your local changes to 'XXXX' would be overwritten by merge・・・ Please, commit your changes or stash them before you can merge・・・ |
また、取り込もうとするコミットによっては、コンフリクトが発生することもあります。
1 2 3 4 5 6 7 |
$ git cherry-pick [コミットのコミットハッシュ値] Automatic cherry-pick failed. After resolving the conflicts, mark the corrected paths with 'git add <paths>' or 'git rm <paths>'・・・ When commiting, use the option '-c XXXXX' to retain authorship・・・ |
この場合、コンフリクトを解消してコミットすればOK。
ここでコミットの際に -c オプションを使うことで、元のコミットの作成者情報を保持したままコミットができます。
取り出したコミットのコミットハッシュ値を記録する
git cherry-pick でコミットを取り出した場合、元のコミット情報が消えてしまいます。
この時、-x オプションを使うことで git cherry-pick した元のコミットハッシュ値をコミットメッセージに追記できます。
1 2 3 4 5 6 7 8 |
$ git cherry-pick -x [コミットのコミットハッシュ値] Finished one cyerry-pick. [master XXXXXXX] INSTALL (cherry picked from commit YYYYYYY...) 1 files changed, 1 insertions(+), - deletions(-) create mode 100644 INSTALL |
ただし、コンフリクトなどによって git cherry-pick コマンドが失敗した場合には追記されませんので、手動で追記する必要があります。
git cherry コマンドではこの行を参照しないので git cherry-pick コマンドでパッチが適用された場合 git cherry コマンドでは確認できず、コミットログから確認することになります。
バグが入り込んだ位置を特定する / git bisect
多人数でGitを使用していると、ある時突然に動作不良やエラーになった経験があるのではないでしょうか?
どこでバグが発生したのかを探す必要がありますが、1つずつ確認していくのはかなりの手間です。
こんな時、Git には git bisect というコマンドがあり、二分探索による問題コミットを探し出すことができます。
コミットが単純(直列)の場合
下記の図の場合で説明します。
コミットEでバグが混入した場合を考えます。
コミットBまではOKだったことを覚えていたとして git bisect コマンドでどのコミットが原因かを探していきましょう。
まずは git bisect start コマンドを実行して二分探索を開始しましょう。
チェックアウトしているブランチ(dev)のHEAD(コミットG)が既に失敗しているため、コミットの指定を省略できますが、下記のように書くこともできます。 git bisect start [失敗しているコミットのハッシュ値] [成功しているコミットのハッシュ値]
次に、バグが混入しているコミットを git bisect bad コマンドで知らせます。
最後にエラーが無かったポイントのコミットBを git bisect good で知らせることで、二分探索が開始されます。
1 2 3 4 5 6 7 |
$ git bisect start $ git bisect bad $ git bisect good [コミットのコミットハッシュ値] Bisecting: 2 revisions left to test after this (roughly 1 steps) [コミットハッシュ値] D |
この場合、コミットDがチェックアウトされます。
この内容で実行して、成功した場合は git bisect good で問題が無かったことを知らせると、続いてコミットFがチェックアウトされます。
1 2 3 4 5 |
$ git bisect good Bisecting: 0 revisions left to test after this (roughly 1 steps) [コミットハッシュ値] F |
再度実行してみると今度はエラーになってしまいましたので git bisect bad コマンドで失敗を伝えます。
1 2 3 4 5 |
$ git bisect bad Bisecting: 0 revisions left to test after this (roughly 0 steps) [コミットハッシュ値] E(bug) |
そしてチェックアウトされたコミットEを実行したらエラーになったので git bisect bad を実行すると、次のように表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ git bisect bad [バグのあったコミットハッシュ値] is the first bad commit commit [バグのあったコミットハッシュ値] Author: Foo Bar <user@example.com> Date: Fri May 15 00:00:00 2020 + 0900 E(bug) :100644 100644 XXXXXXX... YYYYYYY.... M main.c |
このようにして git bisect コマンドでバグが混入したコミットを特定することができます。
あとは git bisect reset コマンドで元の状態に戻して修正を行い、コミットすれば問題が解決します。
1 2 3 4 5 6 |
$ git bisect reset Previous HEAD position was XXXXXX.... E(bug) Switched to branch 'dev' |
コミットツリーが複雑な場合
ブランチやマージにより、コミットツリーが複雑になることもあります。
ここでコミットFの時にエラーになり、コミットBでは成功していた場合のツリーがこちらです。
ブランチが複雑でも git bisect コマンドで特定をすることができますが、ブランチが分かれているのですぐには出てきません。
その場合でも git bisect start でHEADと成功していたコミットハッシュ値で実行しましょう。
すると、順次二分探索によるチェックアウトを行っていきます。
(今回の場合、I > D > E の順になります)
git bisect を自動化する
git bisect は便利ですが、毎回チェックアウトを行って確認するのは面倒ですし、コミット数が増えるとやりきれなくなってくることでしょう。
しかし、コマンドだけでチェックができるのであれば、自動的に問題のコミットを洗い出すことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ git bisect start HEAD XXXXXXXXXXXXXXXXXXXXX.... $ git bisect run make . (省略) . make: *** [main] Error 1 [バグのあったコミットハッシュ値] is the first bad commit commit [バグのあったコミットハッシュ値] Author: Foo Bar <user@example.com> Date: Fri May 15 00:00:00 2020 + 0900 E(bug) :100644 100644 XXXXXXX... YYYYYYY.... M main.c bisect run success |
未コミットの状態を一時保存する / git stash
コミットしていない変更を一時的に保存・退避して置ける機能 がGitには備わっています。
それが git stash です。
よくあるパターンとしては下記のような 作業の割り込み でしょうか。
こんな時、焦ってブランチを切ってコミットしなくてもOKです。
1 2 3 4 5 |
$ git stash save work_1 Saved working directory and index state On master: work_1 |
これで work_1 というstashが出来て一時保存されました。
stashされた一覧は git stash list、変更内容を出力するには git stash show を使用します。
1 2 3 4 5 |
$ git stash list stash@{0}: On master: work_1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ git stash show -p stash@{0} diff --git a/sample.txt b/sample.txt index XXXXXXX..XXXXX 100644 --- a/sample.txt +++ b/sample.txt @@ -1 +1, 3 @@ -row 1 \ no newline at end of file + row 1 + +add text 1 |
キューに保存した内容を適用する
保存した内容を適用するには git stash pop か git stash apply を使用します。
この2つのコマンドの違いは 適用後にキューを削除するかどうか です。
また、リポジトリの状態によってはコンフリクトが発生する可能性がありますが、その時もマージの時と同じようにコンフリクトを解消すればOKです。
git stash は新しくキューに入るたびに番号がインクリメントされるので、最新は常に stash@{0} となります。
保存したキューを削除する
キューに保存した変更は git stash pop コマンドで取り出さない限り消えません。
取り出さずに消すには git stash drop コマンドを使います。
1 2 3 |
$ git stash drop stash@{1} |
また、あまり使うことは無いかもしれませんが、一括で消すこともできます。
1 2 3 |
$ git stash clear |
指定したコミットのスナップショットを作成する / git archive
あるコミット時点のファイルのスナップショットを作成することができます。
これは、開発中のソースコードの最新状態を顧客に送る場合や、差分を取って一括で取得する場合などに便利です。
(ただし、リポジトリのデータは含まれません)
特に納品時に差分のファイルをかき集めるのは面倒ですので、非常に役に立ちます。
現在チェックアウトしているブランチのスナップショットは下記のようなコマンドで作成できます。
1 2 3 |
$ git archive --format=tar --prefix=repo/ HEAD > ../snap_shot.tar |
–formatで出力フォーマット(tar か zip)を、–prefixで展開後のディレクトリを指定できます。
bzip2 や gzip で圧縮する場合は、パイプを利用することで可能になります。
1 2 3 |
$ git archive --format=tar --prefix=repo/ HEAD | bzip2 > ../snap_shot.tar.bz2 |
特定のディレクトリのみを出力したい
特定のディレクトリのみを出力するには、リビジョン指定の後にディレクトリを指定すればOKです。
(ディレクトリは複数指定可能)
1 2 3 |
$ git archive --format=tar --prefix=repo/ HEAD src/libs/ webroot/img/ > ../snap_shot.tar |
リモートリポジトリのスナップショットを作成する
ローカルリポジトリだけではなく、リモートリポジトリもスナップショットの作成が可能です。
リモートリポジトリ origin ブランチ master のスナップショットを作成するには下記のとおりです。
1 2 3 |
$ git archive --format=tar --prefix=repo/ -remote=origin master > ../snap_shot.tar |
他のリポジトリをモジュールとして利用する / git submodule
共通で使用しているライブラリなどをそれぞれのリポジトリでコミットしていると、バージョン管理の煩雑化やコミット漏れに繋がります。
そんな時 git submodule を使用することで、他のGitリポジトリをモジュールとして特定ディレクトリに取り込むことが可能です。
1 2 3 4 5 6 |
# git://example.com/sub.git の中身が modules に取り込まれる $ git submodule add git://example.com/sub.git modules Initialized empty Git repository in git://example.com/sub.git |
このコマンドで modules ディレクトリが作成され、 git://example.com/sub.git のデータをコピーし、HEADをチェックアウトします。
modules ディレクトリにはGitリポジトリの .git ディレクトリとチェックアウトしたファイルができます。
リポジトリでのモジュールの状態
git submodule add で取り込んだ後は、まだモジュールとしてローカルリポジトリに登録されていません(DLしてきただけ)。
この時 .gitmodules ファイルが作成されていますが、この中には外部のリポジトリ情報と対応したディレクトリ及びモジュール名が記録されています。
git submodule はこのファイルの記録情報をもとに動作するので、まずは .gitmodules ファイルをリポジトリに登録しましょう。
作成されたディレクトリは普通のものと異なり、Subprojectという特殊状態で、外部のリポジトリのHEADのみを管理しています。
モジュールをリポジトリに登録する
取り込んだ外部のリポジトリをモジュールとして、リポジトリに登録しましょう。
1 2 3 4 5 6 7 8 |
$ git commit -m "Add submodule sub" [master XXXXXX] Add submodule sub 2 files changed, 5 insertions(+), 0 deletions(-) create mode 100644 .gitmodules create mode 160000 modules |
モジュールの状態を確認
取り込んだモジュールの状態を確認するには git submodule status を使います。
これは設定されているコミットハッシュ値とモジュールが置かれているディレクトリの一覧が表示されます。
1 2 3 4 5 |
$ git submodule status -XXXXXXXXXXXXXXXXXX...XXXXX modules (heads/master) |
ここでコミットハッシュ値の前の記号はリポジトリの状態を表しており、マイナス(-)の場合はモジュールが初期化されていないことを、プラス(+)の場合は設定されているコミットハッシュ値とチェックアウトされている値が異なることを表しています。
(問題がない場合、記号は何も表示されません)
モジュールの初期化
登録したモジュールは git submodule init コマンドで初期化を行いましょう。
これにより、.git/config ファイルにモジュールが参照する外部のリポジトリが登録されます。
1 2 3 4 5 |
$ git submodule init Submodule 'sub' (git://example.com/sub.git) registered ... |
モジュールを更新
モジュールの更新には git submodule update コマンドを使用します。
既存のリポジトリに git submodule add で登録した場合は、登録時にモジュール情報が登録されているので特に更新する必要はありません。
しかし git clone でコピーしたリポジトリにモジュールが既にある場合、モジュールを初期化&更新する必要があります。
git submodule update コマンドに –init オプションをつけると、モジュールの初期化と更新を一括で行うことができます。
モジュールを最新のコミットに更新する
git submodule update コマンドは 登録されているコミットハッシュ値にモジュールを更新する ため、モジュールの外部リポジトリの最新コミットにすることができません。
最新コミットの状態に更新したい場合、モジュールのディレクトリへ移動し git pull コマンドを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ cd modules/ $ git pull From git://example.com/sub.git XXXXXX...XXXXX master -> origin/master Updating XXXXXX...XXXXX Fast-forward . (省略) . |
この時に git submodule status を実行すると、コミットハッシュ値が更新され、先頭の記号がプラス(+)になります。
あとは、ローカルのGitリポジトリをコミットすれば、リポジトリにモジュールの更新情報が登録されます。
(これで先頭のプラス(+)記号がなくなります)
モジュールを削除
登録したモジュールを削除するには .gitmodulesファイル と .git/configファイル を更新して、モジュールのディレクトリを削除します。
まずは .gitmodulesファイル から該当の項目を削除し、次に .git/configファイル から該当の項目を削除します。
最後にモジュールが入っているディレクトリを削除してコミットすれば完了です。
モジュール更新の注意点
git submodule update コマンドでモジュールを更新した場合、登録されているコミットハッシュ値でチェックアウトするため、モジュールのファイルに変更を加えている場合、元に戻されてしまいます。
(先祖返り、ロールバックなどと言ったりします)
また、モジュールのディレクトリでブランチを切り替えたり、コミットをしていた場合でも戻されてしまいます。
モジュールに変更を加える場合、外部リポジトリで変更するようにして、モジュールとしてそれに追従させると良いです。
(簡単に言うなら 「フォークさせてそちらを外部リポジトリにする」 といったところでしょうか)
サブツリーマージを使う方法
サブモジュール以外に、サブツリーマージを使う方法があります。
これは外部リポジトリをディレクトリに展開するところまではサブモジュールと同じですが、展開された部分への変更を ローカルリポジトリに対してコミットやマージする という点で異なります。
サブツリーマージを設定する
サブツリーマージの対象としたいリポジトリをリモートリポジトリとして登録して、その情報を更新します。
1 2 3 4 5 6 7 8 9 |
$ git remote add tree1 git://example.com/tree1.git $ git remote update Fetching tree1 warning: no common commits From git://example.com/tree1.git * [new branch] master -> tree1/master |
サブツリーマージの対象となるリモートブランチを展開
続いてサブツリーマージの対象にするリモートブランチを、指定したディレクトリに展開します。
まずは
git merge コマンドでリモートのトラッキングするブランチ tree1/master の状態を記録します。
(-s ours と –no-commit のオプションをつけることで、チェックアウトしているブランチを優先してマージできます)
1 2 3 4 5 6 |
$ git merge -s ours --no-commit tree1/master Automatic merge went well; stopped before committing as requested Squash commit -- not updating HEAD |
そして、リモートトラッキングブランチの tree1/master を git read-tree コマンドで subtree-tree1 ディレクトリに展開します。
1 2 3 |
$ git read-tree --prefix=subtree-tree1 -u tree1/master |
この段階でワーキングツリーを見てみると git read-tree コマンドにより subtree-tree1 ディレクトリが作成されていることが確認できます。
また、コミットログを確認すると、サブツリーマージの対象をローカルリポジトリに登録していないので、チェックアウトしているブランチのコミットログのみが出力されます。
サブツリーマージの対象を登録
サブツリーマージの対象をローカルリポジトリに登録します。
git read-tree コマンドにより、自動的にインデックスに登録されているので、コミットするだけでOKです。
1 2 3 |
$ git commit -m "Merge Subtree subtree-tree1" |
ここでコミットログを確認してみると、サブツリーマージの対象として管理されていることが確認できます。
サブツリーマージの対処を更新
サブツリーマージしたものを更新するには git pull コマンドの -s オプションにサブツリーマージを指定して実行します。
この時、サブツリーマージの対象を自動的に検出し、サブツリーマージの対象の更新がローカルリポジトリにマージされます。
1 2 3 4 5 6 7 8 9 10 |
$ git pull -s subtree tree1 master From git://example.com/tree1.git * branch master -> FETCH_HEAD Merge made by subtree. . (省略) . |
ここでコミットログを確認すると、マージで更新されていることが分かります。
サブツリーマージのコミットログを取り込まずに扱う
サブツリーマージを行うと、マージ先のブランチで行われたコミットがローカルリポジトリにとりこまれるため、コミットログも統合されます。
コミットログを一度に確認できる反面、コミットログを分割して表示することができません。
外部リポジトリの最新状態を追従するためにサブツリーマージを使用する場合、コミットログを別々にしたいこともあります。
そんな時、サブツリーマージのコミットログを取り込まないようにすることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ git remote add tree1 git://example.com/tree1.git $ git remote update Fetching tree1 warning: no common commits From git://example.com/tree1.git * [new branch] master -> tree1/master $ git read-tree --prefix=subtree-tree1 -u tree1/master $ git commit -am "Add subtree" [master XXXXXXX] Add subtree . (省略) . |
ポイントは マージせずに git read-tree コマンドを使用してリモートトラッキングブランチで管理されている内容を展開・コミットする というところです。
マージ対象を更新する時は、まずリモートリポジトリを更新して 一度マージの対象がチェックアウトされているディレクトリを削除してから、再度展開してコミット します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# リモートリポジトリを更新 $ git remote update . (省略) . # 一度削除する $ git rm -rf subtree-tree1 # 再度展開する $ git read-tree --prefix=subtree-tree1 -u tree1/master # 展開内容をコミットする $ git commit -am "Update subtree" |
削除する理由は git read-tree コマンドの実行時に、ツリーの展開先のディレクトリがインデックスに存在する場合、エラーになってしまうため。
さいごに
今回は実務で使える実用的なコマンドとその解説でした。
皆さんの環境で使えるものはありましたか?
リモートリポジトリの設定に影響されない git stash なんかは今すぐにでも開始できます。
また、コミット単位と内容を意識しておくと git cherry-pick も使っていけるのではないでしょうか?
それではまた。
- おすすめ記事
POPULAR
のえる
Full-stack Developer