detached fc93cf5

feat(ux): installer title with version, hostname default 'arch', black bg, drop limine wallpaper

- installer form title shows 'ArchUp Installer vX.X' from ldflags version
- hostname default changed from 'myarch' to 'arch'
- username starts empty (required, no default)
- limine.conf: add backdrop 000000, remove wallpaper injection
- postinstall: remove installBootLogo step entirely
- plymouth: background set to pure black to match limine backdrop
- archup-first-boot.service: remove TTY stdin to fix SIGHUP on sudo switch
- all.sh: fix blesh pipe exit code bug
commit
fc93cf538f0a5e62a1db5927509592bb30768352
author
brice · · 2026-02-28 04:34 UTC
parent
82a916a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
8 files · +32 -92
M assets/plymouth/archup.script +2 -2
@@ -1,7 +1,7 @@
11 # Arch Linux Plymouth Theme Script
22
3-Window.SetBackgroundTopColor(0.020, 0.039, 0.078);
4-Window.SetBackgroundBottomColor(0.020, 0.039, 0.078);
3+Window.SetBackgroundTopColor(0, 0, 0);
4+Window.SetBackgroundBottomColor(0, 0, 0);
55
66 logo.image = Image("logo.png");
77 logo.sprite = Sprite(logo.image);
@@ -1,7 +1,7 @@
1 # Arch Linux Plymouth Theme Script1 # Arch Linux Plymouth Theme Script
2 2
3-Window.SetBackgroundTopColor(0.020, 0.039, 0.078);3+Window.SetBackgroundTopColor(0, 0, 0);
4-Window.SetBackgroundBottomColor(0.020, 0.039, 0.078);4+Window.SetBackgroundBottomColor(0, 0, 0);
5 5
6 logo.image = Image("logo.png");6 logo.image = Image("logo.png");
7 logo.sprite = Sprite(logo.image);7 logo.sprite = Sprite(logo.image);
M install/configs/limine.conf.template +12 -15
@@ -1,21 +1,18 @@
11 timeout: {{TIMEOUT}}
2-default_entry: 2
2+default_entry: 1
33 interface_branding: {{BRANDING}}
44 interface_branding_colour: {{COLOR}}
55 graphics: yes
6+backdrop: 000000
67
7-/{{BRANDING}}
8+/{{KERNEL}}
9+protocol: linux
10+kernel_path: boot():/vmlinuz-{{KERNEL}}
11+kernel_cmdline: {{KERNEL_PARAMS}}
12+module_path: boot():/initramfs-{{KERNEL}}.img
813
9- //{{KERNEL}}
10- protocol: linux
11- kernel_path: boot():/vmlinuz-{{KERNEL}}
12- kernel_cmdline: {{KERNEL_PARAMS}}
13- module_path: boot():/initramfs-{{KERNEL}}.img
14-
15- //{{KERNEL}}-fallback
16- protocol: linux
17- kernel_path: boot():/vmlinuz-{{KERNEL}}
18- kernel_cmdline: {{KERNEL_PARAMS}}
19- module_path: boot():/initramfs-{{KERNEL}}-fallback.img
20-
21- //Snapshots
14+/{{KERNEL}}-fallback
15+protocol: linux
16+kernel_path: boot():/vmlinuz-{{KERNEL}}
17+kernel_cmdline: {{KERNEL_PARAMS}}
18+module_path: boot():/initramfs-{{KERNEL}}-fallback.img
@@ -1,21 +1,18 @@
1 timeout: {{TIMEOUT}}1 timeout: {{TIMEOUT}}
2-default_entry: 22+default_entry: 1
3 interface_branding: {{BRANDING}}3 interface_branding: {{BRANDING}}
4 interface_branding_colour: {{COLOR}}4 interface_branding_colour: {{COLOR}}
5 graphics: yes5 graphics: yes
6+backdrop: 000000
6 7
7-/{{BRANDING}}8+/{{KERNEL}}
9+protocol: linux
10+kernel_path: boot():/vmlinuz-{{KERNEL}}
11+kernel_cmdline: {{KERNEL_PARAMS}}
12+module_path: boot():/initramfs-{{KERNEL}}.img
8 13
9- //{{KERNEL}}14+/{{KERNEL}}-fallback
10- protocol: linux15+protocol: linux
11- kernel_path: boot():/vmlinuz-{{KERNEL}}16+kernel_path: boot():/vmlinuz-{{KERNEL}}
12- kernel_cmdline: {{KERNEL_PARAMS}}17+kernel_cmdline: {{KERNEL_PARAMS}}
13- module_path: boot():/initramfs-{{KERNEL}}.img18+module_path: boot():/initramfs-{{KERNEL}}-fallback.img
14-
15- //{{KERNEL}}-fallback
16- protocol: linux
17- kernel_path: boot():/vmlinuz-{{KERNEL}}
18- kernel_cmdline: {{KERNEL_PARAMS}}
19- module_path: boot():/initramfs-{{KERNEL}}-fallback.img
20-
21- //Snapshots
M install/mandatory/post-boot/all.sh +4 -11
@@ -53,18 +53,11 @@ fi
5353
5454 # Install ble.sh (Bash Line Editor)
5555 if [ -f /usr/local/share/archup/post-boot/blesh.sh ]; then
56- echo "Installing ble.sh (Bash Line Editor)..." | tee -a "$LOG_FILE"
57- BLESH_OUTPUT=$(bash /usr/local/share/archup/post-boot/blesh.sh 2>&1 | tee -a "$LOG_FILE")
58- if [ $? -eq 0 ]; then
59- echo "[OK] ble.sh installed successfully" | tee -a "$LOG_FILE"
60- # Check if it was added to bashrc
61- if echo "$BLESH_OUTPUT" | grep -q "ble.sh added to .bashrc"; then
62- echo "[OK] ble.sh configured in ~/.bashrc" | tee -a "$LOG_FILE"
63- elif echo "$BLESH_OUTPUT" | grep -q "ble.sh already configured"; then
64- echo "[OK] ble.sh already present in ~/.bashrc" | tee -a "$LOG_FILE"
65- fi
56+ echo "Installing ble.sh (Bash Line Editor)..." >> "$LOG_FILE"
57+ if bash /usr/local/share/archup/post-boot/blesh.sh >> "$LOG_FILE" 2>&1; then
58+ echo "[OK] ble.sh installed successfully" >> "$LOG_FILE"
6659 else
67- echo "[KO] ble.sh installation failed (non-critical)" | tee -a "$LOG_FILE"
60+ echo "[KO] ble.sh installation failed (non-critical)" >> "$LOG_FILE"
6861 fi
6962 fi
7063
@@ -53,18 +53,11 @@ fi
53 53
54 # Install ble.sh (Bash Line Editor)54 # Install ble.sh (Bash Line Editor)
55 if [ -f /usr/local/share/archup/post-boot/blesh.sh ]; then55 if [ -f /usr/local/share/archup/post-boot/blesh.sh ]; then
56- echo "Installing ble.sh (Bash Line Editor)..." | tee -a "$LOG_FILE"56+ echo "Installing ble.sh (Bash Line Editor)..." >> "$LOG_FILE"
57- BLESH_OUTPUT=$(bash /usr/local/share/archup/post-boot/blesh.sh 2>&1 | tee -a "$LOG_FILE")57+ if bash /usr/local/share/archup/post-boot/blesh.sh >> "$LOG_FILE" 2>&1; then
58- if [ $? -eq 0 ]; then58+ echo "[OK] ble.sh installed successfully" >> "$LOG_FILE"
59- echo "[OK] ble.sh installed successfully" | tee -a "$LOG_FILE"
60- # Check if it was added to bashrc
61- if echo "$BLESH_OUTPUT" | grep -q "ble.sh added to .bashrc"; then
62- echo "[OK] ble.sh configured in ~/.bashrc" | tee -a "$LOG_FILE"
63- elif echo "$BLESH_OUTPUT" | grep -q "ble.sh already configured"; then
64- echo "[OK] ble.sh already present in ~/.bashrc" | tee -a "$LOG_FILE"
65- fi
66 else59 else
67- echo "[KO] ble.sh installation failed (non-critical)" | tee -a "$LOG_FILE"60+ echo "[KO] ble.sh installation failed (non-critical)" >> "$LOG_FILE"
68 fi61 fi
69 fi62 fi
70 63
M install/mandatory/post-boot/archup-first-boot.service +1 -3
@@ -10,9 +10,7 @@ Environment="ARCHUP_EMAIL=__ARCHUP_EMAIL__"
1010 Environment="ARCHUP_USERNAME=__ARCHUP_USERNAME__"
1111 ExecStart=/usr/local/share/archup/post-boot/all.sh
1212 ExecStartPost=/usr/bin/touch /var/lib/archup-first-boot-complete
13-TTYPath=/dev/tty1
14-StandardInput=tty
15-StandardOutput=tty
13+StandardOutput=journal+console
1614 StandardError=journal+console
1715
1816 [Install]
@@ -10,9 +10,7 @@ Environment="ARCHUP_EMAIL=__ARCHUP_EMAIL__"
10 Environment="ARCHUP_USERNAME=__ARCHUP_USERNAME__"10 Environment="ARCHUP_USERNAME=__ARCHUP_USERNAME__"
11 ExecStart=/usr/local/share/archup/post-boot/all.sh11 ExecStart=/usr/local/share/archup/post-boot/all.sh
12 ExecStartPost=/usr/bin/touch /var/lib/archup-first-boot-complete12 ExecStartPost=/usr/bin/touch /var/lib/archup-first-boot-complete
13-TTYPath=/dev/tty113+StandardOutput=journal+console
14-StandardInput=tty
15-StandardOutput=tty
16 StandardError=journal+console14 StandardError=journal+console
17 15
18 [Install]16 [Install]
M internal/config/config.go +1 -0
@@ -272,6 +272,7 @@ func NewConfig(version string) *Config {
272272 }
273273
274274 return &Config{
275+ Hostname: "arch",
275276 Locale: "en_US.UTF-8",
276277 Timezone: "UTC",
277278 Keymap: "us",
@@ -272,6 +272,7 @@ func NewConfig(version string) *Config {
272 }272 }
273 273
274 return &Config{274 return &Config{
275+ Hostname: "arch",
275 Locale: "en_US.UTF-8",276 Locale: "en_US.UTF-8",
276 Timezone: "UTC",277 Timezone: "UTC",
277 Keymap: "us",278 Keymap: "us",
M internal/phases/postinstall.go +2 -56
@@ -79,19 +79,10 @@ func (p *PostInstallPhase) PreCheck() error {
7979
8080 // Execute runs the post-install phase
8181 func (p *PostInstallPhase) Execute(progressChan chan<- ProgressUpdate) PhaseResult {
82- totalSteps := 9
82+ totalSteps := 8
8383 currentStep := 0
8484
85- // Step 1: Boot logo
86- currentStep++
87- p.SendProgress(progressChan, "Installing boot logo...", currentStep, totalSteps)
88- switch err := p.installBootLogo(progressChan); {
89- case err != nil:
90- // Non-fatal, continue
91- p.SendOutput(progressChan, fmt.Sprintf("[WARN] Boot logo failed: %v", err))
92- }
93-
94- // Step 2: Plymouth
85+ // Step 1: Plymouth
9586 currentStep++
9687 p.SendProgress(progressChan, "Configuring Plymouth splash screen...", currentStep, totalSteps)
9788 switch err := p.configurePlymouth(progressChan); {
@@ -166,51 +157,6 @@ func (p *PostInstallPhase) Execute(progressChan chan<- ProgressUpdate) PhaseResu
166157 return PhaseResult{Success: true, Message: "Post-installation complete"}
167158 }
168159
169-// installBootLogo downloads and configures Limine boot logo
170-func (p *PostInstallPhase) installBootLogo(progressChan chan<- ProgressUpdate) error {
171- p.SendOutput(progressChan, "Downloading Arch logo...")
172-
173- data, err := p.fetchFile(config.ArchLogoURL)
174- if err != nil {
175- return fmt.Errorf("failed to fetch logo: %w", err)
176- }
177-
178- if err := p.fs.WriteFile(config.PathMntBootLogo, data, 0644); err != nil {
179- return fmt.Errorf("failed to write logo: %w", err)
180- }
181-
182- p.SendOutput(progressChan, "[OK] Logo downloaded")
183-
184- // Update Limine config
185- limineConf := config.PathMntBootLimineConf
186- content, err := p.fs.ReadFile(limineConf)
187- switch {
188- case err != nil:
189- return fmt.Errorf("failed to read limine.conf: %w", err)
190- }
191-
192- contentStr := string(content)
193-
194- // Find "graphics: yes" and add wallpaper settings after it
195- graphicsRegex := regexp.MustCompile(`(?m)^graphics: yes$`)
196- switch {
197- case graphicsRegex.MatchString(contentStr):
198- wallpaperSettings := "\nwallpaper: boot():/arch-logo.png\nwallpaper_style: centered\nbackdrop: 000000"
199- contentStr = graphicsRegex.ReplaceAllString(contentStr, "graphics: yes"+wallpaperSettings)
200-
201- switch err := p.fs.WriteFile(limineConf, []byte(contentStr), 0644); {
202- case err != nil:
203- return fmt.Errorf("failed to update limine.conf: %w", err)
204- }
205-
206- p.SendOutput(progressChan, "[OK] Boot logo configured")
207- default:
208- p.SendOutput(progressChan, "[WARN] graphics: yes not found in limine.conf")
209- }
210-
211- return nil
212-}
213-
214160 // configurePlymouth sets up Plymouth splash screen
215161 func (p *PostInstallPhase) configurePlymouth(progressChan chan<- ProgressUpdate) error {
216162 p.SendOutput(progressChan, "Setting up Plymouth theme...")
@@ -79,19 +79,10 @@ func (p *PostInstallPhase) PreCheck() error {
79 79
80 // Execute runs the post-install phase80 // Execute runs the post-install phase
81 func (p *PostInstallPhase) Execute(progressChan chan<- ProgressUpdate) PhaseResult {81 func (p *PostInstallPhase) Execute(progressChan chan<- ProgressUpdate) PhaseResult {
82- totalSteps := 982+ totalSteps := 8
83 currentStep := 083 currentStep := 0
84 84
85- // Step 1: Boot logo85+ // Step 1: Plymouth
86- currentStep++
87- p.SendProgress(progressChan, "Installing boot logo...", currentStep, totalSteps)
88- switch err := p.installBootLogo(progressChan); {
89- case err != nil:
90- // Non-fatal, continue
91- p.SendOutput(progressChan, fmt.Sprintf("[WARN] Boot logo failed: %v", err))
92- }
93-
94- // Step 2: Plymouth
95 currentStep++86 currentStep++
96 p.SendProgress(progressChan, "Configuring Plymouth splash screen...", currentStep, totalSteps)87 p.SendProgress(progressChan, "Configuring Plymouth splash screen...", currentStep, totalSteps)
97 switch err := p.configurePlymouth(progressChan); {88 switch err := p.configurePlymouth(progressChan); {
@@ -166,51 +157,6 @@ func (p *PostInstallPhase) Execute(progressChan chan<- ProgressUpdate) PhaseResu
166 return PhaseResult{Success: true, Message: "Post-installation complete"}157 return PhaseResult{Success: true, Message: "Post-installation complete"}
167 }158 }
168 159
169-// installBootLogo downloads and configures Limine boot logo
170-func (p *PostInstallPhase) installBootLogo(progressChan chan<- ProgressUpdate) error {
171- p.SendOutput(progressChan, "Downloading Arch logo...")
172-
173- data, err := p.fetchFile(config.ArchLogoURL)
174- if err != nil {
175- return fmt.Errorf("failed to fetch logo: %w", err)
176- }
177-
178- if err := p.fs.WriteFile(config.PathMntBootLogo, data, 0644); err != nil {
179- return fmt.Errorf("failed to write logo: %w", err)
180- }
181-
182- p.SendOutput(progressChan, "[OK] Logo downloaded")
183-
184- // Update Limine config
185- limineConf := config.PathMntBootLimineConf
186- content, err := p.fs.ReadFile(limineConf)
187- switch {
188- case err != nil:
189- return fmt.Errorf("failed to read limine.conf: %w", err)
190- }
191-
192- contentStr := string(content)
193-
194- // Find "graphics: yes" and add wallpaper settings after it
195- graphicsRegex := regexp.MustCompile(`(?m)^graphics: yes$`)
196- switch {
197- case graphicsRegex.MatchString(contentStr):
198- wallpaperSettings := "\nwallpaper: boot():/arch-logo.png\nwallpaper_style: centered\nbackdrop: 000000"
199- contentStr = graphicsRegex.ReplaceAllString(contentStr, "graphics: yes"+wallpaperSettings)
200-
201- switch err := p.fs.WriteFile(limineConf, []byte(contentStr), 0644); {
202- case err != nil:
203- return fmt.Errorf("failed to update limine.conf: %w", err)
204- }
205-
206- p.SendOutput(progressChan, "[OK] Boot logo configured")
207- default:
208- p.SendOutput(progressChan, "[WARN] graphics: yes not found in limine.conf")
209- }
210-
211- return nil
212-}
213-
214 // configurePlymouth sets up Plymouth splash screen160 // configurePlymouth sets up Plymouth splash screen
215 func (p *PostInstallPhase) configurePlymouth(progressChan chan<- ProgressUpdate) error {161 func (p *PostInstallPhase) configurePlymouth(progressChan chan<- ProgressUpdate) error {
216 p.SendOutput(progressChan, "Setting up Plymouth theme...")162 p.SendOutput(progressChan, "Setting up Plymouth theme...")
M internal/ui/installer_forms.go +7 -2
@@ -12,7 +12,7 @@ import (
1212 )
1313
1414 // CreatePreflightForm creates the initial configuration form with all user identity fields
15-func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *system.System) *huh.Form {
15+func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *system.System, version string) *huh.Form {
1616
1717 // Auto-detect timezone from API, fallback to default if detection fails
1818 detectedTimezone := sys.DetectTimezone()
@@ -28,6 +28,11 @@ func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *sy
2828 cfg.Keymap = config.KeymapDefault
2929 }
3030
31+ title := "ArchUp Installer"
32+ if version != "" {
33+ title += " " + version
34+ }
35+
3136 return fb.CreateForm(
3237 // System configuration
3338 huh.NewGroup(
@@ -35,7 +40,7 @@ func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *sy
3540 fb.TextInput("Username", "> ", &cfg.Username, validation.ValidateUsername),
3641 fb.TextInput("Email (optional)", "> ", &cfg.Email, nil).
3742 Description(DescEmail),
38- ).Title(SectionUserIdentity),
43+ ).Title(title),
3944
4045 // Passwords
4146 huh.NewGroup(
@@ -12,7 +12,7 @@ import (
12 )12 )
13 13
14 // CreatePreflightForm creates the initial configuration form with all user identity fields14 // CreatePreflightForm creates the initial configuration form with all user identity fields
15-func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *system.System) *huh.Form {15+func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *system.System, version string) *huh.Form {
16 16
17 // Auto-detect timezone from API, fallback to default if detection fails17 // Auto-detect timezone from API, fallback to default if detection fails
18 detectedTimezone := sys.DetectTimezone()18 detectedTimezone := sys.DetectTimezone()
@@ -28,6 +28,11 @@ func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *sy
28 cfg.Keymap = config.KeymapDefault28 cfg.Keymap = config.KeymapDefault
29 }29 }
30 30
31+ title := "ArchUp Installer"
32+ if version != "" {
33+ title += " " + version
34+ }
35+
31 return fb.CreateForm(36 return fb.CreateForm(
32 // System configuration37 // System configuration
33 huh.NewGroup(38 huh.NewGroup(
@@ -35,7 +40,7 @@ func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *sy
35 fb.TextInput("Username", "> ", &cfg.Username, validation.ValidateUsername),40 fb.TextInput("Username", "> ", &cfg.Username, validation.ValidateUsername),
36 fb.TextInput("Email (optional)", "> ", &cfg.Email, nil).41 fb.TextInput("Email (optional)", "> ", &cfg.Email, nil).
37 Description(DescEmail),42 Description(DescEmail),
38- ).Title(SectionUserIdentity),43+ ).Title(title),
39 44
40 // Passwords45 // Passwords
41 huh.NewGroup(46 huh.NewGroup(
M internal/ui/model.go +3 -3
@@ -119,7 +119,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
119119 // Network check passed, move to preflight form
120120 m.logger.Info("Moving to preflight form")
121121 m.state = StatePreflightForm
122- m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system)
122+ m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system, m.version)
123123 return m, m.currentForm.Init()
124124
125125 case StateConfirmation:
@@ -133,7 +133,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
133133 switch m.state {
134134 case StateDiskForm:
135135 m.state = StatePreflightForm
136- m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system)
136+ m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system, m.version)
137137 return m, m.currentForm.Init()
138138
139139 case StateOptionsForm:
@@ -169,7 +169,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
169169 // Network check passed - automatically continue to preflight form
170170 m.logger.Info("Network check passed - continuing automatically")
171171 m.state = StatePreflightForm
172- m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system)
172+ m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system, m.version)
173173 return m, m.currentForm.Init()
174174
175175 case phaseCompleteMsg:
@@ -119,7 +119,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
119 // Network check passed, move to preflight form119 // Network check passed, move to preflight form
120 m.logger.Info("Moving to preflight form")120 m.logger.Info("Moving to preflight form")
121 m.state = StatePreflightForm121 m.state = StatePreflightForm
122- m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system)122+ m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system, m.version)
123 return m, m.currentForm.Init()123 return m, m.currentForm.Init()
124 124
125 case StateConfirmation:125 case StateConfirmation:
@@ -133,7 +133,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
133 switch m.state {133 switch m.state {
134 case StateDiskForm:134 case StateDiskForm:
135 m.state = StatePreflightForm135 m.state = StatePreflightForm
136- m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system)136+ m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system, m.version)
137 return m, m.currentForm.Init()137 return m, m.currentForm.Init()
138 138
139 case StateOptionsForm:139 case StateOptionsForm:
@@ -169,7 +169,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
169 // Network check passed - automatically continue to preflight form169 // Network check passed - automatically continue to preflight form
170 m.logger.Info("Network check passed - continuing automatically")170 m.logger.Info("Network check passed - continuing automatically")
171 m.state = StatePreflightForm171 m.state = StatePreflightForm
172- m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system)172+ m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system, m.version)
173 return m, m.currentForm.Init()173 return m, m.currentForm.Init()
174 174
175 case phaseCompleteMsg:175 case phaseCompleteMsg:

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