[個人開発]GoogleSpreadSheetをgit管理のような事をする！とっても愚かなCSVコンテンツトラッカー 「sit」

## はじめに

gitコマンドの管理をGoogleSpreadSheetでした人はいても、GoogleSpreadSheet自体のgit管理はよく問題にされては今まで諦められてきました。

![sit用の画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113181/ce45ca4f-e366-5f31-2777-a3ab4d34ba75.png)


「GoogleSpreadSheetをgit管理するのは無理なんじゃないか」

たしかに無理です。(断定していいのか？...む、無理だよね？) 

しかし、git管理に近い事はできるという事をこの記事で示したいと思います。これから紹介するコマンドは完全ではないものの、ちょっと希望を感じれるそんなコマンドです。

その名は `sit` です。

## 対象読者

- GoogleSpreadSheetの魔改造に興味がある人
- おもちゃCLI大好きな人
- 何か新しい刺激をもとめている人

## What is sit?

`sit` は非常に非常に愚かなcsvコンテンツトラッカーです。gitのようにGoogleSpreadSheetCSVデータを管理したいという考えから生まれました。だから、 `sit` は `git` に非常に似ています。 `sit` という名前は`sheet git` に由来します。ですが違いがあります。`git` は `git` コマンドだけで完結できますが、 `sit` はできません。GoogleSpreadSheetのスクリプトを管理するために [clasp](https://github.com/google/clasp/) コマンドを使用する必要があります。

これからチュートリアルを通して説明していきます。

## Tutorial

チュートリアルは以下の項目を用意しています。

- Install sit
- Initialize sit
- Create local repository
  - Setting user.name and user.email
-Create remote repository
 - Setting Sheet Sharing
- Edit data and Push for GoogleSpreadSheet
 - Create remote master sheet
 - Checkout and Edit data and Push
- Fetch from GoogleSpreadSheet
- Resolve Conflict
- Stash File Changes
- Create Pull Request
- [Learn More](https://yukihirop.github.io/sit)

## Install sit

まず `sit` コマンドをインストールします。

```bash
npm install -g @yukihirop/sit
```

## Initialize sit

ローカルリポジトリを作成する前に、`.sitsetting` という設定ファイルを作成する必要があります。

最低限必要な設定は認証設定です。その他の設定については、[SitSetting](https://yukihirop.github.io/sit/#/sitsetting/index)を参照してください。

```bash
$ mkdir sit-tutorial
$ cd sit-tutorial
$ sit init
created setting file: /Users/yukihirop/JavaScriptProjects/sit-tutorial/.sitsetting
```

`.sitsetting` は以下のように生成されます。

```yaml
version: 1.0.0
sheet:
  gss:
    auth:
      credPath: ./creds.json
    openAPIV3Schema:
      type: object
      properties:
        ja:
          type: string
          description: 日本語
        en:
          type: string
          description: 英語
        key:
          type: string
          description: キー
    defaultWorksheet:
      rowCount: 10000
      colCount: 20
repo:
  local: .sit
dist:
  path: ./dist
  sheetName: master_data.csv
```

それではGoogleSpreadSheetにアクセスするための認証設定をしていきます。

GoogleCloudPlatformにアクセスし、サービスアカウントを発行します。

![image](https://user-images.githubusercontent.com/11146767/77819902-d0d99000-7121-11ea-83d6-accdefa8bbef.png)

次に、認証ファイルをcreds.jsonとしてダウンロードします。

![image](https://user-images.githubusercontent.com/11146767/77820003-72f97800-7122-11ea-807d-d4e4f86f748b.png)

もし正しくダウンロードできれば `sit-tutorial` のディレクトリは以下のようになってます。

![image](https://user-images.githubusercontent.com/11146767/77820057-cf5c9780-7122-11ea-9459-ad2763dc066d.png)

これでGoogleSpreadSheetにアクセスできるようになったといいたいのですが、実はもうちょい設定が必要です。
続きは `Create Remote Repository` で説明します。

📌Google Sheets APIが有効になっている事を確認してください

![image](https://user-images.githubusercontent.com/11146767/77845547-ac95b600-71ea-11ea-8ff4-013ced488bdd.png)

## Create local repository

ローカルリポジトリを作成しましょう。
ローカルリポジトリの名前 `.sitsetting` の設定で変更できますが、ここではデフォルトの `.sit` を使用します。

```bash
$ sit repo init
created local repository: ./.sit
created dist file: dist/master_data.csv
created script files: .sit/scripts/clasp
```

このとき、以下の3つのファイルが生成されます。

- sit管理をするために必要なファイル (.sit以下のファイル)
- 管理対象の `dist/master_data.csv`
- `GoogleSpreadSheet` に設定されるスクリプト

3番目のスクリプトファイルはリモートリポジトリを作るのに使われます。

#### user.nameとuser.emailの設定

gitでも設定するように `user.name`と `user.email` を設定すると、誰がコミットしたかなどがわかり、ログがわかりやすくなります。なので設定しましょう。

グローバル設定とローカル設定がありますが、ここではローカルに設定します。

```bash
sit config --local user.name yukihirop
sit config --local user.email yukihirop@example.com
```

設定されたか確認してみましょう。 `.sit/config` で設定されていることがわかります。

```bash
$ cat .sit/config
[user]
name=yukihirop
email=yukihirop@example.com
```

## Create remote repository

ここいうリモートリポジトリはGoogleSpreadSheetであり、GASスクリプトによって疑似リポジトリとして動作できるものを指します。したがって、GoogleSpreadSheetを生成し、GASスクリプトを設定する必要があります。これは `clasp` コマンドを使用して行われます。

```bash
$ clasp create --type sheets
Created new Google Sheet: https://drive.google.com/open?id=1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8
Created new Google Sheets Add-on script: https://script.google.com/d/1GmpSBRbopUky8dkWTHIX88Vj47lHmH8q8melU_zkSEaM0uslRvozbj6c/edit
Warning: files in subfolder are not accounted for unless you set a '.claspignore' file.
Cloned 1 file.
└─ appsscript.json
```

`GoogleDrive` タイプのURLのままでは使用できないため、アクセスして `GoogleSpreadSheet` タイプのURLを取得します。

![image](https://user-images.githubusercontent.com/11146767/77844917-7570d600-71e5-11ea-84d7-7d3631018f72.png)

この例では以下のようなURLになります。

`https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit#gid=0`

取得したURLを `origin` に設定します。これで、`origin` という名前でリモートリポジトリにアクセスできます。

`git` と同じですね。

```bash
$ sit remote add origin https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit\#gid\=0
```

正しく設定されたか確認してみましょう。

```bash
$ sit remote get-url origin
https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit#gid=0
```

次に、生成されたGoogleSpreadSheetにGASスクリプトを設定します。設定にはclaspコマンドを使用します。

```bash
$ clasp push
└─ .sit/scripts/clasp/Code.js
└─ .sit/scripts/clasp/RemoteRepo.js
└─ .sit/scripts/clasp/const.js
└─ .sit/scripts/clasp/util.js
└─ appsscript.json
Pushed 5 files.
```

```
$ clasp deploy
Created version 1.
- AKfycbx7zOoxj4opUQrqA0xtryXvxdYUihPgwGkeJwKmsul6wmf9V-kDbYEReZYU5Op56A7CgQ @1.
```

さぁ、確認してみましょう。

```bash
$ clasp open
Opening script: https://script.google.com/d/1GmpSBRbopUky8dkWTHIX88Vj47lHmH8q8melU_zkSEaM0uslRvozbj6c/edit
```

![image](https://user-images.githubusercontent.com/11146767/77845224-16609080-71e8-11ea-808a-75b6a2dc920e.png)

GASスクリプトがちゃんと設定されていることがわかります。

#### Setting Sheet Sharing

ダウンロードした `creds.json` に書かれた `client_email` のメールアドレスを使って共有設定を行います。

![image](https://user-images.githubusercontent.com/11146767/77845415-b834ad00-71e9-11ea-9e29-bd7e0e92359b.png)

これで `GoogleSpreadSheet` にアクセスする準備は整いました。

## Edit data and Push for GoogleSpreadSheet

次にデータを更新して `GoogleSpreadSheet` にpushしてみましょう。

まずはgitを使う時でもそうであるようにmasterブランチを作成してpushします。

#### Create remote master sheet

まずは初期状態を確認します。

```bash
$ sit status
On branch master

    modified: dist/master_data.csv

no changes added to commit
```

```bash
$ sit branch
```

```bash
$ sit diff
Index: 0000000..35daa93
===================================================================
--- a/dist/master_data.csv
+++ b/dist/master_data.csv
@@ -1,0 +1,1 @@
\ No newline at end of file
+日本語,英語,キー
\ No newline at end of file

```

つまり、デフォルトでは、追加されるヘッダー分だけ差分が発生してます。

コミットします。(`sit add` に相当するものは内部コマンドとして実行されます。)

```bash
$ sit commit -m "Initial Commit"
[master 0aa608d] Initial Commit
```

それではpushしましょう。

```bash
$ sit push origin master
Writed objects: 100% (1/1)
Total 1
remote:
remote: Create a pull request for master on GoogleSpreadSheet by visiting:
remote:     https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit#gid=2143308413
remote:
To https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit#gid=2143308413
    * [new branch]    0000000..0aa608d  master -> master
```

正しくpushされたか確認してみましょう。

```bash
$ sit browse-remote
```

![image](https://user-images.githubusercontent.com/11146767/78094760-4be7c280-7410-11ea-912b-66196f532167.png)

正しくpushされている事が確認できます。

## Checkout and Edit data and Push

現在、`master`ブランチにいるので、`develop`ブランチにチェックアウトします。

```bash
$ sit checkout -b develop
Switched to a new branch 'develop'
```

```bash
$ sit branch
* develop
  master
```

`dist/master_data.csv` を以下のように修正しましょう。

```bash
日本語,英語,キー
こんにちは,hello,greeting.hello
```

差分を確認してコミットします。

```bash
$ sit diff
Index: 35daa93..b0122f0
===================================================================
--- a/dist/master_data.csv
+++ b/dist/master_data.csv
@@ -1,1 +1,2 @@
-日本語,英語,キー
\ No newline at end of file
+日本語,英語,キー
+こんにちは,hello,greeting.hello
\ No newline at end of file
```

```bash
$ sit commit -m "Initial Commit"
[develop 60243bc] Initial Commit
```

ログを確認してみましょう。

```bash
$ sit log
commit 60243bc893424001185de58890104bc49e853ae5 (HEAD -> develop)
Author: yukihirop <yukihirop@example.com>
Date: Sun Mar 0 18:33:57 2020 +0900 +0900

    Initial Commit

```

コミットが確認できたので、プッシュしましょう。

```bash
$ sit push origin develop
Writed objects: 100% (1/1)
Total 1
remote:
remote: Create a pull request for develop on GoogleSpreadSheet by visiting:
remote:     https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit#gid=0
remote:
To https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit#gid=0
    * [new branch]    0000000..60243bc  develop -> develop
```

シートを開いて確認してみましょう。

```bash
$ sit browse-remote
```

![image](https://user-images.githubusercontent.com/11146767/77845934-7c9be200-71ed-11ea-829a-1d44dcbfe1fa.png)

## Fetch and Merge from GoogleSpreadSheet

今、 GoogleSpreadSheetの `develop` ブランチが `master` ブランチにマージされて以下のような状態になったとします。

![image](https://user-images.githubusercontent.com/11146767/78098918-56f42000-741b-11ea-858f-f989aa9ae901.png)

📌残念ながら、GoogleSpreadSheet同士のマージは手動になります。

では、リモートブランチをfetchしてみましょう。

```bash
$ sit fetch origin master
remote: Total 1
From https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit#gid=2143308413
  * branch        master    -> FETCH_HEAD
  0aa608d..5f027c5    master    -> origin/master
```

fetchしてきた内容を確認してみましょう。

```
$ sit cat-file -p 5f027c5
blob b0122f0795b0be80d51a7ff6946f00bf0300e723
parent 0aa608d96db68ac609bfb27de5036fe63a75fcd5
author yukihirop <yukihirop@example.com> 1585488810621 +0900
committer GoogleSpreadSheet <noreply@googlespreadsheet.com> 1585488810621 +0900

Merge from GoogleSpreadSheet/master
```

```bash
$ sit cat-file -p b0122f0795b0be80d51a7ff6946f00bf0300e723
日本語,英語,キー
こんにちは,hello,greeting.hello
```

fetchに確かに成功していることが確認できました。

それではfetchしてきたorigin/masterブランチをマージしてみましょう。

```bash
$ sit merge origin master
The current branch is 'develop'
Sorry... Only the same branch ('origin/develop') on the remote can be merged
```

📌`sit`の仕様によってマージされるローカルブランチとリモートブランチは、一致する名前を持つ必要があります。

したがって、`master`ブランチに一度チェックアウトする必要があります。

```bash
$ sit checkout master
Switched to branch 'master'
```

```bash
$ sit merge origin master
Updating b98f96a..aab61a3

Fast-forward
  dist/master_data.csv
  1 file changed
```

`dist/master_data.csv` を確認すると、マージされていることが確認できます。

```bash
$ cat dist/master_data.csv
日本語,英語,キー
こんにちは,hello,greeting.hello%
```

## Resolve Conflict

今、GoogleSpreadSheetの`master`を手動で変更したとします。

![image](https://user-images.githubusercontent.com/11146767/77930836-57d76580-72e6-11ea-9b9f-5a41cb994557.png)

この状態で `fetch` して `merge` します。

```bash
$ sit fetch origin master
remote: Total 1
From https://docs.google.com/spreadsheets/d/1QPNoQcKGQrk1U9_5RP42y7YbWZMcPcN-Vn96wO2ZCh8/edit#gid=1549813878
  * branch        master    -> FETCH_HEAD
  87fbc98..00323a3    master    -> origin/master
```

```bash
$ sit merge origin master
Two-way-merging dist/master_data.csv
CONFLICT (content): Merge conflict in dist/master_data.csv
two-way-merge failed; fix conflicts and then commit the result.
```

コンフリクトが置きました。 `sitオブジェクト` をリモートリポジトリ(GoogleSpreadSheet)で管理できないため
three way mergeが使えず、two way mergeでマージを行っているのでコンフリクトは起きやすくなってます。

![image](https://user-images.githubusercontent.com/11146767/77931180-cddbcc80-72e6-11ea-86de-daf13f4b6d74.png)

この状態でのマージの様子を見てみましょう。

```bash
$ sit merge --stat
fatal: You have not concluded your merge (MERGE_HEAD exists)
Please, commit your changes before you merge.
```

「`MERGE_HEAD` が存在している。競合を解決してください」というメッセージがでます。

ここで一旦、mergeを取り消してみましょう。

```bash
$ sit merge --abort
$ sit merge --stat
Already up to date.
```

![image](https://user-images.githubusercontent.com/11146767/77932990-40e64280-72e9-11ea-8806-0c2b4e33e8ae.png)

元にもどりました。gitと同じで `--abort` で取り消す事が可能です。

もう一度、`merge` してみましょう。

```bash
$ sit merge origin master
Two-way-merging dist/master_data.csv
CONFLICT (content): Merge conflict in dist/master_data.csv
two-way-merge failed; fix conflicts and then commit the result.
```

次のようにコンフリクトを解消して、 `--continue` をしてみてください。

![image](https://user-images.githubusercontent.com/11146767/77934118-966f1f00-72ea-11ea-92eb-17cc014ab99c.png)

```bash
$ sit merge --continue
hint: Waiting for your editor to close the file...
```

![image](https://user-images.githubusercontent.com/11146767/77934276-d0d8bc00-72ea-11ea-9ab3-f86611f779d0.png)

起動したファイルを閉じ、Ctrl + Cを押してマージを完了します。

```bash
$ sit merge --continue
hint: Waiting for your editor to close the file...
^C
```

それではmergeの状態を調べてみましょう。

```bash
$ sit merge --stat
Already up to date.
```

```bash
$ sit log
commit aab61a36fff2bf2dacc87d416a83b6c0e15a569c (HEAD -> master)
Author: yukihirop <yukihirop@example.com>
Date: Tue Mar 2 00:21:45 2020 +0900 +0900

    Merge from GoogleSpreadSheet/master

commit b98f96a746dba25e2c927beda41cf9fa361a44ab
Author: yukihirop <yukihirop@example.com>
Date: Tue Mar 2 00:11:31 2020 +0900 +0900

    Initial Commit
```

マージが成功しました。

## Stash File Changes

次のようにファイルを編集して、`stash` してみましょう。

![image](https://user-images.githubusercontent.com/11146767/77989866-d241cd00-735a-11ea-85a7-d56c8dc74871.png)

```bash
$ sit stash save
Saved working directory and index state WIP on master: 5fe2e48 Merge remote-tracking branch 'origin/master'
```

`stash`した結果の詳細を確認してみましょう。 `dist/master_data.csv` ファイルを確認してみましょう。

![image](https://user-images.githubusercontent.com/11146767/77990168-a4a95380-735b-11ea-810d-df676b079f24.png)


```bash
$ sit stash list
ffe9070 stash@{0}: On master: WIP
```

```bash
$ sit stash show -p stash@{0}
Index: 9bb9157..0e90f47
===================================================================
--- a/dist/master_data.csv
+++ b/dist/master_data.csv
@@ -1,3 +1,4 @@
 日本語,英語,キー
 こんにちは,hello,greeting.hello
-おやすみ,good night,greeting.good_night
\ No newline at end of file
+おやすみ,good night,greeting.good_night
+歓迎します,wellcome,greeting.wellcome
\ No newline at end of file

```

`stash`した内容の復元をしてみましょう。 `stash apply` を使います。

📌`stash@{0}` は省略できます。`stash@{n} (n>0)`は省略できないです。

```bash
$ sit stash apply stash@{0}
On branch master
Changes not staged for commit:

    modified:    dist/master_data.csv

no changes added to commit
```

もう一度、`stash`して、 次は`pop`でもどしてみましょう。

```bash
$ sit stash save "WIP"
Saved working directory and index state On master: WIP
```

```bash
$ sit stash list
2f077ef stash@{0}: On master: WIP
7284116 stash@{1}: WIP on master: 08d2106 Merge remote-tracking branch 'origin/master'
```

```bash
$ sit stash pop
On branch master
Changes not staged for commit:

    modified:    dist/master_data.csv

Dropped stash@{0} (2f077ef6551e339e1808e6fbb38f27424be46b8d)
```

gitと同様に、popはスタッシュの内容を削除します。

```bash
$ sit stash list
7284116 stash@{0}: WIP on master: 08d2106 Merge remote-tracking branch 'origin/master'
```

## Create Pull Request

`GoogleSpreadSheet`に、`master`とは異なる名前（たとえば、`develop`）という新しいシートを追加しましたが、GitHubのように`develop`ブランチから`master`ブランチにプルリクエストを作成することもできます。

`master`

![image](https://user-images.githubusercontent.com/11146767/77992365-e2f54180-7360-11ea-9958-ba1c4810eba0.png)

`develop`

![image](https://user-images.githubusercontent.com/11146767/77992432-00c2a680-7361-11ea-8019-a6db4cd8d3e2.png)

`develop`ブランチから`master`ブランチにプルリクエストを作成してみましょう。

```bash
$ sit pull-request origin master...develop
Total 1
remote:
remote: Create a pull request for 'master' from 'develop' on GoogleSpreadSheet by visiting:
remote:     https://docs.google.com/spreadsheets/d/1xZ6egbhsuqIh8kMsfeg8vCkyFGabZQybcxKuc2F7Le4/edit#gid=2115668596
remote:
To https://docs.google.com/spreadsheets/d/1xZ6egbhsuqIh8kMsfeg8vCkyFGabZQybcxKuc2F7Le4/edit#gid=2115668596
    Please look at sheet: '[pr] master...develop' in GoogleSpreadSheet
```

📌実行されるたびにローカルリポジトリに変更を生成する`push`コマンドとは異なり、`pull-request`コマンドはステートレスコマンドであり、何度でも実行できます。

`GoogleSpreadSheet` をご覧ください。`[pr]master...develop` という名前でシートが作られていると思います。

![image](https://user-images.githubusercontent.com/11146767/77992891-f5bc4600-7361-11ea-89b8-21e99bced717.png)

📌`[pr]`で始まるシート（ブランチ）名は、プルリクエスト用に予約されているため、ブランチ名として使用できません。

他のシートと違いフッターがあると思います。それについて説明します。

|name|description|
|----|-----------|
|`created at`|作成日時|
|`reviewers`|レビュワー|
|`assignees`|PRを作成した人|
|`message`|PRの内容を簡潔に書く|
|`labels`|自由に活用してください|
|`projects`|自由に活用してください|
|`milestone`|自由に活用してください|

少なくとも `reviewrs`・ `message` はかくべきだと考えます。

次に、[ステータス]列について説明します。 [ステータス]列では3つの記号が使用されています。

|symbol|description|
|----|-----------|
|+|追加|
|-|削除|
|±|追加される側にだけ存在|

つまり、現在の例では、`index==2` の行が変更され、`index==3`の列は`master`シートにのみ存在し、そのままです。`master`シートへのマージはサポートされていないため、問題がなければ、責任者が手動で`master`シートを更新してください。

## Learn More

その他のコマンドの使い方などをまとめた公式ドキュメントがあります。そちらをご参照ください。

https://yukihirop.github.io/sit

## おわりに

このコマンドを作ったきっかけは会社で引き継いだプロジェクトの翻訳ファイルがGoogleSpreadSheetで管理されていて、それを目で読み取ってyamlファイル(ja.yamlとen.yaml)として別途作成して管理していいる現状を目にして「__なんや...この管理のやり方は...__」と思ったのが全てのきっかけだった。GoogleSpreadSheetで管理されているのはデザイナーとのやり取りの都合上、仕方のないものだと言われた。手動で作った翻訳ファイル(ja.yaml・en.yaml)がGoogleSpreadSheetで管理されているものと一致していれば、「 __まぁ...こういうやり方もあるのかな？__ 」 で終わっていたのかもしれないが、かなりズレていて合わせるための修正のだるさから「 __gitみたいに管理できたらなぁ__ 」と強く思ってしまった。

それを形にするまでには、大変だった。 まず [Git の仕組み (1)](http://koseki.hatenablog.com/entry/2014/04/22/inside-git-1) のブログと [p10.1 Git Internals](https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain)を読んで「gitオブジェクトとは何か？」を勉強して、 [Write yourself a Git!](https://wyag.thb.lt/)というpythonでgitコマンドを割と再現していく練習をこなしてイメージを掴んだ後、実装に取り掛かった。gitのソースコードを見たがちっともわからなかったので、 `git init` したプロジェクトにできた `.git` の中で `git init` して各コマンドを叩いた後のファイルの変化をおって想像で実装した。gitのソースコードを見てわかったのは全てC言語で書かれているのかなって思っていたけど、perlで書かれていたり、shellscriptで書かれていたりとバラバラだった事と[stashコマンド](https://github.com/git/git/blob/v2.22.0/git-legacy-stash.sh)を作ったのは日本人女性だったという事である。

調査なども含めて3ヶ月〜４ヶ月くらいはかかったがとりあえず最後までやり遂げる事ができてよかった。

せっかく作ったのでGoogleSpreadSheetで管理されている翻訳ファイルをローカルにcloneしてローカルに持ってきて、今まで手動で作っていた翻訳ファイル(ja.yaml・en.yaml)を自動で作成するようにやってみようと思う。