２つのフォルダを比較して更新・追加された行数を数える [Mac·Linux]

#### 背景

webアプリの開発プロジェクトを前任の方から引き継いだ。
プロジェクトはrailsフレームワークを用いており、前任の方がある程度進めているようだった。

無事納品した際、依頼主より「前任の方のコントリビューションはいくらか」と聞かれた。どうやら報酬の参考にするとのこと。

これは責任重大である。適当に答えれば主観が入りまくってしまう。
何か定量的指標を設けなければ。

そうだ、行数でコントリビューションを評価しよう\[^1]

以上が事の経緯である。

\[^1]： 行数がコントリビューションに一致するわけではない。しかし！あまり深く考えるとキリがない。行数とコントリビューションは相関するのでヨシ！

#### 1. 差分ファイルの比較

ここでは更新のあったファイル・ディレクトリのリストを取得する。

folder1が新しいフォルダ
folder2が古いフォルダとする。

```shell
folder1="./new_project"
folder2="./old_project"

diff -rqw $folder1 $folder2
```

```shell
#Output
Only in [folder]: [newly_created_file]
Files [modified_in_folder1] and [modified_in_folder2] 
```

diff でfolder1とfolder2の間で更新あるいは新規作成されたディレクトリやファイルが表示される。
更新されたものは Files ~ differ と表示され、新規作成されたものは Only in ~~ と表示される。
新規作成されたディレクトリの子要素は表示されない。



#### 2. diffコマンド戻り値のパス化

diffでめでたく更新フォルダ·ファイルを得られたと思いきや、そのフォーマットはパスになっていない。
パスで欲しいんじゃ。
diffの戻り値をfor文で1行ずつ取り出してパス化する。

```shell
while read -r line; do

    modified_string=${line#"Only in "}
    modified_string=${modified_string//: /\/}    
 
    modified_string=${modified_string#"Files "}
    modified_string=${modified_string%% and*}


    file="${modified_string//$folder1/}"
    
done <<< "$diff_output"
```
1行目 `read -r line`は標準入力から一行ずつ読み込み変数lineに格納する。ここでは直前に実行した`diff -rqw [folder1] [folder2]`の戻り値が入力となる。

diffの戻り値を一行ずつ取り出し、フォーマットをパスの形に落とし込んでいく。

- `modified_string=${line#"remove_char"}`
文字列lineよりremove_characterを削除

- `modified_string=${modified_string//pattern/replace/}   `
patternをreplaceに置換

modified_string はfolder1からのパス`./folder1/file1`になっている。
`./folder1`の部分を削除しfolder1以下からのパスにする。

`file="${modified_string//$folder1/}"`



#### 3. 更新行数の出力

変数countを用意し、各ファイルの行数をcountへ加算していく。
`count=$((count + [number_of_updated_lines]))`

```shell
while read -r line; do

    modified_string=${line#"Only in "}
    modified_string=${modified_string//: /\/}    
 
    modified_string=${modified_string#"Files "}
    modified_string=${modified_string%% and*}


    file="${modified_string//$folder1/}"

    if [ -f "$folder2$file" ]; then
        diff_lines=$(diff -u "$folder1$file" "$folder2$file" | grep -E '^\+|^-|^ ' | wc -l)
        echo "更新ファイル: $file, 差分行数: $diff_lines"
        count=$((count + diff_lines))
    else
        if [ -d "$folder1$file" ]; then
            # ファイルがディレクトリの場合    
            # ディレクトリ内の各ファイルに対して行数をカウントする
            for element in "$folder1$file"/*; do
                if [ -f "$element" ]; then
                    if echo "$element" | grep -iq "\.png$"; then
                        continue
                    fi
                    if echo "$element" | grep -iq "\.avif$"; then
                        continue
                    fi
                    if echo "$element" | grep -iq "\.jpeg$"; then
                        continue
                    fi
                    line_count=$(wc -l < "$element")
                    echo "新規ディレクトリ内ファイル: $element, 行数: $line_count"
                    count=$((count + line_count))
                fi
            done
        else
            line_conut=$(wc -l < "$folder1$file")
            echo "新規ファイル:$file 行数: $(wc -l < "$line_count")"
            count=$((count + line_count))
        fi
    fi
      
```

`[ -f "$folder2$file" ]`で古いフォルダ folder2に"file"があることを確認する。
`diff_lines=$(diff -uw "$folder1$file" "$folder2$file" | grep -E '^\+|^-|^ ' | wc -l)`で更新行数を数える。

folder2に"file"がない場合は新規作成ファイル\/フォルダである。新規作成ファイルは全部の行数をカウントする。

変数`file`がディレクトリならば、新規作成ディレクトリの子要素はdiffコマンドで得られてないため子要素を取得する。

```shell

if [ -d "$folder1$file" ]; then
    # ファイルがディレクトリの場合    
    # ディレクトリ内の各ファイルに対して行数をカウントする
    for element in "$folder1$file"/*; do
        if [ -f "$element" ]; then
            line_count=$(wc -l < "$element")
            echo "新規ディレクトリ内ファイル: $element, 行数: $line_count"
            count=$((count + line_count))
        fi
    done
else

```

完成系は以下のようになる。


```shell

#!/bin/bash

folder1="./new_project"
folder2="./old_project"
 
diff_output=$(diff -rqw $folder1 $folder2 | grep -E "controller|views|models|db|$folder1" | grep -v -e "tmp" -e "storage" -e ".git")

count=0

while read -r line; do

    modified_string=${line#"Only in "}
    modified_string=${modified_string//: /\/}    
    modified_string=${modified_string#"Files "}
    modified_string=${modified_string%% and*}
    file="${modified_string//$folder1/}"
    
    echo "========"
    echo $Files

    if [ -f "$folder2$file" ]; then
        diff_lines=$(diff -uw "$folder1$file" "$folder2$file" | grep -E '^\+|^-|^ ' | wc -l)
        echo "更新ファイル: $file, 差分行数: $diff_lines"
        count=$((count + diff_lines))
    else
        if [ -d "$folder1$file" ]; then

            for element in "$folder1$file"/*; do
                if [ -f "$element" ]; then
                    if echo "$element" | grep -iq "\.png$"; then
                        continue
                    fi
                    if echo "$element" | grep -iq "\.avif$"; then
                        continue
                    fi
                    if echo "$element" | grep -iq "\.jpeg$"; then
                        continue
                    fi
                    line_count=$(wc -l < "$element")
                    echo "新規ディレクトリ内ファイル: $element, 行数: $line_count"
                    count=$((count + line_count))
                fi
            done
        else
            line_conut=$(wc -l < "$folder1$file")
            echo "新規ファイル:$file 行数: $(wc -l < "$line_count")"
            count=$((count + line_count))
        fi
    fi 

done <<< "$diff_output"

echo "total $count"

```

いろんなオプションがついたが、私が使ったのはrailsフレームワークであるため、主な編集対象であるcontroller, view, model, dbディレクトリに絞りたかった。また他の自動生成されるファイルも除外したい。
それで grepにより結果を絞っている。

`diff -rqw $folder1 $folder2 | grep -E "controller|views|models|db|$folder1" | grep -v -e "tmp" -e "storage" -e ".git"`

また、ディレクトリ子要素を取り出す際に画像も出てきてしまったので、
`if echo "$element" | grep -iq "\.png$"; then continue fi`
で除外している。

ここら辺は各プロジェクトに依存するため編集して使っていただきたい。


#### 結果

さて、行数を数えることができた。数えたのは３つ。
1. 引き継ぎ時フォルダ(前任者の成果) vs 前任者が用いたRails原型フレームワーク
2. 自分の納品時フォルダ vs 前任者が用いたRails原型フレームワーク
3. 自分の納品時フォルダ vs 引き継ぎ時フォルダ(前任者の成果)

結果,
1. +4481行
2. +4930行
3. +1858行
( + は削除した行も含まれる[コメントを受け追記])

図示すると次の図になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380579/3e7df6d0-63ff-3307-4272-be65c27484f8.png)


前任者のコントリビューションは 4930-1858 = 3072と考えられる。 全体の62%を占める貢献度だと分かった。

ただし、初期の導入コストも考えると、前任者はもうすこし高いコントリビューションになるだろう。
一方で、私は設計を見直したので、その分コントリビューションが加算されても良いだろう。
このように、行数に関係のないコントリビューションはたくさんある。

アルゴリズムの冗長性、可読性、ロジック、複雑性、評価しようと思えば指標にキリがない。
ざっくり行数で評価してあとは依頼主側のお気持ちを加えるのが、フェアかなあ

---
**コメントを受け追記**

図について:
納品時で原型と比較し4930行の追加/削除があり、
引き継ぎ時点と納品時を比較すると、1858行の追加/削除があったらしい。

一回矢印が後退しているが、これはフォルダごと削除され比較されなかったファイルがあるためかと思われる。
(削除した行は考慮されるが、ファイルごと削除したら考慮されない。ここは要改善)

ごっそり消えた行数は　1858-(4930-4481) = 1409
行を消すことが最も生産的な作業とのコメントをいただいた。

そこで、この削除されたファイル1409行分をコントリビューションに加え計算し直すと、私のコントリビューションは 1409+1858　になる...のか？

闇に消えたコードがある分、図のような解釈へ一意に定まるとは言い難い。

----