Help us understand the problem. What is going on with this article?

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

はじめに

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

sit用の画像.png

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

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

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

その名は sit です。

対象読者

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

What is sit?

sit は非常に非常に愚かなcsvコンテンツトラッカーです。gitのようにGoogleSpreadSheetCSVデータを管理したいという考えから生まれました。だから、 sitgit に非常に似ています。 sit という名前はsheet git に由来します。ですが違いがあります。gitgit コマンドだけで完結できますが、 sit はできません。GoogleSpreadSheetのスクリプトを管理するために 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

Install sit

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

npm install -g @yukihirop/sit

Initialize sit

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

最低限必要な設定は認証設定です。その他の設定については、SitSettingを参照してください。

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

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

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

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

image

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

image

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

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

image

Create local repository

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

$ 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.nameuser.email を設定すると、誰がコミットしたかなどがわかり、ログがわかりやすくなります。なので設定しましょう。

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

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

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

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

Create remote repository

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

$ 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

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

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

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

git と同じですね。

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

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

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

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

$ 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.

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

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

image

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

Setting Sheet Sharing

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

image

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

Edit data and Push for GoogleSpreadSheet

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

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

Create remote master sheet

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

$ sit status
On branch master

    modified: dist/master_data.csv

no changes added to commit
$ sit branch
$ 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 に相当するものは内部コマンドとして実行されます。)

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

それではpushしましょう。

$ 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されたか確認してみましょう。

$ sit browse-remote

image

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

Checkout and Edit data and Push

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

$ sit checkout -b develop
Switched to a new branch 'develop'
$ sit branch
* develop
  master

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

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

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

$ 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
$ sit commit -m "Initial Commit"
[develop 60243bc] Initial Commit

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

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

    Initial Commit

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

$ 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

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

$ sit browse-remote

image

Fetch and Merge from GoogleSpreadSheet

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

image

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

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

$ 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
$ sit cat-file -p b0122f0795b0be80d51a7ff6946f00bf0300e723
日本語,英語,キー
こんにちは,hello,greeting.hello

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

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

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

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

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

$ sit checkout master
Switched to branch 'master'
$ sit merge origin master
Updating b98f96a..aab61a3

Fast-forward
  dist/master_data.csv
  1 file changed

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

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

Resolve Conflict

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

image

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

$ 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
$ 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

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

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

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

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

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

image

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

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

$ 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

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

image

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

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

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

$ sit merge --stat
Already up to date.
$ 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

$ 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

$ sit stash list
ffe9070 stash@{0}: On master: WIP
$ 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)は省略できないです。

$ 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でもどしてみましょう。

$ sit stash save "WIP"
Saved working directory and index state On master: WIP
$ sit stash list
2f077ef stash@{0}: On master: WIP
7284116 stash@{1}: WIP on master: 08d2106 Merge remote-tracking branch 'origin/master'
$ sit stash pop
On branch master
Changes not staged for commit:

    modified:    dist/master_data.csv

Dropped stash@{0} (2f077ef6551e339e1808e6fbb38f27424be46b8d)

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

$ 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

develop

image

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

$ 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

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

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

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

少なくとも reviewrsmessage はかくべきだと考えます。

次に、[ステータス]列について説明します。 [ステータス]列では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) のブログと p10.1 Git Internalsを読んで「gitオブジェクトとは何か?」を勉強して、 Write yourself a Git!というpythonでgitコマンドを割と再現していく練習をこなしてイメージを掴んだ後、実装に取り掛かった。gitのソースコードを見たがちっともわからなかったので、 git init したプロジェクトにできた .git の中で git init して各コマンドを叩いた後のファイルの変化をおって想像で実装した。gitのソースコードを見てわかったのは全てC言語で書かれているのかなって思っていたけど、perlで書かれていたり、shellscriptで書かれていたりとバラバラだった事とstashコマンドを作ったのは日本人女性だったという事である。

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

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

yukihirop
気の向くまま。意の向くままにコードを書くプログラマー。 役に立つツールを作るのって本当に難しい。
https://creator-of-what.yukihirop.me/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした