2017年08月03日

git コンフリクトに対処する

ブランチをマージした。
git_conflict01.png
$ git merge new_branch 
Auto-merging mystyle.css
CONFLICT (content): Merge conflict in mystyle.css
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
2つのファイルでコンフリクトが発生した。

コンフリクトした"index.html"ファイルの中身を見てみる。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>git確認</title>
</head>
<body>
<p>master branch</p>
<<<<<<< HEAD
<p>modified master</p>
=======
<p>new_branch</p>
>>>>>>> new_branch
</body>
</html>
gitでは現ブランチの内容と、マージするブランチの内容とでどちらを採用したらいいか判断がつかないときに、この操作を行った人にマージ対応を求めている状態がコンフリクト。


このときコンフリクトしたファイルの中には、コンフリクトした箇所がマーカーで示されている。
<<<<<<< HEAD
"現ブランチのコンフリクト部分"
=======
"マージするブランチのコンフリクト部分"
>>>>>>> マージするブランチ名

このマーカーで示された箇所を処理してあげないとコンフリクトは解消されない。
エディタを開き、このマーカー部分を修正して、再度コミットすればいい。




diffツール


GUIが使えるのであれば、コンフリクトを解消するときにGUIツールで作業したほうが直感的に作業できて便利。meld, KDiff3などいくつかツールがあるが、ここでは P4Merge というツールがあるので使ってみる。



インストール


ここからダウンロードする。

[DOWNLOAD] をクリックしする。
git_conflict02.jpg

OSを選んでダウンロードする。
git_conflict03.jpg

ユーザ登録の画面が表示される。登録しない場合はskipすることもできる。
git_conflict04.jpg

"p4v.tgz" ファイルがダウンロードされる。


ダウンロードが完了したら、ダウンロードしたファイルのディレクトリで展開する。
$ tar -xzvf p4v.tgz

展開されたディレクトリを移動し、シンボリックリンクを設定する。
$ sudo mv p4v-2017.2.1535556/ /opt/
sudo ln -s /opt/p4v-2017.2.1535556/bin/p4merge /usr/local/bin/p4merge




mergetoolに設定


configファイルに設定を行う。

$ git config --global merge.tool p4merge


マージツールを使う際のオプションも設定しておく。

$ git config --global mergetool.p4merge.cmd 'p4merge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"'
$ git config --global mergetool.p4merge.trustExitCode false
$git config --global mergetool.p4merge.keepTemporaries false
$ git config --global mergetool.keepBackup false


"cmd" :p4mergeを実行する方法を指定
$BASE共通箇所の内容を保持する一時ファイルのパス
$LOCAL現ブランチの内容を保持する一時ファイルのパス
$REMOTEマージするブランチの内容を保持する一時ファイルのパス
$MERGEDマージ結果を書き出すファイル(コンフリクトしたファイル)

"trustExitCode" :p4mergeの終了コードでマージが成功したかどうかを判断できるのかどうか→"false" できないとした。
"keepTemporaries" :マージ作業の一時ファイルを残すのかどうか→"false" は自動的に削除される。
"keepBackup " :マージが完了したときに、バックアップファイル(*.orig)が残される→"false" は自動的に削除される。

"~/.gitconfig" ファイルに直接に記述してもいい。
.....

[merge]
tool = p4merge
[mergetool "p4merge"]
cmd = p4merge \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"
keepBackup = false
trustExitCode = false
keepTemporaries = false
[mergetool]
keepBackup = false




mergetoolを起動


コンフリクトした後、マージツールを起動する。

$ git mergertool


gitは "config" に設定された "merge.tool" を探し、起動する。
git_conflict05.jpg
"p4Merge" は4ペイン表示。
上部3つが左側から、"$LOCAL" "$BASE" "$REMOTE" と並び、下部が "$MERGED" となる。


下部のペインの左側にマーカーがあり、上部のペインにも同じマーカーが付けてある。
同じマーカーで "$LOCAL" "$BASE" "$REMOTE" を示しており、下部のマーカーをクリックすることでどの内容をマージで採用するかを決定できる。
複数の内容を有効としたければ、Shift + クリック することで複数採用することが可能。
git_conflict06.jpg

マージが完了すれば、保存終了する。
複数のコンフリクトがある場合、終了したら次のファイルのマージ処理が起動してくる。

2つのファイルのコンフリクトがあったので、2回ツールが起動し、マージ処理を行った。
$ git mergetool 
Merging:
index.html
mystyle.css

Normal merge conflict for 'index.html':
{local}: modified file
{remote}: modified file
QString::arg: 1 argument(s) missing in %1 - %2

Normal merge conflict for 'mystyle.css':
{local}: modified file
{remote}: modified file
QString::arg: 1 argument(s) missing in %1 - %2

"QString::arg: 1 argument(s) missing in %1 - %2" 良くわからないエラーがあるが、ツール起動に影響はないので放置している。。。

すべてマージが完了すれば、ステージして、コミットする。




まとめ


マージツールを使うと直感的に採用したい変更が決定しマージできるので、GUIを使っているのであればコンフリクトの解消にはぜひに使ったほうがいい。同じファイルに複数のコンフリクトがある場合や、ときにはgit側で共通部分の判別がつかず、大きなブロックで差分がでてしまうこともある。このような場合には、ツールで全体を見ながら修正できたほうが安心できる。






gitをインストール
gitサーバーのセットアップ
git ブランチについて
git 変更を一時的に退避 stash
git ブランチを合流するマージ
git ブランチを付け替える
git コミット履歴を変更する
git 変更をリセットする
git リモートでの操作
git ファイルの追跡
git リリース準備
git リモートブランチを追加
git チェックアウトをもっと便利に使う
git プロジェクトの構成
git 変更をpatchファイルにする
git 失敗したときの復元
posted by Zorinos at 20:00| Comment(0) | Linux | 更新情報をチェックする

2017年08月02日

git 変更をpatchファイルにする

変更差分をpatchファイルにすることができる。

事前知識として、Linuxの patch はテキストファイルに差分を適用するコマンド。
パッチファイルを作成するコマンドは以下。

$ diff -u 変更前のファイル 変更後のファイル > パッチファイル名


-u オプションは unified diff形式で出力する指定。

例として、パッチファイルを作成して、そのパッチファイルの中身を見てみる。
$ diff -u a_old.txt a_new.txt > a.patch

$ cat a.patch
--- a_old.txt 2017-08-01 09:00:00.570393470 +0900
+++ a_new.txt 2017-08-01 09:30:00.841552217 +0900
@@ -1 +1,3 @@
aaa
+
+modified 1


パッチを適用するコマンドは以下。

$ patch -u 適用するファイル名 < パッチファイル名



パッチ適用前後を比較しながら、適用内容を確認してみる。
$ cat a.txt
aaa

$ patch a.txt < a.patch
patching file a.txt

$ cat a.txt
aaa

modified 1

変更差分のパッチが適用されたことがわかる。


もし適用するファイルを間違えていたら、パッチ適用を戻す(リバースパッチ)。
-R オプションを使うことで、新旧ファイルが反転しているものとして適用することで元に戻す。

$ patch -u -R 適用するファイル名 < パッチファイル名

$ patch -R a.txt < a.patch
patching file a.txt

$ cat a.txt
aaa

リバースパッチをあてることで元に戻った。





パッチファイルの作成


git diff の出力をパッチファイルとして保存すればいい。
例えば、特定ファイルの異なるコミットの差分のパッチファイルを作る場合、

$ git diff commitID1 commitID2 -- filepath > file.patch

試しに作成してみる。
$ git log --oneline
827142f modified2 a.txt b.txt
e379d74 modified a.txt b.txt
6c5004e modified a.txt d.txt
069d76c Add file :d.txt
4bfcd75 modified c.txt
3f062b7 modified b.txt
9925985 modified a.txt
d79bc0b Add files

$ git diff 069d76c 6c5004e -- p1/p2/d.txt
diff --git a/p1/p2/d.txt b/p1/p2/d.txt
index 1d60b70..c8b35f9 100644
--- a/p1/p2/d.txt
+++ b/p1/p2/d.txt

@@ -1 +1,3 @@
ddd
+
+patch 1:d.txt


$ git diff 069d76c 6c5004e -- p1/p2/d.txt > ~/Documents/test.patch

$ cat ~/Documents/test.patch
diff --git a/p1/p2/d.txt b/p1/p2/d.txt
index 1d60b70..c8b35f9 100644
--- a/p1/p2/d.txt
+++ b/p1/p2/d.txt
@@ -1 +1,3 @@
ddd
+
+patch 1:d.txt






パッチファイルの適用


作成したパッチファイルを適用するには git apply を使う。

$ git apply file.patch


git apply は全部適用するか、または一切適用しないかのどちらか。エラーが出るまで適用するとか中途半端なことはしない。
また、適用できるかあらかじめチェックすることができる。git apply --check を使い、適用できなければエラーがでる。適用可能であれば何も出力されない。
$ git apply --check test.patch
error: patch failed: p1/p2/d.txt:1
error: p1/p2/d.txt: patch does not apply

適用したら、ステージしてコミットすれば良い。



git操作で作成したパッチファイルは、Linuxコマンドの patch コマンドと互換性を持ち、git管理されていないファイルに対しても適用できる。

$ patch -p1 < test.patch

-p オプションは差分に記載されたファイル名から指定した個数分のパス指定を取り除く指定。"-p1" なので1個除く。
差分ファイルのパスの指定には、"a" "b" がついているのでこれを除くことで、patch コマンドと互換性が取れる。
--- a/p1/p2/d.txt
+++ b/p1/p2/d.txt






まとめ


パッチを使うことで、ファイル丸ごとの置き換えはしたくない差分対応とか、離れた環境にあるリポジトリへの適用や、git管理下にないファイルに変更差分を適用したい場合に使える。
変更差分は git diff の操作なので難しいことではない。
ちょっとした変更差分でも、手動でファイルに変化点を加えていくとヒューマンエラーが介入する可能性があり神経を使うので、自動的に作業できることは自動化してしまうほうが時間もかからないし、間違いが少ない。





gitをインストール
gitサーバーのセットアップ
git ブランチについて
git 変更を一時的に退避 stash
git ブランチを合流するマージ
git ブランチを付け替える
git コミット履歴を変更する
git 変更をリセットする
git リモートでの操作
git ファイルの追跡
git リリース準備
git リモートブランチを追加
git チェックアウトをもっと便利に使う
git プロジェクトの構成
git コンフリクトに対処する
git 失敗したときの復元
posted by Zorinos at 20:00| Comment(0) | Linux | 更新情報をチェックする

2017年08月01日

git プロジェクトの構成

複数のプロジェクトの管理について整理する。
1つのリポジトリに、1つのプロジェクト(リリースモジュールが1つ)を格納するのはシンプルな形。リモートリポジトリを準備して、チームでローカルリポジトリを持つ構成だったとしても簡単に想像できる。
git_module01.png


1つのプロジェクトに複数のリリースモジュールがあり、それぞれを別チームで開発しているとする場合はどううしたら良いか。

1つのプロジェクトではあるが、リリースモジュールが相互に関連しておらず、参照する必要がなければ各チームでリポジトリを準備したほうが良い。それぞれの履歴を持つので、履歴を確認するのが簡単。

作業メンバーはそれぞれに必要なリモートリポジトリからクローンすれば良いので、チームに必要のないデータを持たなくてよい。
git_module02.png


逆に、複数モジュールがあるが最終的にまとめて結合してリリースするような場合、リポジトリが複数あると、採取的にまとめるときに、複数のリポジトリそれぞれからクローンする必要があり、このよう場合はリポジトリがまとまってくれていたほうが良かったのにと思うときがある。
git_module03.png

どのような形であれ、チーム単位で見たらそれぞれのリポジトリを単独で使って作業をする限りは管理はさほど難しくならない。
もし、他のリポジトリを自分のチームのリポジトリへ取り込む必要が出てきたらどうすれば良いか?






別のリポジトリを追跡する


例えば、ライブラリを組み込んで使うときのように、異なるモジュールを組み込んで使う場合はどうすべきか?
gitには、外部のリポジトリを取り込むことができるサブモジュールという機能がある。


例として、まずは取り込む元となるリモートリポジトリを準備する。
$ mkdir submodule.git && cd submodule.git
$ git init --bare
Initialized empty Git repository in **********/submodule.git/

このリモートリポジトリをクローンして作業する。簡単な作業として、ファイルを追加してコミットし、プッシュまでしておく。
$ git clone **********/submodule.git/
Cloning into 'submodule'...
warning: You appear to have cloned an empty repository.
done.
$ cd submodule/
$ echo "submodeule" > submodule.txt

$ git add submodule.txt

$ git commit -m "Add new file : submodule.txt"
[master (root-commit) e609385] Add new file : submodule.txt
1 file changed, 1 insertion(+)
create mode 100644 submodule.txt

$ git push origin
Counting objects: 3, done.
Writing objects: 100% (3/3), 234 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To **********/submodule.git/
* [new branch] master -> master

コミットID:e609385 でファイルを追加した。


このリモートリポジトリの取り込み先としてのプロジェクトを同様に準備する。
$ mkdir project1.git && cd project1.git
$ git init --bare
Initialized empty Git repository in **********/project1.git/

$ git clone **********/project1.git/
Cloning into 'project1'...
warning: You appear to have cloned an empty repository.
done.
$ cd project1/
$ echo "project1" > project1.txt

$ git add project1.txt

$ git commit -m "Add new file : project1.txt"
[master (root-commit) 14e6026] Add new file : project1.txt
1 file changed, 1 insertion(+)
create mode 100644 project1.txt

$ git push origin
Counting objects: 3, done.
Writing objects: 100% (3/3), 231 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To **********/project1.git/
* [new branch] master -> master

これにサブモジュール機能を適用していく。




プロジェクトにサブモジュールを追加する


"sub" ディレクトリを作成して、そこに追加してみる。
$ mkdir sub && cd sub

サブモジュールとして追加するリポジトリ(先に作ったリモートリポジトリ)を指定する。

$ git remote add **********/submodule.git/

Cloning into 'sub/submodule'...
done.

ステータスを確認してみる。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD ..." to unstage)

new file: .gitmodules
new file: sub/submodule
取り込みたいファイルと ".gitmodules" が追加され、ステージされている。

diff も見てみる。
$ git diff --cached
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..fdda908
--- /dev/null
+++ b/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "sub/submodule"]
+ path = sub/submodule
+ url = **********/submodule.git/

diff --git a/sub/submodule b/sub/submodule
new file mode 160000
index 0000000..e609385
--- /dev/null
+++ b/sub/submodule

@@ -0,0 +1 @@
+Subproject commit e60938592340493b0a9ba52682c8c0f41c1aa0c2

"+Subproject commit e60938592340493b0a9ba52682c8c0f41c1aa0c2" は、登録したサブモジュールの取り込み元となるコミットを示している。

".git/config" にも情報が書き込まれている。
[submodule "sub/submodule"]
url = **********//submodule.git/




これをコミットすれば、サブモジュールの追加が完了。プッシュもしておく。
$ git commit -m "Add submodule : sub/submodule/"
[master 1df63d2] Add submodule : sub/submodule/
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 sub/submodule

$ git push origin
Counting objects: 4, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 417 bytes | 0 bytes/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To **********/project1.git/
14e6026..1df63d2 master -> master

git_module04.png





サブモジュールを持つプロジェクトをクローンする


リモートリポジトリにプッシュしたので、これを他のメンバーにも展開したい。
他のメンバーはまずは普通にプロジェクトをクローンし、クローンしたファイルの中身を見てみる。
$ git clone  **********/project1.git/
Cloning into 'project1'...
done.


├ .git
└ ....
├ .gitmodules
├ project1.txt
└ sub
└ submodule

"sub/submodule" ディレクトリはあるが、肝心のサブモジュールとしてのファイルが存在していない。それに ".git/config" にもサブモジュールの情報が書き込まれていない。
git_module05.png


サブモジュールのデータを引っ張ってくるためには、以下のようにする。

$ git submodule init
$ git submodule update

または

$ git submodule update --init


init で、".gitmodules" の情報を ".git/config" に書き込む。
updateで、データを引っ張ってきた状態にする。update --init はこれらを一気に行う操作。

さらに、この2つの操作を --recursive オプションでクローンと同時に実行することもできる。

$ git clone --recursive **********/project1.git/




ここで間違えてはいけないことは update 操作。
これは取り込み元のリポジトリの最新をもってくるのではなく、取り込み先のリポジトリ(ここでは "project1.git")に登録されたサブモジュールの情報から、取り込むデータを引っ張ってくる操作。このため取り込み元のリポジトリが更新されていたとしても関係なく、取り込み先にサブモジュールとして登録されているコミットIDからデータを引っ張ってくる。
git_module06.png




サブモジュールを最新に更新する


サブモジュールの取り込み元のリポジトリが更新され、そして取り込み先でもその最新リポジトリ内容を使うことになったとする。そうなるとサブモジュール自体を最新に更新する操作が必要となる。

取り込み元のリポジトリの最新コミットで更新するのであれば以下のコマンドを使う。

$ git submodule foreach git pull <remote> <branch>


もし "master" ブランチだけであれば、省略して、

$ git submodule foreach git pull


この操作後、変化点が出てくるので、ステージして、コミットし、プッシュすれば更新完了。

$ git add.
$ git commit -m "Update submodule"
$ git push --all


サブモジュールの参照内容は切り替わっている。



では最新ではなく、取り込み元リポジトリ内の特定コミット状態に更新したい場合はどうするのか?

その前に、サブモジュールはどのような状態か調べてみる。
サブモジュールがあるディレクトリに移動し、ステータスとリモートリポジトリを見てみる。
$ cd sub/submodule/
$ git status
HEAD detached at e609385
nothing to commit, working directory clean

$ git remote -v
origin **********/submodule.git/ (fetch)
origin **********/submodule.git/ (push)

取り込み先(ここでは "project1.git")が参照するリモートリポジトリを指しておらず、取り込み元のリモートリポジトリを指している。
実は、サブモジュールがあるディレクトリ以下は、取り込み元のリポジトリの管理下にあり、さらにサブモジュールは "detached HEAD" の状態と同じだとわかる。
git_module07.png
サブモジュールは "detached HEAD" で直接コミットIDを参照しているので、取り込み元のリポジトリが変化しても影響を受けずに存在している。
そして取り込み元のgit管理下に置かれているので、取り込み元のリポジトリの内容でブランチの切替もチェックアウト操作も可能。


話を戻して、特定のコミットで更新する場合はどうすれば良いのかというと、
まずは更新するためのコミットIDを知ることから行う。サブモジュールの管理下に移動して、リモートリポジトリからプルして最新情報を取得する。後はブランチを切り替え、ログを確認するなどして必要なコミットIDを取得する。
$ git checkout new_branch
Previous HEAD position was e609385... Add new file : submodule.txt
Branch new_branch set up to track remote branch new_branch from origin.
Switched to a new branch 'new_branch'

$ git log --oneline
83386e4 modified new_branch submodule1
dba71a8 modified submodule3
62dfb39 modified submodule2
e609385 Add new file : submodule.txt

次に、そのコミットIDを使い、サブモジュールを "detached HEAD" 状態に切り替える。
例として、コミットID:dba71a8 に切り替えてみる。
$ git checkout dba71a8
Previous HEAD position was 83386e4... modified new_branch submodule1
HEAD is now at dba71a8... modified submodule3

これで更新できているので、取り込み先のgit管理下に戻して、
この状態をステージし、コミットし、プッシュすれば更新が完了する。
$ git add .
$ git commit -m "Update submodule"
[master f1b5a0b] Update submodule
1 file changed, 1 insertion(+), 1 deletion(-)

$ git push --all
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 286 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To **********/project1.git/
1df63d2..f1b5a0b master -> master




他のメンバーは、通常通りにリモートリポジトリから最新状態に取得しローカルリポジトリを更新したら、サブモジュールをリモートリポジトリの管理状態に更新するだけ。

$ git submodule update






サブモジュールを削除する


サブモジュールが不要になれば、サブモジュールを追加したディレクトリを指定して操作を行う。

$ git submodule deinit sub/submodule/

Cleared directory 'sub/submodule'
Submodule 'sub/submodule' (**********/submodule.git/) unregistered for path 'sub/submodule'
これでサブモジュールを削除できる。




まとめ


サブモジュールは外部のリポジトリを参照し、うまく取り込むことができるので、プロジェクトの構成内容の管理を分散できてパッと見は便利に思える。開発を前に進めて行くときは便利だと思うが、過去の内容を確認したくて過去のリビジョンをチェックアウトするときには注意が必要。サブモジュールはリポジトリの管理が異なるので、同時にはは過去の状態に戻ってくれない。サブモジュールを戻すにはそのリビジョン当時のコミットIDを指定して、別途チェックアウトしてあげないといけない。このような動作の癖を理解して、構成管理をしたうえでプロジェクトを進めないと、過去のリビジョンを急ぎ確認したいときに、間違った構成のまま作業を進めてしまって、要らない苦労をするかもしれない。
サブモジュールを取りこんでそのあとの管理の仕方をメンバー内で共有し、リポジトリが別管理であることを意識して作業することができれば便利だと思う。





gitをインストール
gitサーバーのセットアップ
git ブランチについて
git 変更を一時的に退避 stash
git ブランチを合流するマージ
git ブランチを付け替える
git コミット履歴を変更する
git 変更をリセットする
git リモートでの操作
git ファイルの追跡
git リリース準備
git リモートブランチを追加
git チェックアウトをもっと便利に使う
git 変更をpatchファイルにする
git コンフリクトに対処する
git 失敗したときの復元
posted by Zorinos at 20:00| Comment(0) | Linux | 更新情報をチェックする

2017年07月30日

git チェックアウトをもっと便利に使う

ブランチを切り替えるときに使う git checkout コマンド。
ブランチのチェックアウト操作を行うことで、作業ツリーの状態をそのブランチの最新状態に切り替えることができる。

が、チェックアウトできるのは、ブランチだけではない。
他の使い方もあるので、知っていればより便利に git checkout が使える。


まず "HEAD" の状態を確認しておく。".git/HEAD" というファイルが実体。
git_checkout01.png
$ cat .git/HEAD
ref: refs/heads/master

"refs/heads/master" を参照していることを示している。
では、"refs/heads/" にどのようなファイルがあるのか確認してみる。
└ refs
├ heads
├ master
└ new_branch
"master" ブランチと "new_branch" ブランチのファイルがある。では、肝心の "refs/heads/master" には何が書いてあるのか。
$ cat .git/refs/heads/master
b94d4058ea3ef1537213e35212debb2214507a05
"master" ブランチの最新のコミットIDが書かれている。
この実体は ".git/objects/" にあり、コミットIDをもとに探すことができる。
今、参照するコミットIDが "b94d405" なので ".git/objects/b9/4d4058ea3ef1537213e35212debb2214507a05" が "HEAD" が参照するものとなる。
"HEAD"

.git/HEAD

.git/refs/heads/master

.git/objects/b9/4d4058ea3ef1537213e35212debb2214507a05






ファイルの過去のリビジョンをチェックアウトする


「全体としては今の雰囲気が良いいけど、この部分については前の内容のほうがバランスが取れているよね」的なことを言われたり、「落ち着いて考えたらやっぱりこの内容は無し」と思うこともある。
そういう場合は、git reset で作業ツリーを元に戻すことができる。
しかし git reset は作業ツリー上のすべてのファイルを指定状態に戻す。もし、特定のファイルだけ巻き戻したいという場合には、git checkout が使える。

"HEAD" 状態に戻したい場合は以下のようにする。

git checkout HEAD <対象ファイル>

"HEAD" が参照するコミットIDから対象ファイルをロードしてきて、作業ツリー上のファイルを上書きする。
git_checkout02.png

ファイルを戻すのは、"HEAD" だけではなく、コミットIDを指定すれば過去のコミットから特定ファイルをロードすることができる。

git checkout <コミットID> <対象ファイル>

このときの "HEAD" の状態も確認してみる。
$ git checkout b60971b a.txt
$ cat .git/HEAD
ref: refs/heads/master
"HEAD" の状態に変化はなく、"master" ブランチを参照している。

ステータスを見てみる。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD ..." to unstage)

modified: a.txt
チェックアウトしたファイルは、ロードするだけではなく、ステージされている。このような動きになることを知っておくとよい。

また、コミットIDだけではなく、タグを指定することもできる。

$ git checkout <タグ> <対象ファイル>



ファイルの変更を元に戻すためだけではなく、過去の内容をちょっと確認したい場合にも使える。コミットIDだけではなく、タグを指定することもできるので、過去のリリースバージョン当時のファイルを確認したいときになどに、その当時のファイルをチェックアウトし、確認が終われば、"HEAD"を指定して戻せば良い。




過去のリビジョンを一時的に取り出す


特定のファイルではなく、特定のコミットを指定すれば、作業ツリーがすべてその当時の状態になるのでは?
と思って、特定のコミットだけ指定してチェックアウトしてみる。

$ git checkout b60971b

Note: checking out 'b60971b'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b

HEAD is now at b60971b... Add new file : b.txt
何かメッセージが出て警告を受けた気がする。

メッセージ内容はこんな感じ。
"detached HEAD" 状態です。この時点のファイルを見て回ったり、実験的な変更を加えてコミットすることができる。別のブランチをチェックアウトすれば、その他のブランチに影響を与えることなく、ここでの変更を破棄することができる。
もし作成したコミットを残しておくために新しいブランチを作りたいなら、-b を使って再度チェックアウトをすればいい。
例:git checkout -b <new-branch-name>
"HEAD" は、今、 b60971b... Add new file : b.txt

git_checkout03.png

このときの "HEAD" の状態を確認してみる。
$ cat .git/HEAD
b60971b05d47cb17ce3f4ad01be89e3c1d9c8e29
今までと違い、コミットの実体を直接指定している。
HEAD

.git/objects/b6/0971b05d47cb17ce3f4ad01be89e3c1d9c8e29


間違って "detached HEAD" したのであれば、再度ブランチをチェックアウトして、きちんと "detached" では無い状態にする。


意図的に "detached HEAD" にしたのであれば問題無いが、もしgitがせっかく教えてくれたメッセージを無視してブランチをチェックアウトしたものだと勘違いしたまま作業してコミットまでしてしまったとする。
git_checkout04.png
この状態でブランチを切り替えてみる。
$ git checkout new_branch
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

2c8dbd4 detached HEAD

If you want to keep it by creating a new branch, this may be a good time
to do so with:

git branch 2c8dbd4

Switched to branch 'new_branch'

またメッセージが出た。この内容はこんな感じ。
どのブランチにも繋がっていないコミットが1個残っている。

2c8dbd4 detached HEAD

これを新しいブランチを作ることで残したいのであれば、今がいいタイミングです。
以下のようにすればいい。

git branch <new-branch-name> 2c8dbd4

git_checkout05.png
"detached HEAD" になっていることに気づかずにコミットした状態で、他のブランチにチェックアウトすると、"detached HEAD" 状態でコミットした内容は見えなくなってしまうが、無くなったわけではない。どこからも参照されないだけで記録は残っている。

これを復活させるためには、gitが教えてくれた通りに実行すればいい。
一時的なブランチを作ればいい。

$ git checkout tmp


これで参照できるようにつながったので、後はこのブランチをマージするなりして対応すればいい。作業した内容を無事に復旧できれば、一時的なブランチなので削除すれば終了。






まとめ


git checkout でファイルの変更を無かったことにしたり、巻き戻したりできるので知っておくと、いざというときにさっと作業できるので便利。ただし、コミットIDでチェックアウトし、"detached HEAD" にしてしまうと、この状態を知っていると知らないでは、このあとの作業の内容が違ってくる。間違ったまま作業していくと、コミット内容が見える範囲から消えてしまうことになりかねない。
gitがきちんとメッセージで教えてくれているので、慣れた作業であってもメッセージを読み飛ばさないように注意したほうがいい。





gitをインストール
gitサーバーのセットアップ
git ブランチについて
git 変更を一時的に退避 stash
git ブランチを合流するマージ
git ブランチを付け替える
git コミット履歴を変更する
git 変更をリセットする
git リモートでの操作
git ファイルの追跡
git リリース準備
git リモートブランチを追加
git プロジェクトの構成
git 変更をpatchファイルにする
git コンフリクトに対処する
git 失敗したときの復元
posted by Zorinos at 20:00| Comment(0) | Linux | 更新情報をチェックする

2017年07月25日

git リモートブランチを追加

複数メンバーで作業するプロジェクトを新規で作成する場合、リモートリポジトリを準備してから作業する。これは git サーバーのセットアップ時に触れた。

この場合の手順をざっくりと言えば、こんな感じ。
  1. 空のリモートリポジトリを準備
  2. リーダーのローカルマシンへクローン
  3. ローカルマシンで初期セットアップ
  4. リモートへプッシュ
  5. メンバーが各自のマシンへクローン
  6. 各自で作業開始


でもサーバー側の準備が遅かったら、リモートリポジトリができるまで待たないといけないので、先にローカルで準備をしたほうが時間の無駄がない。
また、単独で作業をしていたプロジェクトを複数メンバーで作業することになり、公開する必要ができた場合は、後からリモートリポジトリを設定できなければいけない。



後からリモートリポジトリを設定する


  1. ローカルリポジトリで作業が進行
  2. サーバー側で準備ができ、空のリモートリポジトリを作成
  3. ローカルリポジトリにリモートリポジトリを設定
  4. リモートへプッシュ
  5. メンバーが各自のマシンへクローン
  6. 各自で作業開始



現在のブランチの状態を確認してみる。
$ git branch -a
* develop
master

まだ、リモート追跡ブランチは存在していない。

ローカルリポジトリに、新たなリモートリポジトリを設定するためには git remote を使う。

$ git remote add origin gitserver:~/git/project1.git


リポジトリを作成したら、git push する。
--all オプションを使えば、すべてのブランチを git push できる。

$ git push --all




現在のブランチの状態を確認してみる。
$ git branch -a
* develop
master
remotes/origin/develop
remotes/origin/master

リモートリポジトリが設定され、リモート追跡ブランチが設定されたことがわかる。






リモートリポジトリの設定を削除する


設定を削除するのも git remote を使う。

$ git remote rm origin



現在のブランチの状態を確認してみる。
$ git branch -a
* develop
master






リモートリポジトリの変更


gitサーバーが変更になり、サーバーアドレスが変更になった場合は、リモートリポジトリも変更しなければならない。リモートリポジトリを変更するには、一旦、削除してから再設定しても良いが、1つのコマンドで対応できる。

$ git remote set-url origin <新しいリポジトリのアドレス>


変更されたことを確認するには git remote -v を使えばよい。
$ git remote -v
origin 新しいリポジトリのアドレス/new-repo.git/ (fetch)
origin 新しいリポジトリのアドレス/new-repo.git/ (push)





まとめ


そんなに頻繁にリモートリポジトリを変更する場面は無さそうな気がするが、例えばペアを組んで作業するメンバー同士のマシンを相互にリモートリポジトリに設定すれば、作業途中の状態を他のメンバーに公開する必要がない。作業に必要な範囲だけで共有し、完成してから全メンバーへ公開すればよい。このようなやり方をするのであれば、リモートリポジトリの設定・削除は頻繁に発生する。
なんでもメインとなるリモートリポジトリを使って作業していると、メンバー数が増えれば増えるほど、サーバーの負荷があがり、逆に作業の効率が悪くなることが予想できる。gitの機能を理解して、効率よく作業を進めれる方法を身に付けたい。





gitをインストール
gitサーバーのセットアップ
git ブランチについて
git 変更を一時的に退避 stash
git ブランチを合流するマージ
git ブランチを付け替える
git コミット履歴を変更する
git 変更をリセットする
git リモートでの操作
git ファイルの追跡
git リリース準備
git チェックアウトをもっと便利に使う
git プロジェクトの構成
git 変更をpatchファイルにする
git コンフリクトに対処する
git 失敗したときの復元
posted by Zorinos at 20:00| Comment(0) | Linux | 更新情報をチェックする