detached ef28457

fix(bootstrap): clone repo with explicit branch to prevent asset drift

commit
ef2845791726bb10bbfe11f3ed9836067050f4b2
author
brice · · 2026-02-27 05:25 UTC
parent
e868eab
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +18 -5
M cmd/install.go +1 -1
@@ -68,7 +68,7 @@ func runInstall(dryRun bool) error {
6868 return fmt.Errorf("create repository adapter: %w", err)
6969 }
7070
71- bootstrapHandler := apphandlers.NewBootstrapHandler(fsAdapter, httpClient, slogAdapter, cfg.RepoURL, cfg.RawURL)
71+ bootstrapHandler := apphandlers.NewBootstrapHandler(fsAdapter, httpClient, slogAdapter, cfg.RepoURL, cfg.RawURL, cfg.Branch())
7272 preflightHandler := apphandlers.NewPreflightHandler(fsAdapter, shellExec, slogAdapter)
7373 partitionHandler := apphandlers.NewPartitionHandler(shellExec, slogAdapter)
7474 baseHandler := apphandlers.NewInstallBaseHandler(fsAdapter, shellExec, chrootExec, slogAdapter)
@@ -68,7 +68,7 @@ func runInstall(dryRun bool) error {
68 return fmt.Errorf("create repository adapter: %w", err)68 return fmt.Errorf("create repository adapter: %w", err)
69 }69 }
70 70
71- bootstrapHandler := apphandlers.NewBootstrapHandler(fsAdapter, httpClient, slogAdapter, cfg.RepoURL, cfg.RawURL)71+ bootstrapHandler := apphandlers.NewBootstrapHandler(fsAdapter, httpClient, slogAdapter, cfg.RepoURL, cfg.RawURL, cfg.Branch())
72 preflightHandler := apphandlers.NewPreflightHandler(fsAdapter, shellExec, slogAdapter)72 preflightHandler := apphandlers.NewPreflightHandler(fsAdapter, shellExec, slogAdapter)
73 partitionHandler := apphandlers.NewPartitionHandler(shellExec, slogAdapter)73 partitionHandler := apphandlers.NewPartitionHandler(shellExec, slogAdapter)
74 baseHandler := apphandlers.NewInstallBaseHandler(fsAdapter, shellExec, chrootExec, slogAdapter)74 baseHandler := apphandlers.NewInstallBaseHandler(fsAdapter, shellExec, chrootExec, slogAdapter)
M internal/application/handlers/bootstrap_handler.go +4 -2
@@ -18,16 +18,18 @@ type BootstrapHandler struct {
1818 logger ports.Logger
1919 repoURL string
2020 rawURL string
21+ branch string
2122 }
2223
2324 // NewBootstrapHandler creates a new bootstrap handler
24-func NewBootstrapHandler(fs ports.FileSystem, httpClient ports.HTTPClient, logger ports.Logger, repoURL, rawURL string) *BootstrapHandler {
25+func NewBootstrapHandler(fs ports.FileSystem, httpClient ports.HTTPClient, logger ports.Logger, repoURL, rawURL, branch string) *BootstrapHandler {
2526 return &BootstrapHandler{
2627 fs: fs,
2728 httpClient: httpClient,
2829 logger: logger,
2930 repoURL: repoURL,
3031 rawURL: rawURL,
32+ branch: branch,
3133 }
3234 }
3335
@@ -86,7 +88,7 @@ func (h *BootstrapHandler) cloneRepo(ctx context.Context) error {
8688
8789 h.logger.Info("Cloning repository", "url", h.repoURL)
8890
89- cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", h.repoURL, repoDir)
91+ cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", "--branch", h.branch, h.repoURL, repoDir)
9092 if output, err := cmd.CombinedOutput(); err != nil {
9193 return fmt.Errorf("git clone failed: %w (output: %s)", err, string(output))
9294 }
@@ -18,16 +18,18 @@ type BootstrapHandler struct {
18 logger ports.Logger18 logger ports.Logger
19 repoURL string19 repoURL string
20 rawURL string20 rawURL string
21+ branch string
21 }22 }
22 23
23 // NewBootstrapHandler creates a new bootstrap handler24 // NewBootstrapHandler creates a new bootstrap handler
24-func NewBootstrapHandler(fs ports.FileSystem, httpClient ports.HTTPClient, logger ports.Logger, repoURL, rawURL string) *BootstrapHandler {25+func NewBootstrapHandler(fs ports.FileSystem, httpClient ports.HTTPClient, logger ports.Logger, repoURL, rawURL, branch string) *BootstrapHandler {
25 return &BootstrapHandler{26 return &BootstrapHandler{
26 fs: fs,27 fs: fs,
27 httpClient: httpClient,28 httpClient: httpClient,
28 logger: logger,29 logger: logger,
29 repoURL: repoURL,30 repoURL: repoURL,
30 rawURL: rawURL,31 rawURL: rawURL,
32+ branch: branch,
31 }33 }
32 }34 }
33 35
@@ -86,7 +88,7 @@ func (h *BootstrapHandler) cloneRepo(ctx context.Context) error {
86 88
87 h.logger.Info("Cloning repository", "url", h.repoURL)89 h.logger.Info("Cloning repository", "url", h.repoURL)
88 90
89- cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", h.repoURL, repoDir)91+ cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", "--branch", h.branch, h.repoURL, repoDir)
90 if output, err := cmd.CombinedOutput(); err != nil {92 if output, err := cmd.CombinedOutput(); err != nil {
91 return fmt.Errorf("git clone failed: %w (output: %s)", err, string(output))93 return fmt.Errorf("git clone failed: %w (output: %s)", err, string(output))
92 }94 }
M internal/application/services/installation_service_test.go +2 -2
@@ -56,7 +56,7 @@ func createTestService(ctrl *gomock.Controller) *InstallationService {
5656 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
5757 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
5858
59- bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev")
59+ bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev", "dev")
6060 preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger)
6161 partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger)
6262 baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger)
@@ -141,7 +141,7 @@ func TestInstallationService_Start_InvalidHostname(t *testing.T) {
141141 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
142142 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
143143
144- bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev")
144+ bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev", "dev")
145145 preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger)
146146 partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger)
147147 baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger)
@@ -56,7 +56,7 @@ func createTestService(ctrl *gomock.Controller) *InstallationService {
56 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()56 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
57 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()57 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
58 58
59- bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev")59+ bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev", "dev")
60 preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger)60 preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger)
61 partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger)61 partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger)
62 baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger)62 baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger)
@@ -141,7 +141,7 @@ func TestInstallationService_Start_InvalidHostname(t *testing.T) {
141 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()141 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
142 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()142 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
143 143
144- bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev")144+ bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev", "dev")
145 preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger)145 preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger)
146 partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger)146 partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger)
147 baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger)147 baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger)
M internal/config/config.go +11 -0
@@ -290,6 +290,17 @@ func NewConfig(version string) *Config {
290290 }
291291 }
292292
293+// Branch returns the git branch to use for asset downloads and repo cloning.
294+func (c *Config) Branch() string {
295+ rawURL := c.RawURL
296+ // Extract branch from RawURL: "https://raw.githubusercontent.com/bnema/archup/<branch>"
297+ parts := strings.Split(rawURL, "/")
298+ if len(parts) > 0 {
299+ return parts[len(parts)-1]
300+ }
301+ return "main"
302+}
303+
293304 // Load reads configuration from a shell-compatible config file
294305 func Load(path string, version string) (*Config, error) {
295306 cfg := NewConfig(version)
@@ -290,6 +290,17 @@ func NewConfig(version string) *Config {
290 }290 }
291 }291 }
292 292
293+// Branch returns the git branch to use for asset downloads and repo cloning.
294+func (c *Config) Branch() string {
295+ rawURL := c.RawURL
296+ // Extract branch from RawURL: "https://raw.githubusercontent.com/bnema/archup/<branch>"
297+ parts := strings.Split(rawURL, "/")
298+ if len(parts) > 0 {
299+ return parts[len(parts)-1]
300+ }
301+ return "main"
302+}
303+
293 // Load reads configuration from a shell-compatible config file304 // Load reads configuration from a shell-compatible config file
294 func Load(path string, version string) (*Config, error) {305 func Load(path string, version string) (*Config, error) {
295 cfg := NewConfig(version)306 cfg := NewConfig(version)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close