Skip to main content

worktree_io/issue/parse/
mod.rs

1mod azure;
2mod centy;
3mod detect;
4mod gh;
5mod github;
6mod gitlab;
7mod jira;
8mod options;
9mod shorthand;
10mod worktree_url;
11
12use anyhow::{bail, Result};
13
14use super::IssueRef;
15
16impl IssueRef {
17    /// Parse any of the supported input formats:
18    /// - `https://github.com/owner/repo/issues/42`
19    /// - `worktree://open?owner=X&repo=Y&issue=42`
20    /// - `worktree://open?url=<encoded-github-url>`
21    /// - `worktree://open?owner=X&repo=Y&linear_id=<uuid>`
22    /// - `owner/repo#42`
23    /// - `owner/repo@<linear-uuid>`
24    /// - `centy:<number>` (context-aware: finds nearest `.centy/` ancestor)
25    /// - `gh:<number>` (context-aware: resolves against the `origin` GitHub remote)
26    /// - `owner/repo` (ad-hoc: auto-generates a random branch name)
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if `s` does not match any supported format or if the
31    /// extracted values (e.g. issue number) are invalid.
32    pub fn parse(s: &str) -> Result<Self> {
33        let s = s.trim();
34
35        if s.starts_with("worktree://") {
36            let (issue, _opts) = worktree_url::parse_worktree_url(s)?;
37            return Ok(issue);
38        }
39
40        if s.starts_with("https://github.com") || s.starts_with("http://github.com") {
41            return github::parse_github_url(s);
42        }
43        if s.starts_with("https://dev.azure.com") || s.starts_with("http://dev.azure.com") {
44            return azure::parse_azure_devops_url(s);
45        }
46        if s.starts_with("https://gitlab.com") || s.starts_with("http://gitlab.com") {
47            return gitlab::parse_gitlab_url(s);
48        }
49
50        if s.contains(".atlassian.net/browse/") {
51            return jira::parse_jira_browse_url(s);
52        }
53
54        if s.starts_with("centy:") {
55            return centy::parse_centy(s);
56        }
57
58        if s.starts_with("gh:") {
59            return gh::parse_gh(s);
60        }
61
62        if s.starts_with("gl:") {
63            return gitlab::parse_gl(s);
64        }
65
66        if let Some(result) = shorthand::try_parse_shorthand(s) {
67            return result;
68        }
69
70        if let Some((owner, repo)) = s.split_once('/') {
71            if !owner.is_empty() && !repo.is_empty() && !repo.contains('/') {
72                return Ok(Self::Adhoc {
73                    owner: owner.to_string(),
74                    repo: repo.to_string(),
75                    name: crate::name_gen::generate_name(),
76                });
77            }
78        }
79
80        bail!(
81            "Could not parse issue reference: {s:?}\n\
82             Supported formats:\n\
83             - https://github.com/owner/repo/issues/42\n\
84             - https://gitlab.com/owner/repo/-/issues/42\n\
85             - https://dev.azure.com/org/project/_workitems/edit/42\n\
86             - worktree://open?owner=owner&repo=repo&issue=42\n\
87             - worktree://open?owner=owner&repo=repo&linear_id=<uuid>\n\
88             - worktree://open?org=org&project=project&repo=repo&work_item_id=42\n\
89             - worktree://open?jira_host=host&jira_issue_key=PROJ-42&owner=owner&repo=repo\n\
90             - worktree://open?gitlab_host=gitlab.com&owner=owner&repo=repo&issue=42\n\
91             - owner/repo#42\n\
92             - owner/repo@<linear-uuid>\n\
93             - org/project/repo!42\n\
94             - centy:<number>\n\
95             - owner/repo (ad-hoc with random branch)\n\
96             - gh:<number>\n\
97             - gl:<number>"
98        )
99    }
100}