20190927のGitに関する記事は6件です。

[Git] Gitのmergeでvimdiffを利用する

目的

pullしたときにコンフリクトが発生したときなどvimdiffを利用してコンフリクトを解消する

手順

1. 使いどころ

例えば以下のようにgit pullが失敗して手動でmergeする必要がでてきたときに利用します

$ git pull
~
Auto-merging aaa
CONFLICT (content): Merge conflict in aaa
Automatic merge failed; fix conflicts and then commit the result.

2. vimdiffをmergeツールとして利用するよう設定する

以下のようにglobalコンフィグに入れておく

$ git config --global merge.tool vimdiff
$ git config --global merge.conflictstyle diff3
$ git config --global mergetool.prompt false

入力せずともgit mergetoolの中でmerge toolに何を使うかきかれるのでvimiffとしておけばよい。

3. vimdiffを開く

$ git mergetool

このコマンドを入力すると以下のようなvimdiffの画面が開く

╔═══════╦══════╦════════╗
║ LOCAL ║ BASE ║ REMOTE ║
╠═══════╩══════╩════════╣
║                       ║
║        MERGED         ║
║                       ║
╚═══════════════════════╝
ウィンドウ 役割
LOCAL 現在のワーキングツリー上のファイル
BASE 共通の先祖ファイル。変更前はどのようなファイルだったか
REMOTE ワーキングツリーへマージしようとしているファイル
MERGED 最終的にレポジトリの中で保存されるファイル

4. vimdiffの使い方

ウィンドウ間の移動

各ウィンドウ間は [ctrl]+w → hjklで移動できる(h:left, j:down, k:up, l:right)。
※hjklの代わりにカーソルでもOK

マージ方法

MERGEDウィンドウに移るとコンフリクト部分が色付きで表示されている

<<<<<<<<から>>>>>>>>の間がコンフリクト箇所

<<<<<<< HEAD
localfile
||||||| merged common ancestors
ancestor
=======
remotefile
>>>>>>> ap8990as3eipsaldjff669197e1sdkfasasdf1131d

コンフリクト箇所にカーソルを合わせたら、LOCAL,BASE,REMOTEどれを採用するかきめて以下のコマンドで反映させる。

採用対象 反映コマンド
LOCAL :diffg L
BASE :diffg B
REMOTE :diffg R
その他 手動で編集
マージ処理の終了

編集が完了したらwindowを閉じる。
:xaで終了することで、MERGEDウィンドウの編集内容を保存しつつ全ウィンドウ一括で閉じることができる。
MERGEDウィンドウにカーソルがあたっているなら、:wで保存して、:qaで全ウィンドウ一括で閉じる。
(:wqaで一気に閉じることも可能だが、この場合LOCAL,BASE,REMOTEのファイルも全て保存され、レポジトリの中にuntrackedファイルが増えてしまう)

:xa
作業中に発生したuntrackedファイルの削除

vimdiffでのmerge処理完了後、コンフリクトが発生していたローカルファイルは拡張子.orig付きの別名で保存されている。
これらvimdiffにより保存されたバックアップファイルはレポジトリには不要なのでgit cleanを利用し削除する。
※誤って新規作成したファイルを一緒に削除してしまわないよう注意が必要

$ git clean -n
$ git clean -f

git cleanはstagedされていないファイルを削除する。-n はドライランオプション。
もし、特定のファイルだけ削除したい場合はファイルパスを指定する。

$ git clean -n %untracked_file_path%
$ git clean -f %untracked_file_path%
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Git/Gerritで始めてrestoreをした話

目的

  • Git/Gerritで始めてrestoreを実施した時の話をまとめる
  • restoreはコマンドで実行したわけではなくGerritのGUI上からボタンクリックで行った。そのときの話をまとめる。

※Gerritでの操作を前提とした話なのであまり汎用性のない話かもしれません。

経緯

  1. Gerritにてレビュー中のファイルの内容を更新したかった。
  2. レビュー中の内容を消したかった。
  3. 「コミットを取り消すならabandon」とバカの一つ覚えしていた。
  4. abandonを実行した。
  5. ファイル内容を修正して修正後のファイルを、修正前ファイルをpushした環境からリモートにpushした。
  6. 自動レビューシステム(社内構築)がエラーを返した。
  7. 修正後ファイルの親コミットがabandonしたコミットになっていて、ファイルの構成がおかしかったためエラーになった。
  8. abandonしたコミットの内容をrestoreボタンをクリックして元に戻した。
  9. restore後、復帰させたコミットが指す親コミットが2個前のコミットを指していた。
  10. 上司にレクチャーを受けながらGerritのRebaseボタンをクリックして親コミットを1個前のコミットに設定した。

経緯に対する私的や個人的メモ

  • 一度pushしたファイルの修正は$ git commit --amendを使用するようにする。
  • restoreはabandonされたコミットを復帰させるためのもの。
  • 親コミットが一つ前のコミットを指してない時はrebaseを使使う。

自分の良くなかった行動・よかった行動

良くなかった行動

  • $ git commit amendはコミットメッセージを修正するためのものと思い込んでいた。
  • 何かあったらabandonすれば良いと思っていた。
  • 自己判断でabandonした。

よかった行動

  • 基礎的なGitの知識がすこしあったため、解決が比較的スムーズだった。
  • 一歩踏み込んだ内容を質問することができた

    • 上司が教えてくれた内容がスムーズに理解できた。
    • 疑問に思ったことをより具体的に上司に質問できた。
  • 一度pushしたファイルを修正したい時の対処法が明確にわかった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

切り替え不要。複数のGithubアカウントの設定と楽な運用。

TLDR

  • git configのurlの設定を利用して、リポの中では自動的に目的のsshホストが使われるようにする。
  • ディレクトリ移動をフックにして、git cloneの前に利用するsshホストを自動的に切り替える。

Gist: https://gist.github.com/AngryMiddleAgedMan/2cde31a9c2ca4b1af44251c3ac628dec

問題

一つのマシン上で複数のGithubアカウントを使い分けている人がどのくらいいるのかわかりませんが、私は仕事とプライベートのGithubアカウントを分けています。
案件によっては求めに応じてその会社用のアカウントを作ったりします。

ただ、複数持っていても運用がつらくなるのは避けたいです。

  • アカウントの切り替えやリポの設定を楽にしたい。全部自動にしたい。
  • SSHの設定を覚えてなくてもいいようにしたい。
  • 間違えて仕事のリポにプライベートのアカウントでコミットしてしたり、プライベートのリポに仕事のアカウントでコミットしたりするのを防ぎたい。

ということで、

  1. 複数アカウントの設定のしかた
  2. 複数アカウントをどうやって楽に運用するか

を書きます。これによって、最初から最後まで(git clone前からpush後まで)アカウントの切り替えを手動でやる必要も、アカウントを意識する必要も無くなりました。ここまでやっている記事を見かけなかったので書いたのですが、密かにやっている人はたくさんいたり、Gitクライアントでこういうことができるものはあるかもしれない。

環境

macOS

sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.5
BuildVersion:   18F132

OpenSSH

ssh -V
OpenSSH_7.9p1, LibreSSL 2.7.3

Git

git --version
git version 2.23.0

仕事用のコードはworkディレクトリ配下、個人用のコードはprivateディレクトリ配下で管理しているとします。

設定のしかた

知っている方は飛ばしてください。

SSHキーを複数用意し、それぞれのGithubアカウントで公開鍵を設定

GithubではSSHキーを使い回せないのでアカウントの数分作ります。Githubじゃなくてもキーの使い回しは避けたほうがいいです。キーが流出しても被害が広がりにくいので。キーの作成方法がわからなければ、タイトルで煽ってるだけのお前らのSSH Keysの作り方は間違っている等を参考に。

~/.ssh/configの設定

Host github_p
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa_github_private
IdentitiesOnly yes
UseKeychain yes
AddKeysToAgent yes

Host github_w
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa_github_work
IdentitiesOnly yes
UseKeychain yes
AddKeysToAgent yes
  • Host 値が一意である必要があるので、それぞれ別の名前にします。
  • HostName, User ここはいつも通りgithub.comgitです。
  • IdentityFile 作成した秘密鍵のファイルパスです。適切に変更します。
  • IdentitiesOnly ssh-agentが複数のアイデンティティを保持しているときでも、ssh_configファイル内で明示的に設定されたアイデンティティのみを使用するかどうかを指定します。まさに今回のようなユースケースのためにある設定項目で、これを忘れると、github_pで接続したいのに、勝手に忖度してgithub_wで接続してしまう、みたいなことが起きます。
  • UseKeychain macOSのみ設定可能。パスワードやアカウント情報を管理するアプリであるKeychainにパスフレーズを保存するかどうか、そこから探すかどうかを指定します。macOSでない場合や、macOSでも古いバージョンのOpenSSHでは動かないらしいので、そんなオプションないって言われたらUseKeychainの行を消してください。
  • IgnoreUnknown ちなみに、そんなオプションないと言われるのを避けるためにこの設定の最初のほうに入れて、認識されないオプションを無視するという手もあるのですが、万が一、有効な設定なのにタイポしたために無視されるという危険もあるので、この設定は利用していません。
  • AddKeysToAgent キーとパスフレーズをssh-agentへ自動的に追加するかどうかを指定します。何度もssh-addするのは面倒くさいのでyesにします。この設定を使うのはやめようみたいな記事を見かけましたが、私はトラブったことはないです。その記事ではホントにこの設定が原因なのか検証されてなかったので、あまり気にしていません。

詳しくはman ssh_configを見てください。

どうやって運用するか

グローバルのユーザ情報を設定しない

git config --global --unset user.name
git config --global --unset user.email
~/.gitconfig
[user]
# nameが設定されていないことを確認
# emailが設定されていないことを確認
[url "github_p"]
# 件のSSHキーに関するURL短縮の設定がされていないことを確認
[url "github_w"]
# 件のSSHキーに関するURL短縮の設定がされていないことを確認

誤ったアカウントで操作したくないので、どのユーザ情報もデフォルトにはしないよう、globalにはユーザ情報は設定しません。git config --localのほうで適切に設定されない限り、git@github.comでアクセスできないようになります。安心。

ローカルで user.name, user.email, url..insteadOfの設定をする

cd 個人の/既存の/リポジトリの/パス
git config --local user.name "個人ユーザ名"
git config --local user.email "個人メールアドレス"
# url."ssh_configで設定したHostの値".insteadOf という形式にします
git config --local url."github_p".insteadOf "git@github.com"


cd 仕事の/既存の/リポジトリの/パス
git config --local user.name "仕事ユーザ名"
git config --local user.email "仕事メールアドレス"
# url."ssh_configで設定したHostの値".insteadOf という形式にします
git config --local url."github_w".insteadOf "git@github.com"
.git/config
[user]
  user = "個人ユーザ名"
  email = "個人メールアドレス"
[url "github_p"]
  insteadOf = "git@github.com"

すでにクローンされてローカルマシンにあるリポジトリについては、リポごとにユーザ情報を設定します。設定し直す必要があるリポジトリが多かったらスクリプト書いたほうがいいです。

ポイントはurlの設定です。

  • url.<base>.insteadOf gitコマンドを実行する際、insteadOfで指定した文字列で始まるURLについて、insteadOfで指定した文字列の部分が<base>で指定した文字列に書き換えられた状態で実行されます。 例えば、上記のgithub_pの設定をしてそのディレクトリ内でリモートとやり取りするgitコマンドを実行すると、 git@github.com:個人ユーザ/あるリポジトリ.git -> github_p:個人ユーザ/あるリポジトリ.git というふうに書き換えられて実行されます。git pushとか、明示的にURLを指定しないときもこの設定は有効なので便利です。git@github.com以外にも、任意の文字列を複数登録しておけます。
  • sshCommandという設定項目もあるのですが、SSHの設定は~/.sshにまとめたく、散り散りに管理したくないので使いません。

noreplyメールアドレスの利用(オプショナル)

ちなみに、これはオプショナルな設定ですが、色々な理由で個人のメールアドレスをさらしたくないケースがあると思います。その場合、登録したメールアドレスではなく、Githubから提供されるno-replyメールアドレスを利用することもできます。
Setting -> Emails -> Keep my email addresses privateをチェックするだけです。Primary email addressの説明のところにno-replyメールアドレスが表示されて、利用できるようになります。
参考: https://help.github.com/en/articles/setting-your-commit-email-address

cdをフックにしてgit cloneのSSH設定を自動で切り替える

もうひと押し。
上記のリポごとの.git/configの設定は一度設定してしまえば良いのですが、最初のgit cloneのときにはそもそもローカルにリポがないので設定できません。毎回クローン後にいちいち設定するのも面倒ですし、間違いの元です。

そこで以下のGistを作りました。Gist冒頭部のコメントの通りに設定して、rcファイル等に貼り付けてロードし直すと動きます。cdとgitコマンドを上書きしています。流用は自己責任で。
https://gist.github.com/AngryMiddleAgedMan/2cde31a9c2ca4b1af44251c3ac628dec

このGistが何をしてくれるかというと、
- git clone実行時のSSH設定の自動切り替え。workディレクトリ配下で実行した場合(直下ではなく、配下ならどこでもいい)はgithub_wprivateディレクトリ配下で実行した場合はgithub_p、それ以外のディレクトリでは切り替えなしでcloneするようになります。
- git clone後の、上記のuser.nameuser.emailurl.<base>.insteadOfの自動設定
- --force-urlオプションでgit clone時の自動切り替えOFF。
- GPGキーのキー生成とgitconfigへの自動設定(git clone実行前に、上記Gistの65行目をアンコメントし、別のGist https://gist.github.com/AngryMiddleAgedMan/74d4aefcd161410bf1487714b829a6ce~/.scripts/git-set-gpg-key.shに保存しておいた場合)

選択肢としてはgit hooksの利用もあったのですが、コードの置き場がバラバラになって見通しが悪くなるのと、git clone専用のフックは存在せず、post-checkoutのフックを利用する必要があるので、上記の道を選びました。

とりあえずはこれで困ってないですが、もっと良い手があるとか、上記gistが動かない等あったら教えてください。

GPGキーの設定(オプショナル)

これはオプショナルな設定ですが、自分のコミットであることの信頼性を高めるためにGPGキーを設定します。基本的に、リポごとではなく、アカウントごとに1つのGPGキーを設定してます。
git clone実行前に、上記Gistの65行目をアンコメントし、別のGist https://gist.github.com/AngryMiddleAgedMan/74d4aefcd161410bf1487714b829a6ce~/.scripts/git-set-gpg-key.shに保存しておくと、GPGキーの設定が走るようになります。
設定したいリポに行って、git-set-gpg-key.shを単体で実行することもできます。

GPGキー生成時に名前とメールアドレスを聞かれますが、Githubのアカウント名とメールアドレスを入れておきます。本名を聞かれますが本名である必要はありません。

ローカルの設定後、gpg --armor --export メールアドレスやGPGキーIDなどキーを特定できる文字列 | pbcopyでクリップボードにGPG公開鍵をコピーし、GithubのSettings -> SSH and GPG keys -> New GPG Keysで貼り付け保存で完了です。これでコミットするとGithub上でverifiedアイコンが表示されるのが確認できます。

まとめ

まとめると、以下2つの対策によって、複数のGithubアカウントを持っていても、ほとんどアカウントについて意識する必要がなくなりました。気にするのは、どのディレクトリの中でクローンするかだけです。

  • git configのurlの設定を利用して、リポの中では自動的に目的のsshホストが使われるようにする。
  • ディレクトリ移動をフックにして、git cloneに利用するsshホストを自動的に切り替える。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラマー1年目の自分が辿り着いたGitまとめ その4

概要

プログラマー1年目の自分が辿り着いたGitまとめ その1
プログラマー1年目の自分が辿り着いたGitまとめ その2
プログラマー1年目の自分が辿り着いたGitまとめ その3

の続きです。

最後は所謂チーム開発の環境である『複数人での開発』を想定していきます。
これまでとは違ってブランチという機能を使うことに注目します。

一旦振り返り

その1~3で紹介したコマンドで出来ることをまとめてみると

  • リモートリポジトリでソースコードを共有
  • 複数人で異なる箇所を開発
  • 各々が担当箇所についてコミットを重ねていき、必要に応じて別メンバーの変更を取り込む
  • 競合が発生した場合は、それぞれの意見に合わせて解決し、プッシュ

これだけでもかなり十分で、簡単な開発なら問題ないと思います。
ただ業務での開発となると、より効率を高めるための工夫が必要になってきます。

逆に現状での問題点、及び足りないものを挙げてみると……

  • 不具合が起きた時にデバッグが行いにくい(変更を巻き戻す必要がある)
  • バグのあるコードを共有してしまう可能性がある
  • 開発箇所を分けていることがわかりにくい

といった感じでしょうか。
これまでも何度か同じ例えをしてますが、バージョン管理をRPGのセーブ機能だとすると、今はセーブと巻き戻しは出来るけれど、肝心のセーブファイルが一つしか作れないという状況に思えます。

Gitにはブランチという機能があり、これを使うことで上記のような問題を解決し開発を円滑に進めることが出来ます。
Gitで最も大切な機能とも呼べる一方で、わかりにくい箇所の筆頭でもあるかと思われるので、ここが理解できると大分見通しが良くなります!(経験談)

checkout, branch

ブランチという機能に触れる前に、デバッグを行いやすくするcheckoutコマンドを紹介します。

checkout - 特定のコミットに移動する

checkoutコマンドはステージ前の変更を元に戻すのにも使われるらしいんですが
実際は専らこっちの用途に使われると思います(前者はIDEとかの機能で大体どうにでもなるので)
git checkout コミット番号
とすることで、そのコミット時点での内容にソースコードを切り替えてくれます。

前々回にはgit reset --softなどで重ねてきたコミットを一旦消すことで変更を巻き戻すという方法を紹介しましたが、このコマンドだと単純に『そのコミットに切り替える』という動作なので、コミットを消したり追加したりというような副作用はありません。

なので不具合が出た際などに、気になる場所に戻ってビルドし直し、その不具合が起きるかどうか検証……みたいなことが簡単に実行できるようになります。

detached HEADについて

このコマンドでコミットを移動すると『detached HEAD』状態になります。
参考: https://yu8mada.com/2018/05/31/detached-head-state-and-its-caution-in-git/

(コミットA)――(コミットB)――(コミットC)――(コミットD) ← master

       ↓ コミットBにcheckout

(コミットA)――(コミットB) ← detached HEAD

簡単に言うと過去のコミットに戻った上で、検証のために新たなコミットを積み重ねていくということが可能な状態です。

新たに行ったコミット達はmasterに戻った時に巻き戻されるため、このコマンドは不具合などが発生した場合に原因を調査し、修正を行う為の環境として役立てることが出来ます。

checkoutしたコミット部分から、自由に変更をコミットしていける
(コミットA)――(コミットB)――(コミットE)――(コミットF)――(コミットG) ← detached HEAD

        ↓ 再びcheckout master

detached HEAD状態のコミットは破棄される
(コミットA)――(コミットB)――(コミットC)――(コミットD) ← masterはこのまま

こうした一時的なコミットはデバッグに便利ですが、一時的であるというのがデメリットになってしまう場合もあります。

例えば上記の例で、コミットBにcheckoutし、幾つか修正のコミットを行ってみたところバグが無事解決したとします。
しかしコミットC及びDの変更を再び加えるためにmasterにcheckoutすると、バグを修正したコミットは一時的なものだったとして消えてしまう訳です。

上記のようにcheckoutした箇所から『派生させた』コミット群を、一時的なものではなくきちんと記録して残しておきたい……という場合の為に、ブランチという仕組みが存在しています。

branch - 現在のコミットに別名をつける(ブランチを作成する)

まずブランチとは何かというと、これは最新コミットの名前を表します。
これがどういうことかというと、Gitのリポジトリにデフォルトで存在しているmasterブランチを考えると分かりやすいです。

(コミットA)――(コミットB)――(コミットC)――(コミットD) ← master(コミットDを指している)
             ↑ その前のmaster

リポジトリの最初のコミットが、まずmasterという名前を持ちます。
そこからコミットを重ねる毎にmasterは最新コミットに移り変わっていきます。上記の例ではコミットDがmasterですが、その前はコミットCがmasterだったし、その前はコミットBがmasterだった、というイメージです。

ここで先程のdetached HEADの例に戻り、コミットBにcheckoutした場合を考えます。

(コミットA)――(コミットB)――(コミットC)――(コミットD) ← master

       ↓ checkout

(コミットA)――(コミットB)――(コミットE)――(コミットF)――(コミットG) ← detached HEAD

この状態でgit branch (ブランチ名)を実行することで、一時的なコミットに名前をつけ記録として残しておくことが出来ます。例えば今回の場合バグを修正したのでbugfixと名前をつけるとすれば、

git branch bugfix を実行

(コミットA)――(コミットB)――(コミットC)――(コミットD) ← master
             |
             ――(コミットE)――(コミットF)――(コミットG) ← bugfix

といった感じに最新コミットGに名前が付きます。
Gに続くコミットを行えば、今度はそのコミットがbugfixになります。そして一度ブランチを作れば、git checkout masterとしてもコミットは破棄されず、再びgit checkout bugfixとすれば戻ってこれる、という仕組みです。

checkout -bについて

既に修正内容がコミットされた状態からブランチを作る、という例を説明しましたが、どちらかというと逆のほうが多いです。
例えばバグ修正ではなく、新しい機能追加を行うと言った場合に、これまでの状態を一旦セーブしておいた上で別の機能開発に取り掛かるといったシチュエーションです。

先程ブランチは最新コミットの名前だと説明しましたが、branchコマンドはその最新コミットのエイリアスを作成するコマンドです。

(コミットA)――(コミットB)――(コミットC) ← master

この状態で、開発用のブランチとしてgit branch developと実行すると、

(コミットA)――(コミットB)――(コミットC) ← masterであり、develop

という状態が生まれます。コミットCはmasterという名前を持っていましたが、branchコマンドによってdevelopという別名をつけられた訳です。

ここでgit checkout developを実行し、ブランチを切り替えてコミットすることでdevelopが最新コミットに移動していきます。この時当然ながらmasterは変わらないため、これまでの状態を保ったまま新しい開発を進めていく、といったことが可能になります。

(コミットA)――(コミットB)――(コミットC) ← masterはこのまま
                       |
                       ――(コミットD)――(コミットE) ← develop

branchコマンドとcheckoutコマンドをそれぞれ実行するのは面倒なので、こういう場合は-bオプションを使います。

git checkout -b develop

とすることで、上記のブランチ作成&移動という部分を一度に行うことが出来るので、よく使うオプションです。

merge

bugfixブランチの例に戻ると、現状としては

(コミットA)――(コミットB)――(コミットC)――(コミットD) ← master
             |
             ――(コミットE)――(コミットF)――(コミットG) ← bugfix

というコミットグラフになっており、

  • masterブランチ → 機能的に最新状態だけど、バグが有る
  • bugfixブランチ → バグは治ったけど機能的に最新ではない

という状況です。
これを一つにまとめて『バグも直った最新版』を作るのがmergeコマンドです。

merge - ブランチを統合する

git checkout masterでmasterブランチに戻ります。
(この時git statusで自分が今どのブランチにいるか分かるので確認する癖をつけておくと良いと思います)

masterブランチにいる状態で、

git merge bugfix

を実行することで、bugfixでの変更を取り込みます。

masterブランチで git merge bugfix を実行

(コミットA)――(コミットB)――(コミットC)――(コミットD)――(マージコミット) ← masterはここに移動
             |                   |
             ――(コミットE)――(コミットF)――(コミットG)―― 
                       ↑ bugfixはここのまま

mergeを実行するとマージコミットという新たなコミットが作成されます。
このコミットはマージをしたという目印のようなものであり、内容としては二つのブランチの変更を全て取り込んだコミットです。

変更を取り込んだmasterブランチは最新コミットであるマージコミットに移動します。
一方、bugfixブランチの位置は変わりません。

bugfixブランチでmarge masterとやってしまうとこの関係が逆になってしまうので注意。
mergeコマンドは指定したブランチ『を』現在のブランチに取り込むコマンドです。

競合とfast-forwarded

上記の例ではコミットC,DとコミットE,F,Gは共にコミットBから連ねっている『並列なコミット』です。
また元々masterにあったバグを修正しているためコードに食い違いがあり、この為mergeの際に競合が発生します。

mergeの際に競合が発生するのはよくあることなので、前回同様競合マーカーを探して該当する箇所の競合を解決しましょう。今回の場合はbugfix側の変更を残すのが良さそうです。
修正が終わったらそれらを改めてコミットすることでマージが完了します。

ここで、masterブランチからdevelopブランチを切った例に戻ってみます。

(コミットA)――(コミットB)――(コミットC) ← master
                       |
                       ――(コミットD)――(コミットE) ← develop

この場合にmasterブランチでgit merge developを行うと、先程の例とは異なり並列になっているコミットは存在しないため競合は発生しそうにありません。
また、わざわざコミットD,Eの変更を統合したマージコミットを作る必要もなさそうです。

なのでこの場合、

masterブランチで git merge develop を実行

(コミットA)――(コミットB)――(コミットC)
                       |
                       ――(コミットD)――(コミットE) ← developもmasterもここになる

という風にmasterがdevelopの位置に直接移動します。マージコミットは作成されません。これをfast-forwardedなマージといい、上記のように並列なコミットが存在しないパターンで発生します。

因みにマージコミットを残したいという人のために、fast-forwardedになってしまう場合でもそれをキャンセル出来るオプションが存在します。

厳密に履歴を管理する場合はfast-forwardedを使わないことが推奨されますが、そこまで拘る必要はないかも知れません。ただマージコミットが作成されたりされなかったりで混乱しない為にも、知っておくべき仕様だと思います。

fetch

自分で作ったブランチ(ローカルブランチといいます)をローカルリポジトリ内でマージするという例を紹介しましたが、ブランチは勿論リモートにプッシュすることも可能です。

というより業務の場合普通はリモートにプッシュしたものをGitHub上でレビューしてもらい、承認を得た上でマージする、というのが基本的な流れになると思います(所謂プルリクエストというやつです)

ローカルブランチをプッシュする場合は、リモートにまだそのブランチが存在しないためgit pushだけではエラーが出ます。なのでgit push origin developのようにリモート及びブランチ名を指定してやることで、リモートへのブランチの作成とプッシュを同時に行ってくれます。

ではリモートに新しくプッシュされたブランチを取り込むとき、pullの場合は? というと、ここで大事なのがfetchコマンドと追跡ブランチです。

追跡ブランチ

追跡ブランチとは、ローカルリポジトリのブランチとは別に存在する『リモートリポジトリの状態を反映したブランチ』です。
上の方では割愛しましたがgit branchを実行すると、現在のローカルブランチの一覧が見られます。ここに-aオプションを加えてgit branch -aとすることで、ローカルに加えてリモートのブランチも参照することが出来ます。

git branch

master
* develop

git branch -a

master
* develop
origin/master
origin/develop

追跡ブランチは『origin/(ブランチ名)』といった名前で存在し(厳密にはリモート名とブランチ名ですが、大抵の場合はoriginです)、リモートブランチの変更を保持しています。
勿論、これらのブランチにcheckoutすることも可能です。

fetch - リモートの状態を追跡ブランチに反映する

ではこれらの追跡ブランチが常にリモートの内容と一致しているかというとそういう訳ではなく、git fetchを実行することで同期が行われます。

例えばリモートのdevelopブランチに何かしらの変更がプッシュされたとして、ローカルには未だ何も反映されません。
そこでgit fetchを実行することで、ローカルのorigin/developブランチにリモートの変更が反映されるという訳です。

同様に、リモートに新たなブランチ(例えばbugfix)が作成されたという場合にもgit fetchを行うことで対応する追跡ブランチを作成してくれます。

git branch -a

master
* develop
origin/master
origin/develop

git fetch
git branch -a

master
* develop
origin/master
origin/develop
origin/bugfix ← 新しく追加される

そしてこのブランチをローカルブランチとして作成する、という場合には

git checkout -b bugfix origin/bugfix

とすればOKです。checkout -bは第2引数を指定することでブランチの派生元を指定できます。
因みにgit fetchした時点で実は『リモートにはbugfixブランチが存在する』ということがGitには分かるので、単純にgit checkout bugfixとしてもブランチの作成が自動的に行えてしまったりします。

ただ間違ってもgit pull origin bugfixのように書かないようにしましょう。pullの紹介時にも書きましたが、ダイレクトにリモートのbugfixを現在のブランチに取り込んでしまうため履歴がめちゃくちゃになります。

pull = fetch + merge

上記の例でgit fetchを行うと、developブランチに変更があった場合origin/developブランチがリモートの状態に更新される、と書きました。

ここで重要なのはローカルのdevelopブランチにはまだ何の変更も行われていない、ということです。
developブランチにも変更を取り込むという場合は、developブランチでgit merge origin/developを行います。

この一連の動作は、developブランチにおいてgit pullを行った場合と一致しています。
なので実はgit pullはfetch + mergeです。

内部でmergeを行っているためにpullの際に競合が発生する場合がありますし、fetchを行っているので、一度pullを行ったブランチは追跡ブランチが作成されているという訳です。

リモートリポジトリにプッシュされた内容を試したいけれど、変更は取り込みたくないという慎重なシチュエーションでは、git fetchだけしておいて、追跡ブランチにcheckoutしてアプリをビルド、という風にすればローカルに変更を取り込まずにそのブランチを実行できます。

別の人がプッシュしたブランチの状態で実行やテストを行いたい、という場合にはfetchコマンドが有用です。その上で編集や新たにプッシュを行いたい、という場合にはローカルブランチにcheckoutする、というのが良いやり方だと思います。

まとめ

ブランチという大きな機能について一気に書いてしまったので、ちょっとまとまっていない部分もあるかもしれません。
個人的に大事だと思うのは、ブランチという名前からグラフのようなイメージがあるそれについて、実体を正しく把握することかと思います。

  • ブランチとは、最新のコミットに付く名前でありコミットが重なる毎に移動していく
  • ブランチを作るというのは、現在のコミットに別名をつけるということ

mergeやfetchよりも正直ここを理解することが、一番Gitを使いやすくするポイントだと感じました。

かなりざっくりとしたシリーズになりましたが、初学者の方の助けになれば幸いです!

前: プログラマー1年目の自分が辿り着いたGitまとめ その3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gitのforkはなぜoriginに対してupstreamと名付けるのか?

通常、forkの例としてfork元のリポジトリの名前をupstreamとつける説明をしている記事が多い。
ただ、なぜupstreamなんだ?という疑問が浮かびましたがその理由が書いていない(そういうものとして扱われている)
なので、なんでupstreamなのかを勝手に考えてみました。

upstreamは上流、上流の〜という意味をもつ

なので通常はオリジナルのリポジトリをoriginとしているが
forkするとフォーク先の自分のリポジトリをoriginとし、本家からみて上流という意味でfork元をupstreamとする
のではないだろうか?

と思っていたら、

https://help.github.com/ja/articles/configuring-a-remote-for-a-fork

githubの公式ドキュメントにフォークしたリポジトリに対して「上流リポジトリを指定します。」なる記載のある記事を見つけました。
このあたりから来ているのかなと、勝手に解釈しました。

大きくは間違っていない。はず。。

参考
http://kik.xii.jp/archives/179
https://blog.labot.jp/entry/2019/07/01/183204#i-want-to-set-a-remote-branch-as-the-upstream-for-a-local-branch

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Version管理のことはじめ

1.はじめに

システム開発を行う上で欠かすことのできない事柄の一つにVersion管理があります。
GitやSubversion、Mercurialといった様々なツールが存在し、開発におけるVersion管理をサポートしてくれています。
この「Version管理」という事柄について理解する、もしくは新人さんや初心者の方に対して説明する、ということが意外と大変である。という経験をした方は、意外に多いのではないでしょうか?

『変更を打ち消してしまっている。:sweat_drops:
『全然Commitしてくれない。:sweat_drops:
『個人の作業履歴の羅列になってしまっている。:sweat_drops:

ツールの使い方を「教わっただけ・教えただけ」では、Version管理が上手くいかないことも多いのではないかと思います。(体験談・失敗談)
そういった「教える側の初心者」「教わる側の初心者」の方たちに対して、『Version管理ってそもそも何だろう?』というところから理解を深めて、GitやSubversionなどのツールを使う前の『心構え』『準備』に本稿を役立てもらえればと幸いです。

2.本稿の対象者

  • :raised_hand:Version管理をしたことのない初心者や新人の方:raised_hand:
  • :raised_hand:GitやSubversionの指導を任せらせた指導者の方:raised_hand:
  • :raised_hand:Version管理くらいはやっておきたいと考える未経験の方:raised_hand:

3.Version管理って何?

まずは「Versionを管理する。」という行為はどういったものなのか?というところです。

バージョン管理システムの最も基本的な機能は、ファイルの作成日時、変更日時、変更点などの履歴を保管することである。

Wikipedia - バージョン管理システム -

 

あなたが(1個または1群の)ファイルに対して行った変更の履歴を、追跡管理するのかバージョン管理である。もっと規模の大きい、会社などの組織にとっても、バージョン管理は必要不可欠な業務だ。つまり、ソフトウェアの様々な版を追跡管理するのがバージョン管理である。

独習Git - Chapter2 Gitとバージョン管理の概要 ~ 2.1 バージョン管理の諸概念 ~ -

上2つの引用から言葉を借りて言うと「Versionを管理する。」という行為は、
ファイルおよびソフトウェアの変更履歴を追跡可能な状態で管理する。ということです。
この要件を満たすことだけを考えるのであれば、下のようなフォルダで変更履歴を追跡管理している場合も『Version管理』と言えます。
folder-vmgn.png
ただし、このようにフォルダ管理していくことはいろいろと不都合や手間が大きいので、実際にはGitやSubversion、Mercurialといったツールを使って、Version管理をしています。

これらのVersion管理ツールは主たる機能として、『変更履歴の追跡管理』を備えているため、簡単な設定とコマンドを叩くだけで、手作業でやっていた管理を何十倍にも簡略化して、Version管理を実施することができます。

4.Version管理はどうして必要か?

次にVersion管理のメリットから必要性を考えていきます。

:large_blue_circle: 保守性が高まる

Version管理を行うことで得られるメリットの1つは『保守性が高まる』という点です。すでにリリース済みのソフトウェアに対して、メンテナンスの必要性が発生した場合、Versionが判明していれば限定的にメンテナンスを行うことができます。

:large_blue_circle: 安定した機能(価値)提供

ソフトウェアにバグはつきものです。どれだけ完璧だと思っていても、人間が完璧ではない以上バグは生まれてしまいます。ある特定のアップデートでバグが発生してしまった際に、Versionが管理されていれば、 過去の動作していたVersionに戻すことができます。もしくは動作していたVersionを配信することができます。 仮にVersionを戻すことができなければ、ソフトウェアは本来の価値を失ってしまいます。ビジネスの場面では、大きな信頼損失に繋がってしまいます。

:large_blue_circle: 開発効率の向上

開発をしているとある特定のVersionに対して、「試験的に機能を追加を行う」ということが度々発生します。そういった場合にVersionが管理されていれば、現在機能を提供しているソフトウェアのVersionを保持したまま、次の「機能追加」「改修」にむけた開発を行うことができます。

5.Version管理の難しさ

4.Version管理はどうして必要か?で、『Version管理』の必要性も理解できて、「さぁ、Version管理をはじめよう:exclamation:」と思った方もいるかも知れません。(もしいるので、あれば本当にうれしいです!)
3.Version管理って何?で説明した行為を『Version管理』とするならば、「とりあえず、変更の履歴をフォルダで保存しておこう。」「Version管理ツールがいろいろとやってくれるなら、そっちを使おう。」など十人十色でいろいろな人がいるかと思います。確かに個人であればフォルダ管理でもよいでしょう。GitやSubversionを使ったとしても、履歴を保存するということのみに着目すれば、覚えるコマンドも3~4つで済んでしまうことでしょう。
しかし、ここで『Version管理なんて余裕だ!』と学習の手を止めてしまうのは時期尚早です。チーム開発になった瞬間に状況が一変してしまうかも知れません。Version管理の難しさは個人では分からない部分が多くあります。まだ、チーム開発をしたことがない人はこれからの『心構え』として。今個人でVersion管理をしていて、これからチーム開発をしていくという方は『振り返りの機会』として、これから紹介する「難しさ」に目を向けてみてください。

:red_circle: 特定多数・不特定多数に対する配慮

個人開発とチーム開発の最も異なる点は、言わずもがな自分以外の『他人』がいるという点です。
1人で開発している場合はフォルダやファイル毎でVersionを管理していても、恐らく編集者はあなた自身だけなので、比較的容易でかつ、ある程度の信頼性がある管理ができます。
しかし、1人,2人と編集者が増えると複数のファイルを各々で編集するようになるため、各ファイルの最新の状態が分からなくなったり、同一ファイルに対する変更が競合するなどの問題が生じやすくなってしまいます。Version管理ツールはそれらの問題を解決する仕組みや機能を提供してくれています。
(※じゃあ、Version管理ツールを使えば問題なし!かというと必ずしもそうではありません。何事にも限度はあります。)

:red_circle: 使用ツールに対する知識・経験不足

ツールを使うことで『Version管理』を簡単かつ安全に行う環境を整えることができます。しかし、ツールを使えば全てが解決するわけではありません。
『Version管理』を行う中で生まれてきた多くのニーズに対応するため、Version管理ツールは様々な機能や特性を備えています。『Version管理』をするための詳細な部分はツールが肩代わりしてくれますが、それをツールに指示するのはあくまで人です。どれだけツールが素晴らしくても、それを人が状況に応じて適切に扱えなければ意味がありません。また、いくらコマンドが簡単だからといっても、『Aの状況ではこのコマンドでいいけど、Bの状況ではコマンド』など、コマンドの使い方は状況に応じて左右される場合があります。
個人で使っているうちは『履歴の保存』程度の意味に覚えていたコマンドも、それを使うためのシチュエーションを意識する必要出てきてしまいます。

:red_circle: 履歴管理に対する個々人の認識齟齬

『変更履歴を追跡可能な状態で管理する』ことがVersion管理といいましたが、そもそも『変更履歴』に対する認識が個々人で違うと『管理し辛さ』を感じてしまいます。ツールを使うことで履歴に関する事柄(『変更日時』『変更内容』、場合によっては『変更理由』など)に関しては比較的容易に統一・管理することができます。
しかし、『どれくらい変更内容を1つの履歴とするか?』(Gitに例えると1コミットの粒度)など、個々人に裁量がある部分は意外と多く残っています。個々人の認識が極端に違う場合には、管理が難しくなってしまいます

:red_circle: プロジェクトの特性や運用から生じる複雑さ

個々人の裁量以外にも『Version管理』に大きな影響を与えるものとして、『プロジェクトの特性』や『運用』が挙げられます。

複数の会社で開発請け負ってしまっているため、会社毎にリポジトリが存在していて、共有リポジトリにPushしようとすると競合してしまう。

これは一例ですが、このように自分たちではどうにもしようがない問題も発生することがあります。

運用部の人達はVersion管理ツールの使い方が良く分からないんで、リリース対象物は別途フォルダでVersion管理してください。

開発チームと運用チームが違う場合には、こういった問題が発生するかもしれません。
このような問題を解決していくためには、『自分たちのチーム』という集団の垣根を越えて、他の集団に対して働きかけが必要になります。

:red_circle: コミュニケーション不足

チーム開発において、コミュニケーションは最も大切な要素と言っても過言ではありません。
『Version管理』を安全かつ、円滑に行っていくために、ツールを導入しました。ルールも明文化しました。リポジトリもチームで共有できています。

これで問題なし:exclamation::exclamation:開発に没頭できる:exclamation:

確かに開発に集中できる、効率的な環境は整っていることでしょう。
しかし、自分勝手に開発を進めたり、相手の言葉を無視しては折角のツール、ルールが無駄になってしまいます。

Slackに書いたので、読んだと思っていました。このVersionではなくで、別のVersionにマージして欲しかったです。

コミュニケーションの問題はほんの少しの気遣い少ない声かけで解決できる問題も多くあります。

明確なVersionを指定せずに分かったつもりで話をしていたが、自分が想定していたVersionとは違うものに改修が加わっていた。

言葉だけではお互いに分かったつもりになってしまうこともあります。
文字だけ。言葉だけ。に偏るのではなく、文字と言葉を適切に使うことで、円滑なコミュニケーションは生まれていきます。

6.さいごに

最後までお読みいただきありがとうございました。
「Version管理」をはじめる前の『心構え』『準備』が少しでもできましたでしょうか?
これからVersion管理をはじめていく方の、一助になれば幸いです。

7.参考・引用資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む