まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:git

令和最初のブログエントリです。


Node.js アプリの中で git を使う方法を調べてみました。git で Node.js のソースコードを管理する、のではなく、アプリケーションの中で特定の git リポジトリに対して clone したり、pull したり、add して commit して push したり、、、といった操作を Node.js アプリ側から行う方法、という意味です。

この操作を実現するためのライブラリとして simple-git というものを使ってみました。npm を使って以下のコマンドを実行することで導入できます:
$ npm install simple-git

2019050201


そして、以下のコードで扱う git リポジトリは https://github.com/dotnsf/simple-git-sample.git であると仮定します。異なるリポジトリで実験する場合は( fork してコピーを作るなどして)適宜読み替えてください。またローカル側のワーキングフォルダ(git で管理するフォルダ)は ./work/ であると仮定します。


【初期化】
まず、このライブラリを使って Node.js アプリを作る際の初期化方法を紹介します(実はここが一番ややこしい所だったりします)。ここは「実行時に git clone して初期化」するのか、「実行時に git pull して初期化」するのかによって手順が異なります。

実行時に git clone して初期化する場合は以下のようにします:
//. git-clone.js
var git = require( 'simple-git' );

var git_url = 'https://github.com/dotnsf/simple-git-sample.git';
var local_folder = 'work';


//. clone
git().clone( git_url, local_folder );

このコードを node コマンドで実行すると、work/ というフォルダが新規に作成され、https://github.com/dotnsf/simple-git-sample.git のクローンがそのフォルダの中に作成されます。

一方、実行時に git pull して初期化する場合は以下のようにします:
//. git-pull.js
var git = require( 'simple-git' );

var git_url = 'https://github.com/dotnsf/simple-git-sample.git';
var local_folder = 'work';


//. pull
git( local_folder ).pull();

このコードを node コマンドで実行すると、既にワーキングフォルダとして存在している ./work/ フォルダの中に最新のリポジトリ状態が git pull されます。

ここでややこしいのが「どちらを使って初期化するべきか?」です。既にワーキングフォルダ ./work/ が存在している状態で git clone を実行するとエラーになってしまうし、一方ワーキングフォルダが存在していない状態では git pull してもエラーとなってしまうからです。というわけで、以下のように実行すべきだと思いました:
//. git-init.js
var git = require( 'simple-git' );
var fs = require( 'fs' );
var path = require( 'path' );

var git_url = 'https://github.com/dotnsf/simple-git-sample.git';
var local_folder = 'work';

//. フォルダの存在確認
var dirname = path.dirname( './' + local_folder );
fs.access( dirname, fs.constants.R_OK | fs.constants.W_OK, ( err ) => {
  if( err ){
    //. clone
    git().clone( git_url, local_folder );
  }else{
    //. pull
    git( local_folder ).pull();
  }
});

ワーキングフォルダが存在しているかどうかを確認し、存在していなかった場合は git clone を、存在していた場合は(git clone 済みと解釈して)git pull を、それぞれ実行して初期化しています。


【ファイル追加】
次にこの(初期化済みの)ライブラリを使って、リポジトリにファイルを追加する方法を紹介します。厳密にいうと「(git pull して、)git add して、git commit して、git push する」までの一連の方法を紹介します。

といっても、実は結構簡単でこんな感じで一連の処理を実現できます:
//. git-push.js
var git = require( 'simple-git' );
var fs = require( 'fs' );
var path = require( 'path' );

var git_url = 'https://github.com/dotnsf/simple-git-sample.git';
var local_folder = 'work';

//. pull, add, commit, and push
git( local_folder ).pull()
  .add( 'README.md' )
  .commit( 'README.md updated.' )
  .push( [ '-u', 'origin', 'master' ] );

add() のパラメータに追加したいファイル名、commit() のパラメータにはコミットメッセージ、そして push のパラメータにはオプションを指定します(この例だと $ git push -u origin master を実行しているのと同じ処理をしています)。


実際にはブランチ切ったり、マージが必要になったりすることもあるので、全ての git 処理を全自動でというのはなかなか難しい所もあると思いますが、一連の決まった処理を(例外処理無しで)実現するにはシンプルで便利なライブラリだと思いました。


GitHub で作成してしばらく使っていたリポジトリが、当初の想定以上に盛り上がったりすると、最初に適当に付けたリポジトリ名からちゃんとした正式名称のリポジトリに変更したくなる(というか、した)、という経験をしました。その時の作業手順メモです。

まず最初の注意点として、GitHub リポジトリのリネームは GitHub サーバー側と、そのクローンを保持するローカル側の両方で行う必要があります。クローンを保持するローカルが複数ある場合は、その全てのローカル側で対応が必要になります。

今回は
 https://github.com/dotnsf/old_app.git

 https://github.com/dotnsf/new_app.git
にリネームする想定で以下を説明します。

【GitHub サーバー側】
サーバー側の変更は GitHub のリポジトリ画面内から行います。まずブラウザでリポジトリページを開き、"Settings" メニューを選択します:
2018061401


"Settings" メニューのすぐ下に "Repository name" フィールドがあり、ここに変更前のリポジトリ名(今回であれば "old_app")が入力されています:
2018061402


ここを新しいリポジトリ名称(今回であれば "new_app")に変更し、"Rename" ボタンをクリックして確定させます:
2018061403


サーバー側の変更はこれだけです。この時点で名称変更前の URL にアクセスしても自動的に新しい URL にフォワードされて、新しい名前のリポジトリが表示されます:
2018061404



【ローカル側】
クローンしたローカルリポジトリ内の .git/config ファイルを編集します:
  :
  :

[remote "origin"]
        url = https://github.com/dotnsf/new_app
        fetch = +refs/heads/*:refs/remotes/origin/*

  :
  :

[remote "origin"] 項目内の url の値を新しいリポジトリの URL に変更して保存します。ローカル側の変更もこれだけですが、複数のマシンにローカルリポジトリが存在する場合は全てのローカルリポジトリを変更します。



ここまでの作業でサーバー側&リモート側ともリポジトリのリネーム作業が完了しました。当然ですが、中身は変わってない(リネーム前のまま)ので、改めてリネーム後に変更が必要なファイル(README.md とか)を更新してください:
2018061405


 

IBM Cloud(Bluemix) のアカウントを所有していると、マネージドサービスとして利用できる GitLab が使えるようになります。サーバーのインストールなどは不要で、プライベートリポジトリを作成することも可能です:
2018011701


使い勝手は GitLab そのものだと思ってください。Issues 管理の機能も使えますし、IBM Cloud の Continous Delivery サービスと連携した Delivery Pipeline による DevOps サービスの一部としても利用できるようになっています。アカウントをお持ちの方は、単にプライベートリポジトリが使える Git として考えるだけでも便利だと思うので、是非活用してください。


ところで、この IBM Cloud の Git を使って Java のアプリケーションコードを管理しようと、作成したリポジトリから Eclipse の Git 機能を使って clone を試みた際に、稀に以下のようなエラーメッセージに遭遇し、クローンに失敗することがあります:
  :
  :
!MESSAGE https://git.ng.bluemix.net/dotnsf/javatest.git: cannot open git-upload-pack
!STACK 0
org.eclipse.jgit.api.errors.TransportException: https://git.ng.bluemix.net/dotnsf/javatest.git: cannot open git-upload-pack
  at org.eclipse.jgit.api.LsRemoteCommand.call(LsRemoteCommand.java:196)
  at org.eclipse.egit.core.op.ListRemoteOperation.run(ListRemoteOperation.java:99)
  at org.eclipse.egit.ui.internal.clone.SourceBranchPage$8.run(SourceBranchPage.java:324)
  at org.eclipse.jface.operation.ModalContext$ModalContextThread.run(ModalContext.java:121)
Caused by: org.eclipse.jgit.errors.TransportException: https://git.ng.bluemix.net/dotnsf/javatest.git: cannot open git-upload-pack
  at org.eclipse.jgit.transport.TransportHttp.connect(TransportHttp.java:499)
  at org.eclipse.jgit.transport.TransportHttp.openFetch(TransportHttp.java:308)
  at org.eclipse.jgit.api.LsRemoteCommand.call(LsRemoteCommand.java:175)
  :
  :

「git-upload-pack がオープンできない」という耳慣れないエラーメッセージで、実はこのメッセージそのものからは原因の追求が難しいものでした。同じようなエラーに遭遇する人が現れた場合に備えて、自分の経験と回避策を紹介します。

エラーメッセージそのものからはわかりにくにのですが、実は直接の原因は暗号化方式の不一致による通信エラーでした。

まず上記で紹介した IBM Cloud の Git 機能を https 接続で使う場合の暗号化方式には TLS v1.2 を使う必要があります:
https://console.bluemix.net/docs/services/ContinuousDelivery/git_working.html#git_local


さて、Eclipse が使う Java のバージョンが 1.8 以上であれば、デフォルト設定のままで TLS v1.2 が使われます。したがってこの場合は何もしなくてもそのまま IBM Cloud の Git を利用することができます。

一方、Eclipse の Java バージョンが 1.7 以下だった場合、デフォルト設定で採用される通信方式は TLS v1.1 以下です。つまり条件を満たしていないことになります。そしてこの条件で Git に接続しようとすると上記のようなエラーメッセージが表示されてしまうのでした。


では、このエラーメッセージが出た場合の解決策はどうすればいいのでしょうか? 1つの方法としてJava のバージョンを 1.8 以上にするという簡単な方法があります。Java 1.8 以上であれば上記のように(デフォルトで) TLS v1.2 が使われるので、この条件を満たすことができるようになります。

ただ何らかの事情で Java 1.8 を導入するわけにはいかない場合もあると思っています。そのような場合は以下の1行を eclipse.ini に追加した上で Eclipse を起動する、という方法もあります:
  :
  :
-Dhttps.protocols=TLSv1.2

この記述により、Java が 1.7 以下であっても強制的に https 接続時の暗号化方式を TLS v1.2 に指定することができ、やはり上記のエラーを回避することができるようになります。


IBM Cloud 以外の Git でも、同様のエラーメッセージが出た場合にはこの対策が有効だと思っています。頭の片隅に入れながら、無料で便利な IBM Cloud の Git を是非使ってみてください。


IBM Bluemix の利用者に提供されているサービスの1つに IBM DevOps Services(以下、IDS)があります。名前の通り、DevOps のための統合環境サービスなのですが、事実上「ウェブから使えるエディタ」として認識している人もいらっしゃるかもしれません:
2016091306


もちろん統合環境であるからには、こうした「ウェブだけでソースコードが編集できる」機能があることも重要ですが、それだけではありません。ソースコードのバージョン管理として事実上の標準となった Git のリポジトリ機能も使えますし、ビルド&デプロイの管理機能も付属しています。これらはバラバラに(=自分の使い慣れたツールと組み合わせて)使うこともできますし、この環境だけで全て利用することもできます。今回はその前者の例として、IDS を Git のリポジトリとして(のみ)使う例を紹介します。要はソースコードの編集は普段使い慣れたローカルマシンのエディタを使いつつ、ソースコードのバージョン管理に IDS を使おう、という考え方です。必ずしも Bluemix アプリケーションではない、普通のウェブアプリケーションやツールのソースコード管理としても IDS が使える、という使い方をイメージしています。

まずは IDS にウェブブラウザでアクセスして IBM Bluemix の ID とパスワードでログインします:
http://hub.jazz.net/


初めて IDS を使う場合はここでユーザー名の別名を聞かれたりするので適当に決めて答えておきましょう。ただ、ここで決めた名前がこの後作成する Git リポジトリの URL の一部になるのでそのつもりで。

ログイン直後の画面です。既にプロジェクトを利用していたりすると、それらが表示されている画面になるので、見た目は人によって多少変わるかもしれません。以下の画面はまだ何も使っていない、まっさらな状態でのプロジェクト一覧画面です:
2016091301


では早速 Git リポジトリとしてのプロジェクトを1つ追加しましょう。"Start coding" と書かれた大きなアイコン部分をクリックします:
2016091301


ここでプロジェクトの名称(以下の例では "my1stApp")を指定します。今回はこの IDS 内に新しいプロジェクトを作成するので、"Create a new repository" を選択します:
2016091302


続いてリポジトリの作成先を指定するので、"Create a Git repo on Bluemix" を選択します:
2016091303


するとアイコンのすぐ下に今から作成する Git のリポジトリ URL が確認できます。後で再確認することもできますが、一度ここでメモしておきましょう。 また作成するリポジトリにサンプルの README を含めるかどうかを選択するチェックボックスが表示されています。ここでは最初に README を含めておくことにします(任意です)ので、チェックボックスを ON にしておきます:
2016091304


続けてオプションが3つ表示されています。上から順に (1)プライベート(非公開)リポジトリにするか、(2) Scrum 管理機能を追加するか、(3) Bluemix アプリケーションプロジェクト機能を追加するか、です。この例では全て OFF にしていますが、いずれも必要に応じて ON/OFF してください。最後に "CREATE" ボタンを押すと、この設定で Git リポジトリが作成されます:
2016091305


Git リポジトリが作成されると、このような画面に切り替わります。指定した名前のプロジェクトが生成され、その中に License.txt と(オプションで指定した)README.md が含まれている状態で生成されています:
2016091306


画面内で Git のブランチ(この図では master)を表示する部分の右側にダウンロードアイコンが表示されています。現在のブランチの状態でソースコードをまとめてダウンロードする場合はこのアイコンをクリックします(今は行わなくて構いません):
2016091307


また、更にその右側に "Git URL" と書かれた箇所があり、そこをクリックすると改めて Git リポジトリの URL を確認することができます。忘れてしまった場合はこの方法で Git URL を確認してください(このすぐ後に使います):
2016091308


では試しにこの Git プロジェクトの中身を別マシンに clone してみましょう。Git が導入されている環境下で以下のように自分の Git URL(以下の例では https://hub.jazz.net/git/teyande/my1stApp)を指定して git clone してみます:
# git clone https://hub.jazz.net/git/teyande/my1stApp

成功するとこのように License.txt と README.md を含む my1stApp というディレクトリが作成され、現在のリポジトリがクローンできたことがわかります:
2016091301


次にこのプロジェクトディレクトリに移動して、ローカルマシンで変更を加えてみます。本当はここでは vim とか Sublime などの普段使い慣れたエディタ等を使ってファイルを編集することを想定していますが、以下の例では echo コマンドを使ってシンプルな phpinfo.php というファイルを1つ追加しています。何か新しいテキストファイルを1つ(以上)同じディレクトリ内に作成してみてください:
# cd my1stApp
# echo '<?php phpinfo(); ?>' > phpinfo.php

この時点でディレクトリにはもともと存在していた2つのファイルに加え、いくつかのファイルが追加されている状態になっています:
2016091302


ではこの変更を Gir リポジトリに反映させましょう。git add して、git commit します:
# git add .
# git commit -m 'phpinfo.php added.'

こんな感じでローカルリポジトリへのコミットが成功するはずです:
2016091303


では最後に、このローカルリポジトリへの変更をリモートリポジトリへも反映させるべく、git push します。途中でユーザー名やパスワードを聞かれるので、IBM Bluemix のIDおよびパスワードをそれぞれ指定して入力します:
# git push
  :
  :

Username for 'https://hub.jazz.net': bluemix@teyan.de
Password for 'https://bluemix@teyan.de@hub.jazz.net':
  :
  :

↓こんな感じになれば成功です:
2016091304


git push が成功すると、IDS 内のファイルにもその変更が反映されているはずです。この例では元のプロジェクトには存在していなかった phpinfo.php ファイルが git push によって送り込まれ、プロジェクトファイルの一部として追加されていることがわかります:
2016091305


とりあえず Git リポジトリとしての機能が使えることがわかりました。ただこれだけなら GitHub と変わりません。GitHub にない機能の1つとしては「(無料で)プライベートリポジトリが持てる」ことが挙げられます。例えば今回は作成時に Private チェックボックスをオフにしていたので、この時点ではリポジトリは公開されており、同じ URL を誰からでも(IBM Bluemix のアカウントを持っていない人からでも)参照することができる状態になっています:
2016091401


公開状態で作成されたリポジトリをプライベートな非公開状態に変更するには、リポジトリ画面右上の歯車アイコンをクリックし、左メニューから "OPTIONS" を選ぶと、"Private" のチェックボックスが現れます。ここにチェックが入っていれば非公開、入っていなければ公開状態となります:
2016091402


非公開状態に切り替えてみましょう。"Private" チェックボックスを ON にして、"SAVE" ボタンをクリックします:
2016091403


この状態で、同じリポジトリ URL にアクセスすると IBM Bluemix アカウントでログインしていない場合はログインが求められるようになります。また自分(リポジトリのオーナー)以外の ID でログインすると、以下の様な「非公開です」という画面だけが表示されて、中身を確認することはできなくなります:
2016091404


Git のリポジトリを非公開状態で管理しようとすると、GitHub では有償アカウントが必要になります。GitHub 以外にオープンソース製品などで独自にプライベートリポジトリを作れないことはないのですが、IBM Bluemix の IDS を使えば、実質的に無料でプライベートリポジトリまで含めて利用することができる、ということになりますね。 IDS は単なる Git のリポジトリ以上の機能がありますが、この無料のプライベートリポジトリ機能だけでも魅力的だと思っています。


Java で Git のリポジトリを操作するライブラリに JGit があります:
http://www.eclipse.org/jgit/
2016082801


現在は Eclipse 参加のサブプロジェクトとして提供されています。すごく便利な反面、ちょっとクセがあります。以下、基本的な使い方を説明しますが、まずは前提をいくつか:

【前提】
(1) 対象とする Git のリモートリポジトリ URI は http://xxxgit.com/name/project.git とします。
(2) 上記リポジトリにアクセス(特にプッシュ)する際の認証は ID: username, Password: password であるとします。
(3) ローカルリポジトリ(上記リポジトリをローカルにクローンする先)のディレクトリは ./project (つまりカレントディレクトリ上に project というサブディレクトリを作る)とします。


【準備】
JGit のダウンロードページから最新版の JGit をダウンロードし、展開して JAR を取り出し、自分のプロジェクト内に(Classpath を通すなどして)用意します。

ここまで用意できれば JGit を使うことができます。以下、一通りの git 操作(clone, pull, add, commit, push)を行う様子を順に紹介します。


【git clone】
目的のリモートリポジトリ(今回の例では http://xxxgit.com/name/project.git)からコードをクローンします:
try{
  Repository localRepo = new FileRepository( "./project/.git" );
  Git git = new Git( localRepo );

  if( git != null ){
    //. git clone
    git.cloneRepository().setURI( "http://xxxgit.com/name/project.git" ).setDirectory( new File( "./project" ) ).call();
  }
}catch( Exception e ){
  e.printStackTrace();
}

以下全ての例に言えることですが、ローカルリポジトリのインスタンス変数を作る際に指定するのは、ローカルリポジトリのフォルダに "/.git" をつけたものです。

一方、クローン実行時に指定するローカルフォルダはローカルリポジトリのフォルダそのものです。この辺りがクセのある所で、知らないと混乱します。


【git pull】
クローンしたローカルリポジトリに対し、リモートリポジトリに加えられている最新の変更を反映させます:
try{
  Repository localRepo = new FileRepository( "./project/.git" );
  Git git = new Git( localRepo );

  if( git != null ){
    //. git pull
    PullCommand pc = git.pull();
    pc.call();
  }
}catch( Exception e ){
  e.printStackTrace();
}

JGit では各コマンドを実行する際には ****Command クラスの(上記例では PullCommand クラス)インスタンスを作って、必要であれば設定を加えて、call() する、という処理を実行します。

そして次の add コマンドを実行する前に、このローカルリポジトリ内のファイルに何らかの変更が加わっていることを想定してください。


【git add】
ローカルリポジトリ内のファイルシステムに対して行った変更作業をリポジトリに加えます:
try{
  Repository localRepo = new FileRepository( "./project/.git" );
  Git git = new Git( localRepo );

  if( git != null ){
    //. git add
    AddCommand ac = git.add();
    ac.addFilepattern( "." );  //. 全ての変更を git add する
    try{
      ac.call();
    }catch( NoFilepatternException e ){
      e.printStackTrace();
    }
  }
}catch( Exception e ){
  e.printStackTrace();
}

AddCommand インスタンスにファイルパターンを指定して call() する、という手順です。


【git commit】
ローカルリポジトリの変更内容をコミットします:
try{
  Repository localRepo = new FileRepository( "./project/.git" );
  Git git = new Git( localRepo );

  if( git != null ){
    //. git commit
    CommitCommand cc = git.commit();
//. コミッターの名前とメールアドレス、コミットメッセージを指定 cc.setCommitter( "commiter_name", "committer_email" ).setMessage( "Some message." ); try{ cc.call(); }catch( NoHeadException e ){ e.printStackTrace(); }catch( NoMessageException e ){ e.printStackTrace(); }catch( ConcurrentRefUpdateException e ){ e.printStackTrace(); }catch( WrongRepositoryStateException e ){ e.printStackTrace(); } } }catch( Exception e ){ e.printStackTrace(); }

コミッターの名前とメールアドレス、そしてコミットメッセージを追加した上で call() します。


【git push】

ローカルリポジトリの変更内容をリモートリポジトリに対してプッシュします:
try{
  Repository localRepo = new FileRepository( "./project/.git" );
  Git git = new Git( localRepo );

  if( git != null ){
//. git push CredentialsProvider cp = new UsernamePasswordCredentialsProvider( username, password ); PushCommand pc = git.push(); pc.setCredentialsProvider( cp ).setForce( true ).setPushAll(); } }catch( Exception e ){ e.printStackTrace(); }

認証用の usernamepassword を指定してプッシュする、という流れです。


以上、JGit を使った基本的な git コマンドの実行方法を紹介しました。クセがある、と紹介しましたが、ある程度慣れてしまえばそんなに苦ではないと思います。何よりもプログラムから git が使えると複製機能をこれで実装できたりするのですごく便利です。

このページのトップヘ