v2.16.0 ebd2ab3

auth.go RawHistoryFolder

1package remote
2
3import (
4	"bytes"
5	"context"
6	"encoding/json"
7	"fmt"
8	"io"
9	"net/http"
10)
11
12// PasswordRequest represents the request body for POST /auth/password.
13type PasswordRequest struct {
14	Username string `json:"username"`
15	Password string `json:"password"`
16}
17
18// PasswordResponse represents the response from POST /auth/password.
19type PasswordResponse struct {
20	Token     string `json:"token"`
21	ExpiresIn int    `json:"expires_in"`
22	IssuedAt  string `json:"issued_at"`
23}
24
25// Authenticate calls POST /auth/password and returns a token.
26// This method does NOT require an existing token since it's used to obtain one.
27func (c *Client) Authenticate(ctx context.Context, username, password string) (*PasswordResponse, error) {
28	url := c.baseURL + "/auth/password"
29
30	reqBody := PasswordRequest{
31		Username: username,
32		Password: password,
33	}
34
35	jsonBody, err := json.Marshal(reqBody)
36	if err != nil {
37		return nil, fmt.Errorf("failed to marshal request body: %w", err)
38	}
39
40	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(jsonBody))
41	if err != nil {
42		return nil, fmt.Errorf("failed to create request: %w", err)
43	}
44
45	req.Header.Set("Content-Type", "application/json")
46	req.Header.Set("Accept", "application/json")
47
48	resp, err := c.httpClient.Do(req)
49	if err != nil {
50		return nil, fmt.Errorf("failed to connect: %w", err)
51	}
52	defer resp.Body.Close()
53
54	if resp.StatusCode >= 400 {
55		body, _ := io.ReadAll(resp.Body)
56		var errResp struct {
57			Error string `json:"error"`
58		}
59		if err := json.Unmarshal(body, &errResp); err == nil && errResp.Error != "" {
60			return nil, fmt.Errorf("%s: %s", resp.Status, errResp.Error)
61		}
62		return nil, fmt.Errorf("%s: %s", resp.Status, string(body))
63	}
64
65	var result PasswordResponse
66	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
67		return nil, fmt.Errorf("failed to decode response: %w", err)
68	}
69
70	return &result, nil
71}
72
73// UpdateRemoteToken updates the token for a named remote.
74func UpdateRemoteToken(name, token string) error {
75	config, err := LoadRemotes("")
76	if err != nil {
77		return err
78	}
79
80	remote, ok := config.Remotes[name]
81	if !ok {
82		return fmt.Errorf("remote '%s' not found", name)
83	}
84
85	remote.Token = token
86	remote.TokenEnv = "" // Clear token_env when setting token directly
87	config.Remotes[name] = remote
88
89	return SaveRemotes("", config)
90}

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 7

Tags 106

↑↓ moveenter openesc close