【 git push 】コマンド(応用編)――リジェクトやコンフリクトが起きたときどうするかLinux基本コマンドTips(398)

本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回はGitのローカルリポジトリの内容をリモートリポジトリに送信する「git push」コマンドです。リジェクトやコンフリクトが起きたときどのように解消するのかを解説します。

» 2020年05月22日 05時00分 公開
[西村めぐみ@IT]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「Linux基本コマンドTips」のインデックス

Linux基本コマンドTips一覧

 本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回はGitのローカルリポジトリの内容をリモートリポジトリに送信する「git push」コマンドです。前回の基礎編に続き、今回は応用編として、リジェクトやコンフリクトが起きたときどのように解消するのかを解説します。

git/git pushコマンドとは?

 「git」は「Git」という分散型バージョン管理システム用のコマンドです。Gitは元々Linuxカーネルのソースコードを管理するために作られた「バージョン管理システム」で、現在は多くのソフトウェアやWebサイトのソースコード、ドキュメントの管理などに用いられています。

 ソースコードを管理する際、最新版だけを保存するやり方はうまくいきません。開発中のさまざまなタイミングで状態を管理し、必要に応じて比較、参照したり、元に戻したりできるようにする仕組みが「バージョン管理システム」です。

 Gitでは、テスト版など複数に枝分かれした状態も管理できます。複数のメンバーによる開発を前提としており、開発中の各時点におけるコメントや、コメントへの返信なども管理できるようになっています。

gitのサブコマンドとGitの仕組み

 gitコマンドはほとんどの場合、「サブコマンド」と組み合わせて利用します(本連載ではgitとサブコマンドの組み合わせをコマンドとして紹介します)。

 今回紹介する「git push」はローカルリポジトリの内容をリモートリポジトリに送信(アップロード)するコマンドです。

 gitコマンドでは「リポジトリ(repository)」を使ってバージョンを管理します。リポジトリにはソースコードや変更履歴、コメントなどを一括して保管します。リポジトリには、自分のPC上に作る「ローカルリポジトリ」と、「GitHub」などのWebサービス上に作る「リモートリポジトリ」があり、両者を連携させることで複数の開発者による開発を1本にまとめることができます。

 また、ローカルリポジトリのみで運用することも可能です。そのような運用をしているリポジトリにリモートリポジトリを追加したり、逆に、リモートリポジトリを削除したりするには、「git remote」コマンドを使用します。「git remote」コマンドには、さらに「add」や「remove」などのサブコマンドがあります。

 既存のリポジトリ(リモートリポジトリ)にあるソースコードなどを入手したい場合は、まず、「git clone」(連載第381回)でリポジトリを自分の環境に複製します(※1)。リモートリポジトリの内容がバージョンアップされたら「git pull」(連載第382回)で最新版を取得します。開発に参加するのではなく、単に最新版を取得したいという場合は、「git clone」と「git pull」を利用すればよいでしょう。

※1 特定のファイルだけが欲しい場合、例えばGitHub(github.com)にあるリポジトリであれば「Raw」というボタンで表示されるURLを使い、「wget」コマンドなどを使ってダウンロードできる。この他、プロジェクト全体をダウンロードするためのリンクも用意されている([Clone or download]ボタン→「Download ZIP」)。



 保管場所であるリポジトリに対し、ファイルの編集などを行う場所を「ワークツリー」「ワーキングエリア」「作業ツリー」などと呼びます。「git clone」や「git pull」で取得した最新版のファイルはワークツリーに配置されます。つまり「作業ディレクトリ」です。

 ワークツリーで編集した結果をリポジトリに反映する操作を「コミット」と呼びます。「git add」(連載第384回)コマンドでコミットしたいファイルを「インデックス」あるいは「ステージングエリア」と呼ばれる領域に追加します。インデックスにはファイルの変更箇所などが記録されます。

 インデックスの内容は「git commit」コマンドでローカルリポジトリにコミットされ、「git push」コマンドでローカルリポジトリの内容をリモートリポジトリに反映します。従って、「git add」や「git commit」などを行わなければ、自分の環境で編集した内容がリポジトリに影響を与えることはありません。自由に編集し、テストできます。なお、ワークツリーのファイルを過去の任意のコミット状態に戻すことも可能です。

 Gitには、この他、開発中のソースコードやドキュメントを、「テスト版」「○○版」……のように枝分かれさせたり、それらを合流させたりする機能もあります。枝分かれしたそれぞれのバージョンを「ブランチ」(branch)と呼び、ブランチを合流させることを「マージ」(merge)と呼びます。

 コミットには「タグ」と呼ばれる名前を付けることができます。その際には「git tag」コマンドを使います。



コマンドの書式

git [オプション] サブコマンド [サブコマンドごとのオプションや引数]

git push [オプション] リモート名

git push add [オプション] リモート名 ブランチ名

git push [オプション] リモート名 ブランチ:リモートのブランチ

git remote サブコマンド 対象

※ [ ]は省略可能な引数を示しています。




gitの主なオプション

短いオプション 長いオプション 意味
-C パス カレントディレクトリではなく指定したディレクトリで実行したものとする
--bare リポジトリを「bareリポジトリ」(ワーキングツリーが存在しない、管理だけを目的としたリポジトリ)として扱う
-c 設定=値 設定値を指定する(設定は「git config」で確認可能)
-p --paginate 全ての出力を「less」コマンドまたは環境変数PAGERで指定されたコマンドで表示する
-P --no-pager 「less」コマンドで表示しない(「-p」の指定を打ち消す)
--exec-path=パス gitの実行ファイルのパスを指定する(「--exec-path」のみの場合、実行ファイルのパスを表示する)
--html-path gitのHTML形式のドキュメントがインストールされたパスを表示する
--man-path gitのmanファイルのパスを表示する
--info-path gitのinfoファイルのパスを表示する

gitのサブコマンド

コマンド 実行内容
clone リポジトリのクローンを作成する
init リポジトリを新規作成する、または既存のリポジトリを初期化する
remote リモートリポジトリを関連付けする
fetch リモートリポジトリの内容を取得する
pull リモートリポジトリの内容を取得し、現在のブランチに取り込む(「fetch」と「merge」を行う)
push ローカルリポジトリの変更内容をリモートリポジトリに送信する
add ファイルをインデックスに追加する(コミットの対象にする)
rm ファイルをインデックスから削除する
mv ファイルやディレクトリの名前を変更する
reset ファイルをインデックスから削除し、特定のコミットの状態まで戻す
status ワークツリーにあるファイルの状態を表示する
show ファイルの内容やコミットの差分などを表示する
diff コミット同士やコミットとワークツリーの内容を比較する
commit インデックスに追加した変更をリポジトリに記録する
tag コミットにタグを付ける、削除する、一覧表示する
log コミット時のログを表示する
grep リポジトリで管理されているファイルをパターン検索する
branch ブランチを作成、削除、一覧表示する
checkout ワークツリーを異なるブランチに切り替える
merge 他のブランチやコミットの内容を現在のブランチに取り込む
rebase コミットを再適用する(ブランチの分岐点を変更したり、コミットの順番を入れ替えたりできる)
config 現在の設定を取得、変更する

git pushの主なオプション

短いオプション 長いオプション 意味
--all 全てのブランチを対象とする
--prune ローカルで削除したブランチをリモートからも削除する
-u --set-upstream 今回対象とするブランチを上流ブランチとして設定する(一度指定すると次回以降も対象となる)
--tags 全てのタグを対象とする
--mirror ローカルの内容をリモートにそのまま複製する
-d --delete 指定したブランチをリモートから削除する(「:」のみを指定すると全て削除する)
--atomic 実行途中でブランチの変更に失敗したら、実行前の状態に戻す(サーバが対応している場合のみ)
-f --force リモートブランチがローカルブランチの派生元ではない場合も、ローカルブランチの内容で強制的に上書きする
--signed=設定 GPG(GNU Privacy Guard)署名付きで実行するかどうかを、「true」(常に署名付き)、「false」(常に署名なし)、「if-asked」(サーバ側が署名付きに対応していた場合だけ署名付き)で指定する
--signed GPG署名を付けて実行する(「--signed=true」相当)
--no-signed GPG署名を付けないで実行する(「--signed=false」相当)
-v --verbose 実行時のメッセージを増やす
-q --quite 実行時のメッセージを減らす
-n --dry-run 実行せずに実行する内容だけを表示する


git pushでリジェクトが起こった場合にどうするか

 複数人がそれぞれのローカルリポジトリでコミット後、「git push」を実行した場合、受け付けられないことがあります。

 このようなことが起きる状況は大きく分けて2つあります。異なるファイルに変更を加えた場合(リジェクトされる)と同一のファイルに変更を加えた場合(リジェクトに加えてコンフリクトが生じる)です。

 最初にリジェクトだけが起きた場合の対応を2種類説明します。「いったん『git pull』(git fetchとgit merge)を実行してから再度『git push』を試みる」という方法と「『git rebase』を実行してから再度『git push』を試みる」という方法です。いずれもマージを実行して問題を解決します。

 チームで開発している場合、リジェクト後の対処などに一定のルールが設けられていることがあります。まずはそちらを確認するようにしてください。

 リジェクトが起きた例を画面1に示しました。

 最初に「git log --oneline」と「git status」で状況を確認しています。最後のコミットがまだリモートリポジトリに反映されていない様子が分かります。

 ここでリモートリポジトリに反映するため「git push」を実行すると、「! [rejected]」(リジェクトされた)というメッセージが表示されて、git pushが失敗しました。

画面1 画面1 リモートリポジトリへの送信操作がリジェクトされたところ

git pull(git fetchとgit merge)を実行して、git pushを試みる

 複数人がそれぞれ異なるファイルを編集していた場合など、現在のリモートリポジトリにローカルの変更をそのまま反映できる場合は、まず、「git pull」を実行するか、「git fetch」と「git merge」を順に実行します(※2)。その後、再度「git push」を試みます。

※2 「git pull」は、「git fetch」と「git merge」を実行するコマンド(連載第382回)。



 画面2は、画面1の続きとして、「git fetch」と「git merge origin/master」を実行してから、「git push」を実行しています。確認のため、実行後にコミットログを一覧表示しています。

画面2 画面2 コンフリクトを解消する方法(1) 「git fetch」と「git merge」の後に、「git push」を実行した

 画面2の「git merge」では、途中でエディタ(デフォルトではvi)が開いています。デフォルトでマージした旨を記録する文面が入っているため、適宜修正後、メッセージを保存します(画面3)。画面3ではデフォルトのメッセージのまま保存しています。

画面3 画面3 viの編集画面

git fetchとgit rebaseを実行してから再度git pushを試みる

 コンフリクトを解決するもう一つの方法は、「git fetch」でリモートリポジトリのコミットを取り込み、「git rebase」を実行してから、「git push」を試みることです。

 この場合、リモートリポジトリから取得した状態に対して、ローカルリポジトリでコミットを行った、という扱いになります。このような操作を「リベース」と呼びます。

 画面4では、画面1の続きを示しています。「git fetch」と「git rebase origin/master」を実行してから、「git push」を実行しています。コミットログの順番が先ほどと異なりますが、最終的な結果は同じになります。

画面4 画面4 コンフリクトを解消する方法(2) 「git fetch」と「git rebase」の後に、「git push」を実行した


git mergeでコンフリクトが起こった場合にどうするか

 同じファイルに対して複数人が変更を加えた場合も画面1のように「! [rejected]」と表示されます。ところが画面4のように問題を解決しようとすると、「git merge」の実行後に、「CONFLICT」(コンフリクト、衝突)というメッセージが表示されます。コンフリクトを解決するにはもう一手間が必要です。

 まず同じファイルに対して複数人が変更を加えた状況を画面5で再現しました。ワークツリーの「hello.txt」というファイルに変更を加えて、「git commit」と「git push」を続けて実行すると「git push」で「! [rejected]」というメッセージが表示されてエラーになりました。

画面5 画面5 リモートリポジトリへの送信操作がリジェクトされたところ

 画面6では先ほどと同じようにコンフリクトを解消するため、「git fetch」でリモートリポジトリの内容を取得し、「git merge origin/master」でマージを試みています。

 ところが、今回の場合は「CONFLICT」というメッセージが表示されました。「hello.txt」でコンフリクトが発生していることが分かります。リモートリポジトリ側で別の個人(Penguin)が「hello.txt」にコミットを実行していたからです。

 「hello.txt」の内容を確認すると、ローカルリポジトリ側の最新の状態(HEAD)では、「hello from Study」という行が追加されているのに対し、リモートリポジトリ側(origin/master)では「hello from Penguin2」という行が追加されていました。

 そこで、エディタ(vi)で、「hello.txt」を編集し、今回はどちらの行も残しました。実際のソフトウェア開発ではどちらの編集を残すのか、それとも組み合わせるのか、もう少し複雑な判断が必要になります。

画面6 画面6 コンフリクト発生後、問題のファイルを編集してコンフリクトを解消したところ

 画面7ではあらためて「hello.txt」をコミットし、「git push」を無事に実行できました。

画面7 画面7 コンフリクトを起こしていたファイルを編集後に送信したところ


筆者紹介

西村 めぐみ(にしむら めぐみ)

元々はDOSユーザー。ソフトハウスに勤務し生産管理のパッケージソフトウェアの開発およびサポート業務を担当、その後ライターになる。著書に『図解でわかるLinux』『らぶらぶLinuxシリーズ』『[新版 zsh&bash対応] macOSコマンド入門』『Accessではじめるデータベース超入門[改訂2版]』など。地方自治体の在宅就業支援事業にてMicrosoft Officeの教材作成およびeラーニング指導を担当。会社等の"PCヘルパー"やピンポイント研修なども行っている。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。