mirror of
https://github.com/go-gitea/gitea
synced 2025-12-07 13:28:25 +00:00
Merge branch 'master' into fix-6409
This commit is contained in:
+323
-130
@@ -7,21 +7,25 @@ package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
// Needed for jpeg support
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/avatar"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
@@ -32,12 +36,10 @@ import (
|
||||
"code.gitea.io/gitea/modules/sync"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/builder"
|
||||
"github.com/go-xorm/xorm"
|
||||
version "github.com/mcuadros/go-version"
|
||||
ini "gopkg.in/ini.v1"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
var repoWorkingPool = sync.NewExclusivePool()
|
||||
@@ -67,8 +69,8 @@ var (
|
||||
ItemsPerPage = 40
|
||||
)
|
||||
|
||||
// LoadRepoConfig loads the repository config
|
||||
func LoadRepoConfig() {
|
||||
// loadRepoConfig loads the repository config
|
||||
func loadRepoConfig() {
|
||||
// Load .gitignore and license files and readme templates.
|
||||
types := []string{"gitignore", "license", "readme", "label"}
|
||||
typeFiles := make([][]string, 4)
|
||||
@@ -119,45 +121,7 @@ func LoadRepoConfig() {
|
||||
|
||||
// NewRepoContext creates a new repository context
|
||||
func NewRepoContext() {
|
||||
zip.Verbose = false
|
||||
|
||||
// Check Git installation.
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
log.Fatal("Failed to test 'git' command: %v (forgotten install?)", err)
|
||||
}
|
||||
|
||||
// Check Git version.
|
||||
var err error
|
||||
setting.Git.Version, err = git.BinVersion()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to get Git version: %v", err)
|
||||
}
|
||||
|
||||
log.Info("Git Version: %s", setting.Git.Version)
|
||||
if version.Compare("1.7.1", setting.Git.Version, ">") {
|
||||
log.Fatal("Gitea requires Git version greater or equal to 1.7.1")
|
||||
}
|
||||
|
||||
// Git requires setting user.name and user.email in order to commit changes.
|
||||
for configKey, defaultValue := range map[string]string{"user.name": "Gitea", "user.email": "gitea@fake.local"} {
|
||||
if stdout, stderr, err := process.GetManager().Exec("NewRepoContext(get setting)", "git", "config", "--get", configKey); err != nil || strings.TrimSpace(stdout) == "" {
|
||||
// ExitError indicates this config is not set
|
||||
if _, ok := err.(*exec.ExitError); ok || strings.TrimSpace(stdout) == "" {
|
||||
if _, stderr, gerr := process.GetManager().Exec("NewRepoContext(set "+configKey+")", "git", "config", "--global", configKey, defaultValue); gerr != nil {
|
||||
log.Fatal("Failed to set git %s(%s): %s", configKey, gerr, stderr)
|
||||
}
|
||||
log.Info("Git config %s set to %s", configKey, defaultValue)
|
||||
} else {
|
||||
log.Fatal("Failed to get git %s(%s): %s", configKey, err, stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set git some configurations.
|
||||
if _, stderr, err := process.GetManager().Exec("NewRepoContext(git config --global core.quotepath false)",
|
||||
"git", "config", "--global", "core.quotepath", "false"); err != nil {
|
||||
log.Fatal("Failed to execute 'git config --global core.quotepath false': %s", stderr)
|
||||
}
|
||||
loadRepoConfig()
|
||||
|
||||
RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp"))
|
||||
}
|
||||
@@ -172,6 +136,7 @@ type Repository struct {
|
||||
Name string `xorm:"INDEX NOT NULL"`
|
||||
Description string
|
||||
Website string
|
||||
OriginalURL string
|
||||
DefaultBranch string
|
||||
|
||||
NumWatches int
|
||||
@@ -207,6 +172,9 @@ type Repository struct {
|
||||
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Topics []string `xorm:"TEXT JSON"`
|
||||
|
||||
// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
|
||||
Avatar string `xorm:"VARCHAR(64)"`
|
||||
|
||||
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
@@ -306,31 +274,64 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
|
||||
parent = repo.BaseRepo.innerAPIFormat(e, mode, true)
|
||||
}
|
||||
}
|
||||
hasIssues := false
|
||||
if _, err := repo.getUnit(e, UnitTypeIssues); err == nil {
|
||||
hasIssues = true
|
||||
}
|
||||
hasWiki := false
|
||||
if _, err := repo.getUnit(e, UnitTypeWiki); err == nil {
|
||||
hasWiki = true
|
||||
}
|
||||
hasPullRequests := false
|
||||
ignoreWhitespaceConflicts := false
|
||||
allowMerge := false
|
||||
allowRebase := false
|
||||
allowRebaseMerge := false
|
||||
allowSquash := false
|
||||
if unit, err := repo.getUnit(e, UnitTypePullRequests); err == nil {
|
||||
config := unit.PullRequestsConfig()
|
||||
hasPullRequests = true
|
||||
ignoreWhitespaceConflicts = config.IgnoreWhitespaceConflicts
|
||||
allowMerge = config.AllowMerge
|
||||
allowRebase = config.AllowRebase
|
||||
allowRebaseMerge = config.AllowRebaseMerge
|
||||
allowSquash = config.AllowSquash
|
||||
}
|
||||
|
||||
return &api.Repository{
|
||||
ID: repo.ID,
|
||||
Owner: repo.Owner.APIFormat(),
|
||||
Name: repo.Name,
|
||||
FullName: repo.FullName(),
|
||||
Description: repo.Description,
|
||||
Private: repo.IsPrivate,
|
||||
Empty: repo.IsEmpty,
|
||||
Archived: repo.IsArchived,
|
||||
Size: int(repo.Size / 1024),
|
||||
Fork: repo.IsFork,
|
||||
Parent: parent,
|
||||
Mirror: repo.IsMirror,
|
||||
HTMLURL: repo.HTMLURL(),
|
||||
SSHURL: cloneLink.SSH,
|
||||
CloneURL: cloneLink.HTTPS,
|
||||
Website: repo.Website,
|
||||
Stars: repo.NumStars,
|
||||
Forks: repo.NumForks,
|
||||
Watchers: repo.NumWatches,
|
||||
OpenIssues: repo.NumOpenIssues,
|
||||
DefaultBranch: repo.DefaultBranch,
|
||||
Created: repo.CreatedUnix.AsTime(),
|
||||
Updated: repo.UpdatedUnix.AsTime(),
|
||||
Permissions: permission,
|
||||
ID: repo.ID,
|
||||
Owner: repo.Owner.APIFormat(),
|
||||
Name: repo.Name,
|
||||
FullName: repo.FullName(),
|
||||
Description: repo.Description,
|
||||
Private: repo.IsPrivate,
|
||||
Empty: repo.IsEmpty,
|
||||
Archived: repo.IsArchived,
|
||||
Size: int(repo.Size / 1024),
|
||||
Fork: repo.IsFork,
|
||||
Parent: parent,
|
||||
Mirror: repo.IsMirror,
|
||||
HTMLURL: repo.HTMLURL(),
|
||||
SSHURL: cloneLink.SSH,
|
||||
CloneURL: cloneLink.HTTPS,
|
||||
Website: repo.Website,
|
||||
Stars: repo.NumStars,
|
||||
Forks: repo.NumForks,
|
||||
Watchers: repo.NumWatches,
|
||||
OpenIssues: repo.NumOpenIssues,
|
||||
DefaultBranch: repo.DefaultBranch,
|
||||
Created: repo.CreatedUnix.AsTime(),
|
||||
Updated: repo.UpdatedUnix.AsTime(),
|
||||
Permissions: permission,
|
||||
HasIssues: hasIssues,
|
||||
HasWiki: hasWiki,
|
||||
HasPullRequests: hasPullRequests,
|
||||
IgnoreWhitespaceConflicts: ignoreWhitespaceConflicts,
|
||||
AllowMerge: allowMerge,
|
||||
AllowRebase: allowRebase,
|
||||
AllowRebaseMerge: allowRebaseMerge,
|
||||
AllowSquash: allowSquash,
|
||||
AvatarURL: repo.avatarLink(e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,10 +378,20 @@ func (repo *Repository) UnitEnabled(tp UnitType) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrUnitNotExist organization does not exist
|
||||
ErrUnitNotExist = errors.New("Unit does not exist")
|
||||
)
|
||||
// ErrUnitTypeNotExist represents a "UnitTypeNotExist" kind of error.
|
||||
type ErrUnitTypeNotExist struct {
|
||||
UT UnitType
|
||||
}
|
||||
|
||||
// IsErrUnitTypeNotExist checks if an error is a ErrUnitNotExist.
|
||||
func IsErrUnitTypeNotExist(err error) bool {
|
||||
_, ok := err.(ErrUnitTypeNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUnitTypeNotExist) Error() string {
|
||||
return fmt.Sprintf("Unit type does not exist: %s", err.UT.String())
|
||||
}
|
||||
|
||||
// MustGetUnit always returns a RepoUnit object
|
||||
func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit {
|
||||
@@ -404,6 +415,11 @@ func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit {
|
||||
Type: tp,
|
||||
Config: new(PullRequestsConfig),
|
||||
}
|
||||
} else if tp == UnitTypeIssues {
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: new(IssuesConfig),
|
||||
}
|
||||
}
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
@@ -425,7 +441,7 @@ func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) {
|
||||
return unit, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnitNotExist
|
||||
return nil, ErrUnitTypeNotExist{tp}
|
||||
}
|
||||
|
||||
func (repo *Repository) getOwner(e Engine) (err error) {
|
||||
@@ -728,17 +744,6 @@ func (repo *Repository) getUsersWithAccessMode(e Engine, mode AccessMode) (_ []*
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// NextIssueIndex returns the next issue index
|
||||
// FIXME: should have a mutex to prevent producing same index for two issues that are created
|
||||
// closely enough.
|
||||
func (repo *Repository) NextIssueIndex() int64 {
|
||||
return int64(repo.NumIssues+repo.NumPulls) + 1
|
||||
}
|
||||
|
||||
var (
|
||||
descPattern = regexp.MustCompile(`https?://\S+`)
|
||||
)
|
||||
|
||||
// DescriptionHTML does special handles to description and return HTML string.
|
||||
func (repo *Repository) DescriptionHTML() template.HTML {
|
||||
desc, err := markup.RenderDescriptionHTML([]byte(repo.Description), repo.HTMLURL(), repo.ComposeMetas())
|
||||
@@ -841,12 +846,14 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
|
||||
|
||||
// MigrateRepoOptions contains the repository migrate options
|
||||
type MigrateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
RemoteAddr string
|
||||
Wiki bool // include wiki repository
|
||||
Name string
|
||||
Description string
|
||||
OriginalURL string
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
RemoteAddr string
|
||||
Wiki bool // include wiki repository
|
||||
SyncReleasesWithTags bool // sync releases from tags
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -873,6 +880,7 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
|
||||
repo, err := CreateRepository(doer, u, CreateRepoOptions{
|
||||
Name: opts.Name,
|
||||
Description: opts.Description,
|
||||
OriginalURL: opts.OriginalURL,
|
||||
IsPrivate: opts.IsPrivate,
|
||||
IsMirror: opts.IsMirror,
|
||||
})
|
||||
@@ -928,22 +936,18 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if repository is empty.
|
||||
_, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1")
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "fatal: bad default revision 'HEAD'") {
|
||||
repo.IsEmpty = true
|
||||
} else {
|
||||
return repo, fmt.Errorf("check empty: %v - %s", err, stderr)
|
||||
}
|
||||
return repo, fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
|
||||
if !repo.IsEmpty {
|
||||
repo.IsEmpty, err = gitRepo.IsEmpty()
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("git.IsEmpty: %v", err)
|
||||
}
|
||||
|
||||
if opts.SyncReleasesWithTags && !repo.IsEmpty {
|
||||
// Try to get HEAD branch and set it as default branch.
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
headBranch, err := gitRepo.GetHEADBranch()
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("GetHEADBranch: %v", err)
|
||||
@@ -1068,20 +1072,20 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
|
||||
var stderr string
|
||||
if _, stderr, err = process.GetManager().ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git add): %s", tmpPath),
|
||||
"git", "add", "--all"); err != nil {
|
||||
git.GitExecutable, "add", "--all"); err != nil {
|
||||
return fmt.Errorf("git add: %s", stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.GetManager().ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath),
|
||||
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
|
||||
git.GitExecutable, "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
|
||||
"-m", "Initial commit"); err != nil {
|
||||
return fmt.Errorf("git commit: %s", stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.GetManager().ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath),
|
||||
"git", "push", "origin", "master"); err != nil {
|
||||
git.GitExecutable, "push", "origin", "master"); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
return nil
|
||||
@@ -1091,6 +1095,7 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
|
||||
type CreateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
OriginalURL string
|
||||
Gitignores string
|
||||
License string
|
||||
Readme string
|
||||
@@ -1127,7 +1132,7 @@ func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts
|
||||
// Clone to temporary path and do the init commit.
|
||||
_, stderr, err := process.GetManager().Exec(
|
||||
fmt.Sprintf("initRepository(git clone): %s", repoPath),
|
||||
"git", "clone", repoPath, tmpDir,
|
||||
git.GitExecutable, "clone", repoPath, tmpDir,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git clone: %v - %s", err, stderr)
|
||||
@@ -1270,8 +1275,8 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
|
||||
}
|
||||
|
||||
// insert units for repo
|
||||
var units = make([]RepoUnit, 0, len(defaultRepoUnits))
|
||||
for _, tp := range defaultRepoUnits {
|
||||
var units = make([]RepoUnit, 0, len(DefaultRepoUnits))
|
||||
for _, tp := range DefaultRepoUnits {
|
||||
if tp == UnitTypeIssues {
|
||||
units = append(units, RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
@@ -1329,11 +1334,9 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
|
||||
return fmt.Errorf("prepareWebhooks: %v", err)
|
||||
}
|
||||
go HookQueue.Add(repo.ID)
|
||||
} else {
|
||||
} else if err = repo.recalculateAccesses(e); err != nil {
|
||||
// Organization automatically called this in addRepository method.
|
||||
if err = repo.recalculateAccesses(e); err != nil {
|
||||
return fmt.Errorf("recalculateAccesses: %v", err)
|
||||
}
|
||||
return fmt.Errorf("recalculateAccesses: %v", err)
|
||||
}
|
||||
|
||||
if setting.Service.AutoWatchNewRepos {
|
||||
@@ -1364,6 +1367,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
|
||||
Name: opts.Name,
|
||||
LowerName: strings.ToLower(opts.Name),
|
||||
Description: opts.Description,
|
||||
OriginalURL: opts.OriginalURL,
|
||||
IsPrivate: opts.IsPrivate,
|
||||
IsFsckEnabled: !opts.IsMirror,
|
||||
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
|
||||
@@ -1393,7 +1397,7 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
|
||||
|
||||
_, stderr, err := process.GetManager().ExecDir(-1,
|
||||
repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath),
|
||||
"git", "update-server-info")
|
||||
git.GitExecutable, "update-server-info")
|
||||
if err != nil {
|
||||
return nil, errors.New("CreateRepository(git update-server-info): " + stderr)
|
||||
}
|
||||
@@ -1512,11 +1516,9 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if err = repo.recalculateAccesses(sess); err != nil {
|
||||
// Organization called this in addRepository method.
|
||||
if err = repo.recalculateAccesses(sess); err != nil {
|
||||
return fmt.Errorf("recalculateAccesses: %v", err)
|
||||
}
|
||||
return fmt.Errorf("recalculateAccesses: %v", err)
|
||||
}
|
||||
|
||||
// Update repository count.
|
||||
@@ -1864,7 +1866,10 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
repoPath := repo.repoPath(sess)
|
||||
removeAllWithNotice(sess, "Delete repository files", repoPath)
|
||||
|
||||
repo.deleteWiki(sess)
|
||||
err = repo.deleteWiki(sess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove attachment files.
|
||||
for i := range attachmentPaths {
|
||||
@@ -1888,10 +1893,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
}
|
||||
|
||||
oidPath := filepath.Join(v.Oid[0:2], v.Oid[2:4], v.Oid[4:len(v.Oid)])
|
||||
err = os.Remove(filepath.Join(setting.LFS.ContentPath, oidPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
removeAllWithNotice(sess, "Delete orphaned LFS file", oidPath)
|
||||
}
|
||||
|
||||
if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil {
|
||||
@@ -1926,6 +1928,15 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
go HookQueue.Add(repo.ID)
|
||||
}
|
||||
|
||||
if len(repo.Avatar) > 0 {
|
||||
avatarPath := repo.CustomAvatarPath()
|
||||
if com.IsExist(avatarPath) {
|
||||
if err := os.Remove(avatarPath); err != nil {
|
||||
return fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeleteRepoFromIndexer(repo)
|
||||
return nil
|
||||
}
|
||||
@@ -2239,7 +2250,7 @@ func GitGcRepos() error {
|
||||
_, stderr, err := process.GetManager().ExecDir(
|
||||
time.Duration(setting.Git.Timeout.GC)*time.Second,
|
||||
RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection",
|
||||
"git", args...)
|
||||
git.GitExecutable, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %v", err, stderr)
|
||||
}
|
||||
@@ -2392,9 +2403,10 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R
|
||||
return nil, err
|
||||
}
|
||||
if forkedRepo != nil {
|
||||
return nil, ErrRepoAlreadyExist{
|
||||
Uname: u.Name,
|
||||
Name: forkedRepo.Name,
|
||||
return nil, ErrForkAlreadyExist{
|
||||
Uname: u.Name,
|
||||
RepoName: oldRepo.FullName(),
|
||||
ForkName: forkedRepo.FullName(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2428,14 +2440,14 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R
|
||||
repoPath := RepoPath(u.Name, repo.Name)
|
||||
_, stderr, err := process.GetManager().ExecTimeout(10*time.Minute,
|
||||
fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name),
|
||||
"git", "clone", "--bare", oldRepo.repoPath(sess), repoPath)
|
||||
git.GitExecutable, "clone", "--bare", oldRepo.repoPath(sess), repoPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("git clone: %v", stderr)
|
||||
}
|
||||
|
||||
_, stderr, err = process.GetManager().ExecDir(-1,
|
||||
repoPath, fmt.Sprintf("ForkRepository(git update-server-info): %s", repoPath),
|
||||
"git", "update-server-info")
|
||||
git.GitExecutable, "update-server-info")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("git update-server-info: %v", stderr)
|
||||
}
|
||||
@@ -2509,3 +2521,184 @@ func (repo *Repository) GetUserFork(userID int64) (*Repository, error) {
|
||||
}
|
||||
return &forkedRepo, nil
|
||||
}
|
||||
|
||||
// CustomAvatarPath returns repository custom avatar file path.
|
||||
func (repo *Repository) CustomAvatarPath() string {
|
||||
// Avatar empty by default
|
||||
if len(repo.Avatar) == 0 {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(setting.RepositoryAvatarUploadPath, repo.Avatar)
|
||||
}
|
||||
|
||||
// generateRandomAvatar generates a random avatar for repository.
|
||||
func (repo *Repository) generateRandomAvatar(e Engine) error {
|
||||
idToString := fmt.Sprintf("%d", repo.ID)
|
||||
|
||||
seed := idToString
|
||||
img, err := avatar.RandomImage([]byte(seed))
|
||||
if err != nil {
|
||||
return fmt.Errorf("RandomImage: %v", err)
|
||||
}
|
||||
|
||||
repo.Avatar = idToString
|
||||
if err = os.MkdirAll(filepath.Dir(repo.CustomAvatarPath()), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("MkdirAll: %v", err)
|
||||
}
|
||||
fw, err := os.Create(repo.CustomAvatarPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Create: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = png.Encode(fw, img); err != nil {
|
||||
return fmt.Errorf("Encode: %v", err)
|
||||
}
|
||||
log.Info("New random avatar created for repository: %d", repo.ID)
|
||||
|
||||
if _, err := e.ID(repo.ID).Cols("avatar").NoAutoTime().Update(repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories
|
||||
func RemoveRandomAvatars() error {
|
||||
return x.
|
||||
Where("id > 0").BufferSize(setting.IterateBufferSize).
|
||||
Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repository := bean.(*Repository)
|
||||
stringifiedID := strconv.FormatInt(repository.ID, 10)
|
||||
if repository.Avatar == stringifiedID {
|
||||
return repository.DeleteAvatar()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// RelAvatarLink returns a relative link to the repository's avatar.
|
||||
func (repo *Repository) RelAvatarLink() string {
|
||||
return repo.relAvatarLink(x)
|
||||
}
|
||||
|
||||
func (repo *Repository) relAvatarLink(e Engine) string {
|
||||
// If no avatar - path is empty
|
||||
avatarPath := repo.CustomAvatarPath()
|
||||
if len(avatarPath) == 0 || !com.IsFile(avatarPath) {
|
||||
switch mode := setting.RepositoryAvatarFallback; mode {
|
||||
case "image":
|
||||
return setting.RepositoryAvatarFallbackImage
|
||||
case "random":
|
||||
if err := repo.generateRandomAvatar(e); err != nil {
|
||||
log.Error("generateRandomAvatar: %v", err)
|
||||
}
|
||||
default:
|
||||
// default behaviour: do not display avatar
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return setting.AppSubURL + "/repo-avatars/" + repo.Avatar
|
||||
}
|
||||
|
||||
// avatarLink returns user avatar absolute link.
|
||||
func (repo *Repository) avatarLink(e Engine) string {
|
||||
link := repo.relAvatarLink(e)
|
||||
// link may be empty!
|
||||
if len(link) > 0 {
|
||||
if link[0] == '/' && link[1] != '/' {
|
||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:]
|
||||
}
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
// UploadAvatar saves custom avatar for repository.
|
||||
// FIXME: split uploads to different subdirs in case we have massive number of repos.
|
||||
func (repo *Repository) UploadAvatar(data []byte) error {
|
||||
m, err := avatar.Prepare(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldAvatarPath := repo.CustomAvatarPath()
|
||||
|
||||
// Users can upload the same image to other repo - prefix it with ID
|
||||
// Then repo will be removed - only it avatar file will be removed
|
||||
repo.Avatar = fmt.Sprintf("%d-%x", repo.ID, md5.Sum(data))
|
||||
if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: Update repository avatar: %v", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(setting.RepositoryAvatarUploadPath, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: Failed to create dir %s: %v", setting.RepositoryAvatarUploadPath, err)
|
||||
}
|
||||
|
||||
fw, err := os.Create(repo.CustomAvatarPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("UploadAvatar: Create file: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = png.Encode(fw, *m); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: Encode png: %v", err)
|
||||
}
|
||||
|
||||
if len(oldAvatarPath) > 0 && oldAvatarPath != repo.CustomAvatarPath() {
|
||||
if err := os.Remove(oldAvatarPath); err != nil {
|
||||
return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %v", oldAvatarPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// DeleteAvatar deletes the repos's custom avatar.
|
||||
func (repo *Repository) DeleteAvatar() error {
|
||||
|
||||
// Avatar not exists
|
||||
if len(repo.Avatar) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
avatarPath := repo.CustomAvatarPath()
|
||||
log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath)
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo.Avatar = ""
|
||||
if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil {
|
||||
return fmt.Errorf("DeleteAvatar: Update repository avatar: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(avatarPath); err == nil {
|
||||
if err := os.Remove(avatarPath); err != nil {
|
||||
return fmt.Errorf("DeleteAvatar: Failed to remove %s: %v", avatarPath, err)
|
||||
}
|
||||
} else {
|
||||
// // Schrodinger: file may or may not exist. See err for details.
|
||||
log.Trace("DeleteAvatar[%d]: %v", err)
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// GetOriginalURLHostname returns the hostname of a URL or the URL
|
||||
func (repo *Repository) GetOriginalURLHostname() string {
|
||||
u, err := url.Parse(repo.OriginalURL)
|
||||
if err != nil {
|
||||
return repo.OriginalURL
|
||||
}
|
||||
|
||||
return u.Host
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user