dir.go RawHistoryFolder

1package platform
2
3import (
4	"fmt"
5	"os"
6	"path/filepath"
7)
8
9const devEnvironmentRootEnv = "VEV_ENV_ROOT"
10
11// ActivateDevelopmentEnvironment redirects vev's XDG roots into an isolated
12// development tree when VEV_ENV is set. The absolute .dev base is retained in
13// VEV_ENV_ROOT so re-executed roles do not rebase after changing directory.
14func ActivateDevelopmentEnvironment() error {
15	name := os.Getenv("VEV_ENV")
16	if name == "" {
17		return nil
18	}
19	if !validDevelopmentEnvironmentName(name) {
20		return fmt.Errorf("vev: invalid VEV_ENV %q (want one safe segment matching [A-Za-z0-9][A-Za-z0-9._-]{0,63})", name)
21	}
22
23	base := os.Getenv(devEnvironmentRootEnv)
24	if base == "" {
25		absolute, err := filepath.Abs(".dev")
26		if err != nil {
27			return fmt.Errorf("vev: resolve VEV_ENV %q root: %w", name, err)
28		}
29		base = absolute
30	} else if !filepath.IsAbs(base) {
31		return fmt.Errorf("vev: invalid %s %q (want an absolute path)", devEnvironmentRootEnv, base)
32	}
33	root := filepath.Join(base, name)
34
35	values := map[string]string{
36		devEnvironmentRootEnv: base,
37		"XDG_CONFIG_HOME":     filepath.Join(root, "config"),
38		"XDG_STATE_HOME":      filepath.Join(root, "state"),
39		"XDG_RUNTIME_DIR":     filepath.Join(root, "runtime"),
40	}
41	type previousValue struct {
42		value string
43		set   bool
44	}
45	previous := make(map[string]previousValue, len(values))
46	for key := range values {
47		value, set := os.LookupEnv(key)
48		previous[key] = previousValue{value: value, set: set}
49	}
50	for key, value := range values {
51		if err := os.Setenv(key, value); err != nil {
52			for rollbackKey, old := range previous {
53				if old.set {
54					_ = os.Setenv(rollbackKey, old.value)
55				} else {
56					_ = os.Unsetenv(rollbackKey)
57				}
58			}
59			return fmt.Errorf("vev: activate VEV_ENV %q: %w", name, err)
60		}
61	}
62	return nil
63}
64
65func validDevelopmentEnvironmentName(name string) bool {
66	if len(name) == 0 || len(name) > 64 || !asciiAlphaNumeric(name[0]) {
67		return false
68	}
69	for i := 1; i < len(name); i++ {
70		if !asciiAlphaNumeric(name[i]) && name[i] != '.' && name[i] != '_' && name[i] != '-' {
71			return false
72		}
73	}
74	return true
75}
76
77func asciiAlphaNumeric(value byte) bool {
78	return value >= 'A' && value <= 'Z' || value >= 'a' && value <= 'z' || value >= '0' && value <= '9'
79}
80
81// RuntimeDirFor returns the XDG_RUNTIME_DIR a vev process with environment
82// getenv and working directory cwd uses once VEV_ENV is activated. It lets a
83// caller resolve another process' runtime from its initial environment.
84func RuntimeDirFor(getenv func(string) string, cwd string) string {
85	name := getenv("VEV_ENV")
86	if name == "" || !validDevelopmentEnvironmentName(name) {
87		return getenv("XDG_RUNTIME_DIR")
88	}
89	base := getenv(devEnvironmentRootEnv)
90	if base == "" {
91		base = filepath.Join(cwd, ".dev")
92	}
93	return filepath.Join(base, name, "runtime")
94}
95
96// DevelopmentEnvironmentTempDir returns the private temporary-file directory
97// for an activated development environment. Ordinary invocations use the
98// existing os.TempDir fallback in the daemon.
99func DevelopmentEnvironmentTempDir() (string, bool) {
100	name := os.Getenv("VEV_ENV")
101	base := os.Getenv(devEnvironmentRootEnv)
102	if name == "" || base == "" {
103		return "", false
104	}
105	return filepath.Join(base, name, "tmp"), true
106}
107
108// StateDir returns the directory vev writes state to: $XDG_STATE_HOME/vev if
109// set, else ~/.local/state/vev.
110func StateDir() string {
111	if xdg := os.Getenv("XDG_STATE_HOME"); xdg != "" {
112		return filepath.Join(xdg, "vev")
113	}
114	home, err := os.UserHomeDir()
115	if err != nil || home == "" {
116		// Last resort: a per-uid temp path so state setup never aborts startup.
117		return filepath.Join(os.TempDir(), fmt.Sprintf("vev-state-%d", os.Getuid()))
118	}
119	return filepath.Join(home, ".local", "state", "vev")
120}
121
122// ConfigPath returns the user config file path: $XDG_CONFIG_HOME/vev/config if
123// set, else ~/.config/vev/config.
124func ConfigPath() string {
125	if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" {
126		return filepath.Join(xdg, "vev", "config")
127	}
128	home, err := os.UserHomeDir()
129	if err != nil || home == "" {
130		// Last resort: a per-uid temp path so config lookup never aborts startup.
131		return filepath.Join(os.TempDir(), fmt.Sprintf("vev-config-%d", os.Getuid()), "config")
132	}
133	return filepath.Join(home, ".config", "vev", "config")
134}
135
136// DirOrHome returns dir when it names an existing directory. Otherwise it
137// falls back to the current user's home directory, then to / as a last resort.
138func DirOrHome(dir string) string {
139	if dir != "" {
140		if st, err := os.Stat(dir); err == nil && st.IsDir() {
141			return dir
142		}
143	}
144	if home, err := os.UserHomeDir(); err == nil && home != "" {
145		return home
146	}
147	return "/"
148}

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 12

Tags 11

↑↓ moveenter openesc close