Open11

Git/GitHub

ちゃまちゃま

Gitで使われる用語

この章で学ぶこと

プロジェクト/リポジトリ/ワーキングツリー

プロジェクト(project) : ソースコード,ドキュメント,設定ファイル,図表,参考文献リストに代表される管理したいファイルやディレクトリの集合

リポジトリ(repository) : Gitがファイルの状態や履歴を管理・保存するための場所。リポジトリの実態は.gitディレクトリ

ワーキングツリー(working tree) : リポジトリの管理下にあるファイルやディレクトリ

gitWord1

ローカルリポジトリ/リモートリポジトリ

ローカルリポジトリ(local repository) : 開発者の手元のPCにあるリポジトリ
リモートリポジトリ(remote repository) : サーバ上にあるリポジトリ

ベアリポジトリ(bare repository) : ワーキングツリーを持たないリポジトリ
ノンベアリポジトリ(non-bare repository) : ワーキングツリーを持つリポジトリ

一般にリモートリポジトリはワーキングツリーを持たないためベアリポジトリ,ローカルリポジトリはノンベアリポジトリで運用する。

スナップショット/コミット

スナップショット : ある時点でのプロジェクトの状態(ファイル・ディレクトリの状態)をまとめて記録したもの
コミット : スナップショットと,他のコミットとの親子関係をリポジトリに記録すること。記録したスナップショットを指してコミットと呼ぶこともある。

Gitはある時点におけるファイル・ディレクトリの状態を、過去のコミットとの差分ではなく、スナップショットで記録している。このため、任意の地点での状態を気軽に復元できる。

インデックス/ステージング

Gitにはワーキングツリー、インデックス、リポジトリの3種類の場所がある。

ワーキングツリー : 今自分が編集しているファイル・ディレクトリが保存されている場所

インデックス(index)・ステージングエリア(staging area) : 修正をリポジトリにコミットする前に、修正するファイルを登録する場所。インデックスにファイルを登録することをステージ(stage)もしくはステージング(staging)という。

リポジトリ : gitがワーキングツリー・ファイルの編集履歴を保存する場所

ブランチ/マージ

ブランチ

プロジェクト開発では,複数人が同時並行で異なる機能を開発する。このようなときは,親コミットから分岐が生じて,1つの親コミットに複数の子コミットが紐づくことがある。反対に分岐したコミットを統合して1つのコミットにまとめ,他の人が開発した機能を取り込むこともある。このようなコミットが分岐する仕組みを提供するのがブランチ,コミットを統合する仕組みを提供するのがマージである。

ブランチ(branch) : コミットに付けたラベル。プロジェクトをGitで初期化するとmainというブランチが用意される。
HEAD : 自分が今作業しているブランチ(カレントブランチ)を表すラベル

ex.) 次のようなコミットの状態を考える。左図はcommit2においてfeatureブランチを作成して,作業ブランチをfeatureに切り替えた(HEADをmainからfeatureに切り替えた)状態である。この状態で,commit3をコミットすると,真ん中の図のようになる。mainブランチは変わらずcommit2を指しているが,featureブランチはcommit3を指すよう変化している。真ん中の図の状態で,作業ブランチをmainからfeatureに切り替えると,右図に示すようにHEADがfeatureからmainに切り替わる。

作業ブランチがmainになっている状態で,commit4をコミットすると分岐が生じる。commit3もcommit4も親コミットは共にcommit2である。

このように,Gitではコミットが分岐することがあり,分岐したコミットを識別するために,ブランチと呼ばれるラベルを付けている。また自分が作業しているブランチを表すラベルとしてHEADが用意されている。

マージ

マージ(merge) : 片方のブランチの修正を,もう一方のブランチに取り込むこと。マージにはFast-ForwardマージとNon Fast-Forwaedマージの2種類がある。

Fast-Forwardマージ : 修正を取り込みたいブランチ(ブランチA)から,修正を取り込むブランチ(ブランチB)までが直線状であるときに行われるマージ。マージが行われるとブランチAからブランチBまでの修正内容が全て適用される。

ex.) featureブランチの修正を,mainブランチにFast-Fowardマージで取り込んでいる。
Fast-Forwardマージ

Non Fast-Forwardマージ : 修正を取り込もうとしているブランチ(ブランチA)のコミットが,修正を取り込むブランチ(ブランチB)の直接の子孫でない場合に行われるマージ。マージが行われると,ブランチAとブランチBの内容を両方取り込んだマージコミット(merge commit)が作成される。

ex.) featureブランチとmainブランチの修正を,mainブランチにNon Fast-Forwardマージで取り込んでいる。

ちゃまちゃま

Gitの基本的な使い方(使うときの流れ)

初期設定

初期設定でやること
  1. メールアドレスと名前の登録
  2. デフォルトエディタの設定
  3. デフォルトブランチ名の設定
  4. 設定の確認
  1. メールアドレスと名前を登録するために,次のコマンドを実行する。chama@example.com"chama"は適切なものに置き換えること。プロジェクト単位でメールアドレスと名前を変える場合には--globalオプションを付けずに実行する。
git config --global user.email chama@example.com
git config --global user.name "chama"
  1. デフォルトエディタを設定するために,次のコマンドを実行する。VScodeを設定する場合は"code --wait",Vimを設定するときはvimを指定する。
git config --global core.editor "code --wait"
  1. デフォルトブランチ名をmainに設定するために,次のコマンドを実行する。
git config --global init.defaultBranch main
  1. 設定が正しく行われているか確認するために,次のコマンドを実行する。設定は.gitconfigに記述されている。
cat .gitconfig
[user]
        email = chama@example.com
        name = chama
[core]
        editor = code --wait
[init]
        defaultBranch = main

補足 : 現在のプロジェクトの設定は次のコマンドで確認できる。

git config -l
user.email=chama@example.com
user.name=chama
core.editor=code --wait
init.defaultbranch=main

リポジトリの初期化/ステージング/コミット

この節でやること
  1. リポジトリを初期化する
  2. ステージングを行う
  3. 最初のコミットを行う
  4. コミットログを確認する
  5. 修正をコミットする
  1. リポジトリの初期化は次のgit initコマンドで行う。ここではgit-tutorialディレクトリを作成し,その中でリポジトリの初期化を行っている。git initコマンドを実行するとgit-tutorialディレクトリ直下に.gitディレクトリが作成される。
mkdir git-tutorial
cd git-tutorial
git init
  1. README.mdを作成し,ステージングする。インデックスにスナップショットを登録するステージングを行うコマンドは,git addコマンドである。また,現在のステージングの状態はgit statusコマンドで確認できる。次に示すgit statusコマンドの実行結果は初期状態(No commits yet)のmainブランチに,READMD.mdというファイルを追加することを表している。
echo "Hello, git" > README.md
git add README.md
git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   README.md
  1. コミットは次のgit commitコマンドで行う。-mオプションでコミットメッセージをつけ,コミットの概要を記述する。コマンドの実行結果はmainブランチへの最初のコミットで,コミットハッシュの先頭7桁が898c68dであることを表している。
git commit -m "initial commit"
[main (root-commit) 898c68d] initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

コミットハッシュ : コミットを区別するための一意な識別子。SHA-1と呼ばれるハッシュ計算アルゴリズムで計算された40桁のハッシュ値で,git commitコマンドを実行するとこのハッシュ値の先頭7桁が表示される。

  1. コミットログの確認は次のgit logコマンドで行う。git logコマンドを実行するとコミットハッシュ,現在のブランチ,コミットした人の名前とメールアドレス,日付,コミットメッセージが表示される。
commit 898c68d70c99391312642238350a2817ebb9b394 (HEAD -> main)
Author: chama <chama@example.com>
Date:   Sun Jun 23 14:18:36 2024 +0900

    initial commit
  1. README.mdに修正を加えて再度コミットしてみる。エディタでREADME.mdを開いて内容を次のように修正する。
Update, git

README.mdを修正したら,git statusコマンドを実行してみる。git statusコマンドを実行すると,README.mdにステージングされていない変更があると表示される。

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")

変更前と変更後の差分を見たいときはgit diffコマンドを実行する。git diffコマンドを実行するとワーキングツリーとインデックスが比較され,「Hello, git」という行がなくなり,「Update, git」が追加されたことが分かる。

diff --git a/README.md b/README.md
index 22f4644..c4bf393 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-Hello, git
+Update, git
\ No newline at end of file

次に示すgit addコマンドでステージングを行い,git statusコマンドでステージングの状態を確認する。表示が「Changes not staged for commit」から「Changes to be committed」に変わったことが読み取れる。

git add README.md
git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md

ちなみに,git addコマンドを実行した後にgit diffコマンドを行うと何も表示されない。これはgit diffコマンドがワーキングツリーとインデックスの差分を比較するためである。インデックスとリポジトリの差分を比較するときはgit diff --cachedコマンドを実行する。

修正内容を次のgit commitコマンドでコミットする。コマンドを実行すると22cdadbというコミットが作成され,README.mdが変更されたことが分かる。

[main 22cdadb] update README.md
 1 file changed, 1 insertion(+), 1 deletion(-)

修正内容をコミットしたら,git logコマンドでコミット履歴を確認してみる。先ほどはコミットが1つであったが,新しく22cdadbというコミットの内容が表示される。

commit 22cdadb8aebd46d5f6c846d747adefe54f0034ea (HEAD -> main)
Author: chama <chama@example.com>
Date:   Sun Jun 23 14:41:28 2024 +0900

    update README.md

commit 898c68d70c99391312642238350a2817ebb9b394
Author: chama <chama@example.com>
Date:   Sun Jun 23 14:18:36 2024 +0900

    initial commit
ちゃまちゃま

Gitの基本的な使い方(よく使うコマンドの説明)

基本的なコマンド

git initコマンド

git initコマンドはリポジトリの初期化を行い,.gitディレクトリを作成するコマンドである。git initコマンドで新しくリポジトリを作成する方法には,既存のプロジェクトをGitの管理下に置く方法と,最初からGitに管理されたディレクトリを作成する方法の2つがある。
方法1 : 既存のプロジェクトをGitの管理下に置く方法では,既にあるディレクトリでgit initコマンドを実行する。

ex.) 既存のディレクトリgit-tutorialでgit initを実行してリポジトリを初期化する。

$ pwd
/home/chama/git-tutorial
$ git init
Initialized empty Git repository in /home/chama/git-tutorial/.git/

方法2 : 最初からGitに管理されたディレクトリを作成する方法では,git initコマンドの引数に作成するディレクトリ名を指定する。

ex.) git-tutorialディレクトリを新規作成し,Git管理下に置く。

git init git-tutorial

git addコマンド

git addコマンドはワーキングツリーにあるファイルをインデックスにステージングするコマンドである。
ワーキングツリーにあるが,インデックスやリポジトリにないファイルにgit addコマンドを実行すると,ワーキングツリーからインデックスにファイルがコピーされる。インデックスやリポジトリに存在し(Gitの管理下にある),ワーキングツリーで修正されたファイルにgit addコマンドを実行すると,インデックスにあるファイルが上書きされる。また,マージ時の衝突が解消されたことを教えるときもgit addコマンドを用いる。

ワーキングツリー/インデックス/リポジトリの図を再掲

git addコマンドは,引数にファイル/ディレクトリを指定して,ステージングを行う。

ex.) index.htmlとsrcディレクトリ配下のファイル全てをステージングする。

git add index.html
git add src/

修正をまとめてステージングするときは,次のコマンドを実行する。

git add .

git statusコマンド

git statusコマンドはワーキングツリーとステージングの状態を表示するコマンドである。ステージングされていないファイルはUntracked files,既にステージングされているファイルであるが修正がステージングされていないファイルはChanges not staged for commit,ステージングされたファイルはChanges to be committedの欄にそれぞれ表示される。
さらにステージングされたファイルは,次に示すような変更の種類が表示される。

  • modified: 既存ファイルの修正
  • new file: 新規ファイルの作成
  • deleted: 既存ファイルの削除

ex) README.mdを修正し,test.mdを新規作成してステージングしたときのgit statusコマンドの実行結果

git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.md
        new file:   test.md

修正されたファイルが多い場合,git statusコマンドの実行結果が長くなる。このため,次に示すような簡易表示を行うオプション-sが用意されている。

git status -s

簡易表示を行うオプション-sを付けてgit statusコマンドを実行した場合の,表示は次の3種類になる。

  • M_ : ステージング済みのファイル
  • _M : ワーキングツリーで修正/削除されているが,ステージングされていないファイル
  • ?? : ワーキングツリーに新規作成したがステージングされていないファイル

なお,git commitコマンドを実行した後にgit statusコマンドを実行すると,次に示す文章が表示される。git statusコマンドはワーキングツリーとステージングの状態を表示するコマンドであるため,コミット直後はワーキングツリーとステージングに差分がないと表示される。

On branch main
nothing to commit, working tree clean

git commitコマンド

git commitコマンドはステージングのスナップショットを,コミットとして記録するコマンドである
。Gitではコミットを行うときにコミットメッセージを付けることが必須になっており,コミットメッセージの入力方法は2通りある。
方法1 : 次に示すgit commitコマンドを実行する。コマンドを実行するとエディタが起動し,COMMIT_EDITMSGが表示される。COMMIT_EDITMSGの1行目にコミットメッセージを入力して閉じると,コミットが実行される。

git commit

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch main
# Changes to be committed:
#	modified:   README.md
#

方法2 : 次に示すgit commitコマンドを実行する。コミットメッセージは-mオプションの後の文字列で指定する。

git commit -m "commit message"

使い分け : 開発チームでコミットメッセージのフォーマットが指定されている場合は方法1を用いるのが良いが,個人用とであれば毎回エディタを開くのは面倒くさいので方法2でいい。


git diffコマンド

git diffコマンドはワーキングツリー,インデックス,リポジトリ間の差分を表示するためのコマンドである。またgit diffコマンドはカレントブランチと指定したブランチの差分を表示することもできる。ここでは,ワーキングツリーとインデックス,インデックスとリポジトリ,カレントブランチと指定したブランチの差分を取得する方法について説明する。

ワーキングツリーとインデックスの差分

ワーキングツリーとインデックスの差分は次のコマンドで取得できる。

git diff

ex.) 次の内容が記述されたtest.mdがコミットされているとする。

Text before change

test.mdを次の内容に修正し,git diffコマンドを実行する。

Text after change

git diffコマンドの実行結果は次のようになる。実行結果から「Text Before change」の行が「Text after change」に変更されたことが読み取れる。このように,git diffコマンドを実行するとワーキングツリーで行われた変更をインデックスと比較して,変更内容が表示される。

diff --git a/test.md b/test.md
index e48aa76..ee51b57 100644
--- a/test.md
+++ b/test.md
@@ -1 +1 @@
-Text before change
\ No newline at end of file
+Text after change
\ No newline at end of file

インデックスとリポジトリの差分

インデックスとリポジトリの差分は次のコマンドで取得できる。

git diff --cached

ex. )先の例において内容を修正したtest.mdをステージングして,インデックスとリポジトリの差分を表示してみる。git diff --cachedコマンドの実行結果は次のようになる。表示はワーキングツリーとインデックスの差分と同じであるが,インデックスとリポジトリの差分である。

diff --git a/test.md b/test.md
index e48aa76..ee51b57 100644
--- a/test.md
+++ b/test.md
@@ -1 +1 @@
-Text before change
\ No newline at end of file
+Text after change
\ No newline at end of file

カレントブランチと指定したブランチの差分

カレントブランチと指定したブランチの差分は次のコマンドで取得できる。

git diff ブランチ名

ex. )先の例において,diff-testブランチを作成してtest.mdを更新して,カレントブランチと指定したブランチの差分を表示してみる。まず,次のコマンドを実行してdiff-testブランチを作成し,ブランチを切り替える。ブランチを切り替えたらtest.mdを修正する。ここでは文章を「Text change for diff command」に変更した。

git branch diff-test
git checkout diff-test

次のコマンドでmainブランチに切り替えて,git diffコマンドを実行する。実行結果からmainブランチとdiff-testブランチで,test.mdに変更があることが読み取れる。

git checkout main
git diff diff-test
diff --git a/test.md b/test.md
index ee51b57..b7051bf 100644
--- a/test.md
+++ b/test.md
@@ -1 +1 @@
-Text after change
\ No newline at end of file
+Text change for diff command
\ No newline at end of file

git logコマンド

git logコマンドはコミット履歴を確認するためのコマンドである。次に示すようにgit logコマンドを引数無しで実行すると,直近のコミットから順にコミットハッシュ,コミットメッセージ,コミットした人の情報,コミット日時が表示される。

git log
commit 96e9d62fe731220dca092fa8de53c74dfc479995 (HEAD -> main)
Author: chama <chama@example.com>
Date:   Mon Jun 24 07:28:13 2024 +0900

    update test.md for git diff command test

commit 4d27ca6afd9eae638fbf00b813769bccab63ee63
Author: chama <chama@example.com>
Date:   Sun Jun 23 22:00:48 2024 +0900

    update README.md

git logコマンドには,簡略的な表示を行うオプション--onelineが用意されている。次に示すコマンドを実行すると,コミットハッシュの先頭7桁,ブランチ,コミットメッセージが表示される。

git log --oneline
96e9d62 (HEAD -> main) update test.md for git diff command test
4d27ca6 update README.md

git logコマンドにはこの他にもブランチの分岐を表示するオプション--graphや,表示するコミット数を指定するオプション-nが用意されている。

ちゃまちゃま

Gitの基本的な使い方(.gitignore)

プロジェクトディレクトリに置いておきたいがGitで管理したくないファイル(実行可能ファイルや中間ファイル)がある。このようなファイルは .gitignoreファイル にファイル名を記述することで,Gitの管理下に置かれないように指示ができる。

ex. )test.datというファイルをGitで管理したくないファイルとする。test.datを作成してgit status -sコマンドを実行すると次に示すように,ステージングされていないファイルであると表示される。

git status -s
?? test.dat

test.datをGitの管理下に置かれないように指示するために,.gitignoreというファイルを作成し,次に示す内容を記述する。

test.dat

.gitignoreを作成して,もう一度git status -sコマンドを実行する。実行結果から,test.datがステージングされていないファイルであると表示されていないことが読み取れる。.gitignoreファイルをコミットすると,その後は.gitignoreから「test.dat」の記述がなくなるまで,Gitの管理下に置かれない。

git status -s
?? .gitignore

補足 : .gitignoreファイルの記述にはワイルドカードが使える。このため.dat拡張子のファイルをすべてGitの管理下に置きたくない場合には次のように記述できる。

*.dat
ちゃまちゃま

ブランチを用いた開発(ブランチ操作の基本)

ワークフロー(workflow) : ブランチの作成および運用方法を整理したルール

フィーチャーブランチ(feature branch) : 追加したい機能ごとに派生したブランチ

フィーチャーブランチワークフロー : 追加機能を作成するときはフィーチャーブランチを切るルールを設けたワークフロー。mainに直接コミットさせず,ブランチで追加機能が完成したらmainにマージする。このためmainブランチはマージでのみ更新される。

本章では,このようなブランチを用いた開発で必要となる基本的なブランチ操作(ブランチの作成・確認・切り替え・削除)について説明する。

ブランチ操作の基本

ブランチの作成

ブランチの作成はgit branchコマンドで行う。現在のコミットにブランチを付けるときは,次に示すコマンドを実行するとブランチ名で指定したブランチが作成される。

git branch ブランチ名

現在のコミットではなく,指定したコミットにブランチを付けるときは,次に示すコマンドを実行する。

git branch ブランチ名 コミットハッシュ(先頭7文字)

ブランチの表示

ローカルブランチ・リモートブランチの一覧を表示する方法について説明する。ローカルブランチを表示するときは,次に示すようにgit branchコマンドを引数なしで実行する。

git branch

リモートブランチを表示するときは,次に示すようにgit branchコマンドをオプション-rを付けて実行する。

git branch -r

ローカル・リモート全てのブランチを表示するときは,次に示すようにgit branchコマンドをオプション-aを付けて実行する。

git branch -a

ブランチの切り替え

ブランチの切り替えはgit switchコマンドで行う。次に示すコマンドを実行すると,HEADが指すブランチが,ブランチ名で指定したブランチに切り替わる。

git switch ブランチ名

ブランチの作成と切り替えは,git switchコマンドの-cオプションを利用すると同時に行える。

git switch -c ブランチ名

ブランチの削除

不要になったブランチは次に示すgit branchコマンドの-dオプションで削除できる。ブランチの削除には2つ注意事項があるため,本当にそのブランチを削除して良いか,よく考えてから実行すること。

git branch -d ブランチ名
ちゃまちゃま

ブランチを用いた開発(マージ)

マージ(merge) : 片方のブランチの修正を,もう一方のブランチに取り込むこと。Gitを用いて開発を行う場合は,原則として機能ごとにブランチを切り,実装が終わったらmainブランチにマージを行う。ここではマージを行うコマンドであるgit mergeコマンド,Fast-Forwardマージ,Non Fast-Forwardマージ,衝突(conflict)の対処について説明する。

マージの種類と方法

git mergeコマンド

マージは次に示すgit mergeコマンドで行う。git mergeコマンドを実行するとカレントブランチに,指定したブランチの修正が取り込まれる。Fast-ForwardマージもNon Fast-Forwardマージもgit mergeコマンドで実行する。

git merge ブランチ名

Fast-Forwardマージ

Fast-Forwardマージってなんだっけ?(復習)
修正を取り込みたいブランチ(ブランチA)から,修正を取り込むブランチ(ブランチB)までが直線状であるときに行われるマージ。マージが行われるとブランチAからブランチBまでの修正内容が全て適用される。

Fast-Forwardマージのイメージ
Fast-Forwardマージ

ex. )mainブランチで,最新のコミットが次のようになっている。このコミットでfeatureブランチを切ってコミットを行い,Fast-Forwardマージを実行してみる。

91bea8b (HEAD -> main) add .gitignore

まずfeatureブランチを切って,適当な内容をコミットする。ここではmerge-test.mdを作成した。実行結果から,コミットハッシュ263e1a2のコミットができたことが読み取れる。

git switch -c feature
echo "fast-forward merge test" >> merge-test.md
git add .
git commit -m "add merge-test.md"
[feature 263e1a2] add merge-test.md
 1 file changed, 1 insertion(+)
 create mode 100644 merge-test.md

mainブランチにfeatureブランチの修正内容をマージする。ブランチをmainに切り替えてgit mergeコマンドを実行する。コマンドを実行すると,mainブランチの指すコミットハッシュが91bea8bから263e1a2に更新され,merge-test.mdがFast-Forwardマージされたことが読み取れる。

git switch main
git merge feature 
Updating 91bea8b..263e1a2
Fast-forward
 merge-test.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 merge-test.md

最後に,featureブランチでの機能の開発が終了したので,ブランチを削除する。

git branch -d feature
Deleted branch feature (was 263e1a2).

Non Fast-Forwardマージ

Non Fast-Forwardマージってなんだっけ?(復習)
修正を取り込もうとしているブランチ(ブランチA)のコミットが,修正を取り込むブランチ(ブランチB)の直接の子孫でない場合に行われるマージ。マージが行われると,ブランチAとブランチBの内容を両方取り込んだマージコミット(merge commit)が作成される。

Non Fast-Forwardマージのイメージ

ex. )mainブランチで,最新のコミットが次のようになっている。このコミットでfeatureブランチを切ってmainブランチ,featureブランチ双方でコミットを行い,Non Fast-Forwardマージを実行してみる。

263e1a2 (HEAD -> main) add merge-test.md

まずfeatureブランチを切って,適当な内容をコミットする。ここではmerge-test.mdを次のように修正した。実行結果から,コミットハッシュ614404eのコミットができたことが読み取れる。

git switch -c feature
echo "non fast-forward merge test" > merge-test.md
git add .
git commit -m "update merge-test.md"
git log --oneline -n 1
614404e (HEAD -> feature) update merge-test.md

次にブランチをmainに切り替えて,適当な内容をコミットする。ここではmerge-test2.mdを作成した。実行結果から,コミットハッシュeb4ad61のコミットができたことが読み取れる。

git switch main
echo "non fast-foward merge test2" > merge-test2.md
git add .
git commit -m "add merge-test2.md"
eb4ad61 (HEAD -> main) add merge-test2.md

これで,mainブランチとfeatureブランチで異なるコミットがある状態になった。この状態でfeatureブランチをmainブランチにNon Fast-Forwardマージしてみる。Non Fast-Forwardマージを実行するときは次に示すように,git mergeコマンドに-mオプションでマージコミットのコミットメッセージを指定する。

git merge feature -m "merge feature to main"
Merge made by the 'ort' strategy.
 merge-test.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

git logコマンドでmainブランチの履歴を確認すると,コミット263e1a2にfeatureブランチのコミット614404eと,mainブランチのコミットeb4ad61,マージコミットe0a9b16が記録されていることが読み取れる。

git log --oneline
e0a9b16 (HEAD -> main) merge feature to main
eb4ad61 add merge-test2.md
614404e (feature) update merge-test.md

最後に,featureブランチでの機能の開発が終了したので,ブランチを削除する。

git branch -d feature
Deleted branch feature (was 614404e).

衝突(conflict)の対処

衝突(conflict)ってなんだっけ?(復習)
Non Fast-Forwardマージで統合する2つのブランチで,同じファイルの同じ場所を修正しており,どちらの変更を反映してよいか分からない状態。

衝突を起こしてみる

ここでは意図的に衝突を発生させて,それに対処する方法について説明する。mainブランチで,最新のコミットが次のようになっている。このコミットでfeatureブランチを切って,mainブランチ,featureブランチの両方でmerge-test.mdを編集して衝突を発生させてみる。

e0a9b16 (HEAD -> main) merge feature to main

まず,featureブランチを切ってmerge-test.mdを編集,コミットする。

git switch -c feature
echo "conflict test feature branch" > merge-test.md
git add merge-test.md
git commit -m "update merge-test.md for conflict-test"
[feature 79aa8af] update merge-test.md for conflict-test
 1 file changed, 1 insertion(+), 1 deletion(-)

次にmainブランチに戻って,merge-test.mdを編集,コミットする。これでmainブランチ,featureブランチの両方でmerge-test.mdを編集した状態になった。

git switch main
echo "conflict test main branch" > merge-test.md
git add merge-test.md
git commit -m "update merge-test.md for conflict-test(main)"
[main aac598f] update merge-test.md for conflict-test(main)
 1 file changed, 1 insertion(+), 1 deletion(-)

mainブランチにfeatureブランチをマージして,衝突が起きることを確認する。git mergeコマンドを実行するとmerge-test.mdでコンフリクトが起きたことが表示され,次の画像に示すようにmerge-test.mdの衝突部分が表示される。

git merge feature 
Auto-merging merge-test.md
CONFLICT (content): Merge conflict in merge-test.md
Automatic merge failed; fix conflicts and then commit the result.

conflict

merge-test.mdの<<<<<<< HEADから=======までの部分は,HEAD(mainブランチ)の修正内容,=======から>>>>>>> featureまでの部分は,featureブランチの変更を表す。

衝突の解消

衝突を解消するにはmerge-test.mdを編集して,どちらのブランチの修正を取り込むのか記述する。ここではfeatureブランチの内容を取り込むことにすると,merge-test.mdを次のように編集する。

conflict test feature branch

merge-test.mdを編集したら,merge-test.mdをステージング/コミットすれば,衝突が解消される。git logコマンドでコミット履歴を確認するとコミットe0a9b16にfeatureブランチで行ったコミット79aa8af,mainブランチで行ったコミットaac598f,衝突を解消するために行った071166cが順に記録されていることが読み取れる。
このように,衝突が発生した場合はどちらのブランチの内容を反映するのか手動で記述する必要がある。

git add merge-test.md
git commit -m "fix conflict merge-test.md"
[main 071166c] fix coflict merge-test.md
git log --oneline
071166c (HEAD -> main) fix coflict merge-test.md
aac598f update merge-test.md for conflict-test(main)
79aa8af (feature) update merge-test.md for conflict-test
e0a9b16 merge feature to main

補足 : 衝突が発生した状態でgit statusコマンドを実行すると,衝突が発生したファイルの状態はboth modifiedと表示される。

On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   merge-test.md
ちゃまちゃま

リベース

リベースとは何か?

リベース : カレントブランチとリベース先ブランチの共通祖先から,カレントブランチまでの修正をリベース先のブランチにぶら下げること。リベースは次に示すgit rebaseコマンドで実行する。

git rebase ブランチ名

リベースのイメージを次に示す。featureブランチでgit rebase mainを実行すると,featureブランチは,mainブランチのコミットに,featureブランチにあるmainブランチの共通祖先(commit2)以降のコミットが加えられた状態になる。注意点として,コミット7はコミット5ではなく,コミット4にコミット5の修正を適用したものになる。同様にコミット8はコミット6ではなく,コミット7にコミット6の修正を適用したものになる。

リベースのイメージ

リベースによりmainブランチは変更を受けないが,mainブランチからfeatureブランチまでのコミットが直線状になる(履歴がクリーン)。この状態でfeatureコマンドの変更をmainブランチにマージするときは,Fast-Fowardマージができる(リベースはマージの前処理)。マージするのか,リベースが必要なのかはチームのルールによる。

対話的リベース

対話的リベース(interactive rebase) : コミット順序の変更や複数のコミットをまとめて1つのコミットにする操作を対話的に行うこと。対話的リベースは次のコマンドで行う。

git rebase -i ブランチ名

対話的リベースの用語
pick : コミットをそのまま使う
squash : 1つ前のコミットとまとめる

ex)対話的リベースをやってみる。mainブランチで,最新のコミットが次のようになっている。このコミットでfeatureブランチを切ってmainブランチ,featureブランチ双方でコミットを行い,対話的リベースを実行してみる。

071166c (HEAD -> main) fix coflict merge-test.md

まず,featureブランチを切って適当な内容をコミットする。ここではrebase-test1.mdを追加し(fb9236d),rebase-test2.mdを追加し(ebb9b95),rebase-test1.mdを修正し(e59f146),rebase-test3.mdを追加した(0dfb394)。

git switch -c feature
echo "git rebase test1" > rebase-test1.md && git add rebase-test1.md && git commit -m "add rebase-test1.md"
echo "git rebase test2" > rebase-test2.md && git add rebase-test2.md && git commit -m "add rebase-test2.md"
echo "git rebase test1 update" > rebase-test1.md && git add rebase-test1.md && git commit -m "update rebase-test1.md"
echo "git rebase test3" > rebase-test3.md && git add rebase-test3.md && git commit -m "add rebase-test3.md"
git log --oneline -n 5
0dfb394 (HEAD -> feature) add rebase-test3.md
e59f146 update rebase-test1.md
ebb9b95 add rebase-test2.md
fb9236d add rebase-test1.md
071166c (main) fix coflict merge-test.md

次にmainブランチに切り替えて適当な内容をコミットする。ここではrebase-test.mdを追加してコミットした。

git switch main
echo "git rebase test main" > rebase-test.md && git add rebase-test.md && git commit -m "add rebase-test.md"
git log --oneline -n 1
7c12527 (HEAD -> main) add rebase-test.md

mainブランチでコミットしたら,featureブランチに切り替えて,対話的リベースのコマンドを実行する。コマンドを実行するとgit-rebase-todoが表示される。このgit-rebase-todoでコミット順序の変更や複数のコミットをまとめるかを記述する。

git switch feature
git rebase -i main
pick fb9236d add rebase-test1.md
pick ebb9b95 add rebase-test2.md
pick e59f146 update rebase-test1.md
pick 0dfb394 add rebase-test3.md

ここでは,コミットfb9236debb9b95の順序を入れ替えて,コミットfb9236de59f146を一つのコミットにまとめる操作を行ってみる。この操作を行うために,git-rebase-todoを次のように編集する。

pick ebb9b95 add rebase-test2.md
pick fb9236d add rebase-test1.md
squash e59f146 update rebase-test1.md
pick 0dfb394 add rebase-test3.md

編集したgit-rebase-todoを閉じると,次に示すようなCOMMIT_EDITMSGが表示される。このCOMMIT_EDITMSGでは,コミットfb9236de59f146をまとめたコミットのコミットメッセージを指定できる。ここでは,COMMIT_EDITMSGを次のように編集した。COMMIT_EDITMSGを閉じるとリベースが実行される。

# This is a combination of 2 commits.
# This is the 1st commit message:

add rebase-test1.md

# This is the commit message #2:

update rebase-test1.md
add and update rebase-test1.md

- add rebase-test1.md
- update rebase-test1.md
[detached HEAD 1343119] add and update rebase-test1.md
 Date: Sat Jun 29 17:45:11 2024 +0900
 1 file changed, 1 insertion(+)
 create mode 100644 rebase-test1.md
Successfully rebased and updated refs/heads/feature.

リベースが正しく行われたか,git logコマンドで確認する。実行結果から,mainブランチで行ったコミットから,featureブランチで行ったコミットが直線状に繋がっていることが読み取れる。またfeatureブランチのコミットは,対話的リベースで操作した内容が反映されていることが読み取れる。このことからリベースが正しく行われていることが確認できた。

git log --oneline -n 5
bdc66f7 (HEAD -> feature) add rebase-test3.md
1343119 add and update rebase-test1.md
02801b0 add rebase-test2.md
7c12527 (main) add rebase-test.md
071166c fix coflict merge-test.md
ちゃまちゃま

トラブルシューティング

トラブルシューティング

コミットメッセージの修正

次に示すように,コミットしたメッセージにミスタイプがあり,コミットメッセージを修正したくなったとする。このようなときは,次に示すgit commit --amendコマンドで直前のコミットのコミットメッセージを修正できる。

! test.mdをtest.dmにミスタイプした
git commit -m "update test.dm"
git commit --amend -m "update test.md"

ファイルの修正取り消し

次に示すように,ファイルの修正を行ったが(ステージング・コミットはしていない),その修正を取り消したいとする。このようなときは,次に示すgit restoreコマンドでファイルの修正を取り消しにできる。

git diff
diff --git a/rebase-test.md b/rebase-test.md
index 515ca44..92a0d50 100644
--- a/rebase-test.md
+++ b/rebase-test.md
@@ -1 +1 @@
-git rebase test feature
+git restore test feature
git restore ファイル名

ステージングの取り消し

次に示すように,ファイルを修正してステージングを行ったが(コミットはしていない),ステージングを取り消したいとする。このようなときは,次に示すgit restore --stagedコマンドでステージングを取り消しにできる。

git status
On branch feature
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   rebase-test.md
git restore --staged ファイル名

ブランチを切り替え忘れて作業をしてしまった

次に示すように,featureブランチで作業をしようと思っていたのに,ブランチの切り替えを忘れてmainブランチで作業をしてしまったする(ステージング・コミットは行っていない)。

git status
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:   test.md

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

このようなときは,git stashコマンドを用いてmainブランチの変更を退避し,featureブランチに退避した変更を取り込んで,あたかもfeatureブランチで作業を始めた状態にすることができる。まず,次に示すgit stashコマンドをmainブランチで実行して,変更を退避する(7c12527 は直前のコミットハッシュ)。git stashコマンドを実行すると,mainブランチは直前のコミットの状態になる。

git stash
Saved working directory and index state WIP on main: 7c12527 add rebase-test.md

git status
On branch main
nothing to commit, working tree clean

退避された変更は,次のコマンドで確認できる。退避した変更はスタックにプッシュされた状態になる。

git stash list
stash@{0}: WIP on main: 7c12527 add rebase-test.md

次に示すコマンドを実行して,featureブランチに退避した変更を取り込む。これで,mainブランチは解くに変更がない状態,featureブランチはmainブランチで行った変更を取り込んダ状態になった。

git switch feature
git stash pop
On branch feature
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:   test.md

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (2d65470a4b67d89d3777bb7d1503401d511a1ace)
ちゃまちゃま

便利なコマンド

行単位で編集履歴を確認する

コードの行単位で,いつ誰が編集を行ったか確認するコマンドとして,git blameコマンドが用意されている。

git blame ファイル名

ex) myfunc.pyの編集履歴を確認する

git blame myfunc.py
a4f9c5ee (chama 2024-07-06 15:24:40 +0900  1) def func1():
a4f9c5ee (chama 2024-07-06 15:24:40 +0900  2)     print("this is func1")
a4f9c5ee (chama 2024-07-06 15:24:40 +0900  3) 
a4f9c5ee (chama 2024-07-06 15:24:40 +0900  4) 
a4f9c5ee (chama 2024-07-06 15:24:40 +0900  5) def func2():
232e58f4 (chama 2024-07-06 20:45:45 +0900  6)     print("update func2")
a4f9c5ee (chama 2024-07-06 15:24:40 +0900  7) 
a4f9c5ee (chama 2024-07-06 15:24:40 +0900  8)     
a4f9c5ee (chama 2024-07-06 15:24:40 +0900  9) if __name__ == "__main__":
a4f9c5ee (chama 2024-07-06 15:24:40 +0900 10)     func1()
a4f9c5ee (chama 2024-07-06 15:24:40 +0900 11)     func2()

バグが混入したコミットを特定する

開発の途中までは正常に動作していたが,どこかのコミットからバグが混入してしまったとする。このようなときに,どのコミットからバグが混入したかを特定するコマンドとして,git bisectコマンドが用意されている。git bisectコマンドでバグが混入したコミットを特定する手順は次の通りである。この例では現在のブランチ(mainブランチ)にバグが入っており,そのバグがどのコミットに入ったのかを特定する。

  1. 確実にバグが入っていないコミットにブランチを付ける(rootブランチと呼称する)。
  2. rootブランチで次に示すコマンドを実行して,二分探索を開始する。
git bisect start main root
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[30a8c2bef755c2853aca4fcfcdfb33874d8d0425] update
  1. git statusコマンドを実行して,git bisectによる二分探索モードになっていることを確認する。標準出力から読み取れるように,二分探索を中止したいときはgit bisect resetコマンドを実行する。
git status
HEAD detached at 30a8c2b
You are currently bisecting, started from branch 'root'.
  (use "git bisect reset" to get back to the original branch)

nothing to commit, working tree clean

You are currently bisecting, started from branch 'main'.
  (use "git bisect reset" to get back to the original branch)

nothing to commit, working tree clean
  1. git bisectによる二分探索モードになっている今の状態では,gitが適当なコミットを指すスナップショットをワーキングツリーに展開している。このため,今の状態のバグの有無を確認し,それをgitに伝える。gitにバグの有無を伝えるコマンドを次に示す。
git bisect good // バグがあるとき
git bisect bad  // バグがないとき

実行結果の例

Bisecting: 2 revisions left to test after this (roughly 1 step)
[2c6814a3ed6aef3509578447a3636d1204599a84] update
  1. 4を何度か繰り返すと次に示すように,バグが混入されたコミットが表示される。これでバグが混入したコミットを特定できた。
a711b478605102fa1d57e07c6f4c837361cce033 is the first bad commit
commit a711b478605102fa1d57e07c6f4c837361cce033 (HEAD)
Author: H. Watanabe <kaityo@users.sourceforge.jp>
Date:   Sat Sep 18 21:43:43 2021 +0900

    update

 evenodd.sh | 4 ++++
 1 file changed, 4 insertions(+)
ちゃまちゃま

リモートリポジトリの操作

リモートリポジトリ : ベアリポジトリ(ワーキングツリーやインデックスを持たない)で構成される。ベアリポジトリはproject名.gitという名前にする。ベアリポジトリはワーキングツリーやインデックスを持たないため,.gitの情報のみ記録している。

本節では,このリモートリポジトリの使い方・操作について説明する。

リモートの情報を取得する

Gitを使用しているプロジェクトに参加したら,まずはリモートリポジトリの情報を取得して,ローカルで開発を行えるようにする。この操作は次に示すgit cloneコマンドで行う。git cloneコマンドを実行するとリモートリポジトリの情報が取得され,プロジェクト名が付いたディレクトリが作成される。作成されたディレクトリは,リモートから取得した.gitディレクトリが置かれており,ワーキングツリーが展開された状態になっている。

git clone リモートリポジトリのURL

リモートにローカルの修正を反映する

ローカルで開発を進めて,その内容をリモートに反映したいとする。この操作は次に示すgit pushコマンドで行う。

git push リモートURL ブランチ名

ローカルにリモートの修正を反映する

既にクローン済みのローカルリポジトリに,誰かがリモートに反映した修正を取り込みたいとする。この操作はgit fetchコマンドとgit mergeコマンドで行う。
ローカルにリモートの修正を取り込みたいときは,まず次に示すgit fetchコマンドを実行する。引数にはリモートURLを指定することも可能だが,既にクローンを行っているため,originで使用することがほとんどである。取得したリモートの修正はorigin/ブランチ名でローカルに保存される。

git fetch origin

git fetchコマンドは,リモートに反映された修正の情報をローカルに取得するが,ローカルへの反映は行わない。このため,リモートの修正を反映するためにはgit mergeコマンドで,origin/ブランチ名をマージする必要がある。
例えばmainブランチの修正をリモートから取得すると,その内容はorigin/mainブランチに保存される。修正をローカルのmainブランチに反映したいのであれば,mainブランチにスイッチして次のコマンドを実行する。

git merge origin/main

git pullコマンド

ローカルにリモートの修正を反映するときは,git fetchコマンドとgit mergeコマンドの2つのコマンドを実行する必要がある。git pullコマンドはこれらをまとめて実行してくれるコマンドである。