gitコマンドって実務でどう使うんだろう?
独学の git コマンドを実務で使いまくり、最近やっとうまく運用できているように感じます。
そのうえで、git コマンドを勉強し始めた頃、「コマンドの説明はいっぱいあるけど、実務でどうコマンドを使うんだろう?」 と感じていたのを思い出しました。
そんな想いから、よく使う git コマンドを実務テイストで振り返ってみました。
本記事に書いていないもの
実務では使うのですが、諸事情により以下は省いています。
- submodule
- 本当はこの記事に含めようかと思ったのですが、長くなりすぎてしまったので、需要がありそうだったら次回作に書こうかと思います。
- プルリク
- コマンドの説明をしたいため、省きます。
- Git Flow やら GitHub Flow やらの Flow 系の考え
- 説明がややこしくなってしまうので省きます。
developブランチ、masterブランチ、開発ブランチしか登場しません。
- 説明がややこしくなってしまうので省きます。
- cherry-pick コマンド
- 実務で使うコマンドではあるのですが、かなりのイレギュラー時のみなので省きます。
0. 前提
プロジェクト内容イメージ
この記事で例として使うプロジェクトのイメージはこちら。
数字をコンソールに表示するプロジェクトです。
現状、1〜5を表示するという、壮大...な...プロジェクトになっています。
.
├── .gitignore
├── README.md
└── counter.py
コードは、Python ファイルが1つのみ。
# -*- coding:utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
def print_number():
"""
1〜5までの数字を表示する
"""
print 1
print 2
print 3
print 4
print 5
# 数字を表示する
print_number()
ロググラフはこうなっている状態です。
プロジェクトメンバー
... 開発リーダーです。
... プロジェクトに入ったばかりのA君。A君目線でコマンドを書いていきます。
... あと1人の開発者のBさんです。
1. 開発環境構築
A君はプロジェクトに加入したので、ローカルPCに開発環境を構築します。
-
リーダーからの指示内容
- gitリポジトリのURLは
https://github.com/west-hiroaki/git-test-for-qiita.git
- gitリポジトリのURLは
1-1. ソースコードの取得
git clone <gitリポジトリURL> <ディレクトリ名>
git clone
コマンドで、開発リポジトリをローカルに取得します。
ディレクトリ名は、「project」とします。
~$ git clone https://github.com/west-hiroaki/git-test-for-qiita.git project
取得後のディレクトリ構造はこうなっています。
以降は project ディレクトリ内で git コマンドを実行します。
~$ tree
.
└── project
├── README.md
└── counter.py
git branch -a
「git branch -a
」 コマンドで、ブランチ状態を確認しておきます。
-a
パラメータを付けないとローカルブランチのみですが、付けるとリモートブランチも確認できます。
リモートには、develop
ブランチと master
ブランチがあることが分かります。
~/project (master)$ git branch -a
* master
remotes/origin/develop
remotes/origin/master
2. 追加開発① (シンプルな開発フロー)
2-1. 開発指示
リーダーからA君に開発指示がありました。
-
リーダーからの指示内容
- 現状1〜5を表示している仕様を拡張。1〜10を表示してほしい。
- 開発ブランチ名は
feature/five-to-ten
とする。まだ作成していないので作成してほしい。
2-2. 開発準備
git checkout -b <作成するブランチ名> [派生元ブランチ名]
「git checkout -b
」 コマンドで、develop
ブランチから派生させた開発ブランチ feature/five-to-ten
をローカルに作成します。
~/project (master)$ git checkout -b feature/five-to-ten remotes/origin/develop
※ この例だと、明示的に派生元ブランチを指定していますが、省略すると現在のブランチから派生されます。(2019/6/27:nishina555さんのコメントにより加筆)
作成と同時に、アクティブブランチが作成した開発ブランチに切り替わっています。
ローカルブランチを確認すると、feature/five-to-ten
ブランチに切り替わっているのが分かります。
~/project (feature/five-to-ten)$ git branch
* feature/five-to-ten
master
2-3. ソースを修正
6〜10 の表示を追加しました。
2-4. 修正したファイルを開発ブランチに反映
git diff <差分を確認するファイル名>
「git diff
」 コマンドで、修正したファイル差分を確認します。
追加した内容が間違っていないことを確認します。
~/project (feature/five-to-ten)$ git diff counter.py
diff --git a/counter.py b/counter.py
index 37aec15..660706c 100644
--- a/counter.py
+++ b/counter.py
@@ -12,6 +12,11 @@ def print_number():
print 3
print 4
print 5
+ print 6
+ print 7
+ print 8
+ print 9
+ print 10
git add <インデックスに移動するファイル名>
「git add
」 コマンドで、修正ファイル(counter.py
)を、ワークツリーからインデックスに移動します。
~/project (feature/five-to-ten)$ git add counter.py
git status
「git status
」 コマンドで、インデックスの状態を確認します。
修正したファイル以外がインデックスに入っていないことを確認します。
~/project (feature/five-to-ten)$ git status
On branch feature/five-to-ten
Your branch is up-to-date with 'origin/develop'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: counter.py
counter.py
以外がインデックスに入っていないことを確認しました。
git diff --cached <差分を確認するファイル名>
「git diff --cached
」 コマンドで、インデックスのファイル差分を確認します。
commit 前に、ファイルの差分を最終確認するためです。
この例だとあまり意味がないですが、実際のプロジェクトだと修正ファイルや差分も多く、間違った修正を入れるわけにはいきませんので重要です。
~/project (feature/five-to-ten)$ git diff --cached counter.py
diff --git a/counter.py b/counter.py
index 37aec15..660706c 100644
--- a/counter.py
+++ b/counter.py
@@ -12,6 +12,11 @@ def print_number():
print 3
print 4
print 5
+ print 6
+ print 7
+ print 8
+ print 9
+ print 10
git commit -m <コミットメッセージ>
修正内容の確認ができたので、「git commit
」 コマンドで、インデックスのファイルをブランチに反映します。
~/project (feature/five-to-ten)$ git commit -m '1から10まで出力するように修正'
git log
「git log
」 コマンドで、ブランチのログを確認します。
ログの一番上に、さっき行ったコミットが追加されていることを確認できます。
~/project (feature/five-to-ten)$ git log
commit 81fbe09affac27b16f81c25c4a053fee62f83af7 (HEAD -> feature/five-to-ten)
Author: hiroaki.nishi <hiroaki.nish@hogehoge.jp>
Date: Fri May 25 16:29:50 2018 +0900
1から10まで出力するように修正
commit acd8ebabdaea3c192e84ad0d875d3a1113807e86 (origin/master, origin/develop, master)
Author: hiroaki.nishi <hiroaki.nish@hogehoge.jp>
Date: Mon May 21 11:11:21 2018 +0900
1から5までを出力する。初期開発
commit 302f5f20f0fd2cac7b603ee009e9ad62fad4dc1b
Author: hiroaki.nishi <hiroaki.nish@hogehoge.jp>
Date: Mon May 21 11:10:45 2018 +0900
add .gitignore
commit 1902752cce860d3c7135b9a4bdfcdeb713ad863e
Author: hiroaki.nishi <hiroaki.nish@hogehoge.jp>
Date: Mon May 21 11:22:31 2018 +0900
README.md 内容作成
commit 933e0434d288358760c3b7da3b033097ebe6f8e7
Author: hiroaki.nishi <hiroaki.nish@hogehoge.jp>
Date: Mon May 21 11:10:05 2018 +0900
init
commit 8448ad712608adc2c9f205e6fd9f60c46249c75c
Author: hiroaki.nishi <hiroaki.nish@hogehoge.jp>
Date: Mon May 21 11:09:14 2018 +0900
ちなみに、まだ git に慣れていない時は情報が多すぎると思いますので、--oneline
オプションを付けると見やすくなっていいかもしれません。(2019/6/27:tommy_aka_jpsさんのコメントにより加筆)
~/project (feature/five-to-ten)$ git log --oneline
81fbe09 (HEAD -> feature/five-to-ten) 1から10まで出力するように修正
acd8eba (origin/master, origin/develop, master) 1から5までを出力する。初期開発
302f5f2 add .gitignore
1902752 README.md 内容作成
933e043 init
8448ad7
ロググラフは以下のようになりました。
*git push [リモート名] [ブランチ名]
「git push
」 コマンドで、リモートの開発ブランチに反映します。
~/project (feature/five-to-ten)$ git push origin feature/five-to-ten
※補足:branch -b 時の追跡ブランチについて
※ 補足に書いてある通り、追跡ブランチを設定しておけば、git push
だけで push 可能です。また、git push origin HEAD
とすれば、追跡ブランチも関係なく同名のリモートブランチに push することができます。
(2019/7/1:nishina555さんのコメントにより加筆)
ロググラフは以下のようになりました。
2-5. リーダーがソースを確認して承認
リーダーから、develop
ブランチへのマージOKの許可をもらいました。
※ 実務だとここでプルリクが挟まることが多いと思いますが、省略しています
2-6. develop ブランチへのマージ
git merge <マージするブランチ名>
「git merge
」 コマンドで、開発ブランチを、develop
ブランチにマージします。
まず、develop
ブランチに移動します。
~/project (feature/five-to-ten)$ git checkout develop
次に、feature/five-to-ten
ブランチをマージして、リモートに push します。
~/project (develop)$ git merge feature/five-to-ten
~/project (develop)$ git push origin develop
ロググラフは以下のようになりました。
2-7. 開発ブランチを削除
git branch -d <削除するブランチ名>
「git branch -d
」 コマンドで、不要になったローカルのfeature/five-to-ten
ブランチを削除します。
~/project (develop)$ git branch -d feature/five-to-ten
※ 今回は、削除対象ブランチがdevelopブランチにマージされているので git branch -d
コマンドで削除できますが、マージされていない場合は削除できません。(ブランチ指定ミスで消してしまわないためです)
強制的に削除したい場合は git branch -D
コマンドで削除してください。
(2019/7/1:otchyさんのコメントにより修正)
git push -d <リモート名> <削除するブランチ名>
同様に、「git push -d
」 コマンドで、リモートブランチも削除します。(2019/7/1:Sheileさんのコメントにより修正)
~/project (develop)$ git push -d origin feature/five-to-ten
3. 追加開発②
3-1. 開発指示
更に、リーダーからA君に開発指示がありました。
- 指示内容
-
print_number
関数に数値型の引数を追加して、1から引数値までの数字を表示するようにする。 - 現状の1〜10表示の後、1〜20も表示する。
- 開発ブランチ名は
feature/to-variable
。まだ無いので作成してほしい。
-
3-2. 開発準備
git fetch -p
ローカルに作成するにあたり、「git fetch -p
」 コマンドで、ローカルで保持しているリモート情報を最新状態にします。
「追加開発①」の時は、clone した直後だったのでローカルとリモートにズレはあり得ませんでしたので省略しました。
以降はこれを行わないと、古い状態でブランチが作成される可能性があります。
(古い状態とは、他の人が push した内容が反映されていない状態ということです。リモートの情報はローカルに保持されており、リモートで誰かが更新を行ったとしても自動的には反映されません。ローカルのリモート情報を最新にするのが fetch コマンドです。)
~/project (develop)$ git fetch -p
次に、先程と同様、開発ブランチ feature/to-variable
を作成します。
~/project (develop)$ git checkout -b feature/to-variable remotes/origin/develop
~/project (feature/to-variable)$ git branch
develop
* feature/to-variable
master
3-3. まず「引数対応」を開発
今回、開発指示が、「引数対応」と「1〜20表示」の2つあり、コミットを分けた方が後々ログで修正を追いやすくなります。
まずは引数対応を行います。
3-4. 割り込みで不具合を修正
修正を commit しよう...と思ったところで、A君はリーダーから不具合報告を受けます。
-
リーダーからの指示内容
- 1〜10表示するように修正してもらったが、関数のコメントが1〜5のままになっていたので修正してほしい。
- bugfixブランチを作成する必要はない。
develop
ブランチを直接修正してほしい。
git stash save -u <メッセージ>
先に不具合対応を行うため、「git stash save
」 コマンドで、差分を一旦 stash に退避しておきます。
~/project (feature/to-variable)$ git stash save -u '引数化対応'
一応、差分が stash に退避されたことを確認します。
「git status
」 コマンドで、差分が無くなっていることを確認します。
~/project (feature/to-variable)$ git status
On branch develop
Your branch is behind 'origin/develop' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
そして develop
ブランチに移動して、コメントを修正します。
~/project (feature/to-variable)$ git checkout develop
そして、修正点を add して commit。
~/project (develop)$ git add counter.py
~/project (develop)$ git commit -m 'コメントを修正'
3-5. push しようとしたらエラー (non fast forward)
ところが... リモートに push しようとしたらエラーになりました。
~/project (develop)$ git push origin develop
To https://github.com/west-hiroaki/git-test-for-qiita.git
! [rejected] develop -> develop (non-fast-forward)
error: failed to push some refs to 'https://github.com/west-hiroaki/git-test-for-qiita.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
エラーメッセージのキーポイントは non-fast-forward
。
ロググラフを確認するとこうなっています。
Bさんが先に push していたため、ロググラフが分岐してしまい、マージが出来ない状態でした。
リーダーの指示で、'start.'、'end.' を表示する開発を行ったとのことです。
この場合、解消方法は2つあります。
- pull する。
- rebase する。
pull した場合、remotes/origin/develop
ブランチをマージすることになり、ロググラフが荒れる基になります。
詳細は、「Qiita - gitロググラフを綺麗にするちょっとした技【基本編】」を読んでください。
おすすめは rebase ですので、rebase する方向で話を進めます。
git rebase <ブランチ名>
「git rebase
」 コマンドで、履歴をキレイにします。1
~/project (develop)$ git rebase remotes/origin/develop
ロググラフを確認すると、分岐がなくなりました。
これで push 可能になりました。
では push します。
~/project (develop)$ git push origin develop
無事、リモートに反映されました。
3-5. 不具合修正が完了したので追加開発に戻る
「git checkout
」 コマンドで、開発ブランチの feature/to-variable
に戻ります。
~/project (develop)$ git checkout feature/to-variable
git stash list
では、先程途中まで作成した「関数化対応」の修正を戻します。
「git stash list
」 コマンドで、stash の一覧を確認して
~/project (feature/to-variable)$ git stash list
stash@{0}: On feature/to-variable: 引数化対応
git stash pop <対象stash番号>
「git stash pop
」 コマンドで、修正途中のコードを取り出します。
~/project (feature/to-variable)$ git stash pop stash@{0}
counter.py
のコードが退避前に戻りました。
では、引数化対応をコミットします。
~/project (feature/to-variable)$ git add counter.py
~/project (feature/to-variable)$ git commit -m '引数化対応'
この時点でのロググラフはこうなっています。
3-6. 最新のdevelopを反映
先程と同じく、分岐してしまっているので rebase
します。
...が...今回は counter.py
で修正箇所が被ってしまうため、コンフリクトが発生します。
~/project (feature/to-variable)$ git rebase remotes/origin/develop
First, rewinding head to replay your work on top of it...
Applying: 引数化対応
Using index info to reconstruct a base tree...
M counter.py
Falling back to patching base and 3-way merge...
Auto-merging counter.py
CONFLICT (content): Merge conflict in counter.py
error: Failed to merge in the changes.
Patch failed at 0001 引数化対応
The copy of the patch that failed is found in: .git/rebase-apply/patch
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
counter.py
のコンフリクトを解消させます。
git rebase --continue
コンフリクトを解消させたファイルを add し、「git rebase --continue
」 コマンドで、rebase を終了させます。
~/project (feature/to-variable)$ git add counter.py
~/project (feature/to-variable)$ git rebase --continue
Applying: 引数化対応
これでロググラフがキレイになりました。
3-7. 次に「1〜20表示」の開発
では、次にもう1つの開発。
現状の1〜10表示の後、1〜20も表示する。
これを行います。
では、add して commit します。
~/project (feature/to-variable)$ git add counter.py
~/project (feature/to-variable)$ git commit -m '1から20も出力する'
ロググラフはこうなりました。
3-8. 1つ前のコミットの修正
コミットしてから気が付きました。
コメントをもうちょっと分かりやすくしておいた方がよかったなと。
コメントを修正します。
git commit --amend
このままコミットすると、「1〜20対応」が2つのコミットに分かれてしまいます。
なるべく1つにしたいので、「git commit --amend
」 コマンド 2 で、1つ前のコミットに含めてしまいます。
(2019/6/27:querykumaさんのコメントにより注釈追加)
~/project (feature/to-variable)$ git add counter.py
~/project (feature/to-variable)$ git commit --amend
[feature/to-variable 3f5cc60] 1から20も出力する
Date: Wed Jun 6 15:41:40 2018 +0900
1 file changed, 4 insertions(+), 1 deletion(-)
ロググラフを確認すると、「1から20も出力する」コミットに含まれたことが分かります。3
コミットIDが「e4182ba」から「3f5cc60」に変わったことからも分かるとおり、コミットが再作成されています。
3-9. 2つ前のコミットの修正
A君は、今回行った開発を見直してみて、「引数対応に対して、引数のマイナス値チェックを入れた方がいいかも」と思いました。
git rebase -i
2つ以上前のコミットの場合、「git rebase -i
」 コマンドを使います。
~/project (feature/to-variable)$ git rebase -i HEAD~2
すると、コミットの編集画面が表示されます。
今回は、「引数化対応」コミットに対して修正したいので、このコミットの「pick」を「edit」に書き換えます。
すると、rebase モード(引数化対応コミットした直後の状態)になりますので、マイナス値チェックを入れ込みます。
そして、先程と同じくコミットは1つにしたいので、addして、「git commit --amend
」 コマンドを実行します。
~/project (feature/to-variable)$ git add counter.py
~/project (feature/to-variable)$ git commit --amend
[detached HEAD 00c15d0] 引数化対応
Date: Wed Jun 6 12:39:56 2018 +0900
1 file changed, 12 insertions(+), 13 deletions(-)
git rebase --continue
追加が完了したので、「git rebase --continue
」 コマンドで rebase モードを終了します。
~/project (feature/to-variable)$ git rebase --continue
Successfully rebased and updated refs/heads/feature/to-variable.
ロググラフを確認すると、「引数化対応」コミットのコミットIDが「00ed0a5」から「00c15d0」に変わっていますが、同時に「1から20も出力する」コミットも、コミットIDが「3f5cc60」から「69b0147」に変わったことが分かります。
「引数化対応」コミットが変わったことに、子供の「1から20も出力する」コミットも再作成されています。
3-10. 開発が完了したのでリモートに反映
~/project (feature/to-variable)$ git push origin feature/to-variable
3-11. masterまで反映
この後、リーダーにより、「develop ブランチへのマージ」「master ブランチへのマージ」「開発ブランチの削除」が行われました。
以上で、基本的な流れと基本的な git コマンドの使い方は網羅できたかなと思います!
補足:追跡ブランチについて
git branch -b
コマンドでブランチを作成すると、追跡ブランチが設定されていない状態になります。
追跡ブランチは、git branch -vv
コマンドで確認できます。
追跡ブランチが無いと、git push
コマンドで対象ブランチ名を省略した場合、push できません。
~/project (feature/to-variable)$ git push
fatal: The current branch test has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature/to-variable
その場合、エラーメッセージに表示されている通り、git push --set-upstream origin feature/to-variable
を実行すれば、以降は origin feature/to-variable
を追跡ブランチとして設定されますので、以降は git push
だけで push できるようになります。
-
同じことが
git pull --rebase
でも行えます。(2019/7/1:Sheileさんのコメントにより加筆) ↩ -
使用例では、
--reset-author
オプションを付けています。これは、git commit --amend
だけだとタイムスタンプが変わらないためです。もしタイムスタンプが重要ではない場合は、git commit --amend
だけで充分です。 ↩ -
コミットがまとまるということは、履歴が変わるということです。つまり、既に push されていて、他の人が前のコミットから修正を行っていたとすると、その人は push できない状態になってしまいます。メンバーに迷惑をかけてしまうので、
--amend
は、push 前のみ使用するようにしてください。 ↩