2015年12月26日土曜日

gitによる開発に関して:特定部分を別branchで管理し、master等にコピーしたい

最近、悩んでいるのがgitの操作方法についてです。しかもやりたいことが少し特殊です。

・プログラムの開発をしているが、異なるbranchで開発しているもののうち、自分の担当している部分だけmasterに持ってきて(コピー)、全体の試験がしたい

通常ならば、branch間でmergeすればいいんですが、そうすると自分が使っているdevelop branchにある別のフォルダの内容がmasterと完全に別内容にしているため、それができない!
本来なら、リポジトリのディレクトリ設計の時に考慮しておくべきことなんですが、それに失敗した例です。ただ開発が最終局面にきていて、いまさらリポジトリの再設定をしてしまうと、これまでの各ファイルの改定履歴がリセットされてしまいます。(git等のバージョン管理システムではよくあることですが、ファイルのディレクトリの位置を変えただけでも、当該ファイルの履歴が消えてしまいます。せいぜい、当該ファイルが移動されたよ、というログが残る程度です。subversionは特別に履歴を引き継ぐオプションがあるようですが、特別扱いのようです。詳細は後の雑談で。)開発が結合試験に入っているため、ログが消えるのはかなり痛いです。

開発が終わってしまえば、ログが消えてもまだ我慢できますが、このフェーズではきついです。mergeできなくてもコピーだけでもできればいい・・・、最後はbranchを切り替えて、一旦別の場所にコピーしておいて、再度master branchに切り替え、(手動で)コピーしようかと考えました。色々とぐぐってみると、かなりトリッキーな技があるようですが、最終的に以下のコマンドで可能なことがわかりました。

$ git branch master
$ git checkout develop /home/hoge/git/repo/A/B
                    (branch名) (path名:フルパスで!)

これでdevelop branchのBディレクトリ以下のファイルがmaster branchのBディレクトリ以下にコピーされてきます。(master, developともrepo/以下のディレクトリ構造は基本的に同じはずですから、コピー元を規定するだけで問題ないはずです。万一、構造が変わっていたりすると・・・、ちょっと考えないと。)もちろん、modifyのstatusになりますので、commitとしとくなりしとかないといけません。(当然、ログには「developからコピーしたよ」等入れておきましょう)

注:上記checkoutではpath名をフルパスで規定しています。ぐぐったサンプルでは如何にもリポジトリのtopからのpath名と推測できるような書き方がしてあるものばかりでしたが、どうもフルパスが必要なようです。

具体的に例を示します。

1.gitリポジトリの作成

$ mkdir git/repo
$ cd git/repo
$ git init

2.リポジトリの中身を作る
今回は以下の様な階層を作ってみました。(詳細なコマンドは省略します)

A/ A.txt
     B/ B.txt
          D/ D.txt
     C/ C.txt

Aディレクトリのあるところに、".git"があります。各ディレクトリにあるテキストファイルには以下の様なテキストが入っています。(注:赤字の部分を別branchからコピーしてきます。ちなみに、B/とC/は同じA/以下の階層にいます。)

A.txtサンプルのテキスト
master branchのファイル

最初にファイル名を入れ、後はbranch名を入れてあります。(もちろん、この後commitしておきましょう)

3.develop branchの作成

$ git checkout -b develop

これで全く同じ内容でdevelop branchが作成され、develop branchに移動しました。

4.develop branch内のファイルの変更
次に、後にmaster brachの特定の部分(Bディレクトリ以下)にこのdevelop branchの内容をコピーするので、それがわかるようにファイルの中身を変更します。具体的には、以下の様にします。(これを、A, B, C, Dの4個のtxtファイルに行います)

A.txtサンプルのテキスト
develop branchのファイル

5.master branchへの移動、develop branchの一部をコピー

$ git checkout master
$ git checkout develop /home/hoge/git/repo/A/B

2回めのcheckoutはbranchを移動しません。単にdevelop branchのBディレクトリ以下をmaster branchにコピーしてくるだけです。これにより、B.txtと配下にいるD.txtがコピーされます。git statusをしてみると、この2つのファイルがmodifiedだといってきて、各ファイルの内容は以下の様になっています。

B.txtサンプルのテキスト
develop branchのファイル


これで自分のメインの開発はdevelop branchで行い、結合試験の必要に応じてmaster branchにコピーすることができます。


雑談:
今回、gitのことを色々調べていて思ったのが、こういうリポジトリ管理ではリポジトリ間でも「部分的な」コピーはどうやっても行えないということです。よく考えてみると、一つのリポジトリは一つの目的を持って作っているのであり、その一部だけを(履歴つきで)持ってくるのはかなり危ないことだと気づきました。そもそもその「履歴」(ログ)は別のリポジトリ(環境)で開発中のことが記録されているので、それを別のリポジトリで見ても何の意味もないか、逆に勘違いの元になる可能性があります。
同じように、一つのリポジトリ内でも、ファイルの移動を行うだけで「履歴」が消えてしまいますが、これはファイルの位置が変わるということは、当該ファイルの(開発)目的が変わる場合がほとんどで
、昔の履歴は害になることが多いはずです。
本来は、そんなことのないようにリポジトリを設計する段階できちんと決めておくべき(将来の可能性・拡張性を考えて)なのですが、世の中万能の人間ばかりではないので、filter-branch等駆使して、強引にログを書き換えてしまう参考例がいくつかありました。
ただ、最初から別リポジトリで開発を進め、途中から一つのリポジトリ管理下に置くようにすることは認められているようです。
後、調べている最中に"git-new-workdir"というツールの紹介がありました。このコマンドを使ってbranch間のコピー(共有?)を設定すると、どちらのbranchで操作を行っても、相手のbranchに反映される上に、履歴もつくのだとか。一瞬これこそ求めていたものだと思いましたが、よく考えたらこんなの使いはじめると「多用」しそうで危ないツールだと思いました。

0 件のコメント:

コメントを投稿