プロジェクト

全般

プロフィール

Gitリポジトリ管理

Gitのリポジトリ管理についてのメモ。

Gitのリポジトリをまとめたい

2つのリポジトリの内容を1つのリポジトリとしてまとめる

2つのリポジトリがあり、その内容を1つのリポジトリにまとめたいときの操作です。

やりたいこと

alfa                            bravo
  +-- .git                        +-- .git
  +-- explain_alfa.txt            +-- explain_bravo.txt
  +-- doc                         +-- doc
        +-- HelloAlfa.txt               +-- HelloBravo.txt

この2つのリポジトリを次のように1つにまとめます。

alfa
  +-- .git
  +-- explain_alfa.txt
  +-- explain_bravo.txt
  +-- doc
        +-- HelloAlfa.txt
        +-- HelloBravo.txt

操作方法

alfa$ git remote add bravo path/to/bravo/
alfa$ git fetch bravo
warning: no common commits
remote: Counting objects: 9, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 9 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (9/9), done.
From ../bravo
 * [new branch]      master     -> bravo/master

alfa$ git merge bravo/master
Merge made by the 'recursive' strategy.
 explain_bravo.txt  |    1 +
 doc/HelloBravo.txt |   27 +++++++++++++++++++++++++++
 2 files changed, 28 insertions(+), 0 deletions(-)
 create mode 100755 explain_bravo.txt
 create mode 100755 doc/HelloBravo.txt

alfa$ git log --oneline
aae6546 Merge remote-tracking branch 'bravo/master'
ddccd53 alfaリポジトリの初期登録
f522a18 Bravoファイルの修正
d808ee8 bravoリポジトリの初期登録

$

マージした結果はコミットされた状態となっています。履歴はリポジトリalfaとbravoのものが合わさっています。

リポジトリを分離

リポジトリのサブディレクトリを独立したリポジトリで管理する

Gitは、サブディレクトリだけをクローンすることができません。そのため、最初は1つのリポジトリにいろいろ放り込んでいたけれど、だんだん大きくなり、分離したくなってきた場合に、特定のサブディレクトリを履歴を伴って新しいリポジトリとして独立させます。

元のリポジトリをクローン

work$ git clone path/to/myproject project_alfa
work$ ls project_alfa
alfa/   bravo/  charlie/   delta/
work$

このうち、alfaディレクトリだけを独立して別リポジトリにします。

独立するサブディレクトリをfilter指定

work$ cd project_alfa
project_alfa$ git filter-branch --subdirectory-filter alfa
Rewrite dcb5e6b37892f11e95aa42f56c1cf719ed808798 (1/22) (0 seconds passed, remai
  :
project_alfa$ ls
build.xml     project/   src/
project_alfa$

リモートリポジトリの作成と登録

独立したリポジトリを共有するリモートリポジトリを新規に作成します。
先ほど作成したディレクトリでリモートを設定します。設定方法は後述の「手元のリポジトリからGitHubにリポジトリを作る」を参照。

サブモジュール

リポジトリの内部に、他のリポジトリを組み込みます。

サブモジュールを含むリポジトリのクローン

サブモジュールを含むリポジトリを通常通りクローンすると、サブモジュールの中身が空となります。
サブモジュールもクローン時に展開するには、git clone に --recursiveオプションを指定します。

リモートリポジトリ

手元のリポジトリからGitHubにリポジトリを作る

ちょっとしたプログラムを手元のリポジトリで作成した後、GitHubに上げたくなった場合の操作です。

  • GitHubに新規リポジトリを作成

すると、作成後に表示される画面中に、"... or push an existing repository from the command line"(コマンドラインで既存のリポジトリをpushする)という項に手順が記載されています。

 git remote add origin https://github.com/torutk/jjugccc2015spring-javafx.git
 git push -u origin master
  • 手元のリポジトリに実施してみたら
someapp$ git remote add origin https://github.com/torutk/jjugccc2015spring-javafx.git
someapp$ fatal: remote origin already exists.

とエラーに。
今回は、手元のリポジトリをcloneして作成したディレクトリで実施しています。そのときにoriginが設定されたと思われます。
対処は、ローカルのoriginを削除して上述手順の実施です。

someapp$ git remote rm origin
someapp$ git remote add origin https://github.com/torutk/jjugccc2015spring-javafx.git
someapp$ git push -u origin master
git: 'credential-wincred' is not a git command. See 'git --help'.
Username for 'https://github.com': 
Password for 'https://torutk@github.com': 
git: 'credential-wincred' is not a git command. See 'git --help'.
Counting objects: 102, done.
Delta compression using up to 6 threads.
Compressing objects: 100% (76/76), done.
Writing objects: 100% (102/102), 58.08 KiB | 0 bytes/s, done.
Total 102 (delta 35), reused 0 (delta 0)
To https://github.com/torutk/jjugccc2015spring-javafx.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

リモートリポジトリのサブディレクトリのみをチェックアウト

  • リポジトリのルート直下だけをクローン
work$ git clone --sparse http://www.torutk.com:8008/git/swe.primus
  :
work$ cd swe.primus
swe.primus$ 
  • ???
swe.primus$ git sparse-checkout init --cone
swe.primus$ 
  • チェックアウトしたいサブディレクトリを指定
swe.primus$ git sparse-checkout add learn/kotlin/javafx
swe.primus$ 

リポジトリ内のファイルの管理

改行コードを揃える

Windows OSと、Unix系OS(LinuxやMacOS Xなど)とがリポジトリを共有すると、ファイルの改行コードが問題に上がります。

Windows OSは、改行コードをCR・LF(0x0d 0x0a)の2キャラクターで扱い、Unix系OSは改行コードをLF(0x0a)の1キャラクターで扱います。
そのため、Windows OS上で作成したファイルをUnix系OS上で開くと行末にCR(0x0d)が付いた(テキストファイルとしては化けた)ファイルになってしまいます。逆に、Unix系OS上で作成したファイルをWindows OS上で(改行コードLFに対応していないエディタ等で)開くと改行と認識できず全ての行がつながったファイルになってしまいます。

また、ファイルの一部を編集した場合、一つのファイルにおいて改行コードがCR・LFの行とLFの行とが混在してしまうこともよくあります。

そこで、改行コードが異なるOS間でリポジトリを共有する場合、改行コードを揃えて管理する必要があります。改行コードを揃える場合、LFにしておくのが対応容易です。

改行コードを揃える方法は2つあります。

No 方法 利点 欠点
1 リポジトリ利用側でcore.autocrlfを設定 利用者が意識的に制御可 利用者の設定ミスで改行が揃わない
2 リポジトリ自体の.gitattributesに設定 方法を強制可 利用者が意識的に改行を制御不可
作業ファイルの改行コードをLFで扱わねばならない
  • 方法1は、リポジトリを共有する利用者のうち1人がcore.autocrlfの設定をfalseにして、改行コードをWindows OSのデフォルトであるCR・LFのファイルをコミットしてしまった場合、リポジトリのファイルの改行コードが揃わなくなってしまいます。また、Windows OS上でUnix系OSのシェルスクリプトファイルを取り出して配布等をすると、改行コードがCR・LFとなっています。
  • 方法2は、Windows OS上で作業ファイルをLFで扱わねばならないので、改行コードLFを扱えるようツールを設定する等の付加作業が生じます。また、CR・LFでなければならないファイル(およそWindows OS固有のファイルでバッチファイルやVC++のプロジェクト定義ファイル等)は.gitattributesに改行コード固定設定を記述する必要があります。

それぞれの利点・欠点を考慮して、利用するシーンに応じて適切な方法を選択します。

改行コードをLFに揃える方法(リポジトリ利用側で設定)

リポジトリに格納するテキストファイルの改行コードはLFで揃え、各OS上からGitリポジトリへ変更をコミットする際に改行コードをLFへ自動変換する方法です。gitの設定で、core.autocrlfをinputまたはautoに設定します。

  • Unix系OSでの設定
    $ git config --global core.autocrlf=input
    
  • Windows OSでの設定
    $ git config --global core.autocrlf=auto
    

inputは、テキストファイルをリポジトリに登録する時に改行コードをCR・LFからLFへ変換します。リポジトリからテキストファイルを取得する時は改行コードの変換はしません。

autoは、テキストファイルをリポジトリに登録する時に改行コードをCR・LFからLFへ変換します。リポジトリからテキストファイルを取得する時は、改行コードをLFからOSの標準改行コードへ変換します(Windows OSであればCR・LFへ、Unix系OSであればLFのまま)。

改行コードをLFに揃える方法(リポジトリ側で設定)

リポジトリのルート直下に.gitattributesを作成し、そこに改行コードの設定を記述します。この設定は、利用者側のcore.autocrlfの設定より優先されます。

Gitに改行コードの制御をおまかせとし、一部のファイルについて明示的に指定する設定

Gitがテキストファイルと認識したファイルについてリポジトリ内では改行コードをLFに揃えます。そのファイルをリポジトリから取り出したときは、OSの改行コードに変換されます。以下のように、全てのファイルのtext属性をautoに設定することで、Gitがテキストファイルと認識したファイルの改行コードの変換が行われます。

* text=auto

特定のファイル名(パターン)に一致するファイルがテキストファイルであるかバイナリファイルであるかを、Gitの自動認識に任せずに設定することができます。

*.c text
*.h text

*.png binary
*.jpg binary

特定のファイル名(パターン)に一致するファイルをテキストとして設定し、かつ取り出した際の改行コードをOSによらず固定することができます。

*.sln text eol=crlf
*.sh text eol=lf

Dealing with line ending - GitHub Help

Gitおまかせにはせず、明示的に設定

いったんすべてのファイルをバイナリ扱い(-textですべてのファイルのtext属性を削除し、かつeol属性を未設定)にします。

* -text !eol

つづいて、テキストファイルとして扱いたいファイル名パターンを逐一指定します。

*.csv text
*.java text
*.mf text
*.properties text
*.txt text 
*.xml text

  • .java text eolのようにeol属性を指定する必要があるか否か要調査

作業ディレクトリ上のファイルの改行コードを指定する場合は、前の方法と同様

*.bat text eol=crlf
*.sh text eol=lf

とします。

バイナリファイルへの改行コード変換の適用有無を調べる

実験(1)LFを含むバイナリファイル

改行コード(LF: 0x0a)が含まれる0x00~0x0fの16バイトを書いたファイルを作成します。

$ hexdump -C binfile 
00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|
00000010

これを、autocrlf=falseな環境(Linux)でadd、commit、pushします。

次に、autocrlf=trueな環境(Windows)でpullしてきます。

$ git pull
  :
Fast-forward
 app/libs/binfile | Bin 0 -> 16 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 app/libs/binfile

この場合は、バイナリは変換されず(元のまま)出てきました。

実験(2)CRLFを含むバイナリファイル

改行コード(CRLF: 0x0d 0x0a)が含まれる0x00~0x0fの16バイトを書いたファイルを作成します。

$ hexdump -C binfile-b
00000000  00 01 02 03 04 05 06 07  08 09 0d 0a 0b 0c 0e 0f  |................|
00000010

これを、autocrlf=trueな環境(Windows)でadd、commit、pushします。

次に、autocrlf=falseな環境(Linux)でpullしてきます。

 app/libs/binfile-b | Bin 0 -> 16 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100755 app/libs/binfile-b

この場合は、バイナリは変換されず(元のまま)出てきました。

実験(3)LFおよびCRLFを含むバイナリファイル

改行コード(LF: 0x0a、およびCRLF: 0x0d 0x0a)が含まれる0x00~0x0fの32バイトを書いたファイルを作成します。

$ hexdump -C binfile-c
00000000  00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |................|
00000010  00 01 02 03 04 05 06 07  08 09 0d 0a 0b 0c 0e 0f  |................|

これをautocrlf=false(Linux)でコミットし、autocrlf=true(Windows)でpullしてみましたが、バイナリは変換されずに出てきました。

実験(4)LFを含みファイル先頭2バイトをキャラクタコード(0x20~0x7f)としたバイナリファイル
$ hexdump -C binfile-d
00000000  31 32 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f  |12..............|
00000010

これをautocrlf=false(Linux)でコミットし、autocrlf=true(Windows)でpullしてみましたが、バイナリは変換されずに出てきました。

 app/libs/binfile-d | 2 ++
 1 file changed, 2 insertions(+)
 create mode 100644 app/libs/binfile-d

ここで、前とは違い、Binと出てないのですが、0aが変換されてはいませんでした。

バイナリファイルに対して改行コード変換を適用してしまう可能性と対策

gitの設定には、core.safecrlfがあります。これをtrueにしておくと、改行コードが混在したファイルに対する改行コードの自動変換が発生する処理を拒否します。これは、対象ファイルが万が一テキストではなくバイナリファイルのとき、回復不能な変更をしてしまうのを未然に防止するためです。
gitのテキスト・バイナリ自動認識に対してリスクがあると認識し、リスク回避策を打つならば、core.safecrlfをtrueに設定することと、.gitattributes設定ファイルでテキストファイルとバイナリファイルのマッチパターンを手動で指定することができます。

リポジトリのタグ

タグの種類

  • 軽量タグ(lightweight tag)
  • 注釈付きタグ(annotated tag)

軽量タグは、特定のコミットにエイリアス名を付けるだけで他の情報を持ちません。
注釈付きタグは、タグ作成者の情報、作成日、コメントなどを保持します。

タグの作成

git tag name-of-tag 軽量タグを現在のコミットに付与
git tag -a name-of-tag -m "commit message" 注釈付きタグを現在のコミットに、メッセージを付けて付与

リポジトリ内のブランチ管理

ブランチの名前を変えたい

ローカルのブランチの名前を変更(リモートにはブランチなし)

$ git branch -m <変更前のブランチ名> <変更後のブランチ名>

ローカルおよびリモートのブランチ名を変更

まず、上述の手順でローカルのブランチ名を変更します。
次に、名前を変更したブランチを新規ブランチとしてリモートに上げます。

$ git push origin <変更後のブランチ名>

旧名のブランチをリモートから削除します。名前を指定して削除もありますが、ここでは

$ git push origin :<変更前のブランチ名>

と実行します。ブランチ名の前にコロンを指定するのを忘れずに。

リモートリポジトリのブランチを削除したい

ブランチ名を指定して削除

$ git push --delete origin <削除するブランチ名>

リモートリポジトリで削除されたブランチをローカルに反映したい

$ git fetch --prune

フック関連

pushされたらメール通知したい

共有リポジトリにpushされたらメール通知する

共有リポジトリ(例:/var/lib/git/swe_nolla.git)のmasterブランチにpushがあったらメール通知をする設定を行います。

  • post-receiveスクリプトをhooksの下に作成(例:/var/lib/git/swe_nolla.git/hooks/post-receive)
    ネット上の情報では、hooksの下にpost-receive.sampleがあるとのことですが、手元の環境(CentOS 6で、Repoforgeのgit-1.7.11、Wandiscoのgit-2.0.0)ではpost-receive.sampleが存在しないので、たまたまredmineのプラグインをgit cloneでインストールした場所のhooksにあったものを利用し、最終行のコメントアウトを解除(有効化)しました。
#!/bin/sh
#
# An example hook script for the "post-receive" event.
#
# The "post-receive" script is run after receive-pack has accepted a pack
# and the repository has been updated.  It is passed arguments in through
# stdin in the form
#  <oldrev> <newrev> <refname>
# For example:
#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
#
# see contrib/hooks/ for a sample, or uncomment the next line and
# rename the file to "post-receive".

. /usr/share/git-core/contrib/hooks/post-receive-email

/usr/share/git-core/contrib/hooks/post-receive-emailは、git-1.7.11にもあるので上述のままで実行可能です。
このemailスクリプトは、gitのconfigから設定を読み取るので、リポジトリのconfigに設定を記述します。

  • 通知メール設定を記述(例:/var/lib/git/swe_nolla.git/config)
[hooks]
        mailinglist = torutk@example.com, torutk@example.org        ← push時の宛先アドレス、
        emailprefix = "[git] "                                      ← 題名の接頭辞(省略時は[SCM])
        envelopesender = git@example.com                            ← 送信元メールアドレス
  • メール題名に記述されるリポジトリ要約を記述(例:/var/lib/git/swe_nolla.git/description)
A software engineer's primary repository
日本語の文字化け時

メールの日本語が文字化けするとき、次のファイルにMIME設定を追記します。
/usr/share/git-core/contrib/hooks/post-receive-email

 generate_email_header()
 {
         # --- Email (all stdout will be the email)
         # Generate header
         cat <<-EOF
         To: $recipients
         Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe
+        MIME-Version: 1.0
+        Content-Type: text/plain; charset="utf-8" 
         X-Git-Refname: $refname
         X-Git-Reftype: $refname_type
         X-Git-Oldrev: $oldrev
         X-Git-Newrev: $newrev

共有リポジトリのmasterブランチにpushされたらメール通知する

先の設定だと、どのブランチにpushされても通知が飛ぶので、特定のブランチだけに限定します。

  • post-receiveスクリプトにブランチ名抽出を追記(例:/var/lib/git/swe_nolla.git/hooks/post-receive)
#!/bin/sh

while read oldrev newrev refname; do
    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
    if [[ "$branch" == "master" ]]; then
        echo "$oldrev" "$newrev" "$refname" | . /usr/share/git-core/contrib/hooks/post-receive-email
    fi
done

post-receiveスクリプト呼び出し時は、パラメータは引数ではなく標準入力で3つ受け取ります。それをreadで拾ってブランチ名を抽出します。
次にブランチ名がmasterであれば先のメール送信スクリプトを動かします。メール送信スクリプトも、post-receiveと同じくパラメータを標準入力で3つ受け取ります。ところが、既に標準入力は読み取り済みなので、再度echoで出してパイプで標準入力で読めるようにして呼び出します。

作業ルール

参考文献


3ヶ月前に更新