6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Gitなにもわからない

Last updated at Posted at 2023-07-13

はじめに

Gitがわからない。
なぜわからないのか?

仮説:動作が見えないからではないか

解決案:.git/内を参照すると、ファイルの変更として見ることができるので、どういう動きをしているのかを確認することで仕組みを理解できないか

やってみてわかったことまとめ

  • branchというのは、言葉から想像していた線形のものではなく、特定のcommitを指す点だった。
  • ファイルをステージングした時点で.git/object/内にバックアップが取られていた。(なのでこまめにaddするとリポジトリの容量が増えてしまう。)
  • commitは差分ではなくスナップショットと言われている意味がわかった
  • 至るところで現れるハッシュの使い道がわかった。
  • stashはcommitだった
  • commit -ammedは上書きじゃなかった
  • resetは言葉から想像するいわゆるリセットではなく特定コミットへの切り替えがメインで、インデックスとワーキングツリーをリセットするのはおまけ
  • なぜ空のディレクトリを記録できないのかわかった(ディレクトリはgitオブジェクトではなく、indexでのファイルのパスでしかないため)

など

この記事で扱う範囲

GitHub前提で書いています。
とりあえず使えるようになるのが目的なので、git gcの動作については扱いません。
pullはfetchとmerge、checkoutはswitchとrestoreを使うので扱いません。

1. .git/とは

GitHubなどのリモートリポジトリをgit cloneしてきたり、ローカルでgit initでローカルリポジトリを作成すると現れます。

1-1. .git/の中身

全体像を示すのでまだ説明していない言葉も出てきますが、後から登場します。

  • 一部のファイルはバイナリファイルなので、エディタでそのまま開いても中身が見られません
  • リポジトリの状態によって、ファイルやディレクトリはあったりなかったりします

ファイル構成

directory
 .git/
  ├ hooks/ 
  │  ├ applypatch-msg.sample  
  │  ├ commit-msg.sample
  │  ├ fsmonitor-watchman.sample
  │  ├ post-update.sample
  │  ├ pre-applypatch.sample
  │  ├ pre-commit.sample
  │  ├ pre-merge-commit.sample
  │  ├ prepare-commit-msg.sample
  │  ├ pre-push.sample
  │  ├ pre-rebase.sample
  │  ├ pre-receive.sample
  │  ├ push-to-checkout.sample
  │  └ update.sample
  ├ info/
  │  ├ exclude 
  │  └ attributes  
  ├ logs/
  │ ├ refs/
  │ │ ├ heads/
  │ │ │  └ ブランチ名
  │ │ ├ remotes/
  │ │ │  └ リモート名/
  │ │ │     └ HEAD
  │ │ ├ stash
  │ │ └ tags/
  │ │     └ タグ名
  │ └ HEAD
  ├  lost-found/
  │   ├ commit/
  │   └ other/
  ├ objects/
  │  ├ info/
  │  │  ├ commit-graph
  │  │  └ packs
  │  └ pack/ 
  │     ├ pack-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.idx
  │     └ pack-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.pack
  ├ refs/
  │  ├ heads/
  │  │  └ ブランチ名
  │  ├ remotes/
  │  │  └ リモート名/
  │  │      └ HEAD
  │  ├ tags/
  │  └ stash
  ├ AUTO_MERGE
  ├ CHERRY_PICK_HEAD
  ├ COMMIT_EDITMSG
  ├ config
  ├ description
  ├ FETCH_HEAD
  ├ HEAD
  ├ index
  ├ MERGE_HEAD
  ├ MERGE_MODE
  ├ MERGE_MSG
  ├ ORIG_HEAD
  ├ packed-refs
  └ REBASE_HEAD
.git/hooks/

hooksのサンプルファイルが13個格納されていますが、この記事には関係ないので触れません。
hooksのサンプルファイルは、末尾の.sampleを外せばフックスクリプトとして動作します。

.git/info/exclude

自分専用の.gitignoreのようなものです。
.gitignore と同様にGit管理から除外されます。
他のリポジトリ(=リモートや他人のローカル)には影響しません。

.git/info/exclude
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.git/info/attributes
.git/logs/refs/heads/ブランチ名

commitの履歴が下に追加されていきます。

.git/logs/refs/heads/ブランチ名
0000000000000000000000000000000000000000 commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	branch: Created from HEAD
.git/logs/refs/remotes/リモート名/HEAD
.git/logs/refs/remotes/リモート名/HEAD
ref: refs/remotes/origin/ブランチ名
.git/logs/refs/stash
.git/logs/refs/tags/タグ名
.git/logs/HEAD

ローカルでのHEADの移動履歴が追記されていきます。

.git/logs/HEAD
commitのハッシュ1(40桁) commitのハッシュ2(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	clone: from github.com:ユーザー名/リポジトリ名.git
.git/lost-found/commit/
.git/lost-found/other/
.git/objects/info/commit-graph
.git/objects/info/packs
.git/objects/info/packs
.git/objects/pack/pack-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.packへの参照
.git/objects/

2文字のディレクトリの中にファイル名が38桁のファイルが入ります。
2+38の40文字がgitオブジェクトのハッシュになります。

.git/objects/pack/

パックしたファイルが格納されます。(idxとpack)
git gcするたびにファイル名は変わります。

.git/refs/heads/ブランチ名

ローカルにある各ブランチが指すcommitへの参照です。

.git/refs/heads/ブランチ名
ブランチのHEADが指すコミットのハッシュ(40桁)
.git/refs/remotes/リモート名/HEAD

リモートブランチのHEADのブランチへの参照です。

.git/refs/remotes/リモート名/HEAD
ref: refs/remotes/origin/ブランチ名
.git/refs/remotes/リモート名/ブランチ名

最後に取得された時点のリモートリポジトリのブランチのcommitの情報が入っています。

.git/refs/remotes/リモート名/ブランチ名
リモートブランチのHEADが指すcommitのハッシュ(40桁)
.git/refs/tags/タグ名

軽量タグ、アノテーションタグを使用するとタグ名のファイルが作成されます。

tagの種類 参照
軽量タグ commitオブジェクト
アノテーションタグ tagオブジェクト
.git/refs/tags/タグ名
tagオブジェクトのハッシュ
.git/refs/stash
.git/refs/stash
最後に行ったstashのcommitのハッシュ
.git/AUTO_MERGE
.git/AUTO_MERGE
コンフリクトしている状態のファイルを含むcommitのハッシュ
.git/CHERRY_PICK_HEAD

チェリーピックでブランチに統合されたcommitを記録しています。

.git/CHERRY_PICK_HEAD
cherry-pickしているcommitのハッシュ
.git/COMMIT_EDITMSG
.git/COMMIT_EDITMSG
最後に実行したcommitのコミットメッセージ
.git/config

上流ブランチの設定はここに記載されます。

.git/config
[core]
	repositoryformatversion = 0
	filemode = false
	bare = false
	logallrefupdates = true
	symlinks = false
	ignorecase = true
[remote "リモート名"]
	url = git@github.com:ユーザー名/リモートリポジトリ名.git
	fetch = +refs/heads/*:refs/remotes/リモート名/*
[branch "ローカルブランチ名"]
	remote = リモート名
    merge = refs/heads/上流ブランチ名
.git/description
.git/description
 //翻訳:無名のリポジトリ。このファイル'description'を編集してリポジトリに名前をつけてください。
 Unnamed repository; edit this file 'description' to name the repository.
用途
  • gitweb
  • リポジトリ名を動的に読み取るカスタムフックの開発用
.git/FETCH_HEAD

最後にgit clone、git fetchなどをしたときのリモートリポジトリの状態が記録されています。

  • git cloneやgit fetch(ブランチ指定なし)をした場合は複数が記録されている
  • git fetch リモート名 ブランチ名などで1つのブランチだけfetchした場合は1つだけ記録されている
terminal
そのブランチが指すコミットのハッシュ(40桁) (not-for-merge)	branch 'ブランチ名' of github.com:ユーザー名/リモートリポジトリ名

「not-for-merge」はマージする対象でないと判定されたブランチに表示されます。

.git/HEAD

現在のブランチが記録されています。
refは通常は、ブランチを指します。
HEADが直接コミットを指す場合、「detached HEAD」(ブランチから切り離されたHEAD)と呼ばれます。
.git/HEADで示しているブランチのHEADのハッシュは.git/refs/heads/ブランチ名で確認できます。

.git/HEAD
ref: refs/heads/ブランチ名
.git/index

バイナリファイルなのでそのままエディタで開いても中身は見られません。
追跡中のファイルが記録されています。

.git/MERGE_HEAD

git mergeを実行するときにブランチにマージするコミットを記録しています。

.git/MERGE_HEAD
マージされるブランチ名のHEADが指すcommitのハッシュ
.git/MERGE_MODE

マージが進行中であることを知らせるためだけに作成されます。

.git/MERGE_MODE
(空)
.git/MERGE_MSG
.git/MERGE_MSG 
コンフリクト解消後のgit commitのデフォルトのメッセージ
.git/ORIG_HEAD

HEADを移動するコマンドを実行したときに、コマンド実行時のHEADだったコミットのハッシュが記録されています。

.git/ORIG_HEAD
commitのハッシュ(40桁)
.git/FETCH_HEAD
.git/FETCH_HEAD
 commitのハッシュ(40桁)		branch 'ブランチ名' of github.com:ユーザー名/リポジトリ名
.git/packed-refs

git gcを実行すると、不要なコミットなどが削除され、refがpacked-refs という一つのファイルにまとめられます。(git gcは明示的に叩かなくても、内部で適宜実行されます。)

.git/REBASE_HEAD

rebase実行前のブランチのHEADのcommitを記録します。

commitのハッシュ(40桁)

1-2. gitオブジェクト

Gitオブジェクト(4種類)

  • commit
  • blob
  • tree
  • tag

1-1-1.png
1-1-2.png
1-1-3.png

gitオブジェクトのタイプ(ハッシュから)
terminal
$ git cat-file -t gitオブジェクトのハッシュ
gitオブジェクトの確認

ワーキングツリーのファイルパスからは確認できません。
(同じファイルパスのものがたくさんblobオブジェクトとして保存されるので、ファイルパスからは一意に特定できない)

terminal
//gitオブジェクトのハッシュから確認
$ git cat-file -p gitオブジェクトのハッシュ
 
//ブランチ名から確認(ブランチ名の場合はcommitオブジェクト)
$ git cat-file -p ブランチ名
commitオブジェクトをgit cat-fileすると確認できるもの
  • トップレベルのtreeオブジェクトへの参照
  • コミットしたユーザーの情報
  • タイムスタンプ
  • コミットメッセージ
  • 親コミットへの参照(1つ前のコミット)
親コミットの数
0 initial commit
1 通常のコミット
2 merge commit、stash
3以上 octopusマージ
blobオブジェクトをgit cat-fileすると確認できるもの
  • blobオブジェクトの中身
    ※ファイル名やパスはblobオブジェクトではなくtreeオブジェクトに記録されているので、確認できません。
treeオブジェクトをgit cat-fileすると確認できるもの

そのtreeオブジェクトが参照している各Gitオブジェクトの

  • タイプ(blob or tree)
  • ハッシュ
  • ファイル名(ディレクトリ名)
数字 種類
100644 blob(通常のファイル)
120000 シンボリックリンク
040000 tree(ディレクトリ)
100755 実行可能ファイル
tagオブジェクトをgit cat-fileすると確認できるもの
tagの種類 参照など
軽量タグ commitオブジェクト
アノテーションタグ tagオブジェクトへの参照、メッセージ
terminal
//現在のHEADが指すcommitオブジェクトに軽量タグをつける
$ git tag タグ名

//commitを指定して軽量タグをつける
$ git tag タグ名 コミットのハッシュ

//現在のHEADが指すcommitオブジェクトにアノテーションタグをつける
$ git tag -a タグ名 -m "メッセージ"

//commitを指定してアノテーションタグをつける
$ git tag -a タグ名 -m "メッセージ" commitのハッシュ
タグが追加されているのを確認
:terminal
$ git log
.git/refs/tags/タグ名が作成される
.git/refs/tags/タグ名
 参照するcommitオブジェクトのハッシュ(軽量タグの場合)
 参照するtagオブジェクトのハッシュ(アノテーションタグの場合)
アノテーションタグ(tagオブジェクト)の確認
:terminal
$ git cat-file -p ハッシュ

object 参照するcommitオブジェクトのハッシュ(40桁)
type commit
tag アノテーションタグ名
tagger ユーザー名 <メールアドレス> UNIX時間 +0900

メッセージ
その他
:terminal
//タグの確認(ローカル)
$ git tag	

//タグの確認(リモート)
$ git ls-remote --tags

//タグの中身の確認
$ git show タグ名	
 
//特定のタグを削除(ローカル)
$ git tag -d タグ名

//特定のタグを削除(リモート)
$ git push リモート名 --delete タグ名

//タグをリモートリポジトリにプッシュ(すべてのタグ)
$ git push --tag
 
//タグをリモートリポジトリにプッシュ(特定のタグ)
$ git push リモート名 タグ名

1-3. ref(リファレンス)

branchとは

commitにつけるポインタです。commitが進むと、自動的に移動します。ブランチ名でcommitを指定できます。

branchの確認

Git操作
terminal
//branchの確認(ローカル)
$ git branch	

//branchの確認(ローカルとリモート追跡)
$ git branch -a
$ git branch --all

//branchの確認(リモート追跡)
$ git branch -r
$ git branch --remotes

上流ブランチの設定

Git操作
terminal
//branchの確認(ローカルと上流の対応)
$ git branch -vv

//上流ブランチの設定(ローカルブランチとリモートブランチの紐付け)
$ git branch -u リモート名/リモート追跡ブランチ名 ローカルブランチ名	

//上流ブランチの設定(ローカルブランチとリモートブランチの紐付け)の解除
$ git branch --unset-upstream ローカルブランチ名
.git内で起こること

設定すると下記が追記され、削除すると消えます。

.git/config
[branch "ブランチ名"]
    remote = リモート名
    merge = refs/heads/上流ブランチ名

リモート追跡ブランチの削除

Git操作
terminal
//特定のリモート追跡ブランチの削除
$ git branch -d -r リモート名/リモート追跡ブランチ名	
 
//リモート追跡ブランチの削除(リモートで削除されたもの)
$ git fetch --prune
$ git fetch -p
$ git remote prune リモート名
.git内で起こること

.git/refs/remotes/リモート名/ブランチ名が削除されます。

ブランチの作成

2-1-4.png

Git操作
terminal
//現在のcommitからbranchを作成
$ git branch 作成するブランチ名

//特定のcommitからbranchを作成
$ git branch 作成するブランチ名 commitのハッシュ

//リモートブランチからローカルブランチの作成
$ git branch 作成するブランチ名 リモートブランチ名
.git内で起こること
  • .git/refs/heads/ブランチ名が作成される
.git/refs/heads/ブランチ名
branchが指すcommitオブジェクトのハッシュ
  • .git/logs/refs/heads/ブランチ名が作成される
    ログが下に追記されていきます。
.git/logs/refs/heads/ブランチ名
親commitオブジェクトのハッシュ commitオブジェクトのハッシュ ユーザー名 <メールアドレス> Linux時間 +0900	branch: Created from 作成元のブランチ名

最初の行の「親commitオブジェクトのハッシュ」は「0000000000000000000000000000000000000000」になります。
最後の行のcommitオブジェクトのハッシュは、.git/refs/heads/ブランチ名に記載されている値と一致します。

.git/refs/heads/ブランチ名に記載されるものがcommitオブジェクトであることを確認する

.git/refs/heads/ブランチ名のファイルに書き込まれているハッシュをgit cat-fileします。

terminal
$ git cat-file -p .git/refs/heads/ブランチ名のハッシュ
 
tree treeオブジェクトのハッシュ
parent 親commitオブジェクトのハッシュ
author ユーザー名 <メールアドレス> UNIX時間 +0900
committer ユーザー名 <メールアドレス> UNIX時間 +0900

 
$ git cat-file -t commitオブジェクトのハッシュ
 
commit

commit オブジェクトと確認できました。

branchの切り替え

2-1-1.png

Git操作

ワーキングツリー上での変更後や、ステージング後にcommitせずにブランチ移動したら?
変更がぶつかる場合はブランチ移動できない(作業を失わないようにgitが止めてくれる)
変更がぶつからない場合(編集箇所がコンフリクトしない場合や、新規ファイルなど)はもったまま移動する

terminal
//branchを指定して切り替え
$ git switch ブランチ名
 
//branchを作成して切り替え
$ git switch -c ブランチ名
 
//指定したコミットからbranchを作成して切り替え
$ git switch -d ブランチ名 branchを作成したいcommitのハッシュ	
.git内で起こること
  • .git/HEADが更新される
.git/HEAD
ref: refs/heads/切り替え後のブランチ名
  • indexが切り替え後のbranchの追跡済みファイルの情報に切り替わる
  • FETCH_HEADはタイムスタンプが更新される
  • .git/logs/HEADの下にログが追記される

detached HEAD

branchを介さず、直接commitオブジェクトを参照している場合はdetached HEAD(ブランチがない)という状態になります。

用途

  • 過去のcommitの状態の再現
  • 実験的に変更を加えてみる

2-1-15.png

detached HEAD状態で特定のcommitに移動する
terminal
//ブランチ名を介さずコミットを直接指定して切り替える
$ git switch -d コミットのハッシュ
$ git switch --detach コミットのハッシュ

detached HEADの状態でもcommitはできるが、他のbranchに切り替えると参照できなくなります。
(commitのハッシュを記録しておけば戻ることは可能です。)

detached HEADであることの確認
terminal
//確認
$ git status
 
HEAD detached at xxxxxx

  
//確認
$ git branch
  
* (HEAD detached at xxxxxxx)

.git/HEADでも確認可能

.git/HEAD
branchのパスではなく、直接commitのハッシュが記載されている
detached HEAD状態から抜け出す
//試したものを破棄したい場合は他の既存のブランチに切り替える
$ git switch ブランチ名

//試したものを使いたい場合は、新規にbranchを作る
$ git branch ブランチ名

ブランチ名変更

Git操作
terminal
//ブランチ名の変更(変更後の名前と同名ブランチがあると失敗)
$ git branch -m 変更前のブランチ名 変更後のブランチ名	
 
//現在のブランチ名を変更する(強制上書き)
$ git branch -M 変更後のブランチ名	
.git内で起こること
  • .git/refs/heads/旧ブランチ名→新ブランチ名
  • .git/logs/refs/heads/旧ブランチ名→新ブランチ名
  • 現在のブランチの名前を変更すると.git/HEADも書き換わる

ブランチ削除

Git操作
terminal
//branchを削除(他のbranchにmerge済のbranchのみ)
$ git branch -d 削除したいブランチ名

//branchを削除(他のbranchにmergeしていないbranchも削除可能)
$ git branch -D 削除したいブランチ名 
.git内で起こること
  • .git/configが変更される
  • リモートにpush済のbranchを削除すると、下記が削除される
.git/congig
[branch "削除したブランチ名"]
    remote = origin
	merge = refs/heads/削除した上流ブランチ名
  • .git/refs/heads/ブランチ名 が削除される
  • .git/logs/refs/heads/ブランチ名 が削除される

リモートのbranchの削除

terminal
//リモートブランチの削除
$ git push -dリモート名 ブランチ名

削除したローカルブランチを復活させる

Git操作

どのコミットを復活させたいのか確認のためHEADの履歴を表示をします。

terminal
$ git reflog

実際に復活させてみます。

terminal
$ git branch 復活後のブランチ名 HEAD@{n}
.git内で起こること
  • .git/refs/heads/復活後のブランチ名 が作成される
.git/refs/heads/復活後のブランチ名
branchが指すcommitオブジェクトのハッシュ
  • .git/logs/refs/heads/復活後のブランチ名 が作成される
.git/logs/refs/heads/
0000000000000000000000000000000000000000 commitオブジェクトのハッシュ ユーザー名 <メールアドレス> Linux時間 +0900
branch: Created from HEAD@{n}

commitせずにbranchを切り替えした場合(stashを使わない場合)

ワーキングツリーのファイルやステージングされたファイルがcommitオブジェクトに紐づけされていないので、切り替え後のbranchと混じってしまいます。(もしくは衝突する場合は切り替え自体できません。)

terminal
//ステージングがぶつかる場合
error: Your local changes to the following files would be overwritten by checkout:
Please commit your changes or stash them before you switch branches.

//ワーキングツリーがぶつかる場合(untracked fileがぶつかる)
error: The following untracked working tree files would be overwritten by checkout:
Please move or remove them before you switch branches.

commitせずにbranchを切り替えした場合(stashを使う場合)

stashしてからブランチを切り替えしてgit stash popすると、auto-mergeされます。
コンフリクトしなければ、その時点ではステージングはもともと存在した方の状態のままです。
コンフリクトしたらboth addedになります。
その時点でステージングを確認すると、同じファイルが2つになります。(ハッシュは違います。)
コンフリクトを解消する場合に片方のみを採用すると、採用した方のハッシュのまま残ります。

stash

見た目上、まだローカルコミットしていない内容(ステージングしているかどうかは関係なし)を一時的に避難します。(実際にはcommitされます。)
stashの対象はGit管理下のファイルです。(untracked fileは退避されません。)

2-1-2.png

ユースケース
  • いますぐ別のbranchで作業したいことができたけど現在の作業内容をcommitしたくないとき
  • 作業ブランチを間違えたときに今までの作業(commitしていないものに限る)を正しいbranchに適用したいとき
Git操作
terminal
//変更差分を退避させる(untracked fileは退避されない)
$ git stash
 
//変更差分を退避させる(名前付き、untracked fileは退避されない)
//git stash listで退避のリストを見るときに便利
$ git stash save スタッシュ名

  
//変更差分を退避させる(untracked fileも変更も退避される)
$ git stash -u
 
//変更を退避させる(ステージングしたファイルは除外)
$ git stash -k	
$ git stash --keep-index
.git内で起こること

stashした時点で

  • ORIG_HEADはかわらない
  • HEADはかわらない(ハッシュは更新されないのでブランチ自体には影響を与えない)
  • ワーキングツリーとステージングはHEADと同じ状態になる
  • reflogは増える
  • 「.git/refs/stash」と「.git/logs/refs/stash」が作成(すでにある場合は変更)される
  • 「.git/refs/stash」は最新のstashのcommitのハッシュ
  • 「.git/logs/refs/stash」は現在stashのcommitすべてのハッシュ
  • stashはコミット(stashするものも、しないものも含むコミット) 親2つ 元々のコミットと、編集したファイル自体はblobオブジェクトとして.git/objects以下に保存されている
  • .git/refs/stashが作成される
.git/refs/stash
参照するcommitオブジェクトのハッシュ(最新のstash)
.git/refs/stashに書かれているハッシュが何か確認する
terminal
$ git cat-file -p .git/refs/stashで参照されているハッシュ(40桁)

tree treeオブジェクトのハッシュ(40桁)
parent 親commitオブジェクト1のハッシュ(40桁)
parent 親commitオブジェクト2のハッシュ(40桁)
author ユーザー名 <メールアドレス> UNIX時間 +0900
committer ユーザー名 <メールアドレス> UNIX時間 +0900

WIP on ブランチ名: ハッシュ7桁 コミットメッセージ

最新commitと、もう一つのcommitの2つを親に持つcommitオブジェクトだとわかる

もう一つの親を確認
terminal
$ git cat-file -p commitオブジェクト1のハッシュ(40桁 or 7桁) 

tree treeオブジェクトのハッシュ(40桁)
parent 親commitオブジェクト1のハッシュ(40桁)
author ユーザー名 <メールアドレス> UNIX時間 +0900
committer ユーザー名 <メールアドレス> UNIX時間 +0900
 
index on ブランチ名: ハッシュ7桁 コミットメッセージ

このcommitオブジェクトも最新commitオブジェクトを親に持つことがわかる

commit 含む内容
親が1つのcommit ステージング時点のファイルを含む
親が2つのcommit ステージング時点のファイル、、ワーキングツリーの変更両方を含む
stashが複数ある場合
  • .git/refs/stashには最新のstashのハッシュ値しか保存されない
  • stashの一覧は.git/logs/refs/stashに保存されている
  • .git/logs/refs/stashにすべてのstashの情報が書き足されていく
.git/logs/refs/stash
0000000000000000000000000000000000000000 commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	WIP on ブランチ名: stashした時のHEADのcommitオブジェクトのハッシュ(7桁)  stashした時のHEADのcommitのコミットメッセージ
commitのハッシュ(40桁) commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	WIP on ブランチ名: stashした時のHEADのcommitオブジェクトのハッシュ(7桁)  stashした時のHEADのcommitのコミットメッセージ
ORIG_HEAD
commitのハッシュ(40桁)
terminal
//reflogの最新に履歴が追加される
$ git reflog 

xxxxxxx (HEAD -> ブランチ名) HEAD@{n}: reset: moving to HEAD

退避したものを確認

terminal
//避難した作業の一覧を表示
$ git stash list 
 
//git stash listとgit diffの合わせ技
$ git stash list -p
 
//stash履歴詳細
$ git stash show

//N番目にスタッシュしたファイルの一覧を表示
$ git stash show stash@{n}
 
//N番目にスタッシュしたファイルの変更差分を表示
$ git stash show -p stash@{n}
stash 消す 消さない
復元する pop apply
復元しない drop -

退避したものを復元

stashしていたファイルの復元します。

  • ステージングもワーキングツリーも戻る
  • 別ブランチで復元するとマージされるが、コンフリクトが起こる場合もある
terminal
//最新の作業を復元
$ git stash apply 
$ git stash apply --index
 
//特定の作業を復元する
$ git stash apply スタッシュ名 
$ git stash apply stash@{n} 
 
//最新からn番目の作業を戻す
$ git stash apply n
 
//退避した中の最新の作業を復元する(戻した内容は退避リストから削除)
$ git stash pop

退避したものを削除

terminal
//避難した中の最新の作業を削除する
$ git stash drop 
 
//特定の避難した作業を削除
$ git stash drop スタッシュ名 
$ git stash drop stash@{n}
 
//最新から2番目のものを削除する
$ git stash drop 2

//避難した作業すべてを削除
$ git stash clear 
.git内で起こること
  • 最新のstashを削除すると、.git/refs/stashのハッシュが、その次に新しいものに書き換わる
  • .git/logs/refs/stashから、削除したstashが消える
  • stashをすべて消すと.git/refs/stashと.git/logs/refs/stashが削除される
  • 削除したstashのcommitオブジェクトはそのまま残る

branchを切る前にファイルを編集してしまった場合の対応

git stashで変更を退避、ブランチを作成して切り替えて、git stash popで退避させておいた変更を復旧します。

terminal
//前回のコミット以降の操作を退避
$ git stash

//ブランチを作成して切り替え
$ git switch -c 新規作成するブランチ名

//退避した操作を再反映する
$ git stash pop

2. Git操作と.git/内の変化

2-0.png

名前 特徴 対象
ワーキングツリー 実際にファイルの変更作業をする場所 .gitの外の実ファイル(.gitignoreなどの設定ファイルは除く)
ステージングエリア .gitが追跡中のファイルが登録される場所(ファイルは最後にステージングされた時点の状態) .git/index(addされたファイルはblobファイルとして.git/objects/内)
ローカルリポジトリ 変更の履歴を記録している場所 .git/内
用語 Gitコマンド 意味 内部的な動作
ステージング git add ワーキングツリーでのファイルの変更情報をステージングエリアに送ること blobオブジェクトの生成、.git/indexの更新など
コミット git commit ステージングエリアに集められたファイルの情報をローカルリポジトリに記録すること commitオブジェクト、treeオブジェクトの生成、HEADの移動など

2-1. ステージング

ファイルの作成

Git操作
terminal
//ワーキングツリーにファイルを作成
$ touch test.txt
.git内で起こること

この時点では.git/内になにも動きなし

ファイルの編集

Git操作

この時点では.git/内になにも動きなし

ステージング

Git操作

ステージングする前に現在インデックスされているファイルを確認します。

terminal
//現在インデックスされているファイル
$ git ls-files --stage

//.git/indexが持っている情報全部を表示
$ git ls-files --stage --debug	

先程作成したtext.txtをステージングします。

terminal
$ git add test.txt
.git内で起こること

blobオブジェクトのファイルが作成されます。
(バイナリファイルなのでエディタでそのまま開くと文字化けしています。)

.git/objects/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xK��OR0`

cat-fileコマンドで中身を確認します。

terminal
 $ git cat-file -p ハッシュ
 
 (ファイルの中身)
 ※ファイルの中身が空だと何も表示されない

indexファイルの中身が更新されます。

terminal
$ git ls-files --stage

その他のステージング

teminal
//部分ごとにステージング
$ git add -p ファイル名

//変更があったすべてのワーキングツリーの変更をステージングに加える
$ git add .	

//変更があった追跡中のファイルのワーキングツリーの変更をステージングに加える
$ git add -u	

インデックスされているファイルの確認

terminal
$ git ls-files --stage

//.git/indexが持っている情報全部を表示
$ git ls-files --stage --debug

ファイルのステージングを取り消す

ワーキングツリーには影響しません。

Git操作
terminal
//add前に戻す
$ git restore --staged ファイル名
$ git restore -S ファイル名
.git内で起こること
  • .git/indexが更新される

追跡を外す

ワーキングツリーのファイルはそのままです。

Git操作
terminal
//インデックスから外す
$ git rm --cached ファイル名

//コミットしてローカルリポジトリから削除
$ git commit -m "コミットメッセージ"
.git内で起こること
  • .git/indexが更新される
terminal
 $ git ls-files --stage
 $ git ls-files -s
 
 ファイルの種類とパーミッション blobオブジェクトのハッシュ(40桁) コンフリクトフラグ	パス/ファイル名
 ファイルの種類とパーミッション blobオブジェクトのハッシュ(40桁) コンフリクトフラグ	パス/ファイル名
 ファイルの種類とパーミッション blobオブジェクトのハッシュ(40桁) コンフリクトフラグ	パス/ファイル名
 ファイルの種類とパーミッション blobオブジェクトのハッシュ(40桁) コンフリクトフラグ	パス/ファイル名 

2-2. commit

commit

2-2-1.png

Git操作
terminal
$ git commit -m "コミットメッセージ"
.git内で起こること
  • commitオブジェクトが作成される
  • treeオブジェクトが作成される
  • ブランチ名の指すcommitが変わるので.git/refs/heads/ブランチ名のハッシュが更新される
  • .git/logs/HEADにログが追加される
  • .git/COMMIT_EDITMSGに先程のコミット時のコミットメッセージが書き込まれる(最後のもののみ)
.git/COMMIT_EDITMSG
コミットメッセージ
  • branchが新しく作成されたcommitオブジェクトを参照するようになる
.git/refs/heads/ブランチ名
新しく作成されたcommitオブジェクトのハッシュ
  • .git/logs/HEADに履歴が追記される
.git/logs/HEAD
 commitのハッシュ(40桁) commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	commit (initial): コミットメッセージ

  • .git/logs/refs/heads/ブランチ名に履歴が追記される
.git/logs/refs/heads/ブランチ名
commitのハッシュ(40桁) commitのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	commit (initial): コミットメッセージ
この時点でのgit logを確認する
terminal
$ git log
commit ハッシュ(40桁) (HEAD -> ブランチ名)
Author: ユーザー名 <メールアドレス>
Date:   曜日 月 日 時:分:秒 年 +0900

    コミットメッセージ
この時点でのステージングを確認する
terminal
$ git ls-files --stage
100644 blobオブジェクトのハッシュ(40桁) 0       test.txt
オブジェクトのタイプ確認
terminal
$ git cat-file -t

blobオブジェクトのハッシュ(40桁)

tree、blobともに、別のcommitでも、変更が無いファイルについては、同じハッシュ値なのでオブジェクトのファイルは増えません。逆にいえば、変更をステージングするとどんどん増えていきます。

  • .git/refs/heads/ブランチ名に格納されるハッシュが新しいcommitオブジェクトのものに書き換わる

commitの上書き

内部的には上書きではなく、同じ親を持つ別のcommitの新規作成です。

2-2-2.png

Git操作
terminal
//前のcommitに追加したい変更をステージングする
$ git add ファイル名

//commitの上書き(コミットメッセージ指定なし)
$ git commit --amend
 
//commitの上書き(コミットメッセージ指定あり)
$ git commit --amend -m "コミットメッセージ"

コミットメッセージを指定していない場合は、COMMIT_EDITMSGが開くので編集して保存して閉じます。

commit履歴の確認

terminal
$ git log

commitのハッシュが変わっているので、別のcommitオブジェクトが作成されたことがわかります。
上書き前のcommitも残っているのでハッシュから確認できます。

2-3. commitの指定方法

ブランチ名で指定する

そのブランチが指しているcommitを指定できる

commitのハッシュで指定する

桁数 説明
7桁 commitオブジェクトのハッシュの最初の7桁で指定する
40桁 commitオブジェクトのハッシュの最初の40桁で指定する

HEADからの相対関係で指定する

親が複数ある場合は最初の親が選択されます。

どのcommitを指すか ~ ^ ~n ~ ^ ~n
現在のHEADが指すcommit HEAD HEAD HEAD @ @ @
現在のHEADから1つ前のcommit HEAD~ HEAD^ HEAD~1(1は省略可能) @~ @^ @~1(1は省略可能)
現在のHEADから2つ前のcommit HEAD~~ HEAD^^ HEAD~2 @~~ @^^ @~2
現在のHEADから3つ前のcommit HEAD~~~ HEAD^^^ HEAD~3 @~~~ @^^ @~3

2-3-1.png

複数の親がある場合に、親を選ぶ

どのcommitを指すか ^n
現在のHEADの2番目の親 HEAD^2
現在のHEADの3番目の親 HEAD^3

2-3-2.png

terminal
//親が3つになる例
//untrackedファイルも含めたgit stash
$ git stash -u
$ git stash -a

//親が3つ以上になる例
//octopusマージ 現在のブランチに2つ以上のブランチをマージ
$ git merge --strategy=octopus ブランチ名1 ブランチ名2 ブランチ名3 ...

シンボリック参照での指定

commitの指定 記載場所
ORIG_HEAD .git/ORIG_HEADに記録されているcommit
FETCH_HEAD .git/FETCH_HEADに記録されているcommit
CHERRY_PICK_HEAD .git/CHERRY_PICK_HEADに記録されているcommit
REBASE_HEAD .git/REBASE_HEADに記録されているcommit
MERGE_HEAD .git/MERGE_HEADに記録されているcommit

2-4. リモートリポジトリ

複数人での開発を行うために使う、ネット上のリポジトリです。他の人の成果を取り込んだり、自分の成果を共有したりしながら開発を進めることができます。

branchの種類

ブランチの種類 説明
上流ブランチ ローカルリポジトリにある、リモートリポジトリのブランチを追跡するbranch
git fetchやgit pullが成功すると更新される
実体は.git/refs/remotes/
リモート追跡ブランチ branchの指定なしで git mergeしたときにマージ対象になるbranch

2-1-3.png

リモートリポジトリの登録、削除

ローカルリポジトリに、リモートリポジトリの登録、削除を行います。

Git操作
terminal
//追加前の確認
//リモート名とリポジトリの一覧を表示
$ git remote -v 	

//リモートリポジトリをリモート名をつけて登録
$ git remote add リモート名 リモートリポジトリのurl

//追加後の確認
$ git remote -v 	
 
//リモートの登録を削除
$ git remote rm リモート名
 
//削除後の確認
$ git remote -v
.git内で起こること

git remote addでリモートを追加すると下記が追記され、git remote rmで削除すると削除されます。

.git/config
[remote "リモート名"]
	url = git@github.com:ユーザー名/リポジトリ名.git
	fetch = +refs/heads/*:refs/remotes/リモート名/*

その他

terminal
//リモート名の変更
$ git remote rename 変更前のリモート名 変更後のリモート名	

//リモートの詳細を確認
$ git remote show リモート名

commitをリモートリポジトリに送る

Git操作
terminal
//現在のbranchの変更をリモートブランチに反映させる(リモートと紐づけできている場合は「リモート名 ブランチ名」省略可能)
$ git push  リモート名 ブランチ名

//現在のbranchの変更をリモートブランチに反映させる(リモート追跡ブランチとの紐付けもする)
$ git push -u リモート名 ブランチ名
$ git push --set-upstream リモート名 ブランチ名

//現在のbranchの変更をリモートブランチに反映させる(強制的)
$ git push --force-with-lease --force-if-include	
.git内で起こること
  • -uオプションを付けてローカルで作成したbranchをpushした場合
.git/config
[branch "ブランチ名"]
	remote = リモート名
	merge = refs/heads/上流ブランチ名
  • .git/FETCH_HEADでpushされたbranchが一番上に表示される(ラグあり)
.git/FETCH_HEAD
コミットのハッシュ(40桁)		branch 'ブランチ名' of github.com:ユーザー名/リモートリポジトリ名
  • .git/logs/refs/remotes/origin/ブランチ名が、そのブランチの初めてのpushの場合に作成される(更新の場合は下に追記されていく)
.git/logs/refs/remotes/origin/ブランチ名
0000000000000000000000000000000000000000 コミットのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	update by push

.git/refs/remotes/origin/ブランチ名がそのbranchの初めてのpushの場合に作成される(更新のpushの場合はハッシュの更新)

.git/refs/remotes/origin/ブランチ名
新しいコミットのハッシュ
その他
terminal
//リモートブランチの削除
$ git push --delete リモート名 リモートのブランチ名
$ git push -d リモート名 リモートのブランチ名

リモートリポジトリの最新情報をローカルリポジトリ(リモート追跡ブランチ)に持ってくる

リモート追跡ブランチの場所は.git/refs/remotes/origin/ブランチ名です。

Git操作
terminal
//リモート追跡ブランチも含めてローカルにあるブランチ表示
//(リモート追跡ブランチは前回fetch時の状態なのでリアルタイムに更新されていない)
$ git branch -a

リモートブランチはremotes/origin/ブランチ名の形で表示される

//リモートリポジトリのすべてのブランチの更新履歴をリモート追跡ブランチに取り込む
//(リモートで削除されているブランチがあっても、ローカルにあるリモート追跡ブランチは残ったまま)
$ git fetch

//リモートブランチの更新情報をリモート追跡ブランチに取り込む
$ git fetch リモート名 リモートブランチ名

//リモートで削除されたブランチのリモート追跡ブランチを削除
$ git fetch --prune
$ git fetch -p
.git内で起こること
  • git fetchを実行すると.git/FETCH_HEADの内容に、取得したリモートリポジトリの状態が反映される
  • git fetch --pruneを実行すると、リモートで削除されていたブランチに対応する下記ファイルが削除される
    「.git/refs/remotes/ブランチ名」
    「.git/logs/refs/remotes/origin/ブランチ名」

fetchしてとってきたブランチに移動する

terminal
//リモート名不要で、ブランチ名のみ
$ git switch ブランチ名
$ git switch -c ブランチ名
  • .git/refs/heads/ブランチ名が作成される
.git/refs/heads/ブランチ名
ブランチのHEADが指しているコミットのハッシュ 
  • .git/logs/refs/heads/ブランチ名が更新作成される(無い場合は作成される)
.git/logs/refs/heads/ブランチ名
0000000000000000000000000000000000000000 コミットのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	clone: from github.com:ユーザー名/リポジトリ名.git
コミットのハッシュ(40桁) コミットのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	commit: コミットメッセージ
コミットのハッシュ(40桁) コミットのハッシュ(40桁) ユーザー名 <メールアドレス> UNIX時間 +0900	commit: コミットメッセージ
Git操作

ブランチの上流ブランチを確認します。

terminal
$ git branch -vv
.git内で起こること

参照するだけなので何も起きません。

リモートブランチの変更を取り込む

リモート追跡ブランチが上流ブランチとして登録されている場合、引数なしのmergeで変更を取り込めます。

terminal
$ git merge

 

2-5. 比較

コミットログの確認

現在のcommitオブジェクトから親(parent)を辿ることによって到達可能なコミットを表示します。
HEADから順に表示されます。
(HEADを過去のcommitに移動している場合、それより新しいcommitは表示されません)

terminal
//現在のブランチのコミットログを出力
$ git log

//全ブランチのコミットログを出力
$ git log --all

//最後のN個のコミットのコミットログを出力
$ git log -n

//特定のファイルのコミットログを出力
$ git log --follow ファイル名
git logの表示オプション
terminal
//コミット番号のハッシュ値を省略表記(7桁)で出力
$ git log --abbrev-commit

//グラフで表示
$ git log --graph

//コミットログ(簡易版)を出力(コミット履歴を1行1履歴)
$ git log --oneline
情報の追加表示
terminal
//ファイルの修正内容を追加表示
$ git log -p
 
//変更されたファイルのパスと差分量を追加表示
$ git log --stat
 
//ファイル毎の削除、追加行数を追加表示
$ git log --numstat
 
//変更したファイルを追加表示
$ git log --name-status
絞り込み
terminal
//commitを日付で絞り込み(以降)
$ git log --after="YYYY/MM/DD"	
$ git log --since="YYYY/MM/DD"

//commitを日付で絞り込み(以前)
$ git log --before="YYYY/MM/DD"
$ git log --until="YYYY/MM/DD"

//commitを特定のファイルのみで絞り込み
$ git log -- パス/ファイル名		

//commitをコミッターで絞り込み
$ git log --author="ユーザー名"	

//commitをコミットメッセージに含まれる文字列で絞り込み
$ git log --grep='コミットメッセージに含まれる文字"	

//commitをマージコミットのみに絞り込み
$ git log --merges

//commitをマージコミット以外に絞り込み
$ git log --no-merges	

//複数の親がある場合最初の親コミットのみを追う
$ git log --first-parent	

//コミット1の子孫、かつコミット2の祖先のみに絞り込み
$ git log --ancestry-path コミット1...コミット2

ファイルの状態の比較

ワーキングツリーとindexの差分
terminal
//ワーキングツリーとindexの差分
$ git diff 
 
//ワーキングツリーとindexの差分(特定のファイルについて)
$ git diff ファイル名
indexとcommitの差分
terminal
//indexとHEADの比較 (git diff --cached HEADと同義)
$ git diff --cached
 
//上記と同じ(git diff --staged HEADと同義)
$ git diff --staged
 
//indexとHEADの比較(特定のファイルについて)
$ git diff --cached ファイル名
$ git diff --staged ファイル名

//indexと特定のコミットの差分
$ git diff commitのハッシュ --staged	

//indexと特定のコミットの差分(特定のファイルについて)
$ git diff commitのハッシュ --staged ファイル名	
commitとcommitの差分
terminal
//「最新のコミット」と「最新のコミットのひとつ前」との差分
$ git diff HEAD~

//「最新のコミット」と「最新のコミットのひとつ前」との差分(特定のファイルについて)
$ git diff HEAD~ ファイル名
 
//現在のHEADと特定のcommitの差分
$ git diff 比較するcommitのハッシュ
$ git diff 比較するブランチ名

//現在のHEADと特定のcommitの差分(特定のファイルについて)
$ git diff 比較するcommitのハッシュ ファイル名 
$ git diff 比較するブランチ ファイル名  
 
//特定の2つのcommitの差分
$ git diff commitのハッシュ1  commitのハッシュ2
$ git diff commitのハッシュ1..commitのハッシュ2
$ git diff ブランチ名1 ブランチ名2
$ git diff リモート追跡ブランチ名 ローカルブランチ名

//特定の2つのcommitの差分(特定のファイルについて)
$ git diff commit1のハッシュ commit2のハッシュ ファイル名
$ git diff ブランチ名1 ブランチ名2 ファイル名
$ git diff リモート追跡ブランチ名 ローカルブランチ名 ファイル名
ワーキングツリーとcommitの差分
terminal
//ワーキングツリーと特定のコミットの差分
$ git diff commitのハッシュ

//ワーキングツリーと特定のコミットの差分(特定のファイルについて)
$ git diff commitのハッシュ ファイル名
diffの表示オプション
terminal
//ファイル名のみ表示
$ git diff --name-only	
diffの絞り込みオプション
terminal
//変更した差分だけに絞り込み
$ git diff --diff-filter=M

//削除した差分だけに絞り込み
$ git diff --diff-filter=D	

//リネームした差分だけに絞り込み
$ git diff --diff-filter=R	

コミットの中身を確認する

terminal
//特定のコミットの中身を確認
$ git show コミットのハッシュ

//HEADのコミットの中身を確認(HEADは省略可能)
$ git show HEAD

//コミットの中身を確認(ファイルを指定)
$ git show コミットのハッシュ:ファイル名

ローカルリポジトリの状態を確認する

terminal 
//staged/modified/untracked の一覧、差分情報などのステータスを表示
$ git status

//簡易形式で表示
$ git status -s
$ git status --short

 

ファイルのステータス
staged ステージングされているファイル(indexとHEADに差分がある)
modified 変更されているファイル(ワーキングツリーとindexに差分がある)
untracked Gitで追跡されていない、gitignoreによって無視されないファイル

特定のファイルの行の変更履歴の確認]

terminal
//行の変更履歴(ファイル内で誰が何をいつ変更したか)
$ git blame ファイル名	

行の変更履歴(ファイル内で誰が何をいつ変更したか) 省略形
$ git blame -s ファイル名

//ファイルの指定した行の変更履歴を出力する
$ git blame -L 行番号1,行番号2,... ファイル名	

各ブランチのcommitを表示

terminal
//各ブランチのcommitを表示
$ git show-branch

//リモート追跡ブランチも表示
$ git show-branch --all

//もっと多く表示
$ git show-branch --more=n

2つのコミットの分岐元のコミットの確認

terminal
//2つのコミットが分岐したcommitのハッシュの確認(コミットのハッシュで指定)
$ git merge-base commit1のハッシュ commit2のハッシュ	

//2つのコミットが分岐したcommitのハッシュの確認(ブランチ名で指定)
$ git merge-base ブランチ1 ブランチ2

2-6.ファイル操作

ファイルのリネーム

追跡済みのファイルのリネームが対象です。
追跡されていないファイルにはgit mvは使用できません。

Git操作

ステージングする前に現在インデックスされているファイルを確認しておきます。

terminal
//リネーム前に現在インデックスされているファイルの確認
$ git ls-files --stage

//ファイル名の変更をステージングする
$ git mv 現在のファイル名 変更後のファイル名

//現在インデックスされているファイルの確認
$ git ls-files --stage

ステージングでのファイル名が変わっていることがわかりますが、ハッシュは変わっていません。
blobオブジェクトは、ファイル名の情報を保持していなく、インデックスがファイルの構造と名前を保持しています。

.git内で起こること
  • blobオブジェクトは追加なし
  • .git/indexのファイル名が更新される

ファイルの移動

terminal
//移動前にインデックスされているファイルの確認
$ git ls-files --stage

//gitコマンドでのファイルの移動(1回でステージングされる)
$ git mv 現在のパス/ファイル名 移動後のパス/ファイル名

//gitコマンドを使わないファイルの移動(ステージングまで2手かかる)
$ mv  現在のパス/ファイル名 移動後のパス/ファイル名
$ git add 移動後のパス/ファイル名

//現在インデックスされているファイルの確認
$ git ls-files --stage
.git内で起こること
  • blobオブジェクトは追加なし
  • .git/indexのパス/ファイル名が更新されます。 (git mvでも、mv+git addでもblobオブジェクトのハッシュは変わりません。)
    (mv+addでもrenameになるのでcommitすると履歴が追えています。)

ファイルの削除

Git管理下にないファイルをワーキングツリーから手動削除なら、最初からなかったのと同じ扱いです。
Git管理下にあるファイルをgit rmコマンドで消すと、インデックスからも消えるので「削除」をaddする必要はありません。
Git管理下にあるファイルを(git rmではなく)rmで削除する場合は、削除済みのファイルをaddする必要があります。

terminal
//gitコマンドでのファイル削除(追跡済みファイルのみ有効)(1回でステージングされる)
$ git rm ファイル名

//gitコマンドを使わないファイル削除(追跡済みファイルの場合は別途addが必要)(ステージングまで2手かかる)
$ rm ファイル名
$ git add ファイル名

//gitコマンドでのファイル削除(ファイル削除をステージングする)(ワーキングツリーのファイルは残る)  
$ git rm --cached ファイル名

追跡されていないファイルの削除

terminal
//Gitで追跡されていないファイルすべてを確認
$git clean -n	

//Gitで追跡されていないファイルすべてを削除
$ git clean	
 
//Gitで追跡されていないファイルすべてを削除(強制)
$ git clean -f	

//Gitで追跡されていない特定のファイルを削除(強制)
$ git clean -f ファイル名

//Gitで追跡されていないファイルを対話モードで削除
$ git clean -i	

.git内で起こること

  • blobオブジェクトは消えない
  • .git/indexから削除したファイルが消える

ステージングしたファイルをワーキングツリーで削除しても、addせずにコミットするとインデックスでは存在するのでコミットに含まれます。

ファイルの削除(git追跡されていないもののみ)

terminal
//Gitで追跡されていないファイルすべてを確認
$ git clean -n

//Gitで追跡されていないすべてのファイルを削除
$ git clean -f

//Gitで追跡されていない特定のファイルを削除する
$ git clean -f ファイル名

ディレクトリ名のリネーム

git ls-filesコマンド
staging-area(ステージングエリア)でインデックスされているファイルを確認したい場合に利用するコマンド
-s(--stage)オプションでリポジトリ格納されたblobのhash値とファイル名の一覧を表示

terminal
//リネーム前に現在インデックスされているファイルの確認
$ git ls-files --stage

//ディレクトリ名の変更をステージングする
$ git mv 現在のディレクトリ名 変更後のディレクトリ名

//現在インデックスされているファイルの確認
$ git ls-files --stage

ステージングでのファイル名が変わっていることがわかりますが、ハッシュは変わっていません。
blobオブジェクトは、ファイル名の情報を保持しておらず、インデックスがファイルの構造と名前を保持しています。

.git内で起こること

.git/indexで変更したディレクトリ名/ファイル名が変更されています。

ディレクトリの移動

terminal
//リネーム前に現在インデックスされているファイルの確認
$ git ls-files --stage

//移動
$ git mv 現在のパス/ディレクトリ名 変更後のパス/ディレクトリ名

//現在インデックスされているファイルの確認
$ git ls-files --stage

ステージングでのファイル名が変わっていることがわかるが、ハッシュは変わっていない
blobオブジェクトは、ファイル名の情報を保持していない
インデックスがファイルの構造と名前を保持している

.git内で起こること

.git/indexで変更したディレクトリ名/ファイル名が変更されています。

ディレクトリの削除(中身ごと)

ディレクトリも(ディレクトリはtreeオブジェクトという点は異なる)

terminal
//ワーキングツリーとステージングの両方を削除
$ git rm -r パス/ディレクトリ名

//ステージングのみ削除(ワーキングツリーのファイルは残る) 
$ git rm -r --cached パス/ディレクトリ名

追跡されていないディレクトリの削除

terminal
//Gitで追跡されていないディレクトリとファイルすべてを確認
$ git clean -dn
 
//Gitで追跡されていないディレクトリとファイルすべてを削除(強制)
$ git clean -df

//Gitで追跡されていない特定のディレクトリを削除
$ git clean -f ディレクトリ名	

2-7. ファイルの復元

2-7-1.png

ファイルがインデックスに登録されたときの状態をワーキングツリーに復元

コミットを指定しない場合は、インデックスからの復元になります。

Git操作
terminal
//ステージングした時点の状態に復元 コミット識別子の引数が無い場合は、復元元は「インデックス」が対象
$ git restore ファイル名

//ステージングした時点の状態に、部分ごとに区切って復元箇所を指定
$ git restore -p ファイル名
.git内で起こること
  • .gitでは何も起きない

特定のコミットのファイルを復元

ファイルがcommitされた時の状態をワーキングツリーに復元します。
削除したファイルを復活させるのにも使えます。

Git操作
terminal
//HEADから復元
$ git restore -s HEAD ファイル名

//HEADからいくつ前のコミットか指定して復元
$ git restore -s @~n ファイル名
$ git restore -s HEAD~n ファイル名
 
//commitのハッシュを指定して復元
$ git restore -s コミットのハッシュ ファイル名

//commitのハッシュを指定して部分ごとに区切って復元箇所を指定
$ git restore -p -s commitのハッシュ ファイル名

//コミット時点の状態をステージングに反映
$ git restore --staged ファイル名	
   
//コミット時点の状態をワーキングツリーに反映
$ git restore --source=HEAD ファイル名
 
//コミット時点の状態をステージングとワーキングツリー両方に反映
$ git restore --source=HEAD --staged --worktree ファイル名

restoreの-s/--sourceオプションdでは、戻したいコミットを指定できます。

.git内で起こること
  • .gitでは何も起きない

2-8. HEADの移動

git resetで任意のcommitに移動することができます。
リセットといっても、何かをある時点に戻すのがメインではなく、commitを参照するHEADを動かしているだけです。オプションで、ステージングとワーキングツリーにどの時点の状態を反映させるか決められます

2-10.png

現在のHEADの確認

Git操作
terminal
$ git log -1
.git内で起こること
  • .gitでは何も起きない
.git内のファイルからHEADのcommitのハッシュを確認する

現在のブランチ

.git/HEAD
ref: refs/heads/ブランチ名


そのブランチ名のファイルからハッシュを確認します。

.git/refs/heads/ブランチ名
現在のbranchが指すcommitのハッシュ(40桁)

HEADの移動

Git操作
terminal
//ステージングをHEADのコミット状態に戻す。ワーキングツリーとHEADはそのまま(add取り消し)
//git reset --mixed HEADと同じ
$ git reset	

//特定のファイルをステージングからワーキングツリーに戻す(commit、indexを元に戻す)
//git reset --mixed HEAD ファイル名と同じ
$ git reset ファイル名

//コミットを指定してファイルを復元する(commit、indexをもとに戻す) 
$ git reset commitのハッシュ ファイル名	
resetのオプション ワーキングツリー インデックス コミット
--sof - - 指定のcommitに移動
--mixed オプション省略時 - 戻したcommitの状態を反映
--hard 戻したコミットの状態を反映 戻したcommitの状態を反映 指定のcommitに移動

どのHEADに移動するのか特定するためにHEADの動きのログを表示

terminal
//移動したいHEADを調べる
//HEADの移動履歴を出力
$ git reflog

//操作履歴数を指定して出力
$ git reflog -n	

//実際にHEADを移動する
$ git reset --hard HEAD@{n}
.git内で起こること
  • indexは戻したコミットまでの時点に戻っている
  • commit、ステージ、ワーキングツリーに戻した先のHEADの状態が反映される
.git/refs/heads/ブランチ名
新しくHEADが指すcommitブランチのハッシュに書き換わる

commitオブジェクトは親commitの参照しか持っていないので、git logでは子commitが表示できません。
git reflogでは過去のHEADの移動の履歴が見られるのでHEADよりも先のcommitへも移動できます。
reflogは、refの動きの履歴なのでブランチ横断での履歴になっています。
別ブランチのcommit履歴に戻すこともできます。
ワーキングツリーとステージングにしかないファイルはなくなります。(完全にcommit時点の状態になるため。)

2-9. commit履歴の改変

commit履歴の改変では、同じ部分を変更していた場合コンフリクト(衝突)することがあります。
コンフリクトが起こった場合は、どの変更を残すかの編集が必要になります。

マージの状況を確認する

terminal
//現在のブランチにマージされているブランチを出力する
$ git branch --merged	

//現在のブランチにマージされていないブランチを出力する
$ git branch --no-merged	

branchの統合

branchの統合のことをbranchのマージと呼びます。

Git操作
terminal
//現在のbranchに特定のブランチをマージする
$ git merge 特定のブランチ名

//現在のbranchに特定のbranchをマージする(コミットメッセージも指定)
$ git merge -m "コミットメッセージ" ブランチ名

//現在のbranchに特定のbranchをマージする(ファストフォワードマージでもマージコミットを作成)
$ git merge 特定のブランチ名 --no-ff	

//特定のリモート追跡ブランチを特定のローカルブランチにマージする
$ git merge オリジン名/ブランチ名 ブランチ名	
コンフリクトしたら
terminal

//継続(競合を修正した後addとcommitを自分で行う)
$ git add 
$ git commit

//継続(競合を修正した後mergeの続きを行う)
$ git merge --continue


//中止(コンフリクトの編集をしていないとき)
$ git merge --abort

//中止(コンフリクトの編集をしているとき)
$ git reset --hard HEAD
.git内で起こること

コンフリクトを解消して、完全に片方だけ残す場合は、どちらかのblobオブジェクトが残ります。どちらとも完全に同じでなければ新しいファイルになるのでハッシュは変わります。

  • コミットが作られて1つ進む
  • コミットメッセージは自動で入る
terminal
$ git log

Merge branch '取り込まれるブランチ名' into 取り込むブランチ名
  • COMMIT_EDITMSGはそのまま
(merge元のbranchの最後のコミットメッセージのまま変化なし)
  • commitがつくられて、HEADは1つすすむ
.git/HEAD
コミットのハッシュ(40桁)
terminal
//コミットログ確認
$ git log

1.マージしてできたcommit
2.取り込まれたブランチのHEADだったcommit
3.取り込むブランチのHEADだったcommit

ORIG_HEADは直前までHEADだった「取り込むbranchのHEADだったcommit」です。

.git/ORIG_HEAD
取り込むブランチのHEADだったcommit
.git/INDEDX
マージされた時点の状態

マージの種類

マージにはファストフォワードとノンファストフォワードがあります。

merge(fast-forward)

分岐してから片方は進んでいないので、ファストフォワードだとコンフリクトしません。
実質統合はされていないので、親は1つだけです。

commitが進むだけ(マージコミットはつくられない=「ブランチをマージした」という履歴が残りません。)
結果的には元のブランチに直接コミットしたのと変わりません。(後から取り消しにくくなります。)

2-11-1.png

merge(non-fast-forward)

マージしたことをコミット情報として持ちます。
分岐してから両方進んでいるので、コンフリクトする可能性があります。

2-11-2.png

マージの取り消し

terminal
//マージが完了した後から取り消す
$ git reset --hard ORIG_HEAD

コミット履歴の付け替え

分岐したbranchを、もう片方にくっつけます。(commitログではrebase前の時刻だが、関係なく後につきます。)
くっつけたcommitは、前の影響を受けるので前とハッシュが変わります。
現在のブランチ名(付け替えられるブランチ名)がキープされます。

2-11-3.png

terminal
//2つのbranchが分岐したcommitのハッシュの確認
$ git merge-base ブランチ名1 ブランチ名2	
terminal
//リベースを利用して現在のbranchを特定のbranchに付け替える
$ git rebase 付け替え先のブランチ名	

//リベースを利用して現在のブランチを特定のbranchに付け替える(インタラクティブモード)
$ git rebase --interactive ブランチ名
$ git rebase i ブランチ名

 

コミット履歴を改変する

2-11-4.png

terminal
//コミット履歴を改変する
$ git rebase -i 改変したい一番古いcommitより1つ古いcommitのハッシュ

各コミットに対する操作を行います。

選択肢 rebaseの各コミットへの操作
pick そのまま
reword コミットメッセージの編集
edit コミット内容の編集
squash 直前のコミットとまとめる(コミットメッセージ編集)
fixup 直前のコミットとまとめる(コミットメッセージ破棄)
drop コミットの削除
リベースでコンフリクトが起きた時
terminal
//リベース時のコンフリクト解消を中止
$ git rebase --abort
 
//リベース時のコンフリクト解消終了時に使用する
$ git rebase --continue
rebaseの取り消し
terminal
//戻りたい時点を確認する 
$ git reflog

//戻す
$ git reset --hard HEAD@{n}

cherry-pick

特定のcommitを取り込みます。
差分ではなく、スナップショットなのでそのコミットで行った変更ではなく、そのcommit時点の状態を取り込みます。
指定したcommitは、cherry-pick時点のHEADの後に追加され、HEADは進みます。
追加されたcommitのハッシュは変わります。

2-11-5.png

terminal
//特定のcommit時点の状態を取り込む
$ git cherry-pick 取り込むcommitのハッシュ
$ git cherry-pick ブランチ名

//複数のcommitをまとめてcherry-pickする(連続している場合)
$ git cherry-pick 取り込むcommitのハッシュ..取り込むcommitのハッシュ

//複数のcommitをまとめてcherry-pickする(連続していない場合)
$ git cherry-pick 取り込むcommitのハッシュ1 取り込むcommitのハッシュ2 取り込むcommitのハッシュ3 
コンフリクトしたら
terminal
//チェリーピック時のコンフリクト解消作業を終了する(次にすすむ)
$ git cherry-pick --continue

//チェリーピック時のコンフリクト解消作業を中止する
$ git cherry-pick --abort
cherry-pickの取り消し
terminal
//cherry-pickの取り消し
$ git reset --hard HEAD~

commitを打ち消す

特定のcommitを打ち消すための新しいcommitを作って、commitを打ち消します。

2-11-5.png

親が1つのcommitの場合
terminal
//HEADのcommitを打ち消す(コミットメッセージを編集する)
$ git revert HEAD

//特定のcommitを打ち消す(コミットメッセージを編集する)
$ git revert 打ち消したいcommitのハッシュ 

//HEADのcommitを打ち消す(コミットメッセージを編集しない)
$ git revert HEAD --no-edit

//特定のcommitを打ち消す(コミットメッセージを編集しない)
$ git revert 打ち消したいcommitのハッシュ --no-edit

revertを実行すると、コミットメッセージを編集する画面になるので入力します。

親が複数のcommitの場合

親が複数あるcommitオブジェクトの例:マージコミット

terminal
 //親が複数ある場合は、親を指定するための-mオプションが必要
 error: commit 打ち消そうとしたcommitオジェクトのハッシュ is a merge but no -m option was given.

対象のマージコミットを確認して、最初に書かれる方を1、2番目を2とします。

terminal
$ git revert -m 1 打ち消したいcommitのハッシュ
親の確認方法

git showでの確認の場合

terminal
$ git show

commit commitオブジェクトのハッシュ
Merge: 親1のcommitのハッシュ(7桁)  親2のcommitのハッシュ(7桁)
Author: ユーザー名 <メールアドレス>
Date:   曜日 月 日 時刻 年 +0900

git cat-fileでの確認の場合

terminal
$ git cat-file -p commitオブジェクトのハッシュ40桁

tree trreeオブジェクトのハッシュ
parent 親1のcommitのハッシュ
parent 親2のcommitのハッシュ
author ユーザー名 <メールアドレス> UNIX時間 +0900
committer ユーザー名 <メールアドレス> UNIX時間 +0900

コミットメッセージ

git logでの確認の場合

terminal
commit commitオブジェクトのハッシュ40桁
Merge: 親1のcommitのハッシュ(7桁)  親2のcommitのハッシュ(7桁)
Author: ユーザー名 <メールアドレス>
Date:   曜日 月 日 時刻 西暦 +0900

コミットメッセージ
commitしないで打ち消し生成までで止める

複数のcommitをrevertするときに、commitを一回にまとめるときなどに使います。

terminal
$ git revert 打ち消したいコミットのハッシュ --no-commit
$ git revert 打ち消したいコミットのハッシュ -n

複数のcommitを打ち消す

打ち消すcommitの数だけ新しくcommitが作られます。(新しい方から打ち消していきます。)

terminal
//複数のcommitをrevertする(コミットメッセージを編集する) HEADでの指定
$ git revert HEAD(打ち消す中で最新のコミット)...HEAD~n(打ち消したい一番古いコミットより一つ古い)

//複数のcommitをrevertする(コミットメッセージを編集する) commitのハッシュでの指定
$ git revert 打ち消す中で最新のコミットのハッシュ...打ち消す最後のコミットより一つ古いコミットのハッシュ


//複数のcommitをrevertする(コミットメッセージを編集しない) HEADでの指定
$ git revert HEAD(打ち消す中で最新のコミット)...HEAD~n(打ち消したい一番古いコミットより一つ古い) --no-edit

//複数のcommitをrevertする(コミットメッセージを編集しない) commitのハッシュでの指定
$ git revert 打ち消す中で最初のコミットのハッシュ...打ち消す最後のコミットより一つ古いコミットのハッシュ --no-edit

打ち消しを打ち消す

terminal
//revertで作成したcommitをrevertする
$ git revert 特定のコミットのハッシュ

//revertで作成したcommitを削除する
$ git reset --hard HEAD~n
6
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?