diff --git a/.vscode/queries.github-graphql-nb b/.vscode/queries.github-graphql-nb index 1cc6719..0b7a39d 100644 --- a/.vscode/queries.github-graphql-nb +++ b/.vscode/queries.github-graphql-nb @@ -1 +1 @@ -{"cells":[{"code":"### Get Default Branch & Tip","kind":"markdown"},{"code":"query getDefaultBranchAndTip(\n\t$owner: String!\n\t$repo: String!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tdefaultBranchRef {\n\t\t\tname\n\t\t\ttarget { oid }\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Get Branches","kind":"markdown"},{"code":"query getBranches(\n\t$owner: String!\n\t$repo: String!\n\t$branchQuery: String\n\t$cursor: String\n\t$limit: Int = 100\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\trefs(query: $branchQuery, refPrefix: \"refs/heads/\", first: $limit, after: $cursor, orderBy: { field: TAG_COMMIT_DATE, direction: DESC }) {\n\t\t\tpageInfo {\n\t\t\t\tendCursor\n\t\t\t\thasNextPage\n\t\t\t}\n\t\t\tnodes {\n\t\t\t\tname\n\t\t\t\ttarget {\n\t\t\t\t\toid\n\t\t\t\t\tcommitUrl\n\t\t\t\t\t...on Commit {\n\t\t\t\t\t\tauthoredDate\n\t\t\t\t\t\tcommittedDate\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Get Blame","kind":"markdown"},{"code":"query getBlame(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String!\n) {\n\tviewer { name }\n\trepository(owner: $owner, name: $repo) {\n\t\tobject(expression: $ref) {\n\t\t\t...on Commit {\n\t\t\t\tblame(path: $path) {\n\t\t\t\t\tranges {\n\t\t\t\t\t\tstartingLine\n\t\t\t\t\t\tendingLine\n\t\t\t\t\t\tage\n\t\t\t\t\t\tcommit {\n\t\t\t\t\t\t\toid\n\t\t\t\t\t\t\tparents(first: 3) { nodes { oid } }\n\t\t\t\t\t\t\tmessage\n\t\t\t\t\t\t\tadditions\n\t\t\t\t\t\t\tchangedFiles\n\t\t\t\t\t\t\tdeletions\n\t\t\t\t\t\t\tauthor {\n\t\t\t\t\t\t\t\tavatarUrl\n\t\t\t\t\t\t\t\tdate\n\t\t\t\t\t\t\t\temail\n\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcommitter {\n\t\t\t\t\t\t\t\tdate\n\t\t\t\t\t\t\t\temail\n\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"gitkraken\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"0b8f151bd0458340b7779a64884c97754c3cedb8\",\n\t\"path\": \"package.json\"\n}","kind":"code"},{"code":"### Get Commit for File","kind":"markdown"},{"code":"query getCommitForFile(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tref(qualifiedName: $ref) {\n\t\t\ttarget {\n\t\t\t\t... on Commit {\n\t\t\t\t\thistory(first: 1, path: $path) {\n\t\t\t\t\t\tnodes {\n\t\t\t\t\t\t\toid\n\t\t\t\t\t\t\tparents(first: 3) { nodes { oid } }\n\t\t\t\t\t\t\tmessage\n\t\t\t\t\t\t\tadditions\n\t\t\t\t\t\t\tchangedFiles\n\t\t\t\t\t\t\tdeletions\n\t\t\t\t\t\t\tauthor {\n\t\t\t\t\t\t\t\tdate\n\t\t\t\t\t\t\t\temail\n\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcommitter { date }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"refs/heads/main\",\n\t\"path\": \"src/extension.ts\"\n}","kind":"code"},{"code":"### Get Current User","kind":"markdown"},{"code":"query getCurrentUser(\n\t$owner: String!\n\t$repo: String!\n) {\n\tviewer { name }\n\trepository(name: $repo owner: $owner) {\n\t\tviewerPermission\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Get Commit","kind":"markdown"},{"code":"query getCommit(\n\t$owner: String!\n\t$repo: String!\n\t$ref: GitObjectID!\n) {\n\trepository(name: $repo owner: $owner) {\n\t\tobject(oid: $ref) {\n\t\t\t...on Commit {\n\t\t\t\toid\n\t\t\t\tparents(first: 3) { nodes { oid } }\n\t\t\t\tmessage\n\t\t\t\tadditions\n\t\t\t\tchangedFiles\n\t\t\t\tdeletions\n\t\t\t\tauthor {\n\t\t\t\t\tdate\n\t\t\t\t\temail\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcommitter { date }\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"54f28933055124d6ba3808a787f6947c929f9db0\"\n}","kind":"code"},{"code":"### Get Commits","kind":"markdown"},{"code":"query getCommits(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String\n\t$author: CommitAuthor\n\t$after: String\n\t$before: String\n\t$limit: Int = 100\n\t$since: GitTimestamp\n\t$until: GitTimestamp\n) {\n\tviewer { name }\n\trepository(name: $repo, owner: $owner) {\n\t\tobject(expression: $ref) {\n\t\t\t... on Commit {\n\t\t\t\thistory(first: $limit, author: $author, path: $path, after: $after, before: $before, since: $since, until: $until) {\n\t\t\t\t\tpageInfo {\n\t\t\t\t\t\tstartCursor\n\t\t\t\t\t\tendCursor\n\t\t\t\t\t\thasNextPage\n\t\t\t\t\t\thasPreviousPage\n\t\t\t\t\t}\n\t\t\t\t\tnodes {\n\t\t\t\t\t\t... on Commit {\n\t\t\t\t\t\t\toid\n\t\t\t\t\t\t\tmessage\n\t\t\t\t\t\t\tparents(first: 3) { nodes { oid } }\n\t\t\t\t\t\t\tadditions\n\t\t\t\t\t\t\tchangedFiles\n\t\t\t\t\t\t\tdeletions\n\t\t\t\t\t\t\tauthor {\n\t\t\t\t\t\t\t\tavatarUrl\n\t\t\t\t\t\t\t\tdate\n\t\t\t\t\t\t\t\temail\n\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcommitter {\n\t\t\t\t\t\t\t\t date\n\t\t\t\t\t\t\t\t email\n\t\t\t\t\t\t\t\t name\n\t\t\t\t\t\t\t }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"HEAD\",\n\t\"-path\": \"src/extension.ts\",\n\t\"since\": \"2022-02-07T00:00:00Z\"\n}","kind":"code"},{"code":"### Get Tags","kind":"markdown"},{"code":"query getTags(\n\t$owner: String!\n\t$repo: String!\n\t$tagQuery: String\n\t$cursor: String\n\t$limit: Int = 100\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\trefs(query: $tagQuery, refPrefix: \"refs/tags/\", first: $limit, after: $cursor, orderBy: { field: TAG_COMMIT_DATE, direction: DESC }) {\n\t\t\tpageInfo {\n\t\t\t\tendCursor\n\t\t\t\thasNextPage\n\t\t\t}\n\t\t\tnodes {\n\t\t\t\tname\n\t\t\t\ttarget {\n\t\t\t\t\toid\n\t\t\t\t\tcommitUrl\n\t\t\t\t\t...on Commit {\n\t\t\t\t\t\tauthoredDate\n\t\t\t\t\t\tcommittedDate\n\t\t\t\t\t\tmessage\n\t\t\t\t\t}\n\t\t\t\t\t...on Tag {\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\ttagger { date }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Get collaborators ","kind":"markdown"},{"code":"query getCollaborators (\n\t$owner: String!\n\t$repo: String!\n\t$cursor: String\n\t$limit: Int = 100\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tcollaborators(affiliation: ALL, first: $limit, after: $cursor) {\n\t\t\tpageInfo {\n\t\t\t\tendCursor\n\t\t\t\thasNextPage\n\t\t\t}\n\t\t\tnodes {\n\t\t\t\tname\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Resolve reference","kind":"markdown"},{"code":"query resolveReference(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tobject(expression: $ref) {\n\t\t\t... on Commit {\n\t\t\t\thistory(first: 1, path: $path) {\n\t\t\t\t\tnodes { oid }\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"d790e9db047769de079f6838c3578f3a47bf5930^\",\n\t\"path\": \"CODE_OF_CONDUCT.md\"\n}","kind":"code"},{"code":"### Get branches that contain commit","kind":"markdown"},{"code":"query getCommitBranches(\n\t$owner: String!\n\t$repo: String!\n\t$since: GitTimestamp!\n\t$until: GitTimestamp!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\trefs(first: 20, refPrefix: \"refs/heads/\", orderBy: { field: TAG_COMMIT_DATE, direction: DESC }) {\n\t\t\tnodes {\n\t\t\t\tname\n\t\t\t\ttarget {\n\t\t\t\t\t... on Commit {\n\t\t\t\t\t\thistory(first: 3, since: $since until: $until) {\n\t\t\t\t\t\t\tnodes { oid }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"since\": \"2022-01-06T01:07:46-04:00\",\n\t\"until\": \"2022-01-06T01:07:46-05:00\"\n}","kind":"code"},{"code":"query getCommitBranch(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$since: GitTimestamp!\n\t$until: GitTimestamp!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tref(qualifiedName: $ref) {\n\t\t\ttarget {\n\t\t\t\t... on Commit {\n\t\t\t\t\thistory(first: 3, since: $since until: $until) {\n\t\t\t\t\t\tnodes { oid }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"refs/heads/main\",\n\t\"since\": \"2022-01-06T01:07:46-04:00\",\n\t\"until\": \"2022-01-06T01:07:46-05:00\"\n}","kind":"code"},{"code":"### Get commit count for branch (ref)","kind":"markdown"},{"code":"query getCommitCount(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tref(qualifiedName: $ref) {\n\t\t\ttarget {\n\t\t\t\t... on Commit {\n\t\t\t\t\thistory(first: 1) {\n\t\t\t\t\t\ttotalCount\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"refs/heads/main\"\n}","kind":"code"},{"code":"### Get commit refs (sha)","kind":"markdown"},{"code":"query getCommitRefs(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String\n\t$since: GitTimestamp\n\t$until: GitTimestamp\n\t$limit: Int = 1\n) {\n\tviewer { name }\n\trepository(name: $repo, owner: $owner) {\n\t\tref(qualifiedName: $ref) {\n\t\t\thistory(first: $limit, path: $path, since: $since, until: $until) {\n\t\t\t\tnodes { oid, message }\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"refs/heads/main\",\n\t\"path\": \"extension.ts\",\n\t\"limit\": 2\n}","kind":"code"},{"code":"### Get next file commit","kind":"markdown"},{"code":"query getCommitDate(\n\t$owner: String!\n\t$repo: String!\n\t$ref: GitObjectID!\n) {\n\trepository(name: $repo owner: $owner) {\n\t\tobject(oid: $ref) {\n\t\t\t...on Commit {\n\t\t\t\tcommitter { date }\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"a03ff942c40665c451bd4c7768f46e3e5f00e97c\"\n}","kind":"code"},{"code":"query getNextCommitCursor(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String!\n\t$since: GitTimestamp!\n) {\n\trepository(name: $repo owner: $owner) {\n\t\tobject(expression: $ref) {\n\t\t\t... on Commit {\n\t\t\t\thistory(first:1, path: $path, since: $since) {\n\t\t\t\t\ttotalCount\n\t\t\t\t\tpageInfo { startCursor }\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"b062e960b6ee5ca7ac081dd84d9217bd4b2051e0\",\n \"path\": \"src/extension.ts\",\n \"since\": \"2021-11-03T02:46:29-04:00\"\n}","kind":"code"},{"code":"query getNextCommit(\n\t$owner: String!\n\t$repo: String!\n $ref: String!\n $path: String!\n\t$before: String!\n) {\trepository(name: $repo owner: $owner) {\n object(expression: $ref) {\n ... on Commit {\n history(last:4, path: $path, before: $before) {\n totalCount\n pageInfo {\n startCursor\n }\n nodes {\n oid\n message\n committedDate\n }\n }\n }\n }\n }\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"496c35eaeff2c33d3f1256a25d83198ace6aa6b0\",\n \"path\": \"src/extension.ts\",\n \"before\": \"496c35eaeff2c33d3f1256a25d83198ace6aa6b0 4\"\n}","kind":"code"},{"code":"### Get Pull Request for Branch","kind":"markdown"},{"code":"query getPullRequestForBranch(\n\t$owner: String!\n\t$repo: String!\n\t$branch: String!\n\t$limit: Int!\n\t$include: [PullRequestState!]\n\t$avatarSize: Int\n) {\n\trepository(name: $repo, owner: $owner) {\n\t\trefs(query: $branch, refPrefix: \"refs/heads/\", first: 1) {\n\t\t\tnodes {\n\t\t\t\tassociatedPullRequests(first: $limit, orderBy: {field: UPDATED_AT, direction: DESC}, states: $include) {\n\t\t\t\t\tnodes {\n\t\t\t\t\t\tauthor {\n\t\t\t\t\t\t\tlogin\n\t\t\t\t\t\t\tavatarUrl(size: $avatarSize)\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpermalink\n\t\t\t\t\t\tnumber\n\t\t\t\t\t\ttitle\n\t\t\t\t\t\tstate\n\t\t\t\t\t\tupdatedAt\n\t\t\t\t\t\tclosedAt\n\t\t\t\t\t\tmergedAt\n\t\t\t\t\t\trepository {\n\t\t\t\t\t\t\tisFork\n\t\t\t\t\t\t\towner {\n\t\t\t\t\t\t\t\tlogin\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"gitkraken\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"branch\": \"main\",\n\t\"limit\": 1\n}","kind":"code"}]} \ No newline at end of file +{"cells":[{"code":"### Get Default Branch & Tip","kind":"markdown"},{"code":"query getDefaultBranchAndTip(\n\t$owner: String!\n\t$repo: String!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tdefaultBranchRef {\n\t\t\tname\n\t\t\ttarget { oid }\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Get Branches","kind":"markdown"},{"code":"query getBranches(\n\t$owner: String!\n\t$repo: String!\n\t$branchQuery: String\n\t$cursor: String\n\t$limit: Int = 100\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\trefs(query: $branchQuery, refPrefix: \"refs/heads/\", first: $limit, after: $cursor, orderBy: { field: TAG_COMMIT_DATE, direction: DESC }) {\n\t\t\tpageInfo {\n\t\t\t\tendCursor\n\t\t\t\thasNextPage\n\t\t\t}\n\t\t\tnodes {\n\t\t\t\tname\n\t\t\t\ttarget {\n\t\t\t\t\toid\n\t\t\t\t\tcommitUrl\n\t\t\t\t\t...on Commit {\n\t\t\t\t\t\tauthoredDate\n\t\t\t\t\t\tcommittedDate\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Get Blame","kind":"markdown"},{"code":"query getBlame(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String!\n) {\n\tviewer { name }\n\trepository(owner: $owner, name: $repo) {\n\t\tobject(expression: $ref) {\n\t\t\t...on Commit {\n\t\t\t\tblame(path: $path) {\n\t\t\t\t\tranges {\n\t\t\t\t\t\tstartingLine\n\t\t\t\t\t\tendingLine\n\t\t\t\t\t\tage\n\t\t\t\t\t\tcommit {\n\t\t\t\t\t\t\toid\n\t\t\t\t\t\t\tparents(first: 3) { nodes { oid } }\n\t\t\t\t\t\t\tmessage\n\t\t\t\t\t\t\tadditions\n\t\t\t\t\t\t\tchangedFiles\n\t\t\t\t\t\t\tdeletions\n\t\t\t\t\t\t\tauthor {\n\t\t\t\t\t\t\t\tavatarUrl\n\t\t\t\t\t\t\t\tdate\n\t\t\t\t\t\t\t\temail\n\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcommitter {\n\t\t\t\t\t\t\t\tdate\n\t\t\t\t\t\t\t\temail\n\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"gitkraken\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"0b8f151bd0458340b7779a64884c97754c3cedb8\",\n\t\"path\": \"package.json\"\n}","kind":"code"},{"code":"### Get Commit for File","kind":"markdown"},{"code":"query getCommitForFile(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tref(qualifiedName: $ref) {\n\t\t\ttarget {\n\t\t\t\t... on Commit {\n\t\t\t\t\thistory(first: 1, path: $path) {\n\t\t\t\t\t\tnodes {\n\t\t\t\t\t\t\toid\n\t\t\t\t\t\t\tparents(first: 3) { nodes { oid } }\n\t\t\t\t\t\t\tmessage\n\t\t\t\t\t\t\tadditions\n\t\t\t\t\t\t\tchangedFiles\n\t\t\t\t\t\t\tdeletions\n\t\t\t\t\t\t\tauthor {\n\t\t\t\t\t\t\t\tdate\n\t\t\t\t\t\t\t\temail\n\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcommitter { date }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"refs/heads/main\",\n\t\"path\": \"src/extension.ts\"\n}","kind":"code"},{"code":"### Get Current User","kind":"markdown"},{"code":"query getCurrentUser(\n\t$owner: String!\n\t$repo: String!\n) {\n\tviewer { name }\n\trepository(name: $repo owner: $owner) {\n\t\tviewerPermission\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Get Commit","kind":"markdown"},{"code":"query getCommit(\n\t$owner: String!\n\t$repo: String!\n\t$ref: GitObjectID!\n) {\n\trepository(name: $repo owner: $owner) {\n\t\tobject(oid: $ref) {\n\t\t\t...on Commit {\n\t\t\t\toid\n\t\t\t\tparents(first: 3) { nodes { oid } }\n\t\t\t\tmessage\n\t\t\t\tadditions\n\t\t\t\tchangedFiles\n\t\t\t\tdeletions\n\t\t\t\tauthor {\n\t\t\t\t\tdate\n\t\t\t\t\temail\n\t\t\t\t\tname\n\t\t\t\t}\n\t\t\t\tcommitter { date }\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"54f28933055124d6ba3808a787f6947c929f9db0\"\n}","kind":"code"},{"code":"### Get Commits","kind":"markdown"},{"code":"query getCommits(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String\n\t$author: CommitAuthor\n\t$after: String\n\t$before: String\n\t$limit: Int = 100\n\t$since: GitTimestamp\n\t$until: GitTimestamp\n) {\n\tviewer { name }\n\trepository(name: $repo, owner: $owner) {\n\t\tobject(expression: $ref) {\n\t\t\t... on Commit {\n\t\t\t\thistory(first: $limit, author: $author, path: $path, after: $after, before: $before, since: $since, until: $until) {\n\t\t\t\t\tpageInfo {\n\t\t\t\t\t\tstartCursor\n\t\t\t\t\t\tendCursor\n\t\t\t\t\t\thasNextPage\n\t\t\t\t\t\thasPreviousPage\n\t\t\t\t\t}\n\t\t\t\t\tnodes {\n\t\t\t\t\t\t... on Commit {\n\t\t\t\t\t\t\toid\n\t\t\t\t\t\t\tmessage\n\t\t\t\t\t\t\tparents(first: 3) { nodes { oid } }\n\t\t\t\t\t\t\tadditions\n\t\t\t\t\t\t\tchangedFiles\n\t\t\t\t\t\t\tdeletions\n\t\t\t\t\t\t\tauthor {\n\t\t\t\t\t\t\t\tavatarUrl\n\t\t\t\t\t\t\t\tdate\n\t\t\t\t\t\t\t\temail\n\t\t\t\t\t\t\t\tname\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcommitter {\n\t\t\t\t\t\t\t\t date\n\t\t\t\t\t\t\t\t email\n\t\t\t\t\t\t\t\t name\n\t\t\t\t\t\t\t }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"HEAD\",\n\t\"-path\": \"src/extension.ts\",\n\t\"since\": \"2022-02-07T00:00:00Z\"\n}","kind":"code"},{"code":"### Get Tags","kind":"markdown"},{"code":"query getTags(\n\t$owner: String!\n\t$repo: String!\n\t$tagQuery: String\n\t$cursor: String\n\t$limit: Int = 100\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\trefs(query: $tagQuery, refPrefix: \"refs/tags/\", first: $limit, after: $cursor, orderBy: { field: TAG_COMMIT_DATE, direction: DESC }) {\n\t\t\tpageInfo {\n\t\t\t\tendCursor\n\t\t\t\thasNextPage\n\t\t\t}\n\t\t\tnodes {\n\t\t\t\tname\n\t\t\t\ttarget {\n\t\t\t\t\toid\n\t\t\t\t\tcommitUrl\n\t\t\t\t\t...on Commit {\n\t\t\t\t\t\tauthoredDate\n\t\t\t\t\t\tcommittedDate\n\t\t\t\t\t\tmessage\n\t\t\t\t\t}\n\t\t\t\t\t...on Tag {\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\ttagger { date }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Get collaborators ","kind":"markdown"},{"code":"query getCollaborators (\n\t$owner: String!\n\t$repo: String!\n\t$cursor: String\n\t$limit: Int = 100\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tcollaborators(affiliation: ALL, first: $limit, after: $cursor) {\n\t\t\tpageInfo {\n\t\t\t\tendCursor\n\t\t\t\thasNextPage\n\t\t\t}\n\t\t\tnodes {\n\t\t\t\tname\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\"\n}","kind":"code"},{"code":"### Resolve reference","kind":"markdown"},{"code":"query resolveReference(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tobject(expression: $ref) {\n\t\t\t... on Commit {\n\t\t\t\thistory(first: 1, path: $path) {\n\t\t\t\t\tnodes { oid }\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"d790e9db047769de079f6838c3578f3a47bf5930^\",\n\t\"path\": \"CODE_OF_CONDUCT.md\"\n}","kind":"code"},{"code":"### Get branches that contain commit","kind":"markdown"},{"code":"query getCommitBranches(\n\t$owner: String!\n\t$repo: String!\n\t$since: GitTimestamp!\n\t$until: GitTimestamp!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\trefs(first: 20, refPrefix: \"refs/heads/\", orderBy: { field: TAG_COMMIT_DATE, direction: DESC }) {\n\t\t\tnodes {\n\t\t\t\tname\n\t\t\t\ttarget {\n\t\t\t\t\t... on Commit {\n\t\t\t\t\t\thistory(first: 3, since: $since until: $until) {\n\t\t\t\t\t\t\tnodes { oid }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"since\": \"2022-01-06T01:07:46-04:00\",\n\t\"until\": \"2022-01-06T01:07:46-05:00\"\n}","kind":"code"},{"code":"query getCommitBranch(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$since: GitTimestamp!\n\t$until: GitTimestamp!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tref(qualifiedName: $ref) {\n\t\t\ttarget {\n\t\t\t\t... on Commit {\n\t\t\t\t\thistory(first: 3, since: $since until: $until) {\n\t\t\t\t\t\tnodes { oid }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"refs/heads/main\",\n\t\"since\": \"2022-01-06T01:07:46-04:00\",\n\t\"until\": \"2022-01-06T01:07:46-05:00\"\n}","kind":"code"},{"code":"### Get commit count for branch (ref)","kind":"markdown"},{"code":"query getCommitCount(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n) {\n\trepository(owner: $owner, name: $repo) {\n\t\tref(qualifiedName: $ref) {\n\t\t\ttarget {\n\t\t\t\t... on Commit {\n\t\t\t\t\thistory(first: 1) {\n\t\t\t\t\t\ttotalCount\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"refs/heads/main\"\n}","kind":"code"},{"code":"### Get commit refs (sha)","kind":"markdown"},{"code":"query getCommitRefs(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String\n\t$since: GitTimestamp\n\t$until: GitTimestamp\n\t$limit: Int = 1\n) {\n\tviewer { name }\n\trepository(name: $repo, owner: $owner) {\n\t\tref(qualifiedName: $ref) {\n\t\t\thistory(first: $limit, path: $path, since: $since, until: $until) {\n\t\t\t\tnodes { oid, message }\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"refs/heads/main\",\n\t\"path\": \"extension.ts\",\n\t\"limit\": 2\n}","kind":"code"},{"code":"### Get next file commit","kind":"markdown"},{"code":"query getCommitDate(\n\t$owner: String!\n\t$repo: String!\n\t$ref: GitObjectID!\n) {\n\trepository(name: $repo owner: $owner) {\n\t\tobject(oid: $ref) {\n\t\t\t...on Commit {\n\t\t\t\tcommitter { date }\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"a03ff942c40665c451bd4c7768f46e3e5f00e97c\"\n}","kind":"code"},{"code":"query getNextCommitCursor(\n\t$owner: String!\n\t$repo: String!\n\t$ref: String!\n\t$path: String!\n\t$since: GitTimestamp!\n) {\n\trepository(name: $repo owner: $owner) {\n\t\tobject(expression: $ref) {\n\t\t\t... on Commit {\n\t\t\t\thistory(first:1, path: $path, since: $since) {\n\t\t\t\t\ttotalCount\n\t\t\t\t\tpageInfo { startCursor }\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"b062e960b6ee5ca7ac081dd84d9217bd4b2051e0\",\n \"path\": \"src/extension.ts\",\n \"since\": \"2021-11-03T02:46:29-04:00\"\n}","kind":"code"},{"code":"query getNextCommit(\n\t$owner: String!\n\t$repo: String!\n $ref: String!\n $path: String!\n\t$before: String!\n) {\trepository(name: $repo owner: $owner) {\n object(expression: $ref) {\n ... on Commit {\n history(last:4, path: $path, before: $before) {\n totalCount\n pageInfo {\n startCursor\n }\n nodes {\n oid\n message\n committedDate\n }\n }\n }\n }\n }\n}\n\nvariables {\n\t\"owner\": \"eamodio\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"ref\": \"496c35eaeff2c33d3f1256a25d83198ace6aa6b0\",\n \"path\": \"src/extension.ts\",\n \"before\": \"496c35eaeff2c33d3f1256a25d83198ace6aa6b0 4\"\n}","kind":"code"},{"code":"### Get Pull Request for Branch","kind":"markdown"},{"code":"query getPullRequestForBranch(\n\t$owner: String!\n\t$repo: String!\n\t$branch: String!\n\t$limit: Int!\n\t$include: [PullRequestState!]\n\t$avatarSize: Int\n) {\n\trepository(name: $repo, owner: $owner) {\n\t\trefs(query: $branch, refPrefix: \"refs/heads/\", first: 1) {\n\t\t\tnodes {\n\t\t\t\tassociatedPullRequests(first: $limit, orderBy: {field: UPDATED_AT, direction: DESC}, states: $include) {\n\t\t\t\t\tnodes {\n\t\t\t\t\t\tauthor {\n\t\t\t\t\t\t\tlogin\n\t\t\t\t\t\t\tavatarUrl(size: $avatarSize)\n\t\t\t\t\t\t\turl\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpermalink\n\t\t\t\t\t\tnumber\n\t\t\t\t\t\ttitle\n\t\t\t\t\t\tstate\n\t\t\t\t\t\tupdatedAt\n\t\t\t\t\t\tclosedAt\n\t\t\t\t\t\tmergedAt\n\t\t\t\t\t\trepository {\n\t\t\t\t\t\t\tisFork\n\t\t\t\t\t\t\towner {\n\t\t\t\t\t\t\t\tlogin\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvariables {\n\t\"owner\": \"gitkraken\",\n\t\"repo\": \"vscode-gitlens\",\n\t\"branch\": \"main\",\n\t\"limit\": 1\n}","kind":"code"},{"code":"### Get My Assigned Pull Requests","kind":"markdown"},{"code":"query getMyAssignedPullRequests($assigned: String!) {\n search(first: 100, query: $assigned, type: ISSUE) {\n nodes {\n ... on PullRequest {\n author {\n login\n avatarUrl\n url\n }\n permalink\n number\n title\n state\n updatedAt\n closedAt\n mergedAt\n reviewDecision\n mergedBy {\n login\n }\n baseRefName\n baseRefOid\n baseRepository {\n name\n owner {\n login\n }\n }\n headRefName\n headRefOid\n headRepository {\n name\n owner {\n login\n }\n }\n repository {\n isFork\n owner {\n login\n }\n }\n isReadByViewer\n isDraft\n isCrossRepository\n checksUrl\n totalCommentsCount\n mergeable\n }\n }\n }\n}\n\nvariables {\n \"assigned\": \"assignee:@me is:pr is:open archived:false repo:gitkraken/vscode-gitlens\"\n}","kind":"code"},{"code":"### Get My Assigned Issues","kind":"markdown"},{"code":"query MyQuery($assigned: String!) {\n search(first: 2, query: $assigned, type: ISSUE) {\n nodes {\n ... on Issue {\n number\n title\n url\n createdAt\n closedAt\n updatedAt\n author {\n login\n avatarUrl\n url\n }\n repository {\n name\n owner {\n login\n }\n }\n assignees(first: 100) {\n nodes {\n login\n url\n avatarUrl\n }\n }\n labels(first: 20) {\n nodes {\n color\n name\n }\n }\n }\n }\n }\n}\n\nvariables {\n \"assigned\": \"assignee:@me type:issue is:open archived:false repo:gitkraken/vscode-gitlens\"\n}","kind":"code"}]} \ No newline at end of file diff --git a/src/git/gitProviderService.ts b/src/git/gitProviderService.ts index 74aef8d..d4bbdda 100644 --- a/src/git/gitProviderService.ts +++ b/src/git/gitProviderService.ts @@ -52,9 +52,10 @@ import type { GitContributor } from './models/contributor'; import type { GitDiff, GitDiffFilter, GitDiffHunkLine, GitDiffShortStat } from './models/diff'; import type { GitFile } from './models/file'; import type { GitGraph } from './models/graph'; +import type { SearchedIssue } from './models/issue'; import type { GitLog } from './models/log'; import type { GitMergeStatus } from './models/merge'; -import type { PullRequest, PullRequestState } from './models/pullRequest'; +import type { PullRequest, PullRequestState, SearchedPullRequest } from './models/pullRequest'; import type { GitRebaseStatus } from './models/rebase'; import type { GitBranchReference, GitReference } from './models/reference'; import { GitRevision } from './models/reference'; @@ -1755,6 +1756,78 @@ export class GitProviderService implements Disposable { } } + @debug({ args: { 0: remoteOrProvider => remoteOrProvider.name } }) + async getMyPullRequests( + remoteOrProvider: GitRemote | RichRemoteProvider, + options?: { timeout?: number }, + ): Promise { + let provider; + if (GitRemote.is(remoteOrProvider)) { + ({ provider } = remoteOrProvider); + if (!provider?.hasRichIntegration()) return undefined; + } else { + provider = remoteOrProvider; + } + + let timeout; + if (options != null) { + ({ timeout, ...options } = options); + } + + let promiseOrPRs = provider.searchMyPullRequests(); + if (promiseOrPRs == null || !isPromise(promiseOrPRs)) { + return promiseOrPRs; + } + + if (timeout != null && timeout > 0) { + promiseOrPRs = cancellable(promiseOrPRs, timeout); + } + + try { + return await promiseOrPRs; + } catch (ex) { + if (ex instanceof PromiseCancelledError) throw ex; + + return undefined; + } + } + + @debug({ args: { 0: remoteOrProvider => remoteOrProvider.name } }) + async getMyIssues( + remoteOrProvider: GitRemote | RichRemoteProvider, + options?: { timeout?: number }, + ): Promise { + let provider; + if (GitRemote.is(remoteOrProvider)) { + ({ provider } = remoteOrProvider); + if (!provider?.hasRichIntegration()) return undefined; + } else { + provider = remoteOrProvider; + } + + let timeout; + if (options != null) { + ({ timeout, ...options } = options); + } + + let promiseOrPRs = provider.searchMyIssues(); + if (promiseOrPRs == null || !isPromise(promiseOrPRs)) { + return promiseOrPRs; + } + + if (timeout != null && timeout > 0) { + promiseOrPRs = cancellable(promiseOrPRs, timeout); + } + + try { + return await promiseOrPRs; + } catch (ex) { + if (ex instanceof PromiseCancelledError) throw ex; + + return undefined; + } + } + @log() async getIncomingActivity( repoPath: string | Uri, diff --git a/src/git/models/issue.ts b/src/git/models/issue.ts index 36408c9..80ac4e5 100644 --- a/src/git/models/issue.ts +++ b/src/git/models/issue.ts @@ -18,6 +18,29 @@ export interface IssueOrPullRequest { readonly closed: boolean; } +export interface IssueLabel { + color: string; + name: string; +} + +export interface IssueMember { + name: string; + avatarUrl: string; + url: string; +} + +export interface IssueShape extends IssueOrPullRequest { + updatedDate: Date; + author: IssueMember; + assignees: IssueMember[]; + labels?: IssueLabel[]; +} + +export interface SearchedIssue { + issue: IssueShape; + reasons: string[]; +} + export function serializeIssueOrPullRequest(value: IssueOrPullRequest): IssueOrPullRequest { const serialized: IssueOrPullRequest = { type: value.type, @@ -96,3 +119,58 @@ export namespace IssueOrPullRequest { return new ThemeIcon('issues', new ThemeColor(Colors.OpenAutolinkedIssueIconColor)); } } + +export function serializeIssue(value: IssueShape): IssueShape { + const serialized: IssueShape = { + type: value.type, + provider: { + id: value.provider.id, + name: value.provider.name, + domain: value.provider.domain, + icon: value.provider.icon, + }, + id: value.id, + title: value.title, + url: value.url, + date: value.date, + closedDate: value.closedDate, + closed: value.closed, + updatedDate: value.updatedDate, + author: { + name: value.author.name, + avatarUrl: value.author.avatarUrl, + url: value.author.url, + }, + assignees: value.assignees.map(assignee => ({ + name: assignee.name, + avatarUrl: assignee.avatarUrl, + url: assignee.url, + })), + labels: + value.labels == null + ? undefined + : value.labels.map(label => ({ + color: label.color, + name: label.name, + })), + }; + return serialized; +} + +export class Issue implements IssueShape { + readonly type = IssueOrPullRequestType.Issue; + + constructor( + public readonly provider: RemoteProviderReference, + public readonly id: string, + public readonly title: string, + public readonly url: string, + public readonly date: Date, + public readonly closed: boolean, + public readonly updatedDate: Date, + public readonly author: IssueMember, + public readonly assignees: IssueMember[], + public readonly closedDate?: Date, + public readonly labels?: IssueLabel[], + ) {} +} diff --git a/src/git/models/pullRequest.ts b/src/git/models/pullRequest.ts index 532f3ad..0a89fce 100644 --- a/src/git/models/pullRequest.ts +++ b/src/git/models/pullRequest.ts @@ -14,6 +14,20 @@ export const enum PullRequestState { Merged = 'Merged', } +export interface PullRequestRef { + owner: string; + repo: string; + branch: string; + sha: string; + exists: boolean; +} + +export interface PullRequestRefs { + base: PullRequestRef; + head: PullRequestRef; + isCrossRepository: boolean; +} + export interface PullRequestShape extends IssueOrPullRequest { readonly author: { readonly name: string; @@ -22,6 +36,13 @@ export interface PullRequestShape extends IssueOrPullRequest { }; readonly state: PullRequestState; readonly mergedDate?: Date; + readonly refs?: PullRequestRefs; + readonly isDraft?: boolean; +} + +export interface SearchedPullRequest { + pullRequest: PullRequest; + reasons: string[]; } export function serializePullRequest(value: PullRequest): PullRequestShape { @@ -46,6 +67,26 @@ export function serializePullRequest(value: PullRequest): PullRequestShape { }, state: value.state, mergedDate: value.mergedDate, + refs: value.refs + ? { + head: { + exists: value.refs.head.exists, + owner: value.refs.head.owner, + repo: value.refs.head.repo, + sha: value.refs.head.sha, + branch: value.refs.head.branch, + }, + base: { + exists: value.refs.base.exists, + owner: value.refs.base.owner, + repo: value.refs.base.repo, + sha: value.refs.base.sha, + branch: value.refs.base.branch, + }, + isCrossRepository: value.refs.isCrossRepository, + } + : undefined, + isDraft: value.isDraft, }; return serialized; } @@ -103,6 +144,8 @@ export class PullRequest implements PullRequestShape { public readonly date: Date, public readonly closedDate?: Date, public readonly mergedDate?: Date, + public readonly refs?: PullRequestRefs, + public readonly isDraft?: boolean, ) {} get closed(): boolean { diff --git a/src/git/remotes/github.ts b/src/git/remotes/github.ts index 140a13d..537ae3f 100644 --- a/src/git/remotes/github.ts +++ b/src/git/remotes/github.ts @@ -13,8 +13,8 @@ import { encodeUrl } from '../../system/encoding'; import { equalsIgnoreCase } from '../../system/string'; import type { Account } from '../models/author'; import type { DefaultBranch } from '../models/defaultBranch'; -import type { IssueOrPullRequest } from '../models/issue'; -import type { PullRequest, PullRequestState } from '../models/pullRequest'; +import type { IssueOrPullRequest, SearchedIssue } from '../models/issue'; +import type { PullRequest, PullRequestState, SearchedPullRequest } from '../models/pullRequest'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; import { ensurePaidPlan, RichRemoteProvider } from './richRemoteProvider'; @@ -334,6 +334,22 @@ export class GitHubRemote extends RichRemoteProvider { baseUrl: this.apiBaseUrl, }); } + + protected async searchProviderMyPullRequests({ + accessToken, + }: AuthenticationSession): Promise { + return (await this.container.github)?.searchMyPullRequests(this, accessToken, { + repos: [this.path], + }); + } + + protected async searchProviderMyIssues({ + accessToken, + }: AuthenticationSession): Promise { + return (await this.container.github)?.searchMyIssues(this, accessToken, { + repos: [this.path], + }); + } } const gitHubNoReplyAddressRegex = /^(?:(\d+)\+)?([a-zA-Z\d-]{1,39})@users\.noreply\.(.*)$/i; diff --git a/src/git/remotes/gitlab.ts b/src/git/remotes/gitlab.ts index 3d9f175..a3fe17b 100644 --- a/src/git/remotes/gitlab.ts +++ b/src/git/remotes/gitlab.ts @@ -13,8 +13,8 @@ import { encodeUrl } from '../../system/encoding'; import { equalsIgnoreCase } from '../../system/string'; import type { Account } from '../models/author'; import type { DefaultBranch } from '../models/defaultBranch'; -import type { IssueOrPullRequest } from '../models/issue'; -import type { PullRequest, PullRequestState } from '../models/pullRequest'; +import type { IssueOrPullRequest, SearchedIssue } from '../models/issue'; +import type { PullRequest, PullRequestState, SearchedPullRequest } from '../models/pullRequest'; import { GitRevision } from '../models/reference'; import type { Repository } from '../models/repository'; import { ensurePaidPlan, RichRemoteProvider } from './richRemoteProvider'; @@ -365,6 +365,16 @@ export class GitLabRemote extends RichRemoteProvider { baseUrl: this.apiBaseUrl, }); } + + protected async searchProviderMyPullRequests( + _session: AuthenticationSession, + ): Promise { + return Promise.resolve(undefined); + } + + protected async searchProviderMyIssues(_session: AuthenticationSession): Promise { + return Promise.resolve(undefined); + } } export class GitLabAuthenticationProvider implements Disposable, IntegrationAuthenticationProvider { diff --git a/src/git/remotes/richRemoteProvider.ts b/src/git/remotes/richRemoteProvider.ts index 3be8c6d..361862a 100644 --- a/src/git/remotes/richRemoteProvider.ts +++ b/src/git/remotes/richRemoteProvider.ts @@ -15,8 +15,8 @@ import { debug, log } from '../../system/decorators/log'; import { isPromise } from '../../system/promise'; import type { Account } from '../models/author'; import type { DefaultBranch } from '../models/defaultBranch'; -import type { IssueOrPullRequest } from '../models/issue'; -import type { PullRequest, PullRequestState } from '../models/pullRequest'; +import type { IssueOrPullRequest, SearchedIssue } from '../models/issue'; +import type { PullRequest, PullRequestState, SearchedPullRequest } from '../models/pullRequest'; import { RemoteProvider } from './remoteProvider'; import { RichRemoteProviders } from './remoteProviderConnections'; @@ -289,6 +289,48 @@ export abstract class RichRemoteProvider extends RemoteProvider { @gate() @debug() + async searchMyPullRequests(): Promise { + const scope = getLogScope(); + + try { + const pullRequests = await this.searchProviderMyPullRequests(this._session!); + this.resetRequestExceptionCount(); + return pullRequests; + } catch (ex) { + Logger.error(ex, scope); + + if (ex instanceof AuthenticationError || ex instanceof ProviderRequestClientError) { + this.trackRequestException(); + } + return undefined; + } + } + protected abstract searchProviderMyPullRequests( + session: AuthenticationSession, + ): Promise; + + @gate() + @debug() + async searchMyIssues(): Promise { + const scope = getLogScope(); + + try { + const issues = await this.searchProviderMyIssues(this._session!); + this.resetRequestExceptionCount(); + return issues; + } catch (ex) { + Logger.error(ex, scope); + + if (ex instanceof AuthenticationError || ex instanceof ProviderRequestClientError) { + this.trackRequestException(); + } + return undefined; + } + } + protected abstract searchProviderMyIssues(session: AuthenticationSession): Promise; + + @gate() + @debug() async getIssueOrPullRequest(id: string): Promise { const scope = getLogScope(); diff --git a/src/plus/github/github.ts b/src/plus/github/github.ts index 6d76859..33d43fa 100644 --- a/src/plus/github/github.ts +++ b/src/plus/github/github.ts @@ -21,8 +21,8 @@ import type { PagedResult } from '../../git/gitProvider'; import { RepositoryVisibility } from '../../git/gitProvider'; import type { Account } from '../../git/models/author'; import type { DefaultBranch } from '../../git/models/defaultBranch'; -import type { IssueOrPullRequest } from '../../git/models/issue'; -import type { PullRequest } from '../../git/models/pullRequest'; +import type { IssueOrPullRequest, SearchedIssue } from '../../git/models/issue'; +import type { PullRequest, SearchedPullRequest } from '../../git/models/pullRequest'; import { GitRevision } from '../../git/models/reference'; import type { GitUser } from '../../git/models/user'; import { getGitHubNoReplyAddressParts } from '../../git/remotes/github'; @@ -34,6 +34,7 @@ import { showIntegrationRequestFailed500WarningMessage, showIntegrationRequestTimedOutWarningMessage, } from '../../messages'; +import { uniqueBy } from '../../system/array'; import { debug } from '../../system/decorators/log'; import { Stopwatch } from '../../system/stopwatch'; import { base64 } from '../../system/string'; @@ -46,13 +47,14 @@ import type { GitHubCommit, GitHubCommitRef, GitHubContributor, + GitHubDetailedPullRequest, GitHubIssueOrPullRequest, GitHubPagedResult, GitHubPageInfo, GitHubPullRequestState, GitHubTag, } from './models'; -import { GitHubPullRequest } from './models'; +import { GitHubDetailedIssue, GitHubPullRequest } from './models'; const emptyPagedResult: PagedResult = Object.freeze({ values: [] }); const emptyBlameResult: GitHubBlame = Object.freeze({ ranges: [] }); @@ -2257,8 +2259,313 @@ export class GitHubApi implements Disposable { // The /u/e endpoint automatically falls back to gravatar if not found return `https://avatars.githubusercontent.com/u/e?email=${encodeURIComponent(email)}&s=${avatarSize}`; } + + @debug({ args: { 0: p => p.name, 1: '' } }) + async searchMyPullRequests( + provider: RichRemoteProvider, + token: string, + options?: { search?: string; user?: string; repos?: string[] }, + ): Promise { + const scope = getLogScope(); + + interface SearchResult { + related: { + nodes: GitHubDetailedPullRequest[]; + }; + authored: { + nodes: GitHubDetailedPullRequest[]; + }; + assigned: { + nodes: GitHubDetailedPullRequest[]; + }; + reviewRequested: { + nodes: GitHubDetailedPullRequest[]; + }; + mentioned: { + nodes: GitHubDetailedPullRequest[]; + }; + } + try { + const query = `query searchPullRequests( + $related: String! + $authored: String! + $assigned: String! + $reviewRequested: String! + $mentioned: String! +) { + related: search(first: 100, query: $related, type: ISSUE) { + nodes { + ...on PullRequest { + ${prNodeProperties} + } + } + } + authored: search(first: 100, query: $authored, type: ISSUE) { + nodes { + ...on PullRequest { + ${prNodeProperties} + } + } + } + assigned: search(first: 100, query: $assigned, type: ISSUE) { + nodes { + ...on PullRequest { + ${prNodeProperties} + } + } + } + reviewRequested: search(first: 100, query: $reviewRequested, type: ISSUE) { + nodes { + ...on PullRequest { + ${prNodeProperties} + } + } + } + mentioned: search(first: 100, query: $mentioned, type: ISSUE) { + nodes { + ...on PullRequest { + ${prNodeProperties} + } + } + } +}`; + + let search = options?.search?.trim() ?? ''; + + if (options?.user) { + search += ` user:${options.user}`; + } + + if (options?.repos != null && options.repos.length > 0) { + const repo = ' repo:'; + search += `${repo}${options.repos.join(repo)}`; + } + + const baseFilters = 'is:pr is:open archived:false'; + const resp = await this.graphql( + undefined, + token, + query, + { + related: `${search} ${baseFilters} user:@me`.trim(), + authored: `${search} ${baseFilters} author:@me`.trim(), + assigned: `${search} ${baseFilters} assignee:@me`.trim(), + reviewRequested: `${search} ${baseFilters} review-requested:@me`.trim(), + mentioned: `${search} ${baseFilters} mentions:@me`.trim(), + }, + scope, + ); + if (resp === undefined) return []; + + function toQueryResult(pr: GitHubDetailedPullRequest, reason?: string): SearchedPullRequest { + return { + pullRequest: GitHubPullRequest.fromDetailed(pr, provider), + reasons: reason ? [reason] : [], + }; + } + + const results: SearchedPullRequest[] = uniqueWithReasons( + [ + ...resp.assigned.nodes.map(pr => toQueryResult(pr, 'assigned')), + ...resp.reviewRequested.nodes.map(pr => toQueryResult(pr, 'review requested')), + ...resp.mentioned.nodes.map(pr => toQueryResult(pr, 'mentioned')), + ...resp.authored.nodes.map(pr => toQueryResult(pr, 'authored')), + ...resp.related.nodes.map(pr => toQueryResult(pr)), + ], + r => r.pullRequest.url, + ); + return results; + } catch (ex) { + throw this.handleException(ex, undefined, scope); + } + } + + @debug({ args: { 0: '' } }) + async searchMyIssues( + provider: RichRemoteProvider, + token: string, + options?: { search?: string; user?: string; repos?: string[] }, + ): Promise { + const scope = getLogScope(); + interface SearchResult { + related: { + nodes: GitHubDetailedIssue[]; + }; + authored: { + nodes: GitHubDetailedIssue[]; + }; + assigned: { + nodes: GitHubDetailedIssue[]; + }; + mentioned: { + nodes: GitHubDetailedIssue[]; + }; + } + + const query = `query searchIssues( + $related: String! + $authored: String! + $assigned: String! + $mentioned: String! + ) { + related: search(first: 100, query: $related, type: ISSUE) { + nodes { + ${issueNodeProperties} + } + } + authored: search(first: 100, query: $authored, type: ISSUE) { + nodes { + ${issueNodeProperties} + } + } + assigned: search(first: 100, query: $assigned, type: ISSUE) { + nodes { + ${issueNodeProperties} + } + } + mentioned: search(first: 100, query: $mentioned, type: ISSUE) { + nodes { + ${issueNodeProperties} + } + } + }`; + + let search = options?.search?.trim() ?? ''; + + if (options?.user) { + search += ` user:${options.user}`; + } + + if (options?.repos != null && options.repos.length > 0) { + const repo = ' repo:'; + search += `${repo}${options.repos.join(repo)}`; + } + + const baseFilters = 'type:issue is:open archived:false'; + try { + const resp = await this.graphql( + undefined, + token, + query, + { + related: `${search} ${baseFilters} user:@me`.trim(), + authored: `${search} ${baseFilters} author:@me`.trim(), + assigned: `${search} ${baseFilters} assignee:@me`.trim(), + mentioned: `${search} ${baseFilters} mentions:@me`.trim(), + }, + scope, + ); + + function toQueryResult(issue: GitHubDetailedIssue, reason?: string): SearchedIssue { + return { + issue: GitHubDetailedIssue.from(issue, provider), + reasons: reason ? [reason] : [], + }; + } + + if (resp === undefined) return []; + + const results: SearchedIssue[] = uniqueWithReasons( + [ + ...resp.assigned.nodes.map(pr => toQueryResult(pr, 'assigned')), + ...resp.mentioned.nodes.map(pr => toQueryResult(pr, 'mentioned')), + ...resp.authored.nodes.map(pr => toQueryResult(pr, 'authored')), + ...resp.related.nodes.map(pr => toQueryResult(pr)), + ], + r => r.issue.url, + ); + return results; + } catch (ex) { + throw this.handleException(ex, undefined, scope); + } + } } function isGitHubDotCom(options?: { baseUrl?: string }) { return options?.baseUrl == null || options.baseUrl === 'https://api.github.com'; } + +function uniqueWithReasons(items: T[], lookup: (item: T) => unknown): T[] { + return uniqueBy(items, lookup, (original, current) => { + if (current.reasons.length !== 0) { + original.reasons.push(...current.reasons); + } + return original; + }); +} + +const prNodeProperties = ` +author { + login + avatarUrl + url +} +permalink +number +title +state +updatedAt +closedAt +mergedAt +repository { + isFork + owner { + login + } +} +reviewDecision +mergedBy { + login +} +baseRefName +baseRefOid +baseRepository { + name + owner { + login + } +} +headRefName +headRefOid +headRepository { + name + owner { + login + } +} +`; + +const issueNodeProperties = ` +... on Issue { + number + title + url + createdAt + closedAt + updatedAt + author { + login + avatarUrl + url + } + repository { + name + owner { + login + } + } + assignees(first: 100) { + nodes { + login + url + avatarUrl + } + } + labels(first: 20) { + nodes { + color + name + } + } +} +`; diff --git a/src/plus/github/models.ts b/src/plus/github/models.ts index fd32e3d..12c57fa 100644 --- a/src/plus/github/models.ts +++ b/src/plus/github/models.ts @@ -1,6 +1,7 @@ import type { Endpoints } from '@octokit/types'; import { GitFileIndexStatus } from '../../git/models/file'; -import type { IssueOrPullRequestType } from '../../git/models/issue'; +import type { IssueLabel, IssueMember, IssueOrPullRequestType } from '../../git/models/issue'; +import { Issue } from '../../git/models/issue'; import { PullRequest, PullRequestState } from '../../git/models/pullRequest'; import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider'; @@ -87,6 +88,48 @@ export interface GitHubPullRequest { }; } +export interface GitHubDetailedIssue extends GitHubIssueOrPullRequest { + date: Date; + updatedDate: Date; + closedDate: Date; + author: { + login: string; + avatarUrl: string; + url: string; + }; + assignees: { nodes: IssueMember[] }; + labels?: { nodes: IssueLabel[] }; +} + +export type GitHubPullRequestReviewDecision = 'CHANGES_REQUESTED' | 'APPROVED' | 'REVIEW_REQUIRED'; +export type GitHubPullRequestMergeableState = 'MERGEABLE' | 'CONFLICTING' | 'UNKNOWN'; + +export interface GitHubDetailedPullRequest extends GitHubPullRequest { + baseRefName: string; + baseRefOid: string; + baseRepository: { + name: string; + owner: { + login: string; + }; + }; + headRefName: string; + headRefOid: string; + headRepository: { + name: string; + owner: { + login: string; + }; + }; + reviewDecision: GitHubPullRequestReviewDecision; + isReadByViewer: boolean; + isDraft: boolean; + isCrossRepository: boolean; + checksUrl: string; + totalCommentsCount: number; + mergeable: GitHubPullRequestMergeableState; +} + export namespace GitHubPullRequest { export function from(pr: GitHubPullRequest, provider: RichRemoteProvider): PullRequest { return new PullRequest( @@ -117,6 +160,78 @@ export namespace GitHubPullRequest { export function toState(state: PullRequestState): GitHubPullRequestState { return state === PullRequestState.Merged ? 'MERGED' : state === PullRequestState.Closed ? 'CLOSED' : 'OPEN'; } + + export function fromDetailed(pr: GitHubDetailedPullRequest, provider: RichRemoteProvider): PullRequest { + return new PullRequest( + provider, + { + name: pr.author.login, + avatarUrl: pr.author.avatarUrl, + url: pr.author.url, + }, + String(pr.number), + pr.title, + pr.permalink, + fromState(pr.state), + new Date(pr.updatedAt), + pr.closedAt == null ? undefined : new Date(pr.closedAt), + pr.mergedAt == null ? undefined : new Date(pr.mergedAt), + { + head: { + exists: pr.headRepository != null, + owner: pr.headRepository?.owner.login, + repo: pr.baseRepository?.name, + sha: pr.headRefOid, + branch: pr.headRefName, + }, + base: { + exists: pr.baseRepository != null, + owner: pr.baseRepository?.owner.login, + repo: pr.baseRepository?.name, + sha: pr.baseRefOid, + branch: pr.baseRefName, + }, + isCrossRepository: pr.isCrossRepository, + }, + pr.isDraft, + ); + } +} + +export namespace GitHubDetailedIssue { + export function from(value: GitHubDetailedIssue, provider: RichRemoteProvider): Issue { + return new Issue( + { + id: provider.id, + name: provider.name, + domain: provider.domain, + icon: provider.icon, + }, + String(value.number), + value.title, + value.url, + value.date, + value.closed, + value.updatedDate, + { + name: value.author.login, + avatarUrl: value.author.avatarUrl, + url: value.author.url, + }, + value.assignees.nodes.map(assignee => ({ + name: assignee.name, + avatarUrl: assignee.avatarUrl, + url: assignee.url, + })), + value.closedDate, + value.labels?.nodes == null + ? undefined + : value.labels.nodes.map(label => ({ + color: label.color, + name: label.name, + })), + ); + } } export interface GitHubTag { diff --git a/src/plus/webviews/workspaces/workspacesWebview.ts b/src/plus/webviews/workspaces/workspacesWebview.ts index 0f5b5f6..1f7ccb1 100644 --- a/src/plus/webviews/workspaces/workspacesWebview.ts +++ b/src/plus/webviews/workspaces/workspacesWebview.ts @@ -2,6 +2,12 @@ import type { Disposable } from 'vscode'; import { Commands, ContextKeys } from '../../../constants'; import type { Container } from '../../../container'; import { setContext } from '../../../context'; +import type { SearchedIssue } from '../../../git/models/issue'; +import { serializeIssue } from '../../../git/models/issue'; +import type { SearchedPullRequest } from '../../../git/models/pullRequest'; +import { serializePullRequest } from '../../../git/models/pullRequest'; +import type { GitRemote } from '../../../git/models/remote'; +import type { RichRemoteProvider } from '../../../git/remotes/richRemoteProvider'; import { registerCommand } from '../../../system/command'; import { WebviewBase } from '../../../webviews/webviewBase'; import type { State } from './protocol'; @@ -47,12 +53,67 @@ export class WorkspacesWebview extends WebviewBase { } private async getState(): Promise { - return Promise.resolve({ - workspaces: this.getWorkspaces(), - }); + const prs = await this.getMyPullRequests(); + const serializedPrs = prs.map(pr => ({ + pullRequest: serializePullRequest(pr.pullRequest), + reasons: pr.reasons, + })); + + const issues = await this.getMyIssues(); + const serializedIssues = issues.map(issue => ({ + issue: serializeIssue(issue.issue), + reasons: issue.reasons, + })); + + return { + // workspaces: await this.getWorkspaces(), + myPullRequests: serializedPrs, + myIssues: serializedIssues, + }; } protected override async includeBootstrap(): Promise { return this.getState(); } + + private async getRichProviders(): Promise[]> { + const remotes: GitRemote[] = []; + for (const repo of this.container.git.openRepositories) { + const richRemote = await repo.getRichRemote(true); + if (richRemote == null || remotes.includes(richRemote)) { + continue; + } + remotes.push(richRemote); + } + + return remotes; + } + + private async getMyPullRequests(): Promise { + const providers = await this.getRichProviders(); + const allPrs = []; + for (const provider of providers) { + const prs = await this.container.git.getMyPullRequests(provider); + if (prs == null) { + continue; + } + allPrs.push(...prs); + } + + return allPrs; + } + + private async getMyIssues(): Promise { + const providers = await this.getRichProviders(); + const allIssues = []; + for (const provider of providers) { + const issues = await this.container.git.getMyIssues(provider); + if (issues == null) { + continue; + } + allIssues.push(...issues); + } + + return allIssues; + } } diff --git a/src/plus/workspaces/workspaces.ts b/src/plus/workspaces/workspaces.ts index 53a12d2..cfca036 100644 --- a/src/plus/workspaces/workspaces.ts +++ b/src/plus/workspaces/workspaces.ts @@ -34,7 +34,7 @@ export class WorkspacesApi implements Disposable { return session.accessToken; } - private async getRichProvider(): Promise | undefined> { + private async getRichProviders(): Promise[]> { const remotes: GitRemote[] = []; for (const repo of this.container.git.openRepositories) { const richRemote = await repo.getRichRemote(true); @@ -44,6 +44,12 @@ export class WorkspacesApi implements Disposable { remotes.push(richRemote); } + return remotes; + } + + private async getRichProvider(): Promise | undefined> { + const remotes = await this.getRichProviders(); + if (remotes.length === 0) { return undefined; } diff --git a/src/webviews/apps/plus/workspaces/workspaces.ts b/src/webviews/apps/plus/workspaces/workspaces.ts index 7ea8bd7..c2485cb 100644 --- a/src/webviews/apps/plus/workspaces/workspaces.ts +++ b/src/webviews/apps/plus/workspaces/workspaces.ts @@ -18,6 +18,7 @@ export class WorkspacesApp extends App { override onInitialize() { this.log(`${this.appName}.onInitialize`); this.renderContent(); + console.log(this.state); } renderContent() {}