実務でどんな git コマンドを使っているか振り返ってみる


gitコマンドって実務でどう使うんだろう?

独学の git コマンドを実務で使いまくり、最近やっとうまく運用できているように感じます。

そのうえで、git コマンドを勉強し始めた頃、「コマンドの説明はいっぱいあるけど、実務でどうコマンドを使うんだろう?」 と感じていたのを思い出しました。

そんな想いから、よく使う git コマンドを実務テイストで振り返ってみました。


本記事に書いていないもの

実務では使うのですが、諸事情により以下は省いています。


  • submodule


    • 本当はこの記事に含めようかと思ったのですが、長くなりすぎてしまったので、需要がありそうだったら次回作に書こうかと思います。



  • プルリク


    • コマンドの説明をしたいため、省きます。



  • Git Flow やら GitHub Flow やらの Flow 系の考え


    • 説明がややこしくなってしまうので省きます。

      developブランチ、masterブランチ、開発ブランチしか登場しません。



  • cherry-pick コマンド


    • 実務で使うコマンドではあるのですが、かなりのイレギュラー時のみなので省きます。




0. 前提


プロジェクト内容イメージ

この記事で例として使うプロジェクトのイメージはこちら。

数字をコンソールに表示するプロジェクトです。

現状、1〜5を表示するという、壮大...な...プロジェクトになっています。


プロジェクトのファイル構造

.

├── .gitignore
├── README.md
└── counter.py

コードは、Python ファイルが1つのみ。


"counter.py"の内容

# -*- 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()


ロググラフはこうなっている状態です。

ロググラフ


プロジェクトメンバー

:bust_in_silhouette: ... 開発リーダーです。

:boy_tone1: ... プロジェクトに入ったばかりのA君。A君目線でコマンドを書いていきます。

:woman_tone1: ... あと1人の開発者のBさんです。


1. 開発環境構築

:boy_tone1:A君はプロジェクトに加入したので、ローカルPCに開発環境を構築します。



  • :bust_in_silhouette:リーダーからの指示内容


    • gitリポジトリのURLは

      https://github.com/west-hiroaki/git-test-for-qiita.git




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. 開発指示

:bust_in_silhouette:リーダーから:boy_tone1:A君に開発指示がありました。



  • :bust_in_silhouette:リーダーからの指示内容


    • 現状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

※補足:branch -b 時の追跡ブランチについて


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さんのコメントにより加筆)


ブランチのログを確認(簡易表示ver)

~/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 log よく使うオプション一覧 - FlashBuilder-Job.com

(2019/7/1:rekiさんのコメントにより加筆)


ロググラフは以下のようになりました。

ロググラフ


*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. リーダーがソースを確認して承認

:bust_in_silhouette:リーダーから、develop ブランチへのマージOKの許可をもらいました。

※ 実務だとここでプルリクが挟まることが多いと思いますが、省略しています


2-6. develop ブランチへのマージ


git merge <マージするブランチ名>

git merge」 コマンドで、開発ブランチを、develop ブランチにマージします。

まず、develop ブランチに移動します。


developブランチに移動

~/project (feature/five-to-ten)$ git checkout develop


次に、feature/five-to-ten ブランチをマージして、リモートに push します。


developブランチに、feature/five-to-tenブランチをマージ

~/project (develop)$ git merge feature/five-to-ten



developブランチをリモートにpush

~/project (develop)$ git push origin develop


ロググラフは以下のようになりました。

ロググラフ


2-7. 開発ブランチを削除


git branch -d <削除するブランチ名>

git branch -d」 コマンドで、不要になったローカルのfeature/five-to-tenブランチを削除します。


ローカルの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さんのコメントにより修正)


リモートのfeature/five-to-tenブランチを削除

~/project (develop)$ git push -d origin feature/five-to-ten



3. 追加開発②


3-1. 開発指示

更に、:bust_in_silhouette:リーダーから:boy_tone1: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 しよう...と思ったところで、:boy_tone1:A君は:bust_in_silhouette:リーダーから不具合報告を受けます。



  • :bust_in_silhouette:リーダーからの指示内容


    • 1〜10表示するように修正してもらったが、関数のコメントが1〜5のままになっていたので修正してほしい。

    • bugfixブランチを作成する必要はない。 develop ブランチを直接修正してほしい。




git stash save -u <メッセージ>

先に不具合対応を行うため、「git stash save」 コマンドで、差分を一旦 stash に退避しておきます。


差分を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 ブランチに移動して、コメントを修正します。


developブランチに移動

~/project (feature/to-variable)$ git checkout develop


コメントを以下の様に修正。

修正

そして、修正点を add して commit。


addしてcommit

~/project (develop)$ git add counter.py


~/project (develop)$ git commit -m 'コメントを修正'


3-5. push しようとしたらエラー (non fast forward)

ところが... リモートに push しようとしたらエラーになりました。


リモートに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

ロググラフを確認するとこうなっています。

ロググラフ

:woman_tone1:Bさんが先に push していたため、ロググラフが分岐してしまい、マージが出来ない状態でした。

:bust_in_silhouette:リーダーの指示で、'start.'、'end.' を表示する開発を行ったとのことです。

この場合、解消方法は2つあります。


  1. pull する。

  2. rebase する。

pull した場合、remotes/origin/develop ブランチをマージすることになり、ロググラフが荒れる基になります。

ロググラフ

詳細は、「Qiita - gitロググラフを綺麗にするちょっとした技【基本編】」を読んでください。

おすすめは rebase ですので、rebase する方向で話を進めます。


git rebase <ブランチ名>

git rebase」 コマンドで、履歴をキレイにします。1


rebaseする

~/project (develop)$ git rebase remotes/origin/develop


ロググラフを確認すると、分岐がなくなりました。

これで push 可能になりました。

ロググラフ

では 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 の一覧を確認して


stashを確認

~/project (feature/to-variable)$ git stash list

stash@{0}: On feature/to-variable: 引数化対応


git stash pop <対象stash番号>

git stash pop」 コマンドで、修正途中のコードを取り出します。


stashから取り出し

~/project (feature/to-variable)$ git stash pop stash@{0}


counter.py のコードが退避前に戻りました。

player1____qiita_player1__-_____counter_py__player1_.jpg

では、引数化対応をコミットします。


addしてcommit

~/project (feature/to-variable)$ git add counter.py


~/project (feature/to-variable)$ git commit -m '引数化対応'

この時点でのロググラフはこうなっています。

ロググラフ


3-6. 最新のdevelopを反映

先程と同じく、分岐してしまっているので rebase します。

...が...今回は counter.py で修正箇所が被ってしまうため、コンフリクトが発生します。


rebaseする

~/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 のコンフリクトを解消させます。

スクリーンショット_2018_06_06_15_20.jpg


git rebase --continue

コンフリクトを解消させたファイルを add し、「git rebase --continue」 コマンドで、rebase を終了させます。


rebase終了

~/project (feature/to-variable)$ git add counter.py


~/project (feature/to-variable)$ git rebase --continue
Applying: 引数化対応

これでロググラフがキレイになりました。

ロググラフ


3-7. 次に「1〜20表示」の開発

では、次にもう1つの開発。


:bust_in_silhouette: 現状の1〜10表示の後、1〜20も表示する。


これを行います。

スクリーンショット_2018_06_06_15_39.jpg

では、add して commit します。


addしてcommit

~/project (feature/to-variable)$ git add counter.py


~/project (feature/to-variable)$ git commit -m '1から20も出力する'

ロググラフはこうなりました。

ロググラフ


3-8. 1つ前のコミットの修正

コミットしてから気が付きました。

コメントをもうちょっと分かりやすくしておいた方がよかったなと。

コメントを修正します。

スクリーンショット_2018_06_06_15_54.jpg


git commit --amend

このままコミットすると、「1〜20対応」が2つのコミットに分かれてしまいます。

なるべく1つにしたいので、「git commit --amend」 コマンド 2 で、1つ前のコミットに含めてしまいます。

(2019/6/27:querykumaさんのコメントにより注釈追加)


addしてcommit--amend

~/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つ前のコミットの修正

:boy_tone1:A君は、今回行った開発を見直してみて、「引数対応に対して、引数のマイナス値チェックを入れた方がいいかも」と思いました。


git rebase -i

2つ以上前のコミットの場合、「git rebase -i」 コマンドを使います。

~/project (feature/to-variable)$ git rebase -i HEAD~2

すると、コミットの編集画面が表示されます。

今回は、「引数化対応」コミットに対して修正したいので、このコミットの「pick」を「edit」に書き換えます。

test__vim_.jpg

すると、rebase モード(引数化対応コミットした直後の状態)になりますので、マイナス値チェックを入れ込みます。

スクリーンショット_2018_06_06_16_19.jpg

そして、先程と同じくコミットは1つにしたいので、addして、「git commit --amend」 コマンドを実行します。


addして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 モードを終了します。


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. 開発が完了したのでリモートに反映


push

~/project (feature/to-variable)$ git push origin feature/to-variable


ロググラフ


3-11. masterまで反映

この後、:bust_in_silhouette:リーダーにより、「develop ブランチへのマージ」「master ブランチへのマージ」「開発ブランチの削除」が行われました。

ロググラフ

以上で、基本的な流れと基本的な git コマンドの使い方は網羅できたかなと思います!


補足:追跡ブランチについて

git branch -b コマンドでブランチを作成すると、追跡ブランチが設定されていない状態になります。

追跡ブランチは、git branch -vv コマンドで確認できます。

Book1.gif

追跡ブランチが無いと、git push コマンドで対象ブランチ名を省略した場合、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 できるようになります。





  1. 同じことが git pull --rebase でも行えます。(2019/7/1:Sheileさんのコメントにより加筆) 



  2. 使用例では、--reset-author オプションを付けています。これは、git commit --amend だけだとタイムスタンプが変わらないためです。もしタイムスタンプが重要ではない場合は、git commit --amend だけで充分です。 



  3. コミットがまとまるということは、履歴が変わるということです。つまり、既に push されていて、他の人が前のコミットから修正を行っていたとすると、その人は push できない状態になってしまいます。メンバーに迷惑をかけてしまうので、--amend は、push 前のみ使用するようにしてください。