@mizumotokのブログ

テクノロジー、投資、書評、映画、筋トレなどについて

Gitの便利な使い方 13個+1

開発者にとってもっとも使われているツールはgitでしょう。Youtubeチャンネルのfireshipチャンネルに「13 Advanced (but useful) Git Techniques and Shortcuts」という動画でgitの便利なテクニックが紹介されています。知っておくと役立つことも多かったので、ここで紹介します。

add と commit を同時に

addコマンドで変更内容をインデックスに追加してから、commitコマンドでコミットします。

$ git add .
$ git commit -m "hi mom"
[main d6b7949] hi mom
 1 file changed, 1 insertion(+)

commitコマンドに-amオプションをつければ、git add .を自動的にやってくれます。

git commimt -am "that was easy!"
[main 4bc37a8] that was easy!
 1 file changed, 1 insertion(+)

エイリアス

毎回同じようなコマンドやオプションをうつことが多いですが、エイリアスをつけて短くタイプすることができるようになります。 例えば、上記のcommit -amacというエイリアスにしてみます。

$ git config --global alias.ac "commit -am"
$
$ git ac "noice!"
[main 296cfb6] noice!
 1 file changed, 1 insertion(+)

コミットの修正

コミットを間違ったので修正したい場合は、amendオプションを使います。 コミットコメントだけを修正したい場合は、-mオプションが使えます。

$ git commit --amend -m "nice!"
[main f57c3ed] nice!
 1 file changed, 1 insertion(+)

ファイルを追加・変更したけど、コミットコメントを変えない場合は、--no-editオプションをつけます。

$ git add .
$ git commit --amend --no-edit
[main 26783b2] nice!
 Date: Sun Sep 5 19:37:55 2021 -0700
 2 file changed, 1 insertion(+)
 create mode 100644 foo.md

強制push

amendオプション修正したコミットをリモートレポジトリにプッシュしようとすると失敗することがあります。

$ git push origin main
To https://github.com/mizumotok/test-repo
! [rejected]         main -> main (fetch first)
error: failed to push some refs to 'https://github.com/mizumotok/test-repo'

こんなときは--forceオプションをつけることでリモートの履歴を上書きできます。

$ git push origin main --force
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressiong objects: 100% (2/2), done.
Writing objects: 100% (4/4), 307 bytes | 307.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/mizumotok/test-repo
 + d78d86a...6299342 main -> main (forced update)

ただし共同作業者がいる場合は、リモートと彼らのローカルで不整合になるので注意しましょう。

コミットの取り消し(revert)

前のコミットが不要な場合があります。その時はrevertコマンドで新しいコミットを作って、1つ前のコミットに戻ることができます。

$ git log --oneline
b4f4098 (HEAD -> main) update
6299342 (origin/main) initial commit

上記のケースでb4f4098のコミットを取り消したいときは、コミットIDを指定します。

$ git revert b4f4098
$ git log --oneline
2218b92 (HEAD -> main) Revert "update"
b4f4098 update
6299342 (origin/main) initial commit

コミット履歴を壊さないので、共同作業で混乱することがなくなります。

リモートワーク(Codespaces)

ローカルPCに触れない状況で、レポジトリで作業をしたいという場合があるでしょう。GitHubではレポジトリのページで「.(ドット)」をうつか、github.com.com.devと変えるだけで、ブラウザ上でVS Codeが使えるようになります。プルリクの対応もここでできるようになります。

https://github.com/mizumotok/zipcode
 ↓
https://github.dev/mizumotok/zipcode

このままではソースコードの編集だけで、開発サーバを動かしての確認はできません。GitHubではCodespacesというクラウド上の開発環境が用意されていて、VS Code(github.dev)のdevcontainerとして接続できるようになっています。 github.co.jp

現在はベータ版で無料ですが、GAになると以下の料金体系が予定されています。

Product SKU 単位 料金
Codespaces Compute 2 core 1時間 $0.18
4 core 1時間 $0.36
8 core 1時間 $0.72
16 core 1時間 $1.44
32 core 1時間 $2.88
Codespaces Storage Storage 1GB-month $0.07

最低スペックで月160時間動かして、3,000円程度です。

About billing for Codespaces - GitHub Docs

作業中に一時退避(stash)

コミットをしていない作業中の状態を一時的に保存しておきたい場合があるでしょう。そのときにはstashコマンドを使います。

$ git stash
Saved working directory and index state WIP on main: 6d010fd 1

stashコマンドにpopオプションをつけると保存したものを取り出すことができます。

$ git stash pop
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (f4a3b907badab9a516405a3b052cbbbceee42cd1)

名前をつけてstashをすることもできます。

$ git stash save coolstuff
Saved working directory and index state On main: coolstuff

stashされた一覧を見るにはlistオプションをつけます。

$ git stash list
stash@{0}: On main: coolstuff

インデックス番号を指定して取り出すことができます。

palau:test kiyoshi% git stash apply 0
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

Codespacesを使ってリモートで作業している場合は、stashはクラウド上に保存されます。

ブランチ名の変更(branch)

gitのデフォルトブランチはmasterが長年使われていましたが、その名前が一部の人にとって不快だといういう理由で、変更する方向になっています。

https://sfconservancy.org/news/2020/jun/23/gitbranchname/

ブランチ名を変えるにはbranchコマンドに-Mオプションをつけます。

$ git branch -M mucho
$ git status
On branch mucho
nothing to commit, working tree clean

ログを見やすく(log)

logコマンドはコミット履歴を見るのに便利ですが、長いコミットIDやAuthorやDate等のいろいろな情報が一つのコミットに凝縮されて、履歴を眺めるのには見づらいです。

--graph --oneline --decorateオプションをつけることで見やすくなります。

$ git log --graph --oneline --decorate

* fa1a4b657c (HEAD -> main, origin/main, origin/HEAD) Let jsbundling-rails expand without requiring a change in Rails
* 83808166e6 Add --css app generator option (#43177)
*   061bf3156f Merge pull request #43148 from jbourassa/polymorphic-custom-name-fixes
|\  
| * 2ea5ff9e13 Use `polymorphic_class_for` over `constantize`
|/  
*   e1f90a30a2 Merge pull request #43171 from yykamei/replace-location-with-request-in-process_action.action_controller
|\  
| * 92ac51bf66 Replace :location with :request in process_action.action_controller
* | 7595c922a6 Use the combined jsbundling-rails gem instead of individual js bundler gems (#43172)
* | 532ef0d13c Add back Lint/UselessAssignment
|/  
*   78e402077d Merge pull request #43170 from byroot/rubocop-explicit-block-argument
|\  
| * c91c266872 Enable `Style/ExplicitBlockArgument` cop
|/  
* 5fbc750840 Get rid of `mattr_accessor` in `ActiveSupport::Dependencies`

問題箇所の特定(bisect)

アプリケーションに不具合が入って、どのコミットで不具合が起き始めたのか見つけたいことがあります。そんなときはbisectコマンドを使って二分探索法でコミットを探していくことができます。

$ git bisect start
$ git bisect bad
$ git bisec good 6d010fd
Bisecting: 0 revisions left to test after this (roughly 1 step)
[18e0abe6f25bcc99f53cad5e426cecc5f5ef18fd] 2

まずbisect startでbisectを開始します。bisect badで現在のコッミットをbadとマークします。bisect good <commit id>で問題がなかったときのコミットをgoodとマークします。bisectbadgoodの中間のコミットを見つけてチェックアウトしてくれます。

このコミットを確認してやはり不具合が残っていた場合は、もう一度bisect badとすることで、また別のコミットを提案してチェックアウトしてくれます。

$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 1 step)
[cd9f0e0038c166467425ea55cff6063c8152a3f7] 1

これを繰り返すことで、問題が発生したコミットを効率よく探すことができます。

自動squash

mainブランチから派生したfeatureブランチで作業していて、コミットが多くなりすぎてコミットを一つにまとめたいときにrebase--interactiveオプションをつけることで対応できます。

(featureブランチで)
$ git rebase main --interactive
hint: Waiting for your editor to close the file...

rebaseコマンドを--interactiveオプションをつけて実行するとエディタが立ち上がります。

pick 62079a8 feature complete
pick b95ba00 1
pick e9d38a4 2
pick 63bc7fd 3

# Rebase 9a8d40c..63bc7fd onto 63bc7fd (4 commits)

ここで最後の3つのコミットをpickからsquashに変えます。

pick 62079a8 feature complete
squash b95ba00 1
squash e9d38a4 2
squash 63bc7fd 3

# Rebase 9a8d40c..63bc7fd onto 63bc7fd (4 commits)

こうして保存すると、featureブランチのコミットが一つになってmasterブランチのHEADにつきます。

(rebase / squash前)
A - B - C - D  <- master
  \  
   X - Y <- feature

(rebase / squash後)    
A - B - C - D  <- master
             \
               Z <- feature

ブランチは同じで、現在の変更を過去のコミットに統合したいというケースがあります。commitコマンドに--fixup--squashオプションをつけて過去のコミットの修正であることを指定します。

$ git commit --fixup fb2f677 # 修正したいコミットIDを指定する

ここでrebaseコマンドに--autosquashオプションをつけるとfixupで作成されたコミットが自動的に元のコミットと統合されてrebaseされます。

git rebase -i --autosquash 62079a8 # 修正したいコミットIDの一つ前のコミット(このコミットから後ろがrebaseされる)
(最初)
[A] - [B] - [C] - [D]

(commit --fixup C)
[A] - [B] - [C] - [D] - [fixup! C]

(rebase -i --autosquash B)
[A] - [B] - [C'] - [D']   ※ [C']は[C]と[fixup! C]が統合されている

フック

コミットの前後で何か処理(例えばシンタックスチェック)を入れたい場合があります。その時はフックが使えます。

.git/hooksフォルダに特定のファイル名をつけたスクリプトを置くだけです。コミット前の処理はpre-commitというファイル名にします。

$ ls .git/hooks                   
applypatch-msg.sample           pre-push.sample
commit-msg.sample               pre-rebase.sample
fsmonitor-watchman.sample       pre-receive.sample
post-update.sample              prepare-commit-msg.sample
pre-applypatch.sample           push-to-checkout.sample
pre-commit.sample               update.sample
pre-merge-commit.sample

huskyというツールを使うとフックが簡単に作れます。 github.com

例えばコミット前にテストを実行したい場合は、以下のようになります。

$ npm install husky -D
$ npm set-script prepare "husky install"
$ npm run prepare
$ npx husky add .husky/pre-commit "npm test"
$ git add .husky/pre-commit

これでコミットコマンド実行前にnpm testが実行されます。

破壊

ローカル環境で作業をしていたけど、リモートレポジトリと同じ状態に戻したい場合(ローカルで作業中の変更は失われるので注意)

$ git fetch origin
$ git reset --hard origin/main

git管理されていないファイル(ビルド生成物やログ等)を消したい場合

$ git clean -df

gitをやめたい場合

$ rm -rf .git

(おまけ)一つ前のブランチに戻る

checkoutコマンドでブランチ名でなく-を指定するだけです。

$ git checkout -