Gitサブモジュールでハマった時の解決法!実務で使える具体的対処法

Gitサブモジュールとは:よくある誤解
Gitサブモジュールは、あるGitリポジトリの中に別のGitリポジトリを埋め込む機能です。この機能は非常に便利ですが、多くの開発者が誤解していたり、適切な使い方を知らないために問題に直面しています。
最もよくある誤解の一つは「サブモジュールは単なるリポジトリへのリンクだ」というものです。実際には、サブモジュールはメインリポジトリ内で特定のコミットを指しているポインタです。この違いを理解していないと、「なぜサブモジュールが更新されないのか」という疑問につながります。
# サブモジュールの正体を確認する方法
cat .gitmodules
# 出力例
[submodule "libs/common"]
path = libs/common
url = https://github.com/example/common.git
サブモジュールが指しているコミットは、メインリポジトリの .git/modules
ディレクトリや、メインリポジトリのコミットに記録されています。開発者が勘違いしがちなのは、サブモジュールディレクトリの中で git pull
を実行すれば、その更新がメインリポジトリにも自動的に反映されると思い込むことです。しかし実際には、サブモジュールでの変更をメインリポジトリに反映するには、追加のコミットが必要になります。
# サブモジュールが現在指しているコミットを確認
git submodule status
# 出力例
a1b2c3d4e5f6g7h8i9j0k libs/common (v1.2.3)
コードの先頭のハッシュ値(上の例では a1b2c3d4e5f6g7h8i9j0k)が、サブモジュールが現在指しているコミットです。この点を正しく理解していないと、後述するようなトラブルに遭遇します。
もう一つの誤解は「サブモジュールは常に最新版を取得する」というものです。クローンしたばかりのリポジトリでは、サブモジュールのディレクトリは存在していても、中身は空であることが多いです。これは、サブモジュールの内容を取得するには追加のコマンドが必要だからです。
これらの誤解を解消し、サブモジュールを正しく使うための具体的な方法を、以降のセクションで詳しく解説します。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
初回クローン時に起こる「空のディレクトリ」問題の解決法
多くの開発者が初めてサブモジュールを含むリポジトリをクローンした際に遭遇する問題が、「サブモジュールディレクトリが空になっている」というものです。通常の git clone
コマンドでは、サブモジュールのディレクトリ構造は作成されますが、その中身(ファイル)は取得されません。
# 通常のクローン方法
git clone https://github.com/example/main-project.git
cd main-project
ls -la libs/common
# 出力: ディレクトリはあるが中身が空(.gitやその他のファイルがない)
この問題を解決するには、以下の2つの方法があります。
方法1: クローン後にサブモジュールを初期化して更新する
# 既にクローンしたリポジトリの場合
git submodule init # サブモジュールを初期化
git submodule update # サブモジュールの内容を取得
# または、1コマンドで両方を実行
git submodule update --init
# 再帰的にすべてのサブモジュール(サブモジュール内のサブモジュールも含む)を更新
git submodule update --init --recursive
方法2: クローン時にサブモジュールも一緒に取得する
新しくリポジトリをクローンする際には、--recursive
オプションを使うことで、サブモジュールの内容も同時に取得できます。
# サブモジュールも一緒にクローンする方法
git clone --recursive https://github.com/example/main-project.git
# Git 2.13以降では、より直感的な --recurse-submodules オプションも使えます
git clone --recurse-submodules https://github.com/example/main-project.git
実際の作業フローにおける注意点
多くのCI/CDパイプラインやデプロイスクリプトでは、リポジトリのクローン後にビルドやテストを行いますが、サブモジュールの初期化を忘れると問題が発生します。
# CI/CDパイプラインの例(誤ったパターン)
git clone https://github.com/example/main-project.git
cd main-project
npm install # サブモジュール内のコードに依存していると失敗する
npm test # 同様に失敗する可能性が高い
# 正しいパターン
git clone --recursive https://github.com/example/main-project.git
cd main-project
npm install
npm test
特に大規模なプロジェクトや、複数のサブモジュールを持つリポジトリでは、このサブモジュールの初期化ステップを忘れがちです。チームの新しいメンバーや、CIシステムのセットアップ時には特に注意が必要です。
初回クローン時の問題を防ぐために、プロジェクトのREADMEファイルにサブモジュールの取得方法を明示的に記載しておくと良いでしょう。
## Getting Started
This repository uses Git submodules. To clone the repository with all submodules, use:
```bash
git clone --recursive https://github.com/example/main-project.git
If you've already cloned the repository without --recursive option, you can fetch the submodules by running:
git submodule update --init --recursive
これらの方法を理解しておくことで、「空のディレクトリ」問題によるフラストレーションを回避できます。
## サブモジュールの更新が反映されない時の対処法
サブモジュールを使用していて最も頻繁に遭遇する問題の1つが、「サブモジュールの更新が反映されない」というものです。この問題は、サブモジュールの仕組みを正しく理解していないと解決が難しく、チーム内での混乱の原因になります。
### よくあるシナリオと問題の原因
以下のような状況を想像してください:
1. チームメンバーAがメインリポジトリをクローンし、サブモジュールを初期化しました
2. サブモジュールのリポジトリに新しい変更がプッシュされました
3. メンバーAがサブモジュールディレクトリで `git pull` を実行しました
4. しかし、メインリポジトリからは更新が反映されていないように見えます
この問題が発生する理由は、サブモジュールがメインリポジトリ内で特定のコミットを指しているポインタだからです。サブモジュールディレクトリ内で `git pull` を実行しても、そのポインタは自動的に更新されません。
### 解決方法1: サブモジュールの更新とメインリポジトリへのコミット
```bash
# サブモジュールディレクトリに移動
cd libs/common
# 最新の変更を取得(例: masterブランチの最新を取得)
git checkout master
git pull origin master
# メインリポジトリディレクトリに戻る
cd ../..
# サブモジュールの変更をステージング
git add libs/common
# 変更をコミット
git commit -m "Update libs/common submodule to latest master"
# 変更をプッシュ(必要に応じて)
git push
このプロセスで重要なのは、サブモジュールの更新後に、メインリポジトリでその変更をコミットする必要があるという点です。このステップを忘れると、サブモジュールの更新は他の開発者には共有されません。
解決方法2: 一括でサブモジュールを最新バージョンに更新
複数のサブモジュールを一度に最新版に更新したい場合は、以下のコマンドが便利です。
# すべてのサブモジュールをリモートの最新commitに更新
git submodule update --remote
# 特定のサブモジュールだけを更新
git submodule update --remote libs/common
# 更新後、メインリポジトリでコミットするのを忘れずに
git add .
git commit -m "Update all submodules to latest version"
解決方法3: 特定のタグやブランチにサブモジュールを固定する
安定版のリリースでは、サブモジュールを特定のタグやブランチに固定したい場合があります。
# サブモジュールを特定のタグにチェックアウト
cd libs/common
git checkout v1.2.3
cd ../..
git add libs/common
git commit -m "Pin libs/common to v1.2.3"
# または、メインリポジトリから直接ブランチを指定して更新
git submodule update --remote --reference origin/stable libs/common
git add libs/common
git commit -m "Update libs/common to track stable branch"
トラブルシューティング: デタッチドHEAD状態の解決
サブモジュールを更新すると、サブモジュールのリポジトリが「detached HEAD」状態(特定のブランチに属さない状態)になることがあります。これが原因で更新が困難になる場合があります。
# サブモジュールディレクトリに移動
cd libs/common
# 現在の状態を確認
git status
# "HEAD detached at a1b2c3d" のようなメッセージが表示される場合
# ブランチを作成して問題を解決
git checkout -b temp-branch
git checkout master
git merge temp-branch
git branch -d temp-branch
# または単純にブランチをチェックアウト
git checkout master
.gitmodules ファイルの活用
サブモジュールの設定を管理するには、.gitmodules
ファイルを編集することも有効です。特に、サブモジュールが追跡するブランチを指定することで、update --remote
コマンドの動作を制御できます。
# .gitmodules ファイルを編集
git config -f .gitmodules submodule.libs/common.branch stable
# 変更を反映
git submodule sync
git submodule update --remote
# 変更をコミット
git add .gitmodules libs/common
git commit -m "Configure libs/common to track stable branch"
このセクションで説明した方法を理解しておくことで、サブモジュールの更新に関する多くの問題を解決できます。次のセクションでは、チーム開発でよく発生するサブモジュールの競合問題について説明します。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
チーム開発で陥りがちなサブモジュールの競合解決テクニック
チーム開発でGitサブモジュールを使用する場合、様々な競合が発生することがあります。これは特に、複数のメンバーが異なるブランチで作業し、サブモジュールの参照するコミットを変更した場合に顕著です。ここでは、そうした競合の原因と解決方法について解説します。
よくある競合パターン
サブモジュールの競合は主に以下のようなシナリオで発生します:
- 並行開発による競合:複数の開発者が同時に異なるサブモジュールのバージョンを使用している
- マージ時の競合:異なるブランチで異なるサブモジュールのコミットを指している
- リベース時の競合:リベース操作でサブモジュールのポインタが変わる
競合の例と解決法
ここでは具体的な競合例とその解決法を見ていきましょう。
シナリオ1: マージ時のサブモジュールポインタの競合
# featureブランチでサブモジュールを更新したとします
git checkout feature
cd libs/common
git checkout v2.0.0
cd ../..
git add libs/common
git commit -m "Update libs/common to v2.0.0"
# masterブランチに切り替えて、別のバージョンに更新したとします
git checkout master
cd libs/common
git checkout v1.5.0
cd ../..
git add libs/common
git commit -m "Update libs/common to v1.5.0"
# featureブランチをマージしようとすると競合が発生
git merge feature
# CONFLICT (submodule): Merge conflict in libs/common
この場合の解決方法:
# どちらのバージョンを使うか決める(例:featureブランチの方を採用)
cd libs/common
git checkout v2.0.0
cd ../..
git add libs/common
git commit -m "Resolve submodule conflict: use v2.0.0 from feature branch"
シナリオ2: 2人の開発者が同時にサブモジュールを更新
開発者AとBが同時に作業し、サブモジュールを異なるバージョンに更新した場合:
# 開発者Aの作業
git pull # リモートの変更を取得
cd libs/common
git checkout v1.6.0
cd ../..
git add libs/common
git commit -m "Update libs/common to v1.6.0"
# 開発者Bも同様の作業を別バージョンで行い、先にプッシュ
# 開発者Aがプッシュしようとすると...
git push
# error: failed to push some refs...
この場合の解決方法:
# まず最新の変更を取得
git pull
# 競合が発生したら、どちらを使うか決める
# たとえば、マージツールを使って解決
git mergetool
# または、手動で解決
cd libs/common
git checkout v1.6.0 # 自分のバージョンを選択
cd ../..
git add libs/common
git commit -m "Resolve submodule conflict"
git push
競合を未然に防ぐためのベストプラクティス
- ブランチ戦略の導入:サブモジュールの更新を計画的に行い、全員が同じタイミングで更新されるようにする
# 例: develop ブランチでのみサブモジュールを更新する
git checkout develop
git submodule update --remote
git add .
git commit -m "Update all submodules to latest version"
git push
# feature ブランチを develop から作成して作業する
git checkout -b feature/new-feature develop
- サブモジュールの更新担当者を決める:チーム内で特定の人だけがサブモジュールの更新を担当
# サブモジュール更新専用のブランチを作成
git checkout -b update-submodules
git submodule update --remote
git add .
git commit -m "Update submodules to latest versions"
git push origin update-submodules
# プルリクエストを作成して、マージする
- サブモジュールの状態を明示的に文書化:プロジェクトのドキュメントや README に現在のサブモジュールのバージョンを記録
## Submodules
This project uses the following submodules:
- libs/common: v1.5.0
- libs/utils: v2.1.0
Please make sure you are using these versions when contributing.
高度な競合解決テクニック: リベース時の注意点
リベース操作は特にサブモジュールの競合を引き起こしやすいです。以下のテクニックを使うと解決しやすくなります:
# リベース前にサブモジュールの状態を保存
git submodule foreach 'git rev-parse HEAD > .current_commit'
# リベースを実行
git rebase master
# 競合が発生した場合、保存したコミットに戻す
git submodule foreach 'git checkout $(cat .current_commit)'
git add .
git rebase --continue
# リベース完了後、一時ファイルを削除
git submodule foreach 'rm .current_commit'
チーム全体へのサブモジュール変更の通知
サブモジュールの更新はチーム全体に影響するため、変更を明示的に通知することが重要です:
# サブモジュールの変更をわかりやすくコミットメッセージに記載
git commit -m "[IMPORTANT] Update libs/common submodule from v1.0.0 to v2.0.0
This is a breaking change that requires updating the API calls in your code.
Please see the changelog at https://github.com/example/common/blob/master/CHANGELOG.md"
チーム内でのコミュニケーションを徹底し、サブモジュールの更新を計画的に行うことで、多くの競合問題を未然に防ぐことができます。次のセクションでは、サブモジュールの移動や名前変更といった、より高度な操作について説明します。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
サブモジュールの移動・名前変更を安全に行う方法
プロジェクトの構造が変化するにつれて、サブモジュールのディレクトリ位置や名前を変更する必要が生じることがあります。これは一見単純な操作に思えますが、誤った方法で行うとリポジトリが壊れたり、履歴が失われたりする可能性があります。このセクションでは、サブモジュールを安全に移動・名前変更する方法を説明します。
安全でない方法(これはやらないでください)
以下の方法は一見シンプルに思えますが、多くの問題を引き起こす可能性があります:
# 危険な方法 - 単純にファイルシステム上で移動する
mv libs/common utils/shared
git add .
git commit -m "Move submodule from libs/common to utils/shared"
この方法はGitの内部状態を適切に更新せず、.git/modules
ディレクトリ内のサブモジュール情報と実際のファイルシステムの位置が一致しなくなるため、様々なエラーを引き起こします。
安全な方法1: git mv コマンドを使用する
Git 1.8.5以降では、git mv
コマンドがサブモジュールの移動にも対応しています:
# サブモジュールを移動
git mv libs/common utils/shared
# 変更をコミット
git add .
git commit -m "Move submodule from libs/common to utils/shared"
しかし、この方法はシンプルなケースにしか対応していません。特に、.gitmodules ファイルの更新が自動的に行われない場合があります。
安全な方法2: 削除と再追加(最も確実な方法)
より確実な方法は、サブモジュールを一度削除してから再追加することです:
# 1. 現在のサブモジュールのURLとコミットハッシュを取得
SUBMODULE_URL=$(git config -f .gitmodules --get submodule.libs/common.url)
SUBMODULE_COMMIT=$(git submodule status libs/common | awk '{print $1}' | sed 's/^-//')
# 2. サブモジュールを削除
git submodule deinit -f libs/common
git rm -f libs/common
# 注意: 以下のコマンドは .git/modules 内のファイルを削除します
rm -rf .git/modules/libs/common
# 3. 新しい場所にサブモジュールを追加
git submodule add $SUBMODULE_URL utils/shared
# 4. 特定のコミットをチェックアウト
cd utils/shared
git checkout $SUBMODULE_COMMIT
cd ../..
# 5. 変更をコミット
git add .
git commit -m "Move submodule from libs/common to utils/shared"
この方法は少し複雑ですが、Gitの内部状態を正しく更新するため、最も安全です。
サブモジュール名だけを変更する場合
サブモジュールの場所は変えずに、名前だけを変更したい場合は、.gitmodules ファイルを直接編集するのが簡単です:
# 1. .gitmodules ファイルを編集
git config -f .gitmodules --rename-section submodule.old-name submodule.new-name
# 2. 変更を反映
git submodule sync
# 3. 変更をコミット
git add .gitmodules
git commit -m "Rename submodule from old-name to new-name"
名前変更時の注意点
サブモジュールの名前や位置を変更する際は、以下の点に注意しましょう:
チームへの通知: サブモジュールの位置や名前の変更は、チーム全体に影響するため、事前に十分な通知を行いましょう
CI/CDパイプラインの更新: 自動ビルドやテストのスクリプトが古いパスを参照している場合は、それらも更新する必要があります
複数のブランチでの作業: サブモジュールの移動や名前変更が完了するまで、他のブランチでの作業を一時停止すると、競合を回避できます
# チームへの通知例(コミットメッセージ)
git commit -m "[IMPORTANT] Move submodule from libs/common to utils/shared
This change affects build scripts and CI pipeline configuration.
All team members should run 'git submodule sync' after pulling this change.
See PR #123 for more details."
移動・名前変更後のトラブルシューティング
サブモジュールの移動や名前変更後に問題が発生した場合の対処法:
# サブモジュールの状態をリセット
git submodule sync
git submodule update --init --recursive
# .git/modules ディレクトリを確認
ls -la .git/modules/
# 古いパスが残っている場合は手動で削除
rm -rf .git/modules/old-path
# 設定ファイルの整合性を確認
git config --list | grep submodule
サブモジュールの移動や名前変更は、慎重に行う必要がある操作です。特に、大きなプロジェクトや複数のサブモジュールを持つプロジェクトでは、変更の影響範囲を事前に検討し、十分なテストを行ってから本番環境に反映するようにしましょう。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
まとめ:Gitサブモジュールを使いこなすためのベストプラクティス
この記事では、Gitサブモジュールを使う際によく遭遇する問題とその解決方法について詳しく解説しました。最後に、サブモジュールを効果的に使いこなすためのベストプラクティスをまとめます。
サブモジュールを使う前に検討すべきこと
サブモジュールは強力なツールですが、すべてのプロジェクトに適しているわけではありません。以下の点を検討しましょう:
本当にサブモジュールが必要か?:依存関係の管理にはnpmやComposerなどのパッケージマネージャが適している場合があります
チームの全員がサブモジュールを理解しているか?:チームメンバー全員がサブモジュールの仕組みと操作方法を理解していることが重要です
変更頻度は適切か?:サブモジュールが頻繁に変更される場合、管理コストが高くなる可能性があります
サブモジュールを効果的に使うためのヒント
ドキュメントの整備:プロジェクトのREADMEに以下の情報を明記しましょう
- サブモジュールの一覧とその目的
- クローンとセットアップの方法
- サブモジュールの更新方法
- 注意点や既知の問題
サブモジュールの粒度を適切に保つ:サブモジュールは独立した機能単位で分割すべきです
# 良い例:明確な責任範囲を持つサブモジュール
libs/ui-components # UIコンポーネントのみを管理
libs/core-services # コアビジネスロジックを管理
# 悪い例:異なる責任が混在したサブモジュール
libs/misc # 様々な機能が混在
バージョン管理の方針を決める:サブモジュールをどのように追跡するかを決めましょう
- 特定のタグやコミットに固定する(安定性重視)
- 常に最新のmasterを追跡する(新機能重視)
- 定期的に更新する(バランス型)
タグと意味のあるコミットメッセージを使う:サブモジュールのリポジトリでは、明確なタグとコミットメッセージを使用しましょう
# サブモジュールのリポジトリでタグを付ける
git tag -a v1.2.0 -m "Add new carousel component"
git push origin v1.2.0
# メインリポジトリで特定のタグを参照
cd libs/ui-components
git checkout v1.2.0
cd ../..
git add libs/ui-components
git commit -m "Update UI components to v1.2.0 for new carousel feature"
- 継続的インテグレーション(CI)の設定:CI環境でもサブモジュールが正しく取得されるようにしましょう
# .gitlab-ci.yml または .github/workflows/main.yml の例
jobs:
build:
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive # サブモジュールも取得
- サブモジュールの更新を一貫して行う:チーム内でサブモジュールの更新方法を統一しましょう
# 例:サブモジュールの更新用スクリプトを作成
#!/bin/bash
# update-submodules.sh
git submodule update --remote
git add .
git commit -m "Update all submodules to latest version"
- 監査とメンテナンス:定期的にサブモジュールの状態と必要性を見直しましょう
# サブモジュールの状態を確認
git submodule status --recursive
# 使われなくなったサブモジュールは削除
git submodule deinit -f unused-module
git rm -f unused-module
rm -rf .git/modules/unused-module
git commit -m "Remove unused submodule"
Gitサブモジュールの代替手段
サブモジュールが合わないと感じる場合は、以下の代替手段も検討してみてください:
- Git Subtree:サブモジュールよりも直感的に扱えますが、高度な操作は複雑になります
# サブツリーの追加
git subtree add --prefix=libs/utility https://github.com/example/utility.git master --squash
# サブツリーの更新
git subtree pull --prefix=libs/utility https://github.com/example/utility.git master --squash
パッケージマネージャ:npm、Composer、Gems、PIPなどプログラミング言語に応じたパッケージマネージャを使用する
モノレポ:複数のプロジェクトを単一のリポジトリで管理する(Lerna、Nx、Bazelなどのツールが利用可能)
最後に
Gitサブモジュールは理解と適切な使い方さえ身につければ、非常に強力なツールになります。この記事で紹介した問題解決法とベストプラクティスを参考に、チームの開発効率を向上させましょう。
# サブモジュールの操作をまとめた便利なエイリアス
git config --global alias.supdate 'submodule update --remote --merge'
git config --global alias.scommit 'submodule foreach "git add . && git commit -m"'
git config --global alias.spush 'submodule foreach "git push"'
「正しいツールを正しく使う」は開発における鉄則です。サブモジュールが本当に必要な場合は、この記事で紹介した方法を活用して、効率的なプロジェクト管理を実現してください。
このトピックはこちらの書籍で勉強するのがおすすめ!
この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!
おすすめコンテンツ
おすすめGit2025/5/19Git rebaseで発生するコンフリクト解決法!1分で実践できる具体的な手順
Git rebaseでコンフリクトが発生した時の具体的な解決手順を丁寧に解説します。実際のコマンドと図解でわかりやすく説明し、開発作業の停滞を最小限に抑える方法が学べます。
続きを読む Docker2025/5/20Docker環境でNodeモジュールが同期されない問題の解決法
Docker開発環境でのNode.jsプロジェクトでnode_modulesが正しく同期されない問題に悩んでいませんか?このよくある問題の具体的な解決策と実践的なコード例を紹介します。
続きを読む TypeScript2025/5/20VS CodeのTypeScript拡張機能が突然動かなくなった時の解決法
VS CodeでTypeScriptの拡張機能が突然動かなくなった時の原因と具体的な解決手順を説明します。設定ファイルの修正方法からキャッシュのクリア方法まで、即効性のある対処法を紹介します。
続きを読む Git2025/5/20Git rebase中に発生するdetached HEAD状態の解決法!1分で実践できる簡単手順
Git rebaseを実行したときに発生しがちなdetached HEAD状態の原因と解決方法を詳しく解説します。初心者でもすぐに実践できる簡単な手順で、Git操作のトラブルを迅速に解決できます。
続きを読む プログラミング2025/6/2プログラミング初心者が実務でよくハマるエラーパターンと効率的な解決法
プログラミング学習を終えて実務に入った初心者が必ず遭遇する典型的なエラーパターンと、その効率的な解決方法を実例付きで解説します。開発現場でスムーズに問題解決できるようになるための実践的なガイド。
続きを読む GitHub Actions2025/5/23GitHub Actionsワークフローが失敗する時の原因と解決法!初心者でも5分で修正できる実践ガイド
GitHub Actionsでワークフローが失敗して困っていませんか?よくあるエラーの原因と具体的な解決方法を、実際のコード例を交えて分かりやすく解説します。構文エラー、権限問題、依存関係のトラブルま...
続きを読む JavaScript2025/5/20Express.jsのmiddlewareでasync/await関数が動かない時の解決法
Express.jsでasync/awaitを使用したミドルウェア関数でエラーハンドリングができない問題の解決策を紹介します。この頻出するバグの原因と解決方法を具体的なコード例で詳しく解説します。
続きを読む Next.js2025/5/20Next.jsのAPIルートでホットリロードが効かない時の解決法
Next.jsの開発中にAPIルートのコード変更が反映されない問題に対処する方法を紹介します。この記事では、環境設定を見直し、効率的に開発を続けるためのヒントを共有します。
続きを読む