Initial commit
This commit is contained in:
commit
e8a74c0293
|
@ -0,0 +1,3 @@
|
|||
config/config.yml
|
||||
bin
|
||||
.idea
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 HJJ
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,255 @@
|
|||
## Gitee CLI
|
||||
> 诞生背景:对于开发者来说,日常的开发往往离不开 terminal,以往的流程一般是:编写代码 -> 提交代码 -> 创建 Pull Request -> 测试 -> Bug Fix -> 重新测试 -> 测试通过 -> Code Review -> 合入主线,
|
||||
> 创建 Pull Request 等一系列动作往往只能在 web 端进行操作,往往需要切换上下文,Gitee CLI 因而诞生,旨在减少上下文的切换.
|
||||
## 部分功能示例
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
### 构建方式
|
||||
```shell
|
||||
git clone https://github.com/JJ-H/gitee_cli.git
|
||||
|
||||
cd gitee_cli
|
||||
|
||||
mkdir $HOME/.gitee
|
||||
|
||||
cp config/config.yml.example $HOME/.gitee/config.yml
|
||||
|
||||
go build -o bin/gitee main.go
|
||||
|
||||
sudo cp ./bin/gitee /usr/local/bin/gitee
|
||||
```
|
||||
|
||||
### 配置说明
|
||||
```shell
|
||||
# 个人私人令牌,用于 V5 鉴权
|
||||
access_token: xxxxxxxx
|
||||
api_prefix: https://gitee.com/api/v5
|
||||
# 用户在 Gitee 上的 ID
|
||||
user_id: xxxxx
|
||||
# 用户名
|
||||
user_name: xxx
|
||||
# 非仓库目录下执行 gitee cli 命令默认仓库全路径(配置你比较常用的仓库)
|
||||
default_path_with_namespace: oschina/gitee
|
||||
# 专业版构建命令前缀
|
||||
premium_build_prefix: premium_ci_build
|
||||
# 企业版构建命令前缀
|
||||
saas_build_prefix: ci_deploy
|
||||
# cookie 用于企业版 API 鉴权(由于加密登录的问题,此处暂时需要手动复制 cookie[可使用 gitee config cookie xxxxxx])
|
||||
cookies_jar: xxxxxxx
|
||||
```
|
||||
#### 可通过 gitee config [key] [value] 的方式设置
|
||||
```shell
|
||||
gitee config access_token xxxxxx
|
||||
|
||||
# 用户 ID 可使用如下命令查询(请精准输入你的username)
|
||||
➜ ~ gitee user search JJ-H
|
||||
用户 ID:7484706
|
||||
用户名称:JJ-H
|
||||
用户主页:https://gitee.com/JJ-H
|
||||
```
|
||||
|
||||
### Tab 自动补全!!!(强烈建议不要跳过这一步)
|
||||
> 得益于框架的优秀设计,Gitee CLI 支持快速生成 Tab 自动补全脚本,目前支持 bash、fish、powershell、zsh
|
||||
|
||||
```shell
|
||||
➜ ~ gitee completion --help
|
||||
Generate the autocompletion script for gitee for the specified shell.
|
||||
See each sub-command's help for details on how to use the generated script.
|
||||
|
||||
Usage:
|
||||
gitee completion [command]
|
||||
|
||||
Available Commands:
|
||||
bash Generate the autocompletion script for bash
|
||||
fish Generate the autocompletion script for fish
|
||||
powershell Generate the autocompletion script for powershell
|
||||
zsh Generate the autocompletion script for zsh
|
||||
|
||||
Flags:
|
||||
-h, --help help for completion
|
||||
|
||||
Use "gitee completion [command] --help" for more information about a command.
|
||||
```
|
||||
#### 下面以 zsh 为例
|
||||
```shell
|
||||
# Linux 用户
|
||||
gitee completion zsh > "${fpath[1]}/_gitee"
|
||||
# 执行完毕后在 ~/.zshrc 中增加如下语句:
|
||||
source ${fpath[1]}/_gitee
|
||||
|
||||
# macOS 用户
|
||||
gitee completion zsh > $(brew --prefix)/share/zsh/site-functions/_gitee
|
||||
# 执行完毕后在 ~/.zshrc 中增加如下语句:
|
||||
source $(brew --prefix)/share/zsh/site-functions/_gitee
|
||||
```
|
||||
重启终端,输入 gitee 按下 tab,您将得到如下自动补全提示(子命令同样支持)
|
||||
```shell
|
||||
➜ ~ gitee [press tab]
|
||||
auth -- Authenticate Gitee CLI with gitee selector
|
||||
build -- Build a k8s pod by note a specified pull request
|
||||
completion -- Generate the autocompletion script for the specified shell
|
||||
config -- Manage Gitee CLI config, Usage: config key [value]
|
||||
selector -- Manage enterprises
|
||||
help -- Help about any command
|
||||
lightPr -- Create a lightPr
|
||||
pr -- Manage pull requests
|
||||
ssh-key -- Manage ssh-keys
|
||||
user -- User related command
|
||||
```
|
||||
|
||||
|
||||
### 使用方式
|
||||
```shell
|
||||
➜ ~ gitee --help
|
||||
Gitee CLI is a tool which interact with gitee server seamlessly via terminal
|
||||
|
||||
Usage:
|
||||
gitee [command]
|
||||
|
||||
Available Commands:
|
||||
auth Authenticate Gitee CLI with gitee selector_tui
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
config Manage Gitee CLI config, Usage: config key [value]
|
||||
enterprise Manage enterprises
|
||||
help Help about any command
|
||||
issue Manage issues
|
||||
pr Manage pull requests
|
||||
ssh-key Manage ssh-keys
|
||||
user User related command
|
||||
|
||||
Flags:
|
||||
-h, --help help for gitee
|
||||
-v, --version version for gitee
|
||||
|
||||
Use "gitee [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
### Auth 相关
|
||||
```shell
|
||||
➜ ~ gitee auth --help
|
||||
Authenticate Gitee CLI with gitee selector
|
||||
|
||||
Usage:
|
||||
gitee auth [flags]
|
||||
|
||||
Flags:
|
||||
-f, --cookies-file string path to a file containing cookies
|
||||
-h, --help help for auth
|
||||
|
||||
```
|
||||
|
||||
### Config 相关
|
||||
```shell
|
||||
➜ ~ gitee config --help
|
||||
Manage Gitee CLI config, Usage: config key [value]
|
||||
|
||||
Usage:
|
||||
gitee config [flags]
|
||||
|
||||
Flags:
|
||||
-h, --help help for config
|
||||
```
|
||||
|
||||
### Pull Request 相关
|
||||
- 列出当前所在仓库下我审查的 Pull Request `gitee pr list [flags]`
|
||||
> 说明:列表模式下,按 c 将拷贝 pull request iid 至粘贴板,按 v 预览详情,按 d 预览 diff,回车使用浏览器打开
|
||||
|
||||

|
||||
- 根据 commit 找到对应的被合入至当前分支的 Pull Request `gitee pr list -c <commit>`
|
||||
```shell
|
||||
➜ ~ gitee pr -c "80b4ef95c0d"
|
||||
请在仓库目录下执行该命令!
|
||||
➜ ~ cd /home/git/gitee
|
||||
➜ gitee (master) ✔ gitee pr -c "80b4ef95c0d"
|
||||
该 commit 由 PR: 「修改仓库模糊查询,支持namespace级联查询,修复全英文字符查询时只匹配path问题」 合入,访问地址: https://gitee.com/hightest/settings/pulls19977
|
||||
```
|
||||
- 创建 pull_request
|
||||
```shell
|
||||
➜ ~ gitee pr create
|
||||
请输入标题:feature -> master
|
||||
请输入目标分支:master
|
||||
? 填写 Pull Request 内容 <Received>
|
||||
创建 PR「feature -> master」 成功,访问地址:https://gitee.com/hightest/settings/pulls/3
|
||||
```
|
||||
- 评论 Pull request,一般用于触发 webhook
|
||||
```shell
|
||||
➜ gitee (master) ✔ gitee pr note -i 19995 /approve
|
||||
评论成功!
|
||||
```
|
||||
|
||||
### Issue 相关
|
||||
- 创建 issue
|
||||
```shell
|
||||
➜ ~ gitee issue create --feature
|
||||
请选择要创建的任务类型
|
||||
> 需求
|
||||
|
||||
Press q to quit.
|
||||
请输入标题 这是需求标题
|
||||
? 填写 Issue 描述 <Received>
|
||||
创建工作项 「需这是需求标题」成功,访问地址:https://gitee.com/kepler-planet-wireless/dashboard/issues?id=I9A7ZY
|
||||
```
|
||||
|
||||
- issue 列表
|
||||
> 说明:列表模式下,按 c 将拷贝 issue ident 至粘贴板,按 v 预览详情,回车使用浏览器打开
|
||||
|
||||

|
||||
|
||||
### SSH Key 相关
|
||||
```shell
|
||||
➜ ~ gitee ssh-key --help
|
||||
Manage ssh-keys
|
||||
|
||||
Usage:
|
||||
gitee ssh-key [command]
|
||||
|
||||
Available Commands:
|
||||
add Add a ssh pub key for personal
|
||||
delete delete a specified ssh key
|
||||
list List personal ssh pub keys
|
||||
|
||||
Flags:
|
||||
-h, --help help for ssh-key
|
||||
|
||||
Use "gitee ssh-key [command] --help" for more information about a command.
|
||||
```
|
||||
- 获取当前账户所有已上传的公钥 `gitee ssh-key list`
|
||||
```shell
|
||||
➜ gitee (master) ✔ gitee ssh-key list
|
||||
+--------------+----------------------------------------------------+--------------------------------+
|
||||
| SSH KEY ID | KEY SHA | PREVIEW URL |
|
||||
+--------------+----------------------------------------------------+--------------------------------+
|
||||
| 3123223 | ssh-rsa AAAAB3NzaC1yc2EAAAADAAXCSAABAQC6r/S6pJsv8x | https://gitee.com/keys/3123223 |
|
||||
| 3233333 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQ786AABgQCgiABu1TWbSI | https://gitee.com/keys/3233333 |
|
||||
| 3242234 | ssh-ed25519 AAAAC3NzaC1lZDI1N765SAAAIISV/On6vy1UNg | https://gitee.com/keys/3242234 |
|
||||
| 2322332 | ssh-rsa AAAAB3NzaC1yc2EAAAADAADSDAABAQCpKcep+/DlEb | https://gitee.com/keys/2322332 |
|
||||
| 1233562 | ssh-ed25519 AAAAC3NzaC1lZDIASDSASAAAIA9aZBvftMp1dT | https://gitee.com/keys/1233562 |
|
||||
+--------------+----------------------------------------------------+--------------------------------+
|
||||
```
|
||||
- 添加本地 ssh pub key
|
||||
```shell
|
||||
➜ ~ gitee ssh-key add -t "Macbook Pro"
|
||||
请选择要上传的 SSH 公钥
|
||||
/Users/JJ-H/.ssh/hexo-deploy-key.pub
|
||||
/Users/JJ-H/.ssh/id_ed25519.pub
|
||||
> /Users/JJ-H/.ssh/id_rsa.pub
|
||||
|
||||
Press q to quit.
|
||||
添加 ssh key 「Macbook Pro」 成功,访问地址:https://gitee.com/keys/449311
|
||||
```
|
||||
|
||||
- 删除已上传的ssh 公钥
|
||||
```shell
|
||||
➜ ~ gitee ssh-key delete 449311
|
||||
删除公钥成功
|
||||
```
|
||||
|
||||
### 其余功能各位自行通过 help 探索~
|
||||
|
||||
### 参考文献
|
||||
- [A Git command to jump from a commit SHA to the PR on GitHub](https://tekin.co.uk/2020/06/jump-from-a-git-commit-to-the-pr-in-one-command)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/internal/api/user"
|
||||
"gitee_cli/utils"
|
||||
"os"
|
||||
|
||||
"gitee_cli/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
CookiesFile string
|
||||
GlobalCookies string
|
||||
)
|
||||
|
||||
var AuthCmd = &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "Authenticate Gitee CLI with gitee selector_tui",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if CookiesFile != "" {
|
||||
// Read cookies from file
|
||||
cookies, err := os.ReadFile(CookiesFile)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to read cookies-file:", err)
|
||||
return
|
||||
}
|
||||
GlobalCookies = string(cookies)
|
||||
}
|
||||
|
||||
// Save cookies to file for future usage
|
||||
if GlobalCookies != "" {
|
||||
if err := config.Update(map[string]interface{}{
|
||||
"cookies_jar": GlobalCookies,
|
||||
}); err != nil {
|
||||
fmt.Println("Failed to save cookies:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
user, err := user.BasicUser()
|
||||
if err != nil {
|
||||
fmt.Println("Authorize error: ", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(fmt.Sprintf("Hi「%s」! You've %s authenticated", utils.Cyan(user.Name), utils.Green("successfully")))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
AuthCmd.Flags().StringVarP(&CookiesFile, "cookies-file", "f", "", "path to a file containing cookies")
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/config"
|
||||
// "os"
|
||||
|
||||
// "gitee_cli/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var ConfigCmdUsage = "Manage Gitee CLI config, Usage: config key [value]"
|
||||
var ConfigCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: ConfigCmdUsage,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
fmt.Println("No config key provided.")
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
key := args[0]
|
||||
value, err := config.Read(key)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(value)
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 2 {
|
||||
key := args[0]
|
||||
value := args[1]
|
||||
if err := config.Update(map[string]interface{}{
|
||||
key: value,
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(value)
|
||||
return
|
||||
}
|
||||
fmt.Println(ConfigCmdUsage)
|
||||
|
||||
},
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package enterprise
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var EntCmd = &cobra.Command{
|
||||
Use: "enterprise",
|
||||
Aliases: []string{"ent"},
|
||||
Short: "Manage enterprises",
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package enterprise
|
||||
|
||||
import (
|
||||
enterprises2 "gitee_cli/internal/api/enterprises"
|
||||
"gitee_cli/utils/tui"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var ListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all enterprises joined by me",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
enterprises, err := enterprises2.List()
|
||||
if err != nil {
|
||||
color.Red("获取企业列表失败!")
|
||||
return
|
||||
}
|
||||
|
||||
columns := []table.Column{
|
||||
{Title: "ID", Width: 8},
|
||||
{Title: "Name", Width: 28},
|
||||
{Title: "Path", Width: 28},
|
||||
}
|
||||
rows := make([]table.Row, 0)
|
||||
for _, ent := range enterprises {
|
||||
rows = append(rows, table.Row{strconv.Itoa(ent.Id), ent.Name, ent.Path})
|
||||
}
|
||||
|
||||
entTable := tui.NewTable(enterprises2.Enterprise{}, tui.Enterprise, columns, rows)
|
||||
|
||||
if _, err := entTable.Run(); err != nil {
|
||||
color.Red("企业渲染失败!")
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
EntCmd.AddCommand(ListCmd)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/withfig/autocomplete-tools/integrations/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(cobracompletefig.CreateCompletionSpecCommand())
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
package issue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/config"
|
||||
"gitee_cli/internal/api/issue"
|
||||
"gitee_cli/internal/api/issue_type"
|
||||
"gitee_cli/internal/api/member"
|
||||
"gitee_cli/utils"
|
||||
"gitee_cli/utils/tui"
|
||||
"gitee_cli/utils/tui/issue_type_tui"
|
||||
"gitee_cli/utils/tui/selector_tui"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var CreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a issue",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
isBug, _ := cmd.Flags().GetBool("bug")
|
||||
isRequirement, _ := cmd.Flags().GetBool("feature")
|
||||
skipBody, _ := cmd.Flags().GetBool("skip-body")
|
||||
entPath, _ := cmd.Flags().GetString("ent")
|
||||
parentKeyWord, _ := cmd.Flags().GetString("parent")
|
||||
assigneeKeyWord, _ := cmd.Flags().GetString("assignee")
|
||||
candidateAssignees := make([]member.Member, 0)
|
||||
candidateTasks := make([]issue.Issue, 0)
|
||||
assigneeId := 0
|
||||
parentTaskId := 0
|
||||
|
||||
if entPath == "" {
|
||||
if entPath = config.Conf.DefaultEntPath; entPath == "" {
|
||||
color.Red("请指定企业 path")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if parentKeyWord != "" {
|
||||
candidateTasks, _ = issue.Find(enterprise.Id, map[string]string{
|
||||
"search": parentKeyWord,
|
||||
})
|
||||
}
|
||||
|
||||
if assigneeKeyWord != "" {
|
||||
candidateAssignees, _ = member.Find(enterprise.Id, map[string]string{
|
||||
"search": assigneeKeyWord,
|
||||
})
|
||||
}
|
||||
|
||||
optionMap := make(map[string]int, 0)
|
||||
options := make([]string, 0)
|
||||
|
||||
var category = issue_type.TASK
|
||||
var categoryText = "请选择任务类型"
|
||||
if isBug {
|
||||
category = issue_type.BUG
|
||||
categoryText = "请选择缺陷类型"
|
||||
} else if isRequirement {
|
||||
category = issue_type.REQUIREMENT
|
||||
categoryText = "请选择需求类型"
|
||||
}
|
||||
|
||||
issueTypes, err := issue_type.List(category, entPath)
|
||||
if err != nil {
|
||||
color.Red("获取任务类型失败!")
|
||||
return
|
||||
}
|
||||
|
||||
// 填充选项
|
||||
promote := "请选择要创建的工作项类型"
|
||||
issueTypeSelector := issue_type_tui.NewIssueTypeSelector(categoryText, issueTypes)
|
||||
var model tea.Model
|
||||
if model, err = issueTypeSelector.Run(); err != nil {
|
||||
color.Red("任务类型选择器加载失败!")
|
||||
return
|
||||
}
|
||||
|
||||
_issueTypeSelector, _ := model.(tui.Table)
|
||||
issueTypeId, err := issue_type_tui.SelectedValue(issueTypes, _issueTypeSelector.SelectedKey)
|
||||
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 传统选择器
|
||||
var mapSelector selector_tui.MapSelector
|
||||
var selector *tea.Program
|
||||
if len(candidateTasks) != 0 {
|
||||
options = make([]string, 0)
|
||||
optionMap = make(map[string]int, 0)
|
||||
optionMap, options = issue.FillOptions(candidateTasks, optionMap, options)
|
||||
promote = "请选择要关联的父任务"
|
||||
selector := selector_tui.NewMapSelector(optionMap, options, promote)
|
||||
if model, err = selector.Run(); err != nil {
|
||||
color.Red("父任务选择器加载失败!")
|
||||
return
|
||||
}
|
||||
mapSelector, _ = model.(selector_tui.MapSelector)
|
||||
|
||||
parentTaskId, err = mapSelector.SelectedValue()
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidateAssignees) != 0 {
|
||||
options = make([]string, 0)
|
||||
optionMap = make(map[string]int, 0)
|
||||
optionMap, options = member.FillOptions(candidateAssignees, optionMap, options)
|
||||
promote = "请选择指派的负责人"
|
||||
selector = selector_tui.NewMapSelector(optionMap, options, promote)
|
||||
if model, err = selector.Run(); err != nil {
|
||||
color.Red("负责人选择器加载失败!")
|
||||
return
|
||||
}
|
||||
mapSelector, _ = model.(selector_tui.MapSelector)
|
||||
|
||||
assigneeId, err = mapSelector.SelectedValue()
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var title string
|
||||
title = utils.ReadFromInput("填写 Issue 标题", title)
|
||||
title = strings.TrimSpace(title)
|
||||
|
||||
if title == "" {
|
||||
color.Red("请输入任务标题!")
|
||||
return
|
||||
}
|
||||
|
||||
var description string
|
||||
|
||||
// 获取模版
|
||||
if template, err := issue_type.FetchTemplate(issueTypeId, enterprise.Id); err == nil {
|
||||
description = template
|
||||
}
|
||||
|
||||
if !skipBody {
|
||||
description = utils.ReadFromEditor(utils.InitialEditor("填写 Issue 描述", description), description)
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"description_type": "md",
|
||||
"issue_type_id": issueTypeId,
|
||||
"title": title,
|
||||
"description": description,
|
||||
}
|
||||
if parentTaskId != 0 {
|
||||
payload["parent_id"] = parentTaskId
|
||||
}
|
||||
|
||||
if assigneeId != 0 {
|
||||
payload["assignee_id"] = assigneeId
|
||||
}
|
||||
|
||||
issue, err := issue.Create(enterprise.Id, payload)
|
||||
if err != nil {
|
||||
color.Red("创建工作项失败!")
|
||||
return
|
||||
}
|
||||
fmt.Printf("创建工作项 「%s」成功,访问地址:%s\n", utils.Cyan(issue.Title), utils.Blue(issue.Url))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CreateCmd.Flags().BoolP("task", "", true, "create a task")
|
||||
CreateCmd.Flags().BoolP("bug", "", false, "create a bug")
|
||||
CreateCmd.Flags().BoolP("feature", "", false, "create a feature")
|
||||
CreateCmd.Flags().BoolP("skip-body", "", false, "skip edit issue description")
|
||||
CreateCmd.Flags().StringP("parent", "p", "", "specify the parent task by search")
|
||||
CreateCmd.Flags().StringP("assignee", "A", "", "specify the assignee by search")
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package issue
|
||||
|
||||
import (
|
||||
"gitee_cli/config"
|
||||
"gitee_cli/internal/api/enterprises"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var enterprise enterprises.Enterprise
|
||||
|
||||
var IssueCmd = &cobra.Command{
|
||||
Use: "issue",
|
||||
Short: "Manage issues",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
entPath, err := cmd.Flags().GetString("ent")
|
||||
if entPath == "" {
|
||||
if entPath = config.Conf.DefaultEntPath; entPath == "" {
|
||||
color.Red("请指定企业 path")
|
||||
return
|
||||
}
|
||||
}
|
||||
enterprise, err = enterprises.Find(entPath)
|
||||
if err != nil {
|
||||
color.Red("企业未找到!")
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
IssueCmd.AddCommand(CreateCmd)
|
||||
IssueCmd.AddCommand(ListCmd)
|
||||
IssueCmd.AddCommand(ViewCmd)
|
||||
IssueCmd.PersistentFlags().StringP("ent", "e", "", "specify the selector_tui path")
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package issue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/internal/api/issue"
|
||||
"gitee_cli/internal/api/user"
|
||||
"gitee_cli/utils/tui"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var ListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List issues",
|
||||
Aliases: []string{"search"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
username, _ := cmd.Flags().GetString("assignee")
|
||||
entPath, _ := cmd.Flags().GetString("ent")
|
||||
limit, _ := cmd.Flags().GetInt("limit")
|
||||
|
||||
var search string
|
||||
payload := make(map[string]string)
|
||||
if len(args) != 0 {
|
||||
search = args[0]
|
||||
}
|
||||
if username == "" && search == "" {
|
||||
payload["only_related_me"] = "1"
|
||||
}
|
||||
|
||||
if username != "" {
|
||||
if assignee, err := user.FindUser(username); err == nil {
|
||||
payload["assignee_id"] = strconv.Itoa(assignee.Id)
|
||||
}
|
||||
}
|
||||
payload["search"] = search
|
||||
payload["page"] = "1"
|
||||
payload["per_page"] = strconv.Itoa(limit)
|
||||
_issues, _ := issue.Find(enterprise.Id, payload)
|
||||
|
||||
columns := []table.Column{
|
||||
{Title: "Ident", Width: 8},
|
||||
{Title: "Title", Width: 60},
|
||||
}
|
||||
|
||||
rows := make([]table.Row, 0)
|
||||
|
||||
for _, issue := range _issues {
|
||||
rows = append(rows, table.Row{issue.Ident, issue.Title})
|
||||
}
|
||||
|
||||
issueTable := tui.NewTable(enterprise, tui.Issue, columns, rows)
|
||||
|
||||
var model tea.Model
|
||||
var err error
|
||||
if model, err = issueTable.Run(); err != nil {
|
||||
color.Red("任务渲染失败!")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_table, _ := model.(tui.Table)
|
||||
ident := _table.SelectedKey
|
||||
if ident == "" {
|
||||
os.Exit(0)
|
||||
}
|
||||
url := fmt.Sprintf("https://e.gitee.com/%s/dashboard?issue=%s", entPath, ident)
|
||||
browser.OpenURL(url)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ListCmd.Flags().StringP("assignee", "A", "", "filter issue assignee")
|
||||
ListCmd.Flags().IntP("limit", "l", 10, "limit the number of issues returned")
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package issue
|
||||
|
||||
import (
|
||||
"gitee_cli/internal/api/issue"
|
||||
"gitee_cli/utils/tui"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var ViewCmd = &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "Display issue detail",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ident := args[0]
|
||||
|
||||
if _issue, err := issue.Detail(enterprise.Id, ident); err == nil {
|
||||
tui.NewPager(_issue.Title, _issue.Description, tui.Markdown).Run()
|
||||
} else {
|
||||
color.Red("获取任务详情失败!")
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package pull_request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/internal/api/pull_request"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
var CommentCmd = &cobra.Command{
|
||||
Use: "comment",
|
||||
Short: "Comment pull request",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Aliases: []string{"note"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
comment := args[0]
|
||||
iid, _ := cmd.Flags().GetInt("iid")
|
||||
|
||||
if iid == 0 {
|
||||
color.Red("请给定有效的 pull request 序号!")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := pull_request.Note(iid, comment); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
color.Green("评论成功!")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CommentCmd.Flags().IntP("iid", "i", 0, "Pull request number")
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package pull_request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/internal/api/pull_request"
|
||||
"gitee_cli/utils"
|
||||
"gitee_cli/utils/git_utils"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var CreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a pull request",
|
||||
Long: "Create a pull request",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
title, _ := cmd.Flags().GetString("title")
|
||||
body, _ := cmd.Flags().GetString("body")
|
||||
base, _ := cmd.Flags().GetString("base")
|
||||
head, _ := cmd.Flags().GetString("head")
|
||||
skipBody, _ := cmd.Flags().GetBool("skip-body")
|
||||
draft, _ := cmd.Flags().GetBool("draft")
|
||||
assignee, _ := cmd.Flags().GetString("assignees")
|
||||
tester, _ := cmd.Flags().GetString("testers")
|
||||
prune, _ := cmd.Flags().GetBool("prune")
|
||||
if !git_utils.IsGitDir() {
|
||||
color.Red("请在仓库目录下执行该命令!")
|
||||
return
|
||||
}
|
||||
|
||||
baseRepo, err := git_utils.ParseCurrentRepo()
|
||||
if err != nil {
|
||||
color.Red("获取当前仓库异常!")
|
||||
return
|
||||
}
|
||||
|
||||
if title == "" {
|
||||
title = utils.ReadFromInput("请输入标题", title)
|
||||
title = strings.TrimSpace(title)
|
||||
}
|
||||
|
||||
if head == "" {
|
||||
branch, _ := git_utils.GetCurrentBranch()
|
||||
if branch == "" {
|
||||
branch = utils.ReadFromInput("请输入起始分支", branch)
|
||||
head = strings.TrimSpace(branch)
|
||||
if head == "" {
|
||||
color.Red("无效的起始分支!")
|
||||
return
|
||||
}
|
||||
}
|
||||
head = branch
|
||||
}
|
||||
|
||||
if base == "" {
|
||||
base = utils.ReadFromInput("请输入目标分支", base)
|
||||
base = strings.TrimSpace(base)
|
||||
if base == "" {
|
||||
color.Red("无效的目标分支!")
|
||||
}
|
||||
}
|
||||
|
||||
if body == "" && !skipBody {
|
||||
body = utils.ReadFromEditor(utils.InitialEditor("填写 Pull Request 内容", ""), body)
|
||||
}
|
||||
|
||||
pullRequest, err := pull_request.CreatePr(baseRepo, base, head, title, body, assignee, tester, draft, prune)
|
||||
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
var input string
|
||||
input = utils.ReadFromInput(utils.Yellow("是否重试?(y/n/q)"), input)
|
||||
if input == "y" || input == "yes" {
|
||||
pullRequest, err = pull_request.CreatePr(baseRepo, base, head, title, body, assignee, tester, draft, prune)
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("创建 PR「%s」 成功,访问地址:%s\n", utils.Yellow(pullRequest.Title), utils.Cyan(pullRequest.HtmlUrl))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
CreateCmd.Flags().StringP("title", "t", "", "Title of the pull request")
|
||||
CreateCmd.Flags().StringP("body", "b", "", "Body of the pull request")
|
||||
CreateCmd.Flags().StringP("base", "B", "", "The branch into which you want your code merged")
|
||||
CreateCmd.Flags().StringP("head", "H", "", "The branch that contains commits for your pull request (default [current branch])")
|
||||
CreateCmd.Flags().BoolP("skip-body", "", false, "Skip adding a body to the pull request")
|
||||
CreateCmd.Flags().BoolP("draft", "", false, "Create a draft pull request")
|
||||
CreateCmd.Flags().StringP("assignees", "a", "", "Assign the pull request to users to code review, multi user split by , user1,user2")
|
||||
CreateCmd.Flags().StringP("testers", "", "", "Assign the pull request to users to test, multi user split by , user1,user2")
|
||||
CreateCmd.Flags().BoolP("prune", "", true, "Prune source branch after pr merged")
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package pull_request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/config"
|
||||
"gitee_cli/internal/api/enterprises"
|
||||
"gitee_cli/internal/api/pull_request"
|
||||
"gitee_cli/utils"
|
||||
"gitee_cli/utils/git_utils"
|
||||
"gitee_cli/utils/tui"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var ListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List pull requests related",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
keyword, _ := cmd.Flags().GetString("keyword")
|
||||
scope, _ := cmd.Flags().GetString("scope")
|
||||
commitSha, _ := cmd.Flags().GetString("commit")
|
||||
openInBrowser, _ := cmd.Flags().GetBool("open")
|
||||
convertEntUrl, _ := cmd.Flags().GetBool("convert")
|
||||
if commitSha != "" {
|
||||
pathWithNamespace, err := git_utils.ParseCurrentRepo()
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
return
|
||||
}
|
||||
pr, err := pull_request.FindPullRequestByIid(commitSha, pathWithNamespace)
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
return
|
||||
}
|
||||
if openInBrowser {
|
||||
browser.OpenURL(pr.HtmlUrl)
|
||||
return
|
||||
}
|
||||
fmt.Printf("该 commit 由 PR: 「%v」 合入,访问地址: %s\n", utils.Green(pr.Title), utils.Blue(pr.HtmlUrl))
|
||||
return
|
||||
}
|
||||
pullRequests := pull_request.List(scope)
|
||||
if keyword != "" {
|
||||
pullRequests = pull_request.FuzzySearch(pullRequests, keyword)
|
||||
}
|
||||
if len(pullRequests) == 0 {
|
||||
color.Cyan("无匹配的 Pull requests.")
|
||||
return
|
||||
}
|
||||
|
||||
columns := []table.Column{
|
||||
{Title: "PR 标题", Width: 60},
|
||||
{Title: "IID", Width: 10},
|
||||
{Title: "创建人", Width: 12},
|
||||
{Title: fmt.Sprintf("%s审查状态", config.Conf.UserName), Width: 18},
|
||||
{Title: "冲突", Width: 10},
|
||||
{Title: "是否可合入", Width: 10},
|
||||
}
|
||||
rows := make([]table.Row, 0)
|
||||
for _, pr := range pullRequests {
|
||||
mergeCheckMsg := "No"
|
||||
if pr.CanMergeCheck {
|
||||
mergeCheckMsg = utils.Green("Yes")
|
||||
}
|
||||
conflictMsg := "No"
|
||||
if !pr.Mergeable {
|
||||
conflictMsg = utils.Magenta("Yes")
|
||||
}
|
||||
acceptMsg := utils.Magenta("未审查")
|
||||
if pr.User.Id == 0 {
|
||||
acceptMsg = "-"
|
||||
} else if pr.User.Accept {
|
||||
acceptMsg = utils.Green("通过")
|
||||
}
|
||||
rows = append(rows, table.Row{pr.Title, strconv.Itoa(pr.Number), pr.Creator.Name, acceptMsg, conflictMsg, mergeCheckMsg})
|
||||
}
|
||||
|
||||
prTable := tui.NewTable(enterprises.Enterprise{}, tui.PullRequest, columns, rows)
|
||||
|
||||
if convertEntUrl {
|
||||
os.Setenv("CONVERT_ENT_URL", "true")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
os.Unsetenv("CONVERT_ENT_URL")
|
||||
}()
|
||||
//var model tea.Model
|
||||
var err error
|
||||
if _, err = prTable.Run(); err != nil {
|
||||
color.Red("pr渲染失败!")
|
||||
os.Exit(1)
|
||||
}
|
||||
//utils.PrRender(pullRequests, convert)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ListCmd.Flags().StringP("keyword", "k", "", "filter pr by keyword")
|
||||
//ListCmd.Flags().BoolP("reviewed", "r", false, "filter pr by review state")
|
||||
ListCmd.Flags().StringP("scope", "s", "", "filter pr by scope (owner)")
|
||||
ListCmd.Flags().StringP("commit", "c", "", "find pr by commit")
|
||||
ListCmd.Flags().BoolP("open", "o", false, "open in browser, only effective for searching pr via commit sha")
|
||||
ListCmd.Flags().BoolP("convert", "", false, "transfer url in enterprise")
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package pull_request
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Pr = &cobra.Command{
|
||||
Use: "pr",
|
||||
Aliases: []string{"pull_request"},
|
||||
Short: "Manage pull requests",
|
||||
}
|
||||
|
||||
func init() {
|
||||
Pr.AddCommand(ListCmd)
|
||||
Pr.AddCommand(CreateCmd)
|
||||
Pr.AddCommand(CommentCmd)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/cmd/auth"
|
||||
"gitee_cli/cmd/enterprise"
|
||||
"gitee_cli/cmd/issue"
|
||||
"gitee_cli/cmd/pull_request"
|
||||
sshkey "gitee_cli/cmd/ssh-key"
|
||||
"gitee_cli/cmd/user"
|
||||
"gitee_cli/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "gitee",
|
||||
Short: "Gitee In terminal",
|
||||
Long: "Gitee CLI is a tool which interact with gitee server seamlessly via terminal",
|
||||
Version: "0.0.1",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(ConfigCmd)
|
||||
RootCmd.AddCommand(pull_request.Pr)
|
||||
RootCmd.AddCommand(issue.IssueCmd)
|
||||
RootCmd.AddCommand(auth.AuthCmd)
|
||||
RootCmd.AddCommand(enterprise.EntCmd)
|
||||
RootCmd.AddCommand(sshkey.SshKeyCommand)
|
||||
RootCmd.AddCommand(user.UserCmd)
|
||||
RootCmd.SetVersionTemplate(fmt.Sprintf("Gitee CLI Version %s\n", utils.Cyan(RootCmd.Version)))
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package ssh_key
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/internal/api/ssh_key"
|
||||
"gitee_cli/utils"
|
||||
tui "gitee_cli/utils/tui/ssh_key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
fp "path/filepath"
|
||||
)
|
||||
|
||||
var AddSshKey = &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add a ssh pub key for personal",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
filepath, _ := cmd.Flags().GetString("filepath")
|
||||
title, _ := cmd.Flags().GetString("title")
|
||||
if title == "" {
|
||||
color.Red("请指定 ssh key 标题")
|
||||
return
|
||||
}
|
||||
if filepath == "" {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
sshDir := fp.Join(homeDir, ".ssh")
|
||||
files, _ := fp.Glob(fp.Join(sshDir, "*.pub"))
|
||||
if len(files) == 0 {
|
||||
color.Red("请先生成 ssh 密钥对!")
|
||||
return
|
||||
}
|
||||
fileSelector := tui.InitialUploadSSHKeyTui(files)
|
||||
var data tea.Model
|
||||
var err error
|
||||
if data, err = fileSelector.Run(); err != nil {
|
||||
color.Red("公钥选择器出错,请指定公钥地址以上传!")
|
||||
return
|
||||
}
|
||||
fileSelectRes, _ := data.(tui.UploadSSHKeyTui)
|
||||
if fileSelectRes.Cursor == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
filepath = fileSelectRes.FileList[fileSelectRes.Cursor]
|
||||
}
|
||||
|
||||
sshKey, err := ssh_key.AddKey(filepath, title)
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Printf("添加 ssh key 「%s」 成功,访问地址:%s\n", utils.Yellow(sshKey.Title), utils.Cyan(fmt.Sprintf("https://gitee.com/keys/%d", sshKey.Id)))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
SshKeyCommand.AddCommand(AddSshKey)
|
||||
AddSshKey.Flags().StringP("filepath", "f", "", "ssh pub key filepath")
|
||||
AddSshKey.Flags().StringP("title", "t", "", "title for ssh key")
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package ssh_key
|
||||
|
||||
import (
|
||||
"gitee_cli/internal/api/ssh_key"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var DeleteSshKey = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "delete a specified ssh key",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
sshKeyId := args[0]
|
||||
if sshKeyId == "" {
|
||||
color.Red("请提供正确的 SSH Key ID")
|
||||
return
|
||||
}
|
||||
err := ssh_key.DeleteKey(sshKeyId)
|
||||
if err != nil {
|
||||
color.Red(err.Error())
|
||||
return
|
||||
}
|
||||
color.Green("删除公钥成功")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
SshKeyCommand.AddCommand(DeleteSshKey)
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package ssh_key
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/internal/api/enterprises"
|
||||
"gitee_cli/internal/api/ssh_key"
|
||||
"gitee_cli/utils/tui"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var ListSshKey = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List personal ssh pub keys",
|
||||
Long: "List personal ssh pub keys",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
sshKeys, err := ssh_key.ListKeys()
|
||||
if err != nil {
|
||||
color.Red("获取ssh公钥列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
if len(sshKeys) == 0 {
|
||||
color.Green("暂未添加 SSH 公钥")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
columns := []table.Column{
|
||||
{Title: "ID", Width: 8},
|
||||
{Title: "Key Sha", Width: 38},
|
||||
{Title: "Preview URL", Width: 32},
|
||||
}
|
||||
|
||||
rows := make([]table.Row, 0)
|
||||
for _, key := range sshKeys {
|
||||
rows = append(rows, table.Row{strconv.Itoa(key.Id), key.Key[:50], fmt.Sprintf("https://gitee.com/keys/%d", key.Id)})
|
||||
}
|
||||
if _, err := tui.NewTable(enterprises.Enterprise{}, tui.SSHKey, columns, rows).Run(); err != nil {
|
||||
color.Red("获取 SSH 公钥失败!")
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
SshKeyCommand.AddCommand(ListSshKey)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package ssh_key
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var SshKeyCommand = &cobra.Command{
|
||||
Use: "ssh-key",
|
||||
Short: "Manage ssh-keys",
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/config"
|
||||
"gitee_cli/internal/api/enterprises"
|
||||
"gitee_cli/internal/api/user"
|
||||
"gitee_cli/utils"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var SearchCmd = &cobra.Command{
|
||||
Use: "search",
|
||||
Short: "Search for a user info, Usage: gitee user search {username}",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
color.Red("请给定用户名!")
|
||||
return
|
||||
}
|
||||
username := args[0]
|
||||
isEntPath, _ := cmd.Flags().GetBool("ent")
|
||||
if isEntPath {
|
||||
entPath := config.Conf.DefaultEntPath
|
||||
|
||||
if entPath == "" {
|
||||
color.Red("请使用 gitee config default_ent_path xxx 指定默认 path!")
|
||||
return
|
||||
}
|
||||
|
||||
enterprise, err := enterprises.Find(entPath)
|
||||
|
||||
if err != nil {
|
||||
color.Red("企业未找到!")
|
||||
return
|
||||
}
|
||||
|
||||
var member user.Member
|
||||
|
||||
if member, err = user.FindMember(username, enterprise.Id); err == nil {
|
||||
fmt.Printf("成员 ID:%s\n成员名称:%s\n用户名:%s\n", utils.Cyan(member.Id), utils.Cyan(member.Remark), utils.Blue(member.UserName))
|
||||
} else {
|
||||
color.Red("未查找到对应用户!")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
user, err := user.FindUser(username)
|
||||
|
||||
if err != nil {
|
||||
color.Red("查询用户失败!%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if user.Id == 0 {
|
||||
color.Red("未查找到对应用户!")
|
||||
return
|
||||
}
|
||||
fmt.Printf("用户 ID:%s\n用户名称:%s\n用户主页:%s\n", utils.Cyan(user.Id), utils.Cyan(user.Name), utils.Blue(user.HtmlUrl))
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
SearchCmd.Flags().BoolP("ent", "e", false, "search member from current enterprise")
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package user
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var UserCmd = &cobra.Command{
|
||||
Use: "user",
|
||||
Short: "User related command",
|
||||
}
|
||||
|
||||
func init() {
|
||||
UserCmd.AddCommand(SearchCmd)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var Conf Config
|
||||
|
||||
type Config struct {
|
||||
AccessToken string `yaml:"access_token"`
|
||||
ApiPrefix string `yaml:"api_prefix"`
|
||||
UserId int `yaml:"user_id"`
|
||||
UserName string `yaml:"user_name"`
|
||||
DefaultEntPath string `yaml:"default_ent_path"`
|
||||
DefaultPathWithNamespace string `yaml:"default_path_with_namespace"`
|
||||
PremiumBuildPrefix string `yaml:"premium_build_prefix"`
|
||||
SaasBuildPrefix string `yaml:"saas_build_prefix"`
|
||||
CookiesJar string `yaml:"cookies_jar"`
|
||||
DefaultEditor string `yaml:"default_editor"`
|
||||
}
|
||||
|
||||
func Read(key string) (string, error) {
|
||||
switch key {
|
||||
case "access_token":
|
||||
return Conf.AccessToken, nil
|
||||
case "api_prefix":
|
||||
return Conf.ApiPrefix, nil
|
||||
case "user_id":
|
||||
return fmt.Sprintf("%v", Conf.UserId), nil
|
||||
case "user_name":
|
||||
return Conf.UserName, nil
|
||||
case "default_ent_path":
|
||||
return Conf.DefaultEntPath, nil
|
||||
case "default_path_with_namespace":
|
||||
return Conf.DefaultPathWithNamespace, nil
|
||||
case "premium_build_prefix":
|
||||
return Conf.PremiumBuildPrefix, nil
|
||||
case "saas_build_prefix":
|
||||
return Conf.SaasBuildPrefix, nil
|
||||
case "cookies_jar":
|
||||
return Conf.CookiesJar, nil
|
||||
case "default_editor":
|
||||
return Conf.DefaultEditor, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Unknown config key: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates the configuration values from a provided map.
|
||||
func Update(values map[string]interface{}) error {
|
||||
for key, value := range values {
|
||||
switch key {
|
||||
case "access_token":
|
||||
Conf.AccessToken = value.(string)
|
||||
case "api_prefix":
|
||||
Conf.ApiPrefix = value.(string)
|
||||
case "user_id":
|
||||
Conf.UserId, _ = strconv.Atoi(parseInput(value))
|
||||
case "user_name":
|
||||
Conf.UserName = value.(string)
|
||||
case "default_ent_path":
|
||||
Conf.DefaultEntPath = value.(string)
|
||||
case "default_path_with_namespace":
|
||||
Conf.DefaultPathWithNamespace = value.(string)
|
||||
case "premium_build_prefix":
|
||||
Conf.PremiumBuildPrefix = value.(string)
|
||||
case "saas_build_prefix":
|
||||
Conf.SaasBuildPrefix = value.(string)
|
||||
case "cookies_jar":
|
||||
Conf.CookiesJar = strings.TrimSpace(value.(string))
|
||||
case "default_editor":
|
||||
Conf.DefaultEditor = value.(string)
|
||||
default:
|
||||
return fmt.Errorf("Unknown configuration key: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Save the updated configuration to the file
|
||||
config, err := yaml.Marshal(&Conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error marshalling configuration: %w", err)
|
||||
}
|
||||
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
configPath := path.Join(homeDir, ".gitee", "config.yml")
|
||||
|
||||
err = os.WriteFile(configPath, config, 0644)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error overwriting configuration file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseInput(input interface{}) string {
|
||||
switch input.(type) {
|
||||
case string:
|
||||
return input.(string)
|
||||
case int:
|
||||
return fmt.Sprintf("%v", input.(int))
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
configPath := path.Join(homeDir, ".gitee", "config.yml")
|
||||
|
||||
config, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
fmt.Printf("读取配置文件失败!请检查 %s 配置内容!\n", configPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(config, &Conf)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("初始化配置文件失败,请检查 %s 配置内容!\n", configPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 兼容 bubbletea border 渲染问题
|
||||
// https://github.com/charmbracelet/lipgloss/issues/40
|
||||
os.Setenv("RUNEWIDTH_EASTASIAN", "0")
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
# 个人私人令牌,用于 V5 鉴权
|
||||
access_token: xxxxxxxx
|
||||
api_prefix: https://gitee.com/api/v5
|
||||
# 用户在 Gitee 上的 ID
|
||||
user_id: 123456
|
||||
# 用户名
|
||||
user_name: xxx
|
||||
# 默认企业 path
|
||||
default_ent_path: oschina
|
||||
# 非仓库目录下执行 gitee cli 命令默认仓库全路径(配置你比较常用的仓库)
|
||||
default_path_with_namespace: oschina/gitee
|
||||
# 专业版构建命令前缀
|
||||
premium_build_prefix: premium_ci_build
|
||||
# 企业版构建命令前缀
|
||||
saas_build_prefix: ci_deploy
|
||||
# cookie 用于企业版 API 鉴权
|
||||
cookies_jar: xxxxxxx
|
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
After Width: | Height: | Size: 502 KiB |
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
|
@ -0,0 +1,51 @@
|
|||
module gitee_cli
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.25.0
|
||||
github.com/charmbracelet/glamour v0.7.0
|
||||
github.com/charmbracelet/lipgloss v0.10.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma/v2 v2.13.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/containerd/console v1.0.4 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/yuin/goldmark v1.7.0 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.2 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/term v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
)
|
|
@ -0,0 +1,145 @@
|
|||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng=
|
||||
github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps=
|
||||
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
|
||||
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
|
||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I=
|
||||
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k=
|
||||
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
|
||||
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
|
||||
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,50 @@
|
|||
package enterprises
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee_cli/utils/http_utils"
|
||||
)
|
||||
|
||||
type Enterprise struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func List() ([]Enterprise, error) {
|
||||
url := "https://api.gitee.com/enterprises/list"
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
_, err := giteeClient.Do()
|
||||
if err != nil || giteeClient.IsFail() {
|
||||
return nil, err
|
||||
}
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
|
||||
type res struct {
|
||||
Data []Enterprise `json:"data"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
|
||||
var _data res
|
||||
|
||||
json.Unmarshal(data, &_data)
|
||||
|
||||
return _data.Data, nil
|
||||
}
|
||||
|
||||
func Find(path string) (Enterprise, error) {
|
||||
url := fmt.Sprintf("https://gitee.com/api/v5/enterprises/%s", path)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
if _, err := giteeClient.Do(); err != nil {
|
||||
return Enterprise{}, errors.New("查询企业失败!")
|
||||
}
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
ent := Enterprise{}
|
||||
|
||||
json.Unmarshal(data, &ent)
|
||||
|
||||
return ent, nil
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package issue
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee_cli/utils/http_utils"
|
||||
)
|
||||
|
||||
const Endpoint = "https://api.gitee.com/enterprises/%d/issues"
|
||||
|
||||
type Issue struct {
|
||||
Id int `json:"id"`
|
||||
Ident string `json:"ident"`
|
||||
Title string `json:"title"`
|
||||
Url string `json:"issue_url"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func Find(enterpriseId int, params map[string]string) ([]Issue, error) {
|
||||
url := fmt.Sprintf(Endpoint, enterpriseId)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, params, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
|
||||
_, err := giteeClient.Do()
|
||||
if err != nil || giteeClient.IsFail() {
|
||||
return []Issue{}, err
|
||||
}
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
type res struct {
|
||||
Data []Issue `json:"data"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
|
||||
var _data res
|
||||
|
||||
json.Unmarshal(data, &_data)
|
||||
|
||||
return _data.Data, nil
|
||||
}
|
||||
|
||||
func Create(enterpriseId int, payload map[string]interface{}) (Issue, error) {
|
||||
url := fmt.Sprintf(Endpoint, enterpriseId)
|
||||
giteeClient := http_utils.NewGiteeClient("POST", url, nil, payload)
|
||||
giteeClient.SetCookieAuth()
|
||||
|
||||
giteeClient.Do()
|
||||
|
||||
if giteeClient.IsFail() {
|
||||
return Issue{}, errors.New("创建工作项失败!")
|
||||
}
|
||||
|
||||
issue := Issue{}
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
json.Unmarshal(data, &issue)
|
||||
|
||||
return issue, nil
|
||||
}
|
||||
|
||||
func FillOptions(issues []Issue, optionMap map[string]int, options []string) (map[string]int, []string) {
|
||||
if len(issues) == 0 {
|
||||
return optionMap, options
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
key := fmt.Sprintf("[%s] %s", issue.Ident, issue.Title)
|
||||
optionMap[key] = issue.Id
|
||||
options = append(options, key)
|
||||
}
|
||||
return optionMap, options
|
||||
}
|
||||
|
||||
func Detail(enterpriseId int, ident string) (Issue, error) {
|
||||
url := fmt.Sprintf("https://api.gitee.com/enterprises/%d/issues/%s?qt=ident", enterpriseId, ident)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
giteeClient.Do()
|
||||
if giteeClient.IsFail() {
|
||||
return Issue{}, errors.New("获取工作想失败!")
|
||||
}
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
issue := Issue{}
|
||||
json.Unmarshal(data, &issue)
|
||||
return issue, nil
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package issue_type
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee_cli/internal/api/enterprises"
|
||||
"gitee_cli/utils/http_utils"
|
||||
)
|
||||
|
||||
const (
|
||||
TASK = iota
|
||||
BUG
|
||||
REQUIREMENT
|
||||
)
|
||||
|
||||
func typeCategory(t int) string {
|
||||
return map[int]string{
|
||||
TASK: "task",
|
||||
BUG: "bug",
|
||||
REQUIREMENT: "requirement",
|
||||
}[t]
|
||||
}
|
||||
|
||||
type IssueType struct {
|
||||
Id int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
func List(issueType int, entPath string) ([]IssueType, error) {
|
||||
ent, err := enterprises.Find(entPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
category := typeCategory(issueType)
|
||||
if category == "" {
|
||||
return nil, errors.New("无效的任务类型!")
|
||||
}
|
||||
url := fmt.Sprintf("https://api.gitee.com/enterprises/%d/issue_types/enterprise_issue_types?category=%s&page=1&per_page=1000&state=1", ent.Id, category)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
if _, err := giteeClient.Do(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res = struct {
|
||||
Data []IssueType `json:"data"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}{}
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
|
||||
json.Unmarshal(data, &res)
|
||||
|
||||
return res.Data, nil
|
||||
}
|
||||
|
||||
func FillOptions(issueTypes []IssueType, optionMap map[string]int, options []string) (map[string]int, []string) {
|
||||
if len(issueTypes) == 0 {
|
||||
return optionMap, options
|
||||
}
|
||||
|
||||
for _, issueType := range issueTypes {
|
||||
optionMap[issueType.Title] = issueType.Id
|
||||
options = append(options, issueType.Title)
|
||||
}
|
||||
return optionMap, options
|
||||
}
|
||||
|
||||
func FetchTemplate(issueTypeId, entId int) (string, error) {
|
||||
url := fmt.Sprintf("https://api.gitee.com/enterprises/%d/issue_types/%d", entId, issueTypeId)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
if _, err := giteeClient.Do(); err != nil {
|
||||
return "", errors.New("获取模板失败!")
|
||||
}
|
||||
issueType := IssueType{}
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
json.Unmarshal(data, &issueType)
|
||||
return issueType.Template, nil
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package member
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitee_cli/utils/http_utils"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func Find(enterpriseId int, params map[string]string) ([]Member, error) {
|
||||
url := fmt.Sprintf("https://api.gitee.com/enterprises/%d/members", enterpriseId)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, params, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
|
||||
_, err := giteeClient.Do()
|
||||
if err != nil || giteeClient.IsFail() {
|
||||
return []Member{}, err
|
||||
}
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
type res struct {
|
||||
Data []Member `json:"data"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
|
||||
var _data res
|
||||
|
||||
json.Unmarshal(data, &_data)
|
||||
|
||||
return _data.Data, nil
|
||||
}
|
||||
|
||||
func FillOptions(members []Member, optionMap map[string]int, options []string) (map[string]int, []string) {
|
||||
if len(members) == 0 {
|
||||
return optionMap, options
|
||||
}
|
||||
|
||||
for _, member := range members {
|
||||
key := fmt.Sprintf("%s(%s)", member.Name, member.Remark)
|
||||
optionMap[key] = member.Id
|
||||
options = append(options, key)
|
||||
}
|
||||
return optionMap, options
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
package pull_request
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee_cli/config"
|
||||
"gitee_cli/utils/git_utils"
|
||||
"gitee_cli/utils/http_utils"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PullRequest struct {
|
||||
Id int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
Mergeable bool `json:"mergeable"`
|
||||
CanMergeCheck bool `json:"can_merge_check"`
|
||||
PatchUrl string `json:"patch_url"`
|
||||
Draft bool `json:"draft"`
|
||||
Creator creator `json:"user"`
|
||||
Assignees []assignee `json:"assignees"`
|
||||
User assignee `json:"-"`
|
||||
Number int `json:"number"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type assignee struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Accept bool `json:"accept"`
|
||||
}
|
||||
|
||||
type creator struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func Note(iid int, note string) error {
|
||||
// https://gitee.com/api/v5/repos/{owner}/{repo}/pulls/{number}/comments
|
||||
pathWithNamespace, err := git_utils.ParseCurrentRepo()
|
||||
if err != nil {
|
||||
pathWithNamespace = config.Conf.DefaultPathWithNamespace
|
||||
}
|
||||
url := fmt.Sprintf(config.Conf.ApiPrefix+"/repos/%s/pulls/%d/comments", pathWithNamespace, iid)
|
||||
|
||||
payload := map[string]string{"body": note}
|
||||
giteeClient := http_utils.NewGiteeClient("POST", url, nil, payload)
|
||||
|
||||
giteeClient, _ = giteeClient.Do()
|
||||
if giteeClient.IsFail() {
|
||||
return errors.New(fmt.Sprintf("评论 pr %d 失败!", iid))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func List(scope string) []PullRequest {
|
||||
pathWithNamespace, err := git_utils.ParseCurrentRepo()
|
||||
if err != nil {
|
||||
pathWithNamespace = config.Conf.DefaultPathWithNamespace
|
||||
}
|
||||
url := fmt.Sprintf(config.Conf.ApiPrefix+"/repos/%s/pulls?state=open&sort=created&direction=desc&page=1&per_page=100", pathWithNamespace)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
|
||||
pullRequests := make([]PullRequest, 50)
|
||||
|
||||
giteeClient.Do()
|
||||
|
||||
res, _ := giteeClient.GetRespBody()
|
||||
err = json.Unmarshal(res, &pullRequests)
|
||||
if err != nil {
|
||||
fmt.Println("解析 Pull Request 异常!")
|
||||
return nil
|
||||
}
|
||||
pullRequests = filterPullRequest(pullRequests, scope)
|
||||
return pullRequests
|
||||
}
|
||||
|
||||
func filterPullRequest(pullRequests []PullRequest, scope string) []PullRequest {
|
||||
if len(pullRequests) == 0 {
|
||||
return pullRequests
|
||||
}
|
||||
|
||||
// 过滤指定 User 审查的 PR
|
||||
filteredPullRequests := make([]PullRequest, 0)
|
||||
userId := config.Conf.UserId
|
||||
|
||||
if scope == "owner" {
|
||||
for _, pr := range pullRequests {
|
||||
if pr.Creator.Id == userId {
|
||||
filteredPullRequests = append(filteredPullRequests, pr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, pr := range pullRequests {
|
||||
assignees := pr.Assignees
|
||||
for _, assignee := range assignees {
|
||||
if assignee.Id == userId {
|
||||
pr.User = assignee
|
||||
filteredPullRequests = append(filteredPullRequests, pr)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredPullRequests
|
||||
}
|
||||
|
||||
func (pr PullRequest) TransferUrlToEnt() string {
|
||||
url := pr.HtmlUrl
|
||||
data := strings.Split(url, "/")
|
||||
iid := data[len(data)-1]
|
||||
pathWithName, err := git_utils.ParseCurrentRepo()
|
||||
if err != nil {
|
||||
pathWithName = config.Conf.DefaultPathWithNamespace
|
||||
}
|
||||
return fmt.Sprintf("https://e.gitee.com/oschina/repos/%s/pulls/%s", pathWithName, iid)
|
||||
}
|
||||
|
||||
func FuzzySearch(pullRequests []PullRequest, keyword string) []PullRequest {
|
||||
if len(pullRequests) == 0 {
|
||||
return pullRequests
|
||||
}
|
||||
|
||||
// 模糊搜索
|
||||
fuzzyPullRequests := make([]PullRequest, 0)
|
||||
|
||||
for _, pr := range pullRequests {
|
||||
if strings.Contains(pr.Title, keyword) {
|
||||
fuzzyPullRequests = append(fuzzyPullRequests, pr)
|
||||
}
|
||||
}
|
||||
return fuzzyPullRequests
|
||||
}
|
||||
|
||||
func FindPullRequestByIid(commitSha, pathWithNamespace string) (PullRequest, error) {
|
||||
|
||||
currentBranch, err := git_utils.GetCurrentBranch()
|
||||
if err != nil {
|
||||
return PullRequest{}, err
|
||||
}
|
||||
|
||||
iid, err := findPrIidBySha(commitSha, git_utils.CurrentDir(), currentBranch)
|
||||
if err != nil {
|
||||
return PullRequest{}, err
|
||||
}
|
||||
url := fmt.Sprintf("https://gitee.com/api/v5/repos/%s/pulls/%d", pathWithNamespace, iid)
|
||||
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.Do()
|
||||
if giteeClient.IsFail() {
|
||||
return PullRequest{}, errors.New("查找 pr 异常!")
|
||||
}
|
||||
res, _ := giteeClient.GetRespBody()
|
||||
|
||||
pullRequest := PullRequest{}
|
||||
err = json.Unmarshal(res, &pullRequest)
|
||||
if err != nil {
|
||||
fmt.Println("解析 Pull Request 异常!")
|
||||
return PullRequest{}, errors.New("解析 Pull Request 异常!")
|
||||
}
|
||||
return pullRequest, nil
|
||||
}
|
||||
|
||||
func findPrIidBySha(commitSha, dir, currentBranch string) (int, error) {
|
||||
command := fmt.Sprintf("git log --merges --ancestry-path --oneline %s..%s | grep 'pull request' | tail -n1 | awk '{print $2\";\"$3}'", commitSha, currentBranch)
|
||||
cmd := exec.Command("/bin/sh", "-c", command)
|
||||
cmd.Dir = dir
|
||||
res, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return 0, errors.New("获取 Pull Request ID 失败!")
|
||||
}
|
||||
result := strings.Split(string(res), ";")
|
||||
if len(result) != 2 {
|
||||
return 0, errors.New("未找到匹配的 Pull Request!")
|
||||
}
|
||||
iid, _ := strconv.Atoi(strings.TrimPrefix(result[0], "!"))
|
||||
title := result[1]
|
||||
if title == "" {
|
||||
return 0, errors.New("获取 Pull Request ID 失败!")
|
||||
}
|
||||
return iid, nil
|
||||
}
|
||||
|
||||
func CreatePr(baseRepo, baseRef, headRef, title, body, assignees, testers string, draft bool, prune bool) (PullRequest, error) {
|
||||
url := fmt.Sprintf("https://gitee.com/api/v5/repos/%s/pulls", baseRepo)
|
||||
payload := map[string]interface{}{
|
||||
"base": baseRef,
|
||||
"head": headRef,
|
||||
"title": title,
|
||||
"body": body,
|
||||
"assignees": assignees,
|
||||
"testers": testers,
|
||||
"draft": draft,
|
||||
"prune_source_branch": prune,
|
||||
}
|
||||
|
||||
giteeClient := http_utils.NewGiteeClient("POST", url, nil, payload)
|
||||
giteeClient, err := giteeClient.Do()
|
||||
|
||||
if err != nil {
|
||||
return PullRequest{}, errors.New("GiteeCilent 异常!")
|
||||
}
|
||||
res, _ := giteeClient.GetRespBody()
|
||||
|
||||
if giteeClient.IsFail() {
|
||||
errResponse := http_utils.ErrMsgV5{}
|
||||
err := json.Unmarshal(res, &errResponse)
|
||||
if err != nil {
|
||||
return PullRequest{}, errors.New("创建 pull request 失败!")
|
||||
}
|
||||
return PullRequest{}, errors.New(errResponse.Message)
|
||||
}
|
||||
|
||||
pullRequest := PullRequest{}
|
||||
if err := json.Unmarshal(res, &pullRequest); err != nil {
|
||||
return pullRequest, errors.New("解析响应失败")
|
||||
}
|
||||
return pullRequest, nil
|
||||
}
|
||||
|
||||
func CreateLightPr(baseRepo, baseRef, prTitle string) (PullRequest, error) {
|
||||
content := "test"
|
||||
message := "test"
|
||||
unixTime := time.Now().Format("20060102150405")
|
||||
path := "test_" + unixTime + ".txt"
|
||||
branch := "test_" + unixTime
|
||||
// 新建分支
|
||||
url := fmt.Sprintf("https://gitee.com/api/v5/repos/%s/branches", baseRepo)
|
||||
payload := map[string]string{"refs": baseRef, "branch_name": branch}
|
||||
giteeClient := http_utils.NewGiteeClient("POST", url, nil, payload)
|
||||
|
||||
giteeClient.Do()
|
||||
if giteeClient.IsFail() {
|
||||
return PullRequest{}, errors.New("创建 PR 失败")
|
||||
}
|
||||
|
||||
giteeClient.Payload = map[string]string{"message": message, "content": content, "branch": branch}
|
||||
giteeClient.Url = fmt.Sprintf("https://gitee.com/api/v5/repos/%s/contents/%s", baseRepo, path)
|
||||
|
||||
giteeClient.Do()
|
||||
if giteeClient.IsFail() {
|
||||
return PullRequest{}, errors.New("创建 PR 失败")
|
||||
}
|
||||
|
||||
// 创建 pr
|
||||
giteeClient.Url = fmt.Sprintf("https://gitee.com/api/v5/repos/%s/pulls", baseRepo)
|
||||
giteeClient.Payload = map[string]string{
|
||||
"title": prTitle,
|
||||
"head": branch,
|
||||
"base": baseRef,
|
||||
}
|
||||
giteeClient.Do()
|
||||
if giteeClient.IsFail() {
|
||||
return PullRequest{}, errors.New("创建 PR 失败")
|
||||
}
|
||||
res, _ := giteeClient.GetRespBody()
|
||||
pullRequest := PullRequest{}
|
||||
err := json.Unmarshal(res, &pullRequest)
|
||||
if err != nil {
|
||||
fmt.Println("解析 Pull Request 异常!")
|
||||
return PullRequest{}, errors.New("解析 Pull Request 异常!")
|
||||
}
|
||||
return pullRequest, nil
|
||||
}
|
||||
|
||||
func Detail(iid, repoPath string) (PullRequest, error) {
|
||||
url := fmt.Sprintf("https://gitee.com/api/v5/repos/%s/pulls/%s", repoPath, iid)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
if _, err := giteeClient.Do(); err != nil || giteeClient.IsFail() {
|
||||
return PullRequest{}, errors.New("获取 Pull Request 详情失败")
|
||||
}
|
||||
|
||||
_pullRequest := PullRequest{}
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
|
||||
json.Unmarshal(data, &_pullRequest)
|
||||
return _pullRequest, nil
|
||||
}
|
||||
|
||||
func FetchPatchContent(iid, repoPath string) (string, error) {
|
||||
url := fmt.Sprintf("https://gitee.com/%s/pulls/%s.diff", repoPath, iid)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
if _, err := giteeClient.Do(); err != nil || giteeClient.IsFail() {
|
||||
return "", errors.New("获取 Pull Request 补丁内容失败")
|
||||
}
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
|
||||
return string(data), nil
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package ssh_key
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee_cli/config"
|
||||
"gitee_cli/utils/http_utils"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type SSHKey struct {
|
||||
Id int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Url string `json:"url"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
func AddKey(filepath, title string) (SSHKey, error) {
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return SSHKey{}, err
|
||||
}
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return SSHKey{}, errors.New("读取公钥失败")
|
||||
}
|
||||
|
||||
url := "https://gitee.com/api/v5/user/keys"
|
||||
payload := map[string]string{
|
||||
"key": string(data),
|
||||
"title": title,
|
||||
}
|
||||
giteeClient := http_utils.NewGiteeClient("POST", url, nil, payload)
|
||||
|
||||
giteeClient.Do()
|
||||
|
||||
res, _ := giteeClient.GetRespBody()
|
||||
|
||||
if giteeClient.IsFail() {
|
||||
errResponse := http_utils.ErrMsgV5{}
|
||||
err := json.Unmarshal(res, &errResponse)
|
||||
if err != nil {
|
||||
return SSHKey{}, errors.New("添加公钥失败")
|
||||
}
|
||||
return SSHKey{}, errors.New(errResponse.Message)
|
||||
}
|
||||
|
||||
sshKey := SSHKey{}
|
||||
err = json.Unmarshal(res, &sshKey)
|
||||
if err != nil {
|
||||
return sshKey, errors.New("解析响应失败")
|
||||
}
|
||||
return sshKey, nil
|
||||
}
|
||||
|
||||
func ListKeys() ([]SSHKey, error) {
|
||||
url := fmt.Sprintf("https://gitee.com/api/v5/users/%s/keys?access_token=%s", config.Conf.UserName, config.Conf.AccessToken)
|
||||
|
||||
req, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sshKeys := make([]SSHKey, 0)
|
||||
|
||||
res, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(res, &sshKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sshKeys, nil
|
||||
}
|
||||
|
||||
func DeleteKey(sshKeyId string) error {
|
||||
url := fmt.Sprintf("https://gitee.com/api/v5/user/keys/%s", sshKeyId)
|
||||
giteeClient := http_utils.NewGiteeClient("DELETE", url, nil, nil)
|
||||
|
||||
giteeClient.Do()
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
|
||||
if giteeClient.IsFail() {
|
||||
if giteeClient.Response.StatusCode == http.StatusNotFound {
|
||||
return errors.New("公钥不存在")
|
||||
}
|
||||
errMsg := http_utils.ErrMsgV5{}
|
||||
json.Unmarshal(data, &errMsg)
|
||||
return errors.New(errMsg.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee_cli/utils/http_utils"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
Id int `json:"id"`
|
||||
Remark string `json:"remark"`
|
||||
UserName string `json:"username"`
|
||||
}
|
||||
|
||||
func FindUser(username string) (User, error) {
|
||||
url := fmt.Sprintf("https://gitee.com/api/v5/search/users?q=%s", username)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.Do()
|
||||
|
||||
if giteeClient.IsFail() {
|
||||
return User{}, errors.New("查询用户失败!")
|
||||
}
|
||||
|
||||
users := make([]User, 0)
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
if err := json.Unmarshal(data, &users); err != nil {
|
||||
return User{}, errors.New("查询用户失败,解析响应失败!")
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return User{}, nil
|
||||
}
|
||||
|
||||
return users[0], nil
|
||||
}
|
||||
|
||||
func FindMember(keyword string, enterpriseId int) (Member, error) {
|
||||
url := fmt.Sprintf("https://api.gitee.com/enterprises/%d/members?search=%s", enterpriseId, keyword)
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
giteeClient.Do()
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
if giteeClient.IsFail() {
|
||||
return Member{}, errors.New("查询企业成员失败!")
|
||||
}
|
||||
|
||||
members := struct {
|
||||
Data []Member `json:"data"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}{}
|
||||
|
||||
if err := json.Unmarshal(data, &members); err != nil {
|
||||
return Member{}, errors.New("查询用户失败,解析响应失败!")
|
||||
}
|
||||
|
||||
if len(members.Data) == 0 {
|
||||
return Member{}, nil
|
||||
}
|
||||
|
||||
return members.Data[0], nil
|
||||
|
||||
}
|
||||
|
||||
func BasicUser() (User, error) {
|
||||
url := "https://api.gitee.com/enterprises/users"
|
||||
giteeClient := http_utils.NewGiteeClient("GET", url, nil, nil)
|
||||
giteeClient.SetCookieAuth()
|
||||
_, err := giteeClient.Do()
|
||||
if err != nil || giteeClient.IsFail() {
|
||||
return User{}, errors.New("查询用户失败!")
|
||||
}
|
||||
user := User{}
|
||||
|
||||
data, _ := giteeClient.GetRespBody()
|
||||
|
||||
if err := json.Unmarshal(data, &user); err != nil {
|
||||
return User{}, errors.New("查询用户失败,解析响应失败!")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gitee_cli/cmd"
|
||||
_ "gitee_cli/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package utils
|
||||
|
||||
import "github.com/fatih/color"
|
||||
|
||||
var (
|
||||
green func(a ...interface{}) string = color.New(color.FgGreen).SprintFunc()
|
||||
blue func(a ...interface{}) string = color.New(color.FgBlue).SprintFunc()
|
||||
yellow func(a ...interface{}) string = color.New(color.FgYellow).SprintFunc()
|
||||
cyan func(a ...interface{}) string = color.New(color.FgCyan).SprintFunc()
|
||||
red func(a ...interface{}) string = color.New(color.FgRed).SprintFunc()
|
||||
magenta func(a ...interface{}) string = color.New(color.FgMagenta).SprintFunc()
|
||||
)
|
||||
|
||||
func Green(a ...interface{}) string {
|
||||
return green(a...)
|
||||
}
|
||||
|
||||
func Blue(a ...interface{}) string {
|
||||
return blue(a...)
|
||||
}
|
||||
|
||||
func Yellow(a ...interface{}) string {
|
||||
return yellow(a...)
|
||||
}
|
||||
|
||||
func Cyan(a ...interface{}) string {
|
||||
return cyan(a...)
|
||||
}
|
||||
|
||||
func Red(a ...interface{}) string {
|
||||
return red(a...)
|
||||
}
|
||||
|
||||
func Magenta(a ...interface{}) string {
|
||||
return magenta(a...)
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/config"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"os"
|
||||
)
|
||||
|
||||
func InitialEditor(message string, defaultContent string) *survey.Editor {
|
||||
return &survey.Editor{
|
||||
Editor: config.Conf.DefaultEditor,
|
||||
Default: fmt.Sprint(defaultContent),
|
||||
AppendDefault: true,
|
||||
Message: message,
|
||||
FileName: "*.md",
|
||||
}
|
||||
}
|
||||
|
||||
func ReadFromEditor(editor *survey.Editor, content string) string {
|
||||
survey.EditorQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if .ShowAnswer}}
|
||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
||||
{{- color "cyan"}}[Enter to launch editor] {{color "reset"}}
|
||||
{{- end}}`
|
||||
if err := survey.AskOne(editor, &content); err != nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
func ReadFromInput(message, content string) string {
|
||||
prompt := &survey.Input{
|
||||
Message: message,
|
||||
}
|
||||
if err := survey.AskOne(prompt, &content); err != nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
return content
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package git_utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
BRANCH_PREFIX = "refs/heads/"
|
||||
HTTP_PREFIX = "https://gitee.com/"
|
||||
SSH_PREFIX = "git@gitee.com:"
|
||||
GIT_SUFFIX = ".git"
|
||||
)
|
||||
|
||||
func CurrentDir() string {
|
||||
wd, _ := os.Getwd()
|
||||
return wd
|
||||
}
|
||||
|
||||
func IsGitDir() bool {
|
||||
wd := CurrentDir()
|
||||
if _, err := os.Stat(fmt.Sprintf("%s/.git", wd)); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GetCurrentBranch() (string, error) {
|
||||
catFile := exec.Command("cat", ".git/HEAD")
|
||||
extractBranch := exec.Command("awk", "{print $2}")
|
||||
|
||||
var output bytes.Buffer
|
||||
catFile.Stdout = &output
|
||||
extractBranch.Stdin = &output
|
||||
err := catFile.Run()
|
||||
res, err := extractBranch.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", errors.New("获取当前分支异常")
|
||||
}
|
||||
return strings.TrimSpace(strings.TrimPrefix(string(res), BRANCH_PREFIX)), nil
|
||||
}
|
||||
|
||||
func ParseCurrentRepo() (string, error) {
|
||||
var err error
|
||||
var pathWithNamespace string
|
||||
if !IsGitDir() {
|
||||
return "", errors.New("请在仓库目录下执行该命令!")
|
||||
}
|
||||
gitRemote := exec.Command("git", "remote")
|
||||
gitRemote.Dir = CurrentDir()
|
||||
output, err := gitRemote.CombinedOutput()
|
||||
getUrl := exec.Command("git", "remote", "get-url", strings.Split(string(output), "\n")[0])
|
||||
getUrl.Dir = CurrentDir()
|
||||
output, err = getUrl.CombinedOutput()
|
||||
gitUrl := strings.Trim(string(output), "\n")
|
||||
gitUrl = strings.TrimPrefix(gitUrl, HTTP_PREFIX)
|
||||
gitUrl = strings.TrimPrefix(gitUrl, SSH_PREFIX)
|
||||
pathWithNamespace = strings.TrimSuffix(gitUrl, GIT_SUFFIX)
|
||||
return pathWithNamespace, err
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package http_utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"gitee_cli/config"
|
||||
"github.com/fatih/color"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
type GiteeClient struct {
|
||||
Url string
|
||||
Method string
|
||||
Payload interface{}
|
||||
Headers map[string]string
|
||||
Response *http.Response
|
||||
CookieAuth bool
|
||||
Query map[string]string
|
||||
}
|
||||
|
||||
type ErrMsgV5 struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func NewGiteeClient(method, urlString string, query map[string]string, payload interface{}) *GiteeClient {
|
||||
parsedUrl, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if query != nil {
|
||||
queryParams := parsedUrl.Query()
|
||||
for k, v := range query {
|
||||
queryParams.Set(k, v)
|
||||
}
|
||||
parsedUrl.RawQuery = queryParams.Encode()
|
||||
}
|
||||
return &GiteeClient{
|
||||
Method: method,
|
||||
Url: parsedUrl.String(),
|
||||
Payload: payload,
|
||||
Query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GiteeClient) SetHeaders(headers map[string]string) *GiteeClient {
|
||||
g.Headers = headers
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *GiteeClient) Do() (*GiteeClient, error) {
|
||||
// 多次调用首先置空
|
||||
g.Response = nil
|
||||
_payload, _ := json.Marshal(g.Payload)
|
||||
req, _ := http.NewRequest(g.Method, g.Url, bytes.NewReader(_payload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
cookie := config.Conf.CookiesJar
|
||||
accessToken := config.Conf.AccessToken
|
||||
if accessToken != "" && !g.CookieAuth {
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
} else if cookie != "" {
|
||||
req.Header.Set("Cookie", cookie)
|
||||
} else {
|
||||
color.Red("授权错误!")
|
||||
os.Exit(1)
|
||||
}
|
||||
for key, value := range g.Headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
client := &http.Client{}
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
if resp, err = client.Do(req); err != nil {
|
||||
return g, err
|
||||
}
|
||||
g.Response = resp
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *GiteeClient) IsSuccess() bool {
|
||||
if g.Response == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
successMap := map[int]struct{}{
|
||||
http.StatusOK: struct{}{},
|
||||
http.StatusCreated: struct{}{},
|
||||
http.StatusNoContent: struct{}{},
|
||||
}
|
||||
|
||||
if _, ok := successMap[g.Response.StatusCode]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *GiteeClient) IsFail() bool {
|
||||
return !g.IsSuccess()
|
||||
}
|
||||
|
||||
func (g *GiteeClient) GetRespBody() ([]byte, error) {
|
||||
return ioutil.ReadAll(g.Response.Body)
|
||||
}
|
||||
|
||||
func (g *GiteeClient) SetCookieAuth() {
|
||||
g.CookieAuth = true
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package issue_type_tui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gitee_cli/internal/api/enterprises"
|
||||
"gitee_cli/internal/api/issue_type"
|
||||
"gitee_cli/utils/tui"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"os"
|
||||
)
|
||||
|
||||
func NewIssueTypeSelector(category string, issueTypes []issue_type.IssueType) *tea.Program {
|
||||
|
||||
rows := make([]table.Row, 0)
|
||||
columns := []table.Column{{Title: category, Width: 20}}
|
||||
for _, issueType := range issueTypes {
|
||||
rows = append(rows, []string{issueType.Title})
|
||||
}
|
||||
|
||||
t := tui.NewTableModel(enterprises.Enterprise{}, tui.IssueType, columns, rows)
|
||||
return tea.NewProgram(t)
|
||||
}
|
||||
|
||||
func SelectedValue(issueTypes []issue_type.IssueType, selectedKey string) (int, error) {
|
||||
if selectedKey == "" {
|
||||
os.Exit(0)
|
||||
}
|
||||
for _, issueType := range issueTypes {
|
||||
if issueType.Title == selectedKey {
|
||||
return issueType.Id, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("无效的选项!")
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/charmbracelet/glamour"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// You generally won't need this unless you're processing stuff with
|
||||
// complicated ANSI escape sequences. Turn it on if you notice flickering.
|
||||
//
|
||||
// Also keep in mind that high performance rendering only works for programs
|
||||
// that use the full size of the terminal. We're enabling that below with
|
||||
// tea.EnterAltScreen().
|
||||
const useHighPerformanceRenderer = true
|
||||
|
||||
const (
|
||||
Markdown = iota
|
||||
Diff
|
||||
)
|
||||
|
||||
var (
|
||||
titleStyle = func() lipgloss.Style {
|
||||
b := lipgloss.RoundedBorder()
|
||||
b.Right = "├"
|
||||
return lipgloss.NewStyle().BorderStyle(b).BorderForeground(lipgloss.Color("#008800")).Padding(0, 1)
|
||||
}()
|
||||
|
||||
infoStyle = func() lipgloss.Style {
|
||||
b := lipgloss.RoundedBorder()
|
||||
b.Left = "┤"
|
||||
return titleStyle.Copy().BorderStyle(b)
|
||||
}()
|
||||
)
|
||||
|
||||
type model struct {
|
||||
title string
|
||||
content string
|
||||
ready bool
|
||||
viewport viewport.Model
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPager(title, content string, contentType int) *tea.Program {
|
||||
switch contentType {
|
||||
case Markdown:
|
||||
content, _ = glamour.Render(content, "dark")
|
||||
}
|
||||
|
||||
return tea.NewProgram(model{title: title, content: content}, tea.WithAltScreen(), tea.WithMouseCellMotion())
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var (
|
||||
cmd tea.Cmd
|
||||
cmds []tea.Cmd
|
||||
)
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" {
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
headerHeight := lipgloss.Height(m.headerView())
|
||||
footerHeight := lipgloss.Height(m.footerView())
|
||||
verticalMarginHeight := headerHeight + footerHeight
|
||||
|
||||
if !m.ready {
|
||||
// Since this program is using the full size of the viewport we
|
||||
// need to wait until we've received the window dimensions before
|
||||
// we can initialize the viewport. The initial dimensions come in
|
||||
// quickly, though asynchronously, which is why we wait for them
|
||||
// here.
|
||||
m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight)
|
||||
m.viewport.YPosition = headerHeight
|
||||
m.viewport.HighPerformanceRendering = useHighPerformanceRenderer
|
||||
m.viewport.SetContent(m.content)
|
||||
m.ready = true
|
||||
|
||||
// This is only necessary for high performance rendering, which in
|
||||
// most cases you won't need.
|
||||
//
|
||||
// Render the viewport one line below the header.
|
||||
m.viewport.YPosition = headerHeight + 1
|
||||
} else {
|
||||
m.viewport.Width = msg.Width
|
||||
m.viewport.Height = msg.Height - verticalMarginHeight
|
||||
}
|
||||
|
||||
if useHighPerformanceRenderer {
|
||||
// Render (or re-render) the whole viewport. Necessary both to
|
||||
// initialize the viewport and when the window is resized.
|
||||
//
|
||||
// This is needed for high-performance rendering only.
|
||||
cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle keyboard and mouse events in the viewport
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
if !m.ready {
|
||||
return "\n Initializing..."
|
||||
}
|
||||
return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView())
|
||||
}
|
||||
|
||||
func (m model) headerView() string {
|
||||
title := titleStyle.Render(m.title)
|
||||
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)))
|
||||
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
|
||||
}
|
||||
|
||||
func (m model) footerView() string {
|
||||
info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
|
||||
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info)))
|
||||
return lipgloss.JoinHorizontal(lipgloss.Center, line, info)
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package selector_tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/utils"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"os"
|
||||
)
|
||||
|
||||
type MapSelector struct {
|
||||
OptionsMap map[string]int
|
||||
Options []string
|
||||
Promote string
|
||||
Cursor int
|
||||
}
|
||||
|
||||
func NewMapSelector(optionsMap map[string]int, options []string, promote string) *tea.Program {
|
||||
mapSelector := MapSelector{
|
||||
OptionsMap: optionsMap,
|
||||
Options: options,
|
||||
Promote: promote,
|
||||
}
|
||||
return tea.NewProgram(mapSelector)
|
||||
}
|
||||
|
||||
func (m MapSelector) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m MapSelector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "up", "k":
|
||||
if m.Cursor > 0 {
|
||||
m.Cursor--
|
||||
}
|
||||
case "down", "j":
|
||||
if m.Cursor < len(m.Options)-1 {
|
||||
m.Cursor++
|
||||
}
|
||||
case "ctrl+c", "q":
|
||||
m.Cursor = -1
|
||||
return m, tea.Quit
|
||||
case "enter":
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m MapSelector) SelectedValue() (int, error) {
|
||||
if m.Cursor == -1 {
|
||||
os.Exit(0)
|
||||
}
|
||||
option := m.Options[m.Cursor]
|
||||
|
||||
if value, ok := m.OptionsMap[option]; ok {
|
||||
return value, nil
|
||||
}
|
||||
return 0, fmt.Errorf("无效的选项")
|
||||
}
|
||||
|
||||
func (m MapSelector) View() string {
|
||||
promote := m.Promote + "\n"
|
||||
for i, option := range m.Options {
|
||||
cursor := " "
|
||||
if i == m.Cursor {
|
||||
cursor = utils.Green(">")
|
||||
option = utils.Yellow(option)
|
||||
}
|
||||
promote += fmt.Sprintf("%s %s\n", cursor, option)
|
||||
}
|
||||
return promote
|
||||
}
|
||||
|
||||
func chunkOptions(options map[string]int) []string {
|
||||
_options := make([]string, 0)
|
||||
for key, _ := range options {
|
||||
_options = append(_options, key)
|
||||
if len(_options) == 16 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return _options
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/utils"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func InitialUploadSSHKeyTui(fileList []string) *tea.Program {
|
||||
return tea.NewProgram(UploadSSHKeyTui{
|
||||
FileList: fileList,
|
||||
})
|
||||
}
|
||||
|
||||
type UploadSSHKeyTui struct {
|
||||
FileList []string
|
||||
Cursor int
|
||||
}
|
||||
|
||||
func (s UploadSSHKeyTui) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s UploadSSHKeyTui) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "up", "k":
|
||||
if s.Cursor > 0 {
|
||||
s.Cursor--
|
||||
}
|
||||
case "down", "j":
|
||||
if s.Cursor < len(s.FileList)-1 {
|
||||
s.Cursor++
|
||||
}
|
||||
case "ctrl+c", "q":
|
||||
s.Cursor = -1
|
||||
return s, tea.Quit
|
||||
case "enter":
|
||||
return s, tea.Quit
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s UploadSSHKeyTui) View() string {
|
||||
promote := "请选择要上传的 SSH 公钥\n"
|
||||
for i, file := range s.FileList {
|
||||
cursor := " "
|
||||
if i == s.Cursor {
|
||||
cursor = utils.Green(">")
|
||||
file = utils.Yellow(file)
|
||||
}
|
||||
promote += fmt.Sprintf("%s %s\n", cursor, file)
|
||||
}
|
||||
return promote
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee_cli/config"
|
||||
"gitee_cli/internal/api/enterprises"
|
||||
"gitee_cli/internal/api/issue"
|
||||
"gitee_cli/internal/api/pull_request"
|
||||
"gitee_cli/utils/git_utils"
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/browser"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var baseStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("240"))
|
||||
|
||||
const (
|
||||
Issue = iota
|
||||
IssueType
|
||||
PullRequest
|
||||
Enterprise
|
||||
SSHKey
|
||||
)
|
||||
|
||||
// Table TODO 初始化改成 options 模式
|
||||
type Table struct {
|
||||
table table.Model
|
||||
SelectedKey string
|
||||
ViewMode bool
|
||||
ResourceType int
|
||||
Enterprise enterprises.Enterprise
|
||||
}
|
||||
|
||||
func NewTableModel(enterprise enterprises.Enterprise, resourceType int, columns []table.Column, rows []table.Row) Table {
|
||||
|
||||
height := 5
|
||||
if resourceType == PullRequest {
|
||||
height = 10
|
||||
}
|
||||
|
||||
t := table.New(
|
||||
table.WithColumns(columns),
|
||||
table.WithRows(rows),
|
||||
table.WithFocused(true),
|
||||
table.WithHeight(height),
|
||||
)
|
||||
|
||||
s := table.DefaultStyles()
|
||||
s.Header = s.Header.
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240")).
|
||||
BorderBottom(true).
|
||||
Bold(false)
|
||||
s.Selected = s.Selected.
|
||||
Foreground(lipgloss.Color("229")).
|
||||
Background(lipgloss.Color("57")).
|
||||
Bold(false)
|
||||
if resourceType == PullRequest {
|
||||
s.Selected = s.Selected.Background(lipgloss.Color("#8B4789"))
|
||||
// 避免与 diff 快捷键冲突
|
||||
t.KeyMap.HalfPageDown = key.NewBinding(
|
||||
key.WithDisabled(),
|
||||
)
|
||||
}
|
||||
t.SetStyles(s)
|
||||
return Table{table: t, Enterprise: enterprise, ResourceType: resourceType}
|
||||
}
|
||||
|
||||
func NewTable(enterprise enterprises.Enterprise, resourceType int, columns []table.Column, rows []table.Row) *tea.Program {
|
||||
if resourceType == SSHKey || resourceType == Enterprise {
|
||||
return tea.NewProgram(NewTableModel(enterprise, resourceType, columns, rows), tea.WithAltScreen())
|
||||
}
|
||||
return tea.NewProgram(NewTableModel(enterprise, resourceType, columns, rows))
|
||||
}
|
||||
|
||||
func (t Table) Init() tea.Cmd { return nil }
|
||||
|
||||
func (t Table) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
if t.table.Focused() {
|
||||
t.table.Blur()
|
||||
} else {
|
||||
t.table.Focus()
|
||||
}
|
||||
case "c":
|
||||
if t.ResourceType == Issue || t.ResourceType == Enterprise {
|
||||
clipboard.WriteAll(t.table.SelectedRow()[0])
|
||||
} else if t.ResourceType == PullRequest {
|
||||
clipboard.WriteAll(t.table.SelectedRow()[1])
|
||||
}
|
||||
case "q", "ctrl+c":
|
||||
t.SelectedKey = ""
|
||||
return t, tea.Quit
|
||||
case "v":
|
||||
if t.ResourceType == Issue {
|
||||
if _issue, err := issue.Detail(t.Enterprise.Id, t.table.SelectedRow()[0]); err == nil {
|
||||
NewPager(_issue.Title, _issue.Description, Markdown).Run()
|
||||
} else {
|
||||
color.Red("获取任务详情失败!")
|
||||
return t, tea.Quit
|
||||
}
|
||||
} else if t.ResourceType == PullRequest {
|
||||
path, _ := git_utils.ParseCurrentRepo()
|
||||
if path == "" {
|
||||
path = config.Conf.DefaultPathWithNamespace
|
||||
}
|
||||
t.SelectedKey = t.table.SelectedRow()[1]
|
||||
if pullRequerst, err := pull_request.Detail(t.SelectedKey, path); err == nil {
|
||||
NewPager(pullRequerst.Title, pullRequerst.Body, Markdown).Run()
|
||||
} else {
|
||||
color.Red("获取pr详情失败!")
|
||||
return t, tea.Quit
|
||||
}
|
||||
}
|
||||
case "d":
|
||||
if t.ResourceType == PullRequest {
|
||||
path, _ := git_utils.ParseCurrentRepo()
|
||||
t.SelectedKey = t.table.SelectedRow()[1]
|
||||
if path == "" {
|
||||
path = config.Conf.DefaultPathWithNamespace
|
||||
}
|
||||
if diff, err := pull_request.FetchPatchContent(t.SelectedKey, path); err == nil {
|
||||
NewPager(t.table.SelectedRow()[0], diff, Diff).Run()
|
||||
} else {
|
||||
color.Red(err.Error())
|
||||
return t, tea.Quit
|
||||
}
|
||||
}
|
||||
case "enter":
|
||||
t.SelectedKey = t.table.SelectedRow()[0]
|
||||
if t.ResourceType == Issue {
|
||||
url := fmt.Sprintf("https://e.gitee.com/%s/dashboard?issue=%s", t.Enterprise.Path, t.SelectedKey)
|
||||
browser.OpenURL(url)
|
||||
} else if t.ResourceType == PullRequest {
|
||||
path, _ := git_utils.ParseCurrentRepo()
|
||||
if path == "" {
|
||||
path = config.Conf.DefaultPathWithNamespace
|
||||
}
|
||||
t.SelectedKey = t.table.SelectedRow()[1]
|
||||
url := fmt.Sprintf("https://gitee.com/%s/pulls/%s", path, t.SelectedKey)
|
||||
if os.Getenv("CONVERT_ENT_URL") != "" {
|
||||
url = fmt.Sprintf("https://e.gitee.com/%s/repos/%s/pulls/%s", strings.Split(path, "/")[0], path, t.SelectedKey)
|
||||
}
|
||||
browser.OpenURL(url)
|
||||
} else {
|
||||
return t, tea.Quit
|
||||
}
|
||||
}
|
||||
}
|
||||
t.table, cmd = t.table.Update(msg)
|
||||
return t, cmd
|
||||
}
|
||||
|
||||
func (t Table) View() string {
|
||||
return baseStyle.Render(t.table.View()) + "\n"
|
||||
}
|
Loading…
Reference in New Issue