Start Debugging

修正: flutter upgrade で "Your flutter checkout has local changes that would be erased by upgrading" と表示される

SDK フォルダー内の git status が何か出力すると flutter upgrade は中断されます。内容を確認し、pubspec.lock を復元するか git stash -u で退避し、--force は最後の手段として使います。

flutter upgrade は Flutter SDK フォルダー内で git status -s を実行し、1 行でも出力されると処理を続行しません。保持するつもりのない未追跡ファイルも対象です。flutter --version --machine の flutterRoot フィールドで SDK の場所を調べ、そこで git status --short を実行します。その結果に応じて、ファイル (たいていは pubspec.lock) を復元するか、git stash push -u で編集内容を退避するか、SDK 内に必要なものが何もなければ flutter upgrade --force を実行します。以下の内容はすべて、2026-10-29 に Flutter 3.44.8 と 3.47.7/3.47.8 (stable、Dart 3.12.2) で確認しました。

エラーの状況

Flutter 本体には一切手を加えていないはずのマシンで flutter upgrade を実行すると、次のように表示されます。

$ flutter upgrade
Your flutter checkout has local changes that would be erased by upgrading. If you want to keep these changes, it is recommended that you stash them via "git stash" or else commit the changes to a local branch. If it is okay to remove local changes, then re-run this command with "--force".

終了コードは 1 で、何も変更されません。このメッセージが指しているのはアプリのリポジトリではなく、SDK の git チェックアウトです。アプリ側の git status が完全にクリーンでも、このエラーは表示されます。

この問題が起きる理由

Flutter SDK は flutter/flutter の git クローンであり、flutter upgrade は基本的に git fetch と git reset --hard <new revision> に安全確認を加えたものです。関連するコードは packages/flutter_tools/lib/src/commands/upgrade.dart にあり、処理の順序が重要です。

  1. git fetch --tags を実行し、現在のブランチ (stable、beta、main) の上流リビジョンを解決します。
  2. すでにそのリビジョンにいる場合は “Flutter is already up to date” と表示して終了コード 0 で終了します。ローカル変更の確認は行われません。
  3. --verify-only が指定されていれば、利用可能なバージョンを表示して終了します。
  4. 現在のコミットに認識可能なリリースタグがなく、--force も指定されていない場合は “Unknown flutter tag” で中断します。
  5. --force が指定されておらず、hasUncommittedChanges() が true を返す場合は、上記のエラーで中断します。
  6. git reset --hard <upstream revision> を実行し、新しいツールを flutter upgrade --continue で再起動して、アーティファクトのプリキャッシュ、プロジェクトに対する pub upgrade、flutter doctor を実行します。

hasUncommittedChanges() は git status -s の薄いラッパーです。stable チャネルでは、出力が空でなければすべて変更とみなされます。Flutter 3.47.0 以降 (PR #184997) では、beta と main のチャネルは pubspec.lock で終わる行を無視します。main がこのファイルに dev SDK の制約を自動で書き込むためです。stable にはこの例外はありません。

git status -s を使っていることから、次の 2 点が直接導かれます。

つまり、本当の問いは”ローカル変更があるか”ではなく、“SDK フォルダーで git status --short は何を出力するか、そしてそれは誰が置いたのか”です。

最小限の再現手順

すべてのケースを、flutter/flutter のスクラッチクローンで再現しました。このクローンは 3.47.7 タグにリセットしてあり、stable (3.47.8) より 1 リリース古い状態です。クローンしたばかりのコードを実行しないよう、インストール済みの Flutter 3.44.8 から、別のチェックアウトを対象にして再入側の後半を省略するツールの隠しフラグ --working-directory でアップグレード処理を動かしました。stable チャネルでの確認処理は 3.44.8 と 3.47.8 でバイト単位で同一です。

# Flutter 3.44.8 tool, scratch checkout at 3.47.7, upstream stable = 3.47.8
C=~/scratch/flutter
git -C "$C" reset -q --hard 3.47.7

echo hi > "$C/notes.txt"                       # untracked file only
flutter upgrade --working-directory="$C"
# Your flutter checkout has local changes that would be erased by upgrading. ...
# exit=1

各シナリオの結果は次のとおりです。

State of the SDK checkoutgit status -sflutter upgrade result
クリーン、1 リリース古い(空)アップグレードされる
未追跡の notes.txt?? notes.txtブロックされる
.idea/、.pub-cache/、debug.log 内のファイル(空。--ignored を付けたときのみ表示)ブロックされない
ルートの pubspec.lock を編集 M pubspec.lockブロックされる
フレームワークのソース編集 + 未追跡ファイル、--force M packages/flutter/..., ?? notes.txtアップグレードされる。フレームワークの編集は消え、notes.txt は残る
すでに 3.47.8 で ?? notes.txt がある?? notes.txt”Flutter is already up to date”、終了コード 0

最後の行は、一見わかりにくいパターンの説明になります。不要なファイルが何か月も問題なく置かれたままで、アップグレード先がようやく出る公開日になって初めてエラーが現れるのです。

最も多い原因: SDK 自身の pubspec.lock

git status --short が M pubspec.lock だけを表示するなら、Flutter が自分自身のロックファイルを変更したということです。Flutter リポジトリは pub ワークスペースであり、ルートの pubspec.lock はツリー内で唯一追跡されているロックファイルです。SDK ルートをプロジェクトとして pub upgrade を実行するものはすべてこのファイルを書き換えます。リリース後に公開されたパッケージ (たとえばより新しい objective_c) が、同じ制約を満たすためです。

Flutter 3.44.x までは、flutter upgrade が自分自身に対してまさにこれを行っていました。再入側の flutter upgrade --continue は SDK ルートを作業ディレクトリとして起動されていたため、“プロジェクトのパッケージを更新する”ステップで呼ばれる findProjectRoot() が、現在のディレクトリから上へたどって pubspec.yaml を探し、SDK 自身のものを見つけて pub upgrade を実行していました。アップグレードは成功しますが、ロックファイルが変更された状態で残り、次の アップグレードが失敗します。これが issue #187977 であり、“一度アップグレードしたインストールでだけ起きる”という報告とも一致します。

このずれは自分で確認できます。3.44.0 タグのクリーンなチェックアウトで、Dart 3.12.2 (3.44 に同梱されている Dart) を使って SDK ルートで pub upgrade を実行すると、ロックファイルが書き換わりました (14 行追加、6 行削除)。

# Flutter 3.44.0 checkout, Dart 3.12.2, 2026-10-29
cd ~/scratch/flutter && dart pub upgrade
git diff --stat
#  pubspec.lock | 20 ++++++++++++++------
       name: jni
-    version: "1.0.0"
+    version: "1.1.1"
       name: jni_flutter
-    version: "1.0.1"
+    version: "1.0.4+1"
       name: objective_c
-    version: "9.2.3"
+    version: "9.3.0"

objective_c の 9.2.3 から 9.3.0 への変更は、issue の報告にある差分と完全に同じです。PR #188794 は、後半を元の作業ディレクトリで起動するようにしてこれを修正し、Flutter 3.47.0 に含まれました。そのため 3.44.x を使っている場合、3.47 へ上がる途中でもう一度このエラーが出ると考えてください。その後は出なくなるはずです。

この修正には落とし穴があります。後半は、あなたの 現在のディレクトリから findProjectRoot() が見つけたプロジェクトに対して pub upgrade を実行しますが、この関数は上方向に探索します。~/development/flutter/bin のように SDK ツリーの内側から flutter upgrade を実行すると、SDK の pubspec.yaml が再び見つかり、3.47 でもロックファイルが変更されます。アップグレードはホームディレクトリかアプリのフォルダーから実行し、SDK の内側では絶対に実行しないでください (アプリのフォルダーから実行すると、そのアプリの依存関係がアップグレードされます。これは別の話題で、flutter upgrade の後で pubspec.lock が変わる理由で扱っています)。

修正の詳細

1. SDK を見つけ、強制する前に中身を確認する

最初から --force に頼らないでください。まず実際に何があるのかを調べます。

# Flutter 3.44+ on macOS / Linux (needs jq)
SDK="$(flutter --version --machine | jq -r .flutterRoot)"
git -C "$SDK" status --short
git -C "$SDK" diff --stat
# Flutter 3.44+ on Windows PowerShell
$sdk = (flutter --version --machine | ConvertFrom-Json).flutterRoot
git -C $sdk status --short
git -C $sdk diff --stat

JSON を解析したくない場合は、flutter doctor -v の “Flutter version X on channel Y at ” の行にも同じパスが表示されます。Homebrew の cask でインストールした場合、flutterRoot は bin/flutter のシンボリックリンクではなく実際のチェックアウトを指します (私の環境では /opt/homebrew/share/flutter です)。

2a. pubspec.lock だけが変更されている場合: 復元する

これは、前述の Flutter 自身が書き換えたケースです。このファイルはどのみち再生成されるので、変更を破棄して通常どおりアップグレードします。

# Flutter 3.44.x -> 3.47.x, stable channel
git -C "$SDK" checkout -- pubspec.lock
cd ~ && flutter upgrade

~ からアップグレードを実行すると、後半が SDK 内の pubspec.yaml を見つけることがなくなります。

2b. フレームワークのファイルを意図的に変更した場合: stash で退避する

バグを追っている最中にウィジェットにパッチを当てたり、packages/flutter の中に debugPrint を追加したりしたかもしれません。アップグレード後にその編集内容を戻したいなら、未追跡ファイルも含めて stash に退避します。

# Flutter 3.47.7 -> 3.47.8, verified 2026-10-29
git -C "$SDK" stash push -u -m "before flutter upgrade"
flutter upgrade
git -C "$SDK" stash pop

私の再現環境では、3.47.8 まで問題なくアップグレードでき、stash pop で変更済みの text.dart と未追跡の notes.txt の両方が復元されました。編集したファイルが 2 つのバージョンの間で上流でも変更されていた場合、stash pop はコンフリクトすることがあります。これは正直な結果です。あなたのパッチは別のバージョンのフレームワークを前提に書かれていたからです。

フレームワークへの長期的なパッチを維持しているなら、stash は適切な道具ではありません。ローカルブランチにコミットするか、さらに良いのはフォークを持ち、バージョン管理ツールでプロジェクト専用の SDK をそのフォークに向けることです。そうすれば、アップグレードする SDK とパッチを当てる SDK が別になります。

2c. 中身がどれも重要でない場合: --force

git status --short が気にしなくてよいものしか表示しないなら、強制します。

# Flutter 3.47.x, stable channel
flutter upgrade --force    # or: flutter upgrade -f

--force が何をするかを理解しておいてください。多くの人が想像するものとは違います。

未追跡ファイルを完全に消すには、プレビューしてから削除します。

# Run inside the SDK folder; -n is a dry run
git -C "$SDK" clean -n -d
git -C "$SDK" clean -f -d

git clean はファイルを完全に削除し、.gitignore を尊重するため、bin/cache/ や .pub-cache/ には触れません。実際に実行する前に、-n の出力をよく確認してください。

3. 再発を防ぐ

注意点と似たエラー

“Unknown flutter tag. Abandoning upgrade to avoid destroying local changes.” 別の確認ですが、同じ系統です。git describe がコミットの近くでリリースタグを見つけられないときに発生します。典型的には、タグなしのシャロークローンの後や、任意のコミットをチェックアウトした後です。公式チャネルでは、git を直接使うようメッセージが案内します。それ以外では --force を提案します。強制する前にタグを取得してください (git -C "$SDK" fetch --tags)。

“Your Flutter checkout is currently not on a release branch.” HEAD が detached 状態です。たいていは、バージョンを固定するためにドキュメントにある git checkout <version> の手順に従ったことが原因です。flutter channel stable を実行してブランチに戻ってから、アップグレードしてください。

“The current Flutter branch/channel is not tracking any remote repository.” ローカルブランチに上流がありません。ドキュメントで案内されている対処は再インストールですが、origin が公式のリモートであれば git -C "$SDK" branch -u origin/stable でも対処できます。

ファイルモードや改行コードのノイズ。 SDK 内の git diff が old mode 100755 / new mode 100644 の行だけを表示するなら、何か (chmod -R、ファイルシステム間のコピー、同期フォルダー) が権限を変更しています。Windows ですべてのファイルが変更済みとして表示される場合は、異なる改行コードでチェックアウトがコピーされています。どちらの場合も、大事な変更は何もありません。git -C "$SDK" checkout -- . か --force が正しい選択です。SDK の .gitattributes は * text=auto を使っているので、コピーがひどく壊れている場合は、新しくクローンし直すのが最もきれいな解決策です。

CI でエラーが出る。 CI イメージが実行間で SDK をキャッシュしていて、以前のジョブが 3.44.x の SDK 上で (または SDK ツリーの内側から) flutter upgrade を実行した場合、キャッシュされたチェックアウトが変更済みのロックファイルを抱えたままになり、以降のアップグレードステップがすべて失敗します。CI ではアップグレードせずにバージョンを固定してください。1 つの CI パイプラインから複数の Flutter バージョンを対象にする方法を参照してください。

flutter upgrade は成功したが、プロジェクトが解決に失敗するようになった。 それは、後半が新しい SDK の固定バージョンでアプリに対して pub upgrade を実行した結果であり、このエラーではありません。まず pubspec.yaml の “version solving failed” の修正から確認してください。

関連記事

参考資料

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< 戻る