Merge branch 'go-gitea:main' into pacman-packages

This commit is contained in:
Dancheg97 2023-07-10 20:24:55 +03:00 committed by GitHub
commit 73fe874862
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 713 additions and 345 deletions

View File

@ -5,7 +5,6 @@ package main
import ( import (
"os" "os"
"strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -13,9 +12,6 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
// EnvironmentPrefix environment variables prefixed with this represent ini values to write
const EnvironmentPrefix = "GITEA"
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
app.Name = "environment-to-ini" app.Name = "environment-to-ini"
@ -70,15 +66,6 @@ func main() {
Value: "", Value: "",
Usage: "Destination file to write to", Usage: "Destination file to write to",
}, },
cli.BoolFlag{
Name: "clear",
Usage: "Clears the matched variables from the environment",
},
cli.StringFlag{
Name: "prefix, p",
Value: EnvironmentPrefix,
Usage: "Environment prefix to look for - will be suffixed by __ (2 underscores)",
},
} }
app.Action = runEnvironmentToIni app.Action = runEnvironmentToIni
err := app.Run(os.Args) err := app.Run(os.Args)
@ -99,9 +86,7 @@ func runEnvironmentToIni(c *cli.Context) error {
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err) log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
} }
prefixGitea := c.String("prefix") + "__" changed := setting.EnvironmentToConfig(cfg, os.Environ())
suffixFile := "__FILE"
changed := setting.EnvironmentToConfig(cfg, prefixGitea, suffixFile, os.Environ())
// try to save the config file // try to save the config file
destination := c.String("out") destination := c.String("out")
@ -116,19 +101,5 @@ func runEnvironmentToIni(c *cli.Context) error {
} }
} }
// clear Gitea's specific environment variables if requested
if c.Bool("clear") {
for _, kv := range os.Environ() {
idx := strings.IndexByte(kv, '=')
if idx < 0 {
continue
}
eKey := kv[:idx]
if strings.HasPrefix(eKey, prefixGitea) {
_ = os.Unsetenv(eKey)
}
}
}
return nil return nil
} }

View File

@ -8,7 +8,7 @@
# #
# And place the original in /usr/lib/gitea with working files in /data/gitea # And place the original in /usr/lib/gitea with working files in /data/gitea
GITEA="/app/gitea/gitea" GITEA="/app/gitea/gitea"
WORK_DIR="/app/gitea" WORK_DIR="/data/gitea"
CUSTOM_PATH="/data/gitea" CUSTOM_PATH="/data/gitea"
# Provide docker defaults # Provide docker defaults

View File

@ -288,7 +288,7 @@ docker-compose up -d
In addition to the environment variables above, any settings in `app.ini` can be set In addition to the environment variables above, any settings in `app.ini` can be set
or overridden with an environment variable of the form: `GITEA__SECTION_NAME__KEY_NAME`. or overridden with an environment variable of the form: `GITEA__SECTION_NAME__KEY_NAME`.
These settings are applied each time the docker container starts. These settings are applied each time the docker container starts, and won't be passed into Gitea's sub-processes.
Full information [here](https://github.com/go-gitea/gitea/tree/main/contrib/environment-to-ini). Full information [here](https://github.com/go-gitea/gitea/tree/main/contrib/environment-to-ini).
These environment variables can be passed to the docker container in `docker-compose.yml`. These environment variables can be passed to the docker container in `docker-compose.yml`.

View File

@ -289,7 +289,7 @@ docker-compose up -d
In addition to the environment variables above, any settings in `app.ini` can be set In addition to the environment variables above, any settings in `app.ini` can be set
or overridden with an environment variable of the form: `GITEA__SECTION_NAME__KEY_NAME`. or overridden with an environment variable of the form: `GITEA__SECTION_NAME__KEY_NAME`.
These settings are applied each time the docker container starts. These settings are applied each time the docker container starts, and won't be passed into Gitea's sub-processes.
Full information [here](https://github.com/go-gitea/gitea/tree/master/contrib/environment-to-ini). Full information [here](https://github.com/go-gitea/gitea/tree/master/contrib/environment-to-ini).
These environment variables can be passed to the docker container in `docker-compose.yml`. These environment variables can be passed to the docker container in `docker-compose.yml`.

View File

@ -81,3 +81,21 @@
uid: 5 uid: 5
org_id: 23 org_id: 23
is_public: false is_public: false
-
id: 15
uid: 1
org_id: 35
is_public: true
-
id: 16
uid: 1
org_id: 36
is_public: true
-
id: 17
uid: 5
org_id: 36
is_public: true

View File

@ -184,3 +184,36 @@
num_members: 1 num_members: 1
includes_all_repositories: false includes_all_repositories: false
can_create_org_repo: true can_create_org_repo: true
-
id: 18
org_id: 35
lower_name: owners
name: Owners
authorize: 4 # owner
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: true
-
id: 19
org_id: 36
lower_name: owners
name: Owners
authorize: 4 # owner
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: true
-
id: 20
org_id: 36
lower_name: team20writepackage
name: team20writepackage
authorize: 1
num_repos: 0
num_members: 1
includes_all_repositories: false
can_create_org_repo: true

View File

@ -273,4 +273,10 @@
id: 46 id: 46
team_id: 17 team_id: 17
type: 9 # package type: 9 # package
access_mode: 0 access_mode: 2
-
id: 47
team_id: 20
type: 9 # package
access_mode: 2

View File

@ -105,3 +105,21 @@
org_id: 23 org_id: 23
team_id: 17 team_id: 17
uid: 5 uid: 5
-
id: 19
org_id: 35
team_id: 18
uid: 1
-
id: 20
org_id: 36
team_id: 19
uid: 1
-
id: 21
org_id: 36
team_id: 20
uid: 5

View File

@ -1258,3 +1258,77 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
-
id: 35
lower_name: private_org35
name: private_org35
full_name: Private Org 35
email: private_org35@example.com
keep_email_private: false
email_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
login_source: 0
login_name: private_org35
type: 1
salt: ZogKvWdyEx
max_repo_creation: -1
is_active: true
is_admin: false
is_restricted: false
allow_git_hook: false
allow_import_local: false
allow_create_organization: true
prohibit_login: false
avatar: avatar35
avatar_email: private_org35@example.com
use_custom_avatar: false
num_followers: 0
num_following: 0
num_stars: 0
num_repos: 0
num_teams: 1
num_members: 1
visibility: 2
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false
-
id: 36
lower_name: limited_org36
name: limited_org36
full_name: Limited Org 36
email: limited_org36@example.com
keep_email_private: false
email_notifications_preference: enabled
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
login_source: 0
login_name: limited_org36
type: 1
salt: ZogKvWdyEx
max_repo_creation: -1
is_active: true
is_admin: false
is_restricted: false
allow_git_hook: false
allow_import_local: false
allow_create_organization: true
prohibit_login: false
avatar: avatar22
avatar_email: limited_org36@example.com
use_custom_avatar: false
num_followers: 0
num_following: 0
num_stars: 0
num_repos: 0
num_teams: 2
num_members: 2
visibility: 1
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false

View File

@ -382,7 +382,8 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
} }
// FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created // FindRecentlyPushedNewBranches return at most 2 new branches pushed by the user in 6 hours which has no opened PRs created
func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64) (BranchList, error) { // except the indicate branch
func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64, excludeBranchName string) (BranchList, error) {
branches := make(BranchList, 0, 2) branches := make(BranchList, 0, 2)
subQuery := builder.Select("head_branch").From("pull_request"). subQuery := builder.Select("head_branch").From("pull_request").
InnerJoin("issue", "issue.id = pull_request.issue_id"). InnerJoin("issue", "issue.id = pull_request.issue_id").
@ -392,6 +393,7 @@ func FindRecentlyPushedNewBranches(ctx context.Context, repoID, userID int64) (B
}) })
err := db.GetEngine(ctx). err := db.GetEngine(ctx).
Where("pusher_id=? AND is_deleted=?", userID, false). Where("pusher_id=? AND is_deleted=?", userID, false).
And("name <> ?", excludeBranchName).
And("updated_unix >= ?", time.Now().Add(-time.Hour*6).Unix()). And("updated_unix >= ?", time.Now().Add(-time.Hour*6).Unix()).
NotIn("name", subQuery). NotIn("name", subQuery).
OrderBy("branch.updated_unix DESC"). OrderBy("branch.updated_unix DESC").

View File

@ -65,13 +65,6 @@ func (a *Attachment) DownloadURL() string {
return setting.AppURL + "attachments/" + url.PathEscape(a.UUID) return setting.AppURL + "attachments/" + url.PathEscape(a.UUID)
} }
// _____ __ __ .__ __
// / _ \_/ |__/ |______ ____ | |__ _____ ____ _____/ |_
// / /_\ \ __\ __\__ \ _/ ___\| | \ / \_/ __ \ / \ __\
// / | \ | | | / __ \\ \___| Y \ Y Y \ ___/| | \ |
// \____|__ /__| |__| (____ /\___ >___| /__|_| /\___ >___| /__|
// \/ \/ \/ \/ \/ \/ \/
// ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error. // ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error.
type ErrAttachmentNotExist struct { type ErrAttachmentNotExist struct {
ID int64 ID int64

View File

@ -11,7 +11,7 @@ type MergeStyle string
const ( const (
// MergeStyleMerge create merge commit // MergeStyleMerge create merge commit
MergeStyleMerge MergeStyle = "merge" MergeStyleMerge MergeStyle = "merge"
// MergeStyleRebase rebase before merging // MergeStyleRebase rebase before merging, and fast-forward
MergeStyleRebase MergeStyle = "rebase" MergeStyleRebase MergeStyle = "rebase"
// MergeStyleRebaseMerge rebase before merging with merge commit (--no-ff) // MergeStyleRebaseMerge rebase before merging with merge commit (--no-ff)
MergeStyleRebaseMerge MergeStyle = "rebase-merge" MergeStyleRebaseMerge MergeStyle = "rebase-merge"

View File

@ -215,6 +215,7 @@ func (l *LayeredFS) WatchLocalChanges(ctx context.Context, callback func()) {
log.Error("Unable to list directories for asset local file-system %q: %v", layer.localPath, err) log.Error("Unable to list directories for asset local file-system %q: %v", layer.localPath, err)
continue continue
} }
layerDirs = append(layerDirs, ".")
for _, dir := range layerDirs { for _, dir := range layerDirs {
if err = watcher.Add(util.FilePathJoinAbs(layer.localPath, dir)); err != nil { if err = watcher.Add(util.FilePathJoinAbs(layer.localPath, dir)); err != nil {
log.Error("Unable to watch directory %s: %v", dir, err) log.Error("Unable to watch directory %s: %v", dir, err)

View File

@ -108,18 +108,28 @@ func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.A
if doer != nil && !doer.IsGhost() { if doer != nil && !doer.IsGhost() {
// 1. If user is logged in, check all team packages permissions // 1. If user is logged in, check all team packages permissions
teams, err := organization.GetUserOrgTeams(ctx, org.ID, doer.ID) var err error
accessMode, err = org.GetOrgUserMaxAuthorizeLevel(doer.ID)
if err != nil { if err != nil {
return accessMode, err return accessMode, err
} }
for _, t := range teams { // If access mode is less than write check every team for more permissions
perm := t.UnitAccessMode(ctx, unit.TypePackages) // The minimum possible access mode is read for org members
if accessMode < perm { if accessMode < perm.AccessModeWrite {
accessMode = perm teams, err := organization.GetUserOrgTeams(ctx, org.ID, doer.ID)
if err != nil {
return accessMode, err
}
for _, t := range teams {
perm := t.UnitAccessMode(ctx, unit.TypePackages)
if accessMode < perm {
accessMode = perm
}
} }
} }
} else if organization.HasOrgOrUserVisible(ctx, pkg.Owner, doer) { }
// 2. If user is non-login, check if org is visible to non-login user if accessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, pkg.Owner, doer) {
// 2. If user is unauthorized or no org member, check if org is visible
accessMode = perm.AccessModeRead accessMode = perm.AccessModeRead
} }
} else { } else {

View File

@ -12,10 +12,31 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
const (
EnvConfigKeyPrefixGitea = "GITEA__"
EnvConfigKeySuffixFile = "__FILE"
)
const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_" const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
var escapeRegex = regexp.MustCompile(escapeRegexpString) var escapeRegex = regexp.MustCompile(escapeRegexpString)
func CollectEnvConfigKeys() (keys []string) {
for _, env := range os.Environ() {
if strings.HasPrefix(env, EnvConfigKeyPrefixGitea) {
k, _, _ := strings.Cut(env, "=")
keys = append(keys, k)
}
}
return keys
}
func ClearEnvConfigKeys() {
for _, k := range CollectEnvConfigKeys() {
_ = os.Unsetenv(k)
}
}
// decodeEnvSectionKey will decode a portable string encoded Section__Key pair // decodeEnvSectionKey will decode a portable string encoded Section__Key pair
// Portable strings are considered to be of the form [A-Z0-9_]* // Portable strings are considered to be of the form [A-Z0-9_]*
// We will encode a disallowed value as the UTF8 byte string preceded by _0X and // We will encode a disallowed value as the UTF8 byte string preceded by _0X and
@ -87,7 +108,7 @@ func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, sect
return ok, section, key, useFileValue return ok, section, key, useFileValue
} }
func EnvironmentToConfig(cfg ConfigProvider, prefixGitea, suffixFile string, envs []string) (changed bool) { func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
for _, kv := range envs { for _, kv := range envs {
idx := strings.IndexByte(kv, '=') idx := strings.IndexByte(kv, '=')
if idx < 0 { if idx < 0 {
@ -97,7 +118,7 @@ func EnvironmentToConfig(cfg ConfigProvider, prefixGitea, suffixFile string, env
// parse the environment variable to config section name and key name // parse the environment variable to config section name and key name
envKey := kv[:idx] envKey := kv[:idx]
envValue := kv[idx+1:] envValue := kv[idx+1:]
ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(prefixGitea, suffixFile, envKey) ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(EnvConfigKeyPrefixGitea, EnvConfigKeySuffixFile, envKey)
if !ok { if !ok {
continue continue
} }

View File

@ -72,7 +72,7 @@ func TestDecodeEnvironmentKey(t *testing.T) {
func TestEnvironmentToConfig(t *testing.T) { func TestEnvironmentToConfig(t *testing.T) {
cfg, _ := NewConfigProviderFromData("") cfg, _ := NewConfigProviderFromData("")
changed := EnvironmentToConfig(cfg, "GITEA__", "__FILE", nil) changed := EnvironmentToConfig(cfg, nil)
assert.False(t, changed) assert.False(t, changed)
cfg, err := NewConfigProviderFromData(` cfg, err := NewConfigProviderFromData(`
@ -81,16 +81,16 @@ key = old
`) `)
assert.NoError(t, err) assert.NoError(t, err)
changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key=new"}) changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key=new"})
assert.True(t, changed) assert.True(t, changed)
assert.Equal(t, "new", cfg.Section("sec").Key("key").String()) assert.Equal(t, "new", cfg.Section("sec").Key("key").String())
changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key=new"}) changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key=new"})
assert.False(t, changed) assert.False(t, changed)
tmpFile := t.TempDir() + "/the-file" tmpFile := t.TempDir() + "/the-file"
_ = os.WriteFile(tmpFile, []byte("value-from-file"), 0o644) _ = os.WriteFile(tmpFile, []byte("value-from-file"), 0o644)
changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key__FILE=" + tmpFile}) changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
assert.True(t, changed) assert.True(t, changed)
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String()) assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
} }

View File

@ -171,6 +171,9 @@ func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkP
// only read the config but do not load/init anything more, because the AppWorkPath and CustomPath are not ready // only read the config but do not load/init anything more, because the AppWorkPath and CustomPath are not ready
InitCfgProvider(tmpCustomConf.Value) InitCfgProvider(tmpCustomConf.Value)
if HasInstallLock(CfgProvider) {
ClearEnvConfigKeys() // if the instance has been installed, do not pass the environment variables to sub-processes
}
configWorkPath := ConfigSectionKeyString(CfgProvider.Section(""), "WORK_PATH") configWorkPath := ConfigSectionKeyString(CfgProvider.Section(""), "WORK_PATH")
if configWorkPath != "" { if configWorkPath != "" {
if !filepath.IsAbs(configWorkPath) { if !filepath.IsAbs(configWorkPath) {

View File

@ -102,7 +102,7 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
func loadSecurityFrom(rootCfg ConfigProvider) { func loadSecurityFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("security") sec := rootCfg.Section("security")
InstallLock = sec.Key("INSTALL_LOCK").MustBool(false) InstallLock = HasInstallLock(rootCfg)
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome") CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome")
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")

View File

@ -183,10 +183,14 @@ func loadRunModeFrom(rootCfg ConfigProvider) {
} }
} }
// HasInstallLock checks the install-lock in ConfigProvider directly, because sometimes the config file is not loaded into setting variables yet.
func HasInstallLock(rootCfg ConfigProvider) bool {
return rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false)
}
func mustCurrentRunUserMatch(rootCfg ConfigProvider) { func mustCurrentRunUserMatch(rootCfg ConfigProvider) {
// Does not check run user when the "InstallLock" is off. // Does not check run user when the "InstallLock" is off.
installLock := rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false) if HasInstallLock(rootCfg) {
if installLock {
currentUser, match := IsRunUserMatchCurrentUser(RunUser) currentUser, match := IsRunUserMatchCurrentUser(RunUser)
if !match { if !match {
log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser) log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser)

View File

@ -296,6 +296,8 @@ invalid_password_algorithm = Invalid password hash algorithm
password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. The argon2 algorithm is rather secure but uses a lot of memory and may be inappropriate for small systems. password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. The argon2 algorithm is rather secure but uses a lot of memory and may be inappropriate for small systems.
enable_update_checker = Enable Update Checker enable_update_checker = Enable Update Checker
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io. enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
env_config_keys = Environment Configuration
env_config_keys_prompt = The following environment variables will also be applied to your configuration file:
[home] [home]
uname_holder = Username or Email Address uname_holder = Username or Email Address

View File

@ -12,4 +12,4 @@ djlint = "1.31.1"
[tool.djlint] [tool.djlint]
profile="golang" profile="golang"
ignore="H005,H006,H008,H013,H014,H016,H020,H021,H023,H026,H030,H031,T027" ignore="H005,H006,H008,H013,H016,H020,H021,H026,H030,H031,T027"

View File

@ -1034,7 +1034,7 @@ func Routes() *web.Route {
m.Group("/assets", func() { m.Group("/assets", func() {
m.Combo("").Get(repo.ListReleaseAttachments). m.Combo("").Get(repo.ListReleaseAttachments).
Post(reqToken(), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment) Post(reqToken(), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment)
m.Combo("/{asset}").Get(repo.GetReleaseAttachment). m.Combo("/{attachment_id}").Get(repo.GetReleaseAttachment).
Patch(reqToken(), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment). Patch(reqToken(), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment) Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment)
}) })
@ -1179,7 +1179,7 @@ func Routes() *web.Route {
m.Combo(""). m.Combo("").
Get(repo.ListIssueCommentAttachments). Get(repo.ListIssueCommentAttachments).
Post(reqToken(), mustNotBeArchived, repo.CreateIssueCommentAttachment) Post(reqToken(), mustNotBeArchived, repo.CreateIssueCommentAttachment)
m.Combo("/{asset}"). m.Combo("/{attachment_id}").
Get(repo.GetIssueCommentAttachment). Get(repo.GetIssueCommentAttachment).
Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment). Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment).
Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueCommentAttachment) Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueCommentAttachment)
@ -1231,7 +1231,7 @@ func Routes() *web.Route {
m.Combo(""). m.Combo("").
Get(repo.ListIssueAttachments). Get(repo.ListIssueAttachments).
Post(reqToken(), mustNotBeArchived, repo.CreateIssueAttachment) Post(reqToken(), mustNotBeArchived, repo.CreateIssueAttachment)
m.Combo("/{asset}"). m.Combo("/{attachment_id}").
Get(repo.GetIssueAttachment). Get(repo.GetIssueAttachment).
Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment). Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment).
Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueAttachment) Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueAttachment)

View File

@ -64,7 +64,7 @@ func GetIssueAttachment(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToAttachment(attach)) ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
} }
// ListIssueAttachments lists all attachments of the issue // ListIssueAttachments lists all attachments of the issue
@ -194,7 +194,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment)) ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
} }
// EditIssueAttachment updates the given attachment // EditIssueAttachment updates the given attachment
@ -254,7 +254,7 @@ func EditIssueAttachment(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", err) ctx.Error(http.StatusInternalServerError, "UpdateAttachment", err)
} }
ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment)) ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
} }
// DeleteIssueAttachment delete a given attachment // DeleteIssueAttachment delete a given attachment
@ -332,7 +332,7 @@ func getIssueAttachmentSafeWrite(ctx *context.APIContext) *repo_model.Attachment
} }
func getIssueAttachmentSafeRead(ctx *context.APIContext, issue *issues_model.Issue) *repo_model.Attachment { func getIssueAttachmentSafeRead(ctx *context.APIContext, issue *issues_model.Issue) *repo_model.Attachment {
attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("asset")) attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("attachment_id"))
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err) ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err)
return nil return nil

View File

@ -103,7 +103,7 @@ func ListIssueComments(ctx *context.APIContext) {
apiComments := make([]*api.Comment, len(comments)) apiComments := make([]*api.Comment, len(comments))
for i, comment := range comments { for i, comment := range comments {
comment.Issue = issue comment.Issue = issue
apiComments[i] = convert.ToComment(ctx, comments[i]) apiComments[i] = convert.ToAPIComment(ctx, ctx.Repo.Repository, comments[i])
} }
ctx.SetTotalCountHeader(totalCount) ctx.SetTotalCountHeader(totalCount)
@ -191,7 +191,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
for _, comment := range comments { for _, comment := range comments {
if comment.Type != issues_model.CommentTypeCode && isXRefCommentAccessible(ctx, ctx.Doer, comment, issue.RepoID) { if comment.Type != issues_model.CommentTypeCode && isXRefCommentAccessible(ctx, ctx.Doer, comment, issue.RepoID) {
comment.Issue = issue comment.Issue = issue
apiComments = append(apiComments, convert.ToTimelineComment(ctx, comment, ctx.Doer)) apiComments = append(apiComments, convert.ToTimelineComment(ctx, issue.Repo, comment, ctx.Doer))
} }
} }
@ -308,7 +308,7 @@ func ListRepoIssueComments(ctx *context.APIContext) {
return return
} }
for i := range comments { for i := range comments {
apiComments[i] = convert.ToComment(ctx, comments[i]) apiComments[i] = convert.ToAPIComment(ctx, ctx.Repo.Repository, comments[i])
} }
ctx.SetTotalCountHeader(totalCount) ctx.SetTotalCountHeader(totalCount)
@ -368,7 +368,7 @@ func CreateIssueComment(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToComment(ctx, comment)) ctx.JSON(http.StatusCreated, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
} }
// GetIssueComment Get a comment by ID // GetIssueComment Get a comment by ID
@ -436,7 +436,7 @@ func GetIssueComment(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToComment(ctx, comment)) ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
} }
// EditIssueComment modify a comment of an issue // EditIssueComment modify a comment of an issue
@ -561,7 +561,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
return return
} }
ctx.JSON(http.StatusOK, convert.ToComment(ctx, comment)) ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
} }
// DeleteIssueComment delete a comment from an issue // DeleteIssueComment delete a comment from an issue

View File

@ -68,7 +68,7 @@ func GetIssueCommentAttachment(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToAttachment(attachment)) ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
} }
// ListIssueCommentAttachments lists all attachments of the comment // ListIssueCommentAttachments lists all attachments of the comment
@ -110,7 +110,7 @@ func ListIssueCommentAttachments(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusOK, convert.ToAttachments(comment.Attachments)) ctx.JSON(http.StatusOK, convert.ToAPIAttachments(ctx.Repo.Repository, comment.Attachments))
} }
// CreateIssueCommentAttachment creates an attachment and saves the given file // CreateIssueCommentAttachment creates an attachment and saves the given file
@ -201,7 +201,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment)) ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attachment))
} }
// EditIssueCommentAttachment updates the given attachment // EditIssueCommentAttachment updates the given attachment
@ -259,7 +259,7 @@ func EditIssueCommentAttachment(ctx *context.APIContext) {
if err := repo_model.UpdateAttachment(ctx, attach); err != nil { if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach) ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
} }
ctx.JSON(http.StatusCreated, convert.ToAttachment(attach)) ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
} }
// DeleteIssueCommentAttachment delete a given attachment // DeleteIssueCommentAttachment delete a given attachment
@ -352,7 +352,7 @@ func canUserWriteIssueCommentAttachment(ctx *context.APIContext, comment *issues
} }
func getIssueCommentAttachmentSafeRead(ctx *context.APIContext, comment *issues_model.Comment) *repo_model.Attachment { func getIssueCommentAttachmentSafeRead(ctx *context.APIContext, comment *issues_model.Comment) *repo_model.Attachment {
attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("asset")) attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("attachment_id"))
if err != nil { if err != nil {
ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err) ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err)
return nil return nil

View File

@ -64,7 +64,7 @@ func GetRelease(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToRelease(ctx, release)) ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release))
} }
// GetLatestRelease gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at // GetLatestRelease gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at
@ -105,7 +105,7 @@ func GetLatestRelease(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToRelease(ctx, release)) ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release))
} }
// ListReleases list a repository's releases // ListReleases list a repository's releases
@ -174,7 +174,7 @@ func ListReleases(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
rels[i] = convert.ToRelease(ctx, release) rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)
} }
filteredCount, err := repo_model.CountReleasesByRepoID(ctx.Repo.Repository.ID, opts) filteredCount, err := repo_model.CountReleasesByRepoID(ctx.Repo.Repository.ID, opts)
@ -272,7 +272,7 @@ func CreateRelease(ctx *context.APIContext) {
return return
} }
} }
ctx.JSON(http.StatusCreated, convert.ToRelease(ctx, rel)) ctx.JSON(http.StatusCreated, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel))
} }
// EditRelease edit a release // EditRelease edit a release
@ -357,7 +357,7 @@ func EditRelease(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToRelease(ctx, rel)) ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel))
} }
// DeleteRelease delete a release from a repository // DeleteRelease delete a release from a repository

View File

@ -52,7 +52,7 @@ func GetReleaseAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/Attachment" // "$ref": "#/responses/Attachment"
releaseID := ctx.ParamsInt64(":id") releaseID := ctx.ParamsInt64(":id")
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID) attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil { if err != nil {
if repo_model.IsErrAttachmentNotExist(err) { if repo_model.IsErrAttachmentNotExist(err) {
@ -68,7 +68,7 @@ func GetReleaseAttachment(ctx *context.APIContext) {
return return
} }
// FIXME Should prove the existence of the given repo, but results in unnecessary database requests // FIXME Should prove the existence of the given repo, but results in unnecessary database requests
ctx.JSON(http.StatusOK, convert.ToAttachment(attach)) ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
} }
// ListReleaseAttachments lists all attachments of the release // ListReleaseAttachments lists all attachments of the release
@ -117,7 +117,7 @@ func ListReleaseAttachments(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToRelease(ctx, release).Attachments) ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release).Attachments)
} }
// CreateReleaseAttachment creates an attachment and saves the given file // CreateReleaseAttachment creates an attachment and saves the given file
@ -209,7 +209,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
return return
} }
ctx.JSON(http.StatusCreated, convert.ToAttachment(attach)) ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
} }
// EditReleaseAttachment updates the given attachment // EditReleaseAttachment updates the given attachment
@ -256,7 +256,7 @@ func EditReleaseAttachment(ctx *context.APIContext) {
// Check if release exists an load release // Check if release exists an load release
releaseID := ctx.ParamsInt64(":id") releaseID := ctx.ParamsInt64(":id")
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID) attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil { if err != nil {
if repo_model.IsErrAttachmentNotExist(err) { if repo_model.IsErrAttachmentNotExist(err) {
@ -279,7 +279,7 @@ func EditReleaseAttachment(ctx *context.APIContext) {
if err := repo_model.UpdateAttachment(ctx, attach); err != nil { if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach) ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
} }
ctx.JSON(http.StatusCreated, convert.ToAttachment(attach)) ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
} }
// DeleteReleaseAttachment delete a given attachment // DeleteReleaseAttachment delete a given attachment
@ -318,7 +318,7 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
// Check if release exists an load release // Check if release exists an load release
releaseID := ctx.ParamsInt64(":id") releaseID := ctx.ParamsInt64(":id")
attachID := ctx.ParamsInt64(":asset") attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID) attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil { if err != nil {
if repo_model.IsErrAttachmentNotExist(err) { if repo_model.IsErrAttachmentNotExist(err) {

View File

@ -63,7 +63,7 @@ func GetReleaseByTag(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToRelease(ctx, release)) ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release))
} }
// DeleteReleaseByTag delete a release from a repository by tag name // DeleteReleaseByTag delete a release from a repository by tag name

View File

@ -56,6 +56,7 @@ func getSupportedDbTypeNames() (dbTypeNames []map[string]string) {
func Contexter() func(next http.Handler) http.Handler { func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer() rnd := templates.HTMLRenderer()
dbTypeNames := getSupportedDbTypeNames() dbTypeNames := getSupportedDbTypeNames()
envConfigKeys := setting.CollectEnvConfigKeys()
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base, baseCleanUp := context.NewBaseContext(resp, req) base, baseCleanUp := context.NewBaseContext(resp, req)
@ -70,11 +71,13 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.AppendContextValue(context.WebContextKey, ctx) ctx.AppendContextValue(context.WebContextKey, ctx)
ctx.Data.MergeFrom(middleware.CommonTemplateContextData()) ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
ctx.Data.MergeFrom(middleware.ContextData{ ctx.Data.MergeFrom(middleware.ContextData{
"locale": ctx.Locale, "locale": ctx.Locale,
"Title": ctx.Locale.Tr("install.install"), "Title": ctx.Locale.Tr("install.install"),
"PageIsInstall": true, "PageIsInstall": true,
"DbTypeNames": dbTypeNames, "DbTypeNames": dbTypeNames,
"AllLangs": translation.AllLangs(), "EnvConfigKeys": envConfigKeys,
"CustomConfFile": setting.CustomConf,
"AllLangs": translation.AllLangs(),
"PasswordHashAlgorithms": hash.RecommendedHashAlgorithms, "PasswordHashAlgorithms": hash.RecommendedHashAlgorithms,
}) })
@ -218,7 +221,7 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool {
return false return false
} }
log.Info("User confirmed reinstallation of Gitea into a pre-existing database") log.Info("User confirmed re-installation of Gitea into a pre-existing database")
} }
if hasPostInstallationUser || dbMigrationVersion > 0 { if hasPostInstallationUser || dbMigrationVersion > 0 {
@ -502,6 +505,8 @@ func SubmitInstall(ctx *context.Context) {
return return
} }
setting.EnvironmentToConfig(cfg, os.Environ())
if err = cfg.SaveTo(setting.CustomConf); err != nil { if err = cfg.SaveTo(setting.CustomConf); err != nil {
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
return return
@ -568,6 +573,7 @@ func SubmitInstall(ctx *context.Context) {
} }
} }
setting.ClearEnvConfigKeys()
log.Info("First-time run install finished!") log.Info("First-time run install finished!")
InstallDone(ctx) InstallDone(ctx)

View File

@ -2058,7 +2058,7 @@ func GetIssueInfo(ctx *context.Context) {
} }
} }
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue)) ctx.JSON(http.StatusOK, convert.ToIssue(ctx, issue))
} }
// UpdateIssueTitle change issue's title // UpdateIssueTitle change issue's title
@ -2563,7 +2563,7 @@ func SearchIssues(ctx *context.Context) {
} }
ctx.SetTotalCountHeader(filteredCount) ctx.SetTotalCountHeader(filteredCount)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToIssueList(ctx, issues))
} }
func getUserIDForFilter(ctx *context.Context, queryName string) int64 { func getUserIDForFilter(ctx *context.Context, queryName string) int64 {
@ -2724,7 +2724,7 @@ func ListIssues(ctx *context.Context) {
} }
ctx.SetTotalCountHeader(filteredCount) ctx.SetTotalCountHeader(filteredCount)
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, issues)) ctx.JSON(http.StatusOK, convert.ToIssueList(ctx, issues))
} }
func BatchDeleteIssues(ctx *context.Context) { func BatchDeleteIssues(ctx *context.Context) {
@ -3290,7 +3290,7 @@ func GetIssueAttachments(ctx *context.Context) {
} }
attachments := make([]*api.Attachment, len(issue.Attachments)) attachments := make([]*api.Attachment, len(issue.Attachments))
for i := 0; i < len(issue.Attachments); i++ { for i := 0; i < len(issue.Attachments); i++ {
attachments[i] = convert.ToAttachment(issue.Attachments[i]) attachments[i] = convert.ToAttachment(ctx.Repo.Repository, issue.Attachments[i])
} }
ctx.JSON(http.StatusOK, attachments) ctx.JSON(http.StatusOK, attachments)
} }
@ -3314,7 +3314,7 @@ func GetCommentAttachments(ctx *context.Context) {
return return
} }
for i := 0; i < len(comment.Attachments); i++ { for i := 0; i < len(comment.Attachments); i++ {
attachments = append(attachments, convert.ToAttachment(comment.Attachments[i])) attachments = append(attachments, convert.ToAttachment(ctx.Repo.Repository, comment.Attachments[i]))
} }
ctx.JSON(http.StatusOK, attachments) ctx.JSON(http.StatusOK, attachments)
} }

View File

@ -723,6 +723,9 @@ func ViewPullCommits(ctx *context.Context) {
ctx.Data["Commits"] = commits ctx.Data["Commits"] = commits
ctx.Data["CommitCount"] = len(commits) ctx.Data["CommitCount"] = len(commits)
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
getBranchData(ctx, issue) getBranchData(ctx, issue)
ctx.HTML(http.StatusOK, tplPullCommits) ctx.HTML(http.StatusOK, tplPullCommits)
} }

View File

@ -982,7 +982,7 @@ func renderCode(ctx *context.Context) {
ctx.ServerError("GetBaseRepo", err) ctx.ServerError("GetBaseRepo", err)
return return
} }
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID) ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Repo.Repository.ID, ctx.Doer.ID, ctx.Repo.Repository.DefaultBranch)
if err != nil { if err != nil {
ctx.ServerError("GetRecentlyPushedBranches", err) ctx.ServerError("GetRecentlyPushedBranches", err)
return return

View File

@ -186,7 +186,7 @@ func NotificationStatusPost(ctx *context.Context) {
if ctx.Written() { if ctx.Written() {
return return
} }
ctx.Data["Link"] = setting.AppURL + "notifications" ctx.Data["Link"] = setting.AppSubURL + "/notifications"
ctx.Data["SequenceNumber"] = ctx.Req.PostFormValue("sequence-number") ctx.Data["SequenceNumber"] = ctx.Req.PostFormValue("sequence-number")
ctx.HTML(http.StatusOK, tplNotificationDiv) ctx.HTML(http.StatusOK, tplNotificationDiv)

View File

@ -171,7 +171,7 @@ func (n *actionsNotifier) NotifyCreateIssueComment(ctx context.Context, doer *us
WithPayload(&api.IssueCommentPayload{ WithPayload(&api.IssueCommentPayload{
Action: api.HookIssueCommentCreated, Action: api.HookIssueCommentCreated,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, issue),
Comment: convert.ToComment(ctx, comment), Comment: convert.ToAPIComment(ctx, repo, comment),
Repository: convert.ToRepo(ctx, repo, permission), Repository: convert.ToRepo(ctx, repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
IsPull: true, IsPull: true,
@ -185,7 +185,7 @@ func (n *actionsNotifier) NotifyCreateIssueComment(ctx context.Context, doer *us
WithPayload(&api.IssueCommentPayload{ WithPayload(&api.IssueCommentPayload{
Action: api.HookIssueCommentCreated, Action: api.HookIssueCommentCreated,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, issue),
Comment: convert.ToComment(ctx, comment), Comment: convert.ToAPIComment(ctx, repo, comment),
Repository: convert.ToRepo(ctx, repo, permission), Repository: convert.ToRepo(ctx, repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
IsPull: false, IsPull: false,

View File

@ -260,7 +260,7 @@ func notifyRelease(ctx context.Context, doer *user_model.User, rel *repo_model.R
WithRef(git.RefNameFromTag(rel.TagName).String()). WithRef(git.RefNameFromTag(rel.TagName).String()).
WithPayload(&api.ReleasePayload{ WithPayload(&api.ReleasePayload{
Action: action, Action: action,
Release: convert.ToRelease(ctx, rel), Release: convert.ToAPIRelease(ctx, rel.Repo, rel),
Repository: convert.ToRepo(ctx, rel.Repo, permission), Repository: convert.ToRepo(ctx, rel.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}). }).

View File

@ -37,7 +37,7 @@ func ToActivity(ctx context.Context, ac *activities_model.Action, doer *user_mod
if ac.Comment != nil { if ac.Comment != nil {
result.CommentID = ac.CommentID result.CommentID = ac.CommentID
result.Comment = ToComment(ctx, ac.Comment) result.Comment = ToAPIComment(ctx, ac.Repo, ac.Comment)
} }
return result return result

View File

@ -4,12 +4,38 @@
package convert package convert
import ( import (
"strconv"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
// ToAttachment converts models.Attachment to api.Attachment func WebAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string {
func ToAttachment(a *repo_model.Attachment) *api.Attachment { return attach.DownloadURL()
}
func APIAssetDownloadURL(repo *repo_model.Repository, attach *repo_model.Attachment) string {
if attach.CustomDownloadURL != "" {
return attach.CustomDownloadURL
}
// /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id}
return setting.AppURL + "api/repos/" + repo.FullName() + "/releases/" + strconv.FormatInt(attach.ReleaseID, 10) + "/assets/" + strconv.FormatInt(attach.ID, 10)
}
// ToAttachment converts models.Attachment to api.Attachment for API usage
func ToAttachment(repo *repo_model.Repository, a *repo_model.Attachment) *api.Attachment {
return toAttachment(repo, a, WebAssetDownloadURL)
}
// ToAPIAttachment converts models.Attachment to api.Attachment for API usage
func ToAPIAttachment(repo *repo_model.Repository, a *repo_model.Attachment) *api.Attachment {
return toAttachment(repo, a, APIAssetDownloadURL)
}
// toAttachment converts models.Attachment to api.Attachment for API usage
func toAttachment(repo *repo_model.Repository, a *repo_model.Attachment, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Attachment {
return &api.Attachment{ return &api.Attachment{
ID: a.ID, ID: a.ID,
Name: a.Name, Name: a.Name,
@ -17,14 +43,18 @@ func ToAttachment(a *repo_model.Attachment) *api.Attachment {
DownloadCount: a.DownloadCount, DownloadCount: a.DownloadCount,
Size: a.Size, Size: a.Size,
UUID: a.UUID, UUID: a.UUID,
DownloadURL: a.DownloadURL(), DownloadURL: getDownloadURL(repo, a), // for web request json and api request json, return different download urls
} }
} }
func ToAttachments(attachments []*repo_model.Attachment) []*api.Attachment { func ToAPIAttachments(repo *repo_model.Repository, attachments []*repo_model.Attachment) []*api.Attachment {
return toAttachments(repo, attachments, APIAssetDownloadURL)
}
func toAttachments(repo *repo_model.Repository, attachments []*repo_model.Attachment, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) []*api.Attachment {
converted := make([]*api.Attachment, 0, len(attachments)) converted := make([]*api.Attachment, 0, len(attachments))
for _, attachment := range attachments { for _, attachment := range attachments {
converted = append(converted, ToAttachment(attachment)) converted = append(converted, toAttachment(repo, attachment, getDownloadURL))
} }
return converted return converted
} }

View File

@ -19,11 +19,19 @@ import (
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
func ToIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
return toIssue(ctx, issue, WebAssetDownloadURL)
}
// ToAPIIssue converts an Issue to API format // ToAPIIssue converts an Issue to API format
// it assumes some fields assigned with values: // it assumes some fields assigned with values:
// Required - Poster, Labels, // Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest // Optional - Milestone, Assignee, PullRequest
func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue { func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
return toIssue(ctx, issue, APIAssetDownloadURL)
}
func toIssue(ctx context.Context, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue {
if err := issue.LoadLabels(ctx); err != nil { if err := issue.LoadLabels(ctx); err != nil {
return &api.Issue{} return &api.Issue{}
} }
@ -40,7 +48,7 @@ func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
Poster: ToUser(ctx, issue.Poster, nil), Poster: ToUser(ctx, issue.Poster, nil),
Title: issue.Title, Title: issue.Title,
Body: issue.Content, Body: issue.Content,
Attachments: ToAttachments(issue.Attachments), Attachments: toAttachments(issue.Repo, issue.Attachments, getDownloadURL),
Ref: issue.Ref, Ref: issue.Ref,
State: issue.State(), State: issue.State(),
IsLocked: issue.IsLocked, IsLocked: issue.IsLocked,
@ -105,6 +113,15 @@ func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
return apiIssue return apiIssue
} }
// ToIssueList converts an IssueList to API format
func ToIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue {
result := make([]*api.Issue, len(il))
for i := range il {
result[i] = ToIssue(ctx, il[i])
}
return result
}
// ToAPIIssueList converts an IssueList to API format // ToAPIIssueList converts an IssueList to API format
func ToAPIIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue { func ToAPIIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue {
result := make([]*api.Issue, len(il)) result := make([]*api.Issue, len(il))

View File

@ -14,8 +14,8 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
// ToComment converts a issues_model.Comment to the api.Comment format // ToAPIComment converts a issues_model.Comment to the api.Comment format for API usage
func ToComment(ctx context.Context, c *issues_model.Comment) *api.Comment { func ToAPIComment(ctx context.Context, repo *repo_model.Repository, c *issues_model.Comment) *api.Comment {
return &api.Comment{ return &api.Comment{
ID: c.ID, ID: c.ID,
Poster: ToUser(ctx, c.Poster, nil), Poster: ToUser(ctx, c.Poster, nil),
@ -23,14 +23,14 @@ func ToComment(ctx context.Context, c *issues_model.Comment) *api.Comment {
IssueURL: c.IssueURL(), IssueURL: c.IssueURL(),
PRURL: c.PRURL(), PRURL: c.PRURL(),
Body: c.Content, Body: c.Content,
Attachments: ToAttachments(c.Attachments), Attachments: ToAPIAttachments(repo, c.Attachments),
Created: c.CreatedUnix.AsTime(), Created: c.CreatedUnix.AsTime(),
Updated: c.UpdatedUnix.AsTime(), Updated: c.UpdatedUnix.AsTime(),
} }
} }
// ToTimelineComment converts a issues_model.Comment to the api.TimelineComment format // ToTimelineComment converts a issues_model.Comment to the api.TimelineComment format
func ToTimelineComment(ctx context.Context, c *issues_model.Comment, doer *user_model.User) *api.TimelineComment { func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issues_model.Comment, doer *user_model.User) *api.TimelineComment {
err := c.LoadMilestone(ctx) err := c.LoadMilestone(ctx)
if err != nil { if err != nil {
log.Error("LoadMilestone: %v", err) log.Error("LoadMilestone: %v", err)
@ -143,7 +143,7 @@ func ToTimelineComment(ctx context.Context, c *issues_model.Comment, doer *user_
log.Error("LoadPoster: %v", err) log.Error("LoadPoster: %v", err)
return nil return nil
} }
comment.RefComment = ToComment(ctx, com) comment.RefComment = ToAPIComment(ctx, repo, com)
} }
if c.Label != nil { if c.Label != nil {

View File

@ -10,8 +10,8 @@ import (
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
// ToRelease convert a repo_model.Release to api.Release // ToAPIRelease convert a repo_model.Release to api.Release
func ToRelease(ctx context.Context, r *repo_model.Release) *api.Release { func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_model.Release) *api.Release {
return &api.Release{ return &api.Release{
ID: r.ID, ID: r.ID,
TagName: r.TagName, TagName: r.TagName,
@ -27,6 +27,6 @@ func ToRelease(ctx context.Context, r *repo_model.Release) *api.Release {
CreatedAt: r.CreatedUnix.AsTime(), CreatedAt: r.CreatedUnix.AsTime(),
PublishedAt: r.CreatedUnix.AsTime(), PublishedAt: r.CreatedUnix.AsTime(),
Publisher: ToUser(ctx, r.Publisher, nil), Publisher: ToUser(ctx, r.Publisher, nil),
Attachments: ToAttachments(r.Attachments), Attachments: ToAPIAttachments(repo, r.Attachments),
} }
} }

View File

@ -110,6 +110,11 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
} }
} }
if mergeStyle == repo_model.MergeStyleRebase {
// for fast-forward rebase, do not amend the last commit if there is no template
return "", "", nil
}
// Squash merge has a different from other styles. // Squash merge has a different from other styles.
if mergeStyle == repo_model.MergeStyleSquash { if mergeStyle == repo_model.MergeStyleSquash {
return fmt.Sprintf("%s (%s%d)", pr.Issue.Title, issueReference, pr.Issue.Index), "", nil return fmt.Sprintf("%s (%s%d)", pr.Issue.Title, issueReference, pr.Issue.Index), "", nil

View File

@ -386,7 +386,7 @@ func (m *webhookNotifier) NotifyUpdateComment(ctx context.Context, doer *user_mo
if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited, Action: api.HookIssueCommentEdited,
Issue: convert.ToAPIIssue(ctx, c.Issue), Issue: convert.ToAPIIssue(ctx, c.Issue),
Comment: convert.ToComment(ctx, c), Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c),
Changes: &api.ChangesPayload{ Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{ Body: &api.ChangesFromPayload{
From: oldContent, From: oldContent,
@ -414,7 +414,7 @@ func (m *webhookNotifier) NotifyCreateIssueComment(ctx context.Context, doer *us
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated, Action: api.HookIssueCommentCreated,
Issue: convert.ToAPIIssue(ctx, issue), Issue: convert.ToAPIIssue(ctx, issue),
Comment: convert.ToComment(ctx, comment), Comment: convert.ToAPIComment(ctx, repo, comment),
Repository: convert.ToRepo(ctx, repo, permission), Repository: convert.ToRepo(ctx, repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
IsPull: issue.IsPull, IsPull: issue.IsPull,
@ -451,7 +451,7 @@ func (m *webhookNotifier) NotifyDeleteComment(ctx context.Context, doer *user_mo
if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted, Action: api.HookIssueCommentDeleted,
Issue: convert.ToAPIIssue(ctx, comment.Issue), Issue: convert.ToAPIIssue(ctx, comment.Issue),
Comment: convert.ToComment(ctx, comment), Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission), Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
IsPull: comment.Issue.IsPull, IsPull: comment.Issue.IsPull,
@ -808,7 +808,7 @@ func sendReleaseHook(ctx context.Context, doer *user_model.User, rel *repo_model
permission, _ := access_model.GetUserRepoPermission(ctx, rel.Repo, doer) permission, _ := access_model.GetUserRepoPermission(ctx, rel.Repo, doer)
if err := PrepareWebhooks(ctx, EventSource{Repository: rel.Repo}, webhook_module.HookEventRelease, &api.ReleasePayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: rel.Repo}, webhook_module.HookEventRelease, &api.ReleasePayload{
Action: action, Action: action,
Release: convert.ToRelease(ctx, rel), Release: convert.ToAPIRelease(ctx, rel.Repo, rel),
Repository: convert.ToRepo(ctx, rel.Repo, permission), Repository: convert.ToRepo(ctx, rel.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}); err != nil { }); err != nil {

View File

@ -106,7 +106,6 @@
<input id="attribute_avatar" name="attribute_avatar" value="{{$cfg.AttributeAvatar}}" placeholder="jpegPhoto"> <input id="attribute_avatar" name="attribute_avatar" value="{{$cfg.AttributeAvatar}}" placeholder="jpegPhoto">
</div> </div>
<!-- ldap group begin --> <!-- ldap group begin -->
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">

View File

@ -23,7 +23,6 @@
<button class="item gt-w-auto ui icon mini button gt-p-3 gt-m-0" id="navbar-expand-toggle">{{svg "octicon-three-bars"}}</button> <button class="item gt-w-auto ui icon mini button gt-p-3 gt-m-0" id="navbar-expand-toggle">{{svg "octicon-three-bars"}}</button>
</div> </div>
<!-- navbar links non-mobile --> <!-- navbar links non-mobile -->
{{if and .IsSigned .MustChangePassword}} {{if and .IsSigned .MustChangePassword}}
{{/* No links */}} {{/* No links */}}

View File

@ -1,6 +1,6 @@
{{template "base/head" .}} {{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content install"> <div role="main" aria-label="{{.Title}}" class="page-content install">
<div class="ui middle very relaxed page grid"> <div class="ui grid install-config-container">
<div class="sixteen wide center aligned centered column"> <div class="sixteen wide center aligned centered column">
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{.locale.Tr "install.title"}} {{.locale.Tr "install.title"}}
@ -149,19 +149,18 @@
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="enable_update_checker">{{.locale.Tr "install.enable_update_checker"}}</label> <label>{{.locale.Tr "install.enable_update_checker"}}</label>
<input name="enable_update_checker" type="checkbox"> <input name="enable_update_checker" type="checkbox">
</div> </div>
<span class="help">{{.locale.Tr "install.enable_update_checker_helper"}}</span> <span class="help">{{.locale.Tr "install.enable_update_checker_helper"}}</span>
</div> </div>
<!-- Optional Settings --> <!-- Optional Settings -->
<h4 class="ui dividing header">{{.locale.Tr "install.optional_title"}}</h4> <h4 class="ui dividing header">{{.locale.Tr "install.optional_title"}}</h4>
<!-- Email --> <!-- Email -->
<details class="optional field"> <details class="optional field">
<summary class="title gt-py-3{{if .Err_SMTP}} text red{{end}}"> <summary class="right-content gt-py-3{{if .Err_SMTP}} text red{{end}}">
{{.locale.Tr "install.email_title"}} {{.locale.Tr "install.email_title"}}
</summary> </summary>
<div class="inline field"> <div class="inline field">
@ -201,7 +200,7 @@
<!-- Server and other services --> <!-- Server and other services -->
<details class="optional field"> <details class="optional field">
<summary class="title gt-py-3{{if .Err_Services}} text red{{end}}"> <summary class="right-content gt-py-3{{if .Err_Services}} text red{{end}}">
{{.locale.Tr "install.server_service_title"}} {{.locale.Tr "install.server_service_title"}}
</summary> </summary>
<div class="inline field"> <div class="inline field">
@ -299,7 +298,7 @@
<!-- Admin --> <!-- Admin -->
<details class="optional field"> <details class="optional field">
<summary class="title gt-py-3{{if .Err_Admin}} text red{{end}}"> <summary class="right-content gt-py-3{{if .Err_Admin}} text red{{end}}">
{{.locale.Tr "install.admin_title"}} {{.locale.Tr "install.admin_title"}}
</summary> </summary>
<p class="center">{{.locale.Tr "install.admin_setting_desc"}}</p> <p class="center">{{.locale.Tr "install.admin_setting_desc"}}</p>
@ -321,10 +320,27 @@
</div> </div>
</details> </details>
{{if .EnvConfigKeys}}
<!-- Environment Config -->
<h4 class="ui dividing header">{{.locale.Tr "install.env_config_keys"}}</h4>
<div class="inline field">
<div class="right-content">
{{.locale.Tr "install.env_config_keys_prompt"}}
</div>
<div class="right-content gt-mt-3">
{{range .EnvConfigKeys}}<span class="ui label">{{.}}</span>{{end}}
</div>
</div>
{{end}}
<div class="divider"></div> <div class="divider"></div>
<div class="inline field"> <div class="inline field">
<label></label> <div class="right-content">
<button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button> These configuration options will be written into: {{.CustomConfFile}}
</div>
<div class="right-content gt-mt-3">
<button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button>
</div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -14,13 +14,13 @@
</form> </form>
<div class="ui {{if .PackageDescriptors}}issue list{{end}}"> <div class="ui {{if .PackageDescriptors}}issue list{{end}}">
{{range .PackageDescriptors}} {{range .PackageDescriptors}}
<li class="item gt-df gt-py-3"> <li class="item">
<div class="issue-item-main"> <div class="issue-item-main">
<div class="issue-item-top-row"> <div class="issue-item-title">
<a class="title" href="{{.FullWebLink}}">{{.Package.Name}}</a> <a class="title" href="{{.FullWebLink}}">{{.Package.Name}}</a>
<span class="ui label">{{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}</span> <span class="ui label">{{svg .Package.Type.SVGName 16}} {{.Package.Type.Name}}</span>
</div> </div>
<div class="desc issue-item-bottom-row"> <div class="issue-item-body">
{{$timeStr := TimeSinceUnix .Version.CreatedUnix $.locale}} {{$timeStr := TimeSinceUnix .Version.CreatedUnix $.locale}}
{{$hasRepositoryAccess := false}} {{$hasRepositoryAccess := false}}
{{if .Repository}} {{if .Repository}}

View File

@ -20,12 +20,12 @@
</form> </form>
<div class="ui {{if .PackageDescriptors}}issue list{{end}}"> <div class="ui {{if .PackageDescriptors}}issue list{{end}}">
{{range .PackageDescriptors}} {{range .PackageDescriptors}}
<li class="item gt-df gt-py-3"> <li class="item">
<div class="issue-item-main"> <div class="issue-item-main">
<div class="issue-item-top-row"> <div class="issue-item-title">
<a class="title" href="{{.FullWebLink}}">{{.Version.LowerVersion}}</a> <a class="title" href="{{.FullWebLink}}">{{.Version.LowerVersion}}</a>
</div> </div>
<div class="desc issue-item-bottom-row"> <div class="issue-item-body">
{{$.locale.Tr "packages.published_by" (TimeSinceUnix .Version.CreatedUnix $.locale) .Creator.HomeLink (.Creator.GetDisplayName | Escape) | Safe}} {{$.locale.Tr "packages.published_by" (TimeSinceUnix .Version.CreatedUnix $.locale) .Creator.HomeLink (.Creator.GetDisplayName | Escape) | Safe}}
</div> </div>
</div> </div>

View File

@ -6,17 +6,17 @@
</div> </div>
{{end}} {{end}}
{{range .Runs}} {{range .Runs}}
<li class="item gt-df gt-py-3"> <li class="item action-item">
<div class="issue-item-left issue-item-icon gt-df gt-items-start"> <div class="issue-item-left issue-item-icon">
{{template "repo/actions/status" (dict "status" .Status.String "locale" $.locale)}} {{template "repo/actions/status" (dict "status" .Status.String "locale" $.locale)}}
</div> </div>
<div class="issue-item-main action-item-main"> <div class="issue-item-main action-item-main">
<div class="issue-item-top-row"> <div class="issue-item-title">
<a class="index gt-no-underline title action-item-title" title="{{.Title}}" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}"> <a class="index gt-no-underline title action-item-title" title="{{.Title}}" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{- .Title -}} {{- .Title -}}
</a> </a>
</div> </div>
<div class="desc issue-item-bottom-row"> <div class="issue-item-body">
<b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b> <b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>
: {{$.locale.Tr "actions.runs.commit"}} : {{$.locale.Tr "actions.runs.commit"}}
<a href="{{$.RepoLink}}/commit/{{.CommitSHA}}">{{ShortSha .CommitSHA}}</a> <a href="{{$.RepoLink}}/commit/{{.CommitSHA}}">{{ShortSha .CommitSHA}}</a>
@ -32,8 +32,8 @@
{{end}} {{end}}
</div> </div>
<div class="action-item-right"> <div class="action-item-right">
<div>{{svg "octicon-calendar" 16 "gt-mr-2"}}{{TimeSinceUnix .Updated $.locale}}</div> <div class="flex-text-block">{{svg "octicon-calendar" 16}}{{TimeSinceUnix .Updated $.locale}}</div>
<div>{{svg "octicon-stopwatch" 16 "gt-mr-2"}}{{.Duration}}</div> <div class="flex-text-block">{{svg "octicon-stopwatch" 16}}{{.Duration}}</div>
</div> </div>
</li> </li>
{{end}} {{end}}

View File

@ -101,7 +101,7 @@
{{template "repo/diff/stats" dict "file" . "root" $}} {{template "repo/diff/stats" dict "file" . "root" $}}
{{end}} {{end}}
</div> </div>
<span class="file gt-mono"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}} &rarr; {{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}} &rarr; {{end}}{{$file.Name}}</a>{{if .IsLFSFile}} ({{$.locale.Tr "repo.stored_lfs"}}){{end}}</span> <span class="file gt-mono"><a class="muted file-link" title="{{if $file.IsRenamed}}{{$file.OldName}} {{end}}{{$file.Name}}" href="#diff-{{$file.NameHash}}">{{if $file.IsRenamed}}{{$file.OldName}} {{end}}{{$file.Name}}</a>{{if .IsLFSFile}} ({{$.locale.Tr "repo.stored_lfs"}}){{end}}</span>
<button class="btn interact-fg gt-p-3" data-clipboard-text="{{$file.Name}}">{{svg "octicon-copy" 14}}</button> <button class="btn interact-fg gt-p-3" data-clipboard-text="{{$file.Name}}">{{svg "octicon-copy" 14}}</button>
{{if $file.IsGenerated}} {{if $file.IsGenerated}}
<span class="ui label">{{$.locale.Tr "repo.diff.generated"}}</span> <span class="ui label">{{$.locale.Tr "repo.diff.generated"}}</span>
@ -110,7 +110,7 @@
<span class="ui label">{{$.locale.Tr "repo.diff.vendored"}}</span> <span class="ui label">{{$.locale.Tr "repo.diff.vendored"}}</span>
{{end}} {{end}}
{{if and $file.Mode $file.OldMode}} {{if and $file.Mode $file.OldMode}}
<span class="gt-ml-4 gt-mono">{{$file.OldMode}} &rarr; {{$file.Mode}}</span> <span class="gt-ml-4 gt-mono">{{$file.OldMode}} {{$file.Mode}}</span>
{{else if $file.Mode}} {{else if $file.Mode}}
<span class="gt-ml-4 gt-mono">{{$file.Mode}}</span> <span class="gt-ml-4 gt-mono">{{$file.Mode}}</span>
{{end}} {{end}}

View File

@ -52,8 +52,6 @@
{{template "repo/editor/commit_form" .}} {{template "repo/editor/commit_form" .}}
</form> </form>
</div> </div>
<div class="ui g-modal-confirm modal" id="edit-empty-content-modal"> <div class="ui g-modal-confirm modal" id="edit-empty-content-modal">
<div class="header"> <div class="header">
{{svg "octicon-file"}} {{svg "octicon-file"}}
@ -73,6 +71,5 @@
</button> </button>
</div> </div>
</div> </div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View File

@ -44,30 +44,25 @@
<label for="website">{{.locale.Tr "repo.settings.site"}}</label> <label for="website">{{.locale.Tr "repo.settings.site"}}</label>
<input id="website" name="website" type="url" maxlength="1024" value="{{.Repository.Website}}"> <input id="website" name="website" type="url" maxlength="1024" value="{{.Repository.Website}}">
</div> </div>
<div class="field"> <div class="field">
<button class="ui green button">{{$.locale.Tr "repo.settings.update_settings"}}</button> <button class="ui green button">{{$.locale.Tr "repo.settings.update_settings"}}</button>
</div> </div>
</form> </form>
<div class="divider"></div> <div class="divider"></div>
<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> <form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="inline field"> <div class="inline field">
<label for="avatar">{{.locale.Tr "settings.choose_new_avatar"}}</label> <label for="avatar">{{.locale.Tr "settings.choose_new_avatar"}}</label>
<input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp"> <input name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
</div> </div>
<div class="field"> <div class="field">
<button class="ui green button">{{$.locale.Tr "settings.update_avatar"}}</button> <button class="ui green button">{{$.locale.Tr "settings.update_avatar"}}</button>
<button class="ui red button link-action" data-url="{{.Link}}/avatar/delete" data-redirect="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</button> <button class="ui red button link-action" data-url="{{.Link}}/avatar/delete" data-redirect="{{.Link}}">{{$.locale.Tr "settings.delete_current_avatar"}}</button>
</div> </div>
</form> </form>
</div> </div>
{{/* These variables exist to make the logic in the Settings window easier to comprehend and are not used later on. */}} {{/* These variables exist to make the logic in the Settings window easier to comprehend and are not used later on. */}}
{{$newMirrorsPartiallyEnabled := or (not .DisableNewPullMirrors) (not .DisableNewPushMirrors)}} {{$newMirrorsPartiallyEnabled := or (not .DisableNewPullMirrors) (not .DisableNewPushMirrors)}}
{{/* .Repository.IsMirror is not always reliable if the repository is not actively acting as a mirror because of errors. */}} {{/* .Repository.IsMirror is not always reliable if the repository is not actively acting as a mirror because of errors. */}}

View File

@ -75,5 +75,4 @@
</div> </div>
{{end}} {{end}}
{{template "base/footer" .}} {{template "base/footer" .}}

View File

@ -30,16 +30,11 @@
</div> </div>
</div> </div>
</h4> </h4>
{{if and .Commits (gt .CommitCount 0)}} {{if and .Commits (gt .CommitCount 0)}}
{{template "repo/commits_list" .}} {{template "repo/commits_list" .}}
{{end}} {{end}}
{{template "base/paginate" .}} {{template "base/paginate" .}}
</div> </div>
</div> </div>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View File

@ -1,8 +1,8 @@
<div class="issue list"> <div class="issue list">
{{$approvalCounts := .ApprovalCounts}} {{$approvalCounts := .ApprovalCounts}}
{{range .Issues}} {{range .Issues}}
<li class="item gt-df gt-py-3"> <li class="item">
<div class="issue-item-left gt-df gt-items-start"> <div class="issue-item-left">
{{if $.CanWriteIssuesOrPulls}} {{if $.CanWriteIssuesOrPulls}}
<input type="checkbox" autocomplete="off" class="issue-checkbox gt-mt-2 gt-mr-4" data-issue-id={{.ID}} aria-label="{{$.locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;"> <input type="checkbox" autocomplete="off" class="issue-checkbox gt-mt-2 gt-mr-4" data-issue-id={{.ID}} aria-label="{{$.locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;">
{{end}} {{end}}
@ -11,21 +11,49 @@
</div> </div>
</div> </div>
<div class="issue-item-main"> <div class="issue-item-main">
<div class="issue-item-top-row"> <div class="issue-item-header">
<a class="title gt-no-underline issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji $.Context .Title | RenderCodeBlock}}</a> <div class="issue-item-title">
{{if .IsPull}} <a class="title gt-no-underline issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji $.Context .Title | RenderCodeBlock}}</a>
{{if (index $.CommitStatuses .PullRequest.ID)}} {{if .IsPull}}
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID) "root" $}} {{if (index $.CommitStatuses .PullRequest.ID)}}
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID) "root" $}}
{{end}}
{{end}} {{end}}
<span class="labels-list gt-ml-2">
{{range .Labels}}
<a href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{RenderLabel $.Context .}}</a>
{{end}}
</span>
</div>
{{if or .TotalTrackedTime .Assignees .NumComments}}
<div class="issue-item-right">
{{if .TotalTrackedTime}}
<div class="text grey flex-text-block">
{{svg "octicon-clock" 16}}
{{.TotalTrackedTime | Sec2Time}}
</div>
{{end}}
{{if .Assignees}}
<div class="text grey">
{{range .Assignees}}
<a class="ui assignee gt-no-underline" href="{{.HomeLink}}" data-tooltip-content="{{.GetDisplayName}}">
{{avatar $.Context . 20}}
</a>
{{end}}
</div>
{{end}}
{{if .NumComments}}
<div class="text grey">
<a class="gt-no-underline muted flex-text-inline" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{svg "octicon-comment" 16}}{{.NumComments}}
</a>
</div>
{{end}}
</div>
{{end}} {{end}}
<span class="labels-list gt-ml-2">
{{range .Labels}}
<a href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{RenderLabel $.Context .}}</a>
{{end}}
</span>
</div> </div>
<div class="desc issue-item-bottom-row"> <div class="issue-item-body">
<a class="index gt-ml-0 gt-mr-2" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}"> <a class="index" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{if eq $.listType "dashboard"}} {{if eq $.listType "dashboard"}}
{{.Repo.FullName}}#{{.Index}} {{.Repo.FullName}}#{{.Index}}
{{else}} {{else}}
@ -41,14 +69,14 @@
{{$.locale.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}} {{$.locale.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}}
{{end}} {{end}}
{{if .IsPull}} {{if .IsPull}}
<div class="branches gt-df gt-ac"> <div class="branches flex-text-inline">
<div class="branch"> <div class="branch">
<a href="{{.PullRequest.BaseRepo.Link}}/src/branch/{{PathEscapeSegments .PullRequest.BaseBranch}}"> <a href="{{.PullRequest.BaseRepo.Link}}/src/branch/{{PathEscapeSegments .PullRequest.BaseBranch}}">
{{/* inline to remove the spaces between spans */}} {{/* inline to remove the spaces between spans */}}
{{if ne .RepoID .PullRequest.BaseRepoID}}<span class="truncated-name">{{.PullRequest.BaseRepo.OwnerName}}</span>:{{end}}<span class="truncated-name">{{.PullRequest.BaseBranch}}</span> {{if ne .RepoID .PullRequest.BaseRepoID}}<span class="truncated-name">{{.PullRequest.BaseRepo.OwnerName}}</span>:{{end}}<span class="truncated-name">{{.PullRequest.BaseBranch}}</span>
</a> </a>
</div> </div>
{{svg "gitea-double-chevron-left" 12 "gt-mx-1"}} {{svg "gitea-double-chevron-left" 12}}
{{if .PullRequest.HeadRepo}} {{if .PullRequest.HeadRepo}}
<div class="branch"> <div class="branch">
<a href="{{.PullRequest.HeadRepo.Link}}/src/branch/{{PathEscapeSegments .PullRequest.HeadBranch}}"> <a href="{{.PullRequest.HeadRepo.Link}}/src/branch/{{PathEscapeSegments .PullRequest.HeadBranch}}">
@ -60,32 +88,32 @@
</div> </div>
{{end}} {{end}}
{{if and .Milestone (ne $.listType "milestone")}} {{if and .Milestone (ne $.listType "milestone")}}
<a class="milestone" {{if $.RepoLink}}href="{{$.RepoLink}}/milestone/{{.Milestone.ID}}"{{else}}href="{{.Repo.Link}}/milestone/{{.Milestone.ID}}"{{end}}> <a class="milestone flex-text-inline" {{if $.RepoLink}}href="{{$.RepoLink}}/milestone/{{.Milestone.ID}}"{{else}}href="{{.Repo.Link}}/milestone/{{.Milestone.ID}}"{{end}}>
{{svg "octicon-milestone" 14 "gt-mr-2"}}{{.Milestone.Name}} {{svg "octicon-milestone" 14}}{{.Milestone.Name}}
</a> </a>
{{end}} {{end}}
{{if .Project}} {{if .Project}}
<a class="project" href="{{.Project.Link}}"> <a class="project flex-text-inline" href="{{.Project.Link}}">
{{svg .Project.IconName 14 "gt-mr-2"}}{{.Project.Title}} {{svg .Project.IconName 14}}{{.Project.Title}}
</a> </a>
{{end}} {{end}}
{{if .Ref}} {{if .Ref}}
<a class="ref" {{if $.RepoLink}}href="{{index $.IssueRefURLs .ID}}"{{else}}href="{{.Repo.Link}}{{index $.IssueRefURLs .ID}}"{{end}}> <a class="ref flex-text-inline" {{if $.RepoLink}}href="{{index $.IssueRefURLs .ID}}"{{else}}href="{{.Repo.Link}}{{index $.IssueRefURLs .ID}}"{{end}}>
{{svg "octicon-git-branch" 14 "gt-mr-2"}}{{index $.IssueRefEndNames .ID}} {{svg "octicon-git-branch" 14}}{{index $.IssueRefEndNames .ID}}
</a> </a>
{{end}} {{end}}
{{$tasks := .GetTasks}} {{$tasks := .GetTasks}}
{{if gt $tasks 0}} {{if gt $tasks 0}}
{{$tasksDone := .GetTasksDone}} {{$tasksDone := .GetTasksDone}}
<span class="checklist"> <span class="checklist flex-text-inline">
{{svg "octicon-checklist" 14 "gt-mr-2"}}{{$tasksDone}} / {{$tasks}} {{svg "octicon-checklist" 14}}{{$tasksDone}} / {{$tasks}}
<progress value="{{$tasksDone}}" max="{{$tasks}}"></progress> <progress value="{{$tasksDone}}" max="{{$tasks}}"></progress>
</span> </span>
{{end}} {{end}}
{{if ne .DeadlineUnix 0}} {{if ne .DeadlineUnix 0}}
<span class="due-date" data-tooltip-content="{{$.locale.Tr "repo.issues.due_date"}}"> <span class="due-date flex-text-inline" data-tooltip-content="{{$.locale.Tr "repo.issues.due_date"}}">
<span{{if .IsOverdue}} class="text red"{{end}}> <span{{if .IsOverdue}} class="text red"{{end}}>
{{svg "octicon-calendar" 14 "gt-mr-2"}} {{svg "octicon-calendar" 14}}
{{DateTime "short" .DeadlineUnix}} {{DateTime "short" .DeadlineUnix}}
</span> </span>
</span> </span>
@ -95,25 +123,25 @@
{{$rejectOfficial := call $approvalCounts .ID "reject"}} {{$rejectOfficial := call $approvalCounts .ID "reject"}}
{{$waitingOfficial := call $approvalCounts .ID "waiting"}} {{$waitingOfficial := call $approvalCounts .ID "waiting"}}
{{if gt $approveOfficial 0}} {{if gt $approveOfficial 0}}
<span class="approvals gt-df gt-ac green"> <span class="approvals green flex-text-inline">
{{svg "octicon-check" 14 "gt-mr-1"}} {{svg "octicon-check" 14}}
{{$.locale.TrN $approveOfficial "repo.pulls.approve_count_1" "repo.pulls.approve_count_n" $approveOfficial}} {{$.locale.TrN $approveOfficial "repo.pulls.approve_count_1" "repo.pulls.approve_count_n" $approveOfficial}}
</span> </span>
{{end}} {{end}}
{{if gt $rejectOfficial 0}} {{if gt $rejectOfficial 0}}
<span class="rejects gt-df gt-ac red"> <span class="rejects red flex-text-inline">
{{svg "octicon-diff" 14 "gt-mr-2"}} {{svg "octicon-diff" 14}}
{{$.locale.TrN $rejectOfficial "repo.pulls.reject_count_1" "repo.pulls.reject_count_n" $rejectOfficial}} {{$.locale.TrN $rejectOfficial "repo.pulls.reject_count_1" "repo.pulls.reject_count_n" $rejectOfficial}}
</span> </span>
{{end}} {{end}}
{{if gt $waitingOfficial 0}} {{if gt $waitingOfficial 0}}
<span class="waiting gt-df gt-ac"> <span class="waiting flex-text-inline">
{{svg "octicon-eye" 14 "gt-mr-2"}} {{svg "octicon-eye" 14}}
{{$.locale.TrN $waitingOfficial "repo.pulls.waiting_count_1" "repo.pulls.waiting_count_n" $waitingOfficial}} {{$.locale.TrN $waitingOfficial "repo.pulls.waiting_count_1" "repo.pulls.waiting_count_n" $waitingOfficial}}
</span> </span>
{{end}} {{end}}
{{if and (not .PullRequest.HasMerged) (gt (len .PullRequest.ConflictedFiles) 0)}} {{if and (not .PullRequest.HasMerged) (gt (len .PullRequest.ConflictedFiles) 0)}}
<span class="conflicting gt-df gt-ac"> <span class="conflicting flex-text-inline">
{{svg "octicon-x" 14}} {{svg "octicon-x" 14}}
{{$.locale.TrN (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n" (len .PullRequest.ConflictedFiles)}} {{$.locale.TrN (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n" (len .PullRequest.ConflictedFiles)}}
</span> </span>
@ -121,32 +149,6 @@
{{end}} {{end}}
</div> </div>
</div> </div>
{{if or .TotalTrackedTime .Assignees .NumComments}}
<div class="issue-item-icons-right gt-df gt-p-2">
{{if .TotalTrackedTime}}
<div class="issue-item-icon-right text grey">
{{svg "octicon-clock" 16 "gt-mr-2"}}
{{.TotalTrackedTime | Sec2Time}}
</div>
{{end}}
{{if .Assignees}}
<div class="issue-item-icon-right text grey">
{{range .Assignees}}
<a class="ui assignee gt-no-underline" href="{{.HomeLink}}" data-tooltip-content="{{.GetDisplayName}}">
{{avatar $.Context . 20}}
</a>
{{end}}
</div>
{{end}}
{{if .NumComments}}
<div class="issue-item-icon-right text grey">
<a class="gt-no-underline muted" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{svg "octicon-comment" 16 "gt-mr-2"}}{{.NumComments}}
</a>
</div>
{{end}}
</div>
{{end}}
</li> </li>
{{end}} {{end}}
{{if .IssueIndexerUnavailable}} {{if .IssueIndexerUnavailable}}

View File

@ -11,13 +11,10 @@
<label for="password">{{.locale.Tr "password"}}</label> <label for="password">{{.locale.Tr "password"}}</label>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="new-password" required> <input id="password" name="password" type="password" value="{{.password}}" autocomplete="new-password" required>
</div> </div>
<div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}"> <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
<label for="retype">{{.locale.Tr "re_type"}}</label> <label for="retype">{{.locale.Tr "re_type"}}</label>
<input id="retype" name="retype" type="password" autocomplete="new-password" required> <input id="retype" name="retype" type="password" autocomplete="new-password" required>
</div> </div>
<div class="inline field"> <div class="inline field">
<label></label> <label></label>
<button class="ui green button">{{.locale.Tr "settings.change_password"}}</button> <button class="ui green button">{{.locale.Tr "settings.change_password"}}</button>

View File

@ -1,4 +1,4 @@
<div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_div" data-params="{{.Page.GetParams}}" data-sequence-number="{{.SequenceNumber}}"> <div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_div" data-sequence-number="{{.SequenceNumber}}">
<div class="ui container"> <div class="ui container">
{{$notificationUnreadCount := call .NotificationUnreadCount}} {{$notificationUnreadCount := call .NotificationUnreadCount}}
<div class="gt-df gt-ac gt-sb gt-mb-4"> <div class="gt-df gt-ac gt-sb gt-mb-4">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}} {{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content user notification" id="notification_subscriptions" data-params="{{.Page.GetParams}}" data-sequence-number="{{.SequenceNumber}}"> <div role="main" aria-label="{{.Title}}" class="page-content user notification">
<div class="ui container"> <div class="ui container">
<div class="ui top attached tabular menu"> <div class="ui top attached tabular menu">
<a href="{{AppSubUrl}}/notifications/subscriptions" class="{{if eq .Status 1}}active {{end}}item"> <a href="{{AppSubUrl}}/notifications/subscriptions" class="{{if eq .Status 1}}active {{end}}item">

View File

@ -45,11 +45,12 @@ func TestAPIGetCommentAttachment(t *testing.T) {
var apiAttachment api.Attachment var apiAttachment api.Attachment
DecodeJSON(t, resp, &apiAttachment) DecodeJSON(t, resp, &apiAttachment)
expect := convert.ToAttachment(attachment) expect := convert.ToAPIAttachment(repo, attachment)
assert.Equal(t, expect.ID, apiAttachment.ID) assert.Equal(t, expect.ID, apiAttachment.ID)
assert.Equal(t, expect.Name, apiAttachment.Name) assert.Equal(t, expect.Name, apiAttachment.Name)
assert.Equal(t, expect.UUID, apiAttachment.UUID) assert.Equal(t, expect.UUID, apiAttachment.UUID)
assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix()) assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix())
assert.Equal(t, expect.DownloadURL, apiAttachment.DownloadURL)
} }
func TestAPIListCommentAttachments(t *testing.T) { func TestAPIListCommentAttachments(t *testing.T) {

View File

@ -128,7 +128,7 @@ func TestAPIGetComment(t *testing.T) {
DecodeJSON(t, resp, &apiComment) DecodeJSON(t, resp, &apiComment)
assert.NoError(t, comment.LoadPoster(db.DefaultContext)) assert.NoError(t, comment.LoadPoster(db.DefaultContext))
expect := convert.ToComment(db.DefaultContext, comment) expect := convert.ToAPIComment(db.DefaultContext, repo, comment)
assert.Equal(t, expect.ID, apiComment.ID) assert.Equal(t, expect.ID, apiComment.ID)
assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName) assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName)

View File

@ -170,9 +170,9 @@ func TestAPIGetAll(t *testing.T) {
var apiOrgList []*api.Organization var apiOrgList []*api.Organization
DecodeJSON(t, resp, &apiOrgList) DecodeJSON(t, resp, &apiOrgList)
assert.Len(t, apiOrgList, 9) assert.Len(t, apiOrgList, 11)
assert.Equal(t, "org25", apiOrgList[1].FullName) assert.Equal(t, "Limited Org 36", apiOrgList[1].FullName)
assert.Equal(t, "public", apiOrgList[1].Visibility) assert.Equal(t, "limited", apiOrgList[1].Visibility)
// accessing without a token will return only public orgs // accessing without a token will return only public orgs
req = NewRequestf(t, "GET", "/api/v1/orgs") req = NewRequestf(t, "GET", "/api/v1/orgs")

View File

@ -157,29 +157,227 @@ func TestPackageAccess(t *testing.T) {
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
inactive := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 9}) inactive := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 9})
privatedOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) limitedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 33})
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31})
privateOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) // user has package write access
limitedOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 36}) // user has package write access
publicOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 25}) // user has package read access
privateOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 35})
limitedOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22})
publicOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17})
uploadPackage := func(doer, owner *user_model.User, expectedStatus int) { uploadPackage := func(doer, owner *user_model.User, filename string, expectedStatus int) {
url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/file.bin", owner.Name) url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/%s.bin", owner.Name, filename)
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})) req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
AddBasicAuthHeader(req, doer.Name) if doer != nil {
AddBasicAuthHeader(req, doer.Name)
}
MakeRequest(t, req, expectedStatus) MakeRequest(t, req, expectedStatus)
} }
uploadPackage(user, inactive, http.StatusUnauthorized) downloadPackage := func(doer, owner *user_model.User, expectedStatus int) {
uploadPackage(inactive, inactive, http.StatusUnauthorized) url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/admin.bin", owner.Name)
uploadPackage(inactive, user, http.StatusUnauthorized) req := NewRequest(t, "GET", url)
uploadPackage(admin, inactive, http.StatusCreated) if doer != nil {
uploadPackage(admin, user, http.StatusCreated) AddBasicAuthHeader(req, doer.Name)
}
MakeRequest(t, req, expectedStatus)
}
// team.authorize is write, but team_unit.access_mode is none type Target struct {
// so the user can not upload packages or get package list Owner *user_model.User
uploadPackage(user, privatedOrg, http.StatusUnauthorized) ExpectedStatus int
}
session := loginUser(t, user.Name) t.Run("Upload", func(t *testing.T) {
tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage) defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s?token=%s", privatedOrg.Name, tokenReadPackage))
MakeRequest(t, req, http.StatusForbidden) cases := []struct {
Doer *user_model.User
Filename string
Targets []Target
}{
{ // Admins can upload to every owner
Doer: admin,
Filename: "admin",
Targets: []Target{
{admin, http.StatusCreated},
{inactive, http.StatusCreated},
{user, http.StatusCreated},
{limitedUser, http.StatusCreated},
{privateUser, http.StatusCreated},
{privateOrgMember, http.StatusCreated},
{limitedOrgMember, http.StatusCreated},
{publicOrgMember, http.StatusCreated},
{privateOrgNoMember, http.StatusCreated},
{limitedOrgNoMember, http.StatusCreated},
{publicOrgNoMember, http.StatusCreated},
},
},
{ // Without credentials no upload should be possible
Doer: nil,
Filename: "nil",
Targets: []Target{
{admin, http.StatusUnauthorized},
{inactive, http.StatusUnauthorized},
{user, http.StatusUnauthorized},
{limitedUser, http.StatusUnauthorized},
{privateUser, http.StatusUnauthorized},
{privateOrgMember, http.StatusUnauthorized},
{limitedOrgMember, http.StatusUnauthorized},
{publicOrgMember, http.StatusUnauthorized},
{privateOrgNoMember, http.StatusUnauthorized},
{limitedOrgNoMember, http.StatusUnauthorized},
{publicOrgNoMember, http.StatusUnauthorized},
},
},
{ // Inactive users can't upload anywhere
Doer: inactive,
Filename: "inactive",
Targets: []Target{
{admin, http.StatusUnauthorized},
{inactive, http.StatusUnauthorized},
{user, http.StatusUnauthorized},
{limitedUser, http.StatusUnauthorized},
{privateUser, http.StatusUnauthorized},
{privateOrgMember, http.StatusUnauthorized},
{limitedOrgMember, http.StatusUnauthorized},
{publicOrgMember, http.StatusUnauthorized},
{privateOrgNoMember, http.StatusUnauthorized},
{limitedOrgNoMember, http.StatusUnauthorized},
{publicOrgNoMember, http.StatusUnauthorized},
},
},
{ // Normal users can upload to self and orgs in which they are members and have package write access
Doer: user,
Filename: "user",
Targets: []Target{
{admin, http.StatusUnauthorized},
{inactive, http.StatusUnauthorized},
{user, http.StatusCreated},
{limitedUser, http.StatusUnauthorized},
{privateUser, http.StatusUnauthorized},
{privateOrgMember, http.StatusCreated},
{limitedOrgMember, http.StatusCreated},
{publicOrgMember, http.StatusUnauthorized},
{privateOrgNoMember, http.StatusUnauthorized},
{limitedOrgNoMember, http.StatusUnauthorized},
{publicOrgNoMember, http.StatusUnauthorized},
},
},
}
for _, c := range cases {
for _, t := range c.Targets {
uploadPackage(c.Doer, t.Owner, c.Filename, t.ExpectedStatus)
}
}
})
t.Run("Download", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
cases := []struct {
Doer *user_model.User
Filename string
Targets []Target
}{
{ // Admins can access everything
Doer: admin,
Targets: []Target{
{admin, http.StatusOK},
{inactive, http.StatusOK},
{user, http.StatusOK},
{limitedUser, http.StatusOK},
{privateUser, http.StatusOK},
{privateOrgMember, http.StatusOK},
{limitedOrgMember, http.StatusOK},
{publicOrgMember, http.StatusOK},
{privateOrgNoMember, http.StatusOK},
{limitedOrgNoMember, http.StatusOK},
{publicOrgNoMember, http.StatusOK},
},
},
{ // Without credentials only public owners are accessible
Doer: nil,
Targets: []Target{
{admin, http.StatusOK},
{inactive, http.StatusOK},
{user, http.StatusOK},
{limitedUser, http.StatusUnauthorized},
{privateUser, http.StatusUnauthorized},
{privateOrgMember, http.StatusUnauthorized},
{limitedOrgMember, http.StatusUnauthorized},
{publicOrgMember, http.StatusOK},
{privateOrgNoMember, http.StatusUnauthorized},
{limitedOrgNoMember, http.StatusUnauthorized},
{publicOrgNoMember, http.StatusOK},
},
},
{ // Inactive users have no access
Doer: inactive,
Targets: []Target{
{admin, http.StatusUnauthorized},
{inactive, http.StatusUnauthorized},
{user, http.StatusUnauthorized},
{limitedUser, http.StatusUnauthorized},
{privateUser, http.StatusUnauthorized},
{privateOrgMember, http.StatusUnauthorized},
{limitedOrgMember, http.StatusUnauthorized},
{publicOrgMember, http.StatusUnauthorized},
{privateOrgNoMember, http.StatusUnauthorized},
{limitedOrgNoMember, http.StatusUnauthorized},
{publicOrgNoMember, http.StatusUnauthorized},
},
},
{ // Normal users can access self, public or limited users/orgs and private orgs in which they are members
Doer: user,
Targets: []Target{
{admin, http.StatusOK},
{inactive, http.StatusOK},
{user, http.StatusOK},
{limitedUser, http.StatusOK},
{privateUser, http.StatusUnauthorized},
{privateOrgMember, http.StatusOK},
{limitedOrgMember, http.StatusOK},
{publicOrgMember, http.StatusOK},
{privateOrgNoMember, http.StatusUnauthorized},
{limitedOrgNoMember, http.StatusOK},
{publicOrgNoMember, http.StatusOK},
},
},
}
for _, c := range cases {
for _, target := range c.Targets {
downloadPackage(c.Doer, target.Owner, target.ExpectedStatus)
}
}
})
t.Run("API", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, user.Name)
tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)
for _, target := range []Target{
{admin, http.StatusOK},
{inactive, http.StatusOK},
{user, http.StatusOK},
{limitedUser, http.StatusOK},
{privateUser, http.StatusForbidden},
{privateOrgMember, http.StatusOK},
{limitedOrgMember, http.StatusOK},
{publicOrgMember, http.StatusOK},
{privateOrgNoMember, http.StatusForbidden},
{limitedOrgNoMember, http.StatusOK},
{publicOrgNoMember, http.StatusOK},
} {
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s?token=%s", target.Owner.Name, tokenReadPackage))
MakeRequest(t, req, target.ExpectedStatus)
}
})
} }
func TestPackageQuota(t *testing.T) { func TestPackageQuota(t *testing.T) {

View File

@ -1,5 +1,6 @@
.page-content.install { .page-content.install .install-config-container {
padding-top: 45px; max-width: 900px;
margin: auto;
} }
.page-content.install form.ui.form .inline.field > label { .page-content.install form.ui.form .inline.field > label {
@ -9,26 +10,20 @@
margin-right: 0; margin-right: 0;
} }
.page-content.install form.ui.form .inline.field > .ui.checkbox:first-child { .page-content.install .ui.form .field > .help,
.page-content.install .ui.form .field > .ui.checkbox:first-child,
.page-content.install .ui.form .field > .right-content {
margin-left: 30%; margin-left: 30%;
padding-left: 5px; padding-left: 5px;
}
.page-content.install form.ui.form .inline.field > .ui.checkbox:first-child label {
width: auto; width: auto;
} }
.page-content.install form.ui.form .title {
margin-left: 30%;
padding-left: 5px;
}
.page-content.install form.ui.form input { .page-content.install form.ui.form input {
width: 60%; width: 60%;
} }
.page-content.install form.ui.form details.optional.field[open] { .page-content.install form.ui.form details.optional.field[open] {
border-bottom: 1px solid var(--color-secondary); border-bottom: 1px dashed var(--color-secondary);
padding-bottom: 10px; padding-bottom: 10px;
} }
@ -44,12 +39,6 @@
text-align: left; text-align: left;
} }
.page-content.install form.ui.form .field .help {
margin-left: 30%;
padding-left: 5px;
width: 60%;
}
.page-content.install .ui .reinstall-message { .page-content.install .ui .reinstall-message {
width: 70%; width: 70%;
margin: 20px auto; margin: 20px auto;

View File

@ -3,6 +3,30 @@
margin-top: 1rem; margin-top: 1rem;
} }
.issue.list .item {
display: flex;
align-items: baseline;
padding: 8px 0;
}
.issue.list .item .issue-item-left {
display: flex;
align-items: flex-start;
}
.issue.list .item .issue-item-main {
display: flex;
flex-direction: column;
width: 100%;
}
.issue.list .item .issue-item-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.issue.list a:not(.label):hover { .issue.list a:not(.label):hover {
color: var(--color-primary) !important; color: var(--color-primary) !important;
} }
@ -12,14 +36,13 @@
margin-top: 1px; margin-top: 1px;
} }
.issue.list > .item .issue-item-icons-right > * + * { .issue.list .item .issue-item-right {
margin-left: 0.5rem; display: flex;
gap: 0.5rem;
} }
.issue.list > .item .issue-item-main { .issue.list > .action-item {
flex: 1; align-items: normal;
display: flex;
flex-direction: column;
} }
.issue.list > .item .action-item-center { .issue.list > .item .action-item-center {
@ -37,7 +60,7 @@
color: var(--color-text-light); color: var(--color-text-light);
} }
.issue.list > .item .issue-item-top-row { .issue.list > .item .issue-item-title {
max-width: 100%; max-width: 100%;
color: var(--color-text); color: var(--color-text);
font-size: 16px; font-size: 16px;
@ -45,7 +68,7 @@
font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold);
} }
.issue.list > .item .issue-item-top-row a.index { .issue.list > .item .issue-item-title a.index {
max-width: fit-content; max-width: fit-content;
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
@ -54,108 +77,49 @@
word-break: break-all; word-break: break-all;
} }
.issue.list > .item .labels-list { .issue.list > .item .title {
position: relative; color: var(--color-text);
top: -1.5px; overflow-wrap: anywhere;
} }
.issue.list > .item .issue-item-bottom-row { .issue.list > .item .issue-item-body {
font-size: 13px; font-size: 13px;
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
margin: .125rem 0; gap: .25rem;
}
.issue.list > .item .title {
color: var(--color-text);
word-break: break-word;
}
.issue.list > .item .issue-item-icon-right {
min-width: 2rem;
}
.issue.list > .item .assignee {
position: relative;
top: -2px;
}
.issue.list > .item .assignee img {
margin-right: 2px;
}
.issue.list > .item .desc {
color: var(--color-text-light-2); color: var(--color-text-light-2);
} }
.issue.list > .item .desc a { .issue.list > .item .issue-item-body a {
color: inherit; color: inherit;
word-break: break-word; word-break: break-word;
} }
.issue.list > .item .desc .time-since, .issue.list > .item .issue-item-body .checklist progress {
.issue.list > .item .desc a {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
.issue.list > .item .desc .waiting,
.issue.list > .item .desc .approvals,
.issue.list > .item .desc .rejects {
padding-left: 5px;
}
.issue.list > .item .desc .checklist {
padding-left: 5px;
}
.issue.list > .item .desc .checklist progress {
margin-left: 2px; margin-left: 2px;
width: 80px; width: 80px;
height: 6px; height: 6px;
display: inline-block; display: inline-block;
border-radius: 3px; border-radius: 3px;
vertical-align: 2px !important;
} }
.issue.list > .item .desc .checklist progress::-webkit-progress-value { .issue.list > .item .issue-item-body .checklist progress::-webkit-progress-value {
background-color: var(--color-secondary-dark-4); background-color: var(--color-secondary-dark-4);
} }
.issue.list > .item .desc .checklist progress::-moz-progress-bar { .issue.list > .item .issue-item-body .checklist progress::-moz-progress-bar {
background-color: var(--color-secondary-dark-4); background-color: var(--color-secondary-dark-4);
} }
.issue.list > .item .desc .conflicting {
padding-left: 5px;
}
.issue.list > .item .desc .due-date {
padding-left: 5px;
}
.issue.list > .item .desc a.milestone,
.issue.list > .item .desc a.project {
margin-left: 5px;
}
.issue.list > .item .desc a.ref {
margin-left: 8px;
}
.issue.list > .item .desc a.ref span {
margin-right: -4px;
}
.issue.list .branches { .issue.list .branches {
display: inline-flex; display: inline-flex;
padding: 0 4px;
} }
.issue.list .branches .branch { .issue.list .branches .branch {
background-color: var(--color-secondary-alpha-40); background-color: var(--color-secondary-alpha-40);
border-radius: 3px; border-radius: 3px;
padding: 0 4px;
} }
.issue.list .branches .truncated-name { .issue.list .branches .truncated-name {

View File

@ -165,7 +165,7 @@ async function updateNotificationTable() {
if (notificationDiv.length > 0) { if (notificationDiv.length > 0) {
const data = await $.ajax({ const data = await $.ajax({
type: 'GET', type: 'GET',
url: `${appSubUrl}/notifications?${notificationDiv.data('params')}`, url: `${appSubUrl}/notifications${window.location.search}`,
data: { data: {
'div-only': true, 'div-only': true,
'sequence-number': ++notificationSequenceNumber, 'sequence-number': ++notificationSequenceNumber,