mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Storage buckets, indexer names, the bleve analyzer, the lock prefix, SSH host keys, the markdown front-matter key, the container data paths, the RPM repo id and the API security scheme all read as this product now, and app.example.ini matches the code again. The upstream maintainer list and the changelog generator config went with the release history they belonged to, and the citation comments state the fact rather than link the issue. Three inert rules turn out to have been inert because they named a module path this tree does not use: gci grouped no first-party imports under prefix(), and depguard let models/migrations import models and modules/structs. They now name github.com/hanzoai/git. The config example also documented .gitea/workflows while the code has read .hanzo/workflows for some time. The authorized_keys marker follows the delegate-hook precedent: the new name is written and the old one is still recognised, because a marker we do not know is copied through as a hand-added key and leaves a revoked key authorized. The stored user_setting key keeps its value; only a migration may change that. Co-authored-by: Hanzo Dev <[email protected]>
391 lines
14 KiB
Go
391 lines
14 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package setting
|
|
|
|
import (
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/git/modules/log"
|
|
)
|
|
|
|
// enumerates all the policy repository creating
|
|
const (
|
|
RepoCreatingLastUserVisibility = "last"
|
|
RepoCreatingPrivate = "private"
|
|
RepoCreatingPublic = "public"
|
|
)
|
|
|
|
// enumerates the values for [repository.pull-request] DEFAULT_TITLE_SOURCE
|
|
const (
|
|
RepoPRTitleSourceFirstCommit = "first-commit"
|
|
RepoPRTitleSourceAuto = "auto"
|
|
RepoPRTitleSourceBranchName = "branch-name"
|
|
)
|
|
|
|
// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
|
|
const ItemsPerPage = 40
|
|
|
|
// Repository settings
|
|
var (
|
|
Repository = struct {
|
|
DetectedCharsetsOrder []string
|
|
DetectedCharsetScore map[string]int `ini:"-"`
|
|
AnsiCharset string
|
|
ForcePrivate bool
|
|
DefaultPrivate string
|
|
DefaultPushCreatePrivate bool
|
|
MaxCreationLimit int
|
|
UserMaxCreationLimit int
|
|
OrgMaxCreationLimit int
|
|
PreferredLicenses []string
|
|
DisableHTTPGit bool
|
|
AccessControlAllowOrigin string
|
|
UseCompatSSHURI bool
|
|
GoGetCloneURLProtocol string
|
|
DefaultCloseIssuesViaCommitsInAnyBranch bool
|
|
EnablePushCreateUser bool
|
|
EnablePushCreateOrg bool
|
|
DisabledRepoUnits []string
|
|
DefaultRepoUnits []string
|
|
DefaultForkRepoUnits []string
|
|
DefaultMirrorRepoUnits []string
|
|
DefaultTemplateRepoUnits []string
|
|
PrefixArchiveFiles bool
|
|
DisableMigrations bool
|
|
DisableStars bool `ini:"DISABLE_STARS"`
|
|
DefaultBranch string
|
|
AllowAdoptionOfUnadoptedRepositories bool
|
|
AllowDeleteOfUnadoptedRepositories bool
|
|
DisableDownloadSourceArchives bool
|
|
AllowForkWithoutMaximumLimit bool
|
|
AllowForkIntoSameOwner bool
|
|
|
|
// StreamArchives makes the forge stream git archive files to the client directly instead of creating an archive first.
|
|
// Ideally all users should use this streaming method. However, at the moment we don't know whether there are
|
|
// any users who still need the old behavior, so we introduce this option, intentionally not documenting it.
|
|
// After one or two releases, if no one complains, we will remove this option and always use streaming.
|
|
StreamArchives bool
|
|
|
|
// Repository editor settings
|
|
Editor struct {
|
|
LineWrapExtensions []string
|
|
} `ini:"-"`
|
|
|
|
// Repository upload settings
|
|
Upload struct {
|
|
Enabled bool
|
|
AllowedTypes string
|
|
FileMaxSize int64
|
|
MaxFiles int
|
|
} `ini:"-"`
|
|
|
|
// Pull request settings
|
|
PullRequest struct {
|
|
WorkInProgressPrefixes []string
|
|
CloseKeywords []string
|
|
ReopenKeywords []string
|
|
DefaultMergeStyle string
|
|
DefaultMergeMessageCommitsLimit int
|
|
DefaultMergeMessageSize int
|
|
DefaultMergeMessageAllAuthors bool
|
|
DefaultMergeMessageMaxApprovers int
|
|
DefaultMergeMessageOfficialApproversOnly bool
|
|
PopulateSquashCommentWithCommitMessages bool
|
|
AddCoCommitterTrailers bool
|
|
RetargetChildrenOnMerge bool
|
|
DelayCheckForInactiveDays int
|
|
DefaultDeleteBranchAfterMerge bool
|
|
DefaultTitleSource string
|
|
} `ini:"repository.pull-request"`
|
|
|
|
// Issue Setting
|
|
Issue struct {
|
|
LockReasons []string
|
|
MaxPinned int
|
|
} `ini:"repository.issue"`
|
|
|
|
Release struct {
|
|
AllowedTypes string
|
|
DefaultPagingNum int
|
|
FileMaxSize int64
|
|
MaxFiles int64
|
|
} `ini:"repository.release"`
|
|
|
|
Signing struct {
|
|
SigningKey string
|
|
SigningName string
|
|
SigningEmail string
|
|
SigningFormat string
|
|
InitialCommit []string
|
|
CRUDActions []string `ini:"CRUD_ACTIONS"`
|
|
Merges []string
|
|
Wiki []string
|
|
DefaultTrustModel string
|
|
TrustedSSHKeys []string `ini:"TRUSTED_SSH_KEYS"`
|
|
} `ini:"repository.signing"`
|
|
}{
|
|
DetectedCharsetsOrder: []string{
|
|
"UTF-8",
|
|
"UTF-16BE",
|
|
"UTF-16LE",
|
|
"UTF-32BE",
|
|
"UTF-32LE",
|
|
"ISO-8859-1",
|
|
"windows-1252",
|
|
"ISO-8859-2",
|
|
"windows-1250",
|
|
"ISO-8859-5",
|
|
"ISO-8859-6",
|
|
"ISO-8859-7",
|
|
"windows-1253",
|
|
"ISO-8859-8-I",
|
|
"windows-1255",
|
|
"ISO-8859-8",
|
|
"windows-1251",
|
|
"windows-1256",
|
|
"KOI8-R",
|
|
"ISO-8859-9",
|
|
"windows-1254",
|
|
"Shift_JIS",
|
|
"GB18030",
|
|
"EUC-JP",
|
|
"EUC-KR",
|
|
"Big5",
|
|
"ISO-2022-JP",
|
|
"ISO-2022-KR",
|
|
"ISO-2022-CN",
|
|
"IBM424_rtl",
|
|
"IBM424_ltr",
|
|
"IBM420_rtl",
|
|
"IBM420_ltr",
|
|
},
|
|
DetectedCharsetScore: map[string]int{},
|
|
AnsiCharset: "",
|
|
ForcePrivate: false,
|
|
DefaultPrivate: RepoCreatingLastUserVisibility,
|
|
DefaultPushCreatePrivate: true,
|
|
MaxCreationLimit: -1,
|
|
UserMaxCreationLimit: -1,
|
|
OrgMaxCreationLimit: -1,
|
|
PreferredLicenses: []string{"Apache License 2.0", "MIT License"},
|
|
DisableHTTPGit: false,
|
|
AccessControlAllowOrigin: "",
|
|
UseCompatSSHURI: false,
|
|
DefaultCloseIssuesViaCommitsInAnyBranch: false,
|
|
EnablePushCreateUser: false,
|
|
EnablePushCreateOrg: false,
|
|
DisabledRepoUnits: []string{},
|
|
DefaultRepoUnits: []string{},
|
|
DefaultForkRepoUnits: []string{},
|
|
DefaultMirrorRepoUnits: []string{},
|
|
DefaultTemplateRepoUnits: []string{},
|
|
PrefixArchiveFiles: true,
|
|
DisableMigrations: false,
|
|
DisableStars: false,
|
|
DefaultBranch: "main",
|
|
AllowForkWithoutMaximumLimit: true,
|
|
StreamArchives: true,
|
|
|
|
// Repository editor settings
|
|
Editor: struct {
|
|
LineWrapExtensions []string
|
|
}{
|
|
LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,.livemd,", ","),
|
|
},
|
|
|
|
// Repository upload settings
|
|
Upload: struct {
|
|
Enabled bool
|
|
AllowedTypes string
|
|
FileMaxSize int64
|
|
MaxFiles int
|
|
}{
|
|
Enabled: true,
|
|
AllowedTypes: "",
|
|
FileMaxSize: 50,
|
|
MaxFiles: 5,
|
|
},
|
|
|
|
// Pull request settings
|
|
PullRequest: struct {
|
|
WorkInProgressPrefixes []string
|
|
CloseKeywords []string
|
|
ReopenKeywords []string
|
|
DefaultMergeStyle string
|
|
DefaultMergeMessageCommitsLimit int
|
|
DefaultMergeMessageSize int
|
|
DefaultMergeMessageAllAuthors bool
|
|
DefaultMergeMessageMaxApprovers int
|
|
DefaultMergeMessageOfficialApproversOnly bool
|
|
PopulateSquashCommentWithCommitMessages bool
|
|
AddCoCommitterTrailers bool
|
|
RetargetChildrenOnMerge bool
|
|
DelayCheckForInactiveDays int
|
|
DefaultDeleteBranchAfterMerge bool
|
|
DefaultTitleSource string
|
|
}{
|
|
WorkInProgressPrefixes: []string{"WIP:", "[WIP]"},
|
|
// Same as GitHub. See
|
|
// https://help.github.com/articles/closing-issues-via-commit-messages
|
|
CloseKeywords: strings.Split("close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved", ","),
|
|
ReopenKeywords: strings.Split("reopen,reopens,reopened", ","),
|
|
DefaultMergeStyle: "merge",
|
|
DefaultMergeMessageCommitsLimit: 50,
|
|
DefaultMergeMessageSize: 5 * 1024,
|
|
DefaultMergeMessageAllAuthors: false,
|
|
DefaultMergeMessageMaxApprovers: 10,
|
|
DefaultMergeMessageOfficialApproversOnly: true,
|
|
PopulateSquashCommentWithCommitMessages: false,
|
|
AddCoCommitterTrailers: true,
|
|
RetargetChildrenOnMerge: true,
|
|
DelayCheckForInactiveDays: 7,
|
|
DefaultTitleSource: RepoPRTitleSourceAuto,
|
|
},
|
|
|
|
// Issue settings
|
|
Issue: struct {
|
|
LockReasons []string
|
|
MaxPinned int
|
|
}{
|
|
LockReasons: strings.Split("Too heated,Off-topic,Spam,Resolved", ","),
|
|
MaxPinned: 3,
|
|
},
|
|
|
|
Release: struct {
|
|
AllowedTypes string
|
|
DefaultPagingNum int
|
|
FileMaxSize int64
|
|
MaxFiles int64
|
|
}{
|
|
AllowedTypes: "",
|
|
DefaultPagingNum: 10,
|
|
FileMaxSize: 2048,
|
|
MaxFiles: 5,
|
|
},
|
|
|
|
// Signing settings
|
|
Signing: struct {
|
|
SigningKey string
|
|
SigningName string
|
|
SigningEmail string
|
|
SigningFormat string
|
|
InitialCommit []string
|
|
CRUDActions []string `ini:"CRUD_ACTIONS"`
|
|
Merges []string
|
|
Wiki []string
|
|
DefaultTrustModel string
|
|
TrustedSSHKeys []string `ini:"TRUSTED_SSH_KEYS"`
|
|
}{
|
|
SigningKey: "default",
|
|
SigningName: "",
|
|
SigningEmail: "",
|
|
SigningFormat: "openpgp", // git.SigningKeyFormatOpenPGP
|
|
InitialCommit: []string{"always"},
|
|
CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
|
|
Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
|
|
Wiki: []string{"never"},
|
|
DefaultTrustModel: "collaborator",
|
|
TrustedSSHKeys: []string{},
|
|
},
|
|
}
|
|
RepoRootPath string
|
|
ScriptType = "bash"
|
|
)
|
|
|
|
func loadRepositoryFrom(rootCfg ConfigProvider) {
|
|
var err error
|
|
// Determine and create root git repository path.
|
|
sec := rootCfg.Section("repository")
|
|
Repository.DisableHTTPGit = sec.Key("DISABLE_HTTP_GIT").MustBool()
|
|
Repository.UseCompatSSHURI = sec.Key("USE_COMPAT_SSH_URI").MustBool()
|
|
Repository.GoGetCloneURLProtocol = sec.Key("GO_GET_CLONE_URL_PROTOCOL").MustString("https")
|
|
// MAX_CREATION_LIMIT is a shortcut that sets the default for the two per-type limits below.
|
|
// USER_/ORG_MAX_CREATION_LIMIT take precedence when explicitly set.
|
|
Repository.MaxCreationLimit = sec.Key("MAX_CREATION_LIMIT").MustInt(-1)
|
|
Repository.UserMaxCreationLimit = sec.Key("USER_MAX_CREATION_LIMIT").MustInt(Repository.MaxCreationLimit)
|
|
Repository.OrgMaxCreationLimit = sec.Key("ORG_MAX_CREATION_LIMIT").MustInt(Repository.MaxCreationLimit)
|
|
Repository.DefaultBranch = sec.Key("DEFAULT_BRANCH").MustString(Repository.DefaultBranch)
|
|
RepoRootPath = sec.Key("ROOT").MustString(filepath.Join(AppDataPath, "repositories"))
|
|
if !filepath.IsAbs(RepoRootPath) {
|
|
RepoRootPath = filepath.Join(AppWorkPath, RepoRootPath)
|
|
} else {
|
|
RepoRootPath = filepath.Clean(RepoRootPath)
|
|
}
|
|
|
|
checkOverlappedPath("[repository].ROOT", RepoRootPath)
|
|
|
|
defaultDetectedCharsetsOrder := make([]string, 0, len(Repository.DetectedCharsetsOrder))
|
|
for _, charset := range Repository.DetectedCharsetsOrder {
|
|
defaultDetectedCharsetsOrder = append(defaultDetectedCharsetsOrder, strings.ToLower(strings.TrimSpace(charset)))
|
|
}
|
|
ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
|
|
|
|
if _, err := exec.LookPath(ScriptType); err != nil {
|
|
log.Warn("SCRIPT_TYPE %q is not on the current PATH. Are you sure that this is the correct SCRIPT_TYPE?", ScriptType)
|
|
}
|
|
|
|
if err = sec.MapTo(&Repository); err != nil {
|
|
log.Fatal("Failed to map Repository settings: %v", err)
|
|
} else if err = rootCfg.Section("repository.editor").MapTo(&Repository.Editor); err != nil {
|
|
log.Fatal("Failed to map Repository.Editor settings: %v", err)
|
|
} else if err = rootCfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil {
|
|
log.Fatal("Failed to map Repository.Upload settings: %v", err)
|
|
} else if err = rootCfg.Section("repository.pull-request").MapTo(&Repository.PullRequest); err != nil {
|
|
log.Fatal("Failed to map Repository.PullRequest settings: %v", err)
|
|
}
|
|
|
|
if !rootCfg.Section("packages").Key("ENABLED").MustBool(Packages.Enabled) {
|
|
Repository.DisabledRepoUnits = append(Repository.DisabledRepoUnits, "repo.packages")
|
|
}
|
|
|
|
if !rootCfg.Section("actions").Key("ENABLED").MustBool(Actions.Enabled) {
|
|
Repository.DisabledRepoUnits = append(Repository.DisabledRepoUnits, "repo.actions")
|
|
}
|
|
|
|
// Handle default trustmodel settings
|
|
Repository.Signing.DefaultTrustModel = strings.ToLower(strings.TrimSpace(Repository.Signing.DefaultTrustModel))
|
|
if Repository.Signing.DefaultTrustModel == "default" {
|
|
Repository.Signing.DefaultTrustModel = "collaborator"
|
|
}
|
|
|
|
// Handle preferred charset orders
|
|
preferred := make([]string, 0, len(Repository.DetectedCharsetsOrder))
|
|
for _, charset := range Repository.DetectedCharsetsOrder {
|
|
canonicalCharset := strings.ToLower(strings.TrimSpace(charset))
|
|
preferred = append(preferred, canonicalCharset)
|
|
// remove it from the defaults
|
|
for i, charset := range defaultDetectedCharsetsOrder {
|
|
if charset == canonicalCharset {
|
|
defaultDetectedCharsetsOrder = append(defaultDetectedCharsetsOrder[:i], defaultDetectedCharsetsOrder[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
i := 0
|
|
for _, charset := range preferred {
|
|
// Add the defaults
|
|
if charset == "defaults" {
|
|
for _, charset := range defaultDetectedCharsetsOrder {
|
|
canonicalCharset := strings.ToLower(strings.TrimSpace(charset))
|
|
if _, has := Repository.DetectedCharsetScore[canonicalCharset]; !has {
|
|
Repository.DetectedCharsetScore[canonicalCharset] = i
|
|
i++
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if _, has := Repository.DetectedCharsetScore[charset]; !has {
|
|
Repository.DetectedCharsetScore[charset] = i
|
|
i++
|
|
}
|
|
}
|
|
|
|
if err := loadRepoArchiveFrom(rootCfg); err != nil {
|
|
log.Fatal("loadRepoArchiveFrom: %v", err)
|
|
}
|
|
}
|