mirror of
https://github.com/gogs/gogs.git
synced 2026-05-07 02:17:04 +02:00
internal: move packages under this directory (#5836)
* Rename pkg -> internal * Rename routes -> route * Move route -> internal/route * Rename models -> db * Move db -> internal/db * Fix route2 -> route * Move cmd -> internal/cmd * Bump version
This commit is contained in:
259
internal/route/admin/admin.go
Normal file
259
internal/route/admin/admin.go
Normal file
@@ -0,0 +1,259 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/json-iterator/go"
|
||||
"github.com/unknwon/com"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/cron"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/mailer"
|
||||
"gogs.io/gogs/internal/process"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
DASHBOARD = "admin/dashboard"
|
||||
CONFIG = "admin/config"
|
||||
MONITOR = "admin/monitor"
|
||||
)
|
||||
|
||||
var (
|
||||
startTime = time.Now()
|
||||
)
|
||||
|
||||
var sysStatus struct {
|
||||
Uptime string
|
||||
NumGoroutine int
|
||||
|
||||
// General statistics.
|
||||
MemAllocated string // bytes allocated and still in use
|
||||
MemTotal string // bytes allocated (even if freed)
|
||||
MemSys string // bytes obtained from system (sum of XxxSys below)
|
||||
Lookups uint64 // number of pointer lookups
|
||||
MemMallocs uint64 // number of mallocs
|
||||
MemFrees uint64 // number of frees
|
||||
|
||||
// Main allocation heap statistics.
|
||||
HeapAlloc string // bytes allocated and still in use
|
||||
HeapSys string // bytes obtained from system
|
||||
HeapIdle string // bytes in idle spans
|
||||
HeapInuse string // bytes in non-idle span
|
||||
HeapReleased string // bytes released to the OS
|
||||
HeapObjects uint64 // total number of allocated objects
|
||||
|
||||
// Low-level fixed-size structure allocator statistics.
|
||||
// Inuse is bytes used now.
|
||||
// Sys is bytes obtained from system.
|
||||
StackInuse string // bootstrap stacks
|
||||
StackSys string
|
||||
MSpanInuse string // mspan structures
|
||||
MSpanSys string
|
||||
MCacheInuse string // mcache structures
|
||||
MCacheSys string
|
||||
BuckHashSys string // profiling bucket hash table
|
||||
GCSys string // GC metadata
|
||||
OtherSys string // other system allocations
|
||||
|
||||
// Garbage collector statistics.
|
||||
NextGC string // next run in HeapAlloc time (bytes)
|
||||
LastGC string // last run in absolute time (ns)
|
||||
PauseTotalNs string
|
||||
PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
|
||||
NumGC uint32
|
||||
}
|
||||
|
||||
func updateSystemStatus() {
|
||||
sysStatus.Uptime = tool.TimeSincePro(startTime)
|
||||
|
||||
m := new(runtime.MemStats)
|
||||
runtime.ReadMemStats(m)
|
||||
sysStatus.NumGoroutine = runtime.NumGoroutine()
|
||||
|
||||
sysStatus.MemAllocated = tool.FileSize(int64(m.Alloc))
|
||||
sysStatus.MemTotal = tool.FileSize(int64(m.TotalAlloc))
|
||||
sysStatus.MemSys = tool.FileSize(int64(m.Sys))
|
||||
sysStatus.Lookups = m.Lookups
|
||||
sysStatus.MemMallocs = m.Mallocs
|
||||
sysStatus.MemFrees = m.Frees
|
||||
|
||||
sysStatus.HeapAlloc = tool.FileSize(int64(m.HeapAlloc))
|
||||
sysStatus.HeapSys = tool.FileSize(int64(m.HeapSys))
|
||||
sysStatus.HeapIdle = tool.FileSize(int64(m.HeapIdle))
|
||||
sysStatus.HeapInuse = tool.FileSize(int64(m.HeapInuse))
|
||||
sysStatus.HeapReleased = tool.FileSize(int64(m.HeapReleased))
|
||||
sysStatus.HeapObjects = m.HeapObjects
|
||||
|
||||
sysStatus.StackInuse = tool.FileSize(int64(m.StackInuse))
|
||||
sysStatus.StackSys = tool.FileSize(int64(m.StackSys))
|
||||
sysStatus.MSpanInuse = tool.FileSize(int64(m.MSpanInuse))
|
||||
sysStatus.MSpanSys = tool.FileSize(int64(m.MSpanSys))
|
||||
sysStatus.MCacheInuse = tool.FileSize(int64(m.MCacheInuse))
|
||||
sysStatus.MCacheSys = tool.FileSize(int64(m.MCacheSys))
|
||||
sysStatus.BuckHashSys = tool.FileSize(int64(m.BuckHashSys))
|
||||
sysStatus.GCSys = tool.FileSize(int64(m.GCSys))
|
||||
sysStatus.OtherSys = tool.FileSize(int64(m.OtherSys))
|
||||
|
||||
sysStatus.NextGC = tool.FileSize(int64(m.NextGC))
|
||||
sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000)
|
||||
sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000)
|
||||
sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000)
|
||||
sysStatus.NumGC = m.NumGC
|
||||
}
|
||||
|
||||
// Operation types.
|
||||
type AdminOperation int
|
||||
|
||||
const (
|
||||
CLEAN_INACTIVATE_USER AdminOperation = iota + 1
|
||||
CLEAN_REPO_ARCHIVES
|
||||
CLEAN_MISSING_REPOS
|
||||
GIT_GC_REPOS
|
||||
SYNC_SSH_AUTHORIZED_KEY
|
||||
SYNC_REPOSITORY_HOOKS
|
||||
REINIT_MISSING_REPOSITORY
|
||||
)
|
||||
|
||||
func Dashboard(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.dashboard")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminDashboard"] = true
|
||||
|
||||
// Run operation.
|
||||
op, _ := com.StrTo(c.Query("op")).Int()
|
||||
if op > 0 {
|
||||
var err error
|
||||
var success string
|
||||
|
||||
switch AdminOperation(op) {
|
||||
case CLEAN_INACTIVATE_USER:
|
||||
success = c.Tr("admin.dashboard.delete_inactivate_accounts_success")
|
||||
err = db.DeleteInactivateUsers()
|
||||
case CLEAN_REPO_ARCHIVES:
|
||||
success = c.Tr("admin.dashboard.delete_repo_archives_success")
|
||||
err = db.DeleteRepositoryArchives()
|
||||
case CLEAN_MISSING_REPOS:
|
||||
success = c.Tr("admin.dashboard.delete_missing_repos_success")
|
||||
err = db.DeleteMissingRepositories()
|
||||
case GIT_GC_REPOS:
|
||||
success = c.Tr("admin.dashboard.git_gc_repos_success")
|
||||
err = db.GitGcRepos()
|
||||
case SYNC_SSH_AUTHORIZED_KEY:
|
||||
success = c.Tr("admin.dashboard.resync_all_sshkeys_success")
|
||||
err = db.RewriteAuthorizedKeys()
|
||||
case SYNC_REPOSITORY_HOOKS:
|
||||
success = c.Tr("admin.dashboard.resync_all_hooks_success")
|
||||
err = db.SyncRepositoryHooks()
|
||||
case REINIT_MISSING_REPOSITORY:
|
||||
success = c.Tr("admin.dashboard.reinit_missing_repos_success")
|
||||
err = db.ReinitMissingRepositories()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.Flash.Error(err.Error())
|
||||
} else {
|
||||
c.Flash.Success(success)
|
||||
}
|
||||
c.Redirect(setting.AppSubURL + "/admin")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["Stats"] = db.GetStatistic()
|
||||
// FIXME: update periodically
|
||||
updateSystemStatus()
|
||||
c.Data["SysStatus"] = sysStatus
|
||||
c.HTML(200, DASHBOARD)
|
||||
}
|
||||
|
||||
func SendTestMail(c *context.Context) {
|
||||
email := c.Query("email")
|
||||
// Send a test email to the user's email address and redirect back to Config
|
||||
if err := mailer.SendTestMail(email); err != nil {
|
||||
c.Flash.Error(c.Tr("admin.config.test_mail_failed", email, err))
|
||||
} else {
|
||||
c.Flash.Info(c.Tr("admin.config.test_mail_sent", email))
|
||||
}
|
||||
|
||||
c.Redirect(setting.AppSubURL + "/admin/config")
|
||||
}
|
||||
|
||||
func Config(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.config")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminConfig"] = true
|
||||
|
||||
c.Data["AppURL"] = setting.AppURL
|
||||
c.Data["Domain"] = setting.Domain
|
||||
c.Data["OfflineMode"] = setting.OfflineMode
|
||||
c.Data["DisableRouterLog"] = setting.DisableRouterLog
|
||||
c.Data["RunUser"] = setting.RunUser
|
||||
c.Data["RunMode"] = strings.Title(macaron.Env)
|
||||
c.Data["StaticRootPath"] = setting.StaticRootPath
|
||||
c.Data["LogRootPath"] = setting.LogRootPath
|
||||
c.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser
|
||||
|
||||
c.Data["SSH"] = setting.SSH
|
||||
|
||||
c.Data["RepoRootPath"] = setting.RepoRootPath
|
||||
c.Data["ScriptType"] = setting.ScriptType
|
||||
c.Data["Repository"] = setting.Repository
|
||||
c.Data["HTTP"] = setting.HTTP
|
||||
|
||||
c.Data["DbCfg"] = db.DbCfg
|
||||
c.Data["Service"] = setting.Service
|
||||
c.Data["Webhook"] = setting.Webhook
|
||||
|
||||
c.Data["MailerEnabled"] = false
|
||||
if setting.MailService != nil {
|
||||
c.Data["MailerEnabled"] = true
|
||||
c.Data["Mailer"] = setting.MailService
|
||||
}
|
||||
|
||||
c.Data["CacheAdapter"] = setting.CacheAdapter
|
||||
c.Data["CacheInterval"] = setting.CacheInterval
|
||||
c.Data["CacheConn"] = setting.CacheConn
|
||||
|
||||
c.Data["SessionConfig"] = setting.SessionConfig
|
||||
|
||||
c.Data["DisableGravatar"] = setting.DisableGravatar
|
||||
c.Data["EnableFederatedAvatar"] = setting.EnableFederatedAvatar
|
||||
|
||||
c.Data["GitVersion"] = setting.Git.Version
|
||||
c.Data["Git"] = setting.Git
|
||||
|
||||
type logger struct {
|
||||
Mode, Config string
|
||||
}
|
||||
loggers := make([]*logger, len(setting.LogModes))
|
||||
for i := range setting.LogModes {
|
||||
loggers[i] = &logger{
|
||||
Mode: strings.Title(setting.LogModes[i]),
|
||||
}
|
||||
|
||||
result, _ := jsoniter.MarshalIndent(setting.LogConfigs[i], "", " ")
|
||||
loggers[i].Config = string(result)
|
||||
}
|
||||
c.Data["Loggers"] = loggers
|
||||
|
||||
c.HTML(200, CONFIG)
|
||||
}
|
||||
|
||||
func Monitor(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.monitor")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminMonitor"] = true
|
||||
c.Data["Processes"] = process.Processes
|
||||
c.Data["Entries"] = cron.ListTasks()
|
||||
c.HTML(200, MONITOR)
|
||||
}
|
||||
278
internal/route/admin/auths.go
Normal file
278
internal/route/admin/auths.go
Normal file
@@ -0,0 +1,278 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
"xorm.io/core"
|
||||
|
||||
"gogs.io/gogs/internal/auth/ldap"
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
AUTHS = "admin/auth/list"
|
||||
AUTH_NEW = "admin/auth/new"
|
||||
AUTH_EDIT = "admin/auth/edit"
|
||||
)
|
||||
|
||||
func Authentications(c *context.Context) {
|
||||
c.Title("admin.authentication")
|
||||
c.PageIs("Admin")
|
||||
c.PageIs("AdminAuthentications")
|
||||
|
||||
var err error
|
||||
c.Data["Sources"], err = db.LoginSources()
|
||||
if err != nil {
|
||||
c.ServerError("LoginSources", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["Total"] = db.CountLoginSources()
|
||||
c.Success(AUTHS)
|
||||
}
|
||||
|
||||
type dropdownItem struct {
|
||||
Name string
|
||||
Type interface{}
|
||||
}
|
||||
|
||||
var (
|
||||
authSources = []dropdownItem{
|
||||
{db.LoginNames[db.LOGIN_LDAP], db.LOGIN_LDAP},
|
||||
{db.LoginNames[db.LOGIN_DLDAP], db.LOGIN_DLDAP},
|
||||
{db.LoginNames[db.LOGIN_SMTP], db.LOGIN_SMTP},
|
||||
{db.LoginNames[db.LOGIN_PAM], db.LOGIN_PAM},
|
||||
{db.LoginNames[db.LOGIN_GITHUB], db.LOGIN_GITHUB},
|
||||
}
|
||||
securityProtocols = []dropdownItem{
|
||||
{db.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED], ldap.SECURITY_PROTOCOL_UNENCRYPTED},
|
||||
{db.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_LDAPS], ldap.SECURITY_PROTOCOL_LDAPS},
|
||||
{db.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_START_TLS], ldap.SECURITY_PROTOCOL_START_TLS},
|
||||
}
|
||||
)
|
||||
|
||||
func NewAuthSource(c *context.Context) {
|
||||
c.Title("admin.auths.new")
|
||||
c.PageIs("Admin")
|
||||
c.PageIs("AdminAuthentications")
|
||||
|
||||
c.Data["type"] = db.LOGIN_LDAP
|
||||
c.Data["CurrentTypeName"] = db.LoginNames[db.LOGIN_LDAP]
|
||||
c.Data["CurrentSecurityProtocol"] = db.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED]
|
||||
c.Data["smtp_auth"] = "PLAIN"
|
||||
c.Data["is_active"] = true
|
||||
c.Data["is_default"] = true
|
||||
c.Data["AuthSources"] = authSources
|
||||
c.Data["SecurityProtocols"] = securityProtocols
|
||||
c.Data["SMTPAuths"] = db.SMTPAuths
|
||||
c.Success(AUTH_NEW)
|
||||
}
|
||||
|
||||
func parseLDAPConfig(f form.Authentication) *db.LDAPConfig {
|
||||
return &db.LDAPConfig{
|
||||
Source: &ldap.Source{
|
||||
Host: f.Host,
|
||||
Port: f.Port,
|
||||
SecurityProtocol: ldap.SecurityProtocol(f.SecurityProtocol),
|
||||
SkipVerify: f.SkipVerify,
|
||||
BindDN: f.BindDN,
|
||||
UserDN: f.UserDN,
|
||||
BindPassword: f.BindPassword,
|
||||
UserBase: f.UserBase,
|
||||
AttributeUsername: f.AttributeUsername,
|
||||
AttributeName: f.AttributeName,
|
||||
AttributeSurname: f.AttributeSurname,
|
||||
AttributeMail: f.AttributeMail,
|
||||
AttributesInBind: f.AttributesInBind,
|
||||
Filter: f.Filter,
|
||||
GroupEnabled: f.GroupEnabled,
|
||||
GroupDN: f.GroupDN,
|
||||
GroupFilter: f.GroupFilter,
|
||||
GroupMemberUID: f.GroupMemberUID,
|
||||
UserUID: f.UserUID,
|
||||
AdminFilter: f.AdminFilter,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func parseSMTPConfig(f form.Authentication) *db.SMTPConfig {
|
||||
return &db.SMTPConfig{
|
||||
Auth: f.SMTPAuth,
|
||||
Host: f.SMTPHost,
|
||||
Port: f.SMTPPort,
|
||||
AllowedDomains: f.AllowedDomains,
|
||||
TLS: f.TLS,
|
||||
SkipVerify: f.SkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAuthSourcePost(c *context.Context, f form.Authentication) {
|
||||
c.Title("admin.auths.new")
|
||||
c.PageIs("Admin")
|
||||
c.PageIs("AdminAuthentications")
|
||||
|
||||
c.Data["CurrentTypeName"] = db.LoginNames[db.LoginType(f.Type)]
|
||||
c.Data["CurrentSecurityProtocol"] = db.SecurityProtocolNames[ldap.SecurityProtocol(f.SecurityProtocol)]
|
||||
c.Data["AuthSources"] = authSources
|
||||
c.Data["SecurityProtocols"] = securityProtocols
|
||||
c.Data["SMTPAuths"] = db.SMTPAuths
|
||||
|
||||
hasTLS := false
|
||||
var config core.Conversion
|
||||
switch db.LoginType(f.Type) {
|
||||
case db.LOGIN_LDAP, db.LOGIN_DLDAP:
|
||||
config = parseLDAPConfig(f)
|
||||
hasTLS = ldap.SecurityProtocol(f.SecurityProtocol) > ldap.SECURITY_PROTOCOL_UNENCRYPTED
|
||||
case db.LOGIN_SMTP:
|
||||
config = parseSMTPConfig(f)
|
||||
hasTLS = true
|
||||
case db.LOGIN_PAM:
|
||||
config = &db.PAMConfig{
|
||||
ServiceName: f.PAMServiceName,
|
||||
}
|
||||
case db.LOGIN_GITHUB:
|
||||
config = &db.GitHubConfig{
|
||||
APIEndpoint: strings.TrimSuffix(f.GitHubAPIEndpoint, "/") + "/",
|
||||
}
|
||||
default:
|
||||
c.Error(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
c.Data["HasTLS"] = hasTLS
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(AUTH_NEW)
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.CreateLoginSource(&db.LoginSource{
|
||||
Type: db.LoginType(f.Type),
|
||||
Name: f.Name,
|
||||
IsActived: f.IsActive,
|
||||
IsDefault: f.IsDefault,
|
||||
Cfg: config,
|
||||
}); err != nil {
|
||||
if db.IsErrLoginSourceAlreadyExist(err) {
|
||||
c.FormErr("Name")
|
||||
c.RenderWithErr(c.Tr("admin.auths.login_source_exist", err.(db.ErrLoginSourceAlreadyExist).Name), AUTH_NEW, f)
|
||||
} else {
|
||||
c.ServerError("CreateSource", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Authentication created by admin(%s): %s", c.User.Name, f.Name)
|
||||
|
||||
c.Flash.Success(c.Tr("admin.auths.new_success", f.Name))
|
||||
c.Redirect(setting.AppSubURL + "/admin/auths")
|
||||
}
|
||||
|
||||
func EditAuthSource(c *context.Context) {
|
||||
c.Title("admin.auths.edit")
|
||||
c.PageIs("Admin")
|
||||
c.PageIs("AdminAuthentications")
|
||||
|
||||
c.Data["SecurityProtocols"] = securityProtocols
|
||||
c.Data["SMTPAuths"] = db.SMTPAuths
|
||||
|
||||
source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid"))
|
||||
if err != nil {
|
||||
c.ServerError("GetLoginSourceByID", err)
|
||||
return
|
||||
}
|
||||
c.Data["Source"] = source
|
||||
c.Data["HasTLS"] = source.HasTLS()
|
||||
|
||||
c.Success(AUTH_EDIT)
|
||||
}
|
||||
|
||||
func EditAuthSourcePost(c *context.Context, f form.Authentication) {
|
||||
c.Title("admin.auths.edit")
|
||||
c.PageIs("Admin")
|
||||
c.PageIs("AdminAuthentications")
|
||||
|
||||
c.Data["SMTPAuths"] = db.SMTPAuths
|
||||
|
||||
source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid"))
|
||||
if err != nil {
|
||||
c.ServerError("GetLoginSourceByID", err)
|
||||
return
|
||||
}
|
||||
c.Data["Source"] = source
|
||||
c.Data["HasTLS"] = source.HasTLS()
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(AUTH_EDIT)
|
||||
return
|
||||
}
|
||||
|
||||
var config core.Conversion
|
||||
switch db.LoginType(f.Type) {
|
||||
case db.LOGIN_LDAP, db.LOGIN_DLDAP:
|
||||
config = parseLDAPConfig(f)
|
||||
case db.LOGIN_SMTP:
|
||||
config = parseSMTPConfig(f)
|
||||
case db.LOGIN_PAM:
|
||||
config = &db.PAMConfig{
|
||||
ServiceName: f.PAMServiceName,
|
||||
}
|
||||
case db.LOGIN_GITHUB:
|
||||
config = &db.GitHubConfig{
|
||||
APIEndpoint: strings.TrimSuffix(f.GitHubAPIEndpoint, "/") + "/",
|
||||
}
|
||||
default:
|
||||
c.Error(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
source.Name = f.Name
|
||||
source.IsActived = f.IsActive
|
||||
source.IsDefault = f.IsDefault
|
||||
source.Cfg = config
|
||||
if err := db.UpdateLoginSource(source); err != nil {
|
||||
c.ServerError("UpdateLoginSource", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Authentication changed by admin '%s': %d", c.User.Name, source.ID)
|
||||
|
||||
c.Flash.Success(c.Tr("admin.auths.update_success"))
|
||||
c.Redirect(setting.AppSubURL + "/admin/auths/" + com.ToStr(f.ID))
|
||||
}
|
||||
|
||||
func DeleteAuthSource(c *context.Context) {
|
||||
source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid"))
|
||||
if err != nil {
|
||||
c.ServerError("GetLoginSourceByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = db.DeleteSource(source); err != nil {
|
||||
if db.IsErrLoginSourceInUse(err) {
|
||||
c.Flash.Error(c.Tr("admin.auths.still_in_used"))
|
||||
} else {
|
||||
c.Flash.Error(fmt.Sprintf("DeleteSource: %v", err))
|
||||
}
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/admin/auths/" + c.Params(":authid"),
|
||||
})
|
||||
return
|
||||
}
|
||||
log.Trace("Authentication deleted by admin(%s): %d", c.User.Name, source.ID)
|
||||
|
||||
c.Flash.Success(c.Tr("admin.auths.deletion_success"))
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/admin/auths",
|
||||
})
|
||||
}
|
||||
72
internal/route/admin/notice.go
Normal file
72
internal/route/admin/notice.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/unknwon/com"
|
||||
"github.com/unknwon/paginater"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
NOTICES = "admin/notice"
|
||||
)
|
||||
|
||||
func Notices(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.notices")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminNotices"] = true
|
||||
|
||||
total := db.CountNotices()
|
||||
page := c.QueryInt("page")
|
||||
if page <= 1 {
|
||||
page = 1
|
||||
}
|
||||
c.Data["Page"] = paginater.New(int(total), setting.UI.Admin.NoticePagingNum, page, 5)
|
||||
|
||||
notices, err := db.Notices(page, setting.UI.Admin.NoticePagingNum)
|
||||
if err != nil {
|
||||
c.Handle(500, "Notices", err)
|
||||
return
|
||||
}
|
||||
c.Data["Notices"] = notices
|
||||
|
||||
c.Data["Total"] = total
|
||||
c.HTML(200, NOTICES)
|
||||
}
|
||||
|
||||
func DeleteNotices(c *context.Context) {
|
||||
strs := c.QueryStrings("ids[]")
|
||||
ids := make([]int64, 0, len(strs))
|
||||
for i := range strs {
|
||||
id := com.StrTo(strs[i]).MustInt64()
|
||||
if id > 0 {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.DeleteNoticesByIDs(ids); err != nil {
|
||||
c.Flash.Error("DeleteNoticesByIDs: " + err.Error())
|
||||
c.Status(500)
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("admin.notices.delete_success"))
|
||||
c.Status(200)
|
||||
}
|
||||
}
|
||||
|
||||
func EmptyNotices(c *context.Context) {
|
||||
if err := db.DeleteNotices(0, 0); err != nil {
|
||||
c.Handle(500, "DeleteNotices", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("System notices deleted by admin (%s): [start: %d]", c.User.Name, 0)
|
||||
c.Flash.Success(c.Tr("admin.notices.delete_success"))
|
||||
c.Redirect(setting.AppSubURL + "/admin/notices")
|
||||
}
|
||||
31
internal/route/admin/orgs.go
Normal file
31
internal/route/admin/orgs.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/route"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
ORGS = "admin/org/list"
|
||||
)
|
||||
|
||||
func Organizations(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.organizations")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminOrganizations"] = true
|
||||
|
||||
route.RenderUserSearch(c, &route.UserSearchOptions{
|
||||
Type: db.USER_TYPE_ORGANIZATION,
|
||||
Counter: db.CountOrganizations,
|
||||
Ranger: db.Organizations,
|
||||
PageSize: setting.UI.Admin.OrgPagingNum,
|
||||
OrderBy: "id ASC",
|
||||
TplName: ORGS,
|
||||
})
|
||||
}
|
||||
87
internal/route/admin/repos.go
Normal file
87
internal/route/admin/repos.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/unknwon/paginater"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
REPOS = "admin/repo/list"
|
||||
)
|
||||
|
||||
func Repos(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.repositories")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminRepositories"] = true
|
||||
|
||||
page := c.QueryInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
var (
|
||||
repos []*db.Repository
|
||||
count int64
|
||||
err error
|
||||
)
|
||||
|
||||
keyword := c.Query("q")
|
||||
if len(keyword) == 0 {
|
||||
repos, err = db.Repositories(page, setting.UI.Admin.RepoPagingNum)
|
||||
if err != nil {
|
||||
c.Handle(500, "Repositories", err)
|
||||
return
|
||||
}
|
||||
count = db.CountRepositories(true)
|
||||
} else {
|
||||
repos, count, err = db.SearchRepositoryByName(&db.SearchRepoOptions{
|
||||
Keyword: keyword,
|
||||
OrderBy: "id ASC",
|
||||
Private: true,
|
||||
Page: page,
|
||||
PageSize: setting.UI.Admin.RepoPagingNum,
|
||||
})
|
||||
if err != nil {
|
||||
c.Handle(500, "SearchRepositoryByName", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["Keyword"] = keyword
|
||||
c.Data["Total"] = count
|
||||
c.Data["Page"] = paginater.New(int(count), setting.UI.Admin.RepoPagingNum, page, 5)
|
||||
|
||||
if err = db.RepositoryList(repos).LoadAttributes(); err != nil {
|
||||
c.Handle(500, "LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
c.Data["Repos"] = repos
|
||||
|
||||
c.HTML(200, REPOS)
|
||||
}
|
||||
|
||||
func DeleteRepo(c *context.Context) {
|
||||
repo, err := db.GetRepositoryByID(c.QueryInt64("id"))
|
||||
if err != nil {
|
||||
c.Handle(500, "GetRepositoryByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DeleteRepository(repo.MustOwner().ID, repo.ID); err != nil {
|
||||
c.Handle(500, "DeleteRepository", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Repository deleted: %s/%s", repo.MustOwner().Name, repo.Name)
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.deletion_success"))
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/admin/repos?page=" + c.Query("page"),
|
||||
})
|
||||
}
|
||||
262
internal/route/admin/users.go
Normal file
262
internal/route/admin/users.go
Normal file
@@ -0,0 +1,262 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/mailer"
|
||||
"gogs.io/gogs/internal/route"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
USERS = "admin/user/list"
|
||||
USER_NEW = "admin/user/new"
|
||||
USER_EDIT = "admin/user/edit"
|
||||
)
|
||||
|
||||
func Users(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.users")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminUsers"] = true
|
||||
|
||||
route.RenderUserSearch(c, &route.UserSearchOptions{
|
||||
Type: db.USER_TYPE_INDIVIDUAL,
|
||||
Counter: db.CountUsers,
|
||||
Ranger: db.Users,
|
||||
PageSize: setting.UI.Admin.UserPagingNum,
|
||||
OrderBy: "id ASC",
|
||||
TplName: USERS,
|
||||
})
|
||||
}
|
||||
|
||||
func NewUser(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.users.new_account")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminUsers"] = true
|
||||
|
||||
c.Data["login_type"] = "0-0"
|
||||
|
||||
sources, err := db.LoginSources()
|
||||
if err != nil {
|
||||
c.Handle(500, "LoginSources", err)
|
||||
return
|
||||
}
|
||||
c.Data["Sources"] = sources
|
||||
|
||||
c.Data["CanSendEmail"] = setting.MailService != nil
|
||||
c.HTML(200, USER_NEW)
|
||||
}
|
||||
|
||||
func NewUserPost(c *context.Context, f form.AdminCrateUser) {
|
||||
c.Data["Title"] = c.Tr("admin.users.new_account")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminUsers"] = true
|
||||
|
||||
sources, err := db.LoginSources()
|
||||
if err != nil {
|
||||
c.Handle(500, "LoginSources", err)
|
||||
return
|
||||
}
|
||||
c.Data["Sources"] = sources
|
||||
|
||||
c.Data["CanSendEmail"] = setting.MailService != nil
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, USER_NEW)
|
||||
return
|
||||
}
|
||||
|
||||
u := &db.User{
|
||||
Name: f.UserName,
|
||||
Email: f.Email,
|
||||
Passwd: f.Password,
|
||||
IsActive: true,
|
||||
LoginType: db.LOGIN_PLAIN,
|
||||
}
|
||||
|
||||
if len(f.LoginType) > 0 {
|
||||
fields := strings.Split(f.LoginType, "-")
|
||||
if len(fields) == 2 {
|
||||
u.LoginType = db.LoginType(com.StrTo(fields[0]).MustInt())
|
||||
u.LoginSource = com.StrTo(fields[1]).MustInt64()
|
||||
u.LoginName = f.LoginName
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.CreateUser(u); err != nil {
|
||||
switch {
|
||||
case db.IsErrUserAlreadyExist(err):
|
||||
c.Data["Err_UserName"] = true
|
||||
c.RenderWithErr(c.Tr("form.username_been_taken"), USER_NEW, &f)
|
||||
case db.IsErrEmailAlreadyUsed(err):
|
||||
c.Data["Err_Email"] = true
|
||||
c.RenderWithErr(c.Tr("form.email_been_used"), USER_NEW, &f)
|
||||
case db.IsErrNameReserved(err):
|
||||
c.Data["Err_UserName"] = true
|
||||
c.RenderWithErr(c.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), USER_NEW, &f)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
c.Data["Err_UserName"] = true
|
||||
c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), USER_NEW, &f)
|
||||
default:
|
||||
c.Handle(500, "CreateUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name)
|
||||
|
||||
// Send email notification.
|
||||
if f.SendNotify && setting.MailService != nil {
|
||||
mailer.SendRegisterNotifyMail(c.Context, db.NewMailerUser(u))
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("admin.users.new_success", u.Name))
|
||||
c.Redirect(setting.AppSubURL + "/admin/users/" + com.ToStr(u.ID))
|
||||
}
|
||||
|
||||
func prepareUserInfo(c *context.Context) *db.User {
|
||||
u, err := db.GetUserByID(c.ParamsInt64(":userid"))
|
||||
if err != nil {
|
||||
c.Handle(500, "GetUserByID", err)
|
||||
return nil
|
||||
}
|
||||
c.Data["User"] = u
|
||||
|
||||
if u.LoginSource > 0 {
|
||||
c.Data["LoginSource"], err = db.GetLoginSourceByID(u.LoginSource)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetLoginSourceByID", err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
c.Data["LoginSource"] = &db.LoginSource{}
|
||||
}
|
||||
|
||||
sources, err := db.LoginSources()
|
||||
if err != nil {
|
||||
c.Handle(500, "LoginSources", err)
|
||||
return nil
|
||||
}
|
||||
c.Data["Sources"] = sources
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func EditUser(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("admin.users.edit_account")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminUsers"] = true
|
||||
c.Data["EnableLocalPathMigration"] = setting.Repository.EnableLocalPathMigration
|
||||
|
||||
prepareUserInfo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(200, USER_EDIT)
|
||||
}
|
||||
|
||||
func EditUserPost(c *context.Context, f form.AdminEditUser) {
|
||||
c.Data["Title"] = c.Tr("admin.users.edit_account")
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminUsers"] = true
|
||||
c.Data["EnableLocalPathMigration"] = setting.Repository.EnableLocalPathMigration
|
||||
|
||||
u := prepareUserInfo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, USER_EDIT)
|
||||
return
|
||||
}
|
||||
|
||||
fields := strings.Split(f.LoginType, "-")
|
||||
if len(fields) == 2 {
|
||||
loginType := db.LoginType(com.StrTo(fields[0]).MustInt())
|
||||
loginSource := com.StrTo(fields[1]).MustInt64()
|
||||
|
||||
if u.LoginSource != loginSource {
|
||||
u.LoginSource = loginSource
|
||||
u.LoginType = loginType
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.Password) > 0 {
|
||||
u.Passwd = f.Password
|
||||
var err error
|
||||
if u.Salt, err = db.GetUserSalt(); err != nil {
|
||||
c.Handle(500, "UpdateUser", err)
|
||||
return
|
||||
}
|
||||
u.EncodePasswd()
|
||||
}
|
||||
|
||||
u.LoginName = f.LoginName
|
||||
u.FullName = f.FullName
|
||||
u.Email = f.Email
|
||||
u.Website = f.Website
|
||||
u.Location = f.Location
|
||||
u.MaxRepoCreation = f.MaxRepoCreation
|
||||
u.IsActive = f.Active
|
||||
u.IsAdmin = f.Admin
|
||||
u.AllowGitHook = f.AllowGitHook
|
||||
u.AllowImportLocal = f.AllowImportLocal
|
||||
u.ProhibitLogin = f.ProhibitLogin
|
||||
|
||||
if err := db.UpdateUser(u); err != nil {
|
||||
if db.IsErrEmailAlreadyUsed(err) {
|
||||
c.Data["Err_Email"] = true
|
||||
c.RenderWithErr(c.Tr("form.email_been_used"), USER_EDIT, &f)
|
||||
} else {
|
||||
c.Handle(500, "UpdateUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Account profile updated by admin (%s): %s", c.User.Name, u.Name)
|
||||
|
||||
c.Flash.Success(c.Tr("admin.users.update_profile_success"))
|
||||
c.Redirect(setting.AppSubURL + "/admin/users/" + c.Params(":userid"))
|
||||
}
|
||||
|
||||
func DeleteUser(c *context.Context) {
|
||||
u, err := db.GetUserByID(c.ParamsInt64(":userid"))
|
||||
if err != nil {
|
||||
c.Handle(500, "GetUserByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = db.DeleteUser(u); err != nil {
|
||||
switch {
|
||||
case db.IsErrUserOwnRepos(err):
|
||||
c.Flash.Error(c.Tr("admin.users.still_own_repo"))
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/admin/users/" + c.Params(":userid"),
|
||||
})
|
||||
case db.IsErrUserHasOrgs(err):
|
||||
c.Flash.Error(c.Tr("admin.users.still_has_org"))
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/admin/users/" + c.Params(":userid"),
|
||||
})
|
||||
default:
|
||||
c.Handle(500, "DeleteUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Account deleted by admin (%s): %s", c.User.Name, u.Name)
|
||||
|
||||
c.Flash.Success(c.Tr("admin.users.deletion_success"))
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/admin/users",
|
||||
})
|
||||
}
|
||||
17
internal/route/api/v1/admin/org.go
Normal file
17
internal/route/api/v1/admin/org.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
org2 "gogs.io/gogs/internal/route/api/v1/org"
|
||||
user2 "gogs.io/gogs/internal/route/api/v1/user"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
)
|
||||
|
||||
func CreateOrg(c *context.APIContext, form api.CreateOrgOption) {
|
||||
org2.CreateOrgForUser(c, form, user2.GetUserByParams(c))
|
||||
}
|
||||
46
internal/route/api/v1/admin/org_repo.go
Normal file
46
internal/route/api/v1/admin/org_repo.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
)
|
||||
|
||||
func GetRepositoryByParams(c *context.APIContext) *db.Repository {
|
||||
repo, err := db.GetRepositoryByName(c.Org.Team.OrgID, c.Params(":reponame"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
|
||||
return nil
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func AddTeamRepository(c *context.APIContext) {
|
||||
repo := GetRepositoryByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
if err := c.Org.Team.AddRepository(repo); err != nil {
|
||||
c.ServerError("AddRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
|
||||
func RemoveTeamRepository(c *context.APIContext) {
|
||||
repo := GetRepositoryByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
if err := c.Org.Team.RemoveRepository(repo.ID); err != nil {
|
||||
c.ServerError("RemoveRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
62
internal/route/api/v1/admin/org_team.go
Normal file
62
internal/route/api/v1/admin/org_team.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
user2 "gogs.io/gogs/internal/route/api/v1/user"
|
||||
"net/http"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
)
|
||||
|
||||
func CreateTeam(c *context.APIContext, form api.CreateTeamOption) {
|
||||
team := &db.Team{
|
||||
OrgID: c.Org.Organization.ID,
|
||||
Name: form.Name,
|
||||
Description: form.Description,
|
||||
Authorize: db.ParseAccessMode(form.Permission),
|
||||
}
|
||||
if err := db.NewTeam(team); err != nil {
|
||||
if db.IsErrTeamAlreadyExist(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("NewTeam", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, convert2.ToTeam(team))
|
||||
}
|
||||
|
||||
func AddTeamMember(c *context.APIContext) {
|
||||
u := user2.GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
if err := c.Org.Team.AddMember(u.ID); err != nil {
|
||||
c.ServerError("AddMember", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
|
||||
func RemoveTeamMember(c *context.APIContext) {
|
||||
u := user2.GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Org.Team.RemoveMember(u.ID); err != nil {
|
||||
c.ServerError("RemoveMember", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
22
internal/route/api/v1/admin/repo.go
Normal file
22
internal/route/api/v1/admin/repo.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
repo2 "gogs.io/gogs/internal/route/api/v1/repo"
|
||||
user2 "gogs.io/gogs/internal/route/api/v1/user"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
)
|
||||
|
||||
func CreateRepo(c *context.APIContext, form api.CreateRepoOption) {
|
||||
owner := user2.GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
repo2.CreateUserRepo(c, owner, form)
|
||||
}
|
||||
159
internal/route/api/v1/admin/user.go
Normal file
159
internal/route/api/v1/admin/user.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
user2 "gogs.io/gogs/internal/route/api/v1/user"
|
||||
"net/http"
|
||||
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/mailer"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
func parseLoginSource(c *context.APIContext, u *db.User, sourceID int64, loginName string) {
|
||||
if sourceID == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
source, err := db.GetLoginSourceByID(sourceID)
|
||||
if err != nil {
|
||||
if errors.IsLoginSourceNotExist(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("GetLoginSourceByID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
u.LoginType = source.Type
|
||||
u.LoginSource = source.ID
|
||||
u.LoginName = loginName
|
||||
}
|
||||
|
||||
func CreateUser(c *context.APIContext, form api.CreateUserOption) {
|
||||
u := &db.User{
|
||||
Name: form.Username,
|
||||
FullName: form.FullName,
|
||||
Email: form.Email,
|
||||
Passwd: form.Password,
|
||||
IsActive: true,
|
||||
LoginType: db.LOGIN_PLAIN,
|
||||
}
|
||||
|
||||
parseLoginSource(c, u, form.SourceID, form.LoginName)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.CreateUser(u); err != nil {
|
||||
if db.IsErrUserAlreadyExist(err) ||
|
||||
db.IsErrEmailAlreadyUsed(err) ||
|
||||
db.IsErrNameReserved(err) ||
|
||||
db.IsErrNamePatternNotAllowed(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("CreateUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Account created by admin %q: %s", c.User.Name, u.Name)
|
||||
|
||||
// Send email notification.
|
||||
if form.SendNotify && setting.MailService != nil {
|
||||
mailer.SendRegisterNotifyMail(c.Context.Context, db.NewMailerUser(u))
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, u.APIFormat())
|
||||
}
|
||||
|
||||
func EditUser(c *context.APIContext, form api.EditUserOption) {
|
||||
u := user2.GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
parseLoginSource(c, u, form.SourceID, form.LoginName)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Password) > 0 {
|
||||
u.Passwd = form.Password
|
||||
var err error
|
||||
if u.Salt, err = db.GetUserSalt(); err != nil {
|
||||
c.ServerError("GetUserSalt", err)
|
||||
return
|
||||
}
|
||||
u.EncodePasswd()
|
||||
}
|
||||
|
||||
u.LoginName = form.LoginName
|
||||
u.FullName = form.FullName
|
||||
u.Email = form.Email
|
||||
u.Website = form.Website
|
||||
u.Location = form.Location
|
||||
if form.Active != nil {
|
||||
u.IsActive = *form.Active
|
||||
}
|
||||
if form.Admin != nil {
|
||||
u.IsAdmin = *form.Admin
|
||||
}
|
||||
if form.AllowGitHook != nil {
|
||||
u.AllowGitHook = *form.AllowGitHook
|
||||
}
|
||||
if form.AllowImportLocal != nil {
|
||||
u.AllowImportLocal = *form.AllowImportLocal
|
||||
}
|
||||
if form.MaxRepoCreation != nil {
|
||||
u.MaxRepoCreation = *form.MaxRepoCreation
|
||||
}
|
||||
|
||||
if err := db.UpdateUser(u); err != nil {
|
||||
if db.IsErrEmailAlreadyUsed(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("UpdateUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Account profile updated by admin %q: %s", c.User.Name, u.Name)
|
||||
|
||||
c.JSONSuccess(u.APIFormat())
|
||||
}
|
||||
|
||||
func DeleteUser(c *context.APIContext) {
|
||||
u := user2.GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DeleteUser(u); err != nil {
|
||||
if db.IsErrUserOwnRepos(err) ||
|
||||
db.IsErrUserHasOrgs(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("DeleteUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Account deleted by admin(%s): %s", c.User.Name, u.Name)
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
|
||||
func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) {
|
||||
u := user2.GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
user2.CreateUserPublicKey(c, form, u.ID)
|
||||
}
|
||||
406
internal/route/api/v1/api.go
Normal file
406
internal/route/api/v1/api.go
Normal file
@@ -0,0 +1,406 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
admin2 "gogs.io/gogs/internal/route/api/v1/admin"
|
||||
misc2 "gogs.io/gogs/internal/route/api/v1/misc"
|
||||
org2 "gogs.io/gogs/internal/route/api/v1/org"
|
||||
repo2 "gogs.io/gogs/internal/route/api/v1/repo"
|
||||
user2 "gogs.io/gogs/internal/route/api/v1/user"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
)
|
||||
|
||||
// repoAssignment extracts information from URL parameters to retrieve the repository,
|
||||
// and makes sure the context user has at least the read access to the repository.
|
||||
func repoAssignment() macaron.Handler {
|
||||
return func(c *context.APIContext) {
|
||||
username := c.Params(":username")
|
||||
reponame := c.Params(":reponame")
|
||||
|
||||
var err error
|
||||
var owner *db.User
|
||||
|
||||
// Check if the context user is the repository owner.
|
||||
if c.IsLogged && c.User.LowerName == strings.ToLower(username) {
|
||||
owner = c.User
|
||||
} else {
|
||||
owner, err = db.GetUserByName(username)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Repo.Owner = owner
|
||||
|
||||
r, err := db.GetRepositoryByName(owner.ID, reponame)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
|
||||
return
|
||||
} else if err = r.GetOwner(); err != nil {
|
||||
c.ServerError("GetOwner", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.IsTokenAuth && c.User.IsAdmin {
|
||||
c.Repo.AccessMode = db.ACCESS_MODE_OWNER
|
||||
} else {
|
||||
mode, err := db.UserAccessMode(c.UserID(), r)
|
||||
if err != nil {
|
||||
c.ServerError("UserAccessMode", err)
|
||||
return
|
||||
}
|
||||
c.Repo.AccessMode = mode
|
||||
}
|
||||
|
||||
if !c.Repo.HasAccess() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
c.Repo.Repository = r
|
||||
}
|
||||
}
|
||||
|
||||
// orgAssignment extracts information from URL parameters to retrieve the organization or team.
|
||||
func orgAssignment(args ...bool) macaron.Handler {
|
||||
var (
|
||||
assignOrg bool
|
||||
assignTeam bool
|
||||
)
|
||||
if len(args) > 0 {
|
||||
assignOrg = args[0]
|
||||
}
|
||||
if len(args) > 1 {
|
||||
assignTeam = args[1]
|
||||
}
|
||||
return func(c *context.APIContext) {
|
||||
c.Org = new(context.APIOrganization)
|
||||
|
||||
var err error
|
||||
if assignOrg {
|
||||
c.Org.Organization, err = db.GetUserByName(c.Params(":orgname"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if assignTeam {
|
||||
c.Org.Team, err = db.GetTeamByID(c.ParamsInt64(":teamid"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetTeamByID", errors.IsTeamNotExist, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reqToken makes sure the context user is authorized via access token.
|
||||
func reqToken() macaron.Handler {
|
||||
return func(c *context.Context) {
|
||||
if !c.IsTokenAuth {
|
||||
c.Error(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reqBasicAuth makes sure the context user is authorized via HTTP Basic Auth.
|
||||
func reqBasicAuth() macaron.Handler {
|
||||
return func(c *context.Context) {
|
||||
if !c.IsBasicAuth {
|
||||
c.Error(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reqAdmin makes sure the context user is a site admin.
|
||||
func reqAdmin() macaron.Handler {
|
||||
return func(c *context.Context) {
|
||||
if !c.IsLogged || !c.User.IsAdmin {
|
||||
c.Error(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reqRepoWriter makes sure the context user has at least write access to the repository.
|
||||
func reqRepoWriter() macaron.Handler {
|
||||
return func(c *context.Context) {
|
||||
if !c.Repo.IsWriter() {
|
||||
c.Error(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reqRepoWriter makes sure the context user has at least admin access to the repository.
|
||||
func reqRepoAdmin() macaron.Handler {
|
||||
return func(c *context.Context) {
|
||||
if !c.Repo.IsAdmin() {
|
||||
c.Error(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustEnableIssues(c *context.APIContext) {
|
||||
if !c.Repo.Repository.EnableIssues || c.Repo.Repository.EnableExternalTracker {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all route in API v1 to the web application.
|
||||
// FIXME: custom form error response
|
||||
func RegisterRoutes(m *macaron.Macaron) {
|
||||
bind := binding.Bind
|
||||
|
||||
m.Group("/v1", func() {
|
||||
// Handle preflight OPTIONS request
|
||||
m.Options("/*", func() {})
|
||||
|
||||
// Miscellaneous
|
||||
m.Post("/markdown", bind(api.MarkdownOption{}), misc2.Markdown)
|
||||
m.Post("/markdown/raw", misc2.MarkdownRaw)
|
||||
|
||||
// Users
|
||||
m.Group("/users", func() {
|
||||
m.Get("/search", user2.Search)
|
||||
|
||||
m.Group("/:username", func() {
|
||||
m.Get("", user2.GetInfo)
|
||||
|
||||
m.Group("/tokens", func() {
|
||||
m.Combo("").
|
||||
Get(user2.ListAccessTokens).
|
||||
Post(bind(api.CreateAccessTokenOption{}), user2.CreateAccessToken)
|
||||
}, reqBasicAuth())
|
||||
})
|
||||
})
|
||||
|
||||
m.Group("/users", func() {
|
||||
m.Group("/:username", func() {
|
||||
m.Get("/keys", user2.ListPublicKeys)
|
||||
|
||||
m.Get("/followers", user2.ListFollowers)
|
||||
m.Group("/following", func() {
|
||||
m.Get("", user2.ListFollowing)
|
||||
m.Get("/:target", user2.CheckFollowing)
|
||||
})
|
||||
})
|
||||
}, reqToken())
|
||||
|
||||
m.Group("/user", func() {
|
||||
m.Get("", user2.GetAuthenticatedUser)
|
||||
m.Combo("/emails").
|
||||
Get(user2.ListEmails).
|
||||
Post(bind(api.CreateEmailOption{}), user2.AddEmail).
|
||||
Delete(bind(api.CreateEmailOption{}), user2.DeleteEmail)
|
||||
|
||||
m.Get("/followers", user2.ListMyFollowers)
|
||||
m.Group("/following", func() {
|
||||
m.Get("", user2.ListMyFollowing)
|
||||
m.Combo("/:username").
|
||||
Get(user2.CheckMyFollowing).
|
||||
Put(user2.Follow).
|
||||
Delete(user2.Unfollow)
|
||||
})
|
||||
|
||||
m.Group("/keys", func() {
|
||||
m.Combo("").
|
||||
Get(user2.ListMyPublicKeys).
|
||||
Post(bind(api.CreateKeyOption{}), user2.CreatePublicKey)
|
||||
m.Combo("/:id").
|
||||
Get(user2.GetPublicKey).
|
||||
Delete(user2.DeletePublicKey)
|
||||
})
|
||||
|
||||
m.Get("/issues", repo2.ListUserIssues)
|
||||
}, reqToken())
|
||||
|
||||
// Repositories
|
||||
m.Get("/users/:username/repos", reqToken(), repo2.ListUserRepositories)
|
||||
m.Get("/orgs/:org/repos", reqToken(), repo2.ListOrgRepositories)
|
||||
m.Combo("/user/repos", reqToken()).
|
||||
Get(repo2.ListMyRepos).
|
||||
Post(bind(api.CreateRepoOption{}), repo2.Create)
|
||||
m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo2.CreateOrgRepo)
|
||||
|
||||
m.Group("/repos", func() {
|
||||
m.Get("/search", repo2.Search)
|
||||
|
||||
m.Get("/:username/:reponame", repoAssignment(), repo2.Get)
|
||||
})
|
||||
|
||||
m.Group("/repos", func() {
|
||||
m.Post("/migrate", bind(form.MigrateRepo{}), repo2.Migrate)
|
||||
m.Delete("/:username/:reponame", repoAssignment(), repo2.Delete)
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Group("/hooks", func() {
|
||||
m.Combo("").
|
||||
Get(repo2.ListHooks).
|
||||
Post(bind(api.CreateHookOption{}), repo2.CreateHook)
|
||||
m.Combo("/:id").
|
||||
Patch(bind(api.EditHookOption{}), repo2.EditHook).
|
||||
Delete(repo2.DeleteHook)
|
||||
}, reqRepoAdmin())
|
||||
|
||||
m.Group("/collaborators", func() {
|
||||
m.Get("", repo2.ListCollaborators)
|
||||
m.Combo("/:collaborator").
|
||||
Get(repo2.IsCollaborator).
|
||||
Put(bind(api.AddCollaboratorOption{}), repo2.AddCollaborator).
|
||||
Delete(repo2.DeleteCollaborator)
|
||||
}, reqRepoAdmin())
|
||||
|
||||
m.Get("/raw/*", context.RepoRef(), repo2.GetRawFile)
|
||||
m.Get("/archive/*", repo2.GetArchive)
|
||||
m.Get("/forks", repo2.ListForks)
|
||||
m.Group("/branches", func() {
|
||||
m.Get("", repo2.ListBranches)
|
||||
m.Get("/*", repo2.GetBranch)
|
||||
})
|
||||
m.Group("/commits", func() {
|
||||
m.Get("/:sha", repo2.GetSingleCommit)
|
||||
m.Get("/*", repo2.GetReferenceSHA)
|
||||
})
|
||||
|
||||
m.Group("/keys", func() {
|
||||
m.Combo("").
|
||||
Get(repo2.ListDeployKeys).
|
||||
Post(bind(api.CreateKeyOption{}), repo2.CreateDeployKey)
|
||||
m.Combo("/:id").
|
||||
Get(repo2.GetDeployKey).
|
||||
Delete(repo2.DeleteDeploykey)
|
||||
}, reqRepoAdmin())
|
||||
|
||||
m.Group("/issues", func() {
|
||||
m.Combo("").
|
||||
Get(repo2.ListIssues).
|
||||
Post(bind(api.CreateIssueOption{}), repo2.CreateIssue)
|
||||
m.Group("/comments", func() {
|
||||
m.Get("", repo2.ListRepoIssueComments)
|
||||
m.Patch("/:id", bind(api.EditIssueCommentOption{}), repo2.EditIssueComment)
|
||||
})
|
||||
m.Group("/:index", func() {
|
||||
m.Combo("").
|
||||
Get(repo2.GetIssue).
|
||||
Patch(bind(api.EditIssueOption{}), repo2.EditIssue)
|
||||
|
||||
m.Group("/comments", func() {
|
||||
m.Combo("").
|
||||
Get(repo2.ListIssueComments).
|
||||
Post(bind(api.CreateIssueCommentOption{}), repo2.CreateIssueComment)
|
||||
m.Combo("/:id").
|
||||
Patch(bind(api.EditIssueCommentOption{}), repo2.EditIssueComment).
|
||||
Delete(repo2.DeleteIssueComment)
|
||||
})
|
||||
|
||||
m.Get("/labels", repo2.ListIssueLabels)
|
||||
m.Group("/labels", func() {
|
||||
m.Combo("").
|
||||
Post(bind(api.IssueLabelsOption{}), repo2.AddIssueLabels).
|
||||
Put(bind(api.IssueLabelsOption{}), repo2.ReplaceIssueLabels).
|
||||
Delete(repo2.ClearIssueLabels)
|
||||
m.Delete("/:id", repo2.DeleteIssueLabel)
|
||||
}, reqRepoWriter())
|
||||
})
|
||||
}, mustEnableIssues)
|
||||
|
||||
m.Group("/labels", func() {
|
||||
m.Get("", repo2.ListLabels)
|
||||
m.Get("/:id", repo2.GetLabel)
|
||||
})
|
||||
m.Group("/labels", func() {
|
||||
m.Post("", bind(api.CreateLabelOption{}), repo2.CreateLabel)
|
||||
m.Combo("/:id").
|
||||
Patch(bind(api.EditLabelOption{}), repo2.EditLabel).
|
||||
Delete(repo2.DeleteLabel)
|
||||
}, reqRepoWriter())
|
||||
|
||||
m.Group("/milestones", func() {
|
||||
m.Get("", repo2.ListMilestones)
|
||||
m.Get("/:id", repo2.GetMilestone)
|
||||
})
|
||||
m.Group("/milestones", func() {
|
||||
m.Post("", bind(api.CreateMilestoneOption{}), repo2.CreateMilestone)
|
||||
m.Combo("/:id").
|
||||
Patch(bind(api.EditMilestoneOption{}), repo2.EditMilestone).
|
||||
Delete(repo2.DeleteMilestone)
|
||||
}, reqRepoWriter())
|
||||
|
||||
m.Patch("/issue-tracker", reqRepoWriter(), bind(api.EditIssueTrackerOption{}), repo2.IssueTracker)
|
||||
m.Post("/mirror-sync", reqRepoWriter(), repo2.MirrorSync)
|
||||
m.Get("/editorconfig/:filename", context.RepoRef(), repo2.GetEditorconfig)
|
||||
}, repoAssignment())
|
||||
}, reqToken())
|
||||
|
||||
m.Get("/issues", reqToken(), repo2.ListUserIssues)
|
||||
|
||||
// Organizations
|
||||
m.Combo("/user/orgs", reqToken()).
|
||||
Get(org2.ListMyOrgs).
|
||||
Post(bind(api.CreateOrgOption{}), org2.CreateMyOrg)
|
||||
|
||||
m.Get("/users/:username/orgs", org2.ListUserOrgs)
|
||||
m.Group("/orgs/:orgname", func() {
|
||||
m.Combo("").
|
||||
Get(org2.Get).
|
||||
Patch(bind(api.EditOrgOption{}), org2.Edit)
|
||||
m.Get("/teams", org2.ListTeams)
|
||||
}, orgAssignment(true))
|
||||
|
||||
m.Group("/admin", func() {
|
||||
m.Group("/users", func() {
|
||||
m.Post("", bind(api.CreateUserOption{}), admin2.CreateUser)
|
||||
|
||||
m.Group("/:username", func() {
|
||||
m.Combo("").
|
||||
Patch(bind(api.EditUserOption{}), admin2.EditUser).
|
||||
Delete(admin2.DeleteUser)
|
||||
m.Post("/keys", bind(api.CreateKeyOption{}), admin2.CreatePublicKey)
|
||||
m.Post("/orgs", bind(api.CreateOrgOption{}), admin2.CreateOrg)
|
||||
m.Post("/repos", bind(api.CreateRepoOption{}), admin2.CreateRepo)
|
||||
})
|
||||
})
|
||||
|
||||
m.Group("/orgs/:orgname", func() {
|
||||
m.Group("/teams", func() {
|
||||
m.Post("", orgAssignment(true), bind(api.CreateTeamOption{}), admin2.CreateTeam)
|
||||
})
|
||||
})
|
||||
|
||||
m.Group("/teams", func() {
|
||||
m.Group("/:teamid", func() {
|
||||
m.Combo("/members/:username").
|
||||
Put(admin2.AddTeamMember).
|
||||
Delete(admin2.RemoveTeamMember)
|
||||
m.Combo("/repos/:reponame").
|
||||
Put(admin2.AddTeamRepository).
|
||||
Delete(admin2.RemoveTeamRepository)
|
||||
}, orgAssignment(false, true))
|
||||
})
|
||||
}, reqAdmin())
|
||||
|
||||
m.Any("/*", func(c *context.Context) {
|
||||
c.NotFound()
|
||||
})
|
||||
}, context.APIContexter())
|
||||
}
|
||||
127
internal/route/api/v1/convert/convert.go
Normal file
127
internal/route/api/v1/convert/convert.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/db"
|
||||
)
|
||||
|
||||
func ToEmail(email *db.EmailAddress) *api.Email {
|
||||
return &api.Email{
|
||||
Email: email.Email,
|
||||
Verified: email.IsActivated,
|
||||
Primary: email.IsPrimary,
|
||||
}
|
||||
}
|
||||
|
||||
func ToBranch(b *db.Branch, c *git.Commit) *api.Branch {
|
||||
return &api.Branch{
|
||||
Name: b.Name,
|
||||
Commit: ToCommit(c),
|
||||
}
|
||||
}
|
||||
|
||||
func ToCommit(c *git.Commit) *api.PayloadCommit {
|
||||
authorUsername := ""
|
||||
author, err := db.GetUserByEmail(c.Author.Email)
|
||||
if err == nil {
|
||||
authorUsername = author.Name
|
||||
}
|
||||
committerUsername := ""
|
||||
committer, err := db.GetUserByEmail(c.Committer.Email)
|
||||
if err == nil {
|
||||
committerUsername = committer.Name
|
||||
}
|
||||
return &api.PayloadCommit{
|
||||
ID: c.ID.String(),
|
||||
Message: c.Message(),
|
||||
URL: "Not implemented",
|
||||
Author: &api.PayloadUser{
|
||||
Name: c.Author.Name,
|
||||
Email: c.Author.Email,
|
||||
UserName: authorUsername,
|
||||
},
|
||||
Committer: &api.PayloadUser{
|
||||
Name: c.Committer.Name,
|
||||
Email: c.Committer.Email,
|
||||
UserName: committerUsername,
|
||||
},
|
||||
Timestamp: c.Author.When,
|
||||
}
|
||||
}
|
||||
|
||||
func ToPublicKey(apiLink string, key *db.PublicKey) *api.PublicKey {
|
||||
return &api.PublicKey{
|
||||
ID: key.ID,
|
||||
Key: key.Content,
|
||||
URL: apiLink + com.ToStr(key.ID),
|
||||
Title: key.Name,
|
||||
Created: key.Created,
|
||||
}
|
||||
}
|
||||
|
||||
func ToHook(repoLink string, w *db.Webhook) *api.Hook {
|
||||
config := map[string]string{
|
||||
"url": w.URL,
|
||||
"content_type": w.ContentType.Name(),
|
||||
}
|
||||
if w.HookTaskType == db.SLACK {
|
||||
s := w.GetSlackHook()
|
||||
config["channel"] = s.Channel
|
||||
config["username"] = s.Username
|
||||
config["icon_url"] = s.IconURL
|
||||
config["color"] = s.Color
|
||||
}
|
||||
|
||||
return &api.Hook{
|
||||
ID: w.ID,
|
||||
Type: w.HookTaskType.Name(),
|
||||
URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID),
|
||||
Active: w.IsActive,
|
||||
Config: config,
|
||||
Events: w.EventsArray(),
|
||||
Updated: w.Updated,
|
||||
Created: w.Created,
|
||||
}
|
||||
}
|
||||
|
||||
func ToDeployKey(apiLink string, key *db.DeployKey) *api.DeployKey {
|
||||
return &api.DeployKey{
|
||||
ID: key.ID,
|
||||
Key: key.Content,
|
||||
URL: apiLink + com.ToStr(key.ID),
|
||||
Title: key.Name,
|
||||
Created: key.Created,
|
||||
ReadOnly: true, // All deploy keys are read-only.
|
||||
}
|
||||
}
|
||||
|
||||
func ToOrganization(org *db.User) *api.Organization {
|
||||
return &api.Organization{
|
||||
ID: org.ID,
|
||||
AvatarUrl: org.AvatarLink(),
|
||||
UserName: org.Name,
|
||||
FullName: org.FullName,
|
||||
Description: org.Description,
|
||||
Website: org.Website,
|
||||
Location: org.Location,
|
||||
}
|
||||
}
|
||||
|
||||
func ToTeam(team *db.Team) *api.Team {
|
||||
return &api.Team{
|
||||
ID: team.ID,
|
||||
Name: team.Name,
|
||||
Description: team.Description,
|
||||
Permission: team.Authorize.String(),
|
||||
}
|
||||
}
|
||||
19
internal/route/api/v1/convert/utils.go
Normal file
19
internal/route/api/v1/convert/utils.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
// ToCorrectPageSize makes sure page size is in allowed range.
|
||||
func ToCorrectPageSize(size int) int {
|
||||
if size <= 0 {
|
||||
size = 10
|
||||
} else if size > setting.API.MaxResponseItems {
|
||||
size = setting.API.MaxResponseItems
|
||||
}
|
||||
return size
|
||||
}
|
||||
42
internal/route/api/v1/misc/markdown.go
Normal file
42
internal/route/api/v1/misc/markdown.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package misc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/markup"
|
||||
)
|
||||
|
||||
func Markdown(c *context.APIContext, form api.MarkdownOption) {
|
||||
if c.HasApiError() {
|
||||
c.Error(http.StatusUnprocessableEntity, "", c.GetErrMsg())
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Text) == 0 {
|
||||
c.Write([]byte(""))
|
||||
return
|
||||
}
|
||||
|
||||
switch form.Mode {
|
||||
case "gfm":
|
||||
c.Write(markup.Markdown([]byte(form.Text), form.Context, nil))
|
||||
default:
|
||||
c.Write(markup.RawMarkdown([]byte(form.Text), ""))
|
||||
}
|
||||
}
|
||||
|
||||
func MarkdownRaw(c *context.APIContext) {
|
||||
body, err := c.Req.Body().Bytes()
|
||||
if err != nil {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
return
|
||||
}
|
||||
c.Write(markup.RawMarkdown(body, ""))
|
||||
}
|
||||
96
internal/route/api/v1/org/org.go
Normal file
96
internal/route/api/v1/org/org.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
user2 "gogs.io/gogs/internal/route/api/v1/user"
|
||||
"net/http"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
)
|
||||
|
||||
func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user *db.User) {
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
org := &db.User{
|
||||
Name: apiForm.UserName,
|
||||
FullName: apiForm.FullName,
|
||||
Description: apiForm.Description,
|
||||
Website: apiForm.Website,
|
||||
Location: apiForm.Location,
|
||||
IsActive: true,
|
||||
Type: db.USER_TYPE_ORGANIZATION,
|
||||
}
|
||||
if err := db.CreateOrganization(org, user); err != nil {
|
||||
if db.IsErrUserAlreadyExist(err) ||
|
||||
db.IsErrNameReserved(err) ||
|
||||
db.IsErrNamePatternNotAllowed(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("CreateOrganization", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(201, convert2.ToOrganization(org))
|
||||
}
|
||||
|
||||
func listUserOrgs(c *context.APIContext, u *db.User, all bool) {
|
||||
if err := u.GetOrganizations(all); err != nil {
|
||||
c.ServerError("GetOrganizations", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiOrgs := make([]*api.Organization, len(u.Orgs))
|
||||
for i := range u.Orgs {
|
||||
apiOrgs[i] = convert2.ToOrganization(u.Orgs[i])
|
||||
}
|
||||
c.JSONSuccess(&apiOrgs)
|
||||
}
|
||||
|
||||
func ListMyOrgs(c *context.APIContext) {
|
||||
listUserOrgs(c, c.User, true)
|
||||
}
|
||||
|
||||
func CreateMyOrg(c *context.APIContext, apiForm api.CreateOrgOption) {
|
||||
CreateOrgForUser(c, apiForm, c.User)
|
||||
}
|
||||
|
||||
func ListUserOrgs(c *context.APIContext) {
|
||||
u := user2.GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
listUserOrgs(c, u, false)
|
||||
}
|
||||
|
||||
func Get(c *context.APIContext) {
|
||||
c.JSONSuccess(convert2.ToOrganization(c.Org.Organization))
|
||||
}
|
||||
|
||||
func Edit(c *context.APIContext, form api.EditOrgOption) {
|
||||
org := c.Org.Organization
|
||||
if !org.IsOwnedBy(c.User.ID) {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
org.FullName = form.FullName
|
||||
org.Description = form.Description
|
||||
org.Website = form.Website
|
||||
org.Location = form.Location
|
||||
if err := db.UpdateUser(org); err != nil {
|
||||
c.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSONSuccess(convert2.ToOrganization(org))
|
||||
}
|
||||
26
internal/route/api/v1/org/team.go
Normal file
26
internal/route/api/v1/org/team.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
)
|
||||
|
||||
func ListTeams(c *context.APIContext) {
|
||||
org := c.Org.Organization
|
||||
if err := org.GetTeams(); err != nil {
|
||||
c.Error(500, "GetTeams", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiTeams := make([]*api.Team, len(org.Teams))
|
||||
for i := range org.Teams {
|
||||
apiTeams[i] = convert2.ToTeam(org.Teams[i])
|
||||
}
|
||||
c.JSON(200, apiTeams)
|
||||
}
|
||||
55
internal/route/api/v1/repo/branch.go
Normal file
55
internal/route/api/v1/repo/branch.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
)
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories#get-branch
|
||||
func GetBranch(c *context.APIContext) {
|
||||
branch, err := c.Repo.Repository.GetBranch(c.Params("*"))
|
||||
if err != nil {
|
||||
if errors.IsErrBranchNotExist(err) {
|
||||
c.Error(404, "GetBranch", err)
|
||||
} else {
|
||||
c.Error(500, "GetBranch", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
commit, err := branch.GetCommit()
|
||||
if err != nil {
|
||||
c.Error(500, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, convert2.ToBranch(branch, commit))
|
||||
}
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories#list-branches
|
||||
func ListBranches(c *context.APIContext) {
|
||||
branches, err := c.Repo.Repository.GetBranches()
|
||||
if err != nil {
|
||||
c.Error(500, "GetBranches", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiBranches := make([]*api.Branch, len(branches))
|
||||
for i := range branches {
|
||||
commit, err := branches[i].GetCommit()
|
||||
if err != nil {
|
||||
c.Error(500, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
apiBranches[i] = convert2.ToBranch(branches[i], commit)
|
||||
}
|
||||
|
||||
c.JSON(200, &apiBranches)
|
||||
}
|
||||
90
internal/route/api/v1/repo/collaborators.go
Normal file
90
internal/route/api/v1/repo/collaborators.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
)
|
||||
|
||||
func ListCollaborators(c *context.APIContext) {
|
||||
collaborators, err := c.Repo.Repository.GetCollaborators()
|
||||
if err != nil {
|
||||
c.ServerError("GetCollaborators", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiCollaborators := make([]*api.Collaborator, len(collaborators))
|
||||
for i := range collaborators {
|
||||
apiCollaborators[i] = collaborators[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiCollaborators)
|
||||
}
|
||||
|
||||
func AddCollaborator(c *context.APIContext, form api.AddCollaboratorOption) {
|
||||
collaborator, err := db.GetUserByName(c.Params(":collaborator"))
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Error(422, "", err)
|
||||
} else {
|
||||
c.Error(500, "GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Repo.Repository.AddCollaborator(collaborator); err != nil {
|
||||
c.Error(500, "AddCollaborator", err)
|
||||
return
|
||||
}
|
||||
|
||||
if form.Permission != nil {
|
||||
if err := c.Repo.Repository.ChangeCollaborationAccessMode(collaborator.ID, db.ParseAccessMode(*form.Permission)); err != nil {
|
||||
c.Error(500, "ChangeCollaborationAccessMode", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Status(204)
|
||||
}
|
||||
|
||||
func IsCollaborator(c *context.APIContext) {
|
||||
collaborator, err := db.GetUserByName(c.Params(":collaborator"))
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Error(422, "", err)
|
||||
} else {
|
||||
c.Error(500, "GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !c.Repo.Repository.IsCollaborator(collaborator.ID) {
|
||||
c.Status(404)
|
||||
} else {
|
||||
c.Status(204)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteCollaborator(c *context.APIContext) {
|
||||
collaborator, err := db.GetUserByName(c.Params(":collaborator"))
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Error(422, "", err)
|
||||
} else {
|
||||
c.Error(500, "GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Repo.Repository.DeleteCollaboration(collaborator.ID); err != nil {
|
||||
c.Error(500, "DeleteCollaboration", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(204)
|
||||
}
|
||||
138
internal/route/api/v1/repo/commits.go
Normal file
138
internal/route/api/v1/repo/commits.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2018 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
func GetSingleCommit(c *context.APIContext) {
|
||||
if strings.Contains(c.Req.Header.Get("Accept"), api.MediaApplicationSHA) {
|
||||
c.SetParams("*", c.Params(":sha"))
|
||||
GetReferenceSHA(c)
|
||||
return
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(c.Repo.Repository.RepoPath())
|
||||
if err != nil {
|
||||
c.ServerError("OpenRepository", err)
|
||||
return
|
||||
}
|
||||
commit, err := gitRepo.GetCommit(c.Params(":sha"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve author and committer information
|
||||
var apiAuthor, apiCommitter *api.User
|
||||
author, err := db.GetUserByEmail(commit.Author.Email)
|
||||
if err != nil && !errors.IsUserNotExist(err) {
|
||||
c.ServerError("Get user by author email", err)
|
||||
return
|
||||
} else if err == nil {
|
||||
apiAuthor = author.APIFormat()
|
||||
}
|
||||
// Save one query if the author is also the committer
|
||||
if commit.Committer.Email == commit.Author.Email {
|
||||
apiCommitter = apiAuthor
|
||||
} else {
|
||||
committer, err := db.GetUserByEmail(commit.Committer.Email)
|
||||
if err != nil && !errors.IsUserNotExist(err) {
|
||||
c.ServerError("Get user by committer email", err)
|
||||
return
|
||||
} else if err == nil {
|
||||
apiCommitter = committer.APIFormat()
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve parent(s) of the commit
|
||||
apiParents := make([]*api.CommitMeta, commit.ParentCount())
|
||||
for i := 0; i < commit.ParentCount(); i++ {
|
||||
sha, _ := commit.ParentID(i)
|
||||
apiParents[i] = &api.CommitMeta{
|
||||
URL: c.BaseURL + "/repos/" + c.Repo.Repository.FullName() + "/commits/" + sha.String(),
|
||||
SHA: sha.String(),
|
||||
}
|
||||
}
|
||||
|
||||
c.JSONSuccess(&api.Commit{
|
||||
CommitMeta: &api.CommitMeta{
|
||||
URL: setting.AppURL + c.Link[1:],
|
||||
SHA: commit.ID.String(),
|
||||
},
|
||||
HTMLURL: c.Repo.Repository.HTMLURL() + "/commits/" + commit.ID.String(),
|
||||
RepoCommit: &api.RepoCommit{
|
||||
URL: setting.AppURL + c.Link[1:],
|
||||
Author: &api.CommitUser{
|
||||
Name: commit.Author.Name,
|
||||
Email: commit.Author.Email,
|
||||
Date: commit.Author.When.Format(time.RFC3339),
|
||||
},
|
||||
Committer: &api.CommitUser{
|
||||
Name: commit.Committer.Name,
|
||||
Email: commit.Committer.Email,
|
||||
Date: commit.Committer.When.Format(time.RFC3339),
|
||||
},
|
||||
Message: commit.Summary(),
|
||||
Tree: &api.CommitMeta{
|
||||
URL: c.BaseURL + "/repos/" + c.Repo.Repository.FullName() + "/tree/" + commit.ID.String(),
|
||||
SHA: commit.ID.String(),
|
||||
},
|
||||
},
|
||||
Author: apiAuthor,
|
||||
Committer: apiCommitter,
|
||||
Parents: apiParents,
|
||||
})
|
||||
}
|
||||
|
||||
func GetReferenceSHA(c *context.APIContext) {
|
||||
gitRepo, err := git.OpenRepository(c.Repo.Repository.RepoPath())
|
||||
if err != nil {
|
||||
c.ServerError("OpenRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
ref := c.Params("*")
|
||||
refType := 0 // 0-undetermined, 1-branch, 2-tag
|
||||
if strings.HasPrefix(ref, git.BRANCH_PREFIX) {
|
||||
ref = strings.TrimPrefix(ref, git.BRANCH_PREFIX)
|
||||
refType = 1
|
||||
} else if strings.HasPrefix(ref, git.TAG_PREFIX) {
|
||||
ref = strings.TrimPrefix(ref, git.TAG_PREFIX)
|
||||
refType = 2
|
||||
} else {
|
||||
if gitRepo.IsBranchExist(ref) {
|
||||
refType = 1
|
||||
} else if gitRepo.IsTagExist(ref) {
|
||||
refType = 2
|
||||
} else {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var sha string
|
||||
if refType == 1 {
|
||||
sha, err = gitRepo.GetBranchCommitID(ref)
|
||||
} else if refType == 2 {
|
||||
sha, err = gitRepo.GetTagCommitID(ref)
|
||||
}
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("get reference commit ID", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
c.PlainText(http.StatusOK, []byte(sha))
|
||||
}
|
||||
62
internal/route/api/v1/repo/file.go
Normal file
62
internal/route/api/v1/repo/file.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/gogs/git-module"
|
||||
repo2 "gogs.io/gogs/internal/route/repo"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
)
|
||||
|
||||
func GetRawFile(c *context.APIContext) {
|
||||
if !c.Repo.HasAccess() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if c.Repo.Repository.IsBare {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetBlobByPath", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
if err = repo2.ServeBlob(c.Context, blob); err != nil {
|
||||
c.ServerError("ServeBlob", err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetArchive(c *context.APIContext) {
|
||||
repoPath := db.RepoPath(c.Params(":username"), c.Params(":reponame"))
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
c.ServerError("OpenRepository", err)
|
||||
return
|
||||
}
|
||||
c.Repo.GitRepo = gitRepo
|
||||
|
||||
repo2.Download(c.Context)
|
||||
}
|
||||
|
||||
func GetEditorconfig(c *context.APIContext) {
|
||||
ec, err := c.Repo.GetEditorconfig()
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetEditorconfig", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
fileName := c.Params("filename")
|
||||
def := ec.GetDefinitionForFilename(fileName)
|
||||
if def == nil {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
c.JSONSuccess(def)
|
||||
}
|
||||
185
internal/route/api/v1/repo/hook.go
Normal file
185
internal/route/api/v1/repo/hook.go
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/json-iterator/go"
|
||||
"github.com/unknwon/com"
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
)
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories#list-hooks
|
||||
func ListHooks(c *context.APIContext) {
|
||||
hooks, err := db.GetWebhooksByRepoID(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.Error(500, "GetWebhooksByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiHooks := make([]*api.Hook, len(hooks))
|
||||
for i := range hooks {
|
||||
apiHooks[i] = convert2.ToHook(c.Repo.RepoLink, hooks[i])
|
||||
}
|
||||
c.JSON(200, &apiHooks)
|
||||
}
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories#create-a-hook
|
||||
func CreateHook(c *context.APIContext, form api.CreateHookOption) {
|
||||
if !db.IsValidHookTaskType(form.Type) {
|
||||
c.Error(422, "", "Invalid hook type")
|
||||
return
|
||||
}
|
||||
for _, name := range []string{"url", "content_type"} {
|
||||
if _, ok := form.Config[name]; !ok {
|
||||
c.Error(422, "", "Missing config option: "+name)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !db.IsValidHookContentType(form.Config["content_type"]) {
|
||||
c.Error(422, "", "Invalid content type")
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Events) == 0 {
|
||||
form.Events = []string{"push"}
|
||||
}
|
||||
w := &db.Webhook{
|
||||
RepoID: c.Repo.Repository.ID,
|
||||
URL: form.Config["url"],
|
||||
ContentType: db.ToHookContentType(form.Config["content_type"]),
|
||||
Secret: form.Config["secret"],
|
||||
HookEvent: &db.HookEvent{
|
||||
ChooseEvents: true,
|
||||
HookEvents: db.HookEvents{
|
||||
Create: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_CREATE)),
|
||||
Delete: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_DELETE)),
|
||||
Fork: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_FORK)),
|
||||
Push: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_PUSH)),
|
||||
Issues: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_ISSUES)),
|
||||
IssueComment: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_ISSUE_COMMENT)),
|
||||
PullRequest: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_PULL_REQUEST)),
|
||||
Release: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_RELEASE)),
|
||||
},
|
||||
},
|
||||
IsActive: form.Active,
|
||||
HookTaskType: db.ToHookTaskType(form.Type),
|
||||
}
|
||||
if w.HookTaskType == db.SLACK {
|
||||
channel, ok := form.Config["channel"]
|
||||
if !ok {
|
||||
c.Error(422, "", "Missing config option: channel")
|
||||
return
|
||||
}
|
||||
meta, err := jsoniter.Marshal(&db.SlackMeta{
|
||||
Channel: channel,
|
||||
Username: form.Config["username"],
|
||||
IconURL: form.Config["icon_url"],
|
||||
Color: form.Config["color"],
|
||||
})
|
||||
if err != nil {
|
||||
c.Error(500, "slack: JSON marshal failed", err)
|
||||
return
|
||||
}
|
||||
w.Meta = string(meta)
|
||||
}
|
||||
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Error(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.CreateWebhook(w); err != nil {
|
||||
c.Error(500, "CreateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(201, convert2.ToHook(c.Repo.RepoLink, w))
|
||||
}
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories#edit-a-hook
|
||||
func EditHook(c *context.APIContext, form api.EditHookOption) {
|
||||
w, err := db.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
if errors.IsWebhookNotExist(err) {
|
||||
c.Status(404)
|
||||
} else {
|
||||
c.Error(500, "GetWebhookOfRepoByID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if form.Config != nil {
|
||||
if url, ok := form.Config["url"]; ok {
|
||||
w.URL = url
|
||||
}
|
||||
if ct, ok := form.Config["content_type"]; ok {
|
||||
if !db.IsValidHookContentType(ct) {
|
||||
c.Error(422, "", "Invalid content type")
|
||||
return
|
||||
}
|
||||
w.ContentType = db.ToHookContentType(ct)
|
||||
}
|
||||
|
||||
if w.HookTaskType == db.SLACK {
|
||||
if channel, ok := form.Config["channel"]; ok {
|
||||
meta, err := jsoniter.Marshal(&db.SlackMeta{
|
||||
Channel: channel,
|
||||
Username: form.Config["username"],
|
||||
IconURL: form.Config["icon_url"],
|
||||
Color: form.Config["color"],
|
||||
})
|
||||
if err != nil {
|
||||
c.Error(500, "slack: JSON marshal failed", err)
|
||||
return
|
||||
}
|
||||
w.Meta = string(meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update events
|
||||
if len(form.Events) == 0 {
|
||||
form.Events = []string{"push"}
|
||||
}
|
||||
w.PushOnly = false
|
||||
w.SendEverything = false
|
||||
w.ChooseEvents = true
|
||||
w.Create = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_CREATE))
|
||||
w.Delete = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_DELETE))
|
||||
w.Fork = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_FORK))
|
||||
w.Push = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_PUSH))
|
||||
w.Issues = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_ISSUES))
|
||||
w.IssueComment = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_ISSUE_COMMENT))
|
||||
w.PullRequest = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_PULL_REQUEST))
|
||||
w.Release = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_RELEASE))
|
||||
if err = w.UpdateEvent(); err != nil {
|
||||
c.Error(500, "UpdateEvent", err)
|
||||
return
|
||||
}
|
||||
|
||||
if form.Active != nil {
|
||||
w.IsActive = *form.Active
|
||||
}
|
||||
|
||||
if err := db.UpdateWebhook(w); err != nil {
|
||||
c.Error(500, "UpdateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, convert2.ToHook(c.Repo.RepoLink, w))
|
||||
}
|
||||
|
||||
func DeleteHook(c *context.APIContext) {
|
||||
if err := db.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
|
||||
c.Error(500, "DeleteWebhookByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(204)
|
||||
}
|
||||
194
internal/route/api/v1/repo/issue.go
Normal file
194
internal/route/api/v1/repo/issue.go
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
func listIssues(c *context.APIContext, opts *db.IssuesOptions) {
|
||||
issues, err := db.Issues(opts)
|
||||
if err != nil {
|
||||
c.ServerError("Issues", err)
|
||||
return
|
||||
}
|
||||
|
||||
count, err := db.IssuesCount(opts)
|
||||
if err != nil {
|
||||
c.ServerError("IssuesCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: use IssueList to improve performance.
|
||||
apiIssues := make([]*api.Issue, len(issues))
|
||||
for i := range issues {
|
||||
if err = issues[i].LoadAttributes(); err != nil {
|
||||
c.ServerError("LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
apiIssues[i] = issues[i].APIFormat()
|
||||
}
|
||||
|
||||
c.SetLinkHeader(int(count), setting.UI.IssuePagingNum)
|
||||
c.JSONSuccess(&apiIssues)
|
||||
}
|
||||
|
||||
func ListUserIssues(c *context.APIContext) {
|
||||
opts := db.IssuesOptions{
|
||||
AssigneeID: c.User.ID,
|
||||
Page: c.QueryInt("page"),
|
||||
IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED,
|
||||
}
|
||||
|
||||
listIssues(c, &opts)
|
||||
}
|
||||
|
||||
func ListIssues(c *context.APIContext) {
|
||||
opts := db.IssuesOptions{
|
||||
RepoID: c.Repo.Repository.ID,
|
||||
Page: c.QueryInt("page"),
|
||||
IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED,
|
||||
}
|
||||
|
||||
listIssues(c, &opts)
|
||||
}
|
||||
|
||||
func GetIssue(c *context.APIContext) {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
c.JSONSuccess(issue.APIFormat())
|
||||
}
|
||||
|
||||
func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
|
||||
issue := &db.Issue{
|
||||
RepoID: c.Repo.Repository.ID,
|
||||
Title: form.Title,
|
||||
PosterID: c.User.ID,
|
||||
Poster: c.User,
|
||||
Content: form.Body,
|
||||
}
|
||||
|
||||
if c.Repo.IsWriter() {
|
||||
if len(form.Assignee) > 0 {
|
||||
assignee, err := db.GetUserByName(form.Assignee)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee))
|
||||
} else {
|
||||
c.ServerError("GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
issue.AssigneeID = assignee.ID
|
||||
}
|
||||
issue.MilestoneID = form.Milestone
|
||||
} else {
|
||||
form.Labels = nil
|
||||
}
|
||||
|
||||
if err := db.NewIssue(c.Repo.Repository, issue, form.Labels, nil); err != nil {
|
||||
c.ServerError("NewIssue", err)
|
||||
return
|
||||
}
|
||||
|
||||
if form.Closed {
|
||||
if err := issue.ChangeStatus(c.User, c.Repo.Repository, true); err != nil {
|
||||
c.ServerError("ChangeStatus", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Refetch from database to assign some automatic values
|
||||
var err error
|
||||
issue, err = db.GetIssueByID(issue.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetIssueByID", err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, issue.APIFormat())
|
||||
}
|
||||
|
||||
func EditIssue(c *context.APIContext, form api.EditIssueOption) {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !issue.IsPoster(c.User.ID) && !c.Repo.IsWriter() {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Title) > 0 {
|
||||
issue.Title = form.Title
|
||||
}
|
||||
if form.Body != nil {
|
||||
issue.Content = *form.Body
|
||||
}
|
||||
|
||||
if c.Repo.IsWriter() && form.Assignee != nil &&
|
||||
(issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) {
|
||||
if len(*form.Assignee) == 0 {
|
||||
issue.AssigneeID = 0
|
||||
} else {
|
||||
assignee, err := db.GetUserByName(*form.Assignee)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee))
|
||||
} else {
|
||||
c.ServerError("GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
issue.AssigneeID = assignee.ID
|
||||
}
|
||||
|
||||
if err = db.UpdateIssueUserByAssignee(issue); err != nil {
|
||||
c.ServerError("UpdateIssueUserByAssignee", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if c.Repo.IsWriter() && form.Milestone != nil &&
|
||||
issue.MilestoneID != *form.Milestone {
|
||||
oldMilestoneID := issue.MilestoneID
|
||||
issue.MilestoneID = *form.Milestone
|
||||
if err = db.ChangeMilestoneAssign(c.User, issue, oldMilestoneID); err != nil {
|
||||
c.ServerError("ChangeMilestoneAssign", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = db.UpdateIssue(issue); err != nil {
|
||||
c.ServerError("UpdateIssue", err)
|
||||
return
|
||||
}
|
||||
if form.State != nil {
|
||||
if err = issue.ChangeStatus(c.User, c.Repo.Repository, api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
|
||||
c.ServerError("ChangeStatus", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Refetch from database to assign some automatic values
|
||||
issue, err = db.GetIssueByID(issue.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetIssueByID", err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, issue.APIFormat())
|
||||
}
|
||||
131
internal/route/api/v1/repo/issue_comment.go
Normal file
131
internal/route/api/v1/repo/issue_comment.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
)
|
||||
|
||||
func ListIssueComments(c *context.APIContext) {
|
||||
var since time.Time
|
||||
if len(c.Query("since")) > 0 {
|
||||
var err error
|
||||
since, err = time.Parse(time.RFC3339, c.Query("since"))
|
||||
if err != nil {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// comments,err:=db.GetCommentsByIssueIDSince(, since)
|
||||
issue, err := db.GetRawIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.ServerError("GetRawIssueByIndex", err)
|
||||
return
|
||||
}
|
||||
|
||||
comments, err := db.GetCommentsByIssueIDSince(issue.ID, since.Unix())
|
||||
if err != nil {
|
||||
c.ServerError("GetCommentsByIssueIDSince", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiComments := make([]*api.Comment, len(comments))
|
||||
for i := range comments {
|
||||
apiComments[i] = comments[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiComments)
|
||||
}
|
||||
|
||||
func ListRepoIssueComments(c *context.APIContext) {
|
||||
var since time.Time
|
||||
if len(c.Query("since")) > 0 {
|
||||
var err error
|
||||
since, err = time.Parse(time.RFC3339, c.Query("since"))
|
||||
if err != nil {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
comments, err := db.GetCommentsByRepoIDSince(c.Repo.Repository.ID, since.Unix())
|
||||
if err != nil {
|
||||
c.ServerError("GetCommentsByRepoIDSince", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiComments := make([]*api.Comment, len(comments))
|
||||
for i := range comments {
|
||||
apiComments[i] = comments[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiComments)
|
||||
}
|
||||
|
||||
func CreateIssueComment(c *context.APIContext, form api.CreateIssueCommentOption) {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.ServerError("GetIssueByIndex", err)
|
||||
return
|
||||
}
|
||||
|
||||
comment, err := db.CreateIssueComment(c.User, c.Repo.Repository, issue, form.Body, nil)
|
||||
if err != nil {
|
||||
c.ServerError("CreateIssueComment", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, comment.APIFormat())
|
||||
}
|
||||
|
||||
func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) {
|
||||
comment, err := db.GetCommentByID(c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetCommentByID", db.IsErrCommentNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
} else if comment.Type != db.COMMENT_TYPE_COMMENT {
|
||||
c.NoContent()
|
||||
return
|
||||
}
|
||||
|
||||
oldContent := comment.Content
|
||||
comment.Content = form.Body
|
||||
if err := db.UpdateComment(c.User, comment, oldContent); err != nil {
|
||||
c.ServerError("UpdateComment", err)
|
||||
return
|
||||
}
|
||||
c.JSONSuccess(comment.APIFormat())
|
||||
}
|
||||
|
||||
func DeleteIssueComment(c *context.APIContext) {
|
||||
comment, err := db.GetCommentByID(c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetCommentByID", db.IsErrCommentNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
} else if comment.Type != db.COMMENT_TYPE_COMMENT {
|
||||
c.NoContent()
|
||||
return
|
||||
}
|
||||
|
||||
if err = db.DeleteCommentByID(c.User, comment.ID); err != nil {
|
||||
c.ServerError("DeleteCommentByID", err)
|
||||
return
|
||||
}
|
||||
c.NoContent()
|
||||
}
|
||||
131
internal/route/api/v1/repo/issue_label.go
Normal file
131
internal/route/api/v1/repo/issue_label.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
)
|
||||
|
||||
func ListIssueLabels(c *context.APIContext) {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLabels := make([]*api.Label, len(issue.Labels))
|
||||
for i := range issue.Labels {
|
||||
apiLabels[i] = issue.Labels[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiLabels)
|
||||
}
|
||||
|
||||
func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
labels, err := db.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels)
|
||||
if err != nil {
|
||||
c.ServerError("GetLabelsInRepoByIDs", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = issue.AddLabels(c.User, labels); err != nil {
|
||||
c.ServerError("AddLabels", err)
|
||||
return
|
||||
}
|
||||
|
||||
labels, err = db.GetLabelsByIssueID(issue.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetLabelsByIssueID", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLabels := make([]*api.Label, len(labels))
|
||||
for i := range labels {
|
||||
apiLabels[i] = issue.Labels[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiLabels)
|
||||
}
|
||||
|
||||
func DeleteIssueLabel(c *context.APIContext) {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
label, err := db.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
if db.IsErrLabelNotExist(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("GetLabelInRepoByID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DeleteIssueLabel(issue, label); err != nil {
|
||||
c.ServerError("DeleteIssueLabel", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
|
||||
func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
labels, err := db.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels)
|
||||
if err != nil {
|
||||
c.ServerError("GetLabelsInRepoByIDs", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := issue.ReplaceLabels(labels); err != nil {
|
||||
c.ServerError("ReplaceLabels", err)
|
||||
return
|
||||
}
|
||||
|
||||
labels, err = db.GetLabelsByIssueID(issue.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetLabelsByIssueID", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLabels := make([]*api.Label, len(labels))
|
||||
for i := range labels {
|
||||
apiLabels[i] = issue.Labels[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiLabels)
|
||||
}
|
||||
|
||||
func ClearIssueLabels(c *context.APIContext) {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := issue.ClearLabels(c.User); err != nil {
|
||||
c.ServerError("ClearLabels", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
114
internal/route/api/v1/repo/key.go
Normal file
114
internal/route/api/v1/repo/key.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
func composeDeployKeysAPILink(repoPath string) string {
|
||||
return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/"
|
||||
}
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#list-deploy-keys
|
||||
func ListDeployKeys(c *context.APIContext) {
|
||||
keys, err := db.ListDeployKeys(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.Error(500, "ListDeployKeys", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
|
||||
apiKeys := make([]*api.DeployKey, len(keys))
|
||||
for i := range keys {
|
||||
if err = keys[i].GetContent(); err != nil {
|
||||
c.Error(500, "GetContent", err)
|
||||
return
|
||||
}
|
||||
apiKeys[i] = convert2.ToDeployKey(apiLink, keys[i])
|
||||
}
|
||||
|
||||
c.JSON(200, &apiKeys)
|
||||
}
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#get-a-deploy-key
|
||||
func GetDeployKey(c *context.APIContext) {
|
||||
key, err := db.GetDeployKeyByID(c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
if db.IsErrDeployKeyNotExist(err) {
|
||||
c.Status(404)
|
||||
} else {
|
||||
c.Error(500, "GetDeployKeyByID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = key.GetContent(); err != nil {
|
||||
c.Error(500, "GetContent", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
|
||||
c.JSON(200, convert2.ToDeployKey(apiLink, key))
|
||||
}
|
||||
|
||||
func HandleCheckKeyStringError(c *context.APIContext, err error) {
|
||||
if db.IsErrKeyUnableVerify(err) {
|
||||
c.Error(422, "", "Unable to verify key content")
|
||||
} else {
|
||||
c.Error(422, "", fmt.Errorf("Invalid key content: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func HandleAddKeyError(c *context.APIContext, err error) {
|
||||
switch {
|
||||
case db.IsErrKeyAlreadyExist(err):
|
||||
c.Error(422, "", "Key content has been used as non-deploy key")
|
||||
case db.IsErrKeyNameAlreadyUsed(err):
|
||||
c.Error(422, "", "Key title has been used")
|
||||
default:
|
||||
c.Error(500, "AddKey", err)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#add-a-new-deploy-key
|
||||
func CreateDeployKey(c *context.APIContext, form api.CreateKeyOption) {
|
||||
content, err := db.CheckPublicKeyString(form.Key)
|
||||
if err != nil {
|
||||
HandleCheckKeyStringError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := db.AddDeployKey(c.Repo.Repository.ID, form.Title, content)
|
||||
if err != nil {
|
||||
HandleAddKeyError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
key.Content = content
|
||||
apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
|
||||
c.JSON(201, convert2.ToDeployKey(apiLink, key))
|
||||
}
|
||||
|
||||
// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key
|
||||
func DeleteDeploykey(c *context.APIContext) {
|
||||
if err := db.DeleteDeployKey(c.User, c.ParamsInt64(":id")); err != nil {
|
||||
if db.IsErrKeyAccessDenied(err) {
|
||||
c.Error(403, "", "You do not have access to this key")
|
||||
} else {
|
||||
c.Error(500, "DeleteDeployKey", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(204)
|
||||
}
|
||||
89
internal/route/api/v1/repo/label.go
Normal file
89
internal/route/api/v1/repo/label.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
)
|
||||
|
||||
func ListLabels(c *context.APIContext) {
|
||||
labels, err := db.GetLabelsByRepoID(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetLabelsByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLabels := make([]*api.Label, len(labels))
|
||||
for i := range labels {
|
||||
apiLabels[i] = labels[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiLabels)
|
||||
}
|
||||
|
||||
func GetLabel(c *context.APIContext) {
|
||||
var label *db.Label
|
||||
var err error
|
||||
idStr := c.Params(":id")
|
||||
if id := com.StrTo(idStr).MustInt64(); id > 0 {
|
||||
label, err = db.GetLabelOfRepoByID(c.Repo.Repository.ID, id)
|
||||
} else {
|
||||
label, err = db.GetLabelOfRepoByName(c.Repo.Repository.ID, idStr)
|
||||
}
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetLabel", db.IsErrLabelNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSONSuccess(label.APIFormat())
|
||||
}
|
||||
|
||||
func CreateLabel(c *context.APIContext, form api.CreateLabelOption) {
|
||||
label := &db.Label{
|
||||
Name: form.Name,
|
||||
Color: form.Color,
|
||||
RepoID: c.Repo.Repository.ID,
|
||||
}
|
||||
if err := db.NewLabels(label); err != nil {
|
||||
c.ServerError("NewLabel", err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, label.APIFormat())
|
||||
}
|
||||
|
||||
func EditLabel(c *context.APIContext, form api.EditLabelOption) {
|
||||
label, err := db.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetLabelOfRepoByID", db.IsErrLabelNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if form.Name != nil {
|
||||
label.Name = *form.Name
|
||||
}
|
||||
if form.Color != nil {
|
||||
label.Color = *form.Color
|
||||
}
|
||||
if err := db.UpdateLabel(label); err != nil {
|
||||
c.ServerError("UpdateLabel", err)
|
||||
return
|
||||
}
|
||||
c.JSONSuccess(label.APIFormat())
|
||||
}
|
||||
|
||||
func DeleteLabel(c *context.APIContext) {
|
||||
if err := db.DeleteLabel(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
|
||||
c.ServerError("DeleteLabel", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
96
internal/route/api/v1/repo/milestone.go
Normal file
96
internal/route/api/v1/repo/milestone.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
)
|
||||
|
||||
func ListMilestones(c *context.APIContext) {
|
||||
milestones, err := db.GetMilestonesByRepoID(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetMilestonesByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiMilestones := make([]*api.Milestone, len(milestones))
|
||||
for i := range milestones {
|
||||
apiMilestones[i] = milestones[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiMilestones)
|
||||
}
|
||||
|
||||
func GetMilestone(c *context.APIContext) {
|
||||
milestone, err := db.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetMilestoneByRepoID", db.IsErrMilestoneNotExist, err)
|
||||
return
|
||||
}
|
||||
c.JSONSuccess(milestone.APIFormat())
|
||||
}
|
||||
|
||||
func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) {
|
||||
if form.Deadline == nil {
|
||||
defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
|
||||
form.Deadline = &defaultDeadline
|
||||
}
|
||||
|
||||
milestone := &db.Milestone{
|
||||
RepoID: c.Repo.Repository.ID,
|
||||
Name: form.Title,
|
||||
Content: form.Description,
|
||||
Deadline: *form.Deadline,
|
||||
}
|
||||
|
||||
if err := db.NewMilestone(milestone); err != nil {
|
||||
c.ServerError("NewMilestone", err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, milestone.APIFormat())
|
||||
}
|
||||
|
||||
func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) {
|
||||
milestone, err := db.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetMilestoneByRepoID", db.IsErrMilestoneNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Title) > 0 {
|
||||
milestone.Name = form.Title
|
||||
}
|
||||
if form.Description != nil {
|
||||
milestone.Content = *form.Description
|
||||
}
|
||||
if form.Deadline != nil && !form.Deadline.IsZero() {
|
||||
milestone.Deadline = *form.Deadline
|
||||
}
|
||||
|
||||
if form.State != nil {
|
||||
if err = milestone.ChangeStatus(api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
|
||||
c.ServerError("ChangeStatus", err)
|
||||
return
|
||||
}
|
||||
} else if err = db.UpdateMilestone(milestone); err != nil {
|
||||
c.ServerError("UpdateMilestone", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSONSuccess(milestone.APIFormat())
|
||||
}
|
||||
|
||||
func DeleteMilestone(c *context.APIContext) {
|
||||
if err := db.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
|
||||
c.ServerError("DeleteMilestoneByRepoID", err)
|
||||
return
|
||||
}
|
||||
c.NoContent()
|
||||
}
|
||||
407
internal/route/api/v1/repo/repo.go
Normal file
407
internal/route/api/v1/repo/repo.go
Normal file
@@ -0,0 +1,407 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
func Search(c *context.APIContext) {
|
||||
opts := &db.SearchRepoOptions{
|
||||
Keyword: path.Base(c.Query("q")),
|
||||
OwnerID: c.QueryInt64("uid"),
|
||||
PageSize: convert2.ToCorrectPageSize(c.QueryInt("limit")),
|
||||
Page: c.QueryInt("page"),
|
||||
}
|
||||
|
||||
// Check visibility.
|
||||
if c.IsLogged && opts.OwnerID > 0 {
|
||||
if c.User.ID == opts.OwnerID {
|
||||
opts.Private = true
|
||||
} else {
|
||||
u, err := db.GetUserByID(opts.OwnerID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if u.IsOrganization() && u.IsOwnedBy(c.User.ID) {
|
||||
opts.Private = true
|
||||
}
|
||||
// FIXME: how about collaborators?
|
||||
}
|
||||
}
|
||||
|
||||
repos, count, err := db.SearchRepositoryByName(opts)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err = db.RepositoryList(repos).LoadAttributes(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]*api.Repository, len(repos))
|
||||
for i := range repos {
|
||||
results[i] = repos[i].APIFormat(nil)
|
||||
}
|
||||
|
||||
c.SetLinkHeader(int(count), opts.PageSize)
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"ok": true,
|
||||
"data": results,
|
||||
})
|
||||
}
|
||||
|
||||
func listUserRepositories(c *context.APIContext, username string) {
|
||||
user, err := db.GetUserByName(username)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Only list public repositories if user requests someone else's repository list,
|
||||
// or an organization isn't a member of.
|
||||
var ownRepos []*db.Repository
|
||||
if user.IsOrganization() {
|
||||
ownRepos, _, err = user.GetUserRepositories(c.User.ID, 1, user.NumRepos)
|
||||
} else {
|
||||
ownRepos, err = db.GetUserRepositories(&db.UserRepoOptions{
|
||||
UserID: user.ID,
|
||||
Private: c.User.ID == user.ID,
|
||||
Page: 1,
|
||||
PageSize: user.NumRepos,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
c.ServerError("GetUserRepositories", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = db.RepositoryList(ownRepos).LoadAttributes(); err != nil {
|
||||
c.ServerError("LoadAttributes(ownRepos)", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Early return for querying other user's repositories
|
||||
if c.User.ID != user.ID {
|
||||
repos := make([]*api.Repository, len(ownRepos))
|
||||
for i := range ownRepos {
|
||||
repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true})
|
||||
}
|
||||
c.JSONSuccess(&repos)
|
||||
return
|
||||
}
|
||||
|
||||
accessibleRepos, err := user.GetRepositoryAccesses()
|
||||
if err != nil {
|
||||
c.ServerError("GetRepositoryAccesses", err)
|
||||
return
|
||||
}
|
||||
|
||||
numOwnRepos := len(ownRepos)
|
||||
repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
|
||||
for i := range ownRepos {
|
||||
repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true})
|
||||
}
|
||||
|
||||
i := numOwnRepos
|
||||
for repo, access := range accessibleRepos {
|
||||
repos[i] = repo.APIFormat(&api.Permission{
|
||||
Admin: access >= db.ACCESS_MODE_ADMIN,
|
||||
Push: access >= db.ACCESS_MODE_WRITE,
|
||||
Pull: true,
|
||||
})
|
||||
i++
|
||||
}
|
||||
|
||||
c.JSONSuccess(&repos)
|
||||
}
|
||||
|
||||
func ListMyRepos(c *context.APIContext) {
|
||||
listUserRepositories(c, c.User.Name)
|
||||
}
|
||||
|
||||
func ListUserRepositories(c *context.APIContext) {
|
||||
listUserRepositories(c, c.Params(":username"))
|
||||
}
|
||||
|
||||
func ListOrgRepositories(c *context.APIContext) {
|
||||
listUserRepositories(c, c.Params(":org"))
|
||||
}
|
||||
|
||||
func CreateUserRepo(c *context.APIContext, owner *db.User, opt api.CreateRepoOption) {
|
||||
repo, err := db.CreateRepository(c.User, owner, db.CreateRepoOptions{
|
||||
Name: opt.Name,
|
||||
Description: opt.Description,
|
||||
Gitignores: opt.Gitignores,
|
||||
License: opt.License,
|
||||
Readme: opt.Readme,
|
||||
IsPrivate: opt.Private,
|
||||
AutoInit: opt.AutoInit,
|
||||
})
|
||||
if err != nil {
|
||||
if db.IsErrRepoAlreadyExist(err) ||
|
||||
db.IsErrNameReserved(err) ||
|
||||
db.IsErrNamePatternNotAllowed(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
if repo != nil {
|
||||
if err = db.DeleteRepository(c.User.ID, repo.ID); err != nil {
|
||||
log.Error(2, "DeleteRepository: %v", err)
|
||||
}
|
||||
}
|
||||
c.ServerError("CreateRepository", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
|
||||
}
|
||||
|
||||
func Create(c *context.APIContext, opt api.CreateRepoOption) {
|
||||
// Shouldn't reach this condition, but just in case.
|
||||
if c.User.IsOrganization() {
|
||||
c.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
|
||||
return
|
||||
}
|
||||
CreateUserRepo(c, c.User, opt)
|
||||
}
|
||||
|
||||
func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) {
|
||||
org, err := db.GetOrgByName(c.Params(":org"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetOrgByName", errors.IsUserNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !org.IsOwnedBy(c.User.ID) {
|
||||
c.Error(http.StatusForbidden, "", "given user is not owner of organization")
|
||||
return
|
||||
}
|
||||
CreateUserRepo(c, org, opt)
|
||||
}
|
||||
|
||||
func Migrate(c *context.APIContext, f form.MigrateRepo) {
|
||||
ctxUser := c.User
|
||||
// Not equal means context user is an organization,
|
||||
// or is another user/organization if current user is admin.
|
||||
if f.Uid != ctxUser.ID {
|
||||
org, err := db.GetUserByID(f.Uid)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.Error(http.StatusInternalServerError, "GetUserByID", err)
|
||||
}
|
||||
return
|
||||
} else if !org.IsOrganization() && !c.User.IsAdmin {
|
||||
c.Error(http.StatusForbidden, "", "given user is not an organization")
|
||||
return
|
||||
}
|
||||
ctxUser = org
|
||||
}
|
||||
|
||||
if c.HasError() {
|
||||
c.Error(http.StatusUnprocessableEntity, "", c.GetErrMsg())
|
||||
return
|
||||
}
|
||||
|
||||
if ctxUser.IsOrganization() && !c.User.IsAdmin {
|
||||
// Check ownership of organization.
|
||||
if !ctxUser.IsOwnedBy(c.User.ID) {
|
||||
c.Error(http.StatusForbidden, "", "Given user is not owner of organization")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
remoteAddr, err := f.ParseRemoteAddr(c.User)
|
||||
if err != nil {
|
||||
if db.IsErrInvalidCloneAddr(err) {
|
||||
addrErr := err.(db.ErrInvalidCloneAddr)
|
||||
switch {
|
||||
case addrErr.IsURLError:
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
case addrErr.IsPermissionDenied:
|
||||
c.Error(http.StatusUnprocessableEntity, "", "you are not allowed to import local repositories")
|
||||
case addrErr.IsInvalidPath:
|
||||
c.Error(http.StatusUnprocessableEntity, "", "invalid local path, it does not exist or not a directory")
|
||||
default:
|
||||
c.ServerError("ParseRemoteAddr", fmt.Errorf("unknown error type (ErrInvalidCloneAddr): %v", err))
|
||||
}
|
||||
} else {
|
||||
c.ServerError("ParseRemoteAddr", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := db.MigrateRepository(c.User, ctxUser, db.MigrateRepoOptions{
|
||||
Name: f.RepoName,
|
||||
Description: f.Description,
|
||||
IsPrivate: f.Private || setting.Repository.ForcePrivate,
|
||||
IsMirror: f.Mirror,
|
||||
RemoteAddr: remoteAddr,
|
||||
})
|
||||
if err != nil {
|
||||
if repo != nil {
|
||||
if errDelete := db.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
|
||||
log.Error(2, "DeleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
|
||||
if errors.IsReachLimitOfRepo(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("MigrateRepository", errors.New(db.HandleMirrorCredentials(err.Error(), true)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository migrated: %s/%s", ctxUser.Name, f.RepoName)
|
||||
c.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
|
||||
}
|
||||
|
||||
// FIXME: inject in the handler chain
|
||||
func parseOwnerAndRepo(c *context.APIContext) (*db.User, *db.Repository) {
|
||||
owner, err := db.GetUserByName(c.Params(":username"))
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("GetUserByName", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
repo, err := db.GetRepositoryByName(owner.ID, c.Params(":reponame"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return owner, repo
|
||||
}
|
||||
|
||||
func Get(c *context.APIContext) {
|
||||
_, repo := parseOwnerAndRepo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSONSuccess(repo.APIFormat(&api.Permission{
|
||||
Admin: c.Repo.IsAdmin(),
|
||||
Push: c.Repo.IsWriter(),
|
||||
Pull: true,
|
||||
}))
|
||||
}
|
||||
|
||||
func Delete(c *context.APIContext) {
|
||||
owner, repo := parseOwnerAndRepo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if owner.IsOrganization() && !owner.IsOwnedBy(c.User.ID) {
|
||||
c.Error(http.StatusForbidden, "", "given user is not owner of organization")
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DeleteRepository(owner.ID, repo.ID); err != nil {
|
||||
c.ServerError("DeleteRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
|
||||
c.NoContent()
|
||||
}
|
||||
|
||||
func ListForks(c *context.APIContext) {
|
||||
forks, err := c.Repo.Repository.GetForks()
|
||||
if err != nil {
|
||||
c.ServerError("GetForks", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiForks := make([]*api.Repository, len(forks))
|
||||
for i := range forks {
|
||||
if err := forks[i].GetOwner(); err != nil {
|
||||
c.ServerError("GetOwner", err)
|
||||
return
|
||||
}
|
||||
apiForks[i] = forks[i].APIFormat(&api.Permission{
|
||||
Admin: c.User.IsAdminOfRepo(forks[i]),
|
||||
Push: c.User.IsWriterOfRepo(forks[i]),
|
||||
Pull: true,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSONSuccess(&apiForks)
|
||||
}
|
||||
|
||||
func IssueTracker(c *context.APIContext, form api.EditIssueTrackerOption) {
|
||||
_, repo := parseOwnerAndRepo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if form.EnableIssues != nil {
|
||||
repo.EnableIssues = *form.EnableIssues
|
||||
}
|
||||
if form.EnableExternalTracker != nil {
|
||||
repo.EnableExternalTracker = *form.EnableExternalTracker
|
||||
}
|
||||
if form.ExternalTrackerURL != nil {
|
||||
repo.ExternalTrackerURL = *form.ExternalTrackerURL
|
||||
}
|
||||
if form.TrackerURLFormat != nil {
|
||||
repo.ExternalTrackerFormat = *form.TrackerURLFormat
|
||||
}
|
||||
if form.TrackerIssueStyle != nil {
|
||||
repo.ExternalTrackerStyle = *form.TrackerIssueStyle
|
||||
}
|
||||
|
||||
if err := db.UpdateRepository(repo, false); err != nil {
|
||||
c.ServerError("UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
|
||||
func MirrorSync(c *context.APIContext) {
|
||||
_, repo := parseOwnerAndRepo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
} else if !repo.IsMirror {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
go db.MirrorQueue.Add(repo.ID)
|
||||
c.Status(http.StatusAccepted)
|
||||
}
|
||||
45
internal/route/api/v1/user/app.go
Normal file
45
internal/route/api/v1/user/app.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
)
|
||||
|
||||
func ListAccessTokens(c *context.APIContext) {
|
||||
tokens, err := db.ListAccessTokens(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("ListAccessTokens", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiTokens := make([]*api.AccessToken, len(tokens))
|
||||
for i := range tokens {
|
||||
apiTokens[i] = &api.AccessToken{tokens[i].Name, tokens[i].Sha1}
|
||||
}
|
||||
c.JSONSuccess(&apiTokens)
|
||||
}
|
||||
|
||||
func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) {
|
||||
t := &db.AccessToken{
|
||||
UID: c.User.ID,
|
||||
Name: form.Name,
|
||||
}
|
||||
if err := db.NewAccessToken(t); err != nil {
|
||||
if errors.IsAccessTokenNameAlreadyExist(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", err)
|
||||
} else {
|
||||
c.ServerError("NewAccessToken", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, &api.AccessToken{t.Name, t.Sha1})
|
||||
}
|
||||
81
internal/route/api/v1/user/email.go
Normal file
81
internal/route/api/v1/user/email.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
"net/http"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
func ListEmails(c *context.APIContext) {
|
||||
emails, err := db.GetEmailAddresses(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
apiEmails := make([]*api.Email, len(emails))
|
||||
for i := range emails {
|
||||
apiEmails[i] = convert2.ToEmail(emails[i])
|
||||
}
|
||||
c.JSONSuccess(&apiEmails)
|
||||
}
|
||||
|
||||
func AddEmail(c *context.APIContext, form api.CreateEmailOption) {
|
||||
if len(form.Emails) == 0 {
|
||||
c.Status(http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
emails := make([]*db.EmailAddress, len(form.Emails))
|
||||
for i := range form.Emails {
|
||||
emails[i] = &db.EmailAddress{
|
||||
UID: c.User.ID,
|
||||
Email: form.Emails[i],
|
||||
IsActivated: !setting.Service.RegisterEmailConfirm,
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.AddEmailAddresses(emails); err != nil {
|
||||
if db.IsErrEmailAlreadyUsed(err) {
|
||||
c.Error(http.StatusUnprocessableEntity, "", "email address has been used: "+err.(db.ErrEmailAlreadyUsed).Email)
|
||||
} else {
|
||||
c.Error(http.StatusInternalServerError, "AddEmailAddresses", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
apiEmails := make([]*api.Email, len(emails))
|
||||
for i := range emails {
|
||||
apiEmails[i] = convert2.ToEmail(emails[i])
|
||||
}
|
||||
c.JSON(http.StatusCreated, &apiEmails)
|
||||
}
|
||||
|
||||
func DeleteEmail(c *context.APIContext, form api.CreateEmailOption) {
|
||||
if len(form.Emails) == 0 {
|
||||
c.NoContent()
|
||||
return
|
||||
}
|
||||
|
||||
emails := make([]*db.EmailAddress, len(form.Emails))
|
||||
for i := range form.Emails {
|
||||
emails[i] = &db.EmailAddress{
|
||||
UID: c.User.ID,
|
||||
Email: form.Emails[i],
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.DeleteEmailAddresses(emails); err != nil {
|
||||
c.Error(http.StatusInternalServerError, "DeleteEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
c.NoContent()
|
||||
}
|
||||
114
internal/route/api/v1/user/follower.go
Normal file
114
internal/route/api/v1/user/follower.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
)
|
||||
|
||||
func responseApiUsers(c *context.APIContext, users []*db.User) {
|
||||
apiUsers := make([]*api.User, len(users))
|
||||
for i := range users {
|
||||
apiUsers[i] = users[i].APIFormat()
|
||||
}
|
||||
c.JSONSuccess(&apiUsers)
|
||||
}
|
||||
|
||||
func listUserFollowers(c *context.APIContext, u *db.User) {
|
||||
users, err := u.GetFollowers(c.QueryInt("page"))
|
||||
if err != nil {
|
||||
c.ServerError("GetUserFollowers", err)
|
||||
return
|
||||
}
|
||||
responseApiUsers(c, users)
|
||||
}
|
||||
|
||||
func ListMyFollowers(c *context.APIContext) {
|
||||
listUserFollowers(c, c.User)
|
||||
}
|
||||
|
||||
func ListFollowers(c *context.APIContext) {
|
||||
u := GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
listUserFollowers(c, u)
|
||||
}
|
||||
|
||||
func listUserFollowing(c *context.APIContext, u *db.User) {
|
||||
users, err := u.GetFollowing(c.QueryInt("page"))
|
||||
if err != nil {
|
||||
c.ServerError("GetFollowing", err)
|
||||
return
|
||||
}
|
||||
responseApiUsers(c, users)
|
||||
}
|
||||
|
||||
func ListMyFollowing(c *context.APIContext) {
|
||||
listUserFollowing(c, c.User)
|
||||
}
|
||||
|
||||
func ListFollowing(c *context.APIContext) {
|
||||
u := GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
listUserFollowing(c, u)
|
||||
}
|
||||
|
||||
func checkUserFollowing(c *context.APIContext, u *db.User, followID int64) {
|
||||
if u.IsFollowing(followID) {
|
||||
c.NoContent()
|
||||
} else {
|
||||
c.NotFound()
|
||||
}
|
||||
}
|
||||
|
||||
func CheckMyFollowing(c *context.APIContext) {
|
||||
target := GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
checkUserFollowing(c, c.User, target.ID)
|
||||
}
|
||||
|
||||
func CheckFollowing(c *context.APIContext) {
|
||||
u := GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
target := GetUserByParamsName(c, ":target")
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
checkUserFollowing(c, u, target.ID)
|
||||
}
|
||||
|
||||
func Follow(c *context.APIContext) {
|
||||
target := GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
if err := db.FollowUser(c.User.ID, target.ID); err != nil {
|
||||
c.ServerError("FollowUser", err)
|
||||
return
|
||||
}
|
||||
c.NoContent()
|
||||
}
|
||||
|
||||
func Unfollow(c *context.APIContext) {
|
||||
target := GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
if err := db.UnfollowUser(c.User.ID, target.ID); err != nil {
|
||||
c.ServerError("UnfollowUser", err)
|
||||
return
|
||||
}
|
||||
c.NoContent()
|
||||
}
|
||||
108
internal/route/api/v1/user/key.go
Normal file
108
internal/route/api/v1/user/key.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
convert2 "gogs.io/gogs/internal/route/api/v1/convert"
|
||||
repo2 "gogs.io/gogs/internal/route/api/v1/repo"
|
||||
"net/http"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
func GetUserByParamsName(c *context.APIContext, name string) *db.User {
|
||||
user, err := db.GetUserByName(c.Params(name))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// GetUserByParams returns user whose name is presented in URL paramenter.
|
||||
func GetUserByParams(c *context.APIContext) *db.User {
|
||||
return GetUserByParamsName(c, ":username")
|
||||
}
|
||||
|
||||
func composePublicKeysAPILink() string {
|
||||
return setting.AppURL + "api/v1/user/keys/"
|
||||
}
|
||||
|
||||
func listPublicKeys(c *context.APIContext, uid int64) {
|
||||
keys, err := db.ListPublicKeys(uid)
|
||||
if err != nil {
|
||||
c.ServerError("ListPublicKeys", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLink := composePublicKeysAPILink()
|
||||
apiKeys := make([]*api.PublicKey, len(keys))
|
||||
for i := range keys {
|
||||
apiKeys[i] = convert2.ToPublicKey(apiLink, keys[i])
|
||||
}
|
||||
|
||||
c.JSONSuccess(&apiKeys)
|
||||
}
|
||||
|
||||
func ListMyPublicKeys(c *context.APIContext) {
|
||||
listPublicKeys(c, c.User.ID)
|
||||
}
|
||||
|
||||
func ListPublicKeys(c *context.APIContext) {
|
||||
user := GetUserByParams(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
listPublicKeys(c, user.ID)
|
||||
}
|
||||
|
||||
func GetPublicKey(c *context.APIContext) {
|
||||
key, err := db.GetPublicKeyByID(c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetPublicKeyByID", db.IsErrKeyNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLink := composePublicKeysAPILink()
|
||||
c.JSONSuccess(convert2.ToPublicKey(apiLink, key))
|
||||
}
|
||||
|
||||
// CreateUserPublicKey creates new public key to given user by ID.
|
||||
func CreateUserPublicKey(c *context.APIContext, form api.CreateKeyOption, uid int64) {
|
||||
content, err := db.CheckPublicKeyString(form.Key)
|
||||
if err != nil {
|
||||
repo2.HandleCheckKeyStringError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := db.AddPublicKey(uid, form.Title, content)
|
||||
if err != nil {
|
||||
repo2.HandleAddKeyError(c, err)
|
||||
return
|
||||
}
|
||||
apiLink := composePublicKeysAPILink()
|
||||
c.JSON(http.StatusCreated, convert2.ToPublicKey(apiLink, key))
|
||||
}
|
||||
|
||||
func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) {
|
||||
CreateUserPublicKey(c, form, c.User.ID)
|
||||
}
|
||||
|
||||
func DeletePublicKey(c *context.APIContext) {
|
||||
if err := db.DeletePublicKey(c.User, c.ParamsInt64(":id")); err != nil {
|
||||
if db.IsErrKeyAccessDenied(err) {
|
||||
c.Error(http.StatusForbidden, "", "you do not have access to this key")
|
||||
} else {
|
||||
c.Error(http.StatusInternalServerError, "DeletePublicKey", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.NoContent()
|
||||
}
|
||||
74
internal/route/api/v1/user/user.go
Normal file
74
internal/route/api/v1/user/user.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/markup"
|
||||
)
|
||||
|
||||
func Search(c *context.APIContext) {
|
||||
opts := &db.SearchUserOptions{
|
||||
Keyword: c.Query("q"),
|
||||
Type: db.USER_TYPE_INDIVIDUAL,
|
||||
PageSize: com.StrTo(c.Query("limit")).MustInt(),
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
opts.PageSize = 10
|
||||
}
|
||||
|
||||
users, _, err := db.SearchUserByName(opts)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]*api.User, len(users))
|
||||
for i := range users {
|
||||
results[i] = &api.User{
|
||||
ID: users[i].ID,
|
||||
UserName: users[i].Name,
|
||||
AvatarUrl: users[i].AvatarLink(),
|
||||
FullName: markup.Sanitize(users[i].FullName),
|
||||
}
|
||||
if c.IsLogged {
|
||||
results[i].Email = users[i].Email
|
||||
}
|
||||
}
|
||||
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"ok": true,
|
||||
"data": results,
|
||||
})
|
||||
}
|
||||
|
||||
func GetInfo(c *context.APIContext) {
|
||||
u, err := db.GetUserByName(c.Params(":username"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Hide user e-mail when API caller isn't signed in.
|
||||
if !c.IsLogged {
|
||||
u.Email = ""
|
||||
}
|
||||
c.JSONSuccess(u.APIFormat())
|
||||
}
|
||||
|
||||
func GetAuthenticatedUser(c *context.APIContext) {
|
||||
c.JSONSuccess(c.User.APIFormat())
|
||||
}
|
||||
24
internal/route/dev/template.go
Normal file
24
internal/route/dev/template.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dev
|
||||
|
||||
import (
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
func TemplatePreview(c *context.Context) {
|
||||
c.Data["User"] = db.User{Name: "Unknown"}
|
||||
c.Data["AppName"] = setting.AppName
|
||||
c.Data["AppVer"] = setting.AppVer
|
||||
c.Data["AppURL"] = setting.AppURL
|
||||
c.Data["Code"] = "2014031910370000009fff6782aadb2162b4a997acb69d4400888e0b9274657374"
|
||||
c.Data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
|
||||
c.Data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
|
||||
c.Data["CurDbValue"] = ""
|
||||
|
||||
c.HTML(200, (c.Params("*")))
|
||||
}
|
||||
163
internal/route/home.go
Normal file
163
internal/route/home.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/unknwon/paginater"
|
||||
user2 "gogs.io/gogs/internal/route/user"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
HOME = "home"
|
||||
EXPLORE_REPOS = "explore/repos"
|
||||
EXPLORE_USERS = "explore/users"
|
||||
EXPLORE_ORGANIZATIONS = "explore/organizations"
|
||||
)
|
||||
|
||||
func Home(c *context.Context) {
|
||||
if c.IsLogged {
|
||||
if !c.User.IsActive && setting.Service.RegisterEmailConfirm {
|
||||
c.Data["Title"] = c.Tr("auth.active_your_account")
|
||||
c.Success(user2.ACTIVATE)
|
||||
} else {
|
||||
user2.Dashboard(c)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check auto-login.
|
||||
uname := c.GetCookie(setting.CookieUserName)
|
||||
if len(uname) != 0 {
|
||||
c.Redirect(setting.AppSubURL + "/user/login")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["PageIsHome"] = true
|
||||
c.Success(HOME)
|
||||
}
|
||||
|
||||
func ExploreRepos(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("explore")
|
||||
c.Data["PageIsExplore"] = true
|
||||
c.Data["PageIsExploreRepositories"] = true
|
||||
|
||||
page := c.QueryInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
keyword := c.Query("q")
|
||||
repos, count, err := db.SearchRepositoryByName(&db.SearchRepoOptions{
|
||||
Keyword: keyword,
|
||||
UserID: c.UserID(),
|
||||
OrderBy: "updated_unix DESC",
|
||||
Page: page,
|
||||
PageSize: setting.UI.ExplorePagingNum,
|
||||
})
|
||||
if err != nil {
|
||||
c.ServerError("SearchRepositoryByName", err)
|
||||
return
|
||||
}
|
||||
c.Data["Keyword"] = keyword
|
||||
c.Data["Total"] = count
|
||||
c.Data["Page"] = paginater.New(int(count), setting.UI.ExplorePagingNum, page, 5)
|
||||
|
||||
if err = db.RepositoryList(repos).LoadAttributes(); err != nil {
|
||||
c.ServerError("RepositoryList.LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
c.Data["Repos"] = repos
|
||||
|
||||
c.Success(EXPLORE_REPOS)
|
||||
}
|
||||
|
||||
type UserSearchOptions struct {
|
||||
Type db.UserType
|
||||
Counter func() int64
|
||||
Ranger func(int, int) ([]*db.User, error)
|
||||
PageSize int
|
||||
OrderBy string
|
||||
TplName string
|
||||
}
|
||||
|
||||
func RenderUserSearch(c *context.Context, opts *UserSearchOptions) {
|
||||
page := c.QueryInt("page")
|
||||
if page <= 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
var (
|
||||
users []*db.User
|
||||
count int64
|
||||
err error
|
||||
)
|
||||
|
||||
keyword := c.Query("q")
|
||||
if len(keyword) == 0 {
|
||||
users, err = opts.Ranger(page, opts.PageSize)
|
||||
if err != nil {
|
||||
c.ServerError("Ranger", err)
|
||||
return
|
||||
}
|
||||
count = opts.Counter()
|
||||
} else {
|
||||
users, count, err = db.SearchUserByName(&db.SearchUserOptions{
|
||||
Keyword: keyword,
|
||||
Type: opts.Type,
|
||||
OrderBy: opts.OrderBy,
|
||||
Page: page,
|
||||
PageSize: opts.PageSize,
|
||||
})
|
||||
if err != nil {
|
||||
c.ServerError("SearchUserByName", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["Keyword"] = keyword
|
||||
c.Data["Total"] = count
|
||||
c.Data["Page"] = paginater.New(int(count), opts.PageSize, page, 5)
|
||||
c.Data["Users"] = users
|
||||
|
||||
c.Success(opts.TplName)
|
||||
}
|
||||
|
||||
func ExploreUsers(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("explore")
|
||||
c.Data["PageIsExplore"] = true
|
||||
c.Data["PageIsExploreUsers"] = true
|
||||
|
||||
RenderUserSearch(c, &UserSearchOptions{
|
||||
Type: db.USER_TYPE_INDIVIDUAL,
|
||||
Counter: db.CountUsers,
|
||||
Ranger: db.Users,
|
||||
PageSize: setting.UI.ExplorePagingNum,
|
||||
OrderBy: "updated_unix DESC",
|
||||
TplName: EXPLORE_USERS,
|
||||
})
|
||||
}
|
||||
|
||||
func ExploreOrganizations(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("explore")
|
||||
c.Data["PageIsExplore"] = true
|
||||
c.Data["PageIsExploreOrganizations"] = true
|
||||
|
||||
RenderUserSearch(c, &UserSearchOptions{
|
||||
Type: db.USER_TYPE_ORGANIZATION,
|
||||
Counter: db.CountOrganizations,
|
||||
Ranger: db.Organizations,
|
||||
PageSize: setting.UI.ExplorePagingNum,
|
||||
OrderBy: "updated_unix DESC",
|
||||
TplName: EXPLORE_ORGANIZATIONS,
|
||||
})
|
||||
}
|
||||
|
||||
func NotFound(c *context.Context) {
|
||||
c.Data["Title"] = "Page Not Found"
|
||||
c.NotFound()
|
||||
}
|
||||
403
internal/route/install.go
Normal file
403
internal/route/install.go
Normal file
@@ -0,0 +1,403 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
"gopkg.in/ini.v1"
|
||||
"gopkg.in/macaron.v1"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/cron"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/mailer"
|
||||
"gogs.io/gogs/internal/markup"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/ssh"
|
||||
"gogs.io/gogs/internal/template/highlight"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
"gogs.io/gogs/internal/user"
|
||||
)
|
||||
|
||||
const (
|
||||
INSTALL = "install"
|
||||
)
|
||||
|
||||
func checkRunMode() {
|
||||
if setting.ProdMode {
|
||||
macaron.Env = macaron.PROD
|
||||
macaron.ColorLog = false
|
||||
} else {
|
||||
git.Debug = true
|
||||
}
|
||||
log.Info("Run Mode: %s", strings.Title(macaron.Env))
|
||||
}
|
||||
|
||||
func NewServices() {
|
||||
setting.NewServices()
|
||||
mailer.NewContext()
|
||||
}
|
||||
|
||||
// GlobalInit is for global configuration reload-able.
|
||||
func GlobalInit() {
|
||||
setting.NewContext()
|
||||
log.Trace("Custom path: %s", setting.CustomPath)
|
||||
log.Trace("Log path: %s", setting.LogRootPath)
|
||||
db.LoadConfigs()
|
||||
NewServices()
|
||||
|
||||
if setting.InstallLock {
|
||||
highlight.NewContext()
|
||||
markup.NewSanitizer()
|
||||
if err := db.NewEngine(); err != nil {
|
||||
log.Fatal(2, "Fail to initialize ORM engine: %v", err)
|
||||
}
|
||||
db.HasEngine = true
|
||||
|
||||
db.LoadAuthSources()
|
||||
db.LoadRepoConfig()
|
||||
db.NewRepoContext()
|
||||
|
||||
// Booting long running goroutines.
|
||||
cron.NewContext()
|
||||
db.InitSyncMirrors()
|
||||
db.InitDeliverHooks()
|
||||
db.InitTestPullRequests()
|
||||
}
|
||||
if db.EnableSQLite3 {
|
||||
log.Info("SQLite3 Supported")
|
||||
}
|
||||
if setting.SupportMiniWinService {
|
||||
log.Info("Builtin Windows Service Supported")
|
||||
}
|
||||
checkRunMode()
|
||||
|
||||
if !setting.InstallLock {
|
||||
return
|
||||
}
|
||||
|
||||
if setting.SSH.StartBuiltinServer {
|
||||
ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers)
|
||||
log.Info("SSH server started on %s:%v", setting.SSH.ListenHost, setting.SSH.ListenPort)
|
||||
log.Trace("SSH server cipher list: %v", setting.SSH.ServerCiphers)
|
||||
}
|
||||
|
||||
if setting.SSH.RewriteAuthorizedKeysAtStart {
|
||||
if err := db.RewriteAuthorizedKeys(); err != nil {
|
||||
log.Warn("Failed to rewrite authorized_keys file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func InstallInit(c *context.Context) {
|
||||
if setting.InstallLock {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
c.Title("install.install")
|
||||
c.PageIs("Install")
|
||||
|
||||
dbOpts := []string{"MySQL", "PostgreSQL", "MSSQL"}
|
||||
if db.EnableSQLite3 {
|
||||
dbOpts = append(dbOpts, "SQLite3")
|
||||
}
|
||||
c.Data["DbOptions"] = dbOpts
|
||||
}
|
||||
|
||||
func Install(c *context.Context) {
|
||||
f := form.Install{}
|
||||
|
||||
// Database settings
|
||||
f.DbHost = db.DbCfg.Host
|
||||
f.DbUser = db.DbCfg.User
|
||||
f.DbName = db.DbCfg.Name
|
||||
f.DbPath = db.DbCfg.Path
|
||||
|
||||
c.Data["CurDbOption"] = "MySQL"
|
||||
switch db.DbCfg.Type {
|
||||
case "postgres":
|
||||
c.Data["CurDbOption"] = "PostgreSQL"
|
||||
case "mssql":
|
||||
c.Data["CurDbOption"] = "MSSQL"
|
||||
case "sqlite3":
|
||||
if db.EnableSQLite3 {
|
||||
c.Data["CurDbOption"] = "SQLite3"
|
||||
}
|
||||
}
|
||||
|
||||
// Application general settings
|
||||
f.AppName = setting.AppName
|
||||
f.RepoRootPath = setting.RepoRootPath
|
||||
|
||||
// Note(unknwon): it's hard for Windows users change a running user,
|
||||
// so just use current one if config says default.
|
||||
if setting.IsWindows && setting.RunUser == "git" {
|
||||
f.RunUser = user.CurrentUsername()
|
||||
} else {
|
||||
f.RunUser = setting.RunUser
|
||||
}
|
||||
|
||||
f.Domain = setting.Domain
|
||||
f.SSHPort = setting.SSH.Port
|
||||
f.UseBuiltinSSHServer = setting.SSH.StartBuiltinServer
|
||||
f.HTTPPort = setting.HTTPPort
|
||||
f.AppUrl = setting.AppURL
|
||||
f.LogRootPath = setting.LogRootPath
|
||||
|
||||
// E-mail service settings
|
||||
if setting.MailService != nil {
|
||||
f.SMTPHost = setting.MailService.Host
|
||||
f.SMTPFrom = setting.MailService.From
|
||||
f.SMTPUser = setting.MailService.User
|
||||
}
|
||||
f.RegisterConfirm = setting.Service.RegisterEmailConfirm
|
||||
f.MailNotify = setting.Service.EnableNotifyMail
|
||||
|
||||
// Server and other services settings
|
||||
f.OfflineMode = setting.OfflineMode
|
||||
f.DisableGravatar = setting.DisableGravatar
|
||||
f.EnableFederatedAvatar = setting.EnableFederatedAvatar
|
||||
f.DisableRegistration = setting.Service.DisableRegistration
|
||||
f.EnableCaptcha = setting.Service.EnableCaptcha
|
||||
f.RequireSignInView = setting.Service.RequireSignInView
|
||||
|
||||
form.Assign(f, c.Data)
|
||||
c.Success(INSTALL)
|
||||
}
|
||||
|
||||
func InstallPost(c *context.Context, f form.Install) {
|
||||
c.Data["CurDbOption"] = f.DbType
|
||||
|
||||
if c.HasError() {
|
||||
if c.HasValue("Err_SMTPEmail") {
|
||||
c.FormErr("SMTP")
|
||||
}
|
||||
if c.HasValue("Err_AdminName") ||
|
||||
c.HasValue("Err_AdminPasswd") ||
|
||||
c.HasValue("Err_AdminEmail") {
|
||||
c.FormErr("Admin")
|
||||
}
|
||||
|
||||
c.Success(INSTALL)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
c.RenderWithErr(c.Tr("install.test_git_failed", err), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
|
||||
// Pass basic check, now test configuration.
|
||||
// Test database setting.
|
||||
dbTypes := map[string]string{"MySQL": "mysql", "PostgreSQL": "postgres", "MSSQL": "mssql", "SQLite3": "sqlite3", "TiDB": "tidb"}
|
||||
db.DbCfg.Type = dbTypes[f.DbType]
|
||||
db.DbCfg.Host = f.DbHost
|
||||
db.DbCfg.User = f.DbUser
|
||||
db.DbCfg.Passwd = f.DbPasswd
|
||||
db.DbCfg.Name = f.DbName
|
||||
db.DbCfg.SSLMode = f.SSLMode
|
||||
db.DbCfg.Path = f.DbPath
|
||||
|
||||
if db.DbCfg.Type == "sqlite3" && len(db.DbCfg.Path) == 0 {
|
||||
c.FormErr("DbPath")
|
||||
c.RenderWithErr(c.Tr("install.err_empty_db_path"), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
|
||||
// Set test engine.
|
||||
var x *xorm.Engine
|
||||
if err := db.NewTestEngine(x); err != nil {
|
||||
if strings.Contains(err.Error(), `Unknown database type: sqlite3`) {
|
||||
c.FormErr("DbType")
|
||||
c.RenderWithErr(c.Tr("install.sqlite3_not_available", "https://gogs.io/docs/installation/install_from_binary.html"), INSTALL, &f)
|
||||
} else {
|
||||
c.FormErr("DbSetting")
|
||||
c.RenderWithErr(c.Tr("install.invalid_db_setting", err), INSTALL, &f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Test repository root path.
|
||||
f.RepoRootPath = strings.Replace(f.RepoRootPath, "\\", "/", -1)
|
||||
if err := os.MkdirAll(f.RepoRootPath, os.ModePerm); err != nil {
|
||||
c.FormErr("RepoRootPath")
|
||||
c.RenderWithErr(c.Tr("install.invalid_repo_path", err), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
|
||||
// Test log root path.
|
||||
f.LogRootPath = strings.Replace(f.LogRootPath, "\\", "/", -1)
|
||||
if err := os.MkdirAll(f.LogRootPath, os.ModePerm); err != nil {
|
||||
c.FormErr("LogRootPath")
|
||||
c.RenderWithErr(c.Tr("install.invalid_log_root_path", err), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
|
||||
currentUser, match := setting.IsRunUserMatchCurrentUser(f.RunUser)
|
||||
if !match {
|
||||
c.FormErr("RunUser")
|
||||
c.RenderWithErr(c.Tr("install.run_user_not_match", f.RunUser, currentUser), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
|
||||
// Check host address and port
|
||||
if len(f.SMTPHost) > 0 && !strings.Contains(f.SMTPHost, ":") {
|
||||
c.FormErr("SMTP", "SMTPHost")
|
||||
c.RenderWithErr(c.Tr("install.smtp_host_missing_port"), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure FROM field is valid
|
||||
if len(f.SMTPFrom) > 0 {
|
||||
_, err := mail.ParseAddress(f.SMTPFrom)
|
||||
if err != nil {
|
||||
c.FormErr("SMTP", "SMTPFrom")
|
||||
c.RenderWithErr(c.Tr("install.invalid_smtp_from", err), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check logic loophole between disable self-registration and no admin account.
|
||||
if f.DisableRegistration && len(f.AdminName) == 0 {
|
||||
c.FormErr("Services", "Admin")
|
||||
c.RenderWithErr(c.Tr("install.no_admin_and_disable_registration"), INSTALL, f)
|
||||
return
|
||||
}
|
||||
|
||||
// Check admin password.
|
||||
if len(f.AdminName) > 0 && len(f.AdminPasswd) == 0 {
|
||||
c.FormErr("Admin", "AdminPasswd")
|
||||
c.RenderWithErr(c.Tr("install.err_empty_admin_password"), INSTALL, f)
|
||||
return
|
||||
}
|
||||
if f.AdminPasswd != f.AdminConfirmPasswd {
|
||||
c.FormErr("Admin", "AdminPasswd")
|
||||
c.RenderWithErr(c.Tr("form.password_not_match"), INSTALL, f)
|
||||
return
|
||||
}
|
||||
|
||||
if f.AppUrl[len(f.AppUrl)-1] != '/' {
|
||||
f.AppUrl += "/"
|
||||
}
|
||||
|
||||
// Save settings.
|
||||
cfg := ini.Empty()
|
||||
if com.IsFile(setting.CustomConf) {
|
||||
// Keeps custom settings if there is already something.
|
||||
if err := cfg.Append(setting.CustomConf); err != nil {
|
||||
log.Error(2, "Fail to load custom conf '%s': %v", setting.CustomConf, err)
|
||||
}
|
||||
}
|
||||
cfg.Section("database").Key("DB_TYPE").SetValue(db.DbCfg.Type)
|
||||
cfg.Section("database").Key("HOST").SetValue(db.DbCfg.Host)
|
||||
cfg.Section("database").Key("NAME").SetValue(db.DbCfg.Name)
|
||||
cfg.Section("database").Key("USER").SetValue(db.DbCfg.User)
|
||||
cfg.Section("database").Key("PASSWD").SetValue(db.DbCfg.Passwd)
|
||||
cfg.Section("database").Key("SSL_MODE").SetValue(db.DbCfg.SSLMode)
|
||||
cfg.Section("database").Key("PATH").SetValue(db.DbCfg.Path)
|
||||
|
||||
cfg.Section("").Key("APP_NAME").SetValue(f.AppName)
|
||||
cfg.Section("repository").Key("ROOT").SetValue(f.RepoRootPath)
|
||||
cfg.Section("").Key("RUN_USER").SetValue(f.RunUser)
|
||||
cfg.Section("server").Key("DOMAIN").SetValue(f.Domain)
|
||||
cfg.Section("server").Key("HTTP_PORT").SetValue(f.HTTPPort)
|
||||
cfg.Section("server").Key("ROOT_URL").SetValue(f.AppUrl)
|
||||
|
||||
if f.SSHPort == 0 {
|
||||
cfg.Section("server").Key("DISABLE_SSH").SetValue("true")
|
||||
} else {
|
||||
cfg.Section("server").Key("DISABLE_SSH").SetValue("false")
|
||||
cfg.Section("server").Key("SSH_PORT").SetValue(com.ToStr(f.SSHPort))
|
||||
cfg.Section("server").Key("START_SSH_SERVER").SetValue(com.ToStr(f.UseBuiltinSSHServer))
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(f.SMTPHost)) > 0 {
|
||||
cfg.Section("mailer").Key("ENABLED").SetValue("true")
|
||||
cfg.Section("mailer").Key("HOST").SetValue(f.SMTPHost)
|
||||
cfg.Section("mailer").Key("FROM").SetValue(f.SMTPFrom)
|
||||
cfg.Section("mailer").Key("USER").SetValue(f.SMTPUser)
|
||||
cfg.Section("mailer").Key("PASSWD").SetValue(f.SMTPPasswd)
|
||||
} else {
|
||||
cfg.Section("mailer").Key("ENABLED").SetValue("false")
|
||||
}
|
||||
cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(com.ToStr(f.RegisterConfirm))
|
||||
cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(com.ToStr(f.MailNotify))
|
||||
|
||||
cfg.Section("server").Key("OFFLINE_MODE").SetValue(com.ToStr(f.OfflineMode))
|
||||
cfg.Section("picture").Key("DISABLE_GRAVATAR").SetValue(com.ToStr(f.DisableGravatar))
|
||||
cfg.Section("picture").Key("ENABLE_FEDERATED_AVATAR").SetValue(com.ToStr(f.EnableFederatedAvatar))
|
||||
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(com.ToStr(f.DisableRegistration))
|
||||
cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(com.ToStr(f.EnableCaptcha))
|
||||
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(f.RequireSignInView))
|
||||
|
||||
cfg.Section("").Key("RUN_MODE").SetValue("prod")
|
||||
|
||||
cfg.Section("session").Key("PROVIDER").SetValue("file")
|
||||
|
||||
mode := "file"
|
||||
if f.EnableConsoleMode {
|
||||
mode = "console, file"
|
||||
}
|
||||
cfg.Section("log").Key("MODE").SetValue(mode)
|
||||
cfg.Section("log").Key("LEVEL").SetValue("Info")
|
||||
cfg.Section("log").Key("ROOT_PATH").SetValue(f.LogRootPath)
|
||||
|
||||
cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
|
||||
secretKey, err := tool.RandomString(15)
|
||||
if err != nil {
|
||||
c.RenderWithErr(c.Tr("install.secret_key_failed", err), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
cfg.Section("security").Key("SECRET_KEY").SetValue(secretKey)
|
||||
|
||||
os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm)
|
||||
if err := cfg.SaveTo(setting.CustomConf); err != nil {
|
||||
c.RenderWithErr(c.Tr("install.save_config_failed", err), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
|
||||
GlobalInit()
|
||||
|
||||
// Create admin account
|
||||
if len(f.AdminName) > 0 {
|
||||
u := &db.User{
|
||||
Name: f.AdminName,
|
||||
Email: f.AdminEmail,
|
||||
Passwd: f.AdminPasswd,
|
||||
IsAdmin: true,
|
||||
IsActive: true,
|
||||
}
|
||||
if err := db.CreateUser(u); err != nil {
|
||||
if !db.IsErrUserAlreadyExist(err) {
|
||||
setting.InstallLock = false
|
||||
c.FormErr("AdminName", "AdminEmail")
|
||||
c.RenderWithErr(c.Tr("install.invalid_admin_setting", err), INSTALL, &f)
|
||||
return
|
||||
}
|
||||
log.Info("Admin account already exist")
|
||||
u, _ = db.GetUserByName(u.Name)
|
||||
}
|
||||
|
||||
// Auto-login for admin
|
||||
c.Session.Set("uid", u.ID)
|
||||
c.Session.Set("uname", u.Name)
|
||||
}
|
||||
|
||||
log.Info("First-time run install finished!")
|
||||
c.Flash.Success(c.Tr("install.install_success"))
|
||||
c.Redirect(f.AppUrl + "user/login")
|
||||
}
|
||||
123
internal/route/org/members.go
Normal file
123
internal/route/org/members.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
MEMBERS = "org/member/members"
|
||||
MEMBER_INVITE = "org/member/invite"
|
||||
)
|
||||
|
||||
func Members(c *context.Context) {
|
||||
org := c.Org.Organization
|
||||
c.Data["Title"] = org.FullName
|
||||
c.Data["PageIsOrgMembers"] = true
|
||||
|
||||
if err := org.GetMembers(); err != nil {
|
||||
c.Handle(500, "GetMembers", err)
|
||||
return
|
||||
}
|
||||
c.Data["Members"] = org.Members
|
||||
|
||||
c.HTML(200, MEMBERS)
|
||||
}
|
||||
|
||||
func MembersAction(c *context.Context) {
|
||||
uid := com.StrTo(c.Query("uid")).MustInt64()
|
||||
if uid == 0 {
|
||||
c.Redirect(c.Org.OrgLink + "/members")
|
||||
return
|
||||
}
|
||||
|
||||
org := c.Org.Organization
|
||||
var err error
|
||||
switch c.Params(":action") {
|
||||
case "private":
|
||||
if c.User.ID != uid && !c.Org.IsOwner {
|
||||
c.Error(404)
|
||||
return
|
||||
}
|
||||
err = db.ChangeOrgUserStatus(org.ID, uid, false)
|
||||
case "public":
|
||||
if c.User.ID != uid && !c.Org.IsOwner {
|
||||
c.Error(404)
|
||||
return
|
||||
}
|
||||
err = db.ChangeOrgUserStatus(org.ID, uid, true)
|
||||
case "remove":
|
||||
if !c.Org.IsOwner {
|
||||
c.Error(404)
|
||||
return
|
||||
}
|
||||
err = org.RemoveMember(uid)
|
||||
if db.IsErrLastOrgOwner(err) {
|
||||
c.Flash.Error(c.Tr("form.last_org_owner"))
|
||||
c.Redirect(c.Org.OrgLink + "/members")
|
||||
return
|
||||
}
|
||||
case "leave":
|
||||
err = org.RemoveMember(c.User.ID)
|
||||
if db.IsErrLastOrgOwner(err) {
|
||||
c.Flash.Error(c.Tr("form.last_org_owner"))
|
||||
c.Redirect(c.Org.OrgLink + "/members")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error(4, "Action(%s): %v", c.Params(":action"), err)
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"ok": false,
|
||||
"err": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if c.Params(":action") != "leave" {
|
||||
c.Redirect(c.Org.OrgLink + "/members")
|
||||
} else {
|
||||
c.Redirect(setting.AppSubURL + "/")
|
||||
}
|
||||
}
|
||||
|
||||
func Invitation(c *context.Context) {
|
||||
org := c.Org.Organization
|
||||
c.Data["Title"] = org.FullName
|
||||
c.Data["PageIsOrgMembers"] = true
|
||||
|
||||
if c.Req.Method == "POST" {
|
||||
uname := c.Query("uname")
|
||||
u, err := db.GetUserByName(uname)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Flash.Error(c.Tr("form.user_not_exist"))
|
||||
c.Redirect(c.Org.OrgLink + "/invitations/new")
|
||||
} else {
|
||||
c.Handle(500, " GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = org.AddMember(u.ID); err != nil {
|
||||
c.Handle(500, " AddMember", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("New member added(%s): %s", org.Name, u.Name)
|
||||
c.Redirect(c.Org.OrgLink + "/members")
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(200, MEMBER_INVITE)
|
||||
}
|
||||
56
internal/route/org/org.go
Normal file
56
internal/route/org/org.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
CREATE = "org/create"
|
||||
)
|
||||
|
||||
func Create(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("new_org")
|
||||
c.HTML(200, CREATE)
|
||||
}
|
||||
|
||||
func CreatePost(c *context.Context, f form.CreateOrg) {
|
||||
c.Data["Title"] = c.Tr("new_org")
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, CREATE)
|
||||
return
|
||||
}
|
||||
|
||||
org := &db.User{
|
||||
Name: f.OrgName,
|
||||
IsActive: true,
|
||||
Type: db.USER_TYPE_ORGANIZATION,
|
||||
}
|
||||
|
||||
if err := db.CreateOrganization(org, c.User); err != nil {
|
||||
c.Data["Err_OrgName"] = true
|
||||
switch {
|
||||
case db.IsErrUserAlreadyExist(err):
|
||||
c.RenderWithErr(c.Tr("form.org_name_been_taken"), CREATE, &f)
|
||||
case db.IsErrNameReserved(err):
|
||||
c.RenderWithErr(c.Tr("org.form.name_reserved", err.(db.ErrNameReserved).Name), CREATE, &f)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
c.RenderWithErr(c.Tr("org.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), CREATE, &f)
|
||||
default:
|
||||
c.Handle(500, "CreateOrganization", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Organization created: %s", org.Name)
|
||||
|
||||
c.Redirect(setting.AppSubURL + "/org/" + f.OrgName + "/dashboard")
|
||||
}
|
||||
168
internal/route/org/setting.go
Normal file
168
internal/route/org/setting.go
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
user2 "gogs.io/gogs/internal/route/user"
|
||||
"strings"
|
||||
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
SETTINGS_OPTIONS = "org/settings/options"
|
||||
SETTINGS_DELETE = "org/settings/delete"
|
||||
SETTINGS_WEBHOOKS = "org/settings/webhooks"
|
||||
)
|
||||
|
||||
func Settings(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("org.settings")
|
||||
c.Data["PageIsSettingsOptions"] = true
|
||||
c.HTML(200, SETTINGS_OPTIONS)
|
||||
}
|
||||
|
||||
func SettingsPost(c *context.Context, f form.UpdateOrgSetting) {
|
||||
c.Data["Title"] = c.Tr("org.settings")
|
||||
c.Data["PageIsSettingsOptions"] = true
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, SETTINGS_OPTIONS)
|
||||
return
|
||||
}
|
||||
|
||||
org := c.Org.Organization
|
||||
|
||||
// Check if organization name has been changed.
|
||||
if org.LowerName != strings.ToLower(f.Name) {
|
||||
isExist, err := db.IsUserExist(org.ID, f.Name)
|
||||
if err != nil {
|
||||
c.Handle(500, "IsUserExist", err)
|
||||
return
|
||||
} else if isExist {
|
||||
c.Data["OrgName"] = true
|
||||
c.RenderWithErr(c.Tr("form.username_been_taken"), SETTINGS_OPTIONS, &f)
|
||||
return
|
||||
} else if err = db.ChangeUserName(org, f.Name); err != nil {
|
||||
c.Data["OrgName"] = true
|
||||
switch {
|
||||
case db.IsErrNameReserved(err):
|
||||
c.RenderWithErr(c.Tr("user.form.name_reserved"), SETTINGS_OPTIONS, &f)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed"), SETTINGS_OPTIONS, &f)
|
||||
default:
|
||||
c.Handle(500, "ChangeUserName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// reset c.org.OrgLink with new name
|
||||
c.Org.OrgLink = setting.AppSubURL + "/org/" + f.Name
|
||||
log.Trace("Organization name changed: %s -> %s", org.Name, f.Name)
|
||||
}
|
||||
// In case it's just a case change.
|
||||
org.Name = f.Name
|
||||
org.LowerName = strings.ToLower(f.Name)
|
||||
|
||||
if c.User.IsAdmin {
|
||||
org.MaxRepoCreation = f.MaxRepoCreation
|
||||
}
|
||||
|
||||
org.FullName = f.FullName
|
||||
org.Description = f.Description
|
||||
org.Website = f.Website
|
||||
org.Location = f.Location
|
||||
if err := db.UpdateUser(org); err != nil {
|
||||
c.Handle(500, "UpdateUser", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Organization setting updated: %s", org.Name)
|
||||
c.Flash.Success(c.Tr("org.settings.update_setting_success"))
|
||||
c.Redirect(c.Org.OrgLink + "/settings")
|
||||
}
|
||||
|
||||
func SettingsAvatar(c *context.Context, f form.Avatar) {
|
||||
f.Source = form.AVATAR_LOCAL
|
||||
if err := user2.UpdateAvatarSetting(c, f, c.Org.Organization); err != nil {
|
||||
c.Flash.Error(err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("org.settings.update_avatar_success"))
|
||||
}
|
||||
|
||||
c.Redirect(c.Org.OrgLink + "/settings")
|
||||
}
|
||||
|
||||
func SettingsDeleteAvatar(c *context.Context) {
|
||||
if err := c.Org.Organization.DeleteAvatar(); err != nil {
|
||||
c.Flash.Error(err.Error())
|
||||
}
|
||||
|
||||
c.Redirect(c.Org.OrgLink + "/settings")
|
||||
}
|
||||
|
||||
func SettingsDelete(c *context.Context) {
|
||||
c.Title("org.settings")
|
||||
c.PageIs("SettingsDelete")
|
||||
|
||||
org := c.Org.Organization
|
||||
if c.Req.Method == "POST" {
|
||||
if _, err := db.UserLogin(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil)
|
||||
} else {
|
||||
c.ServerError("UserLogin", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DeleteOrganization(org); err != nil {
|
||||
if db.IsErrUserOwnRepos(err) {
|
||||
c.Flash.Error(c.Tr("form.org_still_own_repo"))
|
||||
c.Redirect(c.Org.OrgLink + "/settings/delete")
|
||||
} else {
|
||||
c.ServerError("DeleteOrganization", err)
|
||||
}
|
||||
} else {
|
||||
log.Trace("Organization deleted: %s", org.Name)
|
||||
c.Redirect(setting.AppSubURL + "/")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.Success(SETTINGS_DELETE)
|
||||
}
|
||||
|
||||
func Webhooks(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("org.settings")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["BaseLink"] = c.Org.OrgLink
|
||||
c.Data["Description"] = c.Tr("org.settings.hooks_desc")
|
||||
c.Data["Types"] = setting.Webhook.Types
|
||||
|
||||
ws, err := db.GetWebhooksByOrgID(c.Org.Organization.ID)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetWebhooksByOrgId", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["Webhooks"] = ws
|
||||
c.HTML(200, SETTINGS_WEBHOOKS)
|
||||
}
|
||||
|
||||
func DeleteWebhook(c *context.Context) {
|
||||
if err := db.DeleteWebhookOfOrgByID(c.Org.Organization.ID, c.QueryInt64("id")); err != nil {
|
||||
c.Flash.Error("DeleteWebhookByOrgID: " + err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success"))
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": c.Org.OrgLink + "/settings/hooks",
|
||||
})
|
||||
}
|
||||
271
internal/route/org/teams.go
Normal file
271
internal/route/org/teams.go
Normal file
@@ -0,0 +1,271 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
)
|
||||
|
||||
const (
|
||||
TEAMS = "org/team/teams"
|
||||
TEAM_NEW = "org/team/new"
|
||||
TEAM_MEMBERS = "org/team/members"
|
||||
TEAM_REPOSITORIES = "org/team/repositories"
|
||||
)
|
||||
|
||||
func Teams(c *context.Context) {
|
||||
org := c.Org.Organization
|
||||
c.Data["Title"] = org.FullName
|
||||
c.Data["PageIsOrgTeams"] = true
|
||||
|
||||
for _, t := range org.Teams {
|
||||
if err := t.GetMembers(); err != nil {
|
||||
c.Handle(500, "GetMembers", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["Teams"] = org.Teams
|
||||
|
||||
c.HTML(200, TEAMS)
|
||||
}
|
||||
|
||||
func TeamsAction(c *context.Context) {
|
||||
uid := com.StrTo(c.Query("uid")).MustInt64()
|
||||
if uid == 0 {
|
||||
c.Redirect(c.Org.OrgLink + "/teams")
|
||||
return
|
||||
}
|
||||
|
||||
page := c.Query("page")
|
||||
var err error
|
||||
switch c.Params(":action") {
|
||||
case "join":
|
||||
if !c.Org.IsOwner {
|
||||
c.Error(404)
|
||||
return
|
||||
}
|
||||
err = c.Org.Team.AddMember(c.User.ID)
|
||||
case "leave":
|
||||
err = c.Org.Team.RemoveMember(c.User.ID)
|
||||
case "remove":
|
||||
if !c.Org.IsOwner {
|
||||
c.Error(404)
|
||||
return
|
||||
}
|
||||
err = c.Org.Team.RemoveMember(uid)
|
||||
page = "team"
|
||||
case "add":
|
||||
if !c.Org.IsOwner {
|
||||
c.Error(404)
|
||||
return
|
||||
}
|
||||
uname := c.Query("uname")
|
||||
var u *db.User
|
||||
u, err = db.GetUserByName(uname)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Flash.Error(c.Tr("form.user_not_exist"))
|
||||
c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName)
|
||||
} else {
|
||||
c.Handle(500, " GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = c.Org.Team.AddMember(u.ID)
|
||||
page = "team"
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if db.IsErrLastOrgOwner(err) {
|
||||
c.Flash.Error(c.Tr("form.last_org_owner"))
|
||||
} else {
|
||||
log.Error(3, "Action(%s): %v", c.Params(":action"), err)
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"ok": false,
|
||||
"err": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch page {
|
||||
case "team":
|
||||
c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName)
|
||||
default:
|
||||
c.Redirect(c.Org.OrgLink + "/teams")
|
||||
}
|
||||
}
|
||||
|
||||
func TeamsRepoAction(c *context.Context) {
|
||||
if !c.Org.IsOwner {
|
||||
c.Error(404)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
switch c.Params(":action") {
|
||||
case "add":
|
||||
repoName := path.Base(c.Query("repo_name"))
|
||||
var repo *db.Repository
|
||||
repo, err = db.GetRepositoryByName(c.Org.Organization.ID, repoName)
|
||||
if err != nil {
|
||||
if errors.IsRepoNotExist(err) {
|
||||
c.Flash.Error(c.Tr("org.teams.add_nonexistent_repo"))
|
||||
c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName + "/repositories")
|
||||
return
|
||||
}
|
||||
c.Handle(500, "GetRepositoryByName", err)
|
||||
return
|
||||
}
|
||||
err = c.Org.Team.AddRepository(repo)
|
||||
case "remove":
|
||||
err = c.Org.Team.RemoveRepository(com.StrTo(c.Query("repoid")).MustInt64())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error(3, "Action(%s): '%s' %v", c.Params(":action"), c.Org.Team.Name, err)
|
||||
c.Handle(500, "TeamsRepoAction", err)
|
||||
return
|
||||
}
|
||||
c.Redirect(c.Org.OrgLink + "/teams/" + c.Org.Team.LowerName + "/repositories")
|
||||
}
|
||||
|
||||
func NewTeam(c *context.Context) {
|
||||
c.Data["Title"] = c.Org.Organization.FullName
|
||||
c.Data["PageIsOrgTeams"] = true
|
||||
c.Data["PageIsOrgTeamsNew"] = true
|
||||
c.Data["Team"] = &db.Team{}
|
||||
c.HTML(200, TEAM_NEW)
|
||||
}
|
||||
|
||||
func NewTeamPost(c *context.Context, f form.CreateTeam) {
|
||||
c.Data["Title"] = c.Org.Organization.FullName
|
||||
c.Data["PageIsOrgTeams"] = true
|
||||
c.Data["PageIsOrgTeamsNew"] = true
|
||||
|
||||
t := &db.Team{
|
||||
OrgID: c.Org.Organization.ID,
|
||||
Name: f.TeamName,
|
||||
Description: f.Description,
|
||||
Authorize: db.ParseAccessMode(f.Permission),
|
||||
}
|
||||
c.Data["Team"] = t
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, TEAM_NEW)
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.NewTeam(t); err != nil {
|
||||
c.Data["Err_TeamName"] = true
|
||||
switch {
|
||||
case db.IsErrTeamAlreadyExist(err):
|
||||
c.RenderWithErr(c.Tr("form.team_name_been_taken"), TEAM_NEW, &f)
|
||||
case db.IsErrNameReserved(err):
|
||||
c.RenderWithErr(c.Tr("org.form.team_name_reserved", err.(db.ErrNameReserved).Name), TEAM_NEW, &f)
|
||||
default:
|
||||
c.Handle(500, "NewTeam", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Team created: %s/%s", c.Org.Organization.Name, t.Name)
|
||||
c.Redirect(c.Org.OrgLink + "/teams/" + t.LowerName)
|
||||
}
|
||||
|
||||
func TeamMembers(c *context.Context) {
|
||||
c.Data["Title"] = c.Org.Team.Name
|
||||
c.Data["PageIsOrgTeams"] = true
|
||||
if err := c.Org.Team.GetMembers(); err != nil {
|
||||
c.Handle(500, "GetMembers", err)
|
||||
return
|
||||
}
|
||||
c.HTML(200, TEAM_MEMBERS)
|
||||
}
|
||||
|
||||
func TeamRepositories(c *context.Context) {
|
||||
c.Data["Title"] = c.Org.Team.Name
|
||||
c.Data["PageIsOrgTeams"] = true
|
||||
if err := c.Org.Team.GetRepositories(); err != nil {
|
||||
c.Handle(500, "GetRepositories", err)
|
||||
return
|
||||
}
|
||||
c.HTML(200, TEAM_REPOSITORIES)
|
||||
}
|
||||
|
||||
func EditTeam(c *context.Context) {
|
||||
c.Data["Title"] = c.Org.Organization.FullName
|
||||
c.Data["PageIsOrgTeams"] = true
|
||||
c.Data["team_name"] = c.Org.Team.Name
|
||||
c.Data["desc"] = c.Org.Team.Description
|
||||
c.HTML(200, TEAM_NEW)
|
||||
}
|
||||
|
||||
func EditTeamPost(c *context.Context, f form.CreateTeam) {
|
||||
t := c.Org.Team
|
||||
c.Data["Title"] = c.Org.Organization.FullName
|
||||
c.Data["PageIsOrgTeams"] = true
|
||||
c.Data["Team"] = t
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, TEAM_NEW)
|
||||
return
|
||||
}
|
||||
|
||||
isAuthChanged := false
|
||||
if !t.IsOwnerTeam() {
|
||||
// Validate permission level.
|
||||
var auth db.AccessMode
|
||||
switch f.Permission {
|
||||
case "read":
|
||||
auth = db.ACCESS_MODE_READ
|
||||
case "write":
|
||||
auth = db.ACCESS_MODE_WRITE
|
||||
case "admin":
|
||||
auth = db.ACCESS_MODE_ADMIN
|
||||
default:
|
||||
c.Error(401)
|
||||
return
|
||||
}
|
||||
|
||||
t.Name = f.TeamName
|
||||
if t.Authorize != auth {
|
||||
isAuthChanged = true
|
||||
t.Authorize = auth
|
||||
}
|
||||
}
|
||||
t.Description = f.Description
|
||||
if err := db.UpdateTeam(t, isAuthChanged); err != nil {
|
||||
c.Data["Err_TeamName"] = true
|
||||
switch {
|
||||
case db.IsErrTeamAlreadyExist(err):
|
||||
c.RenderWithErr(c.Tr("form.team_name_been_taken"), TEAM_NEW, &f)
|
||||
default:
|
||||
c.Handle(500, "UpdateTeam", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.Redirect(c.Org.OrgLink + "/teams/" + t.LowerName)
|
||||
}
|
||||
|
||||
func DeleteTeam(c *context.Context) {
|
||||
if err := db.DeleteTeam(c.Org.Team); err != nil {
|
||||
c.Flash.Error("DeleteTeam: " + err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("org.teams.delete_team_success"))
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": c.Org.OrgLink + "/teams",
|
||||
})
|
||||
}
|
||||
155
internal/route/repo/branch.go
Normal file
155
internal/route/repo/branch.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
BRANCHES_OVERVIEW = "repo/branches/overview"
|
||||
BRANCHES_ALL = "repo/branches/all"
|
||||
)
|
||||
|
||||
type Branch struct {
|
||||
Name string
|
||||
Commit *git.Commit
|
||||
IsProtected bool
|
||||
}
|
||||
|
||||
func loadBranches(c *context.Context) []*Branch {
|
||||
rawBranches, err := c.Repo.Repository.GetBranches()
|
||||
if err != nil {
|
||||
c.Handle(500, "GetBranches", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
protectBranches, err := db.GetProtectBranchesByRepoID(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetProtectBranchesByRepoID", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
branches := make([]*Branch, len(rawBranches))
|
||||
for i := range rawBranches {
|
||||
commit, err := rawBranches[i].GetCommit()
|
||||
if err != nil {
|
||||
c.Handle(500, "GetCommit", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
branches[i] = &Branch{
|
||||
Name: rawBranches[i].Name,
|
||||
Commit: commit,
|
||||
}
|
||||
|
||||
for j := range protectBranches {
|
||||
if branches[i].Name == protectBranches[j].Name {
|
||||
branches[i].IsProtected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["AllowPullRequest"] = c.Repo.Repository.AllowsPulls()
|
||||
return branches
|
||||
}
|
||||
|
||||
func Branches(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.git_branches")
|
||||
c.Data["PageIsBranchesOverview"] = true
|
||||
|
||||
branches := loadBranches(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
activeBranches := make([]*Branch, 0, 3)
|
||||
staleBranches := make([]*Branch, 0, 3)
|
||||
for i := range branches {
|
||||
switch {
|
||||
case branches[i].Name == c.Repo.BranchName:
|
||||
c.Data["DefaultBranch"] = branches[i]
|
||||
case branches[i].Commit.Committer.When.Add(30 * 24 * time.Hour).After(now): // 30 days
|
||||
activeBranches = append(activeBranches, branches[i])
|
||||
case branches[i].Commit.Committer.When.Add(3 * 30 * 24 * time.Hour).Before(now): // 90 days
|
||||
staleBranches = append(staleBranches, branches[i])
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["ActiveBranches"] = activeBranches
|
||||
c.Data["StaleBranches"] = staleBranches
|
||||
c.HTML(200, BRANCHES_OVERVIEW)
|
||||
}
|
||||
|
||||
func AllBranches(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.git_branches")
|
||||
c.Data["PageIsBranchesAll"] = true
|
||||
|
||||
branches := loadBranches(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["Branches"] = branches
|
||||
|
||||
c.HTML(200, BRANCHES_ALL)
|
||||
}
|
||||
|
||||
func DeleteBranchPost(c *context.Context) {
|
||||
branchName := c.Params("*")
|
||||
commitID := c.Query("commit")
|
||||
|
||||
defer func() {
|
||||
redirectTo := c.Query("redirect_to")
|
||||
if !tool.IsSameSiteURLPath(redirectTo) {
|
||||
redirectTo = c.Repo.RepoLink
|
||||
}
|
||||
c.Redirect(redirectTo)
|
||||
}()
|
||||
|
||||
if !c.Repo.GitRepo.IsBranchExist(branchName) {
|
||||
return
|
||||
}
|
||||
if len(commitID) > 0 {
|
||||
branchCommitID, err := c.Repo.GitRepo.GetBranchCommitID(branchName)
|
||||
if err != nil {
|
||||
log.Error(2, "Failed to get commit ID of branch %q: %v", branchName, err)
|
||||
return
|
||||
}
|
||||
|
||||
if branchCommitID != commitID {
|
||||
c.Flash.Error(c.Tr("repo.pulls.delete_branch_has_new_commits"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.Repo.GitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
||||
Force: true,
|
||||
}); err != nil {
|
||||
log.Error(2, "Failed to delete branch %q: %v", branchName, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.PrepareWebhooks(c.Repo.Repository, db.HOOK_EVENT_DELETE, &api.DeletePayload{
|
||||
Ref: branchName,
|
||||
RefType: "branch",
|
||||
PusherType: api.PUSHER_TYPE_USER,
|
||||
Repo: c.Repo.Repository.APIFormat(nil),
|
||||
Sender: c.User.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error(2, "Failed to prepare webhooks for %q: %v", db.HOOK_EVENT_DELETE, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
236
internal/route/repo/commit.go
Normal file
236
internal/route/repo/commit.go
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"path"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
COMMITS = "repo/commits"
|
||||
DIFF = "repo/diff/page"
|
||||
)
|
||||
|
||||
func RefCommits(c *context.Context) {
|
||||
c.Data["PageIsViewFiles"] = true
|
||||
switch {
|
||||
case len(c.Repo.TreePath) == 0:
|
||||
Commits(c)
|
||||
case c.Repo.TreePath == "search":
|
||||
SearchCommits(c)
|
||||
default:
|
||||
FileHistory(c)
|
||||
}
|
||||
}
|
||||
|
||||
func RenderIssueLinks(oldCommits *list.List, repoLink string) *list.List {
|
||||
newCommits := list.New()
|
||||
for e := oldCommits.Front(); e != nil; e = e.Next() {
|
||||
c := e.Value.(*git.Commit)
|
||||
newCommits.PushBack(c)
|
||||
}
|
||||
return newCommits
|
||||
}
|
||||
|
||||
func renderCommits(c *context.Context, filename string) {
|
||||
c.Data["Title"] = c.Tr("repo.commits.commit_history") + " · " + c.Repo.Repository.FullName()
|
||||
c.Data["PageIsCommits"] = true
|
||||
c.Data["FileName"] = filename
|
||||
|
||||
page := c.QueryInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize := c.QueryInt("pageSize")
|
||||
if pageSize < 1 {
|
||||
pageSize = git.DefaultCommitsPageSize
|
||||
}
|
||||
|
||||
// Both 'git log branchName' and 'git log commitID' work.
|
||||
var err error
|
||||
var commits *list.List
|
||||
if len(filename) == 0 {
|
||||
commits, err = c.Repo.Commit.CommitsByRangeSize(page, pageSize)
|
||||
} else {
|
||||
commits, err = c.Repo.GitRepo.CommitsByFileAndRangeSize(c.Repo.BranchName, filename, page, pageSize)
|
||||
}
|
||||
if err != nil {
|
||||
c.Handle(500, "CommitsByRangeSize/CommitsByFileAndRangeSize", err)
|
||||
return
|
||||
}
|
||||
commits = RenderIssueLinks(commits, c.Repo.RepoLink)
|
||||
commits = db.ValidateCommitsWithEmails(commits)
|
||||
c.Data["Commits"] = commits
|
||||
|
||||
if page > 1 {
|
||||
c.Data["HasPrevious"] = true
|
||||
c.Data["PreviousPage"] = page - 1
|
||||
}
|
||||
if commits.Len() == pageSize {
|
||||
c.Data["HasNext"] = true
|
||||
c.Data["NextPage"] = page + 1
|
||||
}
|
||||
c.Data["PageSize"] = pageSize
|
||||
|
||||
c.Data["Username"] = c.Repo.Owner.Name
|
||||
c.Data["Reponame"] = c.Repo.Repository.Name
|
||||
c.HTML(200, COMMITS)
|
||||
}
|
||||
|
||||
func Commits(c *context.Context) {
|
||||
renderCommits(c, "")
|
||||
}
|
||||
|
||||
func SearchCommits(c *context.Context) {
|
||||
c.Data["PageIsCommits"] = true
|
||||
|
||||
keyword := c.Query("q")
|
||||
if len(keyword) == 0 {
|
||||
c.Redirect(c.Repo.RepoLink + "/commits/" + c.Repo.BranchName)
|
||||
return
|
||||
}
|
||||
|
||||
commits, err := c.Repo.Commit.SearchCommits(keyword)
|
||||
if err != nil {
|
||||
c.Handle(500, "SearchCommits", err)
|
||||
return
|
||||
}
|
||||
commits = RenderIssueLinks(commits, c.Repo.RepoLink)
|
||||
commits = db.ValidateCommitsWithEmails(commits)
|
||||
c.Data["Commits"] = commits
|
||||
|
||||
c.Data["Keyword"] = keyword
|
||||
c.Data["Username"] = c.Repo.Owner.Name
|
||||
c.Data["Reponame"] = c.Repo.Repository.Name
|
||||
c.Data["Branch"] = c.Repo.BranchName
|
||||
c.HTML(200, COMMITS)
|
||||
}
|
||||
|
||||
func FileHistory(c *context.Context) {
|
||||
renderCommits(c, c.Repo.TreePath)
|
||||
}
|
||||
|
||||
func Diff(c *context.Context) {
|
||||
c.PageIs("Diff")
|
||||
c.RequireHighlightJS()
|
||||
|
||||
userName := c.Repo.Owner.Name
|
||||
repoName := c.Repo.Repository.Name
|
||||
commitID := c.Params(":sha")
|
||||
|
||||
commit, err := c.Repo.GitRepo.GetCommit(commitID)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("get commit by ID", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
diff, err := db.GetDiffCommit(db.RepoPath(userName, repoName),
|
||||
commitID, setting.Git.MaxGitDiffLines,
|
||||
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("get diff commit", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
parents := make([]string, commit.ParentCount())
|
||||
for i := 0; i < commit.ParentCount(); i++ {
|
||||
sha, err := commit.ParentID(i)
|
||||
parents[i] = sha.String()
|
||||
if err != nil {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setEditorconfigIfExists(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Title(commit.Summary() + " · " + tool.ShortSHA1(commitID))
|
||||
c.Data["CommitID"] = commitID
|
||||
c.Data["IsSplitStyle"] = c.Query("style") == "split"
|
||||
c.Data["Username"] = userName
|
||||
c.Data["Reponame"] = repoName
|
||||
c.Data["IsImageFile"] = commit.IsImageFile
|
||||
c.Data["Commit"] = commit
|
||||
c.Data["Author"] = db.ValidateCommitWithEmail(commit)
|
||||
c.Data["Diff"] = diff
|
||||
c.Data["Parents"] = parents
|
||||
c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
|
||||
c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", commitID)
|
||||
if commit.ParentCount() > 0 {
|
||||
c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", parents[0])
|
||||
}
|
||||
c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", commitID)
|
||||
c.Success(DIFF)
|
||||
}
|
||||
|
||||
func RawDiff(c *context.Context) {
|
||||
if err := git.GetRawDiff(
|
||||
db.RepoPath(c.Repo.Owner.Name, c.Repo.Repository.Name),
|
||||
c.Params(":sha"),
|
||||
git.RawDiffType(c.Params(":ext")),
|
||||
c.Resp,
|
||||
); err != nil {
|
||||
c.NotFoundOrServerError("GetRawDiff", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func CompareDiff(c *context.Context) {
|
||||
c.Data["IsDiffCompare"] = true
|
||||
userName := c.Repo.Owner.Name
|
||||
repoName := c.Repo.Repository.Name
|
||||
beforeCommitID := c.Params(":before")
|
||||
afterCommitID := c.Params(":after")
|
||||
|
||||
commit, err := c.Repo.GitRepo.GetCommit(afterCommitID)
|
||||
if err != nil {
|
||||
c.Handle(404, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
diff, err := db.GetDiffRange(db.RepoPath(userName, repoName), beforeCommitID,
|
||||
afterCommitID, setting.Git.MaxGitDiffLines,
|
||||
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
|
||||
if err != nil {
|
||||
c.Handle(404, "GetDiffRange", err)
|
||||
return
|
||||
}
|
||||
|
||||
commits, err := commit.CommitsBeforeUntil(beforeCommitID)
|
||||
if err != nil {
|
||||
c.Handle(500, "CommitsBeforeUntil", err)
|
||||
return
|
||||
}
|
||||
commits = db.ValidateCommitsWithEmails(commits)
|
||||
|
||||
c.Data["IsSplitStyle"] = c.Query("style") == "split"
|
||||
c.Data["CommitRepoLink"] = c.Repo.RepoLink
|
||||
c.Data["Commits"] = commits
|
||||
c.Data["CommitsCount"] = commits.Len()
|
||||
c.Data["BeforeCommitID"] = beforeCommitID
|
||||
c.Data["AfterCommitID"] = afterCommitID
|
||||
c.Data["Username"] = userName
|
||||
c.Data["Reponame"] = repoName
|
||||
c.Data["IsImageFile"] = commit.IsImageFile
|
||||
c.Data["Title"] = "Comparing " + tool.ShortSHA1(beforeCommitID) + "..." + tool.ShortSHA1(afterCommitID) + " · " + userName + "/" + repoName
|
||||
c.Data["Commit"] = commit
|
||||
c.Data["Diff"] = diff
|
||||
c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
|
||||
c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", afterCommitID)
|
||||
c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "src", beforeCommitID)
|
||||
c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(userName, repoName, "raw", afterCommitID)
|
||||
c.HTML(200, DIFF)
|
||||
}
|
||||
68
internal/route/repo/download.go
Normal file
68
internal/route/repo/download.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
func ServeData(c *context.Context, name string, reader io.Reader) error {
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := reader.Read(buf)
|
||||
if n >= 0 {
|
||||
buf = buf[:n]
|
||||
}
|
||||
|
||||
commit, err := c.Repo.Commit.GetCommitByPath(c.Repo.TreePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCommitByPath: %v", err)
|
||||
}
|
||||
c.Resp.Header().Set("Last-Modified", commit.Committer.When.Format(http.TimeFormat))
|
||||
|
||||
if !tool.IsTextFile(buf) {
|
||||
if !tool.IsImageFile(buf) {
|
||||
c.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"")
|
||||
c.Resp.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
}
|
||||
} else if !setting.Repository.EnableRawFileRenderMode || !c.QueryBool("render") {
|
||||
c.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
}
|
||||
c.Resp.Write(buf)
|
||||
_, err = io.Copy(c.Resp, reader)
|
||||
return err
|
||||
}
|
||||
|
||||
func ServeBlob(c *context.Context, blob *git.Blob) error {
|
||||
dataRc, err := blob.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ServeData(c, path.Base(c.Repo.TreePath), dataRc)
|
||||
}
|
||||
|
||||
func SingleDownload(c *context.Context) {
|
||||
blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
c.Handle(404, "GetBlobByPath", nil)
|
||||
} else {
|
||||
c.Handle(500, "GetBlobByPath", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = ServeBlob(c, blob); err != nil {
|
||||
c.Handle(500, "ServeBlob", err)
|
||||
}
|
||||
}
|
||||
574
internal/route/repo/editor.go
Normal file
574
internal/route/repo/editor.go
Normal file
@@ -0,0 +1,574 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/template"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
EDIT_FILE = "repo/editor/edit"
|
||||
EDIT_DIFF_PREVIEW = "repo/editor/diff_preview"
|
||||
DELETE_FILE = "repo/editor/delete"
|
||||
UPLOAD_FILE = "repo/editor/upload"
|
||||
)
|
||||
|
||||
// getParentTreeFields returns list of parent tree names and corresponding tree paths
|
||||
// based on given tree path.
|
||||
func getParentTreeFields(treePath string) (treeNames []string, treePaths []string) {
|
||||
if len(treePath) == 0 {
|
||||
return treeNames, treePaths
|
||||
}
|
||||
|
||||
treeNames = strings.Split(treePath, "/")
|
||||
treePaths = make([]string, len(treeNames))
|
||||
for i := range treeNames {
|
||||
treePaths[i] = strings.Join(treeNames[:i+1], "/")
|
||||
}
|
||||
return treeNames, treePaths
|
||||
}
|
||||
|
||||
func editFile(c *context.Context, isNewFile bool) {
|
||||
c.PageIs("Edit")
|
||||
c.RequireHighlightJS()
|
||||
c.RequireSimpleMDE()
|
||||
c.Data["IsNewFile"] = isNewFile
|
||||
|
||||
treeNames, treePaths := getParentTreeFields(c.Repo.TreePath)
|
||||
|
||||
if !isNewFile {
|
||||
entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
// No way to edit a directory online.
|
||||
if entry.IsDir() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
blob := entry.Blob()
|
||||
dataRc, err := blob.Data()
|
||||
if err != nil {
|
||||
c.ServerError("blob.Data", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["FileSize"] = blob.Size()
|
||||
c.Data["FileName"] = blob.Name()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := dataRc.Read(buf)
|
||||
buf = buf[:n]
|
||||
|
||||
// Only text file are editable online.
|
||||
if !tool.IsTextFile(buf) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
d, _ := ioutil.ReadAll(dataRc)
|
||||
buf = append(buf, d...)
|
||||
if err, content := template.ToUTF8WithErr(buf); err != nil {
|
||||
if err != nil {
|
||||
log.Error(2, "Failed to convert encoding to UTF-8: %v", err)
|
||||
}
|
||||
c.Data["FileContent"] = string(buf)
|
||||
} else {
|
||||
c.Data["FileContent"] = content
|
||||
}
|
||||
} else {
|
||||
treeNames = append(treeNames, "") // Append empty string to allow user name the new file.
|
||||
}
|
||||
|
||||
c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath)
|
||||
c.Data["TreeNames"] = treeNames
|
||||
c.Data["TreePaths"] = treePaths
|
||||
c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName
|
||||
c.Data["commit_summary"] = ""
|
||||
c.Data["commit_message"] = ""
|
||||
c.Data["commit_choice"] = "direct"
|
||||
c.Data["new_branch_name"] = ""
|
||||
c.Data["last_commit"] = c.Repo.Commit.ID
|
||||
c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
|
||||
c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||
c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
|
||||
c.Data["EditorconfigURLPrefix"] = fmt.Sprintf("%s/api/v1/repos/%s/editorconfig/", setting.AppSubURL, c.Repo.Repository.FullName())
|
||||
|
||||
c.Success(EDIT_FILE)
|
||||
}
|
||||
|
||||
func EditFile(c *context.Context) {
|
||||
editFile(c, false)
|
||||
}
|
||||
|
||||
func NewFile(c *context.Context) {
|
||||
editFile(c, true)
|
||||
}
|
||||
|
||||
func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
|
||||
c.PageIs("Edit")
|
||||
c.RequireHighlightJS()
|
||||
c.RequireSimpleMDE()
|
||||
c.Data["IsNewFile"] = isNewFile
|
||||
|
||||
oldBranchName := c.Repo.BranchName
|
||||
branchName := oldBranchName
|
||||
oldTreePath := c.Repo.TreePath
|
||||
lastCommit := f.LastCommit
|
||||
f.LastCommit = c.Repo.Commit.ID.String()
|
||||
|
||||
if f.IsNewBrnach() {
|
||||
branchName = f.NewBranchName
|
||||
}
|
||||
|
||||
f.TreePath = strings.Trim(path.Clean("/"+f.TreePath), " /")
|
||||
treeNames, treePaths := getParentTreeFields(f.TreePath)
|
||||
|
||||
c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath)
|
||||
c.Data["TreePath"] = f.TreePath
|
||||
c.Data["TreeNames"] = treeNames
|
||||
c.Data["TreePaths"] = treePaths
|
||||
c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName
|
||||
c.Data["FileContent"] = f.Content
|
||||
c.Data["commit_summary"] = f.CommitSummary
|
||||
c.Data["commit_message"] = f.CommitMessage
|
||||
c.Data["commit_choice"] = f.CommitChoice
|
||||
c.Data["new_branch_name"] = branchName
|
||||
c.Data["last_commit"] = f.LastCommit
|
||||
c.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
|
||||
c.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||
c.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(EDIT_FILE)
|
||||
return
|
||||
}
|
||||
|
||||
if len(f.TreePath) == 0 {
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.filename_cannot_be_empty"), EDIT_FILE, &f)
|
||||
return
|
||||
}
|
||||
|
||||
if oldBranchName != branchName {
|
||||
if _, err := c.Repo.Repository.GetBranch(branchName); err == nil {
|
||||
c.FormErr("NewBranchName")
|
||||
c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), EDIT_FILE, &f)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var newTreePath string
|
||||
for index, part := range treeNames {
|
||||
newTreePath = path.Join(newTreePath, part)
|
||||
entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
// Means there is no item with that name, so we're good
|
||||
break
|
||||
}
|
||||
|
||||
c.ServerError("Repo.Commit.GetTreeEntryByPath", err)
|
||||
return
|
||||
}
|
||||
if index != len(treeNames)-1 {
|
||||
if !entry.IsDir() {
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), EDIT_FILE, &f)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if entry.IsLink() {
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", part), EDIT_FILE, &f)
|
||||
return
|
||||
} else if entry.IsDir() {
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.filename_is_a_directory", part), EDIT_FILE, &f)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isNewFile {
|
||||
_, err := c.Repo.Commit.GetTreeEntryByPath(oldTreePath)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.file_editing_no_longer_exists", oldTreePath), EDIT_FILE, &f)
|
||||
} else {
|
||||
c.ServerError("GetTreeEntryByPath", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if lastCommit != c.Repo.CommitID {
|
||||
files, err := c.Repo.Commit.GetFilesChangedSinceCommit(lastCommit)
|
||||
if err != nil {
|
||||
c.ServerError("GetFilesChangedSinceCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file == f.TreePath {
|
||||
c.RenderWithErr(c.Tr("repo.editor.file_changed_while_editing", c.Repo.RepoLink+"/compare/"+lastCommit+"..."+c.Repo.CommitID), EDIT_FILE, &f)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if oldTreePath != f.TreePath {
|
||||
// We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber.
|
||||
entry, err := c.Repo.Commit.GetTreeEntryByPath(f.TreePath)
|
||||
if err != nil {
|
||||
if !git.IsErrNotExist(err) {
|
||||
c.ServerError("GetTreeEntryByPath", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if entry != nil {
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.file_already_exists", f.TreePath), EDIT_FILE, &f)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
message := strings.TrimSpace(f.CommitSummary)
|
||||
if len(message) == 0 {
|
||||
if isNewFile {
|
||||
message = c.Tr("repo.editor.add", f.TreePath)
|
||||
} else {
|
||||
message = c.Tr("repo.editor.update", f.TreePath)
|
||||
}
|
||||
}
|
||||
|
||||
f.CommitMessage = strings.TrimSpace(f.CommitMessage)
|
||||
if len(f.CommitMessage) > 0 {
|
||||
message += "\n\n" + f.CommitMessage
|
||||
}
|
||||
|
||||
if err := c.Repo.Repository.UpdateRepoFile(c.User, db.UpdateRepoFileOptions{
|
||||
LastCommitID: lastCommit,
|
||||
OldBranch: oldBranchName,
|
||||
NewBranch: branchName,
|
||||
OldTreeName: oldTreePath,
|
||||
NewTreeName: f.TreePath,
|
||||
Message: message,
|
||||
Content: strings.Replace(f.Content, "\r", "", -1),
|
||||
IsNewFile: isNewFile,
|
||||
}); err != nil {
|
||||
log.Error(2, "Failed to update repo file: %v", err)
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.fail_to_update_file", f.TreePath, errors.InternalServerError), EDIT_FILE, &f)
|
||||
return
|
||||
}
|
||||
|
||||
if f.IsNewBrnach() && c.Repo.PullRequest.Allowed {
|
||||
c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName))
|
||||
} else {
|
||||
c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + f.TreePath)
|
||||
}
|
||||
}
|
||||
|
||||
func EditFilePost(c *context.Context, f form.EditRepoFile) {
|
||||
editFilePost(c, f, false)
|
||||
}
|
||||
|
||||
func NewFilePost(c *context.Context, f form.EditRepoFile) {
|
||||
editFilePost(c, f, true)
|
||||
}
|
||||
|
||||
func DiffPreviewPost(c *context.Context, f form.EditPreviewDiff) {
|
||||
treePath := c.Repo.TreePath
|
||||
|
||||
entry, err := c.Repo.Commit.GetTreeEntryByPath(treePath)
|
||||
if err != nil {
|
||||
c.Error(500, "GetTreeEntryByPath: "+err.Error())
|
||||
return
|
||||
} else if entry.IsDir() {
|
||||
c.Error(422)
|
||||
return
|
||||
}
|
||||
|
||||
diff, err := c.Repo.Repository.GetDiffPreview(c.Repo.BranchName, treePath, f.Content)
|
||||
if err != nil {
|
||||
c.Error(500, "GetDiffPreview: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if diff.NumFiles() == 0 {
|
||||
c.PlainText(200, []byte(c.Tr("repo.editor.no_changes_to_show")))
|
||||
return
|
||||
}
|
||||
c.Data["File"] = diff.Files[0]
|
||||
|
||||
c.HTML(200, EDIT_DIFF_PREVIEW)
|
||||
}
|
||||
|
||||
func DeleteFile(c *context.Context) {
|
||||
c.PageIs("Delete")
|
||||
c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName
|
||||
c.Data["TreePath"] = c.Repo.TreePath
|
||||
c.Data["commit_summary"] = ""
|
||||
c.Data["commit_message"] = ""
|
||||
c.Data["commit_choice"] = "direct"
|
||||
c.Data["new_branch_name"] = ""
|
||||
c.Success(DELETE_FILE)
|
||||
}
|
||||
|
||||
func DeleteFilePost(c *context.Context, f form.DeleteRepoFile) {
|
||||
c.PageIs("Delete")
|
||||
c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName
|
||||
c.Data["TreePath"] = c.Repo.TreePath
|
||||
|
||||
oldBranchName := c.Repo.BranchName
|
||||
branchName := oldBranchName
|
||||
|
||||
if f.IsNewBrnach() {
|
||||
branchName = f.NewBranchName
|
||||
}
|
||||
c.Data["commit_summary"] = f.CommitSummary
|
||||
c.Data["commit_message"] = f.CommitMessage
|
||||
c.Data["commit_choice"] = f.CommitChoice
|
||||
c.Data["new_branch_name"] = branchName
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(DELETE_FILE)
|
||||
return
|
||||
}
|
||||
|
||||
if oldBranchName != branchName {
|
||||
if _, err := c.Repo.Repository.GetBranch(branchName); err == nil {
|
||||
c.FormErr("NewBranchName")
|
||||
c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), DELETE_FILE, &f)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
message := strings.TrimSpace(f.CommitSummary)
|
||||
if len(message) == 0 {
|
||||
message = c.Tr("repo.editor.delete", c.Repo.TreePath)
|
||||
}
|
||||
|
||||
f.CommitMessage = strings.TrimSpace(f.CommitMessage)
|
||||
if len(f.CommitMessage) > 0 {
|
||||
message += "\n\n" + f.CommitMessage
|
||||
}
|
||||
|
||||
if err := c.Repo.Repository.DeleteRepoFile(c.User, db.DeleteRepoFileOptions{
|
||||
LastCommitID: c.Repo.CommitID,
|
||||
OldBranch: oldBranchName,
|
||||
NewBranch: branchName,
|
||||
TreePath: c.Repo.TreePath,
|
||||
Message: message,
|
||||
}); err != nil {
|
||||
log.Error(2, "Failed to delete repo file: %v", err)
|
||||
c.RenderWithErr(c.Tr("repo.editor.fail_to_delete_file", c.Repo.TreePath, errors.InternalServerError), DELETE_FILE, &f)
|
||||
return
|
||||
}
|
||||
|
||||
if f.IsNewBrnach() && c.Repo.PullRequest.Allowed {
|
||||
c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName))
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("repo.editor.file_delete_success", c.Repo.TreePath))
|
||||
c.Redirect(c.Repo.RepoLink + "/src/" + branchName)
|
||||
}
|
||||
}
|
||||
|
||||
func renderUploadSettings(c *context.Context) {
|
||||
c.RequireDropzone()
|
||||
c.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",")
|
||||
c.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
|
||||
c.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
|
||||
}
|
||||
|
||||
func UploadFile(c *context.Context) {
|
||||
c.PageIs("Upload")
|
||||
renderUploadSettings(c)
|
||||
|
||||
treeNames, treePaths := getParentTreeFields(c.Repo.TreePath)
|
||||
if len(treeNames) == 0 {
|
||||
// We must at least have one element for user to input.
|
||||
treeNames = []string{""}
|
||||
}
|
||||
|
||||
c.Data["TreeNames"] = treeNames
|
||||
c.Data["TreePaths"] = treePaths
|
||||
c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + c.Repo.BranchName
|
||||
c.Data["commit_summary"] = ""
|
||||
c.Data["commit_message"] = ""
|
||||
c.Data["commit_choice"] = "direct"
|
||||
c.Data["new_branch_name"] = ""
|
||||
c.Success(UPLOAD_FILE)
|
||||
}
|
||||
|
||||
func UploadFilePost(c *context.Context, f form.UploadRepoFile) {
|
||||
c.PageIs("Upload")
|
||||
renderUploadSettings(c)
|
||||
|
||||
oldBranchName := c.Repo.BranchName
|
||||
branchName := oldBranchName
|
||||
|
||||
if f.IsNewBrnach() {
|
||||
branchName = f.NewBranchName
|
||||
}
|
||||
|
||||
f.TreePath = strings.Trim(path.Clean("/"+f.TreePath), " /")
|
||||
treeNames, treePaths := getParentTreeFields(f.TreePath)
|
||||
if len(treeNames) == 0 {
|
||||
// We must at least have one element for user to input.
|
||||
treeNames = []string{""}
|
||||
}
|
||||
|
||||
c.Data["TreePath"] = f.TreePath
|
||||
c.Data["TreeNames"] = treeNames
|
||||
c.Data["TreePaths"] = treePaths
|
||||
c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName
|
||||
c.Data["commit_summary"] = f.CommitSummary
|
||||
c.Data["commit_message"] = f.CommitMessage
|
||||
c.Data["commit_choice"] = f.CommitChoice
|
||||
c.Data["new_branch_name"] = branchName
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(UPLOAD_FILE)
|
||||
return
|
||||
}
|
||||
|
||||
if oldBranchName != branchName {
|
||||
if _, err := c.Repo.Repository.GetBranch(branchName); err == nil {
|
||||
c.FormErr("NewBranchName")
|
||||
c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), UPLOAD_FILE, &f)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var newTreePath string
|
||||
for _, part := range treeNames {
|
||||
newTreePath = path.Join(newTreePath, part)
|
||||
entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
// Means there is no item with that name, so we're good
|
||||
break
|
||||
}
|
||||
|
||||
c.ServerError("GetTreeEntryByPath", err)
|
||||
return
|
||||
}
|
||||
|
||||
// User can only upload files to a directory.
|
||||
if !entry.IsDir() {
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), UPLOAD_FILE, &f)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
message := strings.TrimSpace(f.CommitSummary)
|
||||
if len(message) == 0 {
|
||||
message = c.Tr("repo.editor.upload_files_to_dir", f.TreePath)
|
||||
}
|
||||
|
||||
f.CommitMessage = strings.TrimSpace(f.CommitMessage)
|
||||
if len(f.CommitMessage) > 0 {
|
||||
message += "\n\n" + f.CommitMessage
|
||||
}
|
||||
|
||||
if err := c.Repo.Repository.UploadRepoFiles(c.User, db.UploadRepoFileOptions{
|
||||
LastCommitID: c.Repo.CommitID,
|
||||
OldBranch: oldBranchName,
|
||||
NewBranch: branchName,
|
||||
TreePath: f.TreePath,
|
||||
Message: message,
|
||||
Files: f.Files,
|
||||
}); err != nil {
|
||||
log.Error(2, "Failed to upload files: %v", err)
|
||||
c.FormErr("TreePath")
|
||||
c.RenderWithErr(c.Tr("repo.editor.unable_to_upload_files", f.TreePath, errors.InternalServerError), UPLOAD_FILE, &f)
|
||||
return
|
||||
}
|
||||
|
||||
if f.IsNewBrnach() && c.Repo.PullRequest.Allowed {
|
||||
c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName))
|
||||
} else {
|
||||
c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + f.TreePath)
|
||||
}
|
||||
}
|
||||
|
||||
func UploadFileToServer(c *context.Context) {
|
||||
file, header, err := c.Req.FormFile("file")
|
||||
if err != nil {
|
||||
c.Error(http.StatusInternalServerError, fmt.Sprintf("FormFile: %v", err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := file.Read(buf)
|
||||
if n > 0 {
|
||||
buf = buf[:n]
|
||||
}
|
||||
fileType := http.DetectContentType(buf)
|
||||
|
||||
if len(setting.Repository.Upload.AllowedTypes) > 0 {
|
||||
allowed := false
|
||||
for _, t := range setting.Repository.Upload.AllowedTypes {
|
||||
t := strings.Trim(t, " ")
|
||||
if t == "*/*" || t == fileType {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
c.Error(http.StatusBadRequest, ErrFileTypeForbidden.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
upload, err := db.NewUpload(header.Filename, buf, file)
|
||||
if err != nil {
|
||||
c.Error(http.StatusInternalServerError, fmt.Sprintf("NewUpload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("New file uploaded by user[%d]: %s", c.UserID(), upload.UUID)
|
||||
c.JSONSuccess(map[string]string{
|
||||
"uuid": upload.UUID,
|
||||
})
|
||||
}
|
||||
|
||||
func RemoveUploadFileFromServer(c *context.Context, f form.RemoveUploadFile) {
|
||||
if len(f.File) == 0 {
|
||||
c.Status(204)
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DeleteUploadByUUID(f.File); err != nil {
|
||||
c.Error(500, fmt.Sprintf("DeleteUploadByUUID: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Upload file removed: %s", f.File)
|
||||
c.Status(204)
|
||||
}
|
||||
424
internal/route/repo/http.go
Normal file
424
internal/route/repo/http.go
Normal file
@@ -0,0 +1,424 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "gopkg.in/clog.v1"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
type HTTPContext struct {
|
||||
*context.Context
|
||||
OwnerName string
|
||||
OwnerSalt string
|
||||
RepoID int64
|
||||
RepoName string
|
||||
AuthUser *db.User
|
||||
}
|
||||
|
||||
// askCredentials responses HTTP header and status which informs client to provide credentials.
|
||||
func askCredentials(c *context.Context, status int, text string) {
|
||||
c.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
|
||||
c.HandleText(status, text)
|
||||
}
|
||||
|
||||
func HTTPContexter() macaron.Handler {
|
||||
return func(c *context.Context) {
|
||||
if len(setting.HTTP.AccessControlAllowOrigin) > 0 {
|
||||
// Set CORS headers for browser-based git clients
|
||||
c.Resp.Header().Set("Access-Control-Allow-Origin", setting.HTTP.AccessControlAllowOrigin)
|
||||
c.Resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
|
||||
|
||||
// Handle preflight OPTIONS request
|
||||
if c.Req.Method == "OPTIONS" {
|
||||
c.Status(http.StatusOK)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ownerName := c.Params(":username")
|
||||
repoName := strings.TrimSuffix(c.Params(":reponame"), ".git")
|
||||
repoName = strings.TrimSuffix(repoName, ".wiki")
|
||||
|
||||
isPull := c.Query("service") == "git-upload-pack" ||
|
||||
strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") ||
|
||||
c.Req.Method == "GET"
|
||||
|
||||
owner, err := db.GetUserByName(ownerName)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := db.GetRepositoryByName(owner.ID, repoName)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Authentication is not required for pulling from public repositories.
|
||||
if isPull && !repo.IsPrivate && !setting.Service.RequireSignInView {
|
||||
c.Map(&HTTPContext{
|
||||
Context: c,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// In case user requested a wrong URL and not intended to access Git objects.
|
||||
action := c.Params("*")
|
||||
if !strings.Contains(action, "git-") &&
|
||||
!strings.Contains(action, "info/") &&
|
||||
!strings.Contains(action, "HEAD") &&
|
||||
!strings.Contains(action, "objects/") {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
// Handle HTTP Basic Authentication
|
||||
authHead := c.Req.Header.Get("Authorization")
|
||||
if len(authHead) == 0 {
|
||||
askCredentials(c, http.StatusUnauthorized, "")
|
||||
return
|
||||
}
|
||||
|
||||
auths := strings.Fields(authHead)
|
||||
if len(auths) != 2 || auths[0] != "Basic" {
|
||||
askCredentials(c, http.StatusUnauthorized, "")
|
||||
return
|
||||
}
|
||||
authUsername, authPassword, err := tool.BasicAuthDecode(auths[1])
|
||||
if err != nil {
|
||||
askCredentials(c, http.StatusUnauthorized, "")
|
||||
return
|
||||
}
|
||||
|
||||
authUser, err := db.UserLogin(authUsername, authPassword, -1)
|
||||
if err != nil && !errors.IsUserNotExist(err) {
|
||||
c.Handle(http.StatusInternalServerError, "UserLogin", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If username and password combination failed, try again using username as a token.
|
||||
if authUser == nil {
|
||||
token, err := db.GetAccessTokenBySHA(authUsername)
|
||||
if err != nil {
|
||||
if db.IsErrAccessTokenEmpty(err) || db.IsErrAccessTokenNotExist(err) {
|
||||
askCredentials(c, http.StatusUnauthorized, "")
|
||||
} else {
|
||||
c.Handle(http.StatusInternalServerError, "GetAccessTokenBySHA", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
token.Updated = time.Now()
|
||||
// TODO: verify or update token.Updated in database
|
||||
|
||||
authUser, err = db.GetUserByID(token.UID)
|
||||
if err != nil {
|
||||
// Once we found token, we're supposed to find its related user,
|
||||
// thus any error is unexpected.
|
||||
c.Handle(http.StatusInternalServerError, "GetUserByID", err)
|
||||
return
|
||||
}
|
||||
} else if authUser.IsEnabledTwoFactor() {
|
||||
askCredentials(c, http.StatusUnauthorized, `User with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password
|
||||
Please create and use personal access token on user settings page`)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("HTTPGit - Authenticated user: %s", authUser.Name)
|
||||
|
||||
mode := db.ACCESS_MODE_WRITE
|
||||
if isPull {
|
||||
mode = db.ACCESS_MODE_READ
|
||||
}
|
||||
has, err := db.HasAccess(authUser.ID, repo, mode)
|
||||
if err != nil {
|
||||
c.Handle(http.StatusInternalServerError, "HasAccess", err)
|
||||
return
|
||||
} else if !has {
|
||||
askCredentials(c, http.StatusForbidden, "User permission denied")
|
||||
return
|
||||
}
|
||||
|
||||
if !isPull && repo.IsMirror {
|
||||
c.HandleText(http.StatusForbidden, "Mirror repository is read-only")
|
||||
return
|
||||
}
|
||||
|
||||
c.Map(&HTTPContext{
|
||||
Context: c,
|
||||
OwnerName: ownerName,
|
||||
OwnerSalt: owner.Salt,
|
||||
RepoID: repo.ID,
|
||||
RepoName: repoName,
|
||||
AuthUser: authUser,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type serviceHandler struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
dir string
|
||||
file string
|
||||
|
||||
authUser *db.User
|
||||
ownerName string
|
||||
ownerSalt string
|
||||
repoID int64
|
||||
repoName string
|
||||
}
|
||||
|
||||
func (h *serviceHandler) setHeaderNoCache() {
|
||||
h.w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
|
||||
h.w.Header().Set("Pragma", "no-cache")
|
||||
h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
||||
}
|
||||
|
||||
func (h *serviceHandler) setHeaderCacheForever() {
|
||||
now := time.Now().Unix()
|
||||
expires := now + 31536000
|
||||
h.w.Header().Set("Date", fmt.Sprintf("%d", now))
|
||||
h.w.Header().Set("Expires", fmt.Sprintf("%d", expires))
|
||||
h.w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
}
|
||||
|
||||
func (h *serviceHandler) sendFile(contentType string) {
|
||||
reqFile := path.Join(h.dir, h.file)
|
||||
fi, err := os.Stat(reqFile)
|
||||
if os.IsNotExist(err) {
|
||||
h.w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
h.w.Header().Set("Content-Type", contentType)
|
||||
h.w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
||||
h.w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
|
||||
http.ServeFile(h.w, h.r, reqFile)
|
||||
}
|
||||
|
||||
func serviceRPC(h serviceHandler, service string) {
|
||||
defer h.r.Body.Close()
|
||||
|
||||
if h.r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", service) {
|
||||
h.w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
|
||||
|
||||
var (
|
||||
reqBody = h.r.Body
|
||||
err error
|
||||
)
|
||||
|
||||
// Handle GZIP
|
||||
if h.r.Header.Get("Content-Encoding") == "gzip" {
|
||||
reqBody, err = gzip.NewReader(reqBody)
|
||||
if err != nil {
|
||||
log.Error(2, "HTTP.Get: fail to create gzip reader: %v", err)
|
||||
h.w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
|
||||
if service == "receive-pack" {
|
||||
cmd.Env = append(os.Environ(), db.ComposeHookEnvs(db.ComposeHookEnvsOptions{
|
||||
AuthUser: h.authUser,
|
||||
OwnerName: h.ownerName,
|
||||
OwnerSalt: h.ownerSalt,
|
||||
RepoID: h.repoID,
|
||||
RepoName: h.repoName,
|
||||
RepoPath: h.dir,
|
||||
})...)
|
||||
}
|
||||
cmd.Dir = h.dir
|
||||
cmd.Stdout = h.w
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdin = reqBody
|
||||
if err = cmd.Run(); err != nil {
|
||||
log.Error(2, "HTTP.serviceRPC: fail to serve RPC '%s': %v - %s", service, err, stderr.String())
|
||||
h.w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func serviceUploadPack(h serviceHandler) {
|
||||
serviceRPC(h, "upload-pack")
|
||||
}
|
||||
|
||||
func serviceReceivePack(h serviceHandler) {
|
||||
serviceRPC(h, "receive-pack")
|
||||
}
|
||||
|
||||
func getServiceType(r *http.Request) string {
|
||||
serviceType := r.FormValue("service")
|
||||
if !strings.HasPrefix(serviceType, "git-") {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimPrefix(serviceType, "git-")
|
||||
}
|
||||
|
||||
// FIXME: use process module
|
||||
func gitCommand(dir string, args ...string) []byte {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = dir
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Error(2, fmt.Sprintf("Git: %v - %s", err, out))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func updateServerInfo(dir string) []byte {
|
||||
return gitCommand(dir, "update-server-info")
|
||||
}
|
||||
|
||||
func packetWrite(str string) []byte {
|
||||
s := strconv.FormatInt(int64(len(str)+4), 16)
|
||||
if len(s)%4 != 0 {
|
||||
s = strings.Repeat("0", 4-len(s)%4) + s
|
||||
}
|
||||
return []byte(s + str)
|
||||
}
|
||||
|
||||
func getInfoRefs(h serviceHandler) {
|
||||
h.setHeaderNoCache()
|
||||
service := getServiceType(h.r)
|
||||
if service != "upload-pack" && service != "receive-pack" {
|
||||
updateServerInfo(h.dir)
|
||||
h.sendFile("text/plain; charset=utf-8")
|
||||
return
|
||||
}
|
||||
|
||||
refs := gitCommand(h.dir, service, "--stateless-rpc", "--advertise-refs", ".")
|
||||
h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
|
||||
h.w.WriteHeader(http.StatusOK)
|
||||
h.w.Write(packetWrite("# service=git-" + service + "\n"))
|
||||
h.w.Write([]byte("0000"))
|
||||
h.w.Write(refs)
|
||||
}
|
||||
|
||||
func getTextFile(h serviceHandler) {
|
||||
h.setHeaderNoCache()
|
||||
h.sendFile("text/plain")
|
||||
}
|
||||
|
||||
func getInfoPacks(h serviceHandler) {
|
||||
h.setHeaderCacheForever()
|
||||
h.sendFile("text/plain; charset=utf-8")
|
||||
}
|
||||
|
||||
func getLooseObject(h serviceHandler) {
|
||||
h.setHeaderCacheForever()
|
||||
h.sendFile("application/x-git-loose-object")
|
||||
}
|
||||
|
||||
func getPackFile(h serviceHandler) {
|
||||
h.setHeaderCacheForever()
|
||||
h.sendFile("application/x-git-packed-objects")
|
||||
}
|
||||
|
||||
func getIdxFile(h serviceHandler) {
|
||||
h.setHeaderCacheForever()
|
||||
h.sendFile("application/x-git-packed-objects-toc")
|
||||
}
|
||||
|
||||
var routes = []struct {
|
||||
reg *regexp.Regexp
|
||||
method string
|
||||
handler func(serviceHandler)
|
||||
}{
|
||||
{regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
|
||||
{regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
|
||||
{regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
|
||||
{regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
|
||||
{regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
|
||||
{regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
|
||||
{regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
|
||||
{regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
|
||||
{regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
|
||||
{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
|
||||
{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
|
||||
}
|
||||
|
||||
func getGitRepoPath(dir string) (string, error) {
|
||||
if !strings.HasSuffix(dir, ".git") {
|
||||
dir += ".git"
|
||||
}
|
||||
|
||||
filename := path.Join(setting.RepoRootPath, dir)
|
||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func HTTP(c *HTTPContext) {
|
||||
for _, route := range routes {
|
||||
reqPath := strings.ToLower(c.Req.URL.Path)
|
||||
m := route.reg.FindStringSubmatch(reqPath)
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// We perform check here because route matched in cmd/web.go is wider than needed,
|
||||
// but we only want to output this message only if user is really trying to access
|
||||
// Git HTTP endpoints.
|
||||
if setting.Repository.DisableHTTPGit {
|
||||
c.HandleText(http.StatusForbidden, "Interacting with repositories by HTTP protocol is not disabled")
|
||||
return
|
||||
}
|
||||
|
||||
if route.method != c.Req.Method {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
file := strings.TrimPrefix(reqPath, m[1]+"/")
|
||||
dir, err := getGitRepoPath(m[1])
|
||||
if err != nil {
|
||||
log.Warn("HTTP.getGitRepoPath: %v", err)
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
route.handler(serviceHandler{
|
||||
w: c.Resp,
|
||||
r: c.Req.Request,
|
||||
dir: dir,
|
||||
file: file,
|
||||
|
||||
authUser: c.AuthUser,
|
||||
ownerName: c.OwnerName,
|
||||
ownerSalt: c.OwnerSalt,
|
||||
repoID: c.RepoID,
|
||||
repoName: c.RepoName,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.NotFound()
|
||||
}
|
||||
1278
internal/route/repo/issue.go
Normal file
1278
internal/route/repo/issue.go
Normal file
File diff suppressed because it is too large
Load Diff
771
internal/route/repo/pull.go
Normal file
771
internal/route/repo/pull.go
Normal file
@@ -0,0 +1,771 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
FORK = "repo/pulls/fork"
|
||||
COMPARE_PULL = "repo/pulls/compare"
|
||||
PULL_COMMITS = "repo/pulls/commits"
|
||||
PULL_FILES = "repo/pulls/files"
|
||||
|
||||
PULL_REQUEST_TEMPLATE_KEY = "PullRequestTemplate"
|
||||
)
|
||||
|
||||
var (
|
||||
PullRequestTemplateCandidates = []string{
|
||||
"PULL_REQUEST.md",
|
||||
".gogs/PULL_REQUEST.md",
|
||||
".github/PULL_REQUEST.md",
|
||||
}
|
||||
)
|
||||
|
||||
func parseBaseRepository(c *context.Context) *db.Repository {
|
||||
baseRepo, err := db.GetRepositoryByID(c.ParamsInt64(":repoid"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetRepositoryByID", errors.IsRepoNotExist, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !baseRepo.CanBeForked() || !baseRepo.HasAccess(c.User.ID) {
|
||||
c.NotFound()
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Data["repo_name"] = baseRepo.Name
|
||||
c.Data["description"] = baseRepo.Description
|
||||
c.Data["IsPrivate"] = baseRepo.IsPrivate
|
||||
|
||||
if err = baseRepo.GetOwner(); err != nil {
|
||||
c.ServerError("GetOwner", err)
|
||||
return nil
|
||||
}
|
||||
c.Data["ForkFrom"] = baseRepo.Owner.Name + "/" + baseRepo.Name
|
||||
|
||||
if err := c.User.GetOrganizations(true); err != nil {
|
||||
c.ServerError("GetOrganizations", err)
|
||||
return nil
|
||||
}
|
||||
c.Data["Orgs"] = c.User.Orgs
|
||||
|
||||
return baseRepo
|
||||
}
|
||||
|
||||
func Fork(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("new_fork")
|
||||
|
||||
parseBaseRepository(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["ContextUser"] = c.User
|
||||
c.Success(FORK)
|
||||
}
|
||||
|
||||
func ForkPost(c *context.Context, f form.CreateRepo) {
|
||||
c.Data["Title"] = c.Tr("new_fork")
|
||||
|
||||
baseRepo := parseBaseRepository(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
ctxUser := checkContextUser(c, f.UserID)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["ContextUser"] = ctxUser
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(FORK)
|
||||
return
|
||||
}
|
||||
|
||||
repo, has, err := db.HasForkedRepo(ctxUser.ID, baseRepo.ID)
|
||||
if err != nil {
|
||||
c.ServerError("HasForkedRepo", err)
|
||||
return
|
||||
} else if has {
|
||||
c.Redirect(repo.Link())
|
||||
return
|
||||
}
|
||||
|
||||
// Check ownership of organization.
|
||||
if ctxUser.IsOrganization() && !ctxUser.IsOwnedBy(c.User.ID) {
|
||||
c.Error(403)
|
||||
return
|
||||
}
|
||||
|
||||
// Cannot fork to same owner
|
||||
if ctxUser.ID == baseRepo.OwnerID {
|
||||
c.RenderWithErr(c.Tr("repo.settings.cannot_fork_to_same_owner"), FORK, &f)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err = db.ForkRepository(c.User, ctxUser, baseRepo, f.RepoName, f.Description)
|
||||
if err != nil {
|
||||
c.Data["Err_RepoName"] = true
|
||||
switch {
|
||||
case errors.IsReachLimitOfRepo(err):
|
||||
c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", c.User.RepoCreationNum()), FORK, &f)
|
||||
case db.IsErrRepoAlreadyExist(err):
|
||||
c.RenderWithErr(c.Tr("repo.settings.new_owner_has_same_repo"), FORK, &f)
|
||||
case db.IsErrNameReserved(err):
|
||||
c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), FORK, &f)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), FORK, &f)
|
||||
default:
|
||||
c.ServerError("ForkPost", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository forked from '%s' -> '%s'", baseRepo.FullName(), repo.FullName())
|
||||
c.Redirect(repo.Link())
|
||||
}
|
||||
|
||||
func checkPullInfo(c *context.Context) *db.Issue {
|
||||
issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
|
||||
return nil
|
||||
}
|
||||
c.Data["Title"] = issue.Title
|
||||
c.Data["Issue"] = issue
|
||||
|
||||
if !issue.IsPull {
|
||||
c.Handle(404, "ViewPullCommits", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.IsLogged {
|
||||
// Update issue-user.
|
||||
if err = issue.ReadBy(c.User.ID); err != nil {
|
||||
c.ServerError("ReadBy", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return issue
|
||||
}
|
||||
|
||||
func PrepareMergedViewPullInfo(c *context.Context, issue *db.Issue) {
|
||||
pull := issue.PullRequest
|
||||
c.Data["HasMerged"] = true
|
||||
c.Data["HeadTarget"] = issue.PullRequest.HeadUserName + "/" + pull.HeadBranch
|
||||
c.Data["BaseTarget"] = c.Repo.Owner.Name + "/" + pull.BaseBranch
|
||||
|
||||
var err error
|
||||
c.Data["NumCommits"], err = c.Repo.GitRepo.CommitsCountBetween(pull.MergeBase, pull.MergedCommitID)
|
||||
if err != nil {
|
||||
c.ServerError("Repo.GitRepo.CommitsCountBetween", err)
|
||||
return
|
||||
}
|
||||
c.Data["NumFiles"], err = c.Repo.GitRepo.FilesCountBetween(pull.MergeBase, pull.MergedCommitID)
|
||||
if err != nil {
|
||||
c.ServerError("Repo.GitRepo.FilesCountBetween", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func PrepareViewPullInfo(c *context.Context, issue *db.Issue) *git.PullRequestInfo {
|
||||
repo := c.Repo.Repository
|
||||
pull := issue.PullRequest
|
||||
|
||||
c.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBranch
|
||||
c.Data["BaseTarget"] = c.Repo.Owner.Name + "/" + pull.BaseBranch
|
||||
|
||||
var (
|
||||
headGitRepo *git.Repository
|
||||
err error
|
||||
)
|
||||
|
||||
if pull.HeadRepo != nil {
|
||||
headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath())
|
||||
if err != nil {
|
||||
c.ServerError("OpenRepository", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if pull.HeadRepo == nil || !headGitRepo.IsBranchExist(pull.HeadBranch) {
|
||||
c.Data["IsPullReuqestBroken"] = true
|
||||
c.Data["HeadTarget"] = "deleted"
|
||||
c.Data["NumCommits"] = 0
|
||||
c.Data["NumFiles"] = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
prInfo, err := headGitRepo.GetPullRequestInfo(db.RepoPath(repo.Owner.Name, repo.Name),
|
||||
pull.BaseBranch, pull.HeadBranch)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "fatal: Not a valid object name") {
|
||||
c.Data["IsPullReuqestBroken"] = true
|
||||
c.Data["BaseTarget"] = "deleted"
|
||||
c.Data["NumCommits"] = 0
|
||||
c.Data["NumFiles"] = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
c.ServerError("GetPullRequestInfo", err)
|
||||
return nil
|
||||
}
|
||||
c.Data["NumCommits"] = prInfo.Commits.Len()
|
||||
c.Data["NumFiles"] = prInfo.NumFiles
|
||||
return prInfo
|
||||
}
|
||||
|
||||
func ViewPullCommits(c *context.Context) {
|
||||
c.Data["PageIsPullList"] = true
|
||||
c.Data["PageIsPullCommits"] = true
|
||||
|
||||
issue := checkPullInfo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
pull := issue.PullRequest
|
||||
|
||||
if pull.HeadRepo != nil {
|
||||
c.Data["Username"] = pull.HeadUserName
|
||||
c.Data["Reponame"] = pull.HeadRepo.Name
|
||||
}
|
||||
|
||||
var commits *list.List
|
||||
if pull.HasMerged {
|
||||
PrepareMergedViewPullInfo(c, issue)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
startCommit, err := c.Repo.GitRepo.GetCommit(pull.MergeBase)
|
||||
if err != nil {
|
||||
c.ServerError("Repo.GitRepo.GetCommit", err)
|
||||
return
|
||||
}
|
||||
endCommit, err := c.Repo.GitRepo.GetCommit(pull.MergedCommitID)
|
||||
if err != nil {
|
||||
c.ServerError("Repo.GitRepo.GetCommit", err)
|
||||
return
|
||||
}
|
||||
commits, err = c.Repo.GitRepo.CommitsBetween(endCommit, startCommit)
|
||||
if err != nil {
|
||||
c.ServerError("Repo.GitRepo.CommitsBetween", err)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
prInfo := PrepareViewPullInfo(c, issue)
|
||||
if c.Written() {
|
||||
return
|
||||
} else if prInfo == nil {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
commits = prInfo.Commits
|
||||
}
|
||||
|
||||
commits = db.ValidateCommitsWithEmails(commits)
|
||||
c.Data["Commits"] = commits
|
||||
c.Data["CommitsCount"] = commits.Len()
|
||||
|
||||
c.Success(PULL_COMMITS)
|
||||
}
|
||||
|
||||
func ViewPullFiles(c *context.Context) {
|
||||
c.Data["PageIsPullList"] = true
|
||||
c.Data["PageIsPullFiles"] = true
|
||||
|
||||
issue := checkPullInfo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
pull := issue.PullRequest
|
||||
|
||||
var (
|
||||
diffRepoPath string
|
||||
startCommitID string
|
||||
endCommitID string
|
||||
gitRepo *git.Repository
|
||||
)
|
||||
|
||||
if pull.HasMerged {
|
||||
PrepareMergedViewPullInfo(c, issue)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
diffRepoPath = c.Repo.GitRepo.Path
|
||||
startCommitID = pull.MergeBase
|
||||
endCommitID = pull.MergedCommitID
|
||||
gitRepo = c.Repo.GitRepo
|
||||
} else {
|
||||
prInfo := PrepareViewPullInfo(c, issue)
|
||||
if c.Written() {
|
||||
return
|
||||
} else if prInfo == nil {
|
||||
c.Handle(404, "ViewPullFiles", nil)
|
||||
return
|
||||
}
|
||||
|
||||
headRepoPath := db.RepoPath(pull.HeadUserName, pull.HeadRepo.Name)
|
||||
|
||||
headGitRepo, err := git.OpenRepository(headRepoPath)
|
||||
if err != nil {
|
||||
c.ServerError("OpenRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
headCommitID, err := headGitRepo.GetBranchCommitID(pull.HeadBranch)
|
||||
if err != nil {
|
||||
c.ServerError("GetBranchCommitID", err)
|
||||
return
|
||||
}
|
||||
|
||||
diffRepoPath = headRepoPath
|
||||
startCommitID = prInfo.MergeBase
|
||||
endCommitID = headCommitID
|
||||
gitRepo = headGitRepo
|
||||
}
|
||||
|
||||
diff, err := db.GetDiffRange(diffRepoPath,
|
||||
startCommitID, endCommitID, setting.Git.MaxGitDiffLines,
|
||||
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
|
||||
if err != nil {
|
||||
c.ServerError("GetDiffRange", err)
|
||||
return
|
||||
}
|
||||
c.Data["Diff"] = diff
|
||||
c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
|
||||
|
||||
commit, err := gitRepo.GetCommit(endCommitID)
|
||||
if err != nil {
|
||||
c.ServerError("GetCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
setEditorconfigIfExists(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["IsSplitStyle"] = c.Query("style") == "split"
|
||||
c.Data["IsImageFile"] = commit.IsImageFile
|
||||
|
||||
// It is possible head repo has been deleted for merged pull requests
|
||||
if pull.HeadRepo != nil {
|
||||
c.Data["Username"] = pull.HeadUserName
|
||||
c.Data["Reponame"] = pull.HeadRepo.Name
|
||||
|
||||
headTarget := path.Join(pull.HeadUserName, pull.HeadRepo.Name)
|
||||
c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", endCommitID)
|
||||
c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", startCommitID)
|
||||
c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", endCommitID)
|
||||
}
|
||||
|
||||
c.Data["RequireHighlightJS"] = true
|
||||
c.Success(PULL_FILES)
|
||||
}
|
||||
|
||||
func MergePullRequest(c *context.Context) {
|
||||
issue := checkPullInfo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
if issue.IsClosed {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
pr, err := db.GetPullRequestByIssueID(issue.ID)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetPullRequestByIssueID", db.IsErrPullRequestNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !pr.CanAutoMerge() || pr.HasMerged {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
pr.Issue = issue
|
||||
pr.Issue.Repo = c.Repo.Repository
|
||||
if err = pr.Merge(c.User, c.Repo.GitRepo, db.MergeStyle(c.Query("merge_style")), c.Query("commit_description")); err != nil {
|
||||
c.ServerError("Merge", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Pull request merged: %d", pr.ID)
|
||||
c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
|
||||
}
|
||||
|
||||
func ParseCompareInfo(c *context.Context) (*db.User, *db.Repository, *git.Repository, *git.PullRequestInfo, string, string) {
|
||||
baseRepo := c.Repo.Repository
|
||||
|
||||
// Get compared branches information
|
||||
// format: <base branch>...[<head repo>:]<head branch>
|
||||
// base<-head: master...head:feature
|
||||
// same repo: master...feature
|
||||
infos := strings.Split(c.Params("*"), "...")
|
||||
if len(infos) != 2 {
|
||||
log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos)
|
||||
c.NotFound()
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
|
||||
baseBranch := infos[0]
|
||||
c.Data["BaseBranch"] = baseBranch
|
||||
|
||||
var (
|
||||
headUser *db.User
|
||||
headBranch string
|
||||
isSameRepo bool
|
||||
err error
|
||||
)
|
||||
|
||||
// If there is no head repository, it means pull request between same repository.
|
||||
headInfos := strings.Split(infos[1], ":")
|
||||
if len(headInfos) == 1 {
|
||||
isSameRepo = true
|
||||
headUser = c.Repo.Owner
|
||||
headBranch = headInfos[0]
|
||||
|
||||
} else if len(headInfos) == 2 {
|
||||
headUser, err = db.GetUserByName(headInfos[0])
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
headBranch = headInfos[1]
|
||||
isSameRepo = headUser.ID == baseRepo.OwnerID
|
||||
|
||||
} else {
|
||||
c.NotFound()
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
c.Data["HeadUser"] = headUser
|
||||
c.Data["HeadBranch"] = headBranch
|
||||
c.Repo.PullRequest.SameRepo = isSameRepo
|
||||
|
||||
// Check if base branch is valid.
|
||||
if !c.Repo.GitRepo.IsBranchExist(baseBranch) {
|
||||
c.NotFound()
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
|
||||
var (
|
||||
headRepo *db.Repository
|
||||
headGitRepo *git.Repository
|
||||
)
|
||||
|
||||
// In case user included redundant head user name for comparison in same repository,
|
||||
// no need to check the fork relation.
|
||||
if !isSameRepo {
|
||||
var has bool
|
||||
headRepo, has, err = db.HasForkedRepo(headUser.ID, baseRepo.ID)
|
||||
if err != nil {
|
||||
c.ServerError("HasForkedRepo", err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
} else if !has {
|
||||
log.Trace("ParseCompareInfo [base_repo_id: %d]: does not have fork or in same repository", baseRepo.ID)
|
||||
c.NotFound()
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
|
||||
headGitRepo, err = git.OpenRepository(db.RepoPath(headUser.Name, headRepo.Name))
|
||||
if err != nil {
|
||||
c.ServerError("OpenRepository", err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
} else {
|
||||
headRepo = c.Repo.Repository
|
||||
headGitRepo = c.Repo.GitRepo
|
||||
}
|
||||
|
||||
if !c.User.IsWriterOfRepo(headRepo) && !c.User.IsAdmin {
|
||||
log.Trace("ParseCompareInfo [base_repo_id: %d]: does not have write access or site admin", baseRepo.ID)
|
||||
c.NotFound()
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
|
||||
// Check if head branch is valid.
|
||||
if !headGitRepo.IsBranchExist(headBranch) {
|
||||
c.NotFound()
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
|
||||
headBranches, err := headGitRepo.GetBranches()
|
||||
if err != nil {
|
||||
c.ServerError("GetBranches", err)
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
c.Data["HeadBranches"] = headBranches
|
||||
|
||||
prInfo, err := headGitRepo.GetPullRequestInfo(db.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
|
||||
if err != nil {
|
||||
if git.IsErrNoMergeBase(err) {
|
||||
c.Data["IsNoMergeBase"] = true
|
||||
c.Success(COMPARE_PULL)
|
||||
} else {
|
||||
c.ServerError("GetPullRequestInfo", err)
|
||||
}
|
||||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
c.Data["BeforeCommitID"] = prInfo.MergeBase
|
||||
|
||||
return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch
|
||||
}
|
||||
|
||||
func PrepareCompareDiff(
|
||||
c *context.Context,
|
||||
headUser *db.User,
|
||||
headRepo *db.Repository,
|
||||
headGitRepo *git.Repository,
|
||||
prInfo *git.PullRequestInfo,
|
||||
baseBranch, headBranch string) bool {
|
||||
|
||||
var (
|
||||
repo = c.Repo.Repository
|
||||
err error
|
||||
)
|
||||
|
||||
// Get diff information.
|
||||
c.Data["CommitRepoLink"] = headRepo.Link()
|
||||
|
||||
headCommitID, err := headGitRepo.GetBranchCommitID(headBranch)
|
||||
if err != nil {
|
||||
c.ServerError("GetBranchCommitID", err)
|
||||
return false
|
||||
}
|
||||
c.Data["AfterCommitID"] = headCommitID
|
||||
|
||||
if headCommitID == prInfo.MergeBase {
|
||||
c.Data["IsNothingToCompare"] = true
|
||||
return true
|
||||
}
|
||||
|
||||
diff, err := db.GetDiffRange(db.RepoPath(headUser.Name, headRepo.Name),
|
||||
prInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines,
|
||||
setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles)
|
||||
if err != nil {
|
||||
c.ServerError("GetDiffRange", err)
|
||||
return false
|
||||
}
|
||||
c.Data["Diff"] = diff
|
||||
c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
|
||||
|
||||
headCommit, err := headGitRepo.GetCommit(headCommitID)
|
||||
if err != nil {
|
||||
c.ServerError("GetCommit", err)
|
||||
return false
|
||||
}
|
||||
|
||||
prInfo.Commits = db.ValidateCommitsWithEmails(prInfo.Commits)
|
||||
c.Data["Commits"] = prInfo.Commits
|
||||
c.Data["CommitCount"] = prInfo.Commits.Len()
|
||||
c.Data["Username"] = headUser.Name
|
||||
c.Data["Reponame"] = headRepo.Name
|
||||
c.Data["IsImageFile"] = headCommit.IsImageFile
|
||||
|
||||
headTarget := path.Join(headUser.Name, repo.Name)
|
||||
c.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", headCommitID)
|
||||
c.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", prInfo.MergeBase)
|
||||
c.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", headCommitID)
|
||||
return false
|
||||
}
|
||||
|
||||
func CompareAndPullRequest(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.pulls.compare_changes")
|
||||
c.Data["PageIsComparePull"] = true
|
||||
c.Data["IsDiffCompare"] = true
|
||||
c.Data["RequireHighlightJS"] = true
|
||||
setTemplateIfExists(c, PULL_REQUEST_TEMPLATE_KEY, PullRequestTemplateCandidates)
|
||||
renderAttachmentSettings(c)
|
||||
|
||||
headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
pr, err := db.GetUnmergedPullRequest(headRepo.ID, c.Repo.Repository.ID, headBranch, baseBranch)
|
||||
if err != nil {
|
||||
if !db.IsErrPullRequestNotExist(err) {
|
||||
c.ServerError("GetUnmergedPullRequest", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.Data["HasPullRequest"] = true
|
||||
c.Data["PullRequest"] = pr
|
||||
c.Success(COMPARE_PULL)
|
||||
return
|
||||
}
|
||||
|
||||
nothingToCompare := PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if !nothingToCompare {
|
||||
// Setup information for new form.
|
||||
RetrieveRepoMetas(c, c.Repo.Repository)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setEditorconfigIfExists(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["IsSplitStyle"] = c.Query("style") == "split"
|
||||
c.Success(COMPARE_PULL)
|
||||
}
|
||||
|
||||
func CompareAndPullRequestPost(c *context.Context, f form.NewIssue) {
|
||||
c.Data["Title"] = c.Tr("repo.pulls.compare_changes")
|
||||
c.Data["PageIsComparePull"] = true
|
||||
c.Data["IsDiffCompare"] = true
|
||||
c.Data["RequireHighlightJS"] = true
|
||||
renderAttachmentSettings(c)
|
||||
|
||||
var (
|
||||
repo = c.Repo.Repository
|
||||
attachments []string
|
||||
)
|
||||
|
||||
headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
labelIDs, milestoneID, assigneeID := ValidateRepoMetas(c, f)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if setting.AttachmentEnabled {
|
||||
attachments = f.Files
|
||||
}
|
||||
|
||||
if c.HasError() {
|
||||
form.Assign(f, c.Data)
|
||||
|
||||
// This stage is already stop creating new pull request, so it does not matter if it has
|
||||
// something to compare or not.
|
||||
PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Success(COMPARE_PULL)
|
||||
return
|
||||
}
|
||||
|
||||
patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch)
|
||||
if err != nil {
|
||||
c.ServerError("GetPatch", err)
|
||||
return
|
||||
}
|
||||
|
||||
pullIssue := &db.Issue{
|
||||
RepoID: repo.ID,
|
||||
Index: repo.NextIssueIndex(),
|
||||
Title: f.Title,
|
||||
PosterID: c.User.ID,
|
||||
Poster: c.User,
|
||||
MilestoneID: milestoneID,
|
||||
AssigneeID: assigneeID,
|
||||
IsPull: true,
|
||||
Content: f.Content,
|
||||
}
|
||||
pullRequest := &db.PullRequest{
|
||||
HeadRepoID: headRepo.ID,
|
||||
BaseRepoID: repo.ID,
|
||||
HeadUserName: headUser.Name,
|
||||
HeadBranch: headBranch,
|
||||
BaseBranch: baseBranch,
|
||||
HeadRepo: headRepo,
|
||||
BaseRepo: repo,
|
||||
MergeBase: prInfo.MergeBase,
|
||||
Type: db.PULL_REQUEST_GOGS,
|
||||
}
|
||||
// FIXME: check error in the case two people send pull request at almost same time, give nice error prompt
|
||||
// instead of 500.
|
||||
if err := db.NewPullRequest(repo, pullIssue, labelIDs, attachments, pullRequest, patch); err != nil {
|
||||
c.ServerError("NewPullRequest", err)
|
||||
return
|
||||
} else if err := pullRequest.PushToBaseRepo(); err != nil {
|
||||
c.ServerError("PushToBaseRepo", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
|
||||
c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(pullIssue.Index))
|
||||
}
|
||||
|
||||
func parseOwnerAndRepo(c *context.Context) (*db.User, *db.Repository) {
|
||||
owner, err := db.GetUserByName(c.Params(":username"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
repo, err := db.GetRepositoryByName(owner.ID, c.Params(":reponame"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return owner, repo
|
||||
}
|
||||
|
||||
func TriggerTask(c *context.Context) {
|
||||
pusherID := c.QueryInt64("pusher")
|
||||
branch := c.Query("branch")
|
||||
secret := c.Query("secret")
|
||||
if len(branch) == 0 || len(secret) == 0 || pusherID <= 0 {
|
||||
c.Error(404)
|
||||
log.Trace("TriggerTask: branch or secret is empty, or pusher ID is not valid")
|
||||
return
|
||||
}
|
||||
owner, repo := parseOwnerAndRepo(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
if secret != tool.MD5(owner.Salt) {
|
||||
c.Error(404)
|
||||
log.Trace("TriggerTask [%s/%s]: invalid secret", owner.Name, repo.Name)
|
||||
return
|
||||
}
|
||||
|
||||
pusher, err := db.GetUserByID(pusherID)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByID", errors.IsUserNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("TriggerTask '%s/%s' by '%s'", repo.Name, branch, pusher.Name)
|
||||
|
||||
go db.HookQueue.Add(repo.ID)
|
||||
go db.AddTestPullRequestTask(pusher, repo.ID, branch, true)
|
||||
c.Status(202)
|
||||
}
|
||||
332
internal/route/repo/release.go
Normal file
332
internal/route/repo/release.go
Normal file
@@ -0,0 +1,332 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/markup"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
RELEASES = "repo/release/list"
|
||||
RELEASE_NEW = "repo/release/new"
|
||||
)
|
||||
|
||||
// calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
|
||||
func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *db.Release, countCache map[string]int64) error {
|
||||
// Get count if not exists
|
||||
if _, ok := countCache[release.Target]; !ok {
|
||||
if repoCtx.GitRepo.IsBranchExist(release.Target) {
|
||||
commit, err := repoCtx.GitRepo.GetBranchCommit(release.Target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetBranchCommit: %v", err)
|
||||
}
|
||||
countCache[release.Target], err = commit.CommitsCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("CommitsCount: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Use NumCommits of the newest release on that target
|
||||
countCache[release.Target] = release.NumCommits
|
||||
}
|
||||
}
|
||||
release.NumCommitsBehind = countCache[release.Target] - release.NumCommits
|
||||
return nil
|
||||
}
|
||||
|
||||
func Releases(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.release.releases")
|
||||
c.Data["PageIsViewFiles"] = true
|
||||
c.Data["PageIsReleaseList"] = true
|
||||
|
||||
tagsResult, err := c.Repo.GitRepo.GetTagsAfter(c.Query("after"), 10)
|
||||
if err != nil {
|
||||
c.Handle(500, fmt.Sprintf("GetTags '%s'", c.Repo.Repository.RepoPath()), err)
|
||||
return
|
||||
}
|
||||
|
||||
releases, err := db.GetPublishedReleasesByRepoID(c.Repo.Repository.ID, tagsResult.Tags...)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetPublishedReleasesByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Temproray cache commits count of used branches to speed up.
|
||||
countCache := make(map[string]int64)
|
||||
|
||||
results := make([]*db.Release, len(tagsResult.Tags))
|
||||
for i, rawTag := range tagsResult.Tags {
|
||||
for j, r := range releases {
|
||||
if r == nil || r.TagName != rawTag {
|
||||
continue
|
||||
}
|
||||
releases[j] = nil // Mark as used.
|
||||
|
||||
if err = r.LoadAttributes(); err != nil {
|
||||
c.Handle(500, "LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := calReleaseNumCommitsBehind(c.Repo, r, countCache); err != nil {
|
||||
c.Handle(500, "calReleaseNumCommitsBehind", err)
|
||||
return
|
||||
}
|
||||
|
||||
r.Note = string(markup.Markdown(r.Note, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas()))
|
||||
results[i] = r
|
||||
break
|
||||
}
|
||||
|
||||
// No published release matches this tag
|
||||
if results[i] == nil {
|
||||
commit, err := c.Repo.GitRepo.GetTagCommit(rawTag)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetTagCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
results[i] = &db.Release{
|
||||
Title: rawTag,
|
||||
TagName: rawTag,
|
||||
Sha1: commit.ID.String(),
|
||||
}
|
||||
|
||||
results[i].NumCommits, err = commit.CommitsCount()
|
||||
if err != nil {
|
||||
c.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
results[i].NumCommitsBehind = c.Repo.CommitsCount - results[i].NumCommits
|
||||
}
|
||||
}
|
||||
db.SortReleases(results)
|
||||
|
||||
// Only show drafts if user is viewing the latest page
|
||||
var drafts []*db.Release
|
||||
if tagsResult.HasLatest {
|
||||
drafts, err = db.GetDraftReleasesByRepoID(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetDraftReleasesByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range drafts {
|
||||
if err = r.LoadAttributes(); err != nil {
|
||||
c.Handle(500, "LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := calReleaseNumCommitsBehind(c.Repo, r, countCache); err != nil {
|
||||
c.Handle(500, "calReleaseNumCommitsBehind", err)
|
||||
return
|
||||
}
|
||||
|
||||
r.Note = string(markup.Markdown(r.Note, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas()))
|
||||
}
|
||||
|
||||
if len(drafts) > 0 {
|
||||
results = append(drafts, results...)
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["Releases"] = results
|
||||
c.Data["HasPrevious"] = !tagsResult.HasLatest
|
||||
c.Data["ReachEnd"] = tagsResult.ReachEnd
|
||||
c.Data["PreviousAfter"] = tagsResult.PreviousAfter
|
||||
if len(results) > 0 {
|
||||
c.Data["NextAfter"] = results[len(results)-1].TagName
|
||||
}
|
||||
c.HTML(200, RELEASES)
|
||||
}
|
||||
|
||||
func renderReleaseAttachmentSettings(c *context.Context) {
|
||||
c.Data["RequireDropzone"] = true
|
||||
c.Data["IsAttachmentEnabled"] = setting.Release.Attachment.Enabled
|
||||
c.Data["AttachmentAllowedTypes"] = strings.Join(setting.Release.Attachment.AllowedTypes, ",")
|
||||
c.Data["AttachmentMaxSize"] = setting.Release.Attachment.MaxSize
|
||||
c.Data["AttachmentMaxFiles"] = setting.Release.Attachment.MaxFiles
|
||||
}
|
||||
|
||||
func NewRelease(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.release.new_release")
|
||||
c.Data["PageIsReleaseList"] = true
|
||||
c.Data["tag_target"] = c.Repo.Repository.DefaultBranch
|
||||
renderReleaseAttachmentSettings(c)
|
||||
c.HTML(200, RELEASE_NEW)
|
||||
}
|
||||
|
||||
func NewReleasePost(c *context.Context, f form.NewRelease) {
|
||||
c.Data["Title"] = c.Tr("repo.release.new_release")
|
||||
c.Data["PageIsReleaseList"] = true
|
||||
renderReleaseAttachmentSettings(c)
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, RELEASE_NEW)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.Repo.GitRepo.IsBranchExist(f.Target) {
|
||||
c.RenderWithErr(c.Tr("form.target_branch_not_exist"), RELEASE_NEW, &f)
|
||||
return
|
||||
}
|
||||
|
||||
// Use current time if tag not yet exist, otherwise get time from Git
|
||||
var tagCreatedUnix int64
|
||||
tag, err := c.Repo.GitRepo.GetTag(f.TagName)
|
||||
if err == nil {
|
||||
commit, err := tag.Commit()
|
||||
if err == nil {
|
||||
tagCreatedUnix = commit.Author.When.Unix()
|
||||
}
|
||||
}
|
||||
|
||||
commit, err := c.Repo.GitRepo.GetBranchCommit(f.Target)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := commit.CommitsCount()
|
||||
if err != nil {
|
||||
c.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
var attachments []string
|
||||
if setting.Release.Attachment.Enabled {
|
||||
attachments = f.Files
|
||||
}
|
||||
|
||||
rel := &db.Release{
|
||||
RepoID: c.Repo.Repository.ID,
|
||||
PublisherID: c.User.ID,
|
||||
Title: f.Title,
|
||||
TagName: f.TagName,
|
||||
Target: f.Target,
|
||||
Sha1: commit.ID.String(),
|
||||
NumCommits: commitsCount,
|
||||
Note: f.Content,
|
||||
IsDraft: len(f.Draft) > 0,
|
||||
IsPrerelease: f.Prerelease,
|
||||
CreatedUnix: tagCreatedUnix,
|
||||
}
|
||||
if err = db.NewRelease(c.Repo.GitRepo, rel, attachments); err != nil {
|
||||
c.Data["Err_TagName"] = true
|
||||
switch {
|
||||
case db.IsErrReleaseAlreadyExist(err):
|
||||
c.RenderWithErr(c.Tr("repo.release.tag_name_already_exist"), RELEASE_NEW, &f)
|
||||
case db.IsErrInvalidTagName(err):
|
||||
c.RenderWithErr(c.Tr("repo.release.tag_name_invalid"), RELEASE_NEW, &f)
|
||||
default:
|
||||
c.Handle(500, "NewRelease", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Release created: %s/%s:%s", c.User.LowerName, c.Repo.Repository.Name, f.TagName)
|
||||
|
||||
c.Redirect(c.Repo.RepoLink + "/releases")
|
||||
}
|
||||
|
||||
func EditRelease(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.release.edit_release")
|
||||
c.Data["PageIsReleaseList"] = true
|
||||
c.Data["PageIsEditRelease"] = true
|
||||
renderReleaseAttachmentSettings(c)
|
||||
|
||||
tagName := c.Params("*")
|
||||
rel, err := db.GetRelease(c.Repo.Repository.ID, tagName)
|
||||
if err != nil {
|
||||
if db.IsErrReleaseNotExist(err) {
|
||||
c.Handle(404, "GetRelease", err)
|
||||
} else {
|
||||
c.Handle(500, "GetRelease", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.Data["ID"] = rel.ID
|
||||
c.Data["tag_name"] = rel.TagName
|
||||
c.Data["tag_target"] = rel.Target
|
||||
c.Data["title"] = rel.Title
|
||||
c.Data["content"] = rel.Note
|
||||
c.Data["attachments"] = rel.Attachments
|
||||
c.Data["prerelease"] = rel.IsPrerelease
|
||||
c.Data["IsDraft"] = rel.IsDraft
|
||||
|
||||
c.HTML(200, RELEASE_NEW)
|
||||
}
|
||||
|
||||
func EditReleasePost(c *context.Context, f form.EditRelease) {
|
||||
c.Data["Title"] = c.Tr("repo.release.edit_release")
|
||||
c.Data["PageIsReleaseList"] = true
|
||||
c.Data["PageIsEditRelease"] = true
|
||||
renderReleaseAttachmentSettings(c)
|
||||
|
||||
tagName := c.Params("*")
|
||||
rel, err := db.GetRelease(c.Repo.Repository.ID, tagName)
|
||||
if err != nil {
|
||||
if db.IsErrReleaseNotExist(err) {
|
||||
c.Handle(404, "GetRelease", err)
|
||||
} else {
|
||||
c.Handle(500, "GetRelease", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.Data["tag_name"] = rel.TagName
|
||||
c.Data["tag_target"] = rel.Target
|
||||
c.Data["title"] = rel.Title
|
||||
c.Data["content"] = rel.Note
|
||||
c.Data["attachments"] = rel.Attachments
|
||||
c.Data["prerelease"] = rel.IsPrerelease
|
||||
c.Data["IsDraft"] = rel.IsDraft
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, RELEASE_NEW)
|
||||
return
|
||||
}
|
||||
|
||||
var attachments []string
|
||||
if setting.Release.Attachment.Enabled {
|
||||
attachments = f.Files
|
||||
}
|
||||
|
||||
isPublish := rel.IsDraft && len(f.Draft) == 0
|
||||
rel.Title = f.Title
|
||||
rel.Note = f.Content
|
||||
rel.IsDraft = len(f.Draft) > 0
|
||||
rel.IsPrerelease = f.Prerelease
|
||||
if err = db.UpdateRelease(c.User, c.Repo.GitRepo, rel, isPublish, attachments); err != nil {
|
||||
c.Handle(500, "UpdateRelease", err)
|
||||
return
|
||||
}
|
||||
c.Redirect(c.Repo.RepoLink + "/releases")
|
||||
}
|
||||
|
||||
func UploadReleaseAttachment(c *context.Context) {
|
||||
if !setting.Release.Attachment.Enabled {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
uploadAttachment(c, setting.Release.Attachment.AllowedTypes)
|
||||
}
|
||||
|
||||
func DeleteRelease(c *context.Context) {
|
||||
if err := db.DeleteReleaseOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil {
|
||||
c.Flash.Error("DeleteReleaseByID: " + err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("repo.release.deletion_success"))
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": c.Repo.RepoLink + "/releases",
|
||||
})
|
||||
}
|
||||
342
internal/route/repo/repo.go
Normal file
342
internal/route/repo/repo.go
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
CREATE = "repo/create"
|
||||
MIGRATE = "repo/migrate"
|
||||
)
|
||||
|
||||
func MustBeNotBare(c *context.Context) {
|
||||
if c.Repo.Repository.IsBare {
|
||||
c.Handle(404, "MustBeNotBare", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func checkContextUser(c *context.Context, uid int64) *db.User {
|
||||
orgs, err := db.GetOwnedOrgsByUserIDDesc(c.User.ID, "updated_unix")
|
||||
if err != nil {
|
||||
c.Handle(500, "GetOwnedOrgsByUserIDDesc", err)
|
||||
return nil
|
||||
}
|
||||
c.Data["Orgs"] = orgs
|
||||
|
||||
// Not equal means current user is an organization.
|
||||
if uid == c.User.ID || uid == 0 {
|
||||
return c.User
|
||||
}
|
||||
|
||||
org, err := db.GetUserByID(uid)
|
||||
if errors.IsUserNotExist(err) {
|
||||
return c.User
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.Handle(500, "GetUserByID", fmt.Errorf("[%d]: %v", uid, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check ownership of organization.
|
||||
if !org.IsOrganization() || !(c.User.IsAdmin || org.IsOwnedBy(c.User.ID)) {
|
||||
c.Error(403)
|
||||
return nil
|
||||
}
|
||||
return org
|
||||
}
|
||||
|
||||
func Create(c *context.Context) {
|
||||
c.Title("new_repo")
|
||||
c.RequireAutosize()
|
||||
|
||||
// Give default value for template to render.
|
||||
c.Data["Gitignores"] = db.Gitignores
|
||||
c.Data["Licenses"] = db.Licenses
|
||||
c.Data["Readmes"] = db.Readmes
|
||||
c.Data["readme"] = "Default"
|
||||
c.Data["private"] = c.User.LastRepoVisibility
|
||||
c.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
|
||||
|
||||
ctxUser := checkContextUser(c, c.QueryInt64("org"))
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["ContextUser"] = ctxUser
|
||||
|
||||
c.HTML(200, CREATE)
|
||||
}
|
||||
|
||||
func handleCreateError(c *context.Context, owner *db.User, err error, name, tpl string, form interface{}) {
|
||||
switch {
|
||||
case errors.IsReachLimitOfRepo(err):
|
||||
c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", owner.RepoCreationNum()), tpl, form)
|
||||
case db.IsErrRepoAlreadyExist(err):
|
||||
c.Data["Err_RepoName"] = true
|
||||
c.RenderWithErr(c.Tr("form.repo_name_been_taken"), tpl, form)
|
||||
case db.IsErrNameReserved(err):
|
||||
c.Data["Err_RepoName"] = true
|
||||
c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
c.Data["Err_RepoName"] = true
|
||||
c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form)
|
||||
default:
|
||||
c.Handle(500, name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func CreatePost(c *context.Context, f form.CreateRepo) {
|
||||
c.Data["Title"] = c.Tr("new_repo")
|
||||
|
||||
c.Data["Gitignores"] = db.Gitignores
|
||||
c.Data["Licenses"] = db.Licenses
|
||||
c.Data["Readmes"] = db.Readmes
|
||||
|
||||
ctxUser := checkContextUser(c, f.UserID)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["ContextUser"] = ctxUser
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, CREATE)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := db.CreateRepository(c.User, ctxUser, db.CreateRepoOptions{
|
||||
Name: f.RepoName,
|
||||
Description: f.Description,
|
||||
Gitignores: f.Gitignores,
|
||||
License: f.License,
|
||||
Readme: f.Readme,
|
||||
IsPrivate: f.Private || setting.Repository.ForcePrivate,
|
||||
AutoInit: f.AutoInit,
|
||||
})
|
||||
if err == nil {
|
||||
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
|
||||
c.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if repo != nil {
|
||||
if errDelete := db.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
|
||||
log.Error(4, "DeleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
|
||||
handleCreateError(c, ctxUser, err, "CreatePost", CREATE, &f)
|
||||
}
|
||||
|
||||
func Migrate(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("new_migrate")
|
||||
c.Data["private"] = c.User.LastRepoVisibility
|
||||
c.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
|
||||
c.Data["mirror"] = c.Query("mirror") == "1"
|
||||
|
||||
ctxUser := checkContextUser(c, c.QueryInt64("org"))
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["ContextUser"] = ctxUser
|
||||
|
||||
c.HTML(200, MIGRATE)
|
||||
}
|
||||
|
||||
func MigratePost(c *context.Context, f form.MigrateRepo) {
|
||||
c.Data["Title"] = c.Tr("new_migrate")
|
||||
|
||||
ctxUser := checkContextUser(c, f.Uid)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["ContextUser"] = ctxUser
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, MIGRATE)
|
||||
return
|
||||
}
|
||||
|
||||
remoteAddr, err := f.ParseRemoteAddr(c.User)
|
||||
if err != nil {
|
||||
if db.IsErrInvalidCloneAddr(err) {
|
||||
c.Data["Err_CloneAddr"] = true
|
||||
addrErr := err.(db.ErrInvalidCloneAddr)
|
||||
switch {
|
||||
case addrErr.IsURLError:
|
||||
c.RenderWithErr(c.Tr("form.url_error"), MIGRATE, &f)
|
||||
case addrErr.IsPermissionDenied:
|
||||
c.RenderWithErr(c.Tr("repo.migrate.permission_denied"), MIGRATE, &f)
|
||||
case addrErr.IsInvalidPath:
|
||||
c.RenderWithErr(c.Tr("repo.migrate.invalid_local_path"), MIGRATE, &f)
|
||||
default:
|
||||
c.Handle(500, "Unknown error", err)
|
||||
}
|
||||
} else {
|
||||
c.Handle(500, "ParseRemoteAddr", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := db.MigrateRepository(c.User, ctxUser, db.MigrateRepoOptions{
|
||||
Name: f.RepoName,
|
||||
Description: f.Description,
|
||||
IsPrivate: f.Private || setting.Repository.ForcePrivate,
|
||||
IsMirror: f.Mirror,
|
||||
RemoteAddr: remoteAddr,
|
||||
})
|
||||
if err == nil {
|
||||
log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, f.RepoName)
|
||||
c.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + f.RepoName)
|
||||
return
|
||||
}
|
||||
|
||||
if repo != nil {
|
||||
if errDelete := db.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
|
||||
log.Error(4, "DeleteRepository: %v", errDelete)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "Authentication failed") ||
|
||||
strings.Contains(err.Error(), "could not read Username") {
|
||||
c.Data["Err_Auth"] = true
|
||||
c.RenderWithErr(c.Tr("form.auth_failed", db.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f)
|
||||
return
|
||||
} else if strings.Contains(err.Error(), "fatal:") {
|
||||
c.Data["Err_CloneAddr"] = true
|
||||
c.RenderWithErr(c.Tr("repo.migrate.failed", db.HandleMirrorCredentials(err.Error(), true)), MIGRATE, &f)
|
||||
return
|
||||
}
|
||||
|
||||
handleCreateError(c, ctxUser, err, "MigratePost", MIGRATE, &f)
|
||||
}
|
||||
|
||||
func Action(c *context.Context) {
|
||||
var err error
|
||||
switch c.Params(":action") {
|
||||
case "watch":
|
||||
err = db.WatchRepo(c.User.ID, c.Repo.Repository.ID, true)
|
||||
case "unwatch":
|
||||
if userID := c.QueryInt64("user_id"); userID != 0 {
|
||||
if c.User.IsAdmin {
|
||||
err = db.WatchRepo(userID, c.Repo.Repository.ID, false)
|
||||
}
|
||||
} else {
|
||||
err = db.WatchRepo(c.User.ID, c.Repo.Repository.ID, false)
|
||||
}
|
||||
case "star":
|
||||
err = db.StarRepo(c.User.ID, c.Repo.Repository.ID, true)
|
||||
case "unstar":
|
||||
err = db.StarRepo(c.User.ID, c.Repo.Repository.ID, false)
|
||||
case "desc": // FIXME: this is not used
|
||||
if !c.Repo.IsOwner() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
c.Repo.Repository.Description = c.Query("desc")
|
||||
c.Repo.Repository.Website = c.Query("site")
|
||||
err = db.UpdateRepository(c.Repo.Repository, false)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ServerError(fmt.Sprintf("Action (%s)", c.Params(":action")), err)
|
||||
return
|
||||
}
|
||||
|
||||
redirectTo := c.Query("redirect_to")
|
||||
if !tool.IsSameSiteURLPath(redirectTo) {
|
||||
redirectTo = c.Repo.RepoLink
|
||||
}
|
||||
c.Redirect(redirectTo)
|
||||
}
|
||||
|
||||
func Download(c *context.Context) {
|
||||
var (
|
||||
uri = c.Params("*")
|
||||
refName string
|
||||
ext string
|
||||
archivePath string
|
||||
archiveType git.ArchiveType
|
||||
)
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(uri, ".zip"):
|
||||
ext = ".zip"
|
||||
archivePath = path.Join(c.Repo.GitRepo.Path, "archives/zip")
|
||||
archiveType = git.ZIP
|
||||
case strings.HasSuffix(uri, ".tar.gz"):
|
||||
ext = ".tar.gz"
|
||||
archivePath = path.Join(c.Repo.GitRepo.Path, "archives/targz")
|
||||
archiveType = git.TARGZ
|
||||
default:
|
||||
log.Trace("Unknown format: %s", uri)
|
||||
c.Error(404)
|
||||
return
|
||||
}
|
||||
refName = strings.TrimSuffix(uri, ext)
|
||||
|
||||
if !com.IsDir(archivePath) {
|
||||
if err := os.MkdirAll(archivePath, os.ModePerm); err != nil {
|
||||
c.Handle(500, "Download -> os.MkdirAll(archivePath)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get corresponding commit.
|
||||
var (
|
||||
commit *git.Commit
|
||||
err error
|
||||
)
|
||||
gitRepo := c.Repo.GitRepo
|
||||
if gitRepo.IsBranchExist(refName) {
|
||||
commit, err = gitRepo.GetBranchCommit(refName)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
} else if gitRepo.IsTagExist(refName) {
|
||||
commit, err = gitRepo.GetTagCommit(refName)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetTagCommit", err)
|
||||
return
|
||||
}
|
||||
} else if len(refName) >= 7 && len(refName) <= 40 {
|
||||
commit, err = gitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
archivePath = path.Join(archivePath, tool.ShortSHA1(commit.ID.String())+ext)
|
||||
if !com.IsFile(archivePath) {
|
||||
if err := commit.CreateArchive(archivePath, archiveType); err != nil {
|
||||
c.Handle(500, "Download -> CreateArchive "+archivePath, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.ServeFile(archivePath, c.Repo.Repository.Name+"-"+refName+ext)
|
||||
}
|
||||
695
internal/route/repo/setting.go
Normal file
695
internal/route/repo/setting.go
Normal file
@@ -0,0 +1,695 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/mailer"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
SETTINGS_OPTIONS = "repo/settings/options"
|
||||
SETTINGS_REPO_AVATAR = "repo/settings/avatar"
|
||||
SETTINGS_COLLABORATION = "repo/settings/collaboration"
|
||||
SETTINGS_BRANCHES = "repo/settings/branches"
|
||||
SETTINGS_PROTECTED_BRANCH = "repo/settings/protected_branch"
|
||||
SETTINGS_GITHOOKS = "repo/settings/githooks"
|
||||
SETTINGS_GITHOOK_EDIT = "repo/settings/githook_edit"
|
||||
SETTINGS_DEPLOY_KEYS = "repo/settings/deploy_keys"
|
||||
)
|
||||
|
||||
func Settings(c *context.Context) {
|
||||
c.Title("repo.settings")
|
||||
c.PageIs("SettingsOptions")
|
||||
c.RequireAutosize()
|
||||
c.Success(SETTINGS_OPTIONS)
|
||||
}
|
||||
|
||||
func SettingsPost(c *context.Context, f form.RepoSetting) {
|
||||
c.Title("repo.settings")
|
||||
c.PageIs("SettingsOptions")
|
||||
c.RequireAutosize()
|
||||
|
||||
repo := c.Repo.Repository
|
||||
|
||||
switch c.Query("action") {
|
||||
case "update":
|
||||
if c.HasError() {
|
||||
c.Success(SETTINGS_OPTIONS)
|
||||
return
|
||||
}
|
||||
|
||||
isNameChanged := false
|
||||
oldRepoName := repo.Name
|
||||
newRepoName := f.RepoName
|
||||
// Check if repository name has been changed.
|
||||
if repo.LowerName != strings.ToLower(newRepoName) {
|
||||
isNameChanged = true
|
||||
if err := db.ChangeRepositoryName(c.Repo.Owner, repo.Name, newRepoName); err != nil {
|
||||
c.FormErr("RepoName")
|
||||
switch {
|
||||
case db.IsErrRepoAlreadyExist(err):
|
||||
c.RenderWithErr(c.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, &f)
|
||||
case db.IsErrNameReserved(err):
|
||||
c.RenderWithErr(c.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), SETTINGS_OPTIONS, &f)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
c.RenderWithErr(c.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), SETTINGS_OPTIONS, &f)
|
||||
default:
|
||||
c.ServerError("ChangeRepositoryName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository name changed: %s/%s -> %s", c.Repo.Owner.Name, repo.Name, newRepoName)
|
||||
}
|
||||
// In case it's just a case change.
|
||||
repo.Name = newRepoName
|
||||
repo.LowerName = strings.ToLower(newRepoName)
|
||||
|
||||
repo.Description = f.Description
|
||||
repo.Website = f.Website
|
||||
|
||||
// Visibility of forked repository is forced sync with base repository.
|
||||
if repo.IsFork {
|
||||
f.Private = repo.BaseRepo.IsPrivate
|
||||
}
|
||||
|
||||
visibilityChanged := repo.IsPrivate != f.Private
|
||||
repo.IsPrivate = f.Private
|
||||
if err := db.UpdateRepository(repo, visibilityChanged); err != nil {
|
||||
c.ServerError("UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Repository basic settings updated: %s/%s", c.Repo.Owner.Name, repo.Name)
|
||||
|
||||
if isNameChanged {
|
||||
if err := db.RenameRepoAction(c.User, oldRepoName, repo); err != nil {
|
||||
log.Error(2, "RenameRepoAction: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_settings_success"))
|
||||
c.Redirect(repo.Link() + "/settings")
|
||||
|
||||
case "mirror":
|
||||
if !repo.IsMirror {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if f.Interval > 0 {
|
||||
c.Repo.Mirror.EnablePrune = f.EnablePrune
|
||||
c.Repo.Mirror.Interval = f.Interval
|
||||
c.Repo.Mirror.NextSync = time.Now().Add(time.Duration(f.Interval) * time.Hour)
|
||||
if err := db.UpdateMirror(c.Repo.Mirror); err != nil {
|
||||
c.ServerError("UpdateMirror", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := c.Repo.Mirror.SaveAddress(f.MirrorAddress); err != nil {
|
||||
c.ServerError("SaveAddress", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_settings_success"))
|
||||
c.Redirect(repo.Link() + "/settings")
|
||||
|
||||
case "mirror-sync":
|
||||
if !repo.IsMirror {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
go db.MirrorQueue.Add(repo.ID)
|
||||
c.Flash.Info(c.Tr("repo.settings.mirror_sync_in_progress"))
|
||||
c.Redirect(repo.Link() + "/settings")
|
||||
|
||||
case "advanced":
|
||||
repo.EnableWiki = f.EnableWiki
|
||||
repo.AllowPublicWiki = f.AllowPublicWiki
|
||||
repo.EnableExternalWiki = f.EnableExternalWiki
|
||||
repo.ExternalWikiURL = f.ExternalWikiURL
|
||||
repo.EnableIssues = f.EnableIssues
|
||||
repo.AllowPublicIssues = f.AllowPublicIssues
|
||||
repo.EnableExternalTracker = f.EnableExternalTracker
|
||||
repo.ExternalTrackerURL = f.ExternalTrackerURL
|
||||
repo.ExternalTrackerFormat = f.TrackerURLFormat
|
||||
repo.ExternalTrackerStyle = f.TrackerIssueStyle
|
||||
repo.EnablePulls = f.EnablePulls
|
||||
repo.PullsIgnoreWhitespace = f.PullsIgnoreWhitespace
|
||||
repo.PullsAllowRebase = f.PullsAllowRebase
|
||||
|
||||
if err := db.UpdateRepository(repo, false); err != nil {
|
||||
c.ServerError("UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Repository advanced settings updated: %s/%s", c.Repo.Owner.Name, repo.Name)
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_settings_success"))
|
||||
c.Redirect(c.Repo.RepoLink + "/settings")
|
||||
|
||||
case "convert":
|
||||
if !c.Repo.IsOwner() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
if repo.Name != f.RepoName {
|
||||
c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if c.Repo.Owner.IsOrganization() {
|
||||
if !c.Repo.Owner.IsOwnedBy(c.User.ID) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !repo.IsMirror {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
repo.IsMirror = false
|
||||
|
||||
if _, err := db.CleanUpMigrateInfo(repo); err != nil {
|
||||
c.ServerError("CleanUpMigrateInfo", err)
|
||||
return
|
||||
} else if err = db.DeleteMirrorByRepoID(c.Repo.Repository.ID); err != nil {
|
||||
c.ServerError("DeleteMirrorByRepoID", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Repository converted from mirror to regular: %s/%s", c.Repo.Owner.Name, repo.Name)
|
||||
c.Flash.Success(c.Tr("repo.settings.convert_succeed"))
|
||||
c.Redirect(setting.AppSubURL + "/" + c.Repo.Owner.Name + "/" + repo.Name)
|
||||
|
||||
case "transfer":
|
||||
if !c.Repo.IsOwner() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
if repo.Name != f.RepoName {
|
||||
c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin {
|
||||
if !c.Repo.Owner.IsOwnedBy(c.User.ID) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newOwner := c.Query("new_owner_name")
|
||||
isExist, err := db.IsUserExist(0, newOwner)
|
||||
if err != nil {
|
||||
c.ServerError("IsUserExist", err)
|
||||
return
|
||||
} else if !isExist {
|
||||
c.RenderWithErr(c.Tr("form.enterred_invalid_owner_name"), SETTINGS_OPTIONS, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err = db.TransferOwnership(c.User, newOwner, repo); err != nil {
|
||||
if db.IsErrRepoAlreadyExist(err) {
|
||||
c.RenderWithErr(c.Tr("repo.settings.new_owner_has_same_repo"), SETTINGS_OPTIONS, nil)
|
||||
} else {
|
||||
c.ServerError("TransferOwnership", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Repository transfered: %s/%s -> %s", c.Repo.Owner.Name, repo.Name, newOwner)
|
||||
c.Flash.Success(c.Tr("repo.settings.transfer_succeed"))
|
||||
c.Redirect(setting.AppSubURL + "/" + newOwner + "/" + repo.Name)
|
||||
|
||||
case "delete":
|
||||
if !c.Repo.IsOwner() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
if repo.Name != f.RepoName {
|
||||
c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin {
|
||||
if !c.Repo.Owner.IsOwnedBy(c.User.ID) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.DeleteRepository(c.Repo.Owner.ID, repo.ID); err != nil {
|
||||
c.ServerError("DeleteRepository", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Repository deleted: %s/%s", c.Repo.Owner.Name, repo.Name)
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.deletion_success"))
|
||||
c.Redirect(c.Repo.Owner.DashboardLink())
|
||||
|
||||
case "delete-wiki":
|
||||
if !c.Repo.IsOwner() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
if repo.Name != f.RepoName {
|
||||
c.RenderWithErr(c.Tr("form.enterred_invalid_repo_name"), SETTINGS_OPTIONS, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if c.Repo.Owner.IsOrganization() && !c.User.IsAdmin {
|
||||
if !c.Repo.Owner.IsOwnedBy(c.User.ID) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
repo.DeleteWiki()
|
||||
log.Trace("Repository wiki deleted: %s/%s", c.Repo.Owner.Name, repo.Name)
|
||||
|
||||
repo.EnableWiki = false
|
||||
if err := db.UpdateRepository(repo, false); err != nil {
|
||||
c.ServerError("UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.wiki_deletion_success"))
|
||||
c.Redirect(c.Repo.RepoLink + "/settings")
|
||||
|
||||
default:
|
||||
c.NotFound()
|
||||
}
|
||||
}
|
||||
|
||||
func SettingsAvatar(c *context.Context) {
|
||||
c.Title("settings.avatar")
|
||||
c.PageIs("SettingsAvatar")
|
||||
c.Success(SETTINGS_REPO_AVATAR)
|
||||
}
|
||||
|
||||
func SettingsAvatarPost(c *context.Context, f form.Avatar) {
|
||||
f.Source = form.AVATAR_LOCAL
|
||||
if err := UpdateAvatarSetting(c, f, c.Repo.Repository); err != nil {
|
||||
c.Flash.Error(err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("settings.update_avatar_success"))
|
||||
}
|
||||
c.SubURLRedirect(c.Repo.RepoLink + "/settings")
|
||||
}
|
||||
|
||||
func SettingsDeleteAvatar(c *context.Context) {
|
||||
if err := c.Repo.Repository.DeleteAvatar(); err != nil {
|
||||
c.Flash.Error(fmt.Sprintf("Failed to delete avatar: %v", err))
|
||||
}
|
||||
c.SubURLRedirect(c.Repo.RepoLink + "/settings")
|
||||
}
|
||||
|
||||
// FIXME: limit upload size
|
||||
func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxRepo *db.Repository) error {
|
||||
ctxRepo.UseCustomAvatar = true
|
||||
if f.Avatar != nil {
|
||||
r, err := f.Avatar.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("open avatar reader: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read avatar content: %v", err)
|
||||
}
|
||||
if !tool.IsImageFile(data) {
|
||||
return errors.New(c.Tr("settings.uploaded_avatar_not_a_image"))
|
||||
}
|
||||
if err = ctxRepo.UploadAvatar(data); err != nil {
|
||||
return fmt.Errorf("upload avatar: %v", err)
|
||||
}
|
||||
} else {
|
||||
// No avatar is uploaded and reset setting back.
|
||||
if !com.IsFile(ctxRepo.CustomAvatarPath()) {
|
||||
ctxRepo.UseCustomAvatar = false
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.UpdateRepository(ctxRepo, false); err != nil {
|
||||
return fmt.Errorf("update repository: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SettingsCollaboration(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings")
|
||||
c.Data["PageIsSettingsCollaboration"] = true
|
||||
|
||||
users, err := c.Repo.Repository.GetCollaborators()
|
||||
if err != nil {
|
||||
c.Handle(500, "GetCollaborators", err)
|
||||
return
|
||||
}
|
||||
c.Data["Collaborators"] = users
|
||||
|
||||
c.HTML(200, SETTINGS_COLLABORATION)
|
||||
}
|
||||
|
||||
func SettingsCollaborationPost(c *context.Context) {
|
||||
name := strings.ToLower(c.Query("collaborator"))
|
||||
if len(name) == 0 || c.Repo.Owner.LowerName == name {
|
||||
c.Redirect(setting.AppSubURL + c.Req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := db.GetUserByName(name)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Flash.Error(c.Tr("form.user_not_exist"))
|
||||
c.Redirect(setting.AppSubURL + c.Req.URL.Path)
|
||||
} else {
|
||||
c.Handle(500, "GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Organization is not allowed to be added as a collaborator
|
||||
if u.IsOrganization() {
|
||||
c.Flash.Error(c.Tr("repo.settings.org_not_allowed_to_be_collaborator"))
|
||||
c.Redirect(setting.AppSubURL + c.Req.URL.Path)
|
||||
return
|
||||
}
|
||||
|
||||
if err = c.Repo.Repository.AddCollaborator(u); err != nil {
|
||||
c.Handle(500, "AddCollaborator", err)
|
||||
return
|
||||
}
|
||||
|
||||
if setting.Service.EnableNotifyMail {
|
||||
mailer.SendCollaboratorMail(db.NewMailerUser(u), db.NewMailerUser(c.User), db.NewMailerRepo(c.Repo.Repository))
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.add_collaborator_success"))
|
||||
c.Redirect(setting.AppSubURL + c.Req.URL.Path)
|
||||
}
|
||||
|
||||
func ChangeCollaborationAccessMode(c *context.Context) {
|
||||
if err := c.Repo.Repository.ChangeCollaborationAccessMode(
|
||||
c.QueryInt64("uid"),
|
||||
db.AccessMode(c.QueryInt("mode"))); err != nil {
|
||||
log.Error(2, "ChangeCollaborationAccessMode: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(204)
|
||||
}
|
||||
|
||||
func DeleteCollaboration(c *context.Context) {
|
||||
if err := c.Repo.Repository.DeleteCollaboration(c.QueryInt64("id")); err != nil {
|
||||
c.Flash.Error("DeleteCollaboration: " + err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("repo.settings.remove_collaborator_success"))
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": c.Repo.RepoLink + "/settings/collaboration",
|
||||
})
|
||||
}
|
||||
|
||||
func SettingsBranches(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.branches")
|
||||
c.Data["PageIsSettingsBranches"] = true
|
||||
|
||||
if c.Repo.Repository.IsBare {
|
||||
c.Flash.Info(c.Tr("repo.settings.branches_bare"), true)
|
||||
c.HTML(200, SETTINGS_BRANCHES)
|
||||
return
|
||||
}
|
||||
|
||||
protectBranches, err := db.GetProtectBranchesByRepoID(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetProtectBranchesByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Filter out deleted branches
|
||||
branches := make([]string, 0, len(protectBranches))
|
||||
for i := range protectBranches {
|
||||
if c.Repo.GitRepo.IsBranchExist(protectBranches[i].Name) {
|
||||
branches = append(branches, protectBranches[i].Name)
|
||||
}
|
||||
}
|
||||
c.Data["ProtectBranches"] = branches
|
||||
|
||||
c.HTML(200, SETTINGS_BRANCHES)
|
||||
}
|
||||
|
||||
func UpdateDefaultBranch(c *context.Context) {
|
||||
branch := c.Query("branch")
|
||||
if c.Repo.GitRepo.IsBranchExist(branch) &&
|
||||
c.Repo.Repository.DefaultBranch != branch {
|
||||
c.Repo.Repository.DefaultBranch = branch
|
||||
if err := c.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
|
||||
if !git.IsErrUnsupportedVersion(err) {
|
||||
c.Handle(500, "SetDefaultBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Warning(c.Tr("repo.settings.update_default_branch_unsupported"))
|
||||
c.Redirect(c.Repo.RepoLink + "/settings/branches")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.UpdateRepository(c.Repo.Repository, false); err != nil {
|
||||
c.Handle(500, "UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_default_branch_success"))
|
||||
c.Redirect(c.Repo.RepoLink + "/settings/branches")
|
||||
}
|
||||
|
||||
func SettingsProtectedBranch(c *context.Context) {
|
||||
branch := c.Params("*")
|
||||
if !c.Repo.GitRepo.IsBranchExist(branch) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["Title"] = c.Tr("repo.settings.protected_branches") + " - " + branch
|
||||
c.Data["PageIsSettingsBranches"] = true
|
||||
|
||||
protectBranch, err := db.GetProtectBranchOfRepoByName(c.Repo.Repository.ID, branch)
|
||||
if err != nil {
|
||||
if !errors.IsErrBranchNotExist(err) {
|
||||
c.Handle(500, "GetProtectBranchOfRepoByName", err)
|
||||
return
|
||||
}
|
||||
|
||||
// No options found, create defaults.
|
||||
protectBranch = &db.ProtectBranch{
|
||||
Name: branch,
|
||||
}
|
||||
}
|
||||
|
||||
if c.Repo.Owner.IsOrganization() {
|
||||
users, err := c.Repo.Repository.GetWriters()
|
||||
if err != nil {
|
||||
c.Handle(500, "Repo.Repository.GetPushers", err)
|
||||
return
|
||||
}
|
||||
c.Data["Users"] = users
|
||||
c.Data["whitelist_users"] = protectBranch.WhitelistUserIDs
|
||||
|
||||
teams, err := c.Repo.Owner.TeamsHaveAccessToRepo(c.Repo.Repository.ID, db.ACCESS_MODE_WRITE)
|
||||
if err != nil {
|
||||
c.Handle(500, "Repo.Owner.TeamsHaveAccessToRepo", err)
|
||||
return
|
||||
}
|
||||
c.Data["Teams"] = teams
|
||||
c.Data["whitelist_teams"] = protectBranch.WhitelistTeamIDs
|
||||
}
|
||||
|
||||
c.Data["Branch"] = protectBranch
|
||||
c.HTML(200, SETTINGS_PROTECTED_BRANCH)
|
||||
}
|
||||
|
||||
func SettingsProtectedBranchPost(c *context.Context, f form.ProtectBranch) {
|
||||
branch := c.Params("*")
|
||||
if !c.Repo.GitRepo.IsBranchExist(branch) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
protectBranch, err := db.GetProtectBranchOfRepoByName(c.Repo.Repository.ID, branch)
|
||||
if err != nil {
|
||||
if !errors.IsErrBranchNotExist(err) {
|
||||
c.Handle(500, "GetProtectBranchOfRepoByName", err)
|
||||
return
|
||||
}
|
||||
|
||||
// No options found, create defaults.
|
||||
protectBranch = &db.ProtectBranch{
|
||||
RepoID: c.Repo.Repository.ID,
|
||||
Name: branch,
|
||||
}
|
||||
}
|
||||
|
||||
protectBranch.Protected = f.Protected
|
||||
protectBranch.RequirePullRequest = f.RequirePullRequest
|
||||
protectBranch.EnableWhitelist = f.EnableWhitelist
|
||||
if c.Repo.Owner.IsOrganization() {
|
||||
err = db.UpdateOrgProtectBranch(c.Repo.Repository, protectBranch, f.WhitelistUsers, f.WhitelistTeams)
|
||||
} else {
|
||||
err = db.UpdateProtectBranch(protectBranch)
|
||||
}
|
||||
if err != nil {
|
||||
c.Handle(500, "UpdateOrgProtectBranch/UpdateProtectBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_protect_branch_success"))
|
||||
c.Redirect(fmt.Sprintf("%s/settings/branches/%s", c.Repo.RepoLink, branch))
|
||||
}
|
||||
|
||||
func SettingsGitHooks(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.githooks")
|
||||
c.Data["PageIsSettingsGitHooks"] = true
|
||||
|
||||
hooks, err := c.Repo.GitRepo.Hooks()
|
||||
if err != nil {
|
||||
c.Handle(500, "Hooks", err)
|
||||
return
|
||||
}
|
||||
c.Data["Hooks"] = hooks
|
||||
|
||||
c.HTML(200, SETTINGS_GITHOOKS)
|
||||
}
|
||||
|
||||
func SettingsGitHooksEdit(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.githooks")
|
||||
c.Data["PageIsSettingsGitHooks"] = true
|
||||
c.Data["RequireSimpleMDE"] = true
|
||||
|
||||
name := c.Params(":name")
|
||||
hook, err := c.Repo.GitRepo.GetHook(name)
|
||||
if err != nil {
|
||||
if err == git.ErrNotValidHook {
|
||||
c.Handle(404, "GetHook", err)
|
||||
} else {
|
||||
c.Handle(500, "GetHook", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.Data["Hook"] = hook
|
||||
c.HTML(200, SETTINGS_GITHOOK_EDIT)
|
||||
}
|
||||
|
||||
func SettingsGitHooksEditPost(c *context.Context) {
|
||||
name := c.Params(":name")
|
||||
hook, err := c.Repo.GitRepo.GetHook(name)
|
||||
if err != nil {
|
||||
if err == git.ErrNotValidHook {
|
||||
c.Handle(404, "GetHook", err)
|
||||
} else {
|
||||
c.Handle(500, "GetHook", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
hook.Content = c.Query("content")
|
||||
if err = hook.Update(); err != nil {
|
||||
c.Handle(500, "hook.Update", err)
|
||||
return
|
||||
}
|
||||
c.Redirect(c.Data["Link"].(string))
|
||||
}
|
||||
|
||||
func SettingsDeployKeys(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.deploy_keys")
|
||||
c.Data["PageIsSettingsKeys"] = true
|
||||
|
||||
keys, err := db.ListDeployKeys(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.Handle(500, "ListDeployKeys", err)
|
||||
return
|
||||
}
|
||||
c.Data["Deploykeys"] = keys
|
||||
|
||||
c.HTML(200, SETTINGS_DEPLOY_KEYS)
|
||||
}
|
||||
|
||||
func SettingsDeployKeysPost(c *context.Context, f form.AddSSHKey) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.deploy_keys")
|
||||
c.Data["PageIsSettingsKeys"] = true
|
||||
|
||||
keys, err := db.ListDeployKeys(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.Handle(500, "ListDeployKeys", err)
|
||||
return
|
||||
}
|
||||
c.Data["Deploykeys"] = keys
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, SETTINGS_DEPLOY_KEYS)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := db.CheckPublicKeyString(f.Content)
|
||||
if err != nil {
|
||||
if db.IsErrKeyUnableVerify(err) {
|
||||
c.Flash.Info(c.Tr("form.unable_verify_ssh_key"))
|
||||
} else {
|
||||
c.Data["HasError"] = true
|
||||
c.Data["Err_Content"] = true
|
||||
c.Flash.Error(c.Tr("form.invalid_ssh_key", err.Error()))
|
||||
c.Redirect(c.Repo.RepoLink + "/settings/keys")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
key, err := db.AddDeployKey(c.Repo.Repository.ID, f.Title, content)
|
||||
if err != nil {
|
||||
c.Data["HasError"] = true
|
||||
switch {
|
||||
case db.IsErrKeyAlreadyExist(err):
|
||||
c.Data["Err_Content"] = true
|
||||
c.RenderWithErr(c.Tr("repo.settings.key_been_used"), SETTINGS_DEPLOY_KEYS, &f)
|
||||
case db.IsErrKeyNameAlreadyUsed(err):
|
||||
c.Data["Err_Title"] = true
|
||||
c.RenderWithErr(c.Tr("repo.settings.key_name_used"), SETTINGS_DEPLOY_KEYS, &f)
|
||||
default:
|
||||
c.Handle(500, "AddDeployKey", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Deploy key added: %d", c.Repo.Repository.ID)
|
||||
c.Flash.Success(c.Tr("repo.settings.add_key_success", key.Name))
|
||||
c.Redirect(c.Repo.RepoLink + "/settings/keys")
|
||||
}
|
||||
|
||||
func DeleteDeployKey(c *context.Context) {
|
||||
if err := db.DeleteDeployKey(c.User, c.QueryInt64("id")); err != nil {
|
||||
c.Flash.Error("DeleteDeployKey: " + err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("repo.settings.deploy_key_deletion_success"))
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": c.Repo.RepoLink + "/settings/keys",
|
||||
})
|
||||
}
|
||||
371
internal/route/repo/view.go
Normal file
371
internal/route/repo/view.go
Normal file
@@ -0,0 +1,371 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
gotemplate "html/template"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/unknwon/paginater"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/markup"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/template"
|
||||
"gogs.io/gogs/internal/template/highlight"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
BARE = "repo/bare"
|
||||
HOME = "repo/home"
|
||||
WATCHERS = "repo/watchers"
|
||||
FORKS = "repo/forks"
|
||||
)
|
||||
|
||||
func renderDirectory(c *context.Context, treeLink string) {
|
||||
tree, err := c.Repo.Commit.SubTree(c.Repo.TreePath)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := tree.ListEntries()
|
||||
if err != nil {
|
||||
c.ServerError("ListEntries", err)
|
||||
return
|
||||
}
|
||||
entries.Sort()
|
||||
|
||||
c.Data["Files"], err = entries.GetCommitsInfoWithCustomConcurrency(c.Repo.Commit, c.Repo.TreePath, setting.Repository.CommitsFetchConcurrency)
|
||||
if err != nil {
|
||||
c.ServerError("GetCommitsInfoWithCustomConcurrency", err)
|
||||
return
|
||||
}
|
||||
|
||||
var readmeFile *git.Blob
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !markup.IsReadmeFile(entry.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: collect all possible README files and show with priority.
|
||||
readmeFile = entry.Blob()
|
||||
break
|
||||
}
|
||||
|
||||
if readmeFile != nil {
|
||||
c.Data["RawFileLink"] = ""
|
||||
c.Data["ReadmeInList"] = true
|
||||
c.Data["ReadmeExist"] = true
|
||||
|
||||
dataRc, err := readmeFile.Data()
|
||||
if err != nil {
|
||||
c.ServerError("readmeFile.Data", err)
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := dataRc.Read(buf)
|
||||
buf = buf[:n]
|
||||
|
||||
isTextFile := tool.IsTextFile(buf)
|
||||
c.Data["IsTextFile"] = isTextFile
|
||||
c.Data["FileName"] = readmeFile.Name()
|
||||
if isTextFile {
|
||||
d, _ := ioutil.ReadAll(dataRc)
|
||||
buf = append(buf, d...)
|
||||
|
||||
switch markup.Detect(readmeFile.Name()) {
|
||||
case markup.MARKDOWN:
|
||||
c.Data["IsMarkdown"] = true
|
||||
buf = markup.Markdown(buf, treeLink, c.Repo.Repository.ComposeMetas())
|
||||
case markup.ORG_MODE:
|
||||
c.Data["IsMarkdown"] = true
|
||||
buf = markup.OrgMode(buf, treeLink, c.Repo.Repository.ComposeMetas())
|
||||
case markup.IPYTHON_NOTEBOOK:
|
||||
c.Data["IsIPythonNotebook"] = true
|
||||
c.Data["RawFileLink"] = c.Repo.RepoLink + "/raw/" + path.Join(c.Repo.BranchName, c.Repo.TreePath, readmeFile.Name())
|
||||
default:
|
||||
buf = bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)
|
||||
}
|
||||
c.Data["FileContent"] = string(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// Show latest commit info of repository in table header,
|
||||
// or of directory if not in root directory.
|
||||
latestCommit := c.Repo.Commit
|
||||
if len(c.Repo.TreePath) > 0 {
|
||||
latestCommit, err = c.Repo.Commit.GetCommitByPath(c.Repo.TreePath)
|
||||
if err != nil {
|
||||
c.ServerError("GetCommitByPath", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["LatestCommit"] = latestCommit
|
||||
c.Data["LatestCommitUser"] = db.ValidateCommitWithEmail(latestCommit)
|
||||
|
||||
if c.Repo.CanEnableEditor() {
|
||||
c.Data["CanAddFile"] = true
|
||||
c.Data["CanUploadFile"] = setting.Repository.Upload.Enabled
|
||||
}
|
||||
}
|
||||
|
||||
func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
|
||||
c.Data["IsViewFile"] = true
|
||||
|
||||
blob := entry.Blob()
|
||||
dataRc, err := blob.Data()
|
||||
if err != nil {
|
||||
c.Handle(500, "Data", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["FileSize"] = blob.Size()
|
||||
c.Data["FileName"] = blob.Name()
|
||||
c.Data["HighlightClass"] = highlight.FileNameToHighlightClass(blob.Name())
|
||||
c.Data["RawFileLink"] = rawLink + "/" + c.Repo.TreePath
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := dataRc.Read(buf)
|
||||
buf = buf[:n]
|
||||
|
||||
isTextFile := tool.IsTextFile(buf)
|
||||
c.Data["IsTextFile"] = isTextFile
|
||||
|
||||
// Assume file is not editable first.
|
||||
if !isTextFile {
|
||||
c.Data["EditFileTooltip"] = c.Tr("repo.editor.cannot_edit_non_text_files")
|
||||
}
|
||||
|
||||
canEnableEditor := c.Repo.CanEnableEditor()
|
||||
switch {
|
||||
case isTextFile:
|
||||
if blob.Size() >= setting.UI.MaxDisplayFileSize {
|
||||
c.Data["IsFileTooLarge"] = true
|
||||
break
|
||||
}
|
||||
|
||||
c.Data["ReadmeExist"] = markup.IsReadmeFile(blob.Name())
|
||||
|
||||
d, _ := ioutil.ReadAll(dataRc)
|
||||
buf = append(buf, d...)
|
||||
|
||||
switch markup.Detect(blob.Name()) {
|
||||
case markup.MARKDOWN:
|
||||
c.Data["IsMarkdown"] = true
|
||||
c.Data["FileContent"] = string(markup.Markdown(buf, path.Dir(treeLink), c.Repo.Repository.ComposeMetas()))
|
||||
case markup.ORG_MODE:
|
||||
c.Data["IsMarkdown"] = true
|
||||
c.Data["FileContent"] = string(markup.OrgMode(buf, path.Dir(treeLink), c.Repo.Repository.ComposeMetas()))
|
||||
case markup.IPYTHON_NOTEBOOK:
|
||||
c.Data["IsIPythonNotebook"] = true
|
||||
default:
|
||||
// Building code view blocks with line number on server side.
|
||||
var fileContent string
|
||||
if err, content := template.ToUTF8WithErr(buf); err != nil {
|
||||
if err != nil {
|
||||
log.Error(4, "ToUTF8WithErr: %s", err)
|
||||
}
|
||||
fileContent = string(buf)
|
||||
} else {
|
||||
fileContent = content
|
||||
}
|
||||
|
||||
var output bytes.Buffer
|
||||
lines := strings.Split(fileContent, "\n")
|
||||
// Remove blank line at the end of file
|
||||
if len(lines) > 0 && len(lines[len(lines)-1])==0 {
|
||||
lines = lines[:len(lines)-1]
|
||||
}
|
||||
for index, line := range lines {
|
||||
output.WriteString(fmt.Sprintf(`<li class="L%d" rel="L%d">%s</li>`, index+1, index+1, gotemplate.HTMLEscapeString(strings.TrimRight(line, "\r"))) + "\n")
|
||||
}
|
||||
c.Data["FileContent"] = gotemplate.HTML(output.String())
|
||||
|
||||
output.Reset()
|
||||
for i := 0; i < len(lines); i++ {
|
||||
output.WriteString(fmt.Sprintf(`<span id="L%d">%d</span>`, i+1, i+1))
|
||||
}
|
||||
c.Data["LineNums"] = gotemplate.HTML(output.String())
|
||||
}
|
||||
|
||||
if canEnableEditor {
|
||||
c.Data["CanEditFile"] = true
|
||||
c.Data["EditFileTooltip"] = c.Tr("repo.editor.edit_this_file")
|
||||
} else if !c.Repo.IsViewBranch {
|
||||
c.Data["EditFileTooltip"] = c.Tr("repo.editor.must_be_on_a_branch")
|
||||
} else if !c.Repo.IsWriter() {
|
||||
c.Data["EditFileTooltip"] = c.Tr("repo.editor.fork_before_edit")
|
||||
}
|
||||
|
||||
case tool.IsPDFFile(buf):
|
||||
c.Data["IsPDFFile"] = true
|
||||
case tool.IsVideoFile(buf):
|
||||
c.Data["IsVideoFile"] = true
|
||||
case tool.IsImageFile(buf):
|
||||
c.Data["IsImageFile"] = true
|
||||
}
|
||||
|
||||
if canEnableEditor {
|
||||
c.Data["CanDeleteFile"] = true
|
||||
c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.delete_this_file")
|
||||
} else if !c.Repo.IsViewBranch {
|
||||
c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.must_be_on_a_branch")
|
||||
} else if !c.Repo.IsWriter() {
|
||||
c.Data["DeleteFileTooltip"] = c.Tr("repo.editor.must_have_write_access")
|
||||
}
|
||||
}
|
||||
|
||||
func setEditorconfigIfExists(c *context.Context) {
|
||||
ec, err := c.Repo.GetEditorconfig()
|
||||
if err != nil && !git.IsErrNotExist(err) {
|
||||
log.Trace("setEditorconfigIfExists.GetEditorconfig [%d]: %v", c.Repo.Repository.ID, err)
|
||||
return
|
||||
}
|
||||
c.Data["Editorconfig"] = ec
|
||||
}
|
||||
|
||||
func Home(c *context.Context) {
|
||||
c.Data["PageIsViewFiles"] = true
|
||||
|
||||
if c.Repo.Repository.IsBare {
|
||||
c.HTML(200, BARE)
|
||||
return
|
||||
}
|
||||
|
||||
title := c.Repo.Repository.Owner.Name + "/" + c.Repo.Repository.Name
|
||||
if len(c.Repo.Repository.Description) > 0 {
|
||||
title += ": " + c.Repo.Repository.Description
|
||||
}
|
||||
c.Data["Title"] = title
|
||||
if c.Repo.BranchName != c.Repo.Repository.DefaultBranch {
|
||||
c.Data["Title"] = title + " @ " + c.Repo.BranchName
|
||||
}
|
||||
c.Data["RequireHighlightJS"] = true
|
||||
|
||||
branchLink := c.Repo.RepoLink + "/src/" + c.Repo.BranchName
|
||||
treeLink := branchLink
|
||||
rawLink := c.Repo.RepoLink + "/raw/" + c.Repo.BranchName
|
||||
|
||||
isRootDir := false
|
||||
if len(c.Repo.TreePath) > 0 {
|
||||
treeLink += "/" + c.Repo.TreePath
|
||||
} else {
|
||||
isRootDir = true
|
||||
|
||||
// Only show Git stats panel when view root directory
|
||||
var err error
|
||||
c.Repo.CommitsCount, err = c.Repo.Commit.CommitsCount()
|
||||
if err != nil {
|
||||
c.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
c.Data["CommitsCount"] = c.Repo.CommitsCount
|
||||
}
|
||||
c.Data["PageIsRepoHome"] = isRootDir
|
||||
|
||||
// Get current entry user currently looking at.
|
||||
entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if entry.IsDir() {
|
||||
renderDirectory(c, treeLink)
|
||||
} else {
|
||||
renderFile(c, entry, treeLink, rawLink)
|
||||
}
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
setEditorconfigIfExists(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
var treeNames []string
|
||||
paths := make([]string, 0, 5)
|
||||
if len(c.Repo.TreePath) > 0 {
|
||||
treeNames = strings.Split(c.Repo.TreePath, "/")
|
||||
for i := range treeNames {
|
||||
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
|
||||
}
|
||||
|
||||
c.Data["HasParentPath"] = true
|
||||
if len(paths)-2 >= 0 {
|
||||
c.Data["ParentPath"] = "/" + paths[len(paths)-2]
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["Paths"] = paths
|
||||
c.Data["TreeLink"] = treeLink
|
||||
c.Data["TreeNames"] = treeNames
|
||||
c.Data["BranchLink"] = branchLink
|
||||
c.HTML(200, HOME)
|
||||
}
|
||||
|
||||
func RenderUserCards(c *context.Context, total int, getter func(page int) ([]*db.User, error), tpl string) {
|
||||
page := c.QueryInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
pager := paginater.New(total, db.ItemsPerPage, page, 5)
|
||||
c.Data["Page"] = pager
|
||||
|
||||
items, err := getter(pager.Current())
|
||||
if err != nil {
|
||||
c.Handle(500, "getter", err)
|
||||
return
|
||||
}
|
||||
c.Data["Cards"] = items
|
||||
|
||||
c.HTML(200, tpl)
|
||||
}
|
||||
|
||||
func Watchers(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.watchers")
|
||||
c.Data["CardsTitle"] = c.Tr("repo.watchers")
|
||||
c.Data["PageIsWatchers"] = true
|
||||
RenderUserCards(c, c.Repo.Repository.NumWatches, c.Repo.Repository.GetWatchers, WATCHERS)
|
||||
}
|
||||
|
||||
func Stars(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.stargazers")
|
||||
c.Data["CardsTitle"] = c.Tr("repo.stargazers")
|
||||
c.Data["PageIsStargazers"] = true
|
||||
RenderUserCards(c, c.Repo.Repository.NumStars, c.Repo.Repository.GetStargazers, WATCHERS)
|
||||
}
|
||||
|
||||
func Forks(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repos.forks")
|
||||
|
||||
forks, err := c.Repo.Repository.GetForks()
|
||||
if err != nil {
|
||||
c.Handle(500, "GetForks", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, fork := range forks {
|
||||
if err = fork.GetOwner(); err != nil {
|
||||
c.Handle(500, "GetOwner", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["Forks"] = forks
|
||||
|
||||
c.HTML(200, FORKS)
|
||||
}
|
||||
629
internal/route/repo/webhook.go
Normal file
629
internal/route/repo/webhook.go
Normal file
@@ -0,0 +1,629 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/json-iterator/go"
|
||||
"github.com/unknwon/com"
|
||||
|
||||
git "github.com/gogs/git-module"
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
WEBHOOKS = "repo/settings/webhook/base"
|
||||
WEBHOOK_NEW = "repo/settings/webhook/new"
|
||||
ORG_WEBHOOK_NEW = "org/settings/webhook_new"
|
||||
)
|
||||
|
||||
func Webhooks(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.hooks")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["BaseLink"] = c.Repo.RepoLink
|
||||
c.Data["Description"] = c.Tr("repo.settings.hooks_desc", "https://github.com/gogs/go-gogs-client/wiki/Repositories-Webhooks")
|
||||
c.Data["Types"] = setting.Webhook.Types
|
||||
|
||||
ws, err := db.GetWebhooksByRepoID(c.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetWebhooksByRepoID", err)
|
||||
return
|
||||
}
|
||||
c.Data["Webhooks"] = ws
|
||||
|
||||
c.HTML(200, WEBHOOKS)
|
||||
}
|
||||
|
||||
type OrgRepoCtx struct {
|
||||
OrgID int64
|
||||
RepoID int64
|
||||
Link string
|
||||
NewTemplate string
|
||||
}
|
||||
|
||||
// getOrgRepoCtx determines whether this is a repo context or organization context.
|
||||
func getOrgRepoCtx(c *context.Context) (*OrgRepoCtx, error) {
|
||||
if len(c.Repo.RepoLink) > 0 {
|
||||
c.Data["PageIsRepositoryContext"] = true
|
||||
return &OrgRepoCtx{
|
||||
RepoID: c.Repo.Repository.ID,
|
||||
Link: c.Repo.RepoLink,
|
||||
NewTemplate: WEBHOOK_NEW,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(c.Org.OrgLink) > 0 {
|
||||
c.Data["PageIsOrganizationContext"] = true
|
||||
return &OrgRepoCtx{
|
||||
OrgID: c.Org.Organization.ID,
|
||||
Link: c.Org.OrgLink,
|
||||
NewTemplate: ORG_WEBHOOK_NEW,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("Unable to set OrgRepo context")
|
||||
}
|
||||
|
||||
func checkHookType(c *context.Context) string {
|
||||
hookType := strings.ToLower(c.Params(":type"))
|
||||
if !com.IsSliceContainsStr(setting.Webhook.Types, hookType) {
|
||||
c.Handle(404, "checkHookType", nil)
|
||||
return ""
|
||||
}
|
||||
return hookType
|
||||
}
|
||||
|
||||
func WebhooksNew(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.add_webhook")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksNew"] = true
|
||||
c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}}
|
||||
|
||||
orCtx, err := getOrgRepoCtx(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "getOrgRepoCtx", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["HookType"] = checkHookType(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["BaseLink"] = orCtx.Link
|
||||
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
}
|
||||
|
||||
func ParseHookEvent(f form.Webhook) *db.HookEvent {
|
||||
return &db.HookEvent{
|
||||
PushOnly: f.PushOnly(),
|
||||
SendEverything: f.SendEverything(),
|
||||
ChooseEvents: f.ChooseEvents(),
|
||||
HookEvents: db.HookEvents{
|
||||
Create: f.Create,
|
||||
Delete: f.Delete,
|
||||
Fork: f.Fork,
|
||||
Push: f.Push,
|
||||
Issues: f.Issues,
|
||||
IssueComment: f.IssueComment,
|
||||
PullRequest: f.PullRequest,
|
||||
Release: f.Release,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func WebHooksNewPost(c *context.Context, f form.NewWebhook) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.add_webhook")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksNew"] = true
|
||||
c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}}
|
||||
c.Data["HookType"] = "gogs"
|
||||
|
||||
orCtx, err := getOrgRepoCtx(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "getOrgRepoCtx", err)
|
||||
return
|
||||
}
|
||||
c.Data["BaseLink"] = orCtx.Link
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
return
|
||||
}
|
||||
|
||||
contentType := db.JSON
|
||||
if db.HookContentType(f.ContentType) == db.FORM {
|
||||
contentType = db.FORM
|
||||
}
|
||||
|
||||
w := &db.Webhook{
|
||||
RepoID: orCtx.RepoID,
|
||||
URL: f.PayloadURL,
|
||||
ContentType: contentType,
|
||||
Secret: f.Secret,
|
||||
HookEvent: ParseHookEvent(f.Webhook),
|
||||
IsActive: f.Active,
|
||||
HookTaskType: db.GOGS,
|
||||
OrgID: orCtx.OrgID,
|
||||
}
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Handle(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.CreateWebhook(w); err != nil {
|
||||
c.Handle(500, "CreateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.add_hook_success"))
|
||||
c.Redirect(orCtx.Link + "/settings/hooks")
|
||||
}
|
||||
|
||||
func SlackHooksNewPost(c *context.Context, f form.NewSlackHook) {
|
||||
c.Data["Title"] = c.Tr("repo.settings")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksNew"] = true
|
||||
c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}}
|
||||
|
||||
orCtx, err := getOrgRepoCtx(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "getOrgRepoCtx", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
return
|
||||
}
|
||||
|
||||
meta, err := jsoniter.Marshal(&db.SlackMeta{
|
||||
Channel: f.Channel,
|
||||
Username: f.Username,
|
||||
IconURL: f.IconURL,
|
||||
Color: f.Color,
|
||||
})
|
||||
if err != nil {
|
||||
c.Handle(500, "Marshal", err)
|
||||
return
|
||||
}
|
||||
|
||||
w := &db.Webhook{
|
||||
RepoID: orCtx.RepoID,
|
||||
URL: f.PayloadURL,
|
||||
ContentType: db.JSON,
|
||||
HookEvent: ParseHookEvent(f.Webhook),
|
||||
IsActive: f.Active,
|
||||
HookTaskType: db.SLACK,
|
||||
Meta: string(meta),
|
||||
OrgID: orCtx.OrgID,
|
||||
}
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Handle(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.CreateWebhook(w); err != nil {
|
||||
c.Handle(500, "CreateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.add_hook_success"))
|
||||
c.Redirect(orCtx.Link + "/settings/hooks")
|
||||
}
|
||||
|
||||
// FIXME: merge logic to Slack
|
||||
func DiscordHooksNewPost(c *context.Context, f form.NewDiscordHook) {
|
||||
c.Data["Title"] = c.Tr("repo.settings")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksNew"] = true
|
||||
c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}}
|
||||
|
||||
orCtx, err := getOrgRepoCtx(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "getOrgRepoCtx", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
return
|
||||
}
|
||||
|
||||
meta, err := jsoniter.Marshal(&db.SlackMeta{
|
||||
Username: f.Username,
|
||||
IconURL: f.IconURL,
|
||||
Color: f.Color,
|
||||
})
|
||||
if err != nil {
|
||||
c.Handle(500, "Marshal", err)
|
||||
return
|
||||
}
|
||||
|
||||
w := &db.Webhook{
|
||||
RepoID: orCtx.RepoID,
|
||||
URL: f.PayloadURL,
|
||||
ContentType: db.JSON,
|
||||
HookEvent: ParseHookEvent(f.Webhook),
|
||||
IsActive: f.Active,
|
||||
HookTaskType: db.DISCORD,
|
||||
Meta: string(meta),
|
||||
OrgID: orCtx.OrgID,
|
||||
}
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Handle(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.CreateWebhook(w); err != nil {
|
||||
c.Handle(500, "CreateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.add_hook_success"))
|
||||
c.Redirect(orCtx.Link + "/settings/hooks")
|
||||
}
|
||||
|
||||
func DingtalkHooksNewPost(c *context.Context, f form.NewDingtalkHook) {
|
||||
c.Data["Title"] = c.Tr("repo.settings")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksNew"] = true
|
||||
c.Data["Webhook"] = db.Webhook{HookEvent: &db.HookEvent{}}
|
||||
|
||||
orCtx, err := getOrgRepoCtx(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "getOrgRepoCtx", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
return
|
||||
}
|
||||
|
||||
w := &db.Webhook{
|
||||
RepoID: orCtx.RepoID,
|
||||
URL: f.PayloadURL,
|
||||
ContentType: db.JSON,
|
||||
HookEvent: ParseHookEvent(f.Webhook),
|
||||
IsActive: f.Active,
|
||||
HookTaskType: db.DINGTALK,
|
||||
OrgID: orCtx.OrgID,
|
||||
}
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Handle(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.CreateWebhook(w); err != nil {
|
||||
c.Handle(500, "CreateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.add_hook_success"))
|
||||
c.Redirect(orCtx.Link + "/settings/hooks")
|
||||
}
|
||||
|
||||
func checkWebhook(c *context.Context) (*OrgRepoCtx, *db.Webhook) {
|
||||
c.Data["RequireHighlightJS"] = true
|
||||
|
||||
orCtx, err := getOrgRepoCtx(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "getOrgRepoCtx", err)
|
||||
return nil, nil
|
||||
}
|
||||
c.Data["BaseLink"] = orCtx.Link
|
||||
|
||||
var w *db.Webhook
|
||||
if orCtx.RepoID > 0 {
|
||||
w, err = db.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
|
||||
} else {
|
||||
w, err = db.GetWebhookByOrgID(c.Org.Organization.ID, c.ParamsInt64(":id"))
|
||||
}
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch w.HookTaskType {
|
||||
case db.SLACK:
|
||||
c.Data["SlackHook"] = w.GetSlackHook()
|
||||
c.Data["HookType"] = "slack"
|
||||
case db.DISCORD:
|
||||
c.Data["SlackHook"] = w.GetSlackHook()
|
||||
c.Data["HookType"] = "discord"
|
||||
case db.DINGTALK:
|
||||
c.Data["HookType"] = "dingtalk"
|
||||
default:
|
||||
c.Data["HookType"] = "gogs"
|
||||
}
|
||||
|
||||
c.Data["History"], err = w.History(1)
|
||||
if err != nil {
|
||||
c.Handle(500, "History", err)
|
||||
}
|
||||
return orCtx, w
|
||||
}
|
||||
|
||||
func WebHooksEdit(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.update_webhook")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksEdit"] = true
|
||||
|
||||
orCtx, w := checkWebhook(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["Webhook"] = w
|
||||
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
}
|
||||
|
||||
func WebHooksEditPost(c *context.Context, f form.NewWebhook) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.update_webhook")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksEdit"] = true
|
||||
|
||||
orCtx, w := checkWebhook(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["Webhook"] = w
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
return
|
||||
}
|
||||
|
||||
contentType := db.JSON
|
||||
if db.HookContentType(f.ContentType) == db.FORM {
|
||||
contentType = db.FORM
|
||||
}
|
||||
|
||||
w.URL = f.PayloadURL
|
||||
w.ContentType = contentType
|
||||
w.Secret = f.Secret
|
||||
w.HookEvent = ParseHookEvent(f.Webhook)
|
||||
w.IsActive = f.Active
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Handle(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.UpdateWebhook(w); err != nil {
|
||||
c.Handle(500, "WebHooksEditPost", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_hook_success"))
|
||||
c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
|
||||
}
|
||||
|
||||
func SlackHooksEditPost(c *context.Context, f form.NewSlackHook) {
|
||||
c.Data["Title"] = c.Tr("repo.settings")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksEdit"] = true
|
||||
|
||||
orCtx, w := checkWebhook(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["Webhook"] = w
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
return
|
||||
}
|
||||
|
||||
meta, err := jsoniter.Marshal(&db.SlackMeta{
|
||||
Channel: f.Channel,
|
||||
Username: f.Username,
|
||||
IconURL: f.IconURL,
|
||||
Color: f.Color,
|
||||
})
|
||||
if err != nil {
|
||||
c.Handle(500, "Marshal", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.URL = f.PayloadURL
|
||||
w.Meta = string(meta)
|
||||
w.HookEvent = ParseHookEvent(f.Webhook)
|
||||
w.IsActive = f.Active
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Handle(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.UpdateWebhook(w); err != nil {
|
||||
c.Handle(500, "UpdateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_hook_success"))
|
||||
c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
|
||||
}
|
||||
|
||||
// FIXME: merge logic to Slack
|
||||
func DiscordHooksEditPost(c *context.Context, f form.NewDiscordHook) {
|
||||
c.Data["Title"] = c.Tr("repo.settings")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksEdit"] = true
|
||||
|
||||
orCtx, w := checkWebhook(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["Webhook"] = w
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
return
|
||||
}
|
||||
|
||||
meta, err := jsoniter.Marshal(&db.SlackMeta{
|
||||
Username: f.Username,
|
||||
IconURL: f.IconURL,
|
||||
Color: f.Color,
|
||||
})
|
||||
if err != nil {
|
||||
c.Handle(500, "Marshal", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.URL = f.PayloadURL
|
||||
w.Meta = string(meta)
|
||||
w.HookEvent = ParseHookEvent(f.Webhook)
|
||||
w.IsActive = f.Active
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Handle(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.UpdateWebhook(w); err != nil {
|
||||
c.Handle(500, "UpdateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_hook_success"))
|
||||
c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
|
||||
}
|
||||
|
||||
func DingtalkHooksEditPost(c *context.Context, f form.NewDingtalkHook) {
|
||||
c.Data["Title"] = c.Tr("repo.settings")
|
||||
c.Data["PageIsSettingsHooks"] = true
|
||||
c.Data["PageIsSettingsHooksEdit"] = true
|
||||
|
||||
orCtx, w := checkWebhook(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
c.Data["Webhook"] = w
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, orCtx.NewTemplate)
|
||||
return
|
||||
}
|
||||
|
||||
w.URL = f.PayloadURL
|
||||
w.HookEvent = ParseHookEvent(f.Webhook)
|
||||
w.IsActive = f.Active
|
||||
if err := w.UpdateEvent(); err != nil {
|
||||
c.Handle(500, "UpdateEvent", err)
|
||||
return
|
||||
} else if err := db.UpdateWebhook(w); err != nil {
|
||||
c.Handle(500, "UpdateWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("repo.settings.update_hook_success"))
|
||||
c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
|
||||
}
|
||||
|
||||
func TestWebhook(c *context.Context) {
|
||||
var authorUsername, committerUsername string
|
||||
|
||||
// Grab latest commit or fake one if it's empty repository.
|
||||
commit := c.Repo.Commit
|
||||
if commit == nil {
|
||||
ghost := db.NewGhostUser()
|
||||
commit = &git.Commit{
|
||||
ID: git.MustIDFromString(git.EMPTY_SHA),
|
||||
Author: ghost.NewGitSig(),
|
||||
Committer: ghost.NewGitSig(),
|
||||
CommitMessage: "This is a fake commit",
|
||||
}
|
||||
authorUsername = ghost.Name
|
||||
committerUsername = ghost.Name
|
||||
} else {
|
||||
// Try to match email with a real user.
|
||||
author, err := db.GetUserByEmail(commit.Author.Email)
|
||||
if err == nil {
|
||||
authorUsername = author.Name
|
||||
} else if !errors.IsUserNotExist(err) {
|
||||
c.Handle(500, "GetUserByEmail.(author)", err)
|
||||
return
|
||||
}
|
||||
|
||||
committer, err := db.GetUserByEmail(commit.Committer.Email)
|
||||
if err == nil {
|
||||
committerUsername = committer.Name
|
||||
} else if !errors.IsUserNotExist(err) {
|
||||
c.Handle(500, "GetUserByEmail.(committer)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fileStatus, err := commit.FileStatus()
|
||||
if err != nil {
|
||||
c.Handle(500, "FileStatus", err)
|
||||
return
|
||||
}
|
||||
|
||||
apiUser := c.User.APIFormat()
|
||||
p := &api.PushPayload{
|
||||
Ref: git.BRANCH_PREFIX + c.Repo.Repository.DefaultBranch,
|
||||
Before: commit.ID.String(),
|
||||
After: commit.ID.String(),
|
||||
Commits: []*api.PayloadCommit{
|
||||
{
|
||||
ID: commit.ID.String(),
|
||||
Message: commit.Message(),
|
||||
URL: c.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(),
|
||||
Author: &api.PayloadUser{
|
||||
Name: commit.Author.Name,
|
||||
Email: commit.Author.Email,
|
||||
UserName: authorUsername,
|
||||
},
|
||||
Committer: &api.PayloadUser{
|
||||
Name: commit.Committer.Name,
|
||||
Email: commit.Committer.Email,
|
||||
UserName: committerUsername,
|
||||
},
|
||||
Added: fileStatus.Added,
|
||||
Removed: fileStatus.Removed,
|
||||
Modified: fileStatus.Modified,
|
||||
},
|
||||
},
|
||||
Repo: c.Repo.Repository.APIFormat(nil),
|
||||
Pusher: apiUser,
|
||||
Sender: apiUser,
|
||||
}
|
||||
if err := db.TestWebhook(c.Repo.Repository, db.HOOK_EVENT_PUSH, p, c.ParamsInt64("id")); err != nil {
|
||||
c.Handle(500, "TestWebhook", err)
|
||||
} else {
|
||||
c.Flash.Info(c.Tr("repo.settings.webhook.test_delivery_success"))
|
||||
c.Status(200)
|
||||
}
|
||||
}
|
||||
|
||||
func RedeliveryWebhook(c *context.Context) {
|
||||
webhook, err := db.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetWebhookOfRepoByID/GetWebhookByOrgID", errors.IsWebhookNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
hookTask, err := db.GetHookTaskOfWebhookByUUID(webhook.ID, c.Query("uuid"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetHookTaskOfWebhookByUUID/GetWebhookByOrgID", errors.IsHookTaskNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
hookTask.IsDelivered = false
|
||||
if err = db.UpdateHookTask(hookTask); err != nil {
|
||||
c.Handle(500, "UpdateHookTask", err)
|
||||
} else {
|
||||
go db.HookQueue.Add(c.Repo.Repository.ID)
|
||||
c.Flash.Info(c.Tr("repo.settings.webhook.redelivery_success", hookTask.UUID))
|
||||
c.Status(200)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteWebhook(c *context.Context) {
|
||||
if err := db.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.QueryInt64("id")); err != nil {
|
||||
c.Flash.Error("DeleteWebhookByRepoID: " + err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success"))
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": c.Repo.RepoLink + "/settings/hooks",
|
||||
})
|
||||
}
|
||||
274
internal/route/repo/wiki.go
Normal file
274
internal/route/repo/wiki.go
Normal file
@@ -0,0 +1,274 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/markup"
|
||||
)
|
||||
|
||||
const (
|
||||
WIKI_START = "repo/wiki/start"
|
||||
WIKI_VIEW = "repo/wiki/view"
|
||||
WIKI_NEW = "repo/wiki/new"
|
||||
WIKI_PAGES = "repo/wiki/pages"
|
||||
)
|
||||
|
||||
func MustEnableWiki(c *context.Context) {
|
||||
if !c.Repo.Repository.EnableWiki {
|
||||
c.Handle(404, "MustEnableWiki", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if c.Repo.Repository.EnableExternalWiki {
|
||||
c.Redirect(c.Repo.Repository.ExternalWikiURL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type PageMeta struct {
|
||||
Name string
|
||||
URL string
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
func renderWikiPage(c *context.Context, isViewPage bool) (*git.Repository, string) {
|
||||
wikiRepo, err := git.OpenRepository(c.Repo.Repository.WikiPath())
|
||||
if err != nil {
|
||||
c.Handle(500, "OpenRepository", err)
|
||||
return nil, ""
|
||||
}
|
||||
commit, err := wikiRepo.GetBranchCommit("master")
|
||||
if err != nil {
|
||||
c.Handle(500, "GetBranchCommit", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// Get page list.
|
||||
if isViewPage {
|
||||
entries, err := commit.ListEntries()
|
||||
if err != nil {
|
||||
c.Handle(500, "ListEntries", err)
|
||||
return nil, ""
|
||||
}
|
||||
pages := make([]PageMeta, 0, len(entries))
|
||||
for i := range entries {
|
||||
if entries[i].Type == git.OBJECT_BLOB && strings.HasSuffix(entries[i].Name(), ".md") {
|
||||
name := strings.TrimSuffix(entries[i].Name(), ".md")
|
||||
pages = append(pages, PageMeta{
|
||||
Name: name,
|
||||
URL: db.ToWikiPageURL(name),
|
||||
})
|
||||
}
|
||||
}
|
||||
c.Data["Pages"] = pages
|
||||
}
|
||||
|
||||
pageURL := c.Params(":page")
|
||||
if len(pageURL) == 0 {
|
||||
pageURL = "Home"
|
||||
}
|
||||
c.Data["PageURL"] = pageURL
|
||||
|
||||
pageName := db.ToWikiPageName(pageURL)
|
||||
c.Data["old_title"] = pageName
|
||||
c.Data["Title"] = pageName
|
||||
c.Data["title"] = pageName
|
||||
c.Data["RequireHighlightJS"] = true
|
||||
|
||||
blob, err := commit.GetBlobByPath(pageName + ".md")
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
c.Redirect(c.Repo.RepoLink + "/wiki/_pages")
|
||||
} else {
|
||||
c.Handle(500, "GetBlobByPath", err)
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
r, err := blob.Data()
|
||||
if err != nil {
|
||||
c.Handle(500, "Data", err)
|
||||
return nil, ""
|
||||
}
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
c.Handle(500, "ReadAll", err)
|
||||
return nil, ""
|
||||
}
|
||||
if isViewPage {
|
||||
c.Data["content"] = string(markup.Markdown(data, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas()))
|
||||
} else {
|
||||
c.Data["content"] = string(data)
|
||||
}
|
||||
|
||||
return wikiRepo, pageName
|
||||
}
|
||||
|
||||
func Wiki(c *context.Context) {
|
||||
c.Data["PageIsWiki"] = true
|
||||
|
||||
if !c.Repo.Repository.HasWiki() {
|
||||
c.Data["Title"] = c.Tr("repo.wiki")
|
||||
c.HTML(200, WIKI_START)
|
||||
return
|
||||
}
|
||||
|
||||
wikiRepo, pageName := renderWikiPage(c, true)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get last change information.
|
||||
lastCommit, err := wikiRepo.GetCommitByPath(pageName + ".md")
|
||||
if err != nil {
|
||||
c.Handle(500, "GetCommitByPath", err)
|
||||
return
|
||||
}
|
||||
c.Data["Author"] = lastCommit.Author
|
||||
|
||||
c.HTML(200, WIKI_VIEW)
|
||||
}
|
||||
|
||||
func WikiPages(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.wiki.pages")
|
||||
c.Data["PageIsWiki"] = true
|
||||
|
||||
if !c.Repo.Repository.HasWiki() {
|
||||
c.Redirect(c.Repo.RepoLink + "/wiki")
|
||||
return
|
||||
}
|
||||
|
||||
wikiRepo, err := git.OpenRepository(c.Repo.Repository.WikiPath())
|
||||
if err != nil {
|
||||
c.Handle(500, "OpenRepository", err)
|
||||
return
|
||||
}
|
||||
commit, err := wikiRepo.GetBranchCommit("master")
|
||||
if err != nil {
|
||||
c.Handle(500, "GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := commit.ListEntries()
|
||||
if err != nil {
|
||||
c.Handle(500, "ListEntries", err)
|
||||
return
|
||||
}
|
||||
pages := make([]PageMeta, 0, len(entries))
|
||||
for i := range entries {
|
||||
if entries[i].Type == git.OBJECT_BLOB && strings.HasSuffix(entries[i].Name(), ".md") {
|
||||
commit, err := wikiRepo.GetCommitByPath(entries[i].Name())
|
||||
if err != nil {
|
||||
c.ServerError("GetCommitByPath", err)
|
||||
return
|
||||
}
|
||||
name := strings.TrimSuffix(entries[i].Name(), ".md")
|
||||
pages = append(pages, PageMeta{
|
||||
Name: name,
|
||||
URL: db.ToWikiPageURL(name),
|
||||
Updated: commit.Author.When,
|
||||
})
|
||||
}
|
||||
}
|
||||
c.Data["Pages"] = pages
|
||||
|
||||
c.HTML(200, WIKI_PAGES)
|
||||
}
|
||||
|
||||
func NewWiki(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.wiki.new_page")
|
||||
c.Data["PageIsWiki"] = true
|
||||
c.Data["RequireSimpleMDE"] = true
|
||||
|
||||
if !c.Repo.Repository.HasWiki() {
|
||||
c.Data["title"] = "Home"
|
||||
}
|
||||
|
||||
c.HTML(200, WIKI_NEW)
|
||||
}
|
||||
|
||||
func NewWikiPost(c *context.Context, f form.NewWiki) {
|
||||
c.Data["Title"] = c.Tr("repo.wiki.new_page")
|
||||
c.Data["PageIsWiki"] = true
|
||||
c.Data["RequireSimpleMDE"] = true
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, WIKI_NEW)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Repo.Repository.AddWikiPage(c.User, f.Title, f.Content, f.Message); err != nil {
|
||||
if db.IsErrWikiAlreadyExist(err) {
|
||||
c.Data["Err_Title"] = true
|
||||
c.RenderWithErr(c.Tr("repo.wiki.page_already_exists"), WIKI_NEW, &f)
|
||||
} else {
|
||||
c.Handle(500, "AddWikiPage", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(c.Repo.RepoLink + "/wiki/" + db.ToWikiPageURL(db.ToWikiPageName(f.Title)))
|
||||
}
|
||||
|
||||
func EditWiki(c *context.Context) {
|
||||
c.Data["PageIsWiki"] = true
|
||||
c.Data["PageIsWikiEdit"] = true
|
||||
c.Data["RequireSimpleMDE"] = true
|
||||
|
||||
if !c.Repo.Repository.HasWiki() {
|
||||
c.Redirect(c.Repo.RepoLink + "/wiki")
|
||||
return
|
||||
}
|
||||
|
||||
renderWikiPage(c, false)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(200, WIKI_NEW)
|
||||
}
|
||||
|
||||
func EditWikiPost(c *context.Context, f form.NewWiki) {
|
||||
c.Data["Title"] = c.Tr("repo.wiki.new_page")
|
||||
c.Data["PageIsWiki"] = true
|
||||
c.Data["RequireSimpleMDE"] = true
|
||||
|
||||
if c.HasError() {
|
||||
c.HTML(200, WIKI_NEW)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Repo.Repository.EditWikiPage(c.User, f.OldTitle, f.Title, f.Content, f.Message); err != nil {
|
||||
c.Handle(500, "EditWikiPage", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(c.Repo.RepoLink + "/wiki/" + db.ToWikiPageURL(db.ToWikiPageName(f.Title)))
|
||||
}
|
||||
|
||||
func DeleteWikiPagePost(c *context.Context) {
|
||||
pageURL := c.Params(":page")
|
||||
if len(pageURL) == 0 {
|
||||
pageURL = "Home"
|
||||
}
|
||||
|
||||
pageName := db.ToWikiPageName(pageURL)
|
||||
if err := c.Repo.Repository.DeleteWikiPage(c.User, pageName); err != nil {
|
||||
c.Handle(500, "DeleteWikiPage", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{
|
||||
"redirect": c.Repo.RepoLink + "/wiki/",
|
||||
})
|
||||
}
|
||||
573
internal/route/user/auth.go
Normal file
573
internal/route/user/auth.go
Normal file
@@ -0,0 +1,573 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-macaron/captcha"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/mailer"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
LOGIN = "user/auth/login"
|
||||
TWO_FACTOR = "user/auth/two_factor"
|
||||
TWO_FACTOR_RECOVERY_CODE = "user/auth/two_factor_recovery_code"
|
||||
SIGNUP = "user/auth/signup"
|
||||
ACTIVATE = "user/auth/activate"
|
||||
FORGOT_PASSWORD = "user/auth/forgot_passwd"
|
||||
RESET_PASSWORD = "user/auth/reset_passwd"
|
||||
)
|
||||
|
||||
// AutoLogin reads cookie and try to auto-login.
|
||||
func AutoLogin(c *context.Context) (bool, error) {
|
||||
if !db.HasEngine {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
uname := c.GetCookie(setting.CookieUserName)
|
||||
if len(uname) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
isSucceed := false
|
||||
defer func() {
|
||||
if !isSucceed {
|
||||
log.Trace("auto-login cookie cleared: %s", uname)
|
||||
c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL)
|
||||
c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL)
|
||||
c.SetCookie(setting.LoginStatusCookieName, "", -1, setting.AppSubURL)
|
||||
}
|
||||
}()
|
||||
|
||||
u, err := db.GetUserByName(uname)
|
||||
if err != nil {
|
||||
if !errors.IsUserNotExist(err) {
|
||||
return false, fmt.Errorf("GetUserByName: %v", err)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if val, ok := c.GetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName); !ok || val != u.Name {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
isSucceed = true
|
||||
c.Session.Set("uid", u.ID)
|
||||
c.Session.Set("uname", u.Name)
|
||||
c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL)
|
||||
if setting.EnableLoginStatusCookie {
|
||||
c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func Login(c *context.Context) {
|
||||
c.Title("sign_in")
|
||||
|
||||
// Check auto-login
|
||||
isSucceed, err := AutoLogin(c)
|
||||
if err != nil {
|
||||
c.ServerError("AutoLogin", err)
|
||||
return
|
||||
}
|
||||
|
||||
redirectTo := c.Query("redirect_to")
|
||||
if len(redirectTo) > 0 {
|
||||
c.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL)
|
||||
} else {
|
||||
redirectTo, _ = url.QueryUnescape(c.GetCookie("redirect_to"))
|
||||
}
|
||||
|
||||
if isSucceed {
|
||||
if tool.IsSameSiteURLPath(redirectTo) {
|
||||
c.Redirect(redirectTo)
|
||||
} else {
|
||||
c.SubURLRedirect("/")
|
||||
}
|
||||
c.SetCookie("redirect_to", "", -1, setting.AppSubURL)
|
||||
return
|
||||
}
|
||||
|
||||
// Display normal login page
|
||||
loginSources, err := db.ActivatedLoginSources()
|
||||
if err != nil {
|
||||
c.ServerError("ActivatedLoginSources", err)
|
||||
return
|
||||
}
|
||||
c.Data["LoginSources"] = loginSources
|
||||
for i := range loginSources {
|
||||
if loginSources[i].IsDefault {
|
||||
c.Data["DefaultLoginSource"] = loginSources[i]
|
||||
c.Data["login_source"] = loginSources[i].ID
|
||||
break
|
||||
}
|
||||
}
|
||||
c.Success(LOGIN)
|
||||
}
|
||||
|
||||
func afterLogin(c *context.Context, u *db.User, remember bool) {
|
||||
if remember {
|
||||
days := 86400 * setting.LoginRememberDays
|
||||
c.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true)
|
||||
c.SetSuperSecureCookie(u.Rands+u.Passwd, setting.CookieRememberName, u.Name, days, setting.AppSubURL, "", setting.CookieSecure, true)
|
||||
}
|
||||
|
||||
c.Session.Set("uid", u.ID)
|
||||
c.Session.Set("uname", u.Name)
|
||||
c.Session.Delete("twoFactorRemember")
|
||||
c.Session.Delete("twoFactorUserID")
|
||||
|
||||
// Clear whatever CSRF has right now, force to generate a new one
|
||||
c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL)
|
||||
if setting.EnableLoginStatusCookie {
|
||||
c.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL)
|
||||
}
|
||||
|
||||
redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to"))
|
||||
c.SetCookie("redirect_to", "", -1, setting.AppSubURL)
|
||||
if tool.IsSameSiteURLPath(redirectTo) {
|
||||
c.Redirect(redirectTo)
|
||||
return
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/")
|
||||
}
|
||||
|
||||
func LoginPost(c *context.Context, f form.SignIn) {
|
||||
c.Title("sign_in")
|
||||
|
||||
loginSources, err := db.ActivatedLoginSources()
|
||||
if err != nil {
|
||||
c.ServerError("ActivatedLoginSources", err)
|
||||
return
|
||||
}
|
||||
c.Data["LoginSources"] = loginSources
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(LOGIN)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := db.UserLogin(f.UserName, f.Password, f.LoginSource)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case errors.UserNotExist:
|
||||
c.FormErr("UserName", "Password")
|
||||
c.RenderWithErr(c.Tr("form.username_password_incorrect"), LOGIN, &f)
|
||||
case errors.LoginSourceMismatch:
|
||||
c.FormErr("LoginSource")
|
||||
c.RenderWithErr(c.Tr("form.auth_source_mismatch"), LOGIN, &f)
|
||||
|
||||
default:
|
||||
c.ServerError("UserLogin", err)
|
||||
}
|
||||
for i := range loginSources {
|
||||
if loginSources[i].IsDefault {
|
||||
c.Data["DefaultLoginSource"] = loginSources[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !u.IsEnabledTwoFactor() {
|
||||
afterLogin(c, u, f.Remember)
|
||||
return
|
||||
}
|
||||
|
||||
c.Session.Set("twoFactorRemember", f.Remember)
|
||||
c.Session.Set("twoFactorUserID", u.ID)
|
||||
c.SubURLRedirect("/user/login/two_factor")
|
||||
}
|
||||
|
||||
func LoginTwoFactor(c *context.Context) {
|
||||
_, ok := c.Session.Get("twoFactorUserID").(int64)
|
||||
if !ok {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
c.Success(TWO_FACTOR)
|
||||
}
|
||||
|
||||
func LoginTwoFactorPost(c *context.Context) {
|
||||
userID, ok := c.Session.Get("twoFactorUserID").(int64)
|
||||
if !ok {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
t, err := db.GetTwoFactorByUserID(userID)
|
||||
if err != nil {
|
||||
c.ServerError("GetTwoFactorByUserID", err)
|
||||
return
|
||||
}
|
||||
|
||||
passcode := c.Query("passcode")
|
||||
valid, err := t.ValidateTOTP(passcode)
|
||||
if err != nil {
|
||||
c.ServerError("ValidateTOTP", err)
|
||||
return
|
||||
} else if !valid {
|
||||
c.Flash.Error(c.Tr("settings.two_factor_invalid_passcode"))
|
||||
c.SubURLRedirect("/user/login/two_factor")
|
||||
return
|
||||
}
|
||||
|
||||
u, err := db.GetUserByID(userID)
|
||||
if err != nil {
|
||||
c.ServerError("GetUserByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent same passcode from being reused
|
||||
if c.Cache.IsExist(u.TwoFactorCacheKey(passcode)) {
|
||||
c.Flash.Error(c.Tr("settings.two_factor_reused_passcode"))
|
||||
c.SubURLRedirect("/user/login/two_factor")
|
||||
return
|
||||
}
|
||||
if err = c.Cache.Put(u.TwoFactorCacheKey(passcode), 1, 60); err != nil {
|
||||
log.Error(2, "Failed to put cache 'two factor passcode': %v", err)
|
||||
}
|
||||
|
||||
afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool))
|
||||
}
|
||||
|
||||
func LoginTwoFactorRecoveryCode(c *context.Context) {
|
||||
_, ok := c.Session.Get("twoFactorUserID").(int64)
|
||||
if !ok {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
c.Success(TWO_FACTOR_RECOVERY_CODE)
|
||||
}
|
||||
|
||||
func LoginTwoFactorRecoveryCodePost(c *context.Context) {
|
||||
userID, ok := c.Session.Get("twoFactorUserID").(int64)
|
||||
if !ok {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.UseRecoveryCode(userID, c.Query("recovery_code")); err != nil {
|
||||
if errors.IsTwoFactorRecoveryCodeNotFound(err) {
|
||||
c.Flash.Error(c.Tr("auth.login_two_factor_invalid_recovery_code"))
|
||||
c.SubURLRedirect("/user/login/two_factor_recovery_code")
|
||||
} else {
|
||||
c.ServerError("UseRecoveryCode", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
u, err := db.GetUserByID(userID)
|
||||
if err != nil {
|
||||
c.ServerError("GetUserByID", err)
|
||||
return
|
||||
}
|
||||
afterLogin(c, u, c.Session.Get("twoFactorRemember").(bool))
|
||||
}
|
||||
|
||||
func SignOut(c *context.Context) {
|
||||
c.Session.Flush()
|
||||
c.Session.Destory(c.Context)
|
||||
c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL)
|
||||
c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL)
|
||||
c.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL)
|
||||
c.SubURLRedirect("/")
|
||||
}
|
||||
|
||||
func SignUp(c *context.Context) {
|
||||
c.Title("sign_up")
|
||||
|
||||
c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
|
||||
|
||||
if setting.Service.DisableRegistration {
|
||||
c.Data["DisableRegistration"] = true
|
||||
c.Success(SIGNUP)
|
||||
return
|
||||
}
|
||||
|
||||
c.Success(SIGNUP)
|
||||
}
|
||||
|
||||
func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
|
||||
c.Title("sign_up")
|
||||
|
||||
c.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
|
||||
|
||||
if setting.Service.DisableRegistration {
|
||||
c.Status(403)
|
||||
return
|
||||
}
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(SIGNUP)
|
||||
return
|
||||
}
|
||||
|
||||
if setting.Service.EnableCaptcha && !cpt.VerifyReq(c.Req) {
|
||||
c.FormErr("Captcha")
|
||||
c.RenderWithErr(c.Tr("form.captcha_incorrect"), SIGNUP, &f)
|
||||
return
|
||||
}
|
||||
|
||||
if f.Password != f.Retype {
|
||||
c.FormErr("Password")
|
||||
c.RenderWithErr(c.Tr("form.password_not_match"), SIGNUP, &f)
|
||||
return
|
||||
}
|
||||
|
||||
u := &db.User{
|
||||
Name: f.UserName,
|
||||
Email: f.Email,
|
||||
Passwd: f.Password,
|
||||
IsActive: !setting.Service.RegisterEmailConfirm,
|
||||
}
|
||||
if err := db.CreateUser(u); err != nil {
|
||||
switch {
|
||||
case db.IsErrUserAlreadyExist(err):
|
||||
c.FormErr("UserName")
|
||||
c.RenderWithErr(c.Tr("form.username_been_taken"), SIGNUP, &f)
|
||||
case db.IsErrEmailAlreadyUsed(err):
|
||||
c.FormErr("Email")
|
||||
c.RenderWithErr(c.Tr("form.email_been_used"), SIGNUP, &f)
|
||||
case db.IsErrNameReserved(err):
|
||||
c.FormErr("UserName")
|
||||
c.RenderWithErr(c.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), SIGNUP, &f)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
c.FormErr("UserName")
|
||||
c.RenderWithErr(c.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), SIGNUP, &f)
|
||||
default:
|
||||
c.ServerError("CreateUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Account created: %s", u.Name)
|
||||
|
||||
// Auto-set admin for the only user.
|
||||
if db.CountUsers() == 1 {
|
||||
u.IsAdmin = true
|
||||
u.IsActive = true
|
||||
if err := db.UpdateUser(u); err != nil {
|
||||
c.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Send confirmation email, no need for social account.
|
||||
if setting.Service.RegisterEmailConfirm && u.ID > 1 {
|
||||
mailer.SendActivateAccountMail(c.Context, db.NewMailerUser(u))
|
||||
c.Data["IsSendRegisterMail"] = true
|
||||
c.Data["Email"] = u.Email
|
||||
c.Data["Hours"] = setting.Service.ActiveCodeLives / 60
|
||||
c.Success(ACTIVATE)
|
||||
|
||||
if err := c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil {
|
||||
log.Error(2, "Failed to put cache key 'mail resend': %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/user/login")
|
||||
}
|
||||
|
||||
func Activate(c *context.Context) {
|
||||
code := c.Query("code")
|
||||
if len(code) == 0 {
|
||||
c.Data["IsActivatePage"] = true
|
||||
if c.User.IsActive {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
// Resend confirmation email.
|
||||
if setting.Service.RegisterEmailConfirm {
|
||||
if c.Cache.IsExist(c.User.MailResendCacheKey()) {
|
||||
c.Data["ResendLimited"] = true
|
||||
} else {
|
||||
c.Data["Hours"] = setting.Service.ActiveCodeLives / 60
|
||||
mailer.SendActivateAccountMail(c.Context, db.NewMailerUser(c.User))
|
||||
|
||||
if err := c.Cache.Put(c.User.MailResendCacheKey(), 1, 180); err != nil {
|
||||
log.Error(2, "Failed to put cache key 'mail resend': %v", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.Data["ServiceNotEnabled"] = true
|
||||
}
|
||||
c.Success(ACTIVATE)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify code.
|
||||
if user := db.VerifyUserActiveCode(code); user != nil {
|
||||
user.IsActive = true
|
||||
var err error
|
||||
if user.Rands, err = db.GetUserSalt(); err != nil {
|
||||
c.ServerError("GetUserSalt", err)
|
||||
return
|
||||
}
|
||||
if err := db.UpdateUser(user); err != nil {
|
||||
c.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("User activated: %s", user.Name)
|
||||
|
||||
c.Session.Set("uid", user.ID)
|
||||
c.Session.Set("uname", user.Name)
|
||||
c.SubURLRedirect("/")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["IsActivateFailed"] = true
|
||||
c.Success(ACTIVATE)
|
||||
}
|
||||
|
||||
func ActivateEmail(c *context.Context) {
|
||||
code := c.Query("code")
|
||||
email_string := c.Query("email")
|
||||
|
||||
// Verify code.
|
||||
if email := db.VerifyActiveEmailCode(code, email_string); email != nil {
|
||||
if err := email.Activate(); err != nil {
|
||||
c.ServerError("ActivateEmail", err)
|
||||
}
|
||||
|
||||
log.Trace("Email activated: %s", email.Email)
|
||||
c.Flash.Success(c.Tr("settings.add_email_success"))
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/user/settings/email")
|
||||
return
|
||||
}
|
||||
|
||||
func ForgotPasswd(c *context.Context) {
|
||||
c.Title("auth.forgot_password")
|
||||
|
||||
if setting.MailService == nil {
|
||||
c.Data["IsResetDisable"] = true
|
||||
c.Success(FORGOT_PASSWORD)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["IsResetRequest"] = true
|
||||
c.Success(FORGOT_PASSWORD)
|
||||
}
|
||||
|
||||
func ForgotPasswdPost(c *context.Context) {
|
||||
c.Title("auth.forgot_password")
|
||||
|
||||
if setting.MailService == nil {
|
||||
c.Status(403)
|
||||
return
|
||||
}
|
||||
c.Data["IsResetRequest"] = true
|
||||
|
||||
email := c.Query("email")
|
||||
c.Data["Email"] = email
|
||||
|
||||
u, err := db.GetUserByEmail(email)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.Data["Hours"] = setting.Service.ActiveCodeLives / 60
|
||||
c.Data["IsResetSent"] = true
|
||||
c.Success(FORGOT_PASSWORD)
|
||||
return
|
||||
} else {
|
||||
c.ServerError("GetUserByEmail", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !u.IsLocal() {
|
||||
c.FormErr("Email")
|
||||
c.RenderWithErr(c.Tr("auth.non_local_account"), FORGOT_PASSWORD, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if c.Cache.IsExist(u.MailResendCacheKey()) {
|
||||
c.Data["ResendLimited"] = true
|
||||
c.Success(FORGOT_PASSWORD)
|
||||
return
|
||||
}
|
||||
|
||||
mailer.SendResetPasswordMail(c.Context, db.NewMailerUser(u))
|
||||
if err = c.Cache.Put(u.MailResendCacheKey(), 1, 180); err != nil {
|
||||
log.Error(2, "Failed to put cache key 'mail resend': %v", err)
|
||||
}
|
||||
|
||||
c.Data["Hours"] = setting.Service.ActiveCodeLives / 60
|
||||
c.Data["IsResetSent"] = true
|
||||
c.Success(FORGOT_PASSWORD)
|
||||
}
|
||||
|
||||
func ResetPasswd(c *context.Context) {
|
||||
c.Title("auth.reset_password")
|
||||
|
||||
code := c.Query("code")
|
||||
if len(code) == 0 {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
c.Data["Code"] = code
|
||||
c.Data["IsResetForm"] = true
|
||||
c.Success(RESET_PASSWORD)
|
||||
}
|
||||
|
||||
func ResetPasswdPost(c *context.Context) {
|
||||
c.Title("auth.reset_password")
|
||||
|
||||
code := c.Query("code")
|
||||
if len(code) == 0 {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
c.Data["Code"] = code
|
||||
|
||||
if u := db.VerifyUserActiveCode(code); u != nil {
|
||||
// Validate password length.
|
||||
passwd := c.Query("password")
|
||||
if len(passwd) < 6 {
|
||||
c.Data["IsResetForm"] = true
|
||||
c.Data["Err_Password"] = true
|
||||
c.RenderWithErr(c.Tr("auth.password_too_short"), RESET_PASSWORD, nil)
|
||||
return
|
||||
}
|
||||
|
||||
u.Passwd = passwd
|
||||
var err error
|
||||
if u.Rands, err = db.GetUserSalt(); err != nil {
|
||||
c.ServerError("GetUserSalt", err)
|
||||
return
|
||||
}
|
||||
if u.Salt, err = db.GetUserSalt(); err != nil {
|
||||
c.ServerError("GetUserSalt", err)
|
||||
return
|
||||
}
|
||||
u.EncodePasswd()
|
||||
if err := db.UpdateUser(u); err != nil {
|
||||
c.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("User password reset: %s", u.Name)
|
||||
c.SubURLRedirect("/user/login")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["IsResetFailed"] = true
|
||||
c.Success(RESET_PASSWORD)
|
||||
}
|
||||
424
internal/route/user/home.go
Normal file
424
internal/route/user/home.go
Normal file
@@ -0,0 +1,424 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
"github.com/unknwon/paginater"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
DASHBOARD = "user/dashboard/dashboard"
|
||||
NEWS_FEED = "user/dashboard/feeds"
|
||||
ISSUES = "user/dashboard/issues"
|
||||
PROFILE = "user/profile"
|
||||
ORG_HOME = "org/home"
|
||||
)
|
||||
|
||||
// getDashboardContextUser finds out dashboard is viewing as which context user.
|
||||
func getDashboardContextUser(c *context.Context) *db.User {
|
||||
ctxUser := c.User
|
||||
orgName := c.Params(":org")
|
||||
if len(orgName) > 0 {
|
||||
// Organization.
|
||||
org, err := db.GetUserByName(orgName)
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
|
||||
return nil
|
||||
}
|
||||
ctxUser = org
|
||||
}
|
||||
c.Data["ContextUser"] = ctxUser
|
||||
|
||||
if err := c.User.GetOrganizations(true); err != nil {
|
||||
c.Handle(500, "GetOrganizations", err)
|
||||
return nil
|
||||
}
|
||||
c.Data["Orgs"] = c.User.Orgs
|
||||
|
||||
return ctxUser
|
||||
}
|
||||
|
||||
// retrieveFeeds loads feeds from database by given context user.
|
||||
// The user could be organization so it is not always the logged in user,
|
||||
// which is why we have to explicitly pass the context user ID.
|
||||
func retrieveFeeds(c *context.Context, ctxUser *db.User, userID int64, isProfile bool) {
|
||||
actions, err := db.GetFeeds(ctxUser, userID, c.QueryInt64("after_id"), isProfile)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetFeeds", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check access of private repositories.
|
||||
feeds := make([]*db.Action, 0, len(actions))
|
||||
unameAvatars := make(map[string]string)
|
||||
for _, act := range actions {
|
||||
// Cache results to reduce queries.
|
||||
_, ok := unameAvatars[act.ActUserName]
|
||||
if !ok {
|
||||
u, err := db.GetUserByName(act.ActUserName)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
continue
|
||||
}
|
||||
c.Handle(500, "GetUserByName", err)
|
||||
return
|
||||
}
|
||||
unameAvatars[act.ActUserName] = u.RelAvatarLink()
|
||||
}
|
||||
|
||||
act.ActAvatar = unameAvatars[act.ActUserName]
|
||||
feeds = append(feeds, act)
|
||||
}
|
||||
c.Data["Feeds"] = feeds
|
||||
if len(feeds) > 0 {
|
||||
afterID := feeds[len(feeds)-1].ID
|
||||
c.Data["AfterID"] = afterID
|
||||
c.Header().Set("X-AJAX-URL", fmt.Sprintf("%s?after_id=%d", c.Data["Link"], afterID))
|
||||
}
|
||||
}
|
||||
|
||||
func Dashboard(c *context.Context) {
|
||||
ctxUser := getDashboardContextUser(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
retrieveFeeds(c, ctxUser, c.User.ID, false)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Req.Header.Get("X-AJAX") == "true" {
|
||||
c.HTML(200, NEWS_FEED)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["Title"] = ctxUser.DisplayName() + " - " + c.Tr("dashboard")
|
||||
c.Data["PageIsDashboard"] = true
|
||||
c.Data["PageIsNews"] = true
|
||||
|
||||
// Only user can have collaborative repositories.
|
||||
if !ctxUser.IsOrganization() {
|
||||
collaborateRepos, err := c.User.GetAccessibleRepositories(setting.UI.User.RepoPagingNum)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetAccessibleRepositories", err)
|
||||
return
|
||||
} else if err = db.RepositoryList(collaborateRepos).LoadAttributes(); err != nil {
|
||||
c.Handle(500, "RepositoryList.LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
c.Data["CollaborativeRepos"] = collaborateRepos
|
||||
}
|
||||
|
||||
var err error
|
||||
var repos, mirrors []*db.Repository
|
||||
var repoCount int64
|
||||
if ctxUser.IsOrganization() {
|
||||
repos, repoCount, err = ctxUser.GetUserRepositories(c.User.ID, 1, setting.UI.User.RepoPagingNum)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetUserRepositories", err)
|
||||
return
|
||||
}
|
||||
|
||||
mirrors, err = ctxUser.GetUserMirrorRepositories(c.User.ID)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetUserMirrorRepositories", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = ctxUser.GetRepositories(1, setting.UI.User.RepoPagingNum); err != nil {
|
||||
c.Handle(500, "GetRepositories", err)
|
||||
return
|
||||
}
|
||||
repos = ctxUser.Repos
|
||||
repoCount = int64(ctxUser.NumRepos)
|
||||
|
||||
mirrors, err = ctxUser.GetMirrorRepositories()
|
||||
if err != nil {
|
||||
c.Handle(500, "GetMirrorRepositories", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["Repos"] = repos
|
||||
c.Data["RepoCount"] = repoCount
|
||||
c.Data["MaxShowRepoNum"] = setting.UI.User.RepoPagingNum
|
||||
|
||||
if err := db.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil {
|
||||
c.Handle(500, "MirrorRepositoryList.LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
c.Data["MirrorCount"] = len(mirrors)
|
||||
c.Data["Mirrors"] = mirrors
|
||||
|
||||
c.HTML(200, DASHBOARD)
|
||||
}
|
||||
|
||||
func Issues(c *context.Context) {
|
||||
isPullList := c.Params(":type") == "pulls"
|
||||
if isPullList {
|
||||
c.Data["Title"] = c.Tr("pull_requests")
|
||||
c.Data["PageIsPulls"] = true
|
||||
} else {
|
||||
c.Data["Title"] = c.Tr("issues")
|
||||
c.Data["PageIsIssues"] = true
|
||||
}
|
||||
|
||||
ctxUser := getDashboardContextUser(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
sortType = c.Query("sort")
|
||||
filterMode = db.FILTER_MODE_YOUR_REPOS
|
||||
)
|
||||
|
||||
// Note: Organization does not have view type and filter mode.
|
||||
if !ctxUser.IsOrganization() {
|
||||
viewType := c.Query("type")
|
||||
types := []string{
|
||||
string(db.FILTER_MODE_YOUR_REPOS),
|
||||
string(db.FILTER_MODE_ASSIGN),
|
||||
string(db.FILTER_MODE_CREATE),
|
||||
}
|
||||
if !com.IsSliceContainsStr(types, viewType) {
|
||||
viewType = string(db.FILTER_MODE_YOUR_REPOS)
|
||||
}
|
||||
filterMode = db.FilterMode(viewType)
|
||||
}
|
||||
|
||||
page := c.QueryInt("page")
|
||||
if page <= 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
repoID := c.QueryInt64("repo")
|
||||
isShowClosed := c.Query("state") == "closed"
|
||||
|
||||
// Get repositories.
|
||||
var (
|
||||
err error
|
||||
repos []*db.Repository
|
||||
userRepoIDs []int64
|
||||
showRepos = make([]*db.Repository, 0, 10)
|
||||
)
|
||||
if ctxUser.IsOrganization() {
|
||||
repos, _, err = ctxUser.GetUserRepositories(c.User.ID, 1, ctxUser.NumRepos)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetRepositories", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := ctxUser.GetRepositories(1, c.User.NumRepos); err != nil {
|
||||
c.Handle(500, "GetRepositories", err)
|
||||
return
|
||||
}
|
||||
repos = ctxUser.Repos
|
||||
}
|
||||
|
||||
userRepoIDs = make([]int64, 0, len(repos))
|
||||
for _, repo := range repos {
|
||||
userRepoIDs = append(userRepoIDs, repo.ID)
|
||||
|
||||
if filterMode != db.FILTER_MODE_YOUR_REPOS {
|
||||
continue
|
||||
}
|
||||
|
||||
if isPullList {
|
||||
if isShowClosed && repo.NumClosedPulls == 0 ||
|
||||
!isShowClosed && repo.NumOpenPulls == 0 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if !repo.EnableIssues || repo.EnableExternalTracker ||
|
||||
isShowClosed && repo.NumClosedIssues == 0 ||
|
||||
!isShowClosed && repo.NumOpenIssues == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
showRepos = append(showRepos, repo)
|
||||
}
|
||||
|
||||
// Filter repositories if the page shows issues.
|
||||
if !isPullList {
|
||||
userRepoIDs, err = db.FilterRepositoryWithIssues(userRepoIDs)
|
||||
if err != nil {
|
||||
c.Handle(500, "FilterRepositoryWithIssues", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
issueOptions := &db.IssuesOptions{
|
||||
RepoID: repoID,
|
||||
Page: page,
|
||||
IsClosed: isShowClosed,
|
||||
IsPull: isPullList,
|
||||
SortType: sortType,
|
||||
}
|
||||
switch filterMode {
|
||||
case db.FILTER_MODE_YOUR_REPOS:
|
||||
// Get all issues from repositories from this user.
|
||||
if userRepoIDs == nil {
|
||||
issueOptions.RepoIDs = []int64{-1}
|
||||
} else {
|
||||
issueOptions.RepoIDs = userRepoIDs
|
||||
}
|
||||
|
||||
case db.FILTER_MODE_ASSIGN:
|
||||
// Get all issues assigned to this user.
|
||||
issueOptions.AssigneeID = ctxUser.ID
|
||||
|
||||
case db.FILTER_MODE_CREATE:
|
||||
// Get all issues created by this user.
|
||||
issueOptions.PosterID = ctxUser.ID
|
||||
}
|
||||
|
||||
issues, err := db.Issues(issueOptions)
|
||||
if err != nil {
|
||||
c.Handle(500, "Issues", err)
|
||||
return
|
||||
}
|
||||
|
||||
if repoID > 0 {
|
||||
repo, err := db.GetRepositoryByID(repoID)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetRepositoryByID", fmt.Errorf("[#%d] %v", repoID, err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = repo.GetOwner(); err != nil {
|
||||
c.Handle(500, "GetOwner", fmt.Errorf("[#%d] %v", repoID, err))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has access to given repository.
|
||||
if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser.ID) {
|
||||
c.Handle(404, "Issues", fmt.Errorf("#%d", repoID))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
if err = issue.Repo.GetOwner(); err != nil {
|
||||
c.Handle(500, "GetOwner", fmt.Errorf("[#%d] %v", issue.RepoID, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
issueStats := db.GetUserIssueStats(repoID, ctxUser.ID, userRepoIDs, filterMode, isPullList)
|
||||
|
||||
var total int
|
||||
if !isShowClosed {
|
||||
total = int(issueStats.OpenCount)
|
||||
} else {
|
||||
total = int(issueStats.ClosedCount)
|
||||
}
|
||||
|
||||
c.Data["Issues"] = issues
|
||||
c.Data["Repos"] = showRepos
|
||||
c.Data["Page"] = paginater.New(total, setting.UI.IssuePagingNum, page, 5)
|
||||
c.Data["IssueStats"] = issueStats
|
||||
c.Data["ViewType"] = string(filterMode)
|
||||
c.Data["SortType"] = sortType
|
||||
c.Data["RepoID"] = repoID
|
||||
c.Data["IsShowClosed"] = isShowClosed
|
||||
|
||||
if isShowClosed {
|
||||
c.Data["State"] = "closed"
|
||||
} else {
|
||||
c.Data["State"] = "open"
|
||||
}
|
||||
|
||||
c.HTML(200, ISSUES)
|
||||
}
|
||||
|
||||
func ShowSSHKeys(c *context.Context, uid int64) {
|
||||
keys, err := db.ListPublicKeys(uid)
|
||||
if err != nil {
|
||||
c.Handle(500, "ListPublicKeys", err)
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for i := range keys {
|
||||
buf.WriteString(keys[i].OmitEmail())
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
c.PlainText(200, buf.Bytes())
|
||||
}
|
||||
|
||||
func showOrgProfile(c *context.Context) {
|
||||
c.SetParams(":org", c.Params(":username"))
|
||||
context.HandleOrgAssignment(c)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
org := c.Org.Organization
|
||||
c.Data["Title"] = org.FullName
|
||||
|
||||
page := c.QueryInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
var (
|
||||
repos []*db.Repository
|
||||
count int64
|
||||
err error
|
||||
)
|
||||
if c.IsLogged && !c.User.IsAdmin {
|
||||
repos, count, err = org.GetUserRepositories(c.User.ID, page, setting.UI.User.RepoPagingNum)
|
||||
if err != nil {
|
||||
c.Handle(500, "GetUserRepositories", err)
|
||||
return
|
||||
}
|
||||
c.Data["Repos"] = repos
|
||||
} else {
|
||||
showPrivate := c.IsLogged && c.User.IsAdmin
|
||||
repos, err = db.GetUserRepositories(&db.UserRepoOptions{
|
||||
UserID: org.ID,
|
||||
Private: showPrivate,
|
||||
Page: page,
|
||||
PageSize: setting.UI.User.RepoPagingNum,
|
||||
})
|
||||
if err != nil {
|
||||
c.Handle(500, "GetRepositories", err)
|
||||
return
|
||||
}
|
||||
c.Data["Repos"] = repos
|
||||
count = db.CountUserRepositories(org.ID, showPrivate)
|
||||
}
|
||||
c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
||||
|
||||
if err := org.GetMembers(); err != nil {
|
||||
c.Handle(500, "GetMembers", err)
|
||||
return
|
||||
}
|
||||
c.Data["Members"] = org.Members
|
||||
|
||||
c.Data["Teams"] = org.Teams
|
||||
|
||||
c.HTML(200, ORG_HOME)
|
||||
}
|
||||
|
||||
func Email2User(c *context.Context) {
|
||||
u, err := db.GetUserByEmail(c.Query("email"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetUserByEmail", errors.IsUserNotExist, err)
|
||||
return
|
||||
}
|
||||
c.Redirect(setting.AppSubURL + "/user/" + u.Name)
|
||||
}
|
||||
126
internal/route/user/profile.go
Normal file
126
internal/route/user/profile.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
repo2 "gogs.io/gogs/internal/route/repo"
|
||||
"strings"
|
||||
|
||||
"github.com/unknwon/paginater"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
FOLLOWERS = "user/meta/followers"
|
||||
STARS = "user/meta/stars"
|
||||
)
|
||||
|
||||
func Profile(c *context.Context, puser *context.ParamsUser) {
|
||||
isShowKeys := false
|
||||
if strings.HasSuffix(c.Params(":username"), ".keys") {
|
||||
isShowKeys = true
|
||||
}
|
||||
|
||||
// Show SSH keys.
|
||||
if isShowKeys {
|
||||
ShowSSHKeys(c, puser.ID)
|
||||
return
|
||||
}
|
||||
|
||||
if puser.IsOrganization() {
|
||||
showOrgProfile(c)
|
||||
return
|
||||
}
|
||||
|
||||
c.Title(puser.DisplayName())
|
||||
c.PageIs("UserProfile")
|
||||
c.Data["Owner"] = puser
|
||||
|
||||
orgs, err := db.GetOrgsByUserID(puser.ID, c.IsLogged && (c.User.IsAdmin || c.User.ID == puser.ID))
|
||||
if err != nil {
|
||||
c.ServerError("GetOrgsByUserIDDesc", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["Orgs"] = orgs
|
||||
|
||||
tab := c.Query("tab")
|
||||
c.Data["TabName"] = tab
|
||||
switch tab {
|
||||
case "activity":
|
||||
retrieveFeeds(c, puser.User, -1, true)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
default:
|
||||
page := c.QueryInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
showPrivate := c.IsLogged && (puser.ID == c.User.ID || c.User.IsAdmin)
|
||||
c.Data["Repos"], err = db.GetUserRepositories(&db.UserRepoOptions{
|
||||
UserID: puser.ID,
|
||||
Private: showPrivate,
|
||||
Page: page,
|
||||
PageSize: setting.UI.User.RepoPagingNum,
|
||||
})
|
||||
if err != nil {
|
||||
c.ServerError("GetRepositories", err)
|
||||
return
|
||||
}
|
||||
|
||||
count := db.CountUserRepositories(puser.ID, showPrivate)
|
||||
c.Data["Page"] = paginater.New(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
||||
}
|
||||
|
||||
c.Success(PROFILE)
|
||||
}
|
||||
|
||||
func Followers(c *context.Context, puser *context.ParamsUser) {
|
||||
c.Title(puser.DisplayName())
|
||||
c.PageIs("Followers")
|
||||
c.Data["CardsTitle"] = c.Tr("user.followers")
|
||||
c.Data["Owner"] = puser
|
||||
repo2.RenderUserCards(c, puser.NumFollowers, puser.GetFollowers, FOLLOWERS)
|
||||
}
|
||||
|
||||
func Following(c *context.Context, puser *context.ParamsUser) {
|
||||
c.Title(puser.DisplayName())
|
||||
c.PageIs("Following")
|
||||
c.Data["CardsTitle"] = c.Tr("user.following")
|
||||
c.Data["Owner"] = puser
|
||||
repo2.RenderUserCards(c, puser.NumFollowing, puser.GetFollowing, FOLLOWERS)
|
||||
}
|
||||
|
||||
func Stars(c *context.Context) {
|
||||
|
||||
}
|
||||
|
||||
func Action(c *context.Context, puser *context.ParamsUser) {
|
||||
var err error
|
||||
switch c.Params(":action") {
|
||||
case "follow":
|
||||
err = db.FollowUser(c.UserID(), puser.ID)
|
||||
case "unfollow":
|
||||
err = db.UnfollowUser(c.UserID(), puser.ID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ServerError(fmt.Sprintf("Action (%s)", c.Params(":action")), err)
|
||||
return
|
||||
}
|
||||
|
||||
redirectTo := c.Query("redirect_to")
|
||||
if !tool.IsSameSiteURLPath(redirectTo) {
|
||||
redirectTo = puser.HomeLink()
|
||||
}
|
||||
c.Redirect(redirectTo)
|
||||
}
|
||||
669
internal/route/user/setting.go
Normal file
669
internal/route/user/setting.go
Normal file
@@ -0,0 +1,669 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"gogs.io/gogs/internal/context"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/mailer"
|
||||
"gogs.io/gogs/internal/setting"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
const (
|
||||
SETTINGS_PROFILE = "user/settings/profile"
|
||||
SETTINGS_AVATAR = "user/settings/avatar"
|
||||
SETTINGS_PASSWORD = "user/settings/password"
|
||||
SETTINGS_EMAILS = "user/settings/email"
|
||||
SETTINGS_SSH_KEYS = "user/settings/sshkeys"
|
||||
SETTINGS_SECURITY = "user/settings/security"
|
||||
SETTINGS_TWO_FACTOR_ENABLE = "user/settings/two_factor_enable"
|
||||
SETTINGS_TWO_FACTOR_RECOVERY_CODES = "user/settings/two_factor_recovery_codes"
|
||||
SETTINGS_REPOSITORIES = "user/settings/repositories"
|
||||
SETTINGS_ORGANIZATIONS = "user/settings/organizations"
|
||||
SETTINGS_APPLICATIONS = "user/settings/applications"
|
||||
SETTINGS_DELETE = "user/settings/delete"
|
||||
NOTIFICATION = "user/notification"
|
||||
)
|
||||
|
||||
func Settings(c *context.Context) {
|
||||
c.Title("settings.profile")
|
||||
c.PageIs("SettingsProfile")
|
||||
c.Data["origin_name"] = c.User.Name
|
||||
c.Data["name"] = c.User.Name
|
||||
c.Data["full_name"] = c.User.FullName
|
||||
c.Data["email"] = c.User.Email
|
||||
c.Data["website"] = c.User.Website
|
||||
c.Data["location"] = c.User.Location
|
||||
c.Success(SETTINGS_PROFILE)
|
||||
}
|
||||
|
||||
func SettingsPost(c *context.Context, f form.UpdateProfile) {
|
||||
c.Title("settings.profile")
|
||||
c.PageIs("SettingsProfile")
|
||||
c.Data["origin_name"] = c.User.Name
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(SETTINGS_PROFILE)
|
||||
return
|
||||
}
|
||||
|
||||
// Non-local users are not allowed to change their username
|
||||
if c.User.IsLocal() {
|
||||
// Check if username characters have been changed
|
||||
if c.User.LowerName != strings.ToLower(f.Name) {
|
||||
if err := db.ChangeUserName(c.User, f.Name); err != nil {
|
||||
c.FormErr("Name")
|
||||
var msg string
|
||||
switch {
|
||||
case db.IsErrUserAlreadyExist(err):
|
||||
msg = c.Tr("form.username_been_taken")
|
||||
case db.IsErrEmailAlreadyUsed(err):
|
||||
msg = c.Tr("form.email_been_used")
|
||||
case db.IsErrNameReserved(err):
|
||||
msg = c.Tr("form.name_reserved")
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
msg = c.Tr("form.name_pattern_not_allowed")
|
||||
default:
|
||||
c.ServerError("ChangeUserName", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.RenderWithErr(msg, SETTINGS_PROFILE, &f)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Username changed: %s -> %s", c.User.Name, f.Name)
|
||||
}
|
||||
|
||||
// In case it's just a case change
|
||||
c.User.Name = f.Name
|
||||
c.User.LowerName = strings.ToLower(f.Name)
|
||||
}
|
||||
|
||||
c.User.FullName = f.FullName
|
||||
c.User.Email = f.Email
|
||||
c.User.Website = f.Website
|
||||
c.User.Location = f.Location
|
||||
if err := db.UpdateUser(c.User); err != nil {
|
||||
c.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("settings.update_profile_success"))
|
||||
c.SubURLRedirect("/user/settings")
|
||||
}
|
||||
|
||||
// FIXME: limit upload size
|
||||
func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxUser *db.User) error {
|
||||
ctxUser.UseCustomAvatar = f.Source == form.AVATAR_LOCAL
|
||||
if len(f.Gravatar) > 0 {
|
||||
ctxUser.Avatar = tool.MD5(f.Gravatar)
|
||||
ctxUser.AvatarEmail = f.Gravatar
|
||||
}
|
||||
|
||||
if f.Avatar != nil && f.Avatar.Filename != "" {
|
||||
r, err := f.Avatar.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("open avatar reader: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read avatar content: %v", err)
|
||||
}
|
||||
if !tool.IsImageFile(data) {
|
||||
return errors.New(c.Tr("settings.uploaded_avatar_not_a_image"))
|
||||
}
|
||||
if err = ctxUser.UploadAvatar(data); err != nil {
|
||||
return fmt.Errorf("upload avatar: %v", err)
|
||||
}
|
||||
} else {
|
||||
// No avatar is uploaded but setting has been changed to enable,
|
||||
// generate a random one when needed.
|
||||
if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) {
|
||||
if err := ctxUser.GenerateRandomAvatar(); err != nil {
|
||||
log.Error(2, "generate random avatar [%d]: %v", ctxUser.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.UpdateUser(ctxUser); err != nil {
|
||||
return fmt.Errorf("update user: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SettingsAvatar(c *context.Context) {
|
||||
c.Title("settings.avatar")
|
||||
c.PageIs("SettingsAvatar")
|
||||
c.Success(SETTINGS_AVATAR)
|
||||
}
|
||||
|
||||
func SettingsAvatarPost(c *context.Context, f form.Avatar) {
|
||||
if err := UpdateAvatarSetting(c, f, c.User); err != nil {
|
||||
c.Flash.Error(err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("settings.update_avatar_success"))
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/user/settings/avatar")
|
||||
}
|
||||
|
||||
func SettingsDeleteAvatar(c *context.Context) {
|
||||
if err := c.User.DeleteAvatar(); err != nil {
|
||||
c.Flash.Error(fmt.Sprintf("Failed to delete avatar: %v", err))
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/user/settings/avatar")
|
||||
}
|
||||
|
||||
func SettingsPassword(c *context.Context) {
|
||||
c.Title("settings.password")
|
||||
c.PageIs("SettingsPassword")
|
||||
c.Success(SETTINGS_PASSWORD)
|
||||
}
|
||||
|
||||
func SettingsPasswordPost(c *context.Context, f form.ChangePassword) {
|
||||
c.Title("settings.password")
|
||||
c.PageIs("SettingsPassword")
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(SETTINGS_PASSWORD)
|
||||
return
|
||||
}
|
||||
|
||||
if !c.User.ValidatePassword(f.OldPassword) {
|
||||
c.Flash.Error(c.Tr("settings.password_incorrect"))
|
||||
} else if f.Password != f.Retype {
|
||||
c.Flash.Error(c.Tr("form.password_not_match"))
|
||||
} else {
|
||||
c.User.Passwd = f.Password
|
||||
var err error
|
||||
if c.User.Salt, err = db.GetUserSalt(); err != nil {
|
||||
c.ServerError("GetUserSalt", err)
|
||||
return
|
||||
}
|
||||
c.User.EncodePasswd()
|
||||
if err := db.UpdateUser(c.User); err != nil {
|
||||
c.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
c.Flash.Success(c.Tr("settings.change_password_success"))
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/user/settings/password")
|
||||
}
|
||||
|
||||
func SettingsEmails(c *context.Context) {
|
||||
c.Title("settings.emails")
|
||||
c.PageIs("SettingsEmails")
|
||||
|
||||
emails, err := db.GetEmailAddresses(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
c.Data["Emails"] = emails
|
||||
|
||||
c.Success(SETTINGS_EMAILS)
|
||||
}
|
||||
|
||||
func SettingsEmailPost(c *context.Context, f form.AddEmail) {
|
||||
c.Title("settings.emails")
|
||||
c.PageIs("SettingsEmails")
|
||||
|
||||
// Make emailaddress primary.
|
||||
if c.Query("_method") == "PRIMARY" {
|
||||
if err := db.MakeEmailPrimary(&db.EmailAddress{ID: c.QueryInt64("id")}); err != nil {
|
||||
c.ServerError("MakeEmailPrimary", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/user/settings/email")
|
||||
return
|
||||
}
|
||||
|
||||
// Add Email address.
|
||||
emails, err := db.GetEmailAddresses(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
c.Data["Emails"] = emails
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(SETTINGS_EMAILS)
|
||||
return
|
||||
}
|
||||
|
||||
email := &db.EmailAddress{
|
||||
UID: c.User.ID,
|
||||
Email: f.Email,
|
||||
IsActivated: !setting.Service.RegisterEmailConfirm,
|
||||
}
|
||||
if err := db.AddEmailAddress(email); err != nil {
|
||||
if db.IsErrEmailAlreadyUsed(err) {
|
||||
c.RenderWithErr(c.Tr("form.email_been_used"), SETTINGS_EMAILS, &f)
|
||||
} else {
|
||||
c.ServerError("AddEmailAddress", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Send confirmation email
|
||||
if setting.Service.RegisterEmailConfirm {
|
||||
mailer.SendActivateEmailMail(c.Context, db.NewMailerUser(c.User), email.Email)
|
||||
|
||||
if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil {
|
||||
log.Error(2, "Set cache 'MailResendLimit' failed: %v", err)
|
||||
}
|
||||
c.Flash.Info(c.Tr("settings.add_email_confirmation_sent", email.Email, setting.Service.ActiveCodeLives/60))
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("settings.add_email_success"))
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/user/settings/email")
|
||||
}
|
||||
|
||||
func DeleteEmail(c *context.Context) {
|
||||
if err := db.DeleteEmailAddress(&db.EmailAddress{
|
||||
ID: c.QueryInt64("id"),
|
||||
UID: c.User.ID,
|
||||
}); err != nil {
|
||||
c.ServerError("DeleteEmailAddress", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("settings.email_deletion_success"))
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/email",
|
||||
})
|
||||
}
|
||||
|
||||
func SettingsSSHKeys(c *context.Context) {
|
||||
c.Title("settings.ssh_keys")
|
||||
c.PageIs("SettingsSSHKeys")
|
||||
|
||||
keys, err := db.ListPublicKeys(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("ListPublicKeys", err)
|
||||
return
|
||||
}
|
||||
c.Data["Keys"] = keys
|
||||
|
||||
c.Success(SETTINGS_SSH_KEYS)
|
||||
}
|
||||
|
||||
func SettingsSSHKeysPost(c *context.Context, f form.AddSSHKey) {
|
||||
c.Title("settings.ssh_keys")
|
||||
c.PageIs("SettingsSSHKeys")
|
||||
|
||||
keys, err := db.ListPublicKeys(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("ListPublicKeys", err)
|
||||
return
|
||||
}
|
||||
c.Data["Keys"] = keys
|
||||
|
||||
if c.HasError() {
|
||||
c.Success(SETTINGS_SSH_KEYS)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := db.CheckPublicKeyString(f.Content)
|
||||
if err != nil {
|
||||
if db.IsErrKeyUnableVerify(err) {
|
||||
c.Flash.Info(c.Tr("form.unable_verify_ssh_key"))
|
||||
} else {
|
||||
c.Flash.Error(c.Tr("form.invalid_ssh_key", err.Error()))
|
||||
c.SubURLRedirect("/user/settings/ssh")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = db.AddPublicKey(c.User.ID, f.Title, content); err != nil {
|
||||
c.Data["HasError"] = true
|
||||
switch {
|
||||
case db.IsErrKeyAlreadyExist(err):
|
||||
c.FormErr("Content")
|
||||
c.RenderWithErr(c.Tr("settings.ssh_key_been_used"), SETTINGS_SSH_KEYS, &f)
|
||||
case db.IsErrKeyNameAlreadyUsed(err):
|
||||
c.FormErr("Title")
|
||||
c.RenderWithErr(c.Tr("settings.ssh_key_name_used"), SETTINGS_SSH_KEYS, &f)
|
||||
default:
|
||||
c.ServerError("AddPublicKey", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("settings.add_key_success", f.Title))
|
||||
c.SubURLRedirect("/user/settings/ssh")
|
||||
}
|
||||
|
||||
func DeleteSSHKey(c *context.Context) {
|
||||
if err := db.DeletePublicKey(c.User, c.QueryInt64("id")); err != nil {
|
||||
c.Flash.Error("DeletePublicKey: " + err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("settings.ssh_key_deletion_success"))
|
||||
}
|
||||
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/ssh",
|
||||
})
|
||||
}
|
||||
|
||||
func SettingsSecurity(c *context.Context) {
|
||||
c.Title("settings.security")
|
||||
c.PageIs("SettingsSecurity")
|
||||
|
||||
t, err := db.GetTwoFactorByUserID(c.UserID())
|
||||
if err != nil && !errors.IsTwoFactorNotFound(err) {
|
||||
c.ServerError("GetTwoFactorByUserID", err)
|
||||
return
|
||||
}
|
||||
c.Data["TwoFactor"] = t
|
||||
|
||||
c.Success(SETTINGS_SECURITY)
|
||||
}
|
||||
|
||||
func SettingsTwoFactorEnable(c *context.Context) {
|
||||
if c.User.IsEnabledTwoFactor() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
c.Title("settings.two_factor_enable_title")
|
||||
c.PageIs("SettingsSecurity")
|
||||
|
||||
var key *otp.Key
|
||||
var err error
|
||||
keyURL := c.Session.Get("twoFactorURL")
|
||||
if keyURL != nil {
|
||||
key, _ = otp.NewKeyFromURL(keyURL.(string))
|
||||
}
|
||||
if key == nil {
|
||||
key, err = totp.Generate(totp.GenerateOpts{
|
||||
Issuer: setting.AppName,
|
||||
AccountName: c.User.Email,
|
||||
})
|
||||
if err != nil {
|
||||
c.ServerError("Generate", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Data["TwoFactorSecret"] = key.Secret()
|
||||
|
||||
img, err := key.Image(240, 240)
|
||||
if err != nil {
|
||||
c.ServerError("Image", err)
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err = png.Encode(&buf, img); err != nil {
|
||||
c.ServerError("Encode", err)
|
||||
return
|
||||
}
|
||||
c.Data["QRCode"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes()))
|
||||
|
||||
c.Session.Set("twoFactorSecret", c.Data["TwoFactorSecret"])
|
||||
c.Session.Set("twoFactorURL", key.String())
|
||||
c.Success(SETTINGS_TWO_FACTOR_ENABLE)
|
||||
}
|
||||
|
||||
func SettingsTwoFactorEnablePost(c *context.Context) {
|
||||
secret, ok := c.Session.Get("twoFactorSecret").(string)
|
||||
if !ok {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if !totp.Validate(c.Query("passcode"), secret) {
|
||||
c.Flash.Error(c.Tr("settings.two_factor_invalid_passcode"))
|
||||
c.SubURLRedirect("/user/settings/security/two_factor_enable")
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.NewTwoFactor(c.UserID(), secret); err != nil {
|
||||
c.Flash.Error(c.Tr("settings.two_factor_enable_error", err))
|
||||
c.SubURLRedirect("/user/settings/security/two_factor_enable")
|
||||
return
|
||||
}
|
||||
|
||||
c.Session.Delete("twoFactorSecret")
|
||||
c.Session.Delete("twoFactorURL")
|
||||
c.Flash.Success(c.Tr("settings.two_factor_enable_success"))
|
||||
c.SubURLRedirect("/user/settings/security/two_factor_recovery_codes")
|
||||
}
|
||||
|
||||
func SettingsTwoFactorRecoveryCodes(c *context.Context) {
|
||||
if !c.User.IsEnabledTwoFactor() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
c.Title("settings.two_factor_recovery_codes_title")
|
||||
c.PageIs("SettingsSecurity")
|
||||
|
||||
recoveryCodes, err := db.GetRecoveryCodesByUserID(c.UserID())
|
||||
if err != nil {
|
||||
c.ServerError("GetRecoveryCodesByUserID", err)
|
||||
return
|
||||
}
|
||||
c.Data["RecoveryCodes"] = recoveryCodes
|
||||
|
||||
c.Success(SETTINGS_TWO_FACTOR_RECOVERY_CODES)
|
||||
}
|
||||
|
||||
func SettingsTwoFactorRecoveryCodesPost(c *context.Context) {
|
||||
if !c.User.IsEnabledTwoFactor() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.RegenerateRecoveryCodes(c.UserID()); err != nil {
|
||||
c.Flash.Error(c.Tr("settings.two_factor_regenerate_recovery_codes_error", err))
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("settings.two_factor_regenerate_recovery_codes_success"))
|
||||
}
|
||||
|
||||
c.SubURLRedirect("/user/settings/security/two_factor_recovery_codes")
|
||||
}
|
||||
|
||||
func SettingsTwoFactorDisable(c *context.Context) {
|
||||
if !c.User.IsEnabledTwoFactor() {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DeleteTwoFactor(c.UserID()); err != nil {
|
||||
c.ServerError("DeleteTwoFactor", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("settings.two_factor_disable_success"))
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/security",
|
||||
})
|
||||
}
|
||||
|
||||
func SettingsRepos(c *context.Context) {
|
||||
c.Title("settings.repos")
|
||||
c.PageIs("SettingsRepositories")
|
||||
|
||||
repos, err := db.GetUserAndCollaborativeRepositories(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("GetUserAndCollaborativeRepositories", err)
|
||||
return
|
||||
}
|
||||
if err = db.RepositoryList(repos).LoadAttributes(); err != nil {
|
||||
c.ServerError("LoadAttributes", err)
|
||||
return
|
||||
}
|
||||
c.Data["Repos"] = repos
|
||||
|
||||
c.Success(SETTINGS_REPOSITORIES)
|
||||
}
|
||||
|
||||
func SettingsLeaveRepo(c *context.Context) {
|
||||
repo, err := db.GetRepositoryByID(c.QueryInt64("id"))
|
||||
if err != nil {
|
||||
c.NotFoundOrServerError("GetRepositoryByID", errors.IsRepoNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = repo.DeleteCollaboration(c.User.ID); err != nil {
|
||||
c.ServerError("DeleteCollaboration", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("settings.repos.leave_success", repo.FullName()))
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/repositories",
|
||||
})
|
||||
}
|
||||
|
||||
func SettingsOrganizations(c *context.Context) {
|
||||
c.Title("settings.orgs")
|
||||
c.PageIs("SettingsOrganizations")
|
||||
|
||||
orgs, err := db.GetOrgsByUserID(c.User.ID, true)
|
||||
if err != nil {
|
||||
c.ServerError("GetOrgsByUserID", err)
|
||||
return
|
||||
}
|
||||
c.Data["Orgs"] = orgs
|
||||
|
||||
c.Success(SETTINGS_ORGANIZATIONS)
|
||||
}
|
||||
|
||||
func SettingsLeaveOrganization(c *context.Context) {
|
||||
if err := db.RemoveOrgUser(c.QueryInt64("id"), c.User.ID); err != nil {
|
||||
if db.IsErrLastOrgOwner(err) {
|
||||
c.Flash.Error(c.Tr("form.last_org_owner"))
|
||||
} else {
|
||||
c.ServerError("RemoveOrgUser", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/organizations",
|
||||
})
|
||||
}
|
||||
|
||||
func SettingsApplications(c *context.Context) {
|
||||
c.Title("settings.applications")
|
||||
c.PageIs("SettingsApplications")
|
||||
|
||||
tokens, err := db.ListAccessTokens(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("ListAccessTokens", err)
|
||||
return
|
||||
}
|
||||
c.Data["Tokens"] = tokens
|
||||
|
||||
c.Success(SETTINGS_APPLICATIONS)
|
||||
}
|
||||
|
||||
func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) {
|
||||
c.Title("settings.applications")
|
||||
c.PageIs("SettingsApplications")
|
||||
|
||||
if c.HasError() {
|
||||
tokens, err := db.ListAccessTokens(c.User.ID)
|
||||
if err != nil {
|
||||
c.ServerError("ListAccessTokens", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["Tokens"] = tokens
|
||||
c.Success(SETTINGS_APPLICATIONS)
|
||||
return
|
||||
}
|
||||
|
||||
t := &db.AccessToken{
|
||||
UID: c.User.ID,
|
||||
Name: f.Name,
|
||||
}
|
||||
if err := db.NewAccessToken(t); err != nil {
|
||||
if errors.IsAccessTokenNameAlreadyExist(err) {
|
||||
c.Flash.Error(c.Tr("settings.token_name_exists"))
|
||||
c.SubURLRedirect("/user/settings/applications")
|
||||
} else {
|
||||
c.ServerError("NewAccessToken", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.Flash.Success(c.Tr("settings.generate_token_succees"))
|
||||
c.Flash.Info(t.Sha1)
|
||||
c.SubURLRedirect("/user/settings/applications")
|
||||
}
|
||||
|
||||
func SettingsDeleteApplication(c *context.Context) {
|
||||
if err := db.DeleteAccessTokenOfUserByID(c.User.ID, c.QueryInt64("id")); err != nil {
|
||||
c.Flash.Error("DeleteAccessTokenByID: " + err.Error())
|
||||
} else {
|
||||
c.Flash.Success(c.Tr("settings.delete_token_success"))
|
||||
}
|
||||
|
||||
c.JSONSuccess(map[string]interface{}{
|
||||
"redirect": setting.AppSubURL + "/user/settings/applications",
|
||||
})
|
||||
}
|
||||
|
||||
func SettingsDelete(c *context.Context) {
|
||||
c.Title("settings.delete")
|
||||
c.PageIs("SettingsDelete")
|
||||
|
||||
if c.Req.Method == "POST" {
|
||||
if _, err := db.UserLogin(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil)
|
||||
} else {
|
||||
c.ServerError("UserLogin", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.DeleteUser(c.User); err != nil {
|
||||
switch {
|
||||
case db.IsErrUserOwnRepos(err):
|
||||
c.Flash.Error(c.Tr("form.still_own_repo"))
|
||||
c.Redirect(setting.AppSubURL + "/user/settings/delete")
|
||||
case db.IsErrUserHasOrgs(err):
|
||||
c.Flash.Error(c.Tr("form.still_has_org"))
|
||||
c.Redirect(setting.AppSubURL + "/user/settings/delete")
|
||||
default:
|
||||
c.ServerError("DeleteUser", err)
|
||||
}
|
||||
} else {
|
||||
log.Trace("Account deleted: %s", c.User.Name)
|
||||
c.Redirect(setting.AppSubURL + "/")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.Success(SETTINGS_DELETE)
|
||||
}
|
||||
Reference in New Issue
Block a user