OXYGENHUB

The Code panel offers git clone when the Git mirror is on 7b17d570d7ee

oxy clone first, then git clone with the mirror's address and a copy button,
then the downloads; without the mirror the panel is as before. Tested both
ways in the mirror test.

Closes #215
Author
Andy McCall @andymccall <[email protected]>
via
agent Claude Code <[email protected]> · delegate for changes, until 2026-11-13 19:00Z · key ea5f78160088
When
· 2026-10-16 02:13 UTC
Signature
✔ signed by Claude Code
Hash
7b17d570d7ee61ba1da2460188fdd55f92c2013c6d56e0181e11088788440768
Depends on
19bb31ff1f5c 3f4b0ea6eabf 67b24adbf35a 83d5052bf76c d23464c0ac60
Depended on by
84e3c09656b2 a7aa76ac52d7
In bookmarks
api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
5 atoms, 0 tree operations, 3 files

Browse the tree at this change

modified crates/hub/src/web/mod.rs +3 −0
…
304 unchanged lines
305 305
    pub visibility: String,
306 306
    pub tab: &'static str,
307 307
    pub clone_url: String,
308 +
    /// The Git mirror's address, when the mirror is on (oxygenhub#215).
309 +
    pub git_url: Option<String>,
308 310
    /// The viewer's role, for showing settings and write actions.
309 311
    pub role: Option<Role>,
310 312
    /// Open issues and proposals, shown on their tabs.
…
12 unchanged lines
323 325
            visibility: row.visibility.clone(),
324 326
            tab,
325 327
            clone_url: format!("{}/{}/{}", app.config.base_url(), row.owner, row.name),
328 +
            git_url: row.git_mirror.then(|| format!("{}/{}/{}.git", app.config.base_url(), row.owner, row.name)),
326 329
            role: None,
327 330
            open_issues: row.open_issues,
328 331
            open_proposals: row.open_proposals,
…
248 unchanged lines
modified crates/hub/tests/releases.rs +7 −0
…
229 unchanged lines
230 230
        send(app, "GET", "/andy/demo.git/info/refs?service=git-upload-pack", None, None, None, vec![]).await.status,
231 231
        StatusCode::NOT_FOUND
232 232
    );
233 +
    let overview = send(app, "GET", "/andy/demo", Some(&andy), None, None, vec![]).await.text();
234 +
    assert!(overview.contains("oxy clone ") && !overview.contains("git clone "), "no Git in the Code panel without the mirror");
233 235
    let settings = send(app, "GET", "/andy/demo/settings", Some(&andy), None, None, vec![]).await.text();
234 236
    let r = form(app, "/andy/demo/settings/mirror", &andy, &format!("_csrf={}&enabled=1", csrf_of(&settings))).await;
235 237
    assert!(r.text().contains("mirror is on"), "{}", r.text());
238 +
    // The Code panel then offers git clone too: oxy first, then git, then the downloads (#215).
239 +
    let overview = send(app, "GET", "/andy/demo", Some(&andy), None, None, vec![]).await.text();
240 +
    let at = |s: &str| overview.find(s).unwrap_or_else(|| panic!("{s} in the Code panel"));
241 +
    assert!(at("oxy clone ") < at(&format!("git clone {base}/andy/demo.git")), "oxy first");
242 +
    assert!(at(&format!("data-copy=\"git clone {base}/andy/demo.git\"")) < at("Download ZIP"), "then git, then the downloads");
236 243
    let clone_dir = tempfile::tempdir().unwrap();
237 244
    let url = format!("{base}/andy/demo.git");
238 245
    let target = clone_dir.path().join("demo");
…
89 unchanged lines
modified templates/overview.html +5 −0
…
18 unchanged lines
19 19
          <label for="clone-cmd" class="small mute">Clone with oxy</label>
20 20
          <div class="clone"><input id="clone-cmd" type="text" readonly value="oxy clone {{ repo.clone_url }}" onfocus="this.select()" onclick="this.select()"><button type="button" class="small" data-copy="oxy clone {{ repo.clone_url }}">copy</button></div>
21 21
          <p class="small mute">The URL alone: <code>{{ repo.clone_url }}</code></p>
22 +
          {% if let Some(git) = repo.git_url %}
23 +
          <label for="git-clone-cmd" class="small mute">Clone with Git (a read-only mirror)</label>
24 +
          <div class="clone"><input id="git-clone-cmd" type="text" readonly value="git clone {{ git }}" onfocus="this.select()" onclick="this.select()"><button type="button" class="small" data-copy="git clone {{ git }}">copy</button></div>
25 +
          <p class="small mute">For tools that only speak Git. Push with oxy: the mirror refuses pushes.</p>
26 +
          {% endif %}
22 27
          {% if quickstart.is_none() %}
23 28
          <a class="download" href="{{ repo.url }}/archive?ref={{ repo.default_bookmark|urlencode }}&amp;format=zip" download><span aria-hidden="true">⤓</span> Download ZIP</a>
24 29
          <p class="small mute">{{ repo.default_bookmark }} as it is now, without its history. Also as <a href="{{ repo.url }}/archive?ref={{ repo.default_bookmark|urlencode }}&amp;format=tar.zst" download>.tar.zst</a>.</p>
…
54 unchanged lines