Table of Contents
モチベーション
- 個人的に普段よく使うGitコマンドについて自分なりにまとめておきたい
- Pro Gitだとカロリー過多
Repositoryを用意する
ローカルリポジトリから始める
git init <directory><directory>をローカルリポジトリとする.<directory>内に.gitという隠しフォルダが作成される.Gitの諸々は.gitで管理されることになる.
リモートリポジトリから始める
GitHubにSSH公開鍵を登録しておく
cd ~/.sshssh-keygen -t rsa -b 4096で鍵作成chmod 600 <秘密鍵へのパス>- パスフレーズを設定したなら
ssh-add <秘密鍵へのパス>で秘密鍵をSSHエージェントに登録ssh-add -lでSSHエージェントに秘密鍵が登録されたことを確認
pbcopy < <公開鍵へのパス>で公開鍵をクリップボードにコピー- SSH and GPG keysの
New SSH keyに公開鍵をコピペ ~/.ssh/configに接続先を登録Host <nickname> HostName github.com IdentityFile <秘密鍵へのパス> User gitssh -T <nickname>で接続確認Hi XXX! You've successfully authenticated, but GitHub does not provide shell access.と言われれば優勝
rm <公開鍵へのパス>で公開鍵を削除しておく
git clone git@github.com:<repository>.git <directory><directory>: ローカルリポジトリとしたいディレクトリへのパス<directory>内に.gitという隠しフォルダが作成される.Gitの諸々は.gitで管理されることになる.
Repositoryの初期設定をする
ユーザ情報を設定する
git config --global user.name "<username>"- ローカルマシン内のすべてのlocal repositoryに触れるユーザを
<username>に設定する. - この設定情報は
~/.gitconfigに保存される.
- ローカルマシン内のすべてのlocal repositoryに触れるユーザを
- Local repositroy内で
git config --local user.name "<username>"- Local repositoryでの作業は
<username>によって行われたものとしてログされていく. git config --localにより設定したものは.git/configに保存される.- globalとlocalをどっちも設定している場合は,localの設定が優先される.
- Local repositoryでの作業は
- 同じ要領で,
git config --global/local user.emailでメールアドレスを登録する.
デフォルトのエディタやMerge tool, Diff toolを設定する
~/.gitconfigまたは.git/configに以下を追加する.- デフォルトエディタ,Marge tool, Diff toolにVSCodeを用いる場合は以下のようにする.
[core] editor = code --wait [merge] tool = vscode [mergetool "vscode"] cmd = code --wait $MERGED [diff] tool = vscode [difftool "vscode"] cmd = code --wait --diff $LOCAL $REMOTE
オリジナルのサブコマンドを設定する
- 例えば,
git config --global alias.co "checkout"git coでgit checkoutを呼ぶことができる
~/.gitconfigに直接設定してもよい.例えば,以下はgit historyで変更履歴を可視化するサブコマンド.
[alias]
history = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
設定値の確認
git config --local -lまたはgit config --global -lで設定を確認できる~/.gitconfigまたは<local repository>/.git/configを直接見ても良い.
Gitで管理したくないファイルを指定する
例えば,Macだと,
.DS_Storeという隠しファイルが勝手に作られてしまうので,.DS_StoreをGitの管理対象から除くためには以下を実行すると良い.git config --global core.excludesfile ~/.gitignore_global echo ".DS_Store" >> ~/.gitignore_globalgit ls-files -io --exclude-standardで,excludesfileにて無視されているファイル一覧を確認できる.
リモートリポジトリにはニックネームが付いている
リモートリポジトリには,ニックネームがついている.例えば,cloneしてきたローカルリポジトリにて,git config -lまたはgit remote -vを実行すると,以下のように,リモートリポジトリがoriginと名付けられていることが確認できる.
# git config -l でconfigを直接見る
remote.origin.url=git@github.com:<ユーザ名>/<リポジトリ名>.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
# git remote -v でリモートリポジトリ一覧をみる
origin git@github.com:<ユーザ名>/<リポジトリ名>.git (fetch)
origin git@github.com:<ユーザ名>/<リポジトリ名>.git (push)
一方,git initで作成したローカルリポジトリに,任意のリモートリポジトリのニックネームを設定するには,git remote add <nickname> git@github.com:<user>/<repository>.gitを実行する.
ここで設定された/されているリモートリポジトリのニックネーム(たとえばoriginなど)は,git push origin masterやgit pull origin masterなどのコマンドで利用される.
GitHubのレポジトリにHTTPSではなくSSHを使ってアクセスするように変更する
GitHubは2021年8月13日以降,パスワード(HTTPS)を使ったレポジトリへのアクセスを禁止する.
GitHubにpushしたときに,GitHubから”You recently used a password to access the repository at <repository>へのアクセス方法がSSHではなくHTTPSになっている.
git remote -vすると,以下のようにhttpsから始まるリモートリポジトリが登録されていることが確認できる.
origin https://github.com/<user>/<repository>.git (fetch)
origin https://github.com/<user>/<repository>.git (push)
SSHを使ったアクセスに変更するには,git remote set-url origin git@github.com:<user>/<repository>.gitを実行し(git@github.comの部分は自分のsshの設定による.sshの設定についてはリモートリポジトリから始めるを参照),git remote -vすると,以下のように設定され,以後はSSHによるアクセスとなる.
origin git@github.com:<user>/<repository>.git (fetch)
origin git@github.com:<user>/<repository>.git (push)
コミットのいろいろな呼ばれ方
- ブランチ:枝の先端のコミットを指す.「枝全体のコミット群をまとめたもの」をイメージしてしまうが,そうではない.
- HEAD:あるコミットを指すポインタのようなもの.
- HEAD^ と HEAD~ は,HEADの一つ前を指す.
- HEAD^^^ と HEAD~~~ と HEAD~3 は全部同じ意味.
- あるブランチをチェックアウトしたなら,HEADはそのブランチを指している.
- 無名ブランチとしてチェックアウトしているのなら,HEADはそのブランチを指しており,これをdetached HEADという.
- 無名ブランチ:ブランチ名でなくあるコミットを直接チェックアウトした場合,チェックアウト先のブランチを無名ブランチと呼ぶらしい.
- ORIG_HEAD:コミットオブジェクトをつくるコマンドの実行前にHEADだったコミットを指す
- 基本的に,
git reflogのHEAD@{1}とORIG_HEADは同じコミットを指すが,git stashはコミットオブジェクトをつくるので,git reflogのHEAD@{1}とORIG_HEADがずれることに注意.
- 基本的に,
- FETCH_HEAD:最後に
git fetchによって更新されたorigin/<branch>を指す. - MERGE_HEAD:マージ元のブランチを指す.
- CHERRY_PICK_HEAD:チェリーピック元のコミットを指す.
ブランチのいろいろな呼ばれ方
originが以下のようなブランチを持っているとすると,
master
branch1
branch2
git cloneにより,以下のようなブランチがローカルリポジトリにつくられる.
master
origin/master
origin/branch1
origin/branch2
masterは,origin/masterを追跡するブランチであり,「作業ブランチ」と呼ぶ.origin/masterは,作業ブランチmasterの「上流ブランチ」であり,リモートリポジトリのmasterを追跡する「リモート追跡ブランチ」でもある.git branch -vvにて,作業ブランチと上流ブランチの対応を確認することができる.
- いまいるブランチを「カレントブランチ」と呼ぶ.
git statusで確認できる
リモートブランチをリモート追跡ブランチに取り込む
git fetch <remote> <branch>すると,リモートの<branck>が<remote>/<branch>に取り込まれる.- 例:
git fetch origin masterにより,originのmasterがorigin/masterに取り込まれる.
- 例:
git fetch <remote>すると,リモートの全ブランチがorigin/*に取り込まれる.<branch>を省略すると,.git/configのremote.origin.fetchの設定が用いられる.
[remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* <=== +<source>:<target>- 例:
git fetch originを実行すると,originの全ブランチがorigin/*に取り込まれる.
git fetchを実行すると<remote>として,.git/configの作業ブランチごとに設定されているremoteパラメータが適用される.- 例:以下のような
.git/configの場合,master上で<remote>を省略すると,originが適用される.
[branch "master"] remote = origin- 例:以下のような
カレントブランチに上流ブランチがある場合は
git fetch <remote> <branch>が実行される.カレントブランチに上流ブランチがない場合は
git fetch <remote>が実行される.
特定のコミットをカレントブランチに取り込む
- カレントブランチ上で
git merge <branch>すると,<branch>がカレントブランチに取り込まれ,マージコミットが作られる.
E---F---G dev
/
A---B---C---D master
git checkout master & git merge dev
E---F---G dev
/ \
A---B---C---D---H master
git mergeのように<branch>を省略すると,カレントブランチの上流ブランチがカレントブランチにマージされる.- 以下のようなマージを,fast-forward(ff,早送り)マージという.
C---D---E dev
/
A---B master
git checkout master & git merge dev
C---D---E dev,master
/
A---B
git merge --no-commit <branch>:マージ後にコミットしない
リモートブランチをカレントブランチに取り込む
- カレントブランチで
git pull <remote> <remote branch>:<local branch>すると,<origin>の<remote branch>が<local branch>に取り込まれる.git fetch <remote> <branch> & git merge <remote>/<branch>をまとめたコマンド.- リモートブランチ一覧は
git branch -rで確認できる.
git pullのように<remote>と<branch>を省略して実行すると- カレントブランチに上流ブランチがある場合は,
git fetchとgit mergeのルールが適用される. - カレントブランチに上流ブランチが無い場合は,
git fetchのみ適用される.
- カレントブランチに上流ブランチがある場合は,
特定のコミットに移動する
- コミットに移動する
git checkout <commit>で,<commit>に移動する.<commit>にブランチ名を指定しないでチェックアウトした先を「無名ブランチ」と呼ぶ.一旦,名無しブランチにrebaseさせたりして様子を見て,大丈夫そうならmasterを名無しブランチにresetさせるというような使い方をする.
git chekout -b <branch名>で,<branch名>を作成して,<branck名>に移動する.- チェックアウトすると,内部的には,オブジェクトからインデックスへのコピーが起こり,そこからワーキングツリーへ書き出すという作業が行われている.
- ブランチをつくる
git branch <branch名>で,<branch名>を作成する.git branch <branch名> <commit>で,<commit>を親にした新たなブランチを作成する.
git branch -aで,ブランチ一覧を確認できる.- ブランチを削除する
git branch -d <branch名>
- 上流ブランチを変更する
git branch -u <remote>/<branch> <branch>
- あるコミット時のファイルを参照する
git checkout <commit> <filename>
カレントブランチの状態を確認する
git status- カレントブランチの状態を確認できる.
- ワーキングツリーで変更があったもの,ステージングされているもの,トラッキングされていないものなど
- カレントブランチの状態を確認できる.
変更したものをとりあえずひとまとめにする
git addを実行することで,インデックス(変更されたファイルのスナップショットのようなもの)が内部的につくられる.この動作をステージングという.git add .- ワーキングツリーにて新規作成または変更されたファイルをステージング
git add -u- ワーキングツリーにて
git rmまたは変更されたファイルをステージング - つかいどころ:
git rmしたときにgit add -uする
- ワーキングツリーにて
git add -Agit add .とgit add -uを合体させたもの
変更を確定する
git commit -m <message><message>というコミットメッセージをつけてコミットする
git commit --amend -m 'hogehoge'- 直前のコミットメッセージを変更する
- いくつか前のコミットメッセージを変更したい場合は,
git rebase -iでrewordを指定する.
あるコミット時点のファイルをみる
git show <commit>:<file>- あるコミット時点のあるファイルの中を見る
変更履歴(コミットログ)をみる
git log <branch>で<branch>に属したコミットログをみる--onelineを指定すると,一行1コミットで表示される.--graphを指定すると,枝が可視化される.
git log <branch A> <branch B>- 以下のA,B,C,D,E,F,Gを表示する.
E---F---G branchB / A---B---C---D branchAgit log <branch A>..<branch B>- 以下のE,F,Gを表示する.
git diff X..Yとスコープが異なるので注意.
E---F---G branchB / A---B---C---D branchA- 以下のE,F,Gを表示する.
git log <branch A>...<branch B>- 以下のC,D,E,F,Gを表示する.
git diff X...Yとスコープが異なるので注意.
E---F---G branchB / A---B---C---D branchA- 以下のC,D,E,F,Gを表示する.
git show-branch <branch A> <branch B> ..<branch A>と<branch B>...が交わる根本までのブランチとコミットの対応が表示される.一番下の行が根本.---より上にあるのが作業ブランチ一覧,ブランチごとにインデントがずれている.---より下にあるのがブランチとコミット一覧.*はカレントブランチに属するコミット.+は,作業ブランチ(---より上に表示されたインデントのズレ)に対応するブランチに属していることを表す.
差分をみる
git diff- ワークツリーとインデックスの差分
git diff <target>- ワークツリーと
<target>の差分.<target>にはHEADやHEAD^(HEADの一つ前),コミットのハッシュ値などを指定する.
- ワークツリーと
git diff <source> <target>またはgit diff <source>..<target><source>と<target>の差分.HEADやコミット,ブランチ,タグを指定する.
git diff <commit A>...<commit B><commit A>と<commit B>の共通の親(マージベース)と,<commit B>との差分が得られる.- 例えば,以下の状態で,
git diff G...Dを実行すると,BとDの差分が得られる.
E---F---G dev / A---B---C---D mastergit diff `git merge-base <commitA> <commit B>` <commit B>でも良い.
git diff --cached(--staged)git addしたインデックスとHEADとの差分
git diff <option>--name-only:変更されたファイル一覧-- <file>:特定のファイルに関してdiffをとる-w:改行コードや空白を無視する--ignore-blank-lines:空行を無視する
特定のコミットをカレントブランチに反映させる
git cherry-pick <commit>:<commit>をカレントブランチに反映させる.-nを指定することで,カレントブランチのワークツリーとインデックスに<commit>を持ってくることができる.
枝を付け替える
git rebaseは既存のブランチをガッツリ変更してしまうのでgit pushするときは-fオプションをつけて強制的にpushする必要がある.なので,git rebaseを好まないプロジェクトもある.
git rebase <start> <end>
<start>の次のコミットから<end>までのコミットを<start>に移す.<start>がブランチ名なら,<start>の枝と<end>の枝の共通のコミットの次のコミットから<end>までのコミットが<start>に移される.
<end>は指定しなくても良い.指定しない場合はカレントブランチが選択される.- rebase途中でコンフリクトが起きる場合がある.
- コンフリクトを解消して続ける場合は
git rebase --continue - rebaseをやめるには
git rebase --abort
- コンフリクトを解消して続ける場合は
以下はrebaseの内部動作を図示したもの.元のE,F,Gが,Dからの新しいコミットとして順番にcherry-pickされる.親が変わるので,E,F,GのハッシュとE’,F’,G’のハッシュは違うものになる.
E---F---G dev / A---B---C---D master # git checkout dev & git rebase master # または # git rebase master dev E---F---G A---B---C---D master \ E' dev A---B---C---D master \ E'---F' dev A---B---C---D master \ E'---F'---G' dev # git chekout master & git merge dev A---B---C---D \ E'---F'---G' dev,master
git rebase -i <start> <end>
対話モードでrebaseできる.cherry-pick中のコミットについて,細かい操作が行える.
E---F---G dev / A---B---C---D master # git rebase -i E dev # # pick F <=== Fは採用 # squash G <=== GはFとまとめてしまう # # p, pick:コミットを採用 # r, reword:コミットを採用するが、コミットメッセージを変更 # e, edit:コミットを採用するが、ファイルを修正する # s, squash:一個前のコミットと合体させる # f, fixup:コミットメッセージを変更しない点以外、squashと同じ # x, exec:shellでコマンドを実行する E---F' dev / A---B---C---D master
git rebase --onto <target> <start> <end>
<start>の次のコミットから<end>までのコミットを<target>に持っていく.git checkout <end> & git rebase --onto <target> <start>でも良い.H--I--J dev2 / E---F---G dev / A---B---C---D master # git rebase --onto master F dev2 # または # git checkout dev2 & git rebase --onto master F E---F---G dev / A---B---C---D master \ H'--I'--J' dev2
特定のブランチをリモート追跡ブランチとリモートブランチに反映する
git push <remote> <branch>- 作業ブランチ
<branch>を,同名のリモート追跡ブランチとリモートブランチに反映する - コンフリクトが起きたら失敗する.
- 作業ブランチ
git push <remote><branch>を省略した場合は,カレントブランチと同名のリモート追跡ブランチとリモートブランチに反映される.- 具体的な設定は
push.defaultに依存する.
git push <remote> <local branch>:<remote branch><local branch>の最新の状態を<remote branch>に反映する.<local branch>を空にして実行すると,<remote branch>が削除される.
変更を戻す
git reset
git resetには3つのオプションがある--soft: HEADの位置を変更する--mixedまたは指定しない : HEADとインデックスの位置を変更する--hard: HEADとインデックス,ワーキングツリーの位置を変更する
git reset <target><target>(ファイルやディレクトリ)に対してgit resetを実行する<target>に.を指定すると,今いるディレクトリ配下のファイルに対してgit resetが実行される.
# 初期状態
B <- HEAD,インデックス,ワーキングツリー
|
A
# ファイルを修正するとワーキングツリーが進む
<- ワーキングツリー
|
B <- HEAD,インデックス
|
A
# git add により,インデックスが進む
<- ワーキングツリー,インデックス
|
B <- HEAD
|
A
# ファイルを修正するとワーキングツリーが進む
<- ワーキングツリー
|
<- インデックス
|
B <- HEAD
|
A
# git reset --soft HEAD
# HEADがHEADに移動する(この場合は何も起きない)
<- ワーキングツリー
|
<- インデックス
|
B <- HEAD
|
A
# git reset --soft HEAD^
# HEADがHEAD^に移動する
<- ワーキングツリー
|
<- インデックス
|
B
|
A <- HEAD
# git reset HEAD
# HEADとインデックスがHEADに移動する
<- ワーキングツリー
|
B <- HEAD,インデックス
|
A
# git reset HEAD^
# HEADとインデックスがHEAD^に移動する
<- ワーキングツリー
|
B
|
A <- HEAD,インデックス
# git reset --hard HEAD
# HEAD,インデックス,ワーキングツリーがすべてHEADに移動する
B <- HEAD,インデックス,ワーキングツリー
|
A
# git reset --hard HEAD^
# HEAD,インデックス,ワーキングツリーがすべてHEAD^に移動する
B
|
A <- HEAD,インデックス,ワーキングツリー
git reflog
git reflog- HEADの移動履歴をみる
git reset --hard <commit>でそのコミットまで戻る
git reflog <branch>* ブランチが指していたコミットの一覧をみる
コミットにタグを付ける
git tag <name> <commit><commit>に<name>というタグをつける.<commit>を省略した場合はカレントブランチが選択される.- タグを作成したユーザの情報などは保存されない.
git tag -a <name> <commit> -m <comment><commit>に<name>という注釈付きタグをつける.<commit>を省略した場合はカレントブランチが選択される.- タグを作成したユーザの情報なども保存される.
git tag -s <name> <commit> -m <comment><commit>に<name>という署名付きタグをつける.OSSなどにcontributeする際に使うことが多い.
git tag -d <tag><tag>を削除する
git show <tag><tag>がつけられたコミットをみる
git push origin <tag><tag>をpushする.git push origin --tagsで,ローカルの全tagをpushできる.
- タグをチェックアウトするのはおすすめしない.
git checkout <tag>しても,tagに紐付いたcommitをチェックアウトするだけ.git tag -dで元のタグを消してgit tag -aで新しいタグを付けるか,git tag -f -aで強制的に変更する.
ファイルを削除する
git rm/cleanを使わずにファイルを直接消しても問題ない.git rm <target>- インデックスとワークツリーから
<target>(ファイルまたはディレクトリ)を削除する -rオプションで再帰
- インデックスとワークツリーから
git rm --cached <file>- インデックスから
<file>を削除する
- インデックスから
git clean -n- トラックしていないファイル一覧
git clean -f- トラックしていないファイルを削除する
-dオプションでディレクトリも削除
ワークツリーの内容をまるっと取り置きする
git stashまたはgit stash savegit stash clear:全stashを削除git stash list:stashの一覧を表示git stash list -p:差分を確認git stash show stash@{X}で,スタッシュされたファイル一覧をみる.git stash apply stash@{X}でワーキングツリーに戻す.ただしstashには残っている.git stash drop stash@{X}で特定のstashを削除.git stash pop stash@{X}でpopする.
リポジトリに依存関係を持たす
あるリポジトリをsubmoduleとして追加する
git submodule add <user>@github.com:<repository>.git <directory><directory>/に<repository>へのリンクが追加される.- これにより,
.gitmoduleが作成され,どのリポジトリに依存しているかなどの情報が保持される.
git submodule- submoduleを確認することができる.
submoduleを持っているrepositoryをcloneする
.gitmoduleが埋め込まれたrepositoryをcloneしても,submoduleは自動でcloneされない.git submodule update -i- submoduleをclone/updateしてくれる.
-iは,initとupdateを一括で行うためのオプション.
- submoduleをclone/updateしてくれる.
git submodule deinit- submoduleとの依存を切ることができる.
git submoduleで”-“がついているものは依存が切れている.
- submoduleとの依存を切ることができる.
git rm <submodule名>.gitmoduleからsubmodule情報が消え,submoduleも消える.
そもそもGitはどのように差分を管理しているか
blob/tree/commitオブジェクトを関連付けて差分管理している
- blobオブジェクト
- treeの子.ファイルの中身を保持している.
git hash-object <filename>:<filename>からハッシュ値を生成できる- ファイルサイズとファイル内容からSHA-1でハッシュ値を計算している.
- あるローカルリポジトリ内で,
git cat-file -t <hash>:<hash>で指定したものがblobなのかtreeなのかcommitなのかを確認できる.git cat-file blob <hash>:<hash>に対応したファイルの内容を見ることができる.
- treeオブジェクト
- blobの親.ファイル名や作成日時などのメタデータを保持している.
git cat-file -p <hash of tree>:ツリーにぶら下がっていblob/tree一覧を確認できる.git cat-file -p <branch>^{tree}:<branch>が参照しているツリーオブジェクトを見る.
- commitオブジェクト
git cat-file commit <hash of commit>:treeやparentを確認できる.
git ls-tree <commit>:<commit>でリポジトリに追加されたblob/tree一覧を確認できるgit rev-parse <commit>:<commit>自体のtreeのhashを確認できる.find .git/objects -type f:blob/tree/commitの実体.git showで見れるのはこれ.
git addとgit commitで何をやっているか
echo hoge > hoge.txt & echo hoge > hoge.txt & git add hoge.txtgit ls-files --stage- インデックスされたファイルのblob一覧が表示される
git write-tree- blobをまとめたtreeが作られ,そのtreeのハッシュが表示される.
echo "first commit" | git commit-tree <git write-treeのハッシュ>git write-treeで作られたtreeをまとめたcommitをつくる
echo <git commit-treeのハッシュ> > .git/refs/heads/masterまたはgit update-ref- masterブランチが
git commit-treeのハッシュを指すようにする
- masterブランチが
git symbolic-ref HEAD refs/heads/master- HEADがmasterブランチを指すようにする.
- コミット完了
通常のコミットとマージコミットの違い
git cat-file -p <commit>で通常のコミットを見ると,parentを一つ持っていることがわかる.一方,マージコミットでは,parentを2つ持っている.1つ目のparentはマージ先のコミットを指し,2つ目のparentはマージ元のコミットを指している.- マージを元に戻すときは
git reset --hard ORIG_HEADをおすすめする.git reset --hard HEAD^だと,以下のような現象が起こる.
E---F---G dev
/
A---B---C---D master
git checkout dev & git merge master
E---F---G---I dev
/ /
A---B---C---D---H master
git checkout master & git merge dev
E---F---G---I dev,master <=== fast-forward merge...
/ /
A---B---C---D---H
git reset --hard HEAD^
E---F---G dev
/
A---B---C---D master
- fast-forward mergeにより,HEADがdevとmasterを指してしまっていることが原因.
git reset --hard ORIG_HEADで,チェックアウトしているブランチの直前のHEAD(ORIG_HEAD)にリセットすることで,期待通りの動作が行える.
E---F---G---I dev,master
/ /
A---B---C---D---H
git reset --hard ORIG_HEAD
E---F---G---I dev
/ /
A---B---C---D---H master