From ca5c03bfbfa597b7b597ee449ec57df461ea424e Mon Sep 17 00:00:00 2001 From: xxq250 Date: Fri, 11 Apr 2025 14:35:39 +0800 Subject: [PATCH 01/22] =?UTF-8?q?fixed=201=E3=80=81=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BB=93=E5=BA=93=E5=A4=A7=E5=B0=8F=E9=99=90=E5=88=B6=EF=BC=9B?= =?UTF-8?q?2=E3=80=81arm=E6=9E=B6=E6=9E=84=E4=B8=8Bhook=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9B3=E3=80=81=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BB=93=E5=BA=93=E9=99=90=E5=88=B6=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- routers/hat/hat.go | 3 + routers/hat/repo/size_limit.go | 389 +++++++++++++++++++++++++++++++++ 3 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 routers/hat/repo/size_limit.go diff --git a/main.go b/main.go index f77fcf2..d5bd2b1 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.7, by v1.21.0 " + Version = "v2.8, by v1.21.0 " Tags = "" MakeVersion = "" ) diff --git a/routers/hat/hat.go b/routers/hat/hat.go index 8ac5d76..e3870ff 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -121,6 +121,9 @@ func Routers() *web.Route { m.Group("", func() { m.Get("/version", misc.Version) m.Post("/create_pr_version", bind(gitea_api.PullRequestPayload{}), repo.CreatePrVersion) + m.Post("/create_repo_size_limit_hook", bind(gitea_api.RepositoryPayload{}), repo.CreateRepoSizeLimitHook) + m.Post("/update_hook_arm", bind(gitea_api.RepositoryPayload{}), repo.UpdateHookArm) + m.Post("/update_repo_size_limit_hook", bind(gitea_api.RepositoryPayload{}), repo.UpdateRepoSizeLimitHook) m.Group("/repos", func() { m.Group("/{username}/{reponame}", func() { m.Combo("").Delete(reqToken(), reqOwner(), repo.Delete) diff --git a/routers/hat/repo/size_limit.go b/routers/hat/repo/size_limit.go new file mode 100644 index 0000000..a69e279 --- /dev/null +++ b/routers/hat/repo/size_limit.go @@ -0,0 +1,389 @@ +package repo + +import ( + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + gitea_api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + "fmt" + "github.com/unknwon/com" + "io/ioutil" + "net/http" + "os" + "path/filepath" +) + +const ( + SIZE_LIMIT_SCRIPT_NAME = "size_limit" +) + +type UpdateRepoSizeLimit struct { + OwnerName string `json:"owner_name"` + RepoName string `json:"repo_name"` + LimitSize int `json:"limit_size"` +} + +func CreateRepoSizeLimitHook(ctx *context.APIContext) { + form := web.GetForm(ctx).(*gitea_api.RepositoryPayload) + eventName := string(form.Action) + if eventName == "deleted" { + ctx.Status(http.StatusOK) + return + } + getAssignmentRepo(ctx, form.Repository.ID) + hookDir := filepath.Join(ctx.Repo.Repository.RepoPath(), "hooks") + hookName := "pre-receive" + //results := make([]string, 0, 10) + sizeLimitTpls := []string{ + fmt.Sprintf("#!/usr/bin/env %s\n\n\nset -o pipefail\n\nreadonly DEFAULT_FILE_MAXSIZE_MB=\"30\" \nreadonly CONFIG_NAME=\"hooks.maxfilesize\"\nreadonly NULLSHA=\"0000000000000000000000000000000000000000\"\nreadonly EXIT_SUCCESS=0\nreadonly EXIT_FAILURE=1\nreadonly DEFAULT_REPO_MAXSIZE_MB=\"1024\" \nreadonly CHECK_FLAG_ON=1\n\n\nstatus=\"$EXIT_SUCCESS\"\n\n# skip this hook entirely if shell check is not open\ncheck_flag=${PUSH_SIZE_CHECK_FLAG}\nif [[ $check_flag != $CHECK_FLAG_ON ]]; then\nexit $EXIT_SUCCESS\nfi\n\n\n#######################################\n# check the file max size limit\n#######################################\n\n# get maximum filesize (from repository-specific config)\nmaxsize_mb=\"${REPO_MAX_FILE_SIZE}\"\n\nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\necho \"failed to get ${CONFIG_NAME} from config\"\nexit \"$EXIT_FAILURE\"\nfi\n\npush_size=\"0\"\n# read lines from stdin (format: \" \\n\")\nwhile read oldref newref refname; do\n# skip branch deletions\nif [[ \"$newref\" == \"$NULLSHA\" ]]; then\n continue\nfi\n\n# find large objects\n# check all objects from $oldref (possible $NULLSHA) to $newref, but\n# skip all objects that have already been accepted (i.e. are referenced by\n# another branch or tag).\n\nnew_branch_flag=0\nif [[ \"$oldref\" == \"$NULLSHA\" ]]; then\n target=\"$newref\"\n new_branch_flag=1\n echo \"You are creating a new remote branch,openI will check all files in commit history to find oversize files\"\nelse\n target=\"${oldref}..${newref}\"\nfi\nmaxsize=`expr $maxsize_mb \\* 1048576` \n\n# find objects in this push_size\n# print like:\n# 08da8e2ab9ae4095bf94dd71ac913132b880b463 commit 214\n# 43e993b768ede5740e8c65de2ed6edec25053ea1 tree 185\n# 4476971d76569039df7569af1b8d03c288f6b193 blob 20167318 b0417e6593a1.zip\nfiles=\"$(git rev-list --objects \"$target\" | \\\n git cat-file $'--batch-check=%%(objectname) %%(objecttype) %%(objectsize) %%(rest)' | \\\n awk -F ' ' -v maxbytes=\"$maxsize\" 'BEGIN {totalIn=0} {if( $3 > maxbytes && $2 == \"blob\") { totalIn+=$3; print $4} else { totalIn+=$3}} END { printf (\"totalIn=\\t%%s\",totalIn)}' )\"\n \nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\n echo \"failed to check for large files in ref ${refname}\"\n continue\nfi\n\nIFS=$'\\n'\n# rewrite IFS to seperate line in $files\nfor file in $files; do\n # if don't unset IFS,temp_array=(${file}) will get error answer\n \n if [[ ${file} == totalIn=* ]]; then\n\tIFS=$'\\t'\n\ttemp_array=(${file})\n\tpush_size=${temp_array[1]}\n\tcontinue\n fi\n\tunset IFS\n if [[ \"$status\" == $EXIT_SUCCESS ]]; then\n\t\techo -e \"Error: Your push was rejected because it contains files larger than $(numfmt --to=iec \"$maxsize_mb\") Mb\"\n\t\techo \"help document -- https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n\t\techo \"oversize files:\"\n\t\tstatus=\"$EXIT_FAILURE\"\t\n fi\n echo -e \"\\033[31m- ${file}\\033[0m \"\ndone\n\nif [[ \"$status\" != $EXIT_SUCCESS ]]; then\n\texit \"$status\"\nfi\n\ndone\n\n#######################################\n# check the repo max size limit\n#######################################\nif [[ $push_size -eq \"0\" ]]; then\n\texit $EXIT_SUCCESS\nfi\n\n# if create new branch or tag,use count-objects -v to get pack size\nif [[ $new_branch_flag -eq 1 ]]; then\n size_kb=`git count-objects -v | grep 'size-pack' | sed 's/.*\\(size-pack:\\).//'`\n size_pack_kb=`git count-objects -v | grep 'size:' | sed 's/.*\\(size:\\).//'`\n\ttotal_kb=`expr $size_kb + $size_pack_kb`\n\tlet push_size=$total_kb*1024\nfi\n\nsizelimit_mb=\"${REPO_MAX_SIZE}\"\nlet sizelimit_b=$sizelimit_mb*1024*1024\n\n# repo size at here means the size of repo directory in server \nreposize_b=${REPO_CURRENT_SIZE}\n\ntotal=`expr $push_size + $reposize_b`\n\nif [ $total -gt $sizelimit_b ]; then\n echo \"Error: Your push was rejected because the repository size is large than $sizelimit_mb Mb\"\n echo \"see the help document--https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n exit $EXIT_FAILURE\nfi\n\n\nexit $EXIT_SUCCESS", setting.ScriptType), + fmt.Sprintf(""), + fmt.Sprintf(""), + } + + oldHookPath := filepath.Join(hookDir, hookName+".d", SIZE_LIMIT_SCRIPT_NAME) + if !com.IsExist(oldHookPath) { + //if err := checkHookFile(generateHookScriptPath(hookDir, hookName, SIZE_LIMIT_SCRIPT_NAME), sizeLimitTpls[0], results); err != nil { + // ctx.Error(http.StatusInternalServerError, "CreateSizeLimitHook checkHookFile", err) + // ctx.NotFound() + // return + //} + + if err := writeHookTpl(generateHookScriptPath(hookDir, hookName, SIZE_LIMIT_SCRIPT_NAME), sizeLimitTpls[0]); err != nil { + ctx.Error(http.StatusInternalServerError, "CreateSizeLimitHook checkHookFile", err) + ctx.NotFound() + return + } + } + ctx.Status(http.StatusOK) +} + +// 更新arm架构中事件失效问题 +func UpdateHookArm(ctx *context.APIContext) { + form := web.GetForm(ctx).(*gitea_api.RepositoryPayload) + eventName := string(form.Action) + if eventName == "deleted" { + ctx.Status(http.StatusOK) + return + } + getAssignmentRepo(ctx, form.Repository.ID) + if err := UpdateDelegateHooks(ctx.Repo.Repository.RepoPath()); err != nil { + fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", ctx.Repo, err) + ctx.Error(http.StatusInternalServerError, "", "Unable to recreate delegate hooks.") + return + } + ctx.Status(http.StatusOK) +} + +// 更新仓库限制大小 +func UpdateRepoSizeLimitHook(ctx *context.APIContext) { + form := web.GetForm(ctx).(*UpdateRepoSizeLimit) + ownerName := string(form.OwnerName) + repoName := string(form.RepoName) + LimitSize := form.LimitSize + + getAssignmentRepoByName(ctx, ownerName, repoName) + + hookDir := filepath.Join(ctx.Repo.Repository.RepoPath(), "hooks") + hookName := "pre-receive" + //results := make([]string, 0, 10) + sizeLimitTpls := []string{ + fmt.Sprintf("#!/usr/bin/env %s\n\n\nset -o pipefail\n\nreadonly DEFAULT_FILE_MAXSIZE_MB=\"30\" \nreadonly CONFIG_NAME=\"hooks.maxfilesize\"\nreadonly NULLSHA=\"0000000000000000000000000000000000000000\"\nreadonly EXIT_SUCCESS=0\nreadonly EXIT_FAILURE=1\nreadonly DEFAULT_REPO_MAXSIZE_MB=\"1024\" \nreadonly CHECK_FLAG_ON=1\n\n\nstatus=\"$EXIT_SUCCESS\"\n\n# skip this hook entirely if shell check is not open\ncheck_flag=${PUSH_SIZE_CHECK_FLAG}\nif [[ $check_flag != $CHECK_FLAG_ON ]]; then\nexit $EXIT_SUCCESS\nfi\n\n\n#######################################\n# check the file max size limit\n#######################################\n\n# get maximum filesize (from repository-specific config)\nmaxsize_mb=\"${REPO_MAX_FILE_SIZE}\"\n\nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\necho \"failed to get ${CONFIG_NAME} from config\"\nexit \"$EXIT_FAILURE\"\nfi\n\npush_size=\"0\"\n# read lines from stdin (format: \" \\n\")\nwhile read oldref newref refname; do\n# skip branch deletions\nif [[ \"$newref\" == \"$NULLSHA\" ]]; then\n continue\nfi\n\n# find large objects\n# check all objects from $oldref (possible $NULLSHA) to $newref, but\n# skip all objects that have already been accepted (i.e. are referenced by\n# another branch or tag).\n\nnew_branch_flag=0\nif [[ \"$oldref\" == \"$NULLSHA\" ]]; then\n target=\"$newref\"\n new_branch_flag=1\n echo \"You are creating a new remote branch,openI will check all files in commit history to find oversize files\"\nelse\n target=\"${oldref}..${newref}\"\nfi\nmaxsize=`expr $maxsize_mb \\* 1048576` \n\n# find objects in this push_size\n# print like:\n# 08da8e2ab9ae4095bf94dd71ac913132b880b463 commit 214\n# 43e993b768ede5740e8c65de2ed6edec25053ea1 tree 185\n# 4476971d76569039df7569af1b8d03c288f6b193 blob 20167318 b0417e6593a1.zip\nfiles=\"$(git rev-list --objects \"$target\" | \\\n git cat-file $'--batch-check=%%(objectname) %%(objecttype) %%(objectsize) %%(rest)' | \\\n awk -F ' ' -v maxbytes=\"$maxsize\" 'BEGIN {totalIn=0} {if( $3 > maxbytes && $2 == \"blob\") { totalIn+=$3; print $4} else { totalIn+=$3}} END { printf (\"totalIn=\\t%%s\",totalIn)}' )\"\n \nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\n echo \"failed to check for large files in ref ${refname}\"\n continue\nfi\n\nIFS=$'\\n'\n# rewrite IFS to seperate line in $files\nfor file in $files; do\n # if don't unset IFS,temp_array=(${file}) will get error answer\n \n if [[ ${file} == totalIn=* ]]; then\n\tIFS=$'\\t'\n\ttemp_array=(${file})\n\tpush_size=${temp_array[1]}\n\tcontinue\n fi\n\tunset IFS\n if [[ \"$status\" == $EXIT_SUCCESS ]]; then\n\t\techo -e \"Error: Your push was rejected because it contains files larger than $(numfmt --to=iec \"$maxsize_mb\") Mb\"\n\t\techo \"help document -- https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n\t\techo \"oversize files:\"\n\t\tstatus=\"$EXIT_FAILURE\"\t\n fi\n echo -e \"\\033[31m- ${file}\\033[0m \"\ndone\n\nif [[ \"$status\" != $EXIT_SUCCESS ]]; then\n\texit \"$status\"\nfi\n\ndone\n\n#######################################\n# check the repo max size limit\n#######################################\nif [[ $push_size -eq \"0\" ]]; then\n\texit $EXIT_SUCCESS\nfi\n\n# if create new branch or tag,use count-objects -v to get pack size\nif [[ $new_branch_flag -eq 1 ]]; then\n size_kb=`git count-objects -v | grep 'size-pack' | sed 's/.*\\(size-pack:\\).//'`\n size_pack_kb=`git count-objects -v | grep 'size:' | sed 's/.*\\(size:\\).//'`\n\ttotal_kb=`expr $size_kb + $size_pack_kb`\n\tlet push_size=$total_kb*1024\nfi\n\nsizelimit_mb=\"${REPO_MAX_SIZE}\"\nlet sizelimit_b=$sizelimit_mb*1024*1024\n\n# repo size at here means the size of repo directory in server \nreposize_b=${REPO_CURRENT_SIZE}\n\ntotal=`expr $push_size + $reposize_b`\n\nif [ $total -gt $sizelimit_b ]; then\n echo \"Error: Your push was rejected because the repository size is large than $sizelimit_mb Mb\"\n echo \"see the help document--https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n exit $EXIT_FAILURE\nfi\n\n\nexit $EXIT_SUCCESS", setting.ScriptType, LimitSize), + fmt.Sprintf(""), + fmt.Sprintf(""), + } + + oldHookPath := filepath.Join(hookDir, hookName+".d", SIZE_LIMIT_SCRIPT_NAME) + if !com.IsExist(oldHookPath) { + //if err := checkHookFile(generateHookScriptPath(hookDir, hookName, SIZE_LIMIT_SCRIPT_NAME), sizeLimitTpls[0], results); err != nil { + // ctx.Error(http.StatusInternalServerError, "CreateSizeLimitHook checkHookFile", err) + // ctx.NotFound() + // return + //} + + if err := writeHookTpl(generateHookScriptPath(hookDir, hookName, SIZE_LIMIT_SCRIPT_NAME), sizeLimitTpls[0]); err != nil { + ctx.Error(http.StatusInternalServerError, "CreateSizeLimitHook checkHookFile", err) + ctx.NotFound() + return + } + } + ctx.Status(http.StatusOK) +} + +func resetRepoSizeLimitHook(ctx *context.APIContext) { + form := web.GetForm(ctx).(*gitea_api.RepositoryPayload) + eventName := string(form.Action) + if eventName == "deleted" { + ctx.Status(http.StatusOK) + return + } + getAssignmentRepo(ctx, form.Repository.ID) + hookDir := filepath.Join(ctx.Repo.Repository.RepoPath(), "hooks") + hookName := "pre-receive" + //results := make([]string, 0, 10) + sizeLimitTpls := []string{ + fmt.Sprintf("#!/usr/bin/env %s\n\n\nset -o pipefail\n\nreadonly DEFAULT_FILE_MAXSIZE_MB=\"30\" \nreadonly CONFIG_NAME=\"hooks.maxfilesize\"\nreadonly NULLSHA=\"0000000000000000000000000000000000000000\"\nreadonly EXIT_SUCCESS=0\nreadonly EXIT_FAILURE=1\nreadonly DEFAULT_REPO_MAXSIZE_MB=\"1024\" \nreadonly CHECK_FLAG_ON=1\n\n\nstatus=\"$EXIT_SUCCESS\"\n\n# skip this hook entirely if shell check is not open\ncheck_flag=${PUSH_SIZE_CHECK_FLAG}\nif [[ $check_flag != $CHECK_FLAG_ON ]]; then\nexit $EXIT_SUCCESS\nfi\n\n\n#######################################\n# check the file max size limit\n#######################################\n\n# get maximum filesize (from repository-specific config)\nmaxsize_mb=\"${REPO_MAX_FILE_SIZE}\"\n\nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\necho \"failed to get ${CONFIG_NAME} from config\"\nexit \"$EXIT_FAILURE\"\nfi\n\npush_size=\"0\"\n# read lines from stdin (format: \" \\n\")\nwhile read oldref newref refname; do\n# skip branch deletions\nif [[ \"$newref\" == \"$NULLSHA\" ]]; then\n continue\nfi\n\n# find large objects\n# check all objects from $oldref (possible $NULLSHA) to $newref, but\n# skip all objects that have already been accepted (i.e. are referenced by\n# another branch or tag).\n\nnew_branch_flag=0\nif [[ \"$oldref\" == \"$NULLSHA\" ]]; then\n target=\"$newref\"\n new_branch_flag=1\n echo \"You are creating a new remote branch,openI will check all files in commit history to find oversize files\"\nelse\n target=\"${oldref}..${newref}\"\nfi\nmaxsize=`expr $maxsize_mb \\* 1048576` \n\n# find objects in this push_size\n# print like:\n# 08da8e2ab9ae4095bf94dd71ac913132b880b463 commit 214\n# 43e993b768ede5740e8c65de2ed6edec25053ea1 tree 185\n# 4476971d76569039df7569af1b8d03c288f6b193 blob 20167318 b0417e6593a1.zip\nfiles=\"$(git rev-list --objects \"$target\" | \\\n git cat-file $'--batch-check=%%(objectname) %%(objecttype) %%(objectsize) %%(rest)' | \\\n awk -F ' ' -v maxbytes=\"$maxsize\" 'BEGIN {totalIn=0} {if( $3 > maxbytes && $2 == \"blob\") { totalIn+=$3; print $4} else { totalIn+=$3}} END { printf (\"totalIn=\\t%%s\",totalIn)}' )\"\n \nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\n echo \"failed to check for large files in ref ${refname}\"\n continue\nfi\n\nIFS=$'\\n'\n# rewrite IFS to seperate line in $files\nfor file in $files; do\n # if don't unset IFS,temp_array=(${file}) will get error answer\n \n if [[ ${file} == totalIn=* ]]; then\n\tIFS=$'\\t'\n\ttemp_array=(${file})\n\tpush_size=${temp_array[1]}\n\tcontinue\n fi\n\tunset IFS\n if [[ \"$status\" == $EXIT_SUCCESS ]]; then\n\t\techo -e \"Error: Your push was rejected because it contains files larger than $(numfmt --to=iec \"$maxsize_mb\") Mb\"\n\t\techo \"help document -- https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n\t\techo \"oversize files:\"\n\t\tstatus=\"$EXIT_FAILURE\"\t\n fi\n echo -e \"\\033[31m- ${file}\\033[0m \"\ndone\n\nif [[ \"$status\" != $EXIT_SUCCESS ]]; then\n\texit \"$status\"\nfi\n\ndone\n\n#######################################\n# check the repo max size limit\n#######################################\nif [[ $push_size -eq \"0\" ]]; then\n\texit $EXIT_SUCCESS\nfi\n\n# if create new branch or tag,use count-objects -v to get pack size\nif [[ $new_branch_flag -eq 1 ]]; then\n size_kb=`git count-objects -v | grep 'size-pack' | sed 's/.*\\(size-pack:\\).//'`\n size_pack_kb=`git count-objects -v | grep 'size:' | sed 's/.*\\(size:\\).//'`\n\ttotal_kb=`expr $size_kb + $size_pack_kb`\n\tlet push_size=$total_kb*1024\nfi\n\nsizelimit_mb=\"${REPO_MAX_SIZE}\"\nlet sizelimit_b=$sizelimit_mb*1024*1024\n\n# repo size at here means the size of repo directory in server \nreposize_b=${REPO_CURRENT_SIZE}\n\ntotal=`expr $push_size + $reposize_b`\n\nif [ $total -gt $sizelimit_b ]; then\n echo \"Error: Your push was rejected because the repository size is large than $sizelimit_mb Mb\"\n echo \"see the help document--https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n exit $EXIT_FAILURE\nfi\n\n\nexit $EXIT_SUCCESS", setting.ScriptType), + fmt.Sprintf(""), + fmt.Sprintf(""), + } + + oldHookPath := filepath.Join(hookDir, hookName+".d", SIZE_LIMIT_SCRIPT_NAME) + if !com.IsExist(oldHookPath) { + //if err := checkHookFile(generateHookScriptPath(hookDir, hookName, SIZE_LIMIT_SCRIPT_NAME), sizeLimitTpls[0], results); err != nil { + // ctx.Error(http.StatusInternalServerError, "CreateSizeLimitHook checkHookFile", err) + // ctx.NotFound() + // return + //} + + if err := writeHookTpl(generateHookScriptPath(hookDir, hookName, SIZE_LIMIT_SCRIPT_NAME), sizeLimitTpls[0]); err != nil { + ctx.Error(http.StatusInternalServerError, "CreateSizeLimitHook checkHookFile", err) + ctx.NotFound() + return + } + } + ctx.Status(http.StatusOK) +} + +func getAssignmentRepo(ctx *context.APIContext, id int64) { + // Get repository. + repo, err := repo_model.GetRepositoryByID(ctx, id) + if err != nil { + if repo_model.IsErrRepoNotExist(err) { + redirectRepoID, err := repo_model.LookupRedirect(repo.OwnerID, repo.Name) + if err == nil { + context.RedirectToRepo(ctx.Base, redirectRepoID) + } else if repo_model.IsErrRedirectNotExist(err) { + ctx.NotFound(ctx.Tr("repo.RepoNotFound", repo.OwnerID)) + } else { + ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err) + } + } else { + ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err) + } + return + } + ctx.Repo.Repository = repo + + if !ctx.Repo.HasAccess() { + ctx.NotFound() + return + } +} + +func getAssignmentRepoByName(ctx *context.APIContext, ownerName string, repoName string) { + // Get repository. + owner, err := user_model.GetUserByName(ctx, ownerName) + repo, err := repo_model.GetRepositoryByName(owner.ID, repoName) + if err != nil { + if repo_model.IsErrRepoNotExist(err) { + redirectRepoID, err := repo_model.LookupRedirect(repo.OwnerID, repo.Name) + if err == nil { + context.RedirectToRepo(ctx.Base, redirectRepoID) + } else if repo_model.IsErrRedirectNotExist(err) { + ctx.NotFound(ctx.Tr("repo.RepoNotFound", repo.OwnerID)) + } else { + ctx.Error(http.StatusInternalServerError, "LookupRepoRedirect", err) + } + } else { + ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err) + } + return + } + ctx.Repo.Repository = repo + + if !ctx.Repo.HasAccess() { + ctx.NotFound() + return + } +} + +// UpdateDelegateHooks creates all the hooks scripts for the repo +func UpdateDelegateHooks(repoPath string) (err error) { + hookNames, hookTpls, giteaHookTpls := getHookTemplates() + hookDir := filepath.Join(repoPath, "hooks") + + for i, hookName := range hookNames { + oldHookPath := filepath.Join(hookDir, hookName) + newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") + + if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { + return fmt.Errorf("create hooks dir '%s': %w", filepath.Join(hookDir, hookName+".d"), err) + } + + // WARNING: This will override all old server-side hooks + if err = util.Remove(oldHookPath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %w ", oldHookPath, err) + } + if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o777); err != nil { + return fmt.Errorf("write old hook file '%s': %w", oldHookPath, err) + } + + if err = ensureExecutable(oldHookPath); err != nil { + return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err) + } + + if err = util.Remove(newHookPath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %w", newHookPath, err) + } + if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o777); err != nil { + return fmt.Errorf("write new hook file '%s': %w", newHookPath, err) + } + + if err = ensureExecutable(newHookPath); err != nil { + return fmt.Errorf("Unable to set %s executable. Error %w", oldHookPath, err) + } + } + + return nil +} + +func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) { + hookNames = []string{"pre-receive", "update", "post-receive"} + hookTpls = []string{ + // for pre-receive + fmt.Sprintf(`#!/usr/bin/env %s +# AUTO GENERATED BY GITEA, DO NOT MODIFY +data=$(cat) +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)/..} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do + test -x "${hook}" && test -f "${hook}" + echo "${data}" | "${hook}" + exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do + [ ${i} -eq 0 ] || exit ${i} +done +`, setting.ScriptType), + + // for update + fmt.Sprintf(`#!/usr/bin/env %s +# AUTO GENERATED BY GITEA, DO NOT MODIFY +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0/..)} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do + test -x "${hook}" && test -f "${hook}" + "${hook}" $1 $2 $3 + exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do + [ ${i} -eq 0 ] || exit ${i} +done +`, setting.ScriptType), + + // for post-receive + fmt.Sprintf(`#!/usr/bin/env %s +# AUTO GENERATED BY GITEA, DO NOT MODIFY +data=$(cat) +exitcodes="" +hookname=$(basename $0) +GIT_DIR=${GIT_DIR:-$(dirname $0)/..} + +for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do + test -x "${hook}" && test -f "${hook}" + echo "${data}" | "${hook}" + exitcodes="${exitcodes} $?" +done + +for i in ${exitcodes}; do + [ ${i} -eq 0 ] || exit ${i} +done +`, setting.ScriptType), + } + + giteaHookTpls = []string{ + // for pre-receive + fmt.Sprintf(`#!/usr/bin/env %s +# AUTO GENERATED BY GITEA, DO NOT MODIFY +%s hook --config=%s pre-receive +`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), + + // for update + fmt.Sprintf(`#!/usr/bin/env %s +# AUTO GENERATED BY GITEA, DO NOT MODIFY +%s hook --config=%s update $1 $2 $3 +`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), + + // for post-receive + fmt.Sprintf(`#!/usr/bin/env %s +# AUTO GENERATED BY GITEA, DO NOT MODIFY +%s hook --config=%s post-receive +`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), + } + + if git.SupportProcReceive { + hookNames = append(hookNames, "proc-receive") + hookTpls = append(hookTpls, + fmt.Sprintf(`#!/usr/bin/env %s +# AUTO GENERATED BY GITEA, DO NOT MODIFY +%s hook --config=%s proc-receive +`, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf))) + giteaHookTpls = append(giteaHookTpls, "") + } + + return hookNames, hookTpls, giteaHookTpls +} + +func generateHookScriptPath(hookDir, hookName, fileName string) string { + return filepath.Join(hookDir, hookName+".d", fileName) +} + +func checkHookFile(filePath, tpl string, results []string) error { + if tpl == "" { + return nil + } + contents, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + if string(contents) != tpl { + results = append(results, fmt.Sprintf("old hook file %s is out of date", filePath)) + } + if !checkExecutable(filePath) { + results = append(results, fmt.Sprintf("old hook file %s is not executable", filePath)) + } + return nil +} + +func writeHookTpl(hookPath, content string) error { + if content == "" { + return nil + } + if err := ioutil.WriteFile(hookPath, []byte(content), 0777); err != nil { + return fmt.Errorf("write new hook file '%s': %v", hookPath, err) + } + + if err := ensureExecutable(hookPath); err != nil { + return fmt.Errorf("Unable to set %s executable. Error %v", hookPath, err) + } + return nil +} + +func checkExecutable(filename string) bool { + fileInfo, err := os.Stat(filename) + if err != nil { + return false + } + return (fileInfo.Mode() & 0100) > 0 +} + +func ensureExecutable(filename string) error { + fileInfo, err := os.Stat(filename) + if err != nil { + return err + } + if (fileInfo.Mode() & 0100) > 0 { + return nil + } + mode := fileInfo.Mode() | 0100 + return os.Chmod(filename, mode) +} From 89f6f640e157e25db760c5f85aa04d3070552024 Mon Sep 17 00:00:00 2001 From: xxq250 Date: Fri, 18 Apr 2025 16:09:39 +0800 Subject: [PATCH 02/22] =?UTF-8?q?fixed=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E4=BB=93=E5=BA=93=E5=A4=A7=E5=B0=8F=E9=99=90?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/repo/size_limit.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/routers/hat/repo/size_limit.go b/routers/hat/repo/size_limit.go index a69e279..25fe365 100644 --- a/routers/hat/repo/size_limit.go +++ b/routers/hat/repo/size_limit.go @@ -29,6 +29,10 @@ type UpdateRepoSizeLimit struct { func CreateRepoSizeLimitHook(ctx *context.APIContext) { form := web.GetForm(ctx).(*gitea_api.RepositoryPayload) + limitMb := ctx.FormInt("limit_size") + if limitMb < 30 { + limitMb = 512 + } eventName := string(form.Action) if eventName == "deleted" { ctx.Status(http.StatusOK) @@ -39,7 +43,7 @@ func CreateRepoSizeLimitHook(ctx *context.APIContext) { hookName := "pre-receive" //results := make([]string, 0, 10) sizeLimitTpls := []string{ - fmt.Sprintf("#!/usr/bin/env %s\n\n\nset -o pipefail\n\nreadonly DEFAULT_FILE_MAXSIZE_MB=\"30\" \nreadonly CONFIG_NAME=\"hooks.maxfilesize\"\nreadonly NULLSHA=\"0000000000000000000000000000000000000000\"\nreadonly EXIT_SUCCESS=0\nreadonly EXIT_FAILURE=1\nreadonly DEFAULT_REPO_MAXSIZE_MB=\"1024\" \nreadonly CHECK_FLAG_ON=1\n\n\nstatus=\"$EXIT_SUCCESS\"\n\n# skip this hook entirely if shell check is not open\ncheck_flag=${PUSH_SIZE_CHECK_FLAG}\nif [[ $check_flag != $CHECK_FLAG_ON ]]; then\nexit $EXIT_SUCCESS\nfi\n\n\n#######################################\n# check the file max size limit\n#######################################\n\n# get maximum filesize (from repository-specific config)\nmaxsize_mb=\"${REPO_MAX_FILE_SIZE}\"\n\nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\necho \"failed to get ${CONFIG_NAME} from config\"\nexit \"$EXIT_FAILURE\"\nfi\n\npush_size=\"0\"\n# read lines from stdin (format: \" \\n\")\nwhile read oldref newref refname; do\n# skip branch deletions\nif [[ \"$newref\" == \"$NULLSHA\" ]]; then\n continue\nfi\n\n# find large objects\n# check all objects from $oldref (possible $NULLSHA) to $newref, but\n# skip all objects that have already been accepted (i.e. are referenced by\n# another branch or tag).\n\nnew_branch_flag=0\nif [[ \"$oldref\" == \"$NULLSHA\" ]]; then\n target=\"$newref\"\n new_branch_flag=1\n echo \"You are creating a new remote branch,openI will check all files in commit history to find oversize files\"\nelse\n target=\"${oldref}..${newref}\"\nfi\nmaxsize=`expr $maxsize_mb \\* 1048576` \n\n# find objects in this push_size\n# print like:\n# 08da8e2ab9ae4095bf94dd71ac913132b880b463 commit 214\n# 43e993b768ede5740e8c65de2ed6edec25053ea1 tree 185\n# 4476971d76569039df7569af1b8d03c288f6b193 blob 20167318 b0417e6593a1.zip\nfiles=\"$(git rev-list --objects \"$target\" | \\\n git cat-file $'--batch-check=%%(objectname) %%(objecttype) %%(objectsize) %%(rest)' | \\\n awk -F ' ' -v maxbytes=\"$maxsize\" 'BEGIN {totalIn=0} {if( $3 > maxbytes && $2 == \"blob\") { totalIn+=$3; print $4} else { totalIn+=$3}} END { printf (\"totalIn=\\t%%s\",totalIn)}' )\"\n \nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\n echo \"failed to check for large files in ref ${refname}\"\n continue\nfi\n\nIFS=$'\\n'\n# rewrite IFS to seperate line in $files\nfor file in $files; do\n # if don't unset IFS,temp_array=(${file}) will get error answer\n \n if [[ ${file} == totalIn=* ]]; then\n\tIFS=$'\\t'\n\ttemp_array=(${file})\n\tpush_size=${temp_array[1]}\n\tcontinue\n fi\n\tunset IFS\n if [[ \"$status\" == $EXIT_SUCCESS ]]; then\n\t\techo -e \"Error: Your push was rejected because it contains files larger than $(numfmt --to=iec \"$maxsize_mb\") Mb\"\n\t\techo \"help document -- https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n\t\techo \"oversize files:\"\n\t\tstatus=\"$EXIT_FAILURE\"\t\n fi\n echo -e \"\\033[31m- ${file}\\033[0m \"\ndone\n\nif [[ \"$status\" != $EXIT_SUCCESS ]]; then\n\texit \"$status\"\nfi\n\ndone\n\n#######################################\n# check the repo max size limit\n#######################################\nif [[ $push_size -eq \"0\" ]]; then\n\texit $EXIT_SUCCESS\nfi\n\n# if create new branch or tag,use count-objects -v to get pack size\nif [[ $new_branch_flag -eq 1 ]]; then\n size_kb=`git count-objects -v | grep 'size-pack' | sed 's/.*\\(size-pack:\\).//'`\n size_pack_kb=`git count-objects -v | grep 'size:' | sed 's/.*\\(size:\\).//'`\n\ttotal_kb=`expr $size_kb + $size_pack_kb`\n\tlet push_size=$total_kb*1024\nfi\n\nsizelimit_mb=\"${REPO_MAX_SIZE}\"\nlet sizelimit_b=$sizelimit_mb*1024*1024\n\n# repo size at here means the size of repo directory in server \nreposize_b=${REPO_CURRENT_SIZE}\n\ntotal=`expr $push_size + $reposize_b`\n\nif [ $total -gt $sizelimit_b ]; then\n echo \"Error: Your push was rejected because the repository size is large than $sizelimit_mb Mb\"\n echo \"see the help document--https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n exit $EXIT_FAILURE\nfi\n\n\nexit $EXIT_SUCCESS", setting.ScriptType), + fmt.Sprintf("#!/usr/bin/env %s\n\n\nset -o pipefail\n\nreadonly DEFAULT_FILE_MAXSIZE_MB=\"30\" \nreadonly CONFIG_NAME=\"hooks.maxfilesize\"\nreadonly NULLSHA=\"0000000000000000000000000000000000000000\"\nreadonly EXIT_SUCCESS=0\nreadonly EXIT_FAILURE=1\nreadonly DEFAULT_REPO_MAXSIZE_MB=\"1024\" \nreadonly CHECK_FLAG_ON=1\n\n\nPUSH_SIZE_CHECK_FLAG=1\nREPO_MAX_FILE_SIZE=1024\nREPO_MAX_SIZE=%d\nREPO_CURRENT_SIZE=$(du -sk \"${PWD}\" | cut -f1)\nsize_in_mb=$(($REPO_CURRENT_SIZE / 1024))\nstatus=\"$EXIT_SUCCESS\"\n\n# skip this hook entirely if shell check is not open\ncheck_flag=${PUSH_SIZE_CHECK_FLAG}\nif [[ $check_flag != $CHECK_FLAG_ON ]]; then\nexit $EXIT_SUCCESS\nfi\n\n\n#######################################\n# check the file max size limit\n#######################################\n\n# get maximum filesize (from repository-specific config)\nmaxsize_mb=\"${REPO_MAX_FILE_SIZE}\"\n\nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\necho \"failed to get ${CONFIG_NAME} from config\"\nexit \"$EXIT_FAILURE\"\nfi\n\npush_size=\"0\"\n# read lines from stdin (format: \" \\n\")\nwhile read oldref newref refname; do\n# skip branch deletions\nif [[ \"$newref\" == \"$NULLSHA\" ]]; then\n continue\nfi\n\n# find large objects\n# check all objects from $oldref (possible $NULLSHA) to $newref, but\n# skip all objects that have already been accepted (i.e. are referenced by\n# another branch or tag).\n\nnew_branch_flag=0\nif [[ \"$oldref\" == \"$NULLSHA\" ]]; then\n target=\"$newref\"\n new_branch_flag=1\n echo \"You are creating a new remote branch,openI will check all files in commit history to find oversize files\"\nelse\n target=\"${oldref}..${newref}\"\nfi\nmaxsize=`expr $maxsize_mb \\* 1048576` \n\n# find objects in this push_size\n# print like:\n# 08da8e2ab9ae4095bf94dd71ac913132b880b463 commit 214\n# 43e993b768ede5740e8c65de2ed6edec25053ea1 tree 185\n# 4476971d76569039df7569af1b8d03c288f6b193 blob 20167318 b0417e6593a1.zip\nfiles=\"$(git rev-list --objects \"$target\" | \\\n git cat-file $'--batch-check=%%(objectname) %%(objecttype) %%(objectsize) %%(rest)' | \\\n awk -F ' ' -v maxbytes=\"$maxsize\" 'BEGIN {totalIn=0} {if( $3 > maxbytes && $2 == \"blob\") { totalIn+=$3; print $4} else { totalIn+=$3}} END { printf (\"totalIn=\\t%%s\",totalIn)}' )\"\n \nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\n echo \"failed to check for large files in ref ${refname}\"\n continue\nfi\n\nIFS=$'\\n'\n# rewrite IFS to seperate line in $files\nfor file in $files; do\n # if don't unset IFS,temp_array=(${file}) will get error answer\n \n if [[ ${file} == totalIn=* ]]; then\n\tIFS=$'\\t'\n\ttemp_array=(${file})\n\tpush_size=${temp_array[1]}\n\tcontinue\n fi\n\tunset IFS\n if [[ \"$status\" == $EXIT_SUCCESS ]]; then\n\t\techo -e \"Error: Your push was rejected because it contains files larger than $(numfmt --to=iec \"$maxsize_mb\") Mb\"\n\t\techo \"help document -- ...\"\n\t\techo \"oversize files:\"\n\t\tstatus=\"$EXIT_FAILURE\"\t\n fi\n echo -e \"\\033[31m- ${file}\\033[0m \"\ndone\n\nif [[ \"$status\" != $EXIT_SUCCESS ]]; then\n\texit \"$status\"\nfi\n\ndone\n\n#######################################\n# check the repo max size limit\n#######################################\nif [[ $push_size -eq \"0\" ]]; then\n\texit $EXIT_SUCCESS\nfi\n\n# if create new branch or tag,use count-objects -v to get pack size\nif [[ $new_branch_flag -eq 1 ]]; then\n size_kb=`git count-objects -v | grep 'size-pack' | sed 's/.*\\(size-pack:\\).//'`\n size_pack_kb=`git count-objects -v | grep 'size:' | sed 's/.*\\(size:\\).//'`\n\ttotal_kb=`expr $size_kb + $size_pack_kb`\n\tlet push_size=$total_kb*1024\nfi\n\nsizelimit_mb=\"${REPO_MAX_SIZE}\"\nlet sizelimit_b=$sizelimit_mb*1024*1024\n\n# repo size at here means the size of repo directory in server \nreposize_b=${REPO_CURRENT_SIZE}\n\nlet total=$REPO_CURRENT_SIZE*1024\n\nif [ $total -gt $sizelimit_b ]; then\n echo \"Error: Your push was rejected because the repository size is large than $sizelimit_mb Mb\"\n echo \"Error: 推送被拒绝,文件或目录大小大于 $sizelimit_mb Mb\"\n exit $EXIT_FAILURE\nfi\n\n\nexit $EXIT_SUCCESS", setting.ScriptType, limitMb), fmt.Sprintf(""), fmt.Sprintf(""), } @@ -91,7 +95,9 @@ func UpdateRepoSizeLimitHook(ctx *context.APIContext) { hookName := "pre-receive" //results := make([]string, 0, 10) sizeLimitTpls := []string{ - fmt.Sprintf("#!/usr/bin/env %s\n\n\nset -o pipefail\n\nreadonly DEFAULT_FILE_MAXSIZE_MB=\"30\" \nreadonly CONFIG_NAME=\"hooks.maxfilesize\"\nreadonly NULLSHA=\"0000000000000000000000000000000000000000\"\nreadonly EXIT_SUCCESS=0\nreadonly EXIT_FAILURE=1\nreadonly DEFAULT_REPO_MAXSIZE_MB=\"1024\" \nreadonly CHECK_FLAG_ON=1\n\n\nstatus=\"$EXIT_SUCCESS\"\n\n# skip this hook entirely if shell check is not open\ncheck_flag=${PUSH_SIZE_CHECK_FLAG}\nif [[ $check_flag != $CHECK_FLAG_ON ]]; then\nexit $EXIT_SUCCESS\nfi\n\n\n#######################################\n# check the file max size limit\n#######################################\n\n# get maximum filesize (from repository-specific config)\nmaxsize_mb=\"${REPO_MAX_FILE_SIZE}\"\n\nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\necho \"failed to get ${CONFIG_NAME} from config\"\nexit \"$EXIT_FAILURE\"\nfi\n\npush_size=\"0\"\n# read lines from stdin (format: \" \\n\")\nwhile read oldref newref refname; do\n# skip branch deletions\nif [[ \"$newref\" == \"$NULLSHA\" ]]; then\n continue\nfi\n\n# find large objects\n# check all objects from $oldref (possible $NULLSHA) to $newref, but\n# skip all objects that have already been accepted (i.e. are referenced by\n# another branch or tag).\n\nnew_branch_flag=0\nif [[ \"$oldref\" == \"$NULLSHA\" ]]; then\n target=\"$newref\"\n new_branch_flag=1\n echo \"You are creating a new remote branch,openI will check all files in commit history to find oversize files\"\nelse\n target=\"${oldref}..${newref}\"\nfi\nmaxsize=`expr $maxsize_mb \\* 1048576` \n\n# find objects in this push_size\n# print like:\n# 08da8e2ab9ae4095bf94dd71ac913132b880b463 commit 214\n# 43e993b768ede5740e8c65de2ed6edec25053ea1 tree 185\n# 4476971d76569039df7569af1b8d03c288f6b193 blob 20167318 b0417e6593a1.zip\nfiles=\"$(git rev-list --objects \"$target\" | \\\n git cat-file $'--batch-check=%%(objectname) %%(objecttype) %%(objectsize) %%(rest)' | \\\n awk -F ' ' -v maxbytes=\"$maxsize\" 'BEGIN {totalIn=0} {if( $3 > maxbytes && $2 == \"blob\") { totalIn+=$3; print $4} else { totalIn+=$3}} END { printf (\"totalIn=\\t%%s\",totalIn)}' )\"\n \nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\n echo \"failed to check for large files in ref ${refname}\"\n continue\nfi\n\nIFS=$'\\n'\n# rewrite IFS to seperate line in $files\nfor file in $files; do\n # if don't unset IFS,temp_array=(${file}) will get error answer\n \n if [[ ${file} == totalIn=* ]]; then\n\tIFS=$'\\t'\n\ttemp_array=(${file})\n\tpush_size=${temp_array[1]}\n\tcontinue\n fi\n\tunset IFS\n if [[ \"$status\" == $EXIT_SUCCESS ]]; then\n\t\techo -e \"Error: Your push was rejected because it contains files larger than $(numfmt --to=iec \"$maxsize_mb\") Mb\"\n\t\techo \"help document -- https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n\t\techo \"oversize files:\"\n\t\tstatus=\"$EXIT_FAILURE\"\t\n fi\n echo -e \"\\033[31m- ${file}\\033[0m \"\ndone\n\nif [[ \"$status\" != $EXIT_SUCCESS ]]; then\n\texit \"$status\"\nfi\n\ndone\n\n#######################################\n# check the repo max size limit\n#######################################\nif [[ $push_size -eq \"0\" ]]; then\n\texit $EXIT_SUCCESS\nfi\n\n# if create new branch or tag,use count-objects -v to get pack size\nif [[ $new_branch_flag -eq 1 ]]; then\n size_kb=`git count-objects -v | grep 'size-pack' | sed 's/.*\\(size-pack:\\).//'`\n size_pack_kb=`git count-objects -v | grep 'size:' | sed 's/.*\\(size:\\).//'`\n\ttotal_kb=`expr $size_kb + $size_pack_kb`\n\tlet push_size=$total_kb*1024\nfi\n\nsizelimit_mb=\"${REPO_MAX_SIZE}\"\nlet sizelimit_b=$sizelimit_mb*1024*1024\n\n# repo size at here means the size of repo directory in server \nreposize_b=${REPO_CURRENT_SIZE}\n\ntotal=`expr $push_size + $reposize_b`\n\nif [ $total -gt $sizelimit_b ]; then\n echo \"Error: Your push was rejected because the repository size is large than $sizelimit_mb Mb\"\n echo \"see the help document--https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n exit $EXIT_FAILURE\nfi\n\n\nexit $EXIT_SUCCESS", setting.ScriptType, LimitSize), + //fmt.Sprintf("#!/usr/bin/env %s\n\n\nset -o pipefail\n\nreadonly DEFAULT_FILE_MAXSIZE_MB=\"30\" \nreadonly CONFIG_NAME=\"hooks.maxfilesize\"\nreadonly NULLSHA=\"0000000000000000000000000000000000000000\"\nreadonly EXIT_SUCCESS=0\nreadonly EXIT_FAILURE=1\nreadonly DEFAULT_REPO_MAXSIZE_MB=\"1024\" \nreadonly CHECK_FLAG_ON=1\n\n\nstatus=\"$EXIT_SUCCESS\"\n\n# skip this hook entirely if shell check is not open\ncheck_flag=${PUSH_SIZE_CHECK_FLAG}\nif [[ $check_flag != $CHECK_FLAG_ON ]]; then\nexit $EXIT_SUCCESS\nfi\n\n\n#######################################\n# check the file max size limit\n#######################################\n\n# get maximum filesize (from repository-specific config)\nmaxsize_mb=\"${REPO_MAX_FILE_SIZE}\"\n\nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\necho \"failed to get ${CONFIG_NAME} from config\"\nexit \"$EXIT_FAILURE\"\nfi\n\npush_size=\"0\"\n# read lines from stdin (format: \" \\n\")\nwhile read oldref newref refname; do\n# skip branch deletions\nif [[ \"$newref\" == \"$NULLSHA\" ]]; then\n continue\nfi\n\n# find large objects\n# check all objects from $oldref (possible $NULLSHA) to $newref, but\n# skip all objects that have already been accepted (i.e. are referenced by\n# another branch or tag).\n\nnew_branch_flag=0\nif [[ \"$oldref\" == \"$NULLSHA\" ]]; then\n target=\"$newref\"\n new_branch_flag=1\n echo \"You are creating a new remote branch,openI will check all files in commit history to find oversize files\"\nelse\n target=\"${oldref}..${newref}\"\nfi\nmaxsize=`expr $maxsize_mb \\* 1048576` \n\n# find objects in this push_size\n# print like:\n# 08da8e2ab9ae4095bf94dd71ac913132b880b463 commit 214\n# 43e993b768ede5740e8c65de2ed6edec25053ea1 tree 185\n# 4476971d76569039df7569af1b8d03c288f6b193 blob 20167318 b0417e6593a1.zip\nfiles=\"$(git rev-list --objects \"$target\" | \\\n git cat-file $'--batch-check=%%(objectname) %%(objecttype) %%(objectsize) %%(rest)' | \\\n awk -F ' ' -v maxbytes=\"$maxsize\" 'BEGIN {totalIn=0} {if( $3 > maxbytes && $2 == \"blob\") { totalIn+=$3; print $4} else { totalIn+=$3}} END { printf (\"totalIn=\\t%%s\",totalIn)}' )\"\n \nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\n echo \"failed to check for large files in ref ${refname}\"\n continue\nfi\n\nIFS=$'\\n'\n# rewrite IFS to seperate line in $files\nfor file in $files; do\n # if don't unset IFS,temp_array=(${file}) will get error answer\n \n if [[ ${file} == totalIn=* ]]; then\n\tIFS=$'\\t'\n\ttemp_array=(${file})\n\tpush_size=${temp_array[1]}\n\tcontinue\n fi\n\tunset IFS\n if [[ \"$status\" == $EXIT_SUCCESS ]]; then\n\t\techo -e \"Error: Your push was rejected because it contains files larger than $(numfmt --to=iec \"$maxsize_mb\") Mb\"\n\t\techo \"help document -- https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n\t\techo \"oversize files:\"\n\t\tstatus=\"$EXIT_FAILURE\"\t\n fi\n echo -e \"\\033[31m- ${file}\\033[0m \"\ndone\n\nif [[ \"$status\" != $EXIT_SUCCESS ]]; then\n\texit \"$status\"\nfi\n\ndone\n\n#######################################\n# check the repo max size limit\n#######################################\nif [[ $push_size -eq \"0\" ]]; then\n\texit $EXIT_SUCCESS\nfi\n\n# if create new branch or tag,use count-objects -v to get pack size\nif [[ $new_branch_flag -eq 1 ]]; then\n size_kb=`git count-objects -v | grep 'size-pack' | sed 's/.*\\(size-pack:\\).//'`\n size_pack_kb=`git count-objects -v | grep 'size:' | sed 's/.*\\(size:\\).//'`\n\ttotal_kb=`expr $size_kb + $size_pack_kb`\n\tlet push_size=$total_kb*1024\nfi\n\nsizelimit_mb=\"${REPO_MAX_SIZE}\"\nlet sizelimit_b=$sizelimit_mb*1024*1024\n\n# repo size at here means the size of repo directory in server \nreposize_b=${REPO_CURRENT_SIZE}\n\ntotal=`expr $push_size + $reposize_b`\n\nif [ $total -gt $sizelimit_b ]; then\n echo \"Error: Your push was rejected because the repository size is large than $sizelimit_mb Mb\"\n echo \"see the help document--https://openi.pcl.ac.cn/zeizei/OpenI_Learning/src/branch/master/docs/git/repository_capacity_help.md\"\n exit $EXIT_FAILURE\nfi\n\n\nexit $EXIT_SUCCESS", setting.ScriptType, LimitSize), + fmt.Sprintf("#!/usr/bin/env %s\n\n\nset -o pipefail\n\nreadonly DEFAULT_FILE_MAXSIZE_MB=\"30\" \nreadonly CONFIG_NAME=\"hooks.maxfilesize\"\nreadonly NULLSHA=\"0000000000000000000000000000000000000000\"\nreadonly EXIT_SUCCESS=0\nreadonly EXIT_FAILURE=1\nreadonly DEFAULT_REPO_MAXSIZE_MB=\"1024\" \nreadonly CHECK_FLAG_ON=1\n\n\nPUSH_SIZE_CHECK_FLAG=1\nREPO_MAX_FILE_SIZE=1024\nREPO_MAX_SIZE=%d\nREPO_CURRENT_SIZE=$(du -sk \"${PWD}\" | cut -f1)\nsize_in_mb=$(($REPO_CURRENT_SIZE / 1024))\nstatus=\"$EXIT_SUCCESS\"\n\n# skip this hook entirely if shell check is not open\ncheck_flag=${PUSH_SIZE_CHECK_FLAG}\nif [[ $check_flag != $CHECK_FLAG_ON ]]; then\nexit $EXIT_SUCCESS\nfi\n\n\n#######################################\n# check the file max size limit\n#######################################\n\n# get maximum filesize (from repository-specific config)\nmaxsize_mb=\"${REPO_MAX_FILE_SIZE}\"\n\nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\necho \"failed to get ${CONFIG_NAME} from config\"\nexit \"$EXIT_FAILURE\"\nfi\n\npush_size=\"0\"\n# read lines from stdin (format: \" \\n\")\nwhile read oldref newref refname; do\n# skip branch deletions\nif [[ \"$newref\" == \"$NULLSHA\" ]]; then\n continue\nfi\n\n# find large objects\n# check all objects from $oldref (possible $NULLSHA) to $newref, but\n# skip all objects that have already been accepted (i.e. are referenced by\n# another branch or tag).\n\nnew_branch_flag=0\nif [[ \"$oldref\" == \"$NULLSHA\" ]]; then\n target=\"$newref\"\n new_branch_flag=1\n echo \"You are creating a new remote branch,openI will check all files in commit history to find oversize files\"\nelse\n target=\"${oldref}..${newref}\"\nfi\nmaxsize=`expr $maxsize_mb \\* 1048576` \n\n# find objects in this push_size\n# print like:\n# 08da8e2ab9ae4095bf94dd71ac913132b880b463 commit 214\n# 43e993b768ede5740e8c65de2ed6edec25053ea1 tree 185\n# 4476971d76569039df7569af1b8d03c288f6b193 blob 20167318 b0417e6593a1.zip\nfiles=\"$(git rev-list --objects \"$target\" | \\\n git cat-file $'--batch-check=%%(objectname) %%(objecttype) %%(objectsize) %%(rest)' | \\\n awk -F ' ' -v maxbytes=\"$maxsize\" 'BEGIN {totalIn=0} {if( $3 > maxbytes && $2 == \"blob\") { totalIn+=$3; print $4} else { totalIn+=$3}} END { printf (\"totalIn=\\t%%s\",totalIn)}' )\"\n \nif [[ \"$?\" != $EXIT_SUCCESS ]]; then\n echo \"failed to check for large files in ref ${refname}\"\n continue\nfi\n\nIFS=$'\\n'\n# rewrite IFS to seperate line in $files\nfor file in $files; do\n # if don't unset IFS,temp_array=(${file}) will get error answer\n \n if [[ ${file} == totalIn=* ]]; then\n\tIFS=$'\\t'\n\ttemp_array=(${file})\n\tpush_size=${temp_array[1]}\n\tcontinue\n fi\n\tunset IFS\n if [[ \"$status\" == $EXIT_SUCCESS ]]; then\n\t\techo -e \"Error: Your push was rejected because it contains files larger than $(numfmt --to=iec \"$maxsize_mb\") Mb\"\n\t\techo \"help document -- ...\"\n\t\techo \"oversize files:\"\n\t\tstatus=\"$EXIT_FAILURE\"\t\n fi\n echo -e \"\\033[31m- ${file}\\033[0m \"\ndone\n\nif [[ \"$status\" != $EXIT_SUCCESS ]]; then\n\texit \"$status\"\nfi\n\ndone\n\n#######################################\n# check the repo max size limit\n#######################################\nif [[ $push_size -eq \"0\" ]]; then\n\texit $EXIT_SUCCESS\nfi\n\n# if create new branch or tag,use count-objects -v to get pack size\nif [[ $new_branch_flag -eq 1 ]]; then\n size_kb=`git count-objects -v | grep 'size-pack' | sed 's/.*\\(size-pack:\\).//'`\n size_pack_kb=`git count-objects -v | grep 'size:' | sed 's/.*\\(size:\\).//'`\n\ttotal_kb=`expr $size_kb + $size_pack_kb`\n\tlet push_size=$total_kb*1024\nfi\n\nsizelimit_mb=\"${REPO_MAX_SIZE}\"\nlet sizelimit_b=$sizelimit_mb*1024*1024\n\n# repo size at here means the size of repo directory in server \nreposize_b=${REPO_CURRENT_SIZE}\n\nlet total=$REPO_CURRENT_SIZE*1024\n\nif [ $total -gt $sizelimit_b ]; then\n echo \"Error: Your push was rejected because the repository size is large than $sizelimit_mb Mb\"\n echo \"Error: 推送被拒绝,文件或目录大小大于 $sizelimit_mb Mb\"\n exit $EXIT_FAILURE\nfi\n\n\nexit $EXIT_SUCCESS", setting.ScriptType, LimitSize), + fmt.Sprintf(""), fmt.Sprintf(""), } From 4c2cc2a65808cb19e5f8b81f96a41485703d84df Mon Sep 17 00:00:00 2001 From: xxq250 Date: Mon, 28 Apr 2025 10:47:27 +0800 Subject: [PATCH 03/22] =?UTF-8?q?fixed=20=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E4=BB=93=E5=BA=93=E5=A4=A7=E5=B0=8F=E9=99=90?= =?UTF-8?q?=E5=88=B6,bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/repo/size_limit.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/routers/hat/repo/size_limit.go b/routers/hat/repo/size_limit.go index 25fe365..235a6f1 100644 --- a/routers/hat/repo/size_limit.go +++ b/routers/hat/repo/size_limit.go @@ -172,11 +172,6 @@ func getAssignmentRepo(ctx *context.APIContext, id int64) { return } ctx.Repo.Repository = repo - - if !ctx.Repo.HasAccess() { - ctx.NotFound() - return - } } func getAssignmentRepoByName(ctx *context.APIContext, ownerName string, repoName string) { @@ -199,11 +194,6 @@ func getAssignmentRepoByName(ctx *context.APIContext, ownerName string, repoName return } ctx.Repo.Repository = repo - - if !ctx.Repo.HasAccess() { - ctx.NotFound() - return - } } // UpdateDelegateHooks creates all the hooks scripts for the repo From b015baf624db03758dc5f9416382d4a9578d047c Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 12 May 2025 13:12:59 +0800 Subject: [PATCH 04/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9Awiki=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=BA=93=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/hat.go | 1 + routers/hat/repo/wiki.go | 110 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/routers/hat/hat.go b/routers/hat/hat.go index e3870ff..d31b09e 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -159,6 +159,7 @@ func Routers() *web.Route { }, reqRepoReader(unit_model.TypeCode), context.ReferencesGitRepo(true)) m.Group("/wiki", func() { m.Get("/page_names", repo.ListWikiPageNames) + m.Get("/revisions/{pageName}/{revision}", repo.ShowRevision) }) m.Group("/readme", func() { m.Get("", repo.GetReadmeContents) diff --git a/routers/hat/repo/wiki.go b/routers/hat/repo/wiki.go index 1f0e013..1bf58a0 100644 --- a/routers/hat/repo/wiki.go +++ b/routers/hat/repo/wiki.go @@ -2,12 +2,15 @@ package repo import ( "net/http" + "net/url" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" util "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/convert" wiki_service "code.gitea.io/gitea/services/wiki" ) @@ -73,3 +76,110 @@ func ListWikiPageNames(ctx *context.APIContext) { ctx.SetTotalCountHeader(int64(len(entries))) ctx.JSON(http.StatusOK, pages) } + +func ShowRevision(ctx *context.APIContext) { + wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath()) + if err != nil { + if git.IsErrNotExist(err) || err.Error() == "no such file or directory" { + ctx.NotFound(err) + } else { + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) + } + return + } + + if wikiRepo != nil { + + defer wikiRepo.Close() + } + + // get requested pagename + pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName")) + if len(pageName) == 0 { + pageName = "Home" + } + + revision := ctx.PathParamRaw(":revision") + + commit, err := wikiRepo.GetCommit(revision) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetCommit", err) + return + } + + content, pageFilename := wikiContentsByName(ctx, commit, pageName, false) + + sidebarContent, _ := wikiContentsByName(ctx, commit, "_Sidebar", true) + + footerContent, _ := wikiContentsByName(ctx, commit, "_Footer", true) + + // get commit count - wiki revisions + commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename) + + // Get last change information. + lastCommit, err := wikiRepo.GetCommitByPath(pageFilename) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetCommitByPath", err) + return + } + + ctx.JSON(http.StatusOK, &api.WikiPage{ + WikiPageMetaData: convert.ToWikiPageMetaData(pageName, lastCommit, ctx.Repo.Repository), + ContentBase64: content, + CommitCount: commitsCount, + Sidebar: sidebarContent, + Footer: footerContent, + }) + +} + +// findEntryForFile finds the tree entry for a target filepath. +func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) { + entry, err := commit.GetTreeEntryByPath(target) + if err != nil { + return nil, err + } + if entry != nil { + return entry, nil + } + + // Then the unescaped, shortest alternative + var unescapedTarget string + if unescapedTarget, err = url.QueryUnescape(target); err != nil { + return nil, err + } + return commit.GetTreeEntryByPath(unescapedTarget) +} + +// wikiContentsByEntry returns the contents of the wiki page referenced by the +// given tree entry, encoded with base64. Writes to ctx if an error occurs. +func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string { + blob := entry.Blob() + if blob.Size() > setting.API.DefaultMaxBlobSize { + return "" + } + content, err := blob.GetBlobContentBase64() + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetBlobContentBase64", err) + return "" + } + return content +} + +// wikiContentsByName returns the contents of a wiki page, along with a boolean +// indicating whether the page exists. Writes to ctx if an error occurs. +func wikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName wiki_service.WebPath, isSidebarOrFooter bool) (string, string) { + gitFilename := wiki_service.WebPathToGitPath(wikiName) + entry, err := findEntryForFile(commit, gitFilename) + if err != nil { + if git.IsErrNotExist(err) { + if !isSidebarOrFooter { + ctx.NotFound() + } + } else { + ctx.ServerError("findEntryForFile", err) + } + return "", "" + } + return wikiContentsByEntry(ctx, entry), gitFilename +} From 8910972c65a54b0fb45db4ed0ecdbb93c2d3cae2 Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 23 Jun 2025 19:26:16 +0800 Subject: [PATCH 05/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E6=89=93=E5=BC=80pr=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- routers/hat/hat.go | 1 + routers/hat/repo/pull.go | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index d5bd2b1..9010c75 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8, by v1.21.0 " + Version = "v2.8.1, by v1.21.0 " Tags = "" MakeVersion = "" ) diff --git a/routers/hat/hat.go b/routers/hat/hat.go index d31b09e..c265181 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -188,6 +188,7 @@ func Routers() *web.Route { m.Get("", repo.ListPullRequestVersions) m.Get("/{versionId}/diff", context.ReferencesGitRepo(), repo.GetPullRequestVersionDiff) }) + m.Get("/change_status", repo.ChangePullRequestCloseStatus) }) }, mustAllowPulls, reqRepoReader(unit_model.TypeCode), context.ReferencesGitRepo()) m.Group("/releases", func() { diff --git a/routers/hat/repo/pull.go b/routers/hat/repo/pull.go index b02d826..0bf4644 100644 --- a/routers/hat/repo/pull.go +++ b/routers/hat/repo/pull.go @@ -35,6 +35,25 @@ import ( hat_pull_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/pull" ) +func ChangePullRequestCloseStatus(ctx *context.APIContext) { + pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if issues_model.IsErrPullRequestNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err) + } + return + } + changeStatus := ctx.FormBool("is_closed") + _, err = issues_model.ChangeIssueStatus(ctx, pr.Issue, ctx.Doer, changeStatus) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ChangeIssueStatus", err) + } + + ctx.Status(http.StatusNoContent) +} + func CreatePrVersion(ctx *context.APIContext) { form := web.GetForm(ctx).(*gitea_api.PullRequestPayload) hat_pull_service.AddToTaskQueue(&issues_model.PullRequest{ID: form.PullRequest.ID}, string(form.Action)) From c954b4799e6c681fd91336fbd94605142f994252 Mon Sep 17 00:00:00 2001 From: yystopf Date: Tue, 24 Jun 2025 14:52:46 +0800 Subject: [PATCH 06/22] =?UTF-8?q?fix=EF=BC=9A=E8=8E=B7=E5=8F=96=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E6=96=87=E4=BB=B6=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/repo/pull.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/routers/hat/repo/pull.go b/routers/hat/repo/pull.go index 0bf4644..315112c 100644 --- a/routers/hat/repo/pull.go +++ b/routers/hat/repo/pull.go @@ -51,6 +51,22 @@ func ChangePullRequestCloseStatus(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "ChangeIssueStatus", err) } + if err = pull_service.TestPatch(pr); err != nil { + ctx.Error(http.StatusInternalServerError, "TestPatch", err) + } + + // Update Commit Divergence + divergence, err := pull_service.GetDiverging(ctx, pr) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetDiverging", err) + } + pr.CommitsAhead = divergence.Ahead + pr.CommitsBehind = divergence.Behind + + if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files", "base_branch", "commits_ahead", "commits_behind"); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateColsIfNotMerged", err) + } + ctx.Status(http.StatusNoContent) } From dfe0ce8de0b23cf8d49b08eac731455624a21579 Mon Sep 17 00:00:00 2001 From: yystopf Date: Thu, 24 Jul 2025 08:30:51 +0800 Subject: [PATCH 07/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=90=9C=E7=B4=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/hat.go | 4 ++ routers/hat/repo/explore/code.go | 119 +++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 routers/hat/repo/explore/code.go diff --git a/routers/hat/hat.go b/routers/hat/hat.go index c265181..7a3d7dc 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -30,6 +30,7 @@ import ( "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/org" "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo" "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo/actions" + "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/repo/explore" "code.gitlink.org.cn/Gitlink/gitea_hat.git/routers/hat/user" "github.com/go-chi/cors" ) @@ -125,6 +126,9 @@ func Routers() *web.Route { m.Post("/update_hook_arm", bind(gitea_api.RepositoryPayload{}), repo.UpdateHookArm) m.Post("/update_repo_size_limit_hook", bind(gitea_api.RepositoryPayload{}), repo.UpdateRepoSizeLimitHook) m.Group("/repos", func() { + if setting.Indexer.RepoIndexerEnabled { + m.Get("/explore/code", explore.Code) + } m.Group("/{username}/{reponame}", func() { m.Combo("").Delete(reqToken(), reqOwner(), repo.Delete) m.Group("/actions", func() { diff --git a/routers/hat/repo/explore/code.go b/routers/hat/repo/explore/code.go new file mode 100644 index 0000000..b3f3089 --- /dev/null +++ b/routers/hat/repo/explore/code.go @@ -0,0 +1,119 @@ +package explore + +import ( + "fmt" + "math" + "net/http" + "strconv" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/context" + code_indexer "code.gitea.io/gitea/modules/indexer/code" + "code.gitea.io/gitea/modules/setting" +) + +func Code(ctx *context.APIContext) { + language := ctx.FormString("language") + keyword := ctx.FormString("keyword") + queryType := ctx.FormString("query_type") + isMatch := queryType == "match" + + page := ctx.FormInt("page") + if page <= 0 { + page = 1 + } + + var ( + repoIDs []int64 + err error + isAdmin bool + ) + + if ctx.Doer != nil { + isAdmin = ctx.Doer.IsAdmin + } + + if ctx.Doer == nil || !isAdmin { + repoIDs, err = repo_model.FindUserCodeAccessibleRepoIDs(ctx, ctx.Doer) + if err != nil { + ctx.Error(http.StatusInternalServerError, "FindUserCodeAccessibleRepoIDs", err) + return + } + } + + var ( + total int + searchResults []*code_indexer.Result + searchResultLanguages []*code_indexer.SearchResultLanguages + ) + + var codeIndexerUnavailable bool + var repoMaps map[int64]*repo_model.Repository + if (len(repoIDs) > 0) || isAdmin { + total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) + if err != nil { + if code_indexer.IsAvailable(ctx) { + ctx.Error(http.StatusInternalServerError, "SearchResults", err) + return + } + codeIndexerUnavailable = true + } else { + codeIndexerUnavailable = !code_indexer.IsAvailable(ctx) + } + + loadRepoIDs := make([]int64, 0, len(searchResults)) + for _, result := range searchResults { + var find bool + for _, id := range loadRepoIDs { + if id == result.RepoID { + find = true + break + } + } + if !find { + loadRepoIDs = append(loadRepoIDs, result.RepoID) + } + } + + repoMaps, err := repo_model.GetRepositoriesMapByIDs(loadRepoIDs) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetRepositoriesMapByIDs", err) + return + } + + repoMaps = repoMaps + + if len(loadRepoIDs) != len(repoMaps) { + // Remove deleted repos from search results + cleanedSearchResults := make([]*code_indexer.Result, 0, len(repoMaps)) + for _, sr := range searchResults { + if _, found := repoMaps[sr.RepoID]; found { + cleanedSearchResults = append(cleanedSearchResults, sr) + } + } + + searchResults = cleanedSearchResults + } + } + + pageCount := int(math.Ceil(float64(total) / float64(setting.UI.RepoSearchPagingNum))) + ctx.Resp.Header().Set("X-Page", strconv.Itoa(page)) + ctx.Resp.Header().Set("X-PerPage", strconv.Itoa(setting.UI.RepoSearchPagingNum)) + ctx.Resp.Header().Set("X-Total", strconv.FormatInt(int64(total), 10)) + ctx.Resp.Header().Set("X-PageCount", strconv.Itoa(pageCount)) + ctx.Resp.Header().Set("X-HasMore", strconv.FormatBool(page < pageCount)) + + ctx.SetLinkHeader(int(total), setting.UI.RepoSearchPagingNum) + ctx.Resp.Header().Set("X-Total-Count", fmt.Sprintf("%d", total)) + ctx.JSON(http.StatusOK, struct { + CodeIndexerUnavailable bool + RepoMaps map[int64]*repo_model.Repository + SearchResults []*code_indexer.Result + SearchResultLanguages []*code_indexer.SearchResultLanguages + }{ + CodeIndexerUnavailable: codeIndexerUnavailable, + RepoMaps: repoMaps, + SearchResults: searchResults, + SearchResultLanguages: searchResultLanguages, + }) +} From 37f8da019dbf5ed7246f7663db6c31849efed0df Mon Sep 17 00:00:00 2001 From: yystopf Date: Thu, 24 Jul 2025 17:14:27 +0800 Subject: [PATCH 08/22] =?UTF-8?q?=E6=9B=B4=E6=94=B9=EF=BC=9A=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=90=9C=E7=B4=A2=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 9010c75..d26d1fc 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8.1, by v1.21.0 " + Version = "v2.8.2, by v1.21.0 " Tags = "" MakeVersion = "" ) From 713af786c18ea72c021479319d7d4d8039d4aec6 Mon Sep 17 00:00:00 2001 From: xxq250 Date: Mon, 28 Jul 2025 15:10:55 +0800 Subject: [PATCH 09/22] add action run stop api --- routers/hat/hat.go | 1 + routers/hat/repo/actions/actions.go | 42 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/routers/hat/hat.go b/routers/hat/hat.go index 7a3d7dc..5cd0497 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -137,6 +137,7 @@ func Routers() *web.Route { m.Post("/enable", reqAdmin(), actions.EnableWorkflowFile) m.Post("/runs/{run}/jobs/{job}", context.ReferencesGitRepo(), bind(actions.ViewRequest{}), actions.ListJobs) m.Post("/runs/{run}/jobs/{job}/rerun", reqRepoWriter(unit_model.TypeActions), actions.Rerun) + m.Post("/runs/{run}/jobs/{job}/cancel", reqRepoWriter(unit_model.TypeActions), actions.Cancel) m.Get("/runs/{run}/jobs/{job}/logs", actions.Logs) m.Post("/runs/{run}/rerun", reqRepoWriter(unit_model.TypeActions), actions.Rerun) m.Post("/runs", context.ReferencesGitRepo(), reqRepoWriter(unit_model.TypeActions), actions.Run) diff --git a/routers/hat/repo/actions/actions.go b/routers/hat/repo/actions/actions.go index 32bf949..2a5695b 100644 --- a/routers/hat/repo/actions/actions.go +++ b/routers/hat/repo/actions/actions.go @@ -2,6 +2,7 @@ package actions import ( "bytes" + "code.gitea.io/gitea/modules/timeutil" "errors" "fmt" "net/http" @@ -684,3 +685,44 @@ func disableOrEnableWorkflowFile(ctx *context.APIContext, isEnable bool) { ctx.Status(http.StatusNoContent) } + +func Cancel(ctx *context.APIContext) { + runIndex := ctx.ParamsInt64("run") + + _, jobs := getRunJobs(ctx, runIndex, -1) + if ctx.Written() { + return + } + + if err := db.WithTx(ctx, func(ctx context.Context) error { + for _, job := range jobs { + status := job.Status + if status.IsDone() { + continue + } + if job.TaskID == 0 { + job.Status = actions_model.StatusCancelled + job.Stopped = timeutil.TimeStampNow() + n, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped") + if err != nil { + return err + } + if n == 0 { + return fmt.Errorf("job has changed, try again") + } + continue + } + if err := actions_model.StopTask(ctx, job.TaskID, actions_model.StatusCancelled); err != nil { + return err + } + } + return nil + }); err != nil { + ctx.Error(http.StatusInternalServerError, "runs Cancel", err.Error()) + return + } + + actions_service.CreateCommitStatus(ctx, jobs...) + + ctx.JSON(http.StatusOK, struct{}{}) +} From 31c339fd1d42f518b89d74d1a4f91d80f8abbf91 Mon Sep 17 00:00:00 2001 From: xxq250 Date: Mon, 28 Jul 2025 15:41:45 +0800 Subject: [PATCH 10/22] add action run stop api --- routers/hat/repo/actions/actions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/hat/repo/actions/actions.go b/routers/hat/repo/actions/actions.go index 2a5695b..abb7905 100644 --- a/routers/hat/repo/actions/actions.go +++ b/routers/hat/repo/actions/actions.go @@ -694,7 +694,7 @@ func Cancel(ctx *context.APIContext) { return } - if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := db.WithTx(ctx, func(ctx stdCtx.Context) error { for _, job := range jobs { status := job.Status if status.IsDone() { From 6f0092b14cff7b0d74286d466e74d049d4a88817 Mon Sep 17 00:00:00 2001 From: xxq250 Date: Mon, 28 Jul 2025 15:54:07 +0800 Subject: [PATCH 11/22] fixed action run stop api --- routers/hat/hat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/hat/hat.go b/routers/hat/hat.go index 5cd0497..3ac4239 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -137,9 +137,9 @@ func Routers() *web.Route { m.Post("/enable", reqAdmin(), actions.EnableWorkflowFile) m.Post("/runs/{run}/jobs/{job}", context.ReferencesGitRepo(), bind(actions.ViewRequest{}), actions.ListJobs) m.Post("/runs/{run}/jobs/{job}/rerun", reqRepoWriter(unit_model.TypeActions), actions.Rerun) - m.Post("/runs/{run}/jobs/{job}/cancel", reqRepoWriter(unit_model.TypeActions), actions.Cancel) m.Get("/runs/{run}/jobs/{job}/logs", actions.Logs) m.Post("/runs/{run}/rerun", reqRepoWriter(unit_model.TypeActions), actions.Rerun) + m.Post("/runs/{run}/cancel", reqRepoWriter(unit_model.TypeActions), actions.Cancel) m.Post("/runs", context.ReferencesGitRepo(), reqRepoWriter(unit_model.TypeActions), actions.Run) }, reqRepoReader(unit_model.TypeActions)) m.Post("/transfer", reqOwner(), bind(gitea_api.TransferRepoOption{}), repo.Transfer) From 7d71cba643915620165a6238156ae7c00ea33913 Mon Sep 17 00:00:00 2001 From: xxq250 Date: Tue, 29 Jul 2025 11:24:17 +0800 Subject: [PATCH 12/22] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=97=A5=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index d26d1fc..8b2c239 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8.2, by v1.21.0 " + Version = "v2.8.3, by v1.21.0, 20250729 " Tags = "" MakeVersion = "" ) From 902314a41376132797cc9aeaf3f5d94c6c3e084a Mon Sep 17 00:00:00 2001 From: xxq250 Date: Tue, 12 Aug 2025 10:44:56 +0800 Subject: [PATCH 13/22] =?UTF-8?q?=20=E9=BB=98=E8=AE=A4=E5=BC=80=E5=90=AF?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E8=AE=BF=E9=97=AE=EF=BC=8C=E4=B8=8E=E5=8F=81?= =?UTF-8?q?=E6=95=B0RequireSignInView=E6=97=A0=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/web/web.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/routers/hat/web/web.go b/routers/hat/web/web.go index d25977a..24ad47f 100644 --- a/routers/hat/web/web.go +++ b/routers/hat/web/web.go @@ -282,8 +282,10 @@ func registerRoutes(m *web.Route) { reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true}) // TODO: rename them to "optSignIn", which means that the "sign-in" could be optional, depends on the VerifyOptions (RequireSignInView) - ignSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView}) - ignExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView}) + //ignSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView}) + ignSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) + //ignExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView}) + ignExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) validation.AddBindingRules() From 868d41e74945d0f636a0d77c387a65ea196a3be7 Mon Sep 17 00:00:00 2001 From: xxq250 Date: Tue, 12 Aug 2025 10:52:35 +0800 Subject: [PATCH 14/22] =?UTF-8?q?=20=E9=BB=98=E8=AE=A4=E5=BC=80=E5=90=AF?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E8=AE=BF=E9=97=AE=EF=BC=8C=E4=B8=8E=E5=8F=81?= =?UTF-8?q?=E6=95=B0RequireSignInView=E6=97=A0=E5=85=B3=EF=BC=8C=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 8b2c239..b76d971 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8.3, by v1.21.0, 20250729 " + Version = "v2.8.4, by v1.21.0, 20250812 " Tags = "" MakeVersion = "" ) From db3a241c92a5436b1182779f0e44d403207a0616 Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 1 Sep 2025 17:34:43 +0800 Subject: [PATCH 15/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9Adetail=5Fstatu?= =?UTF-8?q?s=E5=9C=A8runs=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/repo/actions/actions.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/routers/hat/repo/actions/actions.go b/routers/hat/repo/actions/actions.go index abb7905..c9de5a9 100644 --- a/routers/hat/repo/actions/actions.go +++ b/routers/hat/repo/actions/actions.go @@ -132,6 +132,11 @@ type Workflow struct { ErrMsg string } +type DetailStatus struct { + RunID int64 + Status []actions_model.Status +} + type ResponseAction struct { Workflows []Workflow CurWorkflow string @@ -144,6 +149,7 @@ type ResponseAction struct { Runs actions_model.RunList Actors []*user.User StatusInfoList []actions_model.StatusInfo + DetailStatusList []DetailStatus } func ListActions(ctx *context.APIContext) { @@ -262,7 +268,20 @@ func ListActions(ctx *context.APIContext) { } for _, run := range runs { + var detailStatuses []actions_model.Status + jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "actions_model.GetRunJobsByRunID", err.Error()) + return + } + for _, j := range jobs { + detailStatuses = append(detailStatuses, j.Status) + } run.Repo = ctx.Repo.Repository + responseAction.DetailStatusList = append(responseAction.DetailStatusList, DetailStatus{ + RunID: run.ID, + Status: detailStatuses, + }) } if err := runs.LoadTriggerUser(ctx); err != nil { From 4c65b6777ad920283765a1fad90bf7b4eb82978d Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 1 Sep 2025 17:38:59 +0800 Subject: [PATCH 16/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9Adetail=5Fstatu?= =?UTF-8?q?s=E5=9C=A8runs=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index b76d971..e0dda4d 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8.4, by v1.21.0, 20250812 " + Version = "v2.8.5, by v1.21.0, 20250901 " Tags = "" MakeVersion = "" ) From f091079312341339143c253990aa062c71ee43b3 Mon Sep 17 00:00:00 2001 From: yystopf Date: Wed, 3 Sep 2025 09:15:06 +0800 Subject: [PATCH 17/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E4=BB=93?= =?UTF-8?q?=E5=BA=93=E7=BB=84=E7=BB=87=E5=86=85=E4=BB=A3=E7=A0=81=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- routers/hat/hat.go | 2 + routers/hat/repo/search.go | 68 +++++++++++++++++++++++++++ routers/hat/user/user.go | 96 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 routers/hat/repo/search.go diff --git a/main.go b/main.go index e0dda4d..c1ce1d9 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8.5, by v1.21.0, 20250901 " + Version = "v2.8.5, by v1.21.0, 20250903 " Tags = "" MakeVersion = "" ) diff --git a/routers/hat/hat.go b/routers/hat/hat.go index 3ac4239..ca9a3b5 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -130,6 +130,7 @@ func Routers() *web.Route { m.Get("/explore/code", explore.Code) } m.Group("/{username}/{reponame}", func() { + m.Get("/code", context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode), repo.Code) m.Combo("").Delete(reqToken(), reqOwner(), repo.Delete) m.Group("/actions", func() { m.Get("", context.ReferencesGitRepo(), actions.ListActions) @@ -245,6 +246,7 @@ func Routers() *web.Route { if setting.Service.EnableUserHeatmap { m.Get("/heatmap", user.GetUserHeatmapData) } + m.Get("/code", user.Code) }, context_service.UserAssignmentAPI()) }) m.Post("/orgs", reqToken(), bind(gitea_api.CreateOrgOption{}), org.Create) diff --git a/routers/hat/repo/search.go b/routers/hat/repo/search.go new file mode 100644 index 0000000..c2bb846 --- /dev/null +++ b/routers/hat/repo/search.go @@ -0,0 +1,68 @@ +package repo + +import ( + "fmt" + "math" + "net/http" + "strconv" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/context" + code_indexer "code.gitea.io/gitea/modules/indexer/code" + + "code.gitea.io/gitea/modules/setting" +) + +func Code(ctx *context.APIContext) { + language := ctx.FormString("language") + keyword := ctx.FormString("keyword") + queryType := ctx.FormString("query_type") + isMatch := queryType == "match" + + page := ctx.FormInt("page") + if page <= 0 { + page = 1 + } + + var ( + total int + searchResults []*code_indexer.Result + searchResultLanguages []*code_indexer.SearchResultLanguages + ) + + var codeIndexerUnavailable bool + var repoMaps map[int64]*repo_model.Repository + + total, searchResults, searchResultLanguages, err := code_indexer.PerformSearch(ctx, []int64{ctx.Repo.Repository.ID}, + language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) + if err != nil { + if code_indexer.IsAvailable(ctx) { + ctx.Error(http.StatusInternalServerError, "SearchResults", err) + return + } + codeIndexerUnavailable = true + } else { + codeIndexerUnavailable = !code_indexer.IsAvailable(ctx) + } + + pageCount := int(math.Ceil(float64(total) / float64(setting.UI.RepoSearchPagingNum))) + ctx.Resp.Header().Set("X-Page", strconv.Itoa(page)) + ctx.Resp.Header().Set("X-PerPage", strconv.Itoa(setting.UI.RepoSearchPagingNum)) + ctx.Resp.Header().Set("X-Total", strconv.FormatInt(int64(total), 10)) + ctx.Resp.Header().Set("X-PageCount", strconv.Itoa(pageCount)) + ctx.Resp.Header().Set("X-HasMore", strconv.FormatBool(page < pageCount)) + + ctx.SetLinkHeader(int(total), setting.UI.RepoSearchPagingNum) + ctx.Resp.Header().Set("X-Total-Count", fmt.Sprintf("%d", total)) + ctx.JSON(http.StatusOK, struct { + CodeIndexerUnavailable bool + RepoMaps map[int64]*repo_model.Repository + SearchResults []*code_indexer.Result + SearchResultLanguages []*code_indexer.SearchResultLanguages + }{ + CodeIndexerUnavailable: codeIndexerUnavailable, + RepoMaps: repoMaps, + SearchResults: searchResults, + SearchResultLanguages: searchResultLanguages, + }) +} diff --git a/routers/hat/user/user.go b/routers/hat/user/user.go index b933c91..9f9b558 100644 --- a/routers/hat/user/user.go +++ b/routers/hat/user/user.go @@ -1,10 +1,18 @@ package user import ( + "fmt" + "math" "net/http" + "strconv" + + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/context" + code_indexer "code.gitea.io/gitea/modules/indexer/code" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + hat_activities_models "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/activities" ) @@ -25,3 +33,91 @@ func GetUserHeatmapData(ctx *context.APIContext) { ctx.JSON(http.StatusOK, heatmap) } + +func Code(ctx *context.APIContext) { + language := ctx.FormString("language") + keyword := ctx.FormString("keyword") + queryType := ctx.FormString("query_type") + isMatch := queryType == "match" + + page := ctx.FormInt("page") + if page <= 0 { + page = 1 + } + + var ( + repoIDs []int64 + err error + ) + + repoIDs, err = repo_model.FindUserCodeAccessibleOwnerRepoIDs(ctx, ctx.ContextUser.ID, ctx.Doer) + if err != nil { + ctx.Error(http.StatusInternalServerError, "FindUserCodeAccessibleOwnerRepoIDs", err) + return + } + + var ( + total int + searchResults []*code_indexer.Result + searchResultLanguages []*code_indexer.SearchResultLanguages + ) + + var codeIndexerUnavailable bool + var repoMaps map[int64]*repo_model.Repository + + if len(repoIDs) > 0 { + total, searchResults, searchResultLanguages, err = code_indexer.PerformSearch(ctx, repoIDs, language, keyword, page, setting.UI.RepoSearchPagingNum, isMatch) + if err != nil { + if code_indexer.IsAvailable(ctx) { + ctx.Error(http.StatusInternalServerError, "SearchResults", err) + return + } + codeIndexerUnavailable = true + } else { + codeIndexerUnavailable = !code_indexer.IsAvailable(ctx) + } + + loadRepoIDs := make([]int64, 0, len(searchResults)) + for _, result := range searchResults { + var find bool + for _, id := range loadRepoIDs { + if id == result.RepoID { + find = true + break + } + } + if !find { + loadRepoIDs = append(loadRepoIDs, result.RepoID) + } + } + + repoMaps, err := repo_model.GetRepositoriesMapByIDs(loadRepoIDs) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetRepositoriesMapByIDs", err) + return + } + + repoMaps = repoMaps + } + + pageCount := int(math.Ceil(float64(total) / float64(setting.UI.RepoSearchPagingNum))) + ctx.Resp.Header().Set("X-Page", strconv.Itoa(page)) + ctx.Resp.Header().Set("X-PerPage", strconv.Itoa(setting.UI.RepoSearchPagingNum)) + ctx.Resp.Header().Set("X-Total", strconv.FormatInt(int64(total), 10)) + ctx.Resp.Header().Set("X-PageCount", strconv.Itoa(pageCount)) + ctx.Resp.Header().Set("X-HasMore", strconv.FormatBool(page < pageCount)) + + ctx.SetLinkHeader(int(total), setting.UI.RepoSearchPagingNum) + ctx.Resp.Header().Set("X-Total-Count", fmt.Sprintf("%d", total)) + ctx.JSON(http.StatusOK, struct { + CodeIndexerUnavailable bool + RepoMaps map[int64]*repo_model.Repository + SearchResults []*code_indexer.Result + SearchResultLanguages []*code_indexer.SearchResultLanguages + }{ + CodeIndexerUnavailable: codeIndexerUnavailable, + RepoMaps: repoMaps, + SearchResults: searchResults, + SearchResultLanguages: searchResultLanguages, + }) +} From 684b2733a02d64685967854d66e4af8668ff0c3f Mon Sep 17 00:00:00 2001 From: yystopf Date: Tue, 11 Nov 2025 14:01:23 +0800 Subject: [PATCH 18/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=80=89?= =?UTF-8?q?=E5=BA=AB=E5=AF=86=E9=88=85=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/hat.go | 1 + routers/hat/repo/actions/actions.go | 36 ++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/routers/hat/hat.go b/routers/hat/hat.go index ca9a3b5..55218ef 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -133,6 +133,7 @@ func Routers() *web.Route { m.Get("/code", context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode), repo.Code) m.Combo("").Delete(reqToken(), reqOwner(), repo.Delete) m.Group("/actions", func() { + m.Get("/secrets", context.ReferencesGitRepo(), actions.ListActionsSecrets) m.Get("", context.ReferencesGitRepo(), actions.ListActions) m.Post("/disable", reqAdmin(), actions.DisableWorkflowFile) m.Post("/enable", reqAdmin(), actions.EnableWorkflowFile) diff --git a/routers/hat/repo/actions/actions.go b/routers/hat/repo/actions/actions.go index c9de5a9..b44e472 100644 --- a/routers/hat/repo/actions/actions.go +++ b/routers/hat/repo/actions/actions.go @@ -2,18 +2,20 @@ package actions import ( "bytes" - "code.gitea.io/gitea/modules/timeutil" "errors" "fmt" "net/http" "strings" "time" + "code.gitea.io/gitea/modules/timeutil" + stdCtx "context" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/actions" @@ -22,8 +24,10 @@ import ( "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/web/repo" actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/convert" @@ -33,6 +37,36 @@ import ( "xorm.io/builder" ) +func ListActionsSecrets(ctx *context.APIContext) { + opts := &secret_model.FindSecretsOptions{ + RepoID: ctx.Repo.Repository.ID, + ListOptions: utils.GetListOptions(ctx), + } + + count, err := secret_model.CountSecrets(ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + secrets, err := secret_model.FindSecrets(ctx, *opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + apiSecrets := make([]*api.Secret, len(secrets)) + for k, v := range secrets { + apiSecrets[k] = &api.Secret{ + Name: v.Name, + Created: v.CreatedUnix.AsTime(), + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, apiSecrets) +} + func Run(ctx *context.APIContext) { workflow := ctx.FormString("workflow") ref := ctx.FormString("ref") From 1b7a0ffb369b82011c303cdd585e917571290542 Mon Sep 17 00:00:00 2001 From: yystopf Date: Tue, 11 Nov 2025 14:10:00 +0800 Subject: [PATCH 19/22] =?UTF-8?q?=E6=9B=B4=E6=94=B9=EF=BC=9A=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index c1ce1d9..0a99b0e 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8.5, by v1.21.0, 20250903 " + Version = "v2.8.6, by v1.21.0, 20251111 " Tags = "" MakeVersion = "" ) From 4b32e70cdbd1d3fb5bc440052fc724b68c7831ea Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 1 Dec 2025 19:29:09 +0800 Subject: [PATCH 20/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9Asecret?= =?UTF-8?q?=E5=92=8Cvariable=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- models/actions/secret.go | 59 +++++++ models/actions/variables.go | 65 +++++++ modules/structs/secret.go | 34 ++++ modules/structs/variable.go | 36 ++++ modules/util/util.go | 12 ++ routers/hat/hat.go | 33 +++- routers/hat/org/action.go | 261 ++++++++++++++++++++++++++++ routers/hat/repo/actions/actions.go | 223 +++++++++++++++++++++++- services/actions/secrets.go | 105 +++++++++++ services/actions/variables.go | 69 ++++++++ 11 files changed, 893 insertions(+), 6 deletions(-) create mode 100644 models/actions/secret.go create mode 100644 models/actions/variables.go create mode 100644 modules/structs/secret.go create mode 100644 modules/structs/variable.go create mode 100644 modules/util/util.go create mode 100644 routers/hat/org/action.go create mode 100644 services/actions/secrets.go create mode 100644 services/actions/variables.go diff --git a/main.go b/main.go index 0a99b0e..2a95a1a 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8.6, by v1.21.0, 20251111 " + Version = "v2.8.7, by v1.21.0, 20251201 " Tags = "" MakeVersion = "" ) diff --git a/models/actions/secret.go b/models/actions/secret.go new file mode 100644 index 0000000..737da74 --- /dev/null +++ b/models/actions/secret.go @@ -0,0 +1,59 @@ +package actions + +import ( + "context" + "strings" + + "code.gitea.io/gitea/models/db" + secret_model "code.gitea.io/gitea/models/secret" + "xorm.io/builder" +) + +type FindSecretsOptions struct { + db.ListOptions + OwnerID int64 + RepoID int64 + SecretID int64 + Name string +} + +func (opts *FindSecretsOptions) toConds() builder.Cond { + cond := builder.NewCond() + if opts.OwnerID > 0 { + cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) + } + if opts.RepoID > 0 { + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) + } + if opts.SecretID != 0 { + cond = cond.And(builder.Eq{"id": opts.SecretID}) + } + if opts.Name != "" { + cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)}) + } + + return cond +} + +func FindSecrets(ctx context.Context, opts FindSecretsOptions) ([]*secret_model.Secret, error) { + var secrets []*secret_model.Secret + sess := db.GetEngine(ctx) + if opts.PageSize != 0 { + sess = db.SetSessionPagination(sess, &opts.ListOptions) + } + return secrets, sess. + Where(opts.toConds()). + OrderBy("created_unix desc"). + Find(&secrets) +} + +func CountSecrets(ctx context.Context, opts *FindSecretsOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.toConds()).Count(new(secret_model.Secret)) +} + +func DeleteSecret(ctx context.Context, secret *secret_model.Secret) error { + if _, err := db.DeleteByID(ctx, secret.ID, secret); err != nil { + return err + } + return nil +} diff --git a/models/actions/variables.go b/models/actions/variables.go new file mode 100644 index 0000000..ebe340a --- /dev/null +++ b/models/actions/variables.go @@ -0,0 +1,65 @@ +package actions + +import ( + "context" + "strings" + + gitea_actions "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + "xorm.io/builder" +) + +type FindVariablesOpts struct { + db.ListOptions + IDs []int64 + RepoID int64 + OwnerID int64 // it will be ignored if RepoID is set + Name string +} + +func (opts FindVariablesOpts) ToConds() builder.Cond { + cond := builder.NewCond() + if len(opts.IDs) > 0 { + if len(opts.IDs) == 1 { + cond = cond.And(builder.Eq{"id": opts.IDs[0]}) + } else { + cond = cond.And(builder.In("id", opts.IDs)) + } + } + + // Since we now support instance-level variables, + // there is no need to check for null values for `owner_id` and `repo_id` + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) + if opts.RepoID != 0 { // if RepoID is set + // ignore OwnerID and treat it as 0 + cond = cond.And(builder.Eq{"owner_id": 0}) + } else { + cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) + } + + if opts.Name != "" { + cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)}) + } + return cond +} + +func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*gitea_actions.ActionVariable, error) { + var variables []*gitea_actions.ActionVariable + sess := db.GetEngine(ctx) + if opts.PageSize != 0 { + sess = db.SetSessionPagination(sess, &opts.ListOptions) + } + return variables, sess.Where(opts.ToConds()).OrderBy("created_unix desc").Find(&variables) +} + +func CountVariables(ctx context.Context, opts FindVariablesOpts) (int64, error) { + sess := db.GetEngine(ctx) + return sess.Where(opts.ToConds()).Count(new(gitea_actions.ActionVariable)) +} + +func DeleteVariable(ctx context.Context, variable *gitea_actions.ActionVariable) error { + if _, err := db.DeleteByID(ctx, variable.ID, variable); err != nil { + return err + } + return nil +} diff --git a/modules/structs/secret.go b/modules/structs/secret.go new file mode 100644 index 0000000..c7b38d1 --- /dev/null +++ b/modules/structs/secret.go @@ -0,0 +1,34 @@ +package structs + +import "time" + +// CreateVariableOption the option when creating variable +// swagger:model +type CreateSecretOption struct { + // Value of the variable to create + // + // required: true + Data string `json:"data" binding:"Required"` +} + +// UpdateVariableOption the option when updating variable +// swagger:model +type UpdateSecretOption struct { + // New name for the variable. If the field is empty, the variable name won't be updated. + Name string `json:"name"` + // Value of the variable to update + // + // required: true + Data string `json:"data" binding:"Required"` + + // Description of the variable to update +} + +// Secret represents a secret +// swagger:model +type Secret struct { + // the secret's name + Name string `json:"name"` + // swagger:strfmt date-time + Created time.Time `json:"created_at"` +} diff --git a/modules/structs/variable.go b/modules/structs/variable.go new file mode 100644 index 0000000..034523a --- /dev/null +++ b/modules/structs/variable.go @@ -0,0 +1,36 @@ +package structs + +// CreateVariableOption the option when creating variable +// swagger:model +type CreateVariableOption struct { + // Value of the variable to create + // + // required: true + Value string `json:"value" binding:"Required"` +} + +// UpdateVariableOption the option when updating variable +// swagger:model +type UpdateVariableOption struct { + // New name for the variable. If the field is empty, the variable name won't be updated. + Name string `json:"name"` + // Value of the variable to update + // + // required: true + Value string `json:"value" binding:"Required"` + + // Description of the variable to update +} + +// ActionVariable return value of the query API +// swagger:model +type ActionVariable struct { + // the owner to which the variable belongs + OwnerID int64 `json:"owner_id"` + // the repository to which the variable belongs + RepoID int64 `json:"repo_id"` + // the name of the variable + Name string `json:"name"` + // the value of the variable + Data string `json:"data"` +} diff --git a/modules/util/util.go b/modules/util/util.go new file mode 100644 index 0000000..c301b67 --- /dev/null +++ b/modules/util/util.go @@ -0,0 +1,12 @@ +package util + +import "strings" + +func ReserveLineBreakForTextarea(input string) string { + // Since the content is from a form which is a textarea, the line endings are \r\n. + // It's a standard behavior of HTML. + // But we want to store them as \n like what GitHub does. + // And users are unlikely to really need to keep the \r. + // Other than this, we should respect the original content, even leading or trailing spaces. + return strings.ReplaceAll(input, "\r\n", "\n") +} diff --git a/routers/hat/hat.go b/routers/hat/hat.go index 55218ef..91611ba 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -133,7 +133,21 @@ func Routers() *web.Route { m.Get("/code", context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode), repo.Code) m.Combo("").Delete(reqToken(), reqOwner(), repo.Delete) m.Group("/actions", func() { - m.Get("/secrets", context.ReferencesGitRepo(), actions.ListActionsSecrets) + m.Group("/secrets", func() { + m.Get("", reqToken(), reqAdmin(), actions.ListActionsSecrets) + m.Combo("/{secretname}"). + Delete(reqToken(), reqAdmin(), actions.DeleteActionsSecret). + Post(reqToken(), reqAdmin(), bind(hat_api.CreateSecretOption{}), actions.CreateActionsSecret). + Put(reqToken(), reqAdmin(), bind(hat_api.UpdateSecretOption{}), actions.UpdateActionsSecret) + }) + m.Group("/variables", func() { + m.Get("", reqToken(), reqAdmin(), actions.ListVariables) + m.Combo("/{variablename}"). + Get(reqToken(), reqAdmin(), actions.GetVariable). + Delete(reqToken(), reqAdmin(), actions.DeleteVariable). + Post(reqToken(), reqAdmin(), bind(hat_api.CreateVariableOption{}), actions.CreateVariable). + Put(reqToken(), reqAdmin(), bind(hat_api.UpdateVariableOption{}), actions.UpdateVariable) + }) m.Get("", context.ReferencesGitRepo(), actions.ListActions) m.Post("/disable", reqAdmin(), actions.DisableWorkflowFile) m.Post("/enable", reqAdmin(), actions.EnableWorkflowFile) @@ -253,6 +267,23 @@ func Routers() *web.Route { m.Post("/orgs", reqToken(), bind(gitea_api.CreateOrgOption{}), org.Create) m.Group("/orgs/{org}", func() { m.Combo("").Patch(reqToken(), reqOrgOwnership(), bind(hat_api.EditOrgOption{}), org.Edit) + m.Group("/actions", func() { + m.Group("/secrets", func() { + m.Get("", reqToken(), reqAdmin(), org.ListActionsSecrets) + m.Combo("/{secretname}"). + Delete(reqToken(), reqAdmin(), org.DeleteActionsSecret). + Post(reqToken(), reqAdmin(), bind(hat_api.CreateSecretOption{}), org.CreateActionsSecret). + Put(reqToken(), reqAdmin(), bind(hat_api.UpdateSecretOption{}), org.UpdateActionsSecret) + }) + m.Group("/variables", func() { + m.Get("", reqToken(), reqOrgOwnership(), org.ListVariables) + m.Combo("/{variablename}"). + Get(reqToken(), reqOrgOwnership(), org.GetVariable). + Delete(reqToken(), reqOrgOwnership(), org.DeleteVariable). + Post(reqToken(), reqOrgOwnership(), bind(hat_api.CreateVariableOption{}), org.CreateVariable). + Put(reqToken(), reqOrgOwnership(), bind(hat_api.UpdateVariableOption{}), org.UpdateVariable) + }) + }) }, orgAssignment(true)) m.Group("/teams/{teamid}", func() { diff --git a/routers/hat/org/action.go b/routers/hat/org/action.go new file mode 100644 index 0000000..7e06361 --- /dev/null +++ b/routers/hat/org/action.go @@ -0,0 +1,261 @@ +package org + +import ( + "errors" + "net/http" + + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/utils" + + hat_actions_model "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/actions" + hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs" + hat_actions_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/actions" +) + +func DeleteActionsSecret(ctx *context.APIContext) { + if err := hat_actions_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname")); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteSecretByName", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.NotFound(err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +func CreateActionsSecret(ctx *context.APIContext) { + opt := web.GetForm(ctx).(*hat_api.CreateSecretOption) + ownerID := ctx.Org.Organization.ID + secretName := ctx.Params("secretname") + + v, err := hat_actions_service.GetSecret(ctx, hat_actions_model.FindSecretsOptions{ + OwnerID: ownerID, + Name: secretName, + }) + if err != nil && !errors.Is(err, util.ErrNotExist) { + ctx.InternalServerError(err) + return + } + if v != nil && v.ID > 0 { + ctx.Error(http.StatusConflict, "", util.NewAlreadyExistErrorf("secret name %s already exists", secretName)) + return + } + if _, err := hat_actions_service.CreateSecret(ctx, ownerID, 0, secretName, opt.Data); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateSecret", err) + } else { + ctx.InternalServerError(err) + } + return + } + ctx.Status(http.StatusCreated) +} + +func UpdateActionsSecret(ctx *context.APIContext) { + opt := web.GetForm(ctx).(*hat_api.UpdateSecretOption) + + _, err := hat_actions_service.GetSecret(ctx, hat_actions_model.FindSecretsOptions{ + OwnerID: ctx.Org.Organization.ID, + Name: ctx.Params("secretname"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetSecret", err) + } else { + ctx.InternalServerError(err) + } + return + } + + if opt.Name == "" { + opt.Name = ctx.Params("secretname") + } + + _, err = hat_actions_service.UpdateSecret(ctx, ctx.Org.Organization.ID, 0, opt.Name, opt.Data) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "UpdateSecret", err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusNoContent) + +} + +func ListActionsSecrets(ctx *context.APIContext) { + opts := &hat_actions_model.FindSecretsOptions{ + OwnerID: ctx.Org.Organization.ID, + ListOptions: utils.GetListOptions(ctx), + } + + count, err := hat_actions_model.CountSecrets(ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + secrets, err := hat_actions_model.FindSecrets(ctx, *opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + apiSecrets := make([]*api.Secret, len(secrets)) + for k, v := range secrets { + apiSecrets[k] = &api.Secret{ + Name: v.Name, + Created: v.CreatedUnix.AsTime(), + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, apiSecrets) +} + +func GetVariable(ctx *context.APIContext) { + v, err := hat_actions_service.GetVariable(ctx, hat_actions_model.FindVariablesOpts{ + OwnerID: ctx.Org.Organization.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.NotFound(err) + } else { + ctx.InternalServerError(err) + } + return + } + + variable := &hat_api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + + ctx.JSON(http.StatusOK, variable) +} + +func DeleteVariable(ctx *context.APIContext) { + if err := hat_actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("variablename")); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.NotFound(err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +func CreateVariable(ctx *context.APIContext) { + opt := web.GetForm(ctx).(*hat_api.CreateVariableOption) + ownerID := ctx.Org.Organization.ID + variableName := ctx.Params("variablename") + + v, err := hat_actions_service.GetVariable(ctx, hat_actions_model.FindVariablesOpts{ + OwnerID: ownerID, + Name: variableName, + }) + if err != nil && !errors.Is(err, util.ErrNotExist) { + ctx.InternalServerError(err) + return + } + if v != nil && v.ID > 0 { + ctx.Error(http.StatusConflict, "", util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) + return + } + + if _, err := hat_actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateVariable", err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusCreated) +} + +func UpdateVariable(ctx *context.APIContext) { + opt := web.GetForm(ctx).(*hat_api.UpdateVariableOption) + + v, err := hat_actions_service.GetVariable(ctx, hat_actions_model.FindVariablesOpts{ + OwnerID: ctx.Org.Organization.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetVariable", err) + } else { + ctx.InternalServerError(err) + } + return + } + + if opt.Name == "" { + opt.Name = ctx.Params("variablename") + } + + v.Name = opt.Name + v.Data = opt.Value + + if _, err := hat_actions_service.UpdateVariableNameData(ctx, v); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "UpdateVariableNameData", err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +func ListVariables(ctx *context.APIContext) { + opts := hat_actions_model.FindVariablesOpts{ + OwnerID: ctx.Org.Organization.ID, + ListOptions: utils.GetListOptions(ctx), + } + + vars, err := hat_actions_model.FindVariables(ctx, opts) + + if err != nil { + ctx.InternalServerError(err) + return + } + + count, err := hat_actions_model.CountVariables(ctx, opts) + + if err != nil { + ctx.InternalServerError(err) + return + } + + variables := make([]*hat_api.ActionVariable, len(vars)) + for i, v := range vars { + variables[i] = &hat_api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, variables) +} diff --git a/routers/hat/repo/actions/actions.go b/routers/hat/repo/actions/actions.go index b44e472..7e56bb1 100644 --- a/routers/hat/repo/actions/actions.go +++ b/routers/hat/repo/actions/actions.go @@ -15,7 +15,6 @@ import ( actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" - secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/actions" @@ -32,24 +31,103 @@ import ( actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/convert" hat_actions_model "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/actions" + hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs" + hat_actions_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/actions" jobparser "github.com/nektos/act/pkg/jobparser" "github.com/nektos/act/pkg/model" "xorm.io/builder" ) +func DeleteActionsSecret(ctx *context.APIContext) { + if err := hat_actions_service.DeleteSecretByName(ctx, 0, ctx.Repo.Repository.ID, ctx.Params("secretname")); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteSecretByName", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.NotFound(err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +func CreateActionsSecret(ctx *context.APIContext) { + opt := web.GetForm(ctx).(*hat_api.CreateSecretOption) + repoID := ctx.Repo.Repository.ID + secretName := ctx.Params("secretname") + + v, err := hat_actions_service.GetSecret(ctx, hat_actions_model.FindSecretsOptions{ + RepoID: repoID, + Name: secretName, + }) + if err != nil && !errors.Is(err, util.ErrNotExist) { + ctx.InternalServerError(err) + return + } + if v != nil && v.ID > 0 { + ctx.Error(http.StatusConflict, "", util.NewAlreadyExistErrorf("secret name %s already exists", secretName)) + return + } + if _, err := hat_actions_service.CreateSecret(ctx, 0, repoID, secretName, opt.Data); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateSecret", err) + } else { + ctx.InternalServerError(err) + } + return + } + ctx.Status(http.StatusCreated) +} + +func UpdateActionsSecret(ctx *context.APIContext) { + opt := web.GetForm(ctx).(*hat_api.UpdateSecretOption) + + _, err := hat_actions_service.GetSecret(ctx, hat_actions_model.FindSecretsOptions{ + RepoID: ctx.Repo.Repository.ID, + Name: ctx.Params("secretname"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetSecret", err) + } else { + ctx.InternalServerError(err) + } + return + } + + if opt.Name == "" { + opt.Name = ctx.Params("secretname") + } + + _, err = hat_actions_service.UpdateSecret(ctx, 0, ctx.Repo.Repository.ID, opt.Name, opt.Data) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "UpdateSecret", err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusNoContent) + +} + func ListActionsSecrets(ctx *context.APIContext) { - opts := &secret_model.FindSecretsOptions{ + opts := &hat_actions_model.FindSecretsOptions{ RepoID: ctx.Repo.Repository.ID, ListOptions: utils.GetListOptions(ctx), } - count, err := secret_model.CountSecrets(ctx, opts) + count, err := hat_actions_model.CountSecrets(ctx, opts) if err != nil { ctx.InternalServerError(err) return } - secrets, err := secret_model.FindSecrets(ctx, *opts) + secrets, err := hat_actions_model.FindSecrets(ctx, *opts) if err != nil { ctx.InternalServerError(err) return @@ -67,6 +145,143 @@ func ListActionsSecrets(ctx *context.APIContext) { ctx.JSON(http.StatusOK, apiSecrets) } +func GetVariable(ctx *context.APIContext) { + v, err := hat_actions_service.GetVariable(ctx, hat_actions_model.FindVariablesOpts{ + RepoID: ctx.Repo.Repository.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.NotFound(err) + } else { + ctx.InternalServerError(err) + } + return + } + + variable := &hat_api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + + ctx.JSON(http.StatusOK, variable) +} + +func DeleteVariable(ctx *context.APIContext) { + if err := hat_actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.Params("variablename")); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.NotFound(err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +func CreateVariable(ctx *context.APIContext) { + opt := web.GetForm(ctx).(*hat_api.CreateVariableOption) + repoID := ctx.Repo.Repository.ID + variableName := ctx.Params("variablename") + + v, err := hat_actions_service.GetVariable(ctx, hat_actions_model.FindVariablesOpts{ + RepoID: repoID, + Name: variableName, + }) + if err != nil && !errors.Is(err, util.ErrNotExist) { + ctx.InternalServerError(err) + return + } + if v != nil && v.ID > 0 { + ctx.Error(http.StatusConflict, "", util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) + return + } + + if _, err := hat_actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateVariable", err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusCreated) +} + +func UpdateVariable(ctx *context.APIContext) { + opt := web.GetForm(ctx).(*hat_api.UpdateVariableOption) + + v, err := hat_actions_service.GetVariable(ctx, hat_actions_model.FindVariablesOpts{ + RepoID: ctx.Repo.Repository.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetVariable", err) + } else { + ctx.InternalServerError(err) + } + return + } + + if opt.Name == "" { + opt.Name = ctx.Params("variablename") + } + + v.Name = opt.Name + v.Data = opt.Value + + if _, err := hat_actions_service.UpdateVariableNameData(ctx, v); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "UpdateVariableNameData", err) + } else { + ctx.InternalServerError(err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +func ListVariables(ctx *context.APIContext) { + opts := hat_actions_model.FindVariablesOpts{ + RepoID: ctx.Repo.Repository.ID, + ListOptions: utils.GetListOptions(ctx), + } + vars, err := hat_actions_model.FindVariables(ctx, opts) + + if err != nil { + ctx.InternalServerError(err) + return + } + + count, err := hat_actions_model.CountVariables(ctx, opts) + + if err != nil { + ctx.InternalServerError(err) + return + } + + variables := make([]*hat_api.ActionVariable, len(vars)) + for i, v := range vars { + variables[i] = &hat_api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, variables) +} + func Run(ctx *context.APIContext) { workflow := ctx.FormString("workflow") ref := ctx.FormString("ref") diff --git a/services/actions/secrets.go b/services/actions/secrets.go new file mode 100644 index 0000000..44dd92d --- /dev/null +++ b/services/actions/secrets.go @@ -0,0 +1,105 @@ +package actions + +import ( + "context" + "regexp" + "strings" + "sync" + + secret_model "code.gitea.io/gitea/models/secret" + hat_actions_model "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/actions" + + "code.gitea.io/gitea/modules/util" +) + +var globalVars = sync.OnceValue(func() (ret struct { + namePattern, forbiddenPrefixPattern *regexp.Regexp +}, +) { + ret.namePattern = regexp.MustCompile("(?i)^[A-Z_][A-Z0-9_]*$") + ret.forbiddenPrefixPattern = regexp.MustCompile("(?i)^GIT(EA|HUB)_") + return ret +}) + +func ValidateName(name string) error { + vars := globalVars() + if !vars.namePattern.MatchString(name) || + vars.forbiddenPrefixPattern.MatchString(name) || + strings.EqualFold(name, "CI") /* CI is always set to true in GitHub Actions*/ { + return util.NewInvalidArgumentErrorf("invalid variable or secret name") + } + return nil +} + +func CreateSecret(ctx context.Context, ownerID, repoID int64, name, data string) (*secret_model.Secret, error) { + if err := ValidateName(name); err != nil { + return nil, err + } + + s, err := secret_model.InsertEncryptedSecret(ctx, ownerID, repoID, name, data) + if err != nil { + return nil, err + } + + return s, nil +} + +func UpdateSecret(ctx context.Context, ownerID, repoID int64, name, data string) (bool, error) { + if err := ValidateName(name); err != nil { + return false, err + } + + s, err := GetSecret(ctx, hat_actions_model.FindSecretsOptions{ + OwnerID: ownerID, + RepoID: repoID, + Name: name, + }) + if err != nil { + return false, err + } + + if s == nil { + return false, util.NewNotExistErrorf("secret not found") + } + + err = secret_model.UpdateSecret(ctx, s.ID, data) + if err != nil { + return false, err + } + + return true, nil +} + +func GetSecret(ctx context.Context, opts hat_actions_model.FindSecretsOptions) (*secret_model.Secret, error) { + vars, err := hat_actions_model.FindSecrets(ctx, opts) + if err != nil { + return nil, err + } + if len(vars) != 1 { + return nil, util.NewNotExistErrorf("variable not found") + } + return vars[0], nil +} + +func DeleteSecretByName(ctx context.Context, ownerID, repoID int64, name string) error { + s, err := GetSecret(ctx, hat_actions_model.FindSecretsOptions{ + OwnerID: ownerID, + RepoID: repoID, + Name: name, + }) + if err != nil { + return err + } + + return hat_actions_model.DeleteSecret(ctx, s) +} + +func DeleteSecretByID(ctx context.Context, secretID int64) error { + s, err := GetSecret(ctx, hat_actions_model.FindSecretsOptions{ + SecretID: secretID, + }) + if err != nil { + return err + } + return hat_actions_model.DeleteSecret(ctx, s) +} diff --git a/services/actions/variables.go b/services/actions/variables.go new file mode 100644 index 0000000..31b8cd1 --- /dev/null +++ b/services/actions/variables.go @@ -0,0 +1,69 @@ +package actions + +import ( + "context" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/modules/util" + + secret_service "code.gitea.io/gitea/services/secrets" + hat_actions_model "code.gitlink.org.cn/Gitlink/gitea_hat.git/models/actions" + hat_util "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/util" +) + +func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*actions_model.ActionVariable, error) { + if err := secret_service.ValidateName(name); err != nil { + return nil, err + } + + v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, hat_util.ReserveLineBreakForTextarea(data)) + if err != nil { + return nil, err + } + + return v, nil +} + +func UpdateVariableNameData(ctx context.Context, variable *actions_model.ActionVariable) (bool, error) { + if err := secret_service.ValidateName(variable.Name); err != nil { + return false, err + } + + variable.Data = hat_util.ReserveLineBreakForTextarea(variable.Data) + + return actions_model.UpdateVariable(ctx, variable) +} + +func DeleteVariableByID(ctx context.Context, variableID int64) error { + v, err := GetVariable(ctx, hat_actions_model.FindVariablesOpts{ + IDs: []int64{variableID}, + }) + if err != nil { + return err + } + return hat_actions_model.DeleteVariable(ctx, v) +} + +func DeleteVariableByName(ctx context.Context, ownerID, repoID int64, name string) error { + v, err := GetVariable(ctx, hat_actions_model.FindVariablesOpts{ + OwnerID: ownerID, + RepoID: repoID, + Name: name, + }) + if err != nil { + return err + } + + return hat_actions_model.DeleteVariable(ctx, v) +} + +func GetVariable(ctx context.Context, opts hat_actions_model.FindVariablesOpts) (*actions_model.ActionVariable, error) { + vars, err := hat_actions_model.FindVariables(ctx, opts) + if err != nil { + return nil, err + } + if len(vars) != 1 { + return nil, util.NewNotExistErrorf("variable not found") + } + return vars[0], nil +} From feefd16ae13e4717bae57b12ac1bddf02bcaac04 Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 19 Jan 2026 17:13:54 +0800 Subject: [PATCH 21/22] =?UTF-8?q?=E6=9B=B4=E6=94=B9=EF=BC=9A=E8=BF=94?= =?UTF-8?q?=E5=9B=9Etag=E5=8E=BB=E9=99=A4=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- modules/convert/convert.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 2a95a1a..aa7955a 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) var ( - Version = "v2.8.7, by v1.21.0, 20251201 " + Version = "v2.8.8, by v1.21.0, 20260119 " Tags = "" MakeVersion = "" ) diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 873e314..25a6fc3 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -23,7 +23,7 @@ func ToTag(repo *repo.Repository, gitRepo *git.Repository, t *git.Tag) (tag *api return &api.Tag{}, err } return &api.Tag{ - Name: t.Name, + Name: strings.TrimPrefix(t.Name, git.TagPrefix), Message: strings.TrimSpace(t.Message), ID: t.ID.String(), Commit: tagCommit, From d9a412c3a282e91ec3983fa79602e9560f03bf3a Mon Sep 17 00:00:00 2001 From: yystopf Date: Mon, 19 Jan 2026 17:25:22 +0800 Subject: [PATCH 22/22] =?UTF-8?q?=E6=9B=B4=E6=94=B9=EF=BC=9Atag=E5=8E=BB?= =?UTF-8?q?=E9=99=A4=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/convert/convert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 25a6fc3..bac8e63 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -23,7 +23,7 @@ func ToTag(repo *repo.Repository, gitRepo *git.Repository, t *git.Tag) (tag *api return &api.Tag{}, err } return &api.Tag{ - Name: strings.TrimPrefix(t.Name, git.TagPrefix), + Name: strings.TrimPrefix(t.Name, "tags/"), Message: strings.TrimSpace(t.Message), ID: t.ID.String(), Commit: tagCommit,