再エクスポートしたファイル一式を、前の作業フォルダにそのまま重ねた夜のことです。表示の崩れを手で直した箇所が、翌朝には元の形に戻っておりました。
戻ったことよりも、丸一日それに気づかなかったことのほうが応えました。差分を見ていなかったからです。フォルダを上書きした時点で、前の状態はもうどこにも残っておりませんでした。
個人開発でアプリをいくつか長く運用しておりますので、コードの変更を目で追う習慣はあるつもりでおりました。それでも相手が生成されたものになると、私自身「丸ごと受け取って丸ごと置き換える」という乱暴な扱いに流れておりました。
いまは、エクスポートのたびに同じ順番で読んでから取り込んでいます。覚えることは少なく、コマンドは4つだけです。
生成されたものを、自分の枝に直接おろさない
エクスポートは差分ではなく、その時点のプロジェクト一式です。つまり受け取った瞬間に、自分が手で入れた修正を巻き込む力を持っています。
ですので、受け取る場所と育てる場所を分けます。生成結果だけが降りてくる枝を1本用意し、自分が手を入れる枝はそのまま残しておきます。
生成の結果は枝で受け取り、合流は自分の目で決めます。 この一行だけは、急いでいる日でも崩さないようにしております。
# 受け取り用の枝へ移ります(無ければ作ります)
git switch rork-export 2>/dev/null || git switch -c rork-exportGitHub への出力を使っている方は、その出力先のブランチをそのまま受け取り用として扱えます。手元にファイル一式として降ろしている方は、上のように自分で1本作るところから始めていただければと思います。
追跡中のファイルを一度消してから重ねます
ここが、この手順でいちばん効く一手です。
新しいエクスポートを古いフォルダへ上から重ねると、追加と変更は見えます。ところが消えたファイルだけは見えません。生成側で削除された画面やコンポーネントが手元に残り続け、差分にも現れないためです。
消したはずの古い設定画面がビルドに入ったまま出ていた、という事故はこの経路で起きます。ですので、重ねる前に追跡中のファイルだけを落とします。
# いま Git が追跡しているファイルだけを消します
# (.git と、追跡していない手元のメモや .env は残ります)
git ls-files -z | xargs -0 rm -f
# 新しいエクスポートを重ねます(.git は触りません)
rsync -a --exclude '.git/' "$EXPORT_DIR"/ .
# 削除・追加・変更がすべて index に載ります
git add -A
git status --short | head -20期待される出力はこのような形です。
D src/screens/OldSettings.tsx
M package.json
M app.json
A src/screens/Settings.tsx
先頭の D が、上書きだけでは決して出てこなかった行です。この一行を落としていたのだと気づいたのは、消したつもりの画面がもう一度ビルドに入っていた日のことでした。
git ls-files は追跡中のファイルしか並べませんので、.gitignore で除外してある .env や鍵は消えません——逆に言えば、それらを追跡してしまっていると、ここで一緒に消えます。最初の git init より先に除外設定を置く理由は、Rork のコードをエクスポートしたら、git init より先に .gitignore を用意します に書き残しております。
差分は、雑音を外してから数えます
git diff をそのまま開くと、ロックファイルと生成ディレクトリで画面が埋まります。数千行の差分を前にすると、人はまず読むのをやめます。読むのをやめた差分は、承認したことと同じ意味になります。
そこで、数える前に外します。
git diff --cached --stat -- . \
':(exclude)package-lock.json' \
':(exclude)yarn.lock' \
':(exclude)ios' \
':(exclude)android'':(exclude)…' は Git のパス指定に付けられる除外の書き方です。.gitignore と違って追跡状態を変えませんので、その場の読み方を変えるだけで済みます。残るのはこのくらいの分量になります。
app.json | 6 ++++--
package.json | 3 ++-
src/screens/Settings.tsx | 84 ++++++++++++++++++++++++++++++---
3 files changed, 79 insertions(+), 14 deletions(-)
何を外してよいかは、自分がその場所を手で触っているかどうかで決まります。
| 対象 | ふだん外してよい理由 | 外してはいけない場合 |
|---|---|---|
package-lock.json / yarn.lock | 依存の解決結果で、意図は package.json 側に出るため | 特定の版に固定した経緯がある場合 |
ios/ / android/ | prebuild で作り直される生成物である場合が多いため | ネイティブ側を手で編集している場合 |
| アイコン・スプラッシュ画像 | 差分が読めないバイナリで、見た目で確認したほうが早いため | 差し替えた覚えがないのに変わっている場合 |
ネイティブのフォルダを手で触っている方は、ここを外さずに読みます。アップロードの範囲と追跡の範囲がずれる話は .easignore があると .gitignore は読まれません — EAS Build にアップロードされる範囲を確かめる のほうに寄せてあります。
毎回かならず開く3つのファイル
分量を減らしたら、次は順番です。私は行数の多い画面ファイルから読まずに、小さいほうから開きます。設定と依存は、たった数行で挙動を変えてしまうためです。
# 設定と依存だけを先に読みます
git diff --cached -- app.json app.config.ts package.json見ているのは3点です。
- 権限と用途文字列。カメラや位置情報の宣言が1行増えているだけで、ストアの審査で説明する責任が増えます。使っていない権限が入っていたら、その場で戻します。
- 依存の版。生成のたびに上がる版があります。上がったこと自体は問題ではなく、上がったと知らないまま出すことが問題になります。
- 直書きされた値。本来は環境変数に置くはずの鍵が、そのままコードに現れることがあります。
3つめは目で追うと抜けますので、追加された行だけを拾います。
# 追加行(+)の中から、鍵らしき記述だけを拾います
# 誤検出は前提で、拾いすぎるくらいがちょうどよい設定です
git diff --cached -U0 -- '*.ts' '*.tsx' '*.js' '*.swift' '*.kt' \
| grep '^+' \
| grep -Ei '(api[_-]?key|secret|token)[^a-z0-9]{0,3}[=:]' \
| head-U0 は前後の文脈行を落とす指定で、これを付けないと変更していない行まで + 側に混ざって読みにくくなります。何も出てこなければ、それが今回の答えです。
合流は、コミットの前に一度止めます
最後に、読み終えた差分を自分の枝へ入れます。ここで --no-commit を付けるかどうかが、私にとっては分かれ目でした。
git switch main
git merge --no-commit --no-ff rork-export
# 自分の枝に入る直前の、最後の確認です
git diff --cached --stat
# 手で直した箇所が残っていることを見てから
git commit -m "Rork の生成結果を取り込み"--no-commit は、合流の結果を index に置いたまま止めてくれます。ここで手元の修正が消えていれば、git checkout HEAD -- <ファイル> で自分の側を取り戻してからコミットできます。止まる場所がひとつあるだけで、冒頭の夜のような取り返しのつかなさは消えます。
競合が出たときは、迷わず自分が手で直した側を残します。生成されたものは、もう一度頼めば作り直せるためです。手で直した理由のほうは、同じ形では戻ってきません。
差分が読めるようになったあとに残るのは、「どれを自分で直し、どれをもう一度 Rork に頼むか」という別の判断です。その線の引き方は Rork が直せるバグと自分で直すバグを見分ける — エクスポートコードのトリアージ手順 にまとめてあります。
次の再エクスポートのときに、受け取り用の枝を1本だけ作ってみてください。読む手順はそのあとから足しても間に合います。私もそこから始めました。
最後までお読みくださり、ありがとうございました。