From 3178d973215c7681792250aaf002ec784c90d4e9 Mon Sep 17 00:00:00 2001 From: Kolja Lampe Date: Thu, 26 Mar 2026 17:31:45 +0000 Subject: [PATCH] common: allow automatic branch following for extensions When an application or runtime is updated and its metadata requests a new branch of an extension, Flatpak should automatically pull the new branch if the user already has at least one branch of that extension installed. This ensures that "no-autodownload" extensions (like GIMP plugins) stay functional after an update that requires a new branch, while still respecting the user's explicit opt-in (the existing installation of a previous branch). Fixes: https://github.com/flatpak/flatpak/issues/4208 --- common/flatpak-dir.c | 20 +++++ tests/test-extension-branch-follow.sh | 107 ++++++++++++++++++++++++++ tests/test-matrix/meson.build | 1 + tests/update-test-matrix | 1 + 4 files changed, 129 insertions(+) create mode 100755 tests/test-extension-branch-follow.sh diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c index 7925d4e0..be6ec15f 100644 --- a/common/flatpak-dir.c +++ b/common/flatpak-dir.c @@ -16573,6 +16573,26 @@ add_related (FlatpakDir *self, flatpak_extension_matches_reason (id, download_if, !no_autodownload) || deploy_data != NULL; + /* Automatic branch following: if this extension wouldn't normally be + * auto-downloaded, still download it if there's already an installed branch + * of the same extension for this arch. This handles the case where an app + * updates its extension version requirement. */ + if (!download) + { + g_autoptr(GPtrArray) installed_branches = + flatpak_dir_list_refs_for_name (self, FLATPAK_KINDS_RUNTIME, id, NULL, NULL); + + for (size_t i = 0; installed_branches && i < installed_branches->len; i++) + { + FlatpakDecomposed *installed_ref = g_ptr_array_index (installed_branches, i); + if (flatpak_decomposed_is_arch (installed_ref, arch)) + { + download = TRUE; + break; + } + } + } + if (!flatpak_extension_matches_reason (id, autoprune_unless, TRUE)) auto_prune = TRUE; diff --git a/tests/test-extension-branch-follow.sh b/tests/test-extension-branch-follow.sh new file mode 100755 index 00000000..6300fc2e --- /dev/null +++ b/tests/test-extension-branch-follow.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +set -euo pipefail + +# shellcheck source=tests/libtest.sh +# shellcheck disable=SC1091 +. "$(dirname "$0")/libtest.sh" + +skip_without_bwrap + +echo "1..1" + +setup_repo () { + mkdir -p repos + ostree init --repo=repos/test --mode=archive-z2 +} + +make_extension () { + local ID=$1 + local VERSION=$2 + + local DIR + DIR=$(mktemp -d) + + cat > "${DIR}/metadata" <&2 + update_repo + rm -rf "${DIR}" +} + +setup_repo + +"$(dirname "$0")/make-test-runtime.sh" repos/test org.test.Platform master "" "" bash ls cat echo readlink > /dev/null + +# Create app version 1 with extension version 1.0 +mkdir -p hello +cat > hello/metadata <&2 +${FLATPAK} build-export --no-update-summary --disable-sandbox repos/test hello master >&2 +update_repo + +# Create extension branch 1.0 +make_extension org.test.Extension 1.0 + +# Install app and extension 1.0 +${FLATPAK} remote-add --user --no-gpg-verify test-repo repos/test >&2 +${FLATPAK} --user install -y test-repo org.test.Hello master >&2 +${FLATPAK} --user install -y test-repo org.test.Extension 1.0 >&2 + +if ! ${FLATPAK} --user list --runtime | grep -q "org.test.Extension.*1.0"; then + ${FLATPAK} --user list --runtime >&2 + assert_not_reached "Extension 1.0 not installed" +fi +echo "# Extension 1.0 installed successfully" >&2 + +# Create app version 2 with extension version 2.0 +mkdir -p hello2 +cat > hello2/metadata <&2 +${FLATPAK} build-export --no-update-summary --disable-sandbox repos/test hello2 master >&2 +update_repo + +# Create extension branch 2.0 +make_extension org.test.Extension 2.0 + +# Update the app +${FLATPAK} --user update -y --verbose org.test.Hello master >&2 + +# Check if extension 2.0 was installed +${FLATPAK} --user list --runtime >&2 +if ${FLATPAK} --user list --runtime | grep -q "org.test.Extension.*2.0"; then + echo "# Success: Extension 2.0 installed automatically after branch follow" +else + assert_not_reached "Extension 2.0 NOT installed automatically" +fi + +ok "extension branch follow" diff --git a/tests/test-matrix/meson.build b/tests/test-matrix/meson.build index 466ab0f7..5faa53ed 100644 --- a/tests/test-matrix/meson.build +++ b/tests/test-matrix/meson.build @@ -40,6 +40,7 @@ wrapped_tests += {'name' : 'test-history.sh', 'script' : 'test-history.sh'} wrapped_tests += {'name' : 'test-default-remotes.sh', 'script' : 'test-default-remotes.sh'} wrapped_tests += {'name' : 'test-metadata-validation.sh', 'script' : 'test-metadata-validation.sh'} wrapped_tests += {'name' : 'test-extensions.sh', 'script' : 'test-extensions.sh'} +wrapped_tests += {'name' : 'test-extension-branch-follow.sh', 'script' : 'test-extension-branch-follow.sh'} wrapped_tests += {'name' : 'test-oci.sh', 'script' : 'test-oci.sh'} wrapped_tests += {'name' : 'test-override.sh', 'script' : 'test-override.sh'} wrapped_tests += {'name' : 'test-auth.sh', 'script' : 'test-auth.sh'} diff --git a/tests/update-test-matrix b/tests/update-test-matrix index c76443c5..f4674b2a 100755 --- a/tests/update-test-matrix +++ b/tests/update-test-matrix @@ -21,6 +21,7 @@ TEST_MATRIX_SOURCE=( 'tests/test-default-remotes.sh' \ 'tests/test-metadata-validation.sh' \ 'tests/test-extensions.sh' \ + 'tests/test-extension-branch-follow.sh' \ 'tests/test-bundle.sh{user+system+system-norevokefs}' \ 'tests/test-oci.sh' \ 'tests/test-oci-registry.sh{{user+system},{http+https}}' \