別々に管理していた複数のリポジトリを統合する

# 背景

WEBアプリケーションのソースコードとそのWEBアプリケーションの管理コマンド（コンソールアプリケーション）のソースコードをそれぞれ異なるリポジトリで管理していたが、これらを統合して1つのリポジトリにしたい。やり方をググって調べた。


# 環境

Git for Windows 2.19.0


# 手順
実際に動くコードではなく適当なサンプルを作ってやってみた。
WEBアプリケーションのファイルがwebディレクトリに、コンソールアプリケーションのファイルがconsoleディレクトリにある。それぞれ独立した別個のリポジトリでバージョン管理している。

```

.
├console
└web
```


## 作業開始前の状態

### WEBアプリケーション

webディレクトリ以下は下記のような状態。ファイルが3個。3回コミットしている。

```shell
y_ito@note MINGW64 /c/work/git_test/web (master)
$ git ls-files
htdocs/index.php
tpl/page_a.html
tpl/top.html

y_ito@note MINGW64 /c/work/git_test/web (master)
$ git log --oneline --name-status
d80ab66 (HEAD -> master) commit 2 web
M       htdocs/index.php
A       tpl/page_a.html
7bc1991 commit 1 web
A       htdocs/index.php
A       tpl/top.html
1b7143c first commit

```

### コンソールアプリケーション

webディレクトリと同じレベルのconsoleディレクトリがある。ファイルが2個。3回コミットしている。

```shell
y_ito@note MINGW64 /c/work/git_test/console (master)
$ git ls-files
cmd01.php
cmd02.php

y_ito@note MINGW64 /c/work/git_test/console (master)
$ git log --oneline --name-status
171e35c (HEAD -> master) commit 2 console
A       cmd02.php
5b821b0 commit 1 console
A       cmd01.php
9c022e6 first commit

```

# 統合作業

[参考ページ1](#1)の方法を参考に、というかそのまま実施する。

web を tmp_web というディレクトリにcloneする。

```shell
y_ito@note MINGW64 /c/work/git_test
$ git clone web tmp_web
Cloning into 'tmp_web'...
done.

y_ito@note MINGW64 /c/work/git_test
$ cd tmp_web/

y_ito@note MINGW64 /c/work/git_test/tmp_web (master)
$ git ls-files
htdocs/index.php
tpl/page_a.html
tpl/top.html

```

tmp_web で 下記のコマンドを実行する。正直個々のコマンドの意味は分からないが、tmp_web 配下のファイルが tmp_web/web に移動される。`git log` で表示される各ファイルのパスも変更される。

```
git filter-branch --index-filter \
'git ls-files -s | sed "s@\t\"*@&web/@" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || true' HEAD
```

```shell
y_ito@note MINGW64 /c/work/git_test/tmp_web (master)
$ git filter-branch --index-filter \
> 'git ls-files -s | sed "s@\t\"*@&web/@" |
> GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
> mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || true' HEAD
Rewrite 1b7143cf964acff7adef594c844b23dbea1b3917 (1/3) (0 seconds passed, remaining 0 predicted)    mv: cannot stat 'C:/work/git_test/tmp_web/.git-rewrite/t/../index.new': No such file or directory
Rewrite d80ab662731bffd2c8d39502286e0c4a3c4375d1 (3/3) (0 seconds passed, remaining 0 predicted)
Ref 'refs/heads/master' was rewritten

y_ito@note MINGW64 /c/work/git_test/tmp_web (master)
$ git ls-files
web/htdocs/index.php
web/tpl/page_a.html
web/tpl/top.html

y_ito@note MINGW64 /c/work/git_test/tmp_web (master)
$ git log --oneline --name-status
3002144 (HEAD -> master) commit 2 web
M       web/htdocs/index.php
A       web/tpl/page_a.html
74a8852 commit 1 web
A       web/htdocs/index.php
A       web/tpl/top.html
1b7143c first commit

```

console の方も同様に `git clone` してよくわからないgitコマンドを実行する。

```
git filter-branch --index-filter \
'git ls-files -s | sed "s@\t\"*@&console/@" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
git update-index --index-info &&
mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || true' HEAD
```

```shell
y_ito@note MINGW64 /c/work/git_test
$ git clone console tmp_console
Cloning into 'tmp_console'...
done.

y_ito@note MINGW64 /c/work/git_test
$ cd tmp_console/

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git ls-files
cmd01.php
cmd02.php

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git filter-branch --index-filter \
> 'git ls-files -s | sed "s@\t\"*@&console/@" |
> GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
> git update-index --index-info &&
> mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || true' HEAD
Rewrite 9c022e6a7c00e68bff183543bb342f161e45bcb4 (1/3) (0 seconds passed, remaining 0 predicted)    mv: cannot stat 'C:/work/git_test/tmp_console/.git-rewrite/t/../index.new': No such file or directory
Rewrite 171e35c26f3e02899d81f5001a981c0ea67fce9f (2/3) (1 seconds passed, remaining 0 predicted)
Ref 'refs/heads/master' was rewritten

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git ls-files
console/cmd01.php
console/cmd02.php

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git log --oneline --name-status
de9f386 (HEAD -> master) commit 2 console
A       console/cmd02.php
93fa86a commit 1 console
A       console/cmd01.php
9c022e6 first commit

```

consoleをcloneした方で、webをcloneしたものをリモートとして登録し、fetchしてmergeする。
mergeの際にオプション `--allow-unrelated-histories` が必要。

```shell
y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git remote add web ../tmp_web

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git fetch web
warning: no common commits
remote: Enumerating objects: 16, done.
remote: Counting objects: 100% (16/16), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 16 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (16/16), done.
From ../tmp_web
 * [new branch]      master     -> web/master

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/web/master

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git merge web/master
fatal: refusing to merge unrelated histories

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git merge web/master --allow-unrelated-histories
Merge made by the 'recursive' strategy.
 web/htdocs/index.php |  8 ++++++++
 web/tpl/page_a.html  | 17 +++++++++++++++++
 web/tpl/top.html     | 24 ++++++++++++++++++++++++
 3 files changed, 49 insertions(+)
 create mode 100644 web/htdocs/index.php
 create mode 100644 web/tpl/page_a.html
 create mode 100644 web/tpl/top.html

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git ls-files
console/cmd01.php
console/cmd02.php
web/htdocs/index.php
web/tpl/page_a.html
web/tpl/top.html

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git log --oneline --name-status
5846cc0 (HEAD -> master) Merge remote-tracking branch 'web/master'
de9f386 commit 2 console
A       console/cmd02.php
3002144 (web/master) commit 2 web
M       web/htdocs/index.php
A       web/tpl/page_a.html
74a8852 commit 1 web
A       web/htdocs/index.php
A       web/tpl/top.html
1b7143c first commit
93fa86a commit 1 console
A       console/cmd01.php
9c022e6 first commit

```

これで2つのリポジトリが統合された。あとはどこか（Githubとかbacklogとか？）にpushすれば完了。
ここでは適当なbareリポジトリを作ってpushしてみる。

```shell
y_ito@note MINGW64 /c/work/git_test
$ git init --bare --shared z
Initialized empty shared Git repository in C:/work/git_test/z/

y_ito@note MINGW64 /c/work/git_test
$ cd tmp_console/

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git remote add z ../z

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ git push z master
Enumerating objects: 27, done.
Counting objects: 100% (27/27), done.
Delta compression using up to 4 threads
Compressing objects: 100% (15/15), done.
Writing objects: 100% (27/27), 2.26 KiB | 578.00 KiB/s, done.
Total 27 (delta 2), reused 0 (delta 0)
To ../z
 * [new branch]      master -> master

y_ito@note MINGW64 /c/work/git_test/tmp_console (master)
$ cd ../z

y_ito@note MINGW64 /c/work/git_test/z (BARE:master)
$ git branch -a
* master

y_ito@note MINGW64 /c/work/git_test/z (BARE:master)
$ git ls-tree --full-tree -r --name-only HEAD
console/cmd01.php
console/cmd02.php
web/htdocs/index.php
web/tpl/page_a.html
web/tpl/top.html

y_ito@note MINGW64 /c/work/git_test/z (BARE:master)
$ git log --oneline --name-status
5846cc0 (HEAD -> master) Merge remote-tracking branch 'web/master'
de9f386 commit 2 console
A       console/cmd02.php
3002144 commit 2 web
M       web/htdocs/index.php
A       web/tpl/page_a.html
74a8852 commit 1 web
A       web/htdocs/index.php
A       web/tpl/top.html
1b7143c first commit
93fa86a commit 1 console
A       console/cmd01.php
9c022e6 first commit

y_ito@note MINGW64 /c/work/git_test/z (BARE:master)
$ git log --graph --oneline
*   5846cc0 (HEAD -> master) Merge remote-tracking branch 'web/master'
|\
| * 3002144 commit 2 web
| * 74a8852 commit 1 web
| * 1b7143c first commit
* de9f386 commit 2 console
* 93fa86a commit 1 console
* 9c022e6 first commit

````

# 補足

`git filter-branch` で始まる長いコマンドを、改行を入れずに書くと下記のようになる。

```
git filter-branch --index-filter 'git ls-files -s | sed "s@\t\"*@&web/@" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || true' HEAD
```

git の処理としてもよく、わからないし、それ以前にシェルスクリプトもよくわかっていないが、部分的に調べて理解したことを書いておく。

### git filter-branch

`git filter-branch` は大量のコミットの書き換えを機械的に行う。
`git filter-branch --index-filter '<<コマンド>>'` は各コミットに対してクォートで囲った部分に書いたコマンドを実行する。


### git ls-files

`git ls-files -s` は「インデックスのオブジェクト名、mode bits、stage number を表示する」そうだが、mode bits と stage number とはなんだろう？
`git ls-files -s` 単独で実行すると下記のようになる。パスの前はタブ文字。

```shell
y_ito@note MINGW64 /c/tmp/work/git_test/tmp2_web (master)
$ git ls-files -s
100644 0cd341eaf60035609b1303b0d474a3fdbf0109a5 0       htdocs/index.php
100644 53970b75f87927f678373e9acf79074a7b3e5b5b 0       tpl/page_a.html
100644 05b1c16f43461896ff3071ab4781baeb1c0d6077 0       tpl/top.html
```

### sed

`git ls-files -s` の出力をパイプで sed に渡している。
"@" は区切り文字。区切り文字にはスラッシュを使う場合が多いがスラッシュでなければいけないということはないので、パターンや置換後の文字列にスラッシュが含まれる場合は "@" や他の文字を使うとよい。
\# スラッシュ以外なら "@" を使うことが多い？
"&" はマッチしたもの全体。よって `sed "s@\t\"*@&web/@"` は「タブ文字＋ダブルクォート0文字以上」を「タブ文字＋ダブルクォート0文字以上web/」と置換される。
`git ls-files -s | sed "s@\t\"*@&web/@"` を実行するとこうなる。

```shell
y_ito@note MINGW64 /c/tmp/work/git_test/tmp2_web (master)
$ git ls-files -s | sed "s@\t\"*@&web/@"
100644 0cd341eaf60035609b1303b0d474a3fdbf0109a5 0       web/htdocs/index.php
100644 53970b75f87927f678373e9acf79074a7b3e5b5b 0       web/tpl/page_a.html
100644 05b1c16f43461896ff3071ab4781baeb1c0d6077 0       web/tpl/top.html
```

### GIT_INDEX_FILE

GITの環境変数の1つ。インデックスファイルのパス。
設定していない場合は $GIT_DIR/index をインデックスファイルのパスとして使用する。

### git update-index

`git update-index --index-info` は標準入力から読み込んでインデックスを更新する。

### && と || 

`command1 && command2` はcommand1が正常終了した（終了ステータスが0）場合にcommand2を実行する。
`command1 || command2` はcommand1が異常終了した（終了ステータスが0ではない）場合にcommand2を実行する。


# 参考ページ

1.[履歴を残したまま複数のgitリポジトリを統合する](https://qiita.com/edvakf@github/items/9e7ccbaa944d26f9b69c)
2.[Git 別のリポジトリを履歴を残したまま取り込みたい - かもメモ](https://chaika.hatenablog.com/entry/2015/06/04/173401)
3.[複数のリポジトリをマージしようとすると fatal: refusing to merge unrelated histories が出る | ハックノート](https://hacknote.jp/archives/31307/)
4.[Git 別のリポジトリを履歴を残したまま取り込みたい - かもメモ](https://chaika.hatenablog.com/entry/2015/06/04/173401)
5.[sed の区切り文字 - Invalid signature](http://kazuya3.hateblo.jp/entry/2014/03/11/092954)

### git fiter-branch
[Git - 歴史の書き換え](https://git-scm.com/book/ja/v1/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E6%AD%B4%E5%8F%B2%E3%81%AE%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88)
[git filter-branchで過去の全てのcommitから画像ファイルの追加/変更をなかったことにしてリポジトリを軽量化する - dskd](https://dskd.jp/archives/46.html)
[git最強のオプション filter-branch - Qiita](https://qiita.com/Spring_MT/items/f60c391b5dbf569a1d12)
[git filter-branch とは？ - Qiita](https://qiita.com/m-yamazaki/items/035101d4c5a6e8666992)

### git ls-files
[git ls-files - Qiita](https://qiita.com/zimathon/items/d964c4b3cc688cb2d114)

### sed
[ワンライナーでお手軽実行可能なsed入門 - Qiita](https://qiita.com/muran001/items/472abcfc353d5df7b77a)

### gitの環境変数
[Git - 環境変数](https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0)
[環境変数 — 読書ノート v1.5dev](https://showa-yojyo.github.io/notebook/git/envvars.html)
[Gitで使用できる環境変数 - Qiita](https://showa-yojyo.github.io/notebook/git/envvars.html)

### git update-index
[Git - git-update-index Documentation](https://git-scm.com/docs/git-update-index)

### シェルスクリプトの&&と||
[便利だが理解を要する制御演算子 \`&&\` と \`||\` - Qiita](https://qiita.com/miyu/items/f467c3fd693b5a8d5472)
[(Linux)コマンドを連結して行う｢;｣｢&｣｢&&｣｢||｣の違い : old_3流プログラマのメモ書き](https://jehupc.exblog.jp/15729095/)