Commit 7c015c39b3
Verified · cmc
Layout: unified · split
CHANGELOG.org +28 −18
| @@ -14,18 +14,29 @@ anything beyond "replace the binary and restart" is needed. | |||
| 14 | - A wiki link to an existing non-page file (an .svg, .txt, .pdf) now | 14 | - A wiki link to an existing non-page file (an .svg, .txt, .pdf) now |
| 15 | resolves to the raw route instead of 404ing against the page route | 15 | resolves to the raw route instead of 404ing against the page route |
| 16 | (#283). | 16 | (#283). |
| 17 | |||
| 18 | - The new-issue form takes labels, milestone and assignee in one step | 17 | - The new-issue form takes labels, milestone and assignee in one step |
| 19 | for writers; the watch button names watching, muted and default; a | 18 | for writers; a Discussion heading sits before comment threads; the |
| 20 | Discussion heading sits before comment threads; the build page's | 19 | build page's live note says the page updates itself; and the rail |
| 21 | live note says the page updates itself; and the rail and the phone | 20 | and the phone More menu render from one list (#271). |
| 22 | More menu render from one list (#271). | 21 | - The new-issue form keeps milestone and assignee through preview and |
| 23 | 22 | a refused create, the way it already kept title, body and labels; | |
| 23 | a refused create re-renders the form with the draft and the | ||
| 24 | refusal instead of an error page (#271). | ||
| 25 | - The merge request range-diff page renders a bad =from=/=to== query | ||
| 26 | parameter inline instead of 404ing; only an unknown merge request | ||
| 27 | 404s (#271). | ||
| 24 | - Empty states on the web state the fact instead of a CLI command, and | 28 | - Empty states on the web state the fact instead of a CLI command, and |
| 25 | drop "yet" on a finished item; the merge request list offers a New | 29 | drop "yet" on a finished item; the merge request list offers a New |
| 26 | merge request link, a fork link, or a sign-in prompt depending on | 30 | merge request link, a fork link, or a sign-in prompt depending on |
| 27 | what the visitor can do; and the search page's scope caption is | 31 | what the visitor can do; and the search page's scope caption is |
| 28 | always visible, not only before a first search (#270). | 32 | always visible, not only before a first search (#270). |
| 33 | - Issues, milestones, org milestones and releases drop the CLI command | ||
| 34 | from their empty states too, matching the rest of the register: a | ||
| 35 | link to the web form that does the thing when one exists, otherwise | ||
| 36 | just the fact (#270). | ||
| 37 | - The merge request list and compare page offer New merge request to a | ||
| 38 | reader who owns a writable fork of the repository, not only to a | ||
| 39 | writer (#270). | ||
| 29 | 40 | ||
| 30 | Credentials and sessions: revocation, delegation, expiry and an idle | 41 | Credentials and sessions: revocation, delegation, expiry and an idle |
| 31 | timeout (#256, #257, #276, #277). | 42 | timeout (#256, #257, #276, #277). |
| @@ -148,18 +159,17 @@ missing, =gitbayd admin backup --verify <archive>= names it, and | |||
| 148 | - A =-- foreign_keys: off= migration's =foreign_key_check= now runs | 159 | - A =-- foreign_keys: off= migration's =foreign_key_check= now runs |
| 149 | inside the migration's own transaction, before commit, so a | 160 | inside the migration's own transaction, before commit, so a |
| 150 | violation rolls the migration back instead of leaving the bad | 161 | violation rolls the migration back instead of leaving the bad |
| 151 | schema and =user_version= already persisted (#261). | 162 | schema and =user_version= already persisted; the web pin and watch |
| 152 | - The web pin and watch buttons dispatch through =repo pin=/=unpin= | 163 | buttons dispatch through =repo pin=/=unpin= and =repo |
| 153 | and =repo watch=/=mute=/=unwatch= instead of writing the store | 164 | watch=/=mute=/=unwatch= instead of writing the store directly, so a |
| 154 | directly, so a refusal reaches the viewer as a message instead of | 165 | refusal reaches the viewer as a message instead of being dropped, |
| 155 | being dropped. The watch button now cycles three states — default, | 166 | and the watch button now cycles three states — default, watching, |
| 156 | watching, muted — instead of two (#261). | 167 | muted — instead of two; the response that consumes a login link's |
| 157 | - The response that consumes a login link's =?token== sends | 168 | =?token== sends =Cache-Control: no-store=, so no intermediary keeps |
| 158 | =Cache-Control: no-store=, so no intermediary keeps a copy of the | 169 | a copy of the single-use URL; and wiki documentation fixes: |
| 159 | single-use URL (#261). | 170 | API.org clarifies token commands work on the API, Parity.org |
| 160 | - Wiki documentation fixes: API.org clarifies token commands work on the | 171 | documents batched review and web watch/pin dispatch, Threat-Model.org |
| 161 | API, Parity.org documents batched review and web watch/pin dispatch, | 172 | documents the login-link URL exception (#261). |
| 162 | Threat-Model.org documents the login-link URL exception (#261). | ||
| 163 | - The account settings page quotes the CLI and SSH command forms that | 173 | - The account settings page quotes the CLI and SSH command forms that |
| 164 | actually resolve; a test runs every command a web page quotes against | 174 | actually resolve; a test runs every command a web page quotes against |
| 165 | the CLI and control registries so a renamed command fails CI instead | 175 | the CLI and control registries so a renamed command fails CI instead |
internal/httpd/accounts_test.go +2 −2
| @@ -21,7 +21,7 @@ func TestRegisteredPageNumberedStepsAndTokenMention(t *testing.T) { | |||
| 21 | if !strings.Contains(out, "<ol>") { | 21 | if !strings.Contains(out, "<ol>") { |
| 22 | t.Error("next steps are not a numbered list") | 22 | t.Error("next steps are not a numbered list") |
| 23 | } | 23 | } |
| 24 | if !strings.Contains(out, "Settings → Tokens") { | 24 | if !strings.Contains(out, "Settings → API tokens") { |
| 25 | t.Error("no mention of Settings → Tokens for the iOS app") | 25 | t.Error("no mention of Settings → API tokens for the iOS app") |
| 26 | } | 26 | } |
| 27 | } | 27 | } |
internal/httpd/mrrangediff.go +4 −4
| @@ -37,11 +37,11 @@ func (s *Server) mrRangeDiff(w http.ResponseWriter, r *http.Request) { | |||
| 37 | if to := r.URL.Query().Get("to"); to != "" { | 37 | if to := r.URL.Query().Get("to"); to != "" { |
| 38 | argv = append(argv, "--to", to) | 38 | argv = append(argv, "--to", to) |
| 39 | } | 39 | } |
| 40 | // Only an unknown MR 404s (checked above). A bad --from/--to also | ||
| 41 | // resolves to nothing in git, which range-diff reports as | ||
| 42 | // ExitNotFound too, so that result renders on the page instead of | ||
| 43 | // turning a bad query parameter into a 404 (#271). | ||
| 40 | out, msg, code := s.runControlCode(viewer, argv) | 44 | out, msg, code := s.runControlCode(viewer, argv) |
| 41 | if code == protocol.ExitNotFound { | ||
| 42 | s.notFound(w, r) | ||
| 43 | return | ||
| 44 | } | ||
| 45 | errMsg := "" | 45 | errMsg := "" |
| 46 | if code != protocol.ExitOK { | 46 | if code != protocol.ExitOK { |
| 47 | errMsg = msg | 47 | errMsg = msg |
internal/httpd/mrrangediff_test.go +12 −5
| @@ -228,8 +228,8 @@ func TestMRRangeDiffPagePrivateRepo(t *testing.T) { | |||
| 228 | } | 228 | } |
| 229 | 229 | ||
| 230 | // --from/--to reach the control command as real argv: an unknown | 230 | // --from/--to reach the control command as real argv: an unknown |
| 231 | // revision is refused with not-found, and two real revisions render the | 231 | // revision renders the command's refusal on the page, and two real |
| 232 | // range-diff between exactly those two. | 232 | // revisions render the range-diff between exactly those two. |
| 233 | func TestMRRangeDiffPageFromToQuery(t *testing.T) { | 233 | func TestMRRangeDiffPageFromToQuery(t *testing.T) { |
| 234 | st, cfg, alice, _, repo, n, v1, v2, _ := rangeDiffFixture(t) | 234 | st, cfg, alice, _, repo, n, v1, v2, _ := rangeDiffFixture(t) |
| 235 | s := New(cfg, st, nil) | 235 | s := New(cfg, st, nil) |
| @@ -244,11 +244,18 @@ func TestMRRangeDiffPageFromToQuery(t *testing.T) { | |||
| 244 | return httptest.NewRecorder(), req | 244 | return httptest.NewRecorder(), req |
| 245 | } | 245 | } |
| 246 | 246 | ||
| 247 | t.Run("unknown revision is 404", func(t *testing.T) { | 247 | // A bad --from/--to is a query parameter, not an unknown merge |
| 248 | // request: it renders the command's refusal inline rather than | ||
| 249 | // 404ing the page, which is reserved for an MR that does not exist | ||
| 250 | // (#271). | ||
| 251 | t.Run("unknown revision renders the refusal inline", func(t *testing.T) { | ||
| 248 | rr, req := newReq("?from=0000000000000000000000000000000000000000") | 252 | rr, req := newReq("?from=0000000000000000000000000000000000000000") |
| 249 | s.mrRangeDiff(rr, req) | 253 | s.mrRangeDiff(rr, req) |
| 250 | if rr.Code != 404 { | 254 | if rr.Code != 200 { |
| 251 | t.Fatalf("status %d, want 404, body %s", rr.Code, rr.Body.String()) | 255 | t.Fatalf("status %d, want 200 (the refusal renders on the page), body %s", rr.Code, rr.Body.String()) |
| 256 | } | ||
| 257 | if !strings.Contains(rr.Body.String(), "is not a revision of") { | ||
| 258 | t.Errorf("page does not show the refusal:\n%s", rr.Body.String()) | ||
| 252 | } | 259 | } |
| 253 | }) | 260 | }) |
| 254 | 261 | ||
internal/store/store.go +1 −1
| @@ -284,7 +284,7 @@ func (s *Store) migrateStep(sqlText string, newVersion int, fkOff bool) (retErr | |||
| 284 | return err | 284 | return err |
| 285 | } | 285 | } |
| 286 | rows.Close() | 286 | rows.Close() |
| 287 | return fmt.Errorf("foreign_key_check failed after migration: %s row %v", table, rowid) | 287 | return fmt.Errorf("foreign_key_check failed after migration: %s row %d", table, rowid.Int64) |
| 288 | } | 288 | } |
| 289 | if err := rows.Err(); err != nil { | 289 | if err := rows.Err(); err != nil { |
| 290 | rows.Close() | 290 | rows.Close() |
internal/web/templates/account.html +1 −1
| @@ -232,7 +232,7 @@ narrowest scope and shortest lifetime the job needs.</p> | |||
| 232 | gitbay admin ... # instance administration</pre> | 232 | gitbay admin ... # instance administration</pre> |
| 233 | <p class="meta">All of it works from stock OpenSSH too, with the CLI's | 233 | <p class="meta">All of it works from stock OpenSSH too, with the CLI's |
| 234 | grouping words dropped: <code>ssh git@{{.Host}} whoami</code>, | 234 | grouping words dropped: <code>ssh git@{{.Host}} whoami</code>, |
| 235 | <code>ssh git@{{.Host}} token create --name laptop</code>.</p> | 235 | <code>ssh git@{{.Host}} web sessions list</code>.</p> |
| 236 | </section> | 236 | </section> |
| 237 | </div> | 237 | </div> |
| 238 | </div> | 238 | </div> |
internal/web/templates/privacy.html +1 −1
| @@ -30,7 +30,7 @@ is active is kept in app preferences; nothing else is retained on the | |||
| 30 | device. The app embeds no analytics, no crash reporting, and no | 30 | device. The app embeds no analytics, no crash reporting, and no |
| 31 | third-party SDK, so there is nothing to opt out of. Removing the account | 31 | third-party SDK, so there is nothing to opt out of. Removing the account |
| 32 | deletes the token from the Keychain.</p> | 32 | deletes the token from the Keychain.</p> |
| 33 | <p>Tokens are minted over SSH and can be revoked at any time with | 33 | <p>Tokens are minted over SSH or on the settings page and can be revoked at any time with |
| 34 | <code>gitbay auth token revoke</code>, which ends the app's access | 34 | <code>gitbay auth token revoke</code>, which ends the app's access |
| 35 | immediately.</p> | 35 | immediately.</p> |
| 36 | 36 | ||
internal/web/templates/registered.html +2 −2
| @@ -8,10 +8,10 @@ | |||
| 8 | <ol> | 8 | <ol> |
| 9 | <li>Copy the verification code from the mail you were just sent.</li> | 9 | <li>Copy the verification code from the mail you were just sent.</li> |
| 10 | <li><a href="/login">Sign in</a> with an emailed link.</li> | 10 | <li><a href="/login">Sign in</a> with an emailed link.</li> |
| 11 | <li>Paste the code in <a href="/settings#emails">Settings → Email</a>.</li> | 11 | <li>Paste the code in <a href="/settings#emails">Settings → Email addresses</a>.</li> |
| 12 | </ol> | 12 | </ol> |
| 13 | <p>Then + creates your first repository. Using the iOS app? Create a | 13 | <p>Then + creates your first repository. Using the iOS app? Create a |
| 14 | token in <a href="/settings#tokens">Settings → Tokens</a>.</p> | 14 | token in <a href="/settings#tokens">Settings → API tokens</a>.</p> |
| 15 | <h2>From the terminal</h2> | 15 | <h2>From the terminal</h2> |
| 16 | <pre class="quickstart" tabindex="0">ssh git@{{.Host}} whoami | 16 | <pre class="quickstart" tabindex="0">ssh git@{{.Host}} whoami |
| 17 | ssh git@{{.Host}} repo create {{.Username}}/hello | 17 | ssh git@{{.Host}} repo create {{.Username}}/hello |