From 4eb5f80e9e6171729a88a8ad27a65db12c62649c Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 13 Feb 2019 12:45:53 -0600 Subject: [PATCH 1/7] Add optional label sets on repo creation --- models/repo.go | 5057 ++++++++++++++++--------------- modules/auth/repo_form.go | 1209 ++++---- options/locale/locale_en-US.ini | 3548 +++++++++++----------- routers/repo/repo.go | 835 ++--- templates/repo/create.tmpl | 245 +- 5 files changed, 5469 insertions(+), 5425 deletions(-) diff --git a/models/repo.go b/models/repo.go index 848a76fe883b4..07345edc8c5a8 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1,2516 +1,2541 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Copyright 2017 The Gitea 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 models - -import ( - "bytes" - "errors" - "fmt" - "html/template" - "io/ioutil" - "net/url" - "os" - "os/exec" - "path" - "path/filepath" - "regexp" - "sort" - "strings" - "time" - - "code.gitea.io/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/process" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/sync" - "code.gitea.io/gitea/modules/util" - api "code.gitea.io/sdk/gitea" - - "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" -) - -var repoWorkingPool = sync.NewExclusivePool() - -var ( - // ErrMirrorNotExist mirror does not exist error - ErrMirrorNotExist = errors.New("Mirror does not exist") - - // ErrNameEmpty name is empty error - ErrNameEmpty = errors.New("Name is empty") -) - -var ( - // Gitignores contains the gitiginore files - Gitignores []string - - // Licenses contains the license files - Licenses []string - - // Readmes contains the readme files - Readmes []string - - // LabelTemplates contains the label template files - LabelTemplates []string - - // ItemsPerPage maximum items per page in forks, watchers and stars of a repo - ItemsPerPage = 40 -) - -// 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) - for i, t := range types { - files, err := options.Dir(t) - if err != nil { - log.Fatal(4, "Failed to get %s files: %v", t, err) - } - customPath := path.Join(setting.CustomPath, "options", t) - if com.IsDir(customPath) { - customFiles, err := com.StatDir(customPath) - if err != nil { - log.Fatal(4, "Failed to get custom %s files: %v", t, err) - } - - for _, f := range customFiles { - if !com.IsSliceContainsStr(files, f) { - files = append(files, f) - } - } - } - typeFiles[i] = files - } - - Gitignores = typeFiles[0] - Licenses = typeFiles[1] - Readmes = typeFiles[2] - LabelTemplates = typeFiles[3] - sort.Strings(Gitignores) - sort.Strings(Licenses) - sort.Strings(Readmes) - sort.Strings(LabelTemplates) - - // Filter out invalid names and promote preferred licenses. - sortedLicenses := make([]string, 0, len(Licenses)) - for _, name := range setting.Repository.PreferredLicenses { - if com.IsSliceContainsStr(Licenses, name) { - sortedLicenses = append(sortedLicenses, name) - } - } - for _, name := range Licenses { - if !com.IsSliceContainsStr(setting.Repository.PreferredLicenses, name) { - sortedLicenses = append(sortedLicenses, name) - } - } - Licenses = sortedLicenses -} - -// NewRepoContext creates a new repository context -func NewRepoContext() { - zip.Verbose = false - - // Check Git installation. - if _, err := exec.LookPath("git"); err != nil { - log.Fatal(4, "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(4, "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(4, "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(4, "Failed to set git %s(%s): %s", configKey, gerr, stderr) - } - log.Info("Git config %s set to %s", configKey, defaultValue) - } else { - log.Fatal(4, "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(4, "Failed to execute 'git config --global core.quotepath false': %s", stderr) - } - - RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) -} - -// Repository represents a git repository. -type Repository struct { - ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"UNIQUE(s)"` - OwnerName string `xorm:"-"` - Owner *User `xorm:"-"` - LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"INDEX NOT NULL"` - Description string - Website string - DefaultBranch string - - NumWatches int - NumStars int - NumForks int - NumIssues int - NumClosedIssues int - NumOpenIssues int `xorm:"-"` - NumPulls int - NumClosedPulls int - NumOpenPulls int `xorm:"-"` - NumMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumOpenMilestones int `xorm:"-"` - NumReleases int `xorm:"-"` - - IsPrivate bool `xorm:"INDEX"` - IsEmpty bool `xorm:"INDEX"` - IsArchived bool `xorm:"INDEX"` - - IsMirror bool `xorm:"INDEX"` - *Mirror `xorm:"-"` - - ExternalMetas map[string]string `xorm:"-"` - Units []*RepoUnit `xorm:"-"` - - IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` - ForkID int64 `xorm:"INDEX"` - BaseRepo *Repository `xorm:"-"` - Size int64 `xorm:"NOT NULL DEFAULT 0"` - IndexerStatus *RepoIndexerStatus `xorm:"-"` - IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` - CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` - Topics []string `xorm:"TEXT JSON"` - - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` -} - -// AfterLoad is invoked from XORM after setting the values of all fields of this object. -func (repo *Repository) AfterLoad() { - // FIXME: use models migration to solve all at once. - if len(repo.DefaultBranch) == 0 { - repo.DefaultBranch = "master" - } - - repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues - repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls - repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones -} - -// MustOwner always returns a valid *User object to avoid -// conceptually impossible error handling. -// It creates a fake object that contains error details -// when error occurs. -func (repo *Repository) MustOwner() *User { - return repo.mustOwner(x) -} - -// MustOwnerName always returns valid owner name to avoid -// conceptually impossible error handling. -// It returns "error" and logs error details when error -// occurs. -func (repo *Repository) MustOwnerName() string { - return repo.mustOwnerName(x) -} - -// FullName returns the repository full name -func (repo *Repository) FullName() string { - return repo.MustOwnerName() + "/" + repo.Name -} - -// HTMLURL returns the repository HTML URL -func (repo *Repository) HTMLURL() string { - return setting.AppURL + repo.FullName() -} - -// APIURL returns the repository API URL -func (repo *Repository) APIURL() string { - return setting.AppURL + path.Join("api/v1/repos", repo.FullName()) -} - -// APIFormat converts a Repository to api.Repository -func (repo *Repository) APIFormat(mode AccessMode) *api.Repository { - return repo.innerAPIFormat(x, mode, false) -} - -// GetCommitsCountCacheKey returns cache key used for commits count caching. -func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string { - var prefix string - if isRef { - prefix = "ref" - } else { - prefix = "commit" - } - return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName) -} - -func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) *api.Repository { - var parent *api.Repository - - cloneLink := repo.cloneLink(e, false) - permission := &api.Permission{ - Admin: mode >= AccessModeAdmin, - Push: mode >= AccessModeWrite, - Pull: mode >= AccessModeRead, - } - if !isParent { - err := repo.getBaseRepo(e) - if err != nil { - log.Error(4, "APIFormat: %v", err) - } - if repo.BaseRepo != nil { - parent = repo.BaseRepo.innerAPIFormat(e, mode, true) - } - } - 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, - } -} - -func (repo *Repository) getUnits(e Engine) (err error) { - if repo.Units != nil { - return nil - } - - repo.Units, err = getUnitsByRepoID(e, repo.ID) - return err -} - -// CheckUnitUser check whether user could visit the unit of this repository -func (repo *Repository) CheckUnitUser(userID int64, isAdmin bool, unitType UnitType) bool { - return repo.checkUnitUser(x, userID, isAdmin, unitType) -} - -func (repo *Repository) checkUnitUser(e Engine, userID int64, isAdmin bool, unitType UnitType) bool { - if isAdmin { - return true - } - user, err := getUserByID(e, userID) - if err != nil { - return false - } - perm, err := getUserRepoPermission(e, repo, user) - if err != nil { - return false - } - - return perm.CanRead(unitType) -} - -// UnitEnabled if this repository has the given unit enabled -func (repo *Repository) UnitEnabled(tp UnitType) bool { - if err := repo.getUnits(x); err != nil { - log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error()) - } - for _, unit := range repo.Units { - if unit.Type == tp { - return true - } - } - return false -} - -var ( - // ErrUnitNotExist organization does not exist - ErrUnitNotExist = errors.New("Unit does not exist") -) - -// MustGetUnit always returns a RepoUnit object -func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit { - ru, err := repo.GetUnit(tp) - if err == nil { - return ru - } - - if tp == UnitTypeExternalWiki { - return &RepoUnit{ - Type: tp, - Config: new(ExternalWikiConfig), - } - } else if tp == UnitTypeExternalTracker { - return &RepoUnit{ - Type: tp, - Config: new(ExternalTrackerConfig), - } - } else if tp == UnitTypePullRequests { - return &RepoUnit{ - Type: tp, - Config: new(PullRequestsConfig), - } - } - return &RepoUnit{ - Type: tp, - Config: new(UnitConfig), - } -} - -// GetUnit returns a RepoUnit object -func (repo *Repository) GetUnit(tp UnitType) (*RepoUnit, error) { - return repo.getUnit(x, tp) -} - -func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) { - if err := repo.getUnits(e); err != nil { - return nil, err - } - for _, unit := range repo.Units { - if unit.Type == tp { - return unit, nil - } - } - return nil, ErrUnitNotExist -} - -func (repo *Repository) getOwner(e Engine) (err error) { - if repo.Owner != nil { - return nil - } - - repo.Owner, err = getUserByID(e, repo.OwnerID) - return err -} - -// GetOwner returns the repository owner -func (repo *Repository) GetOwner() error { - return repo.getOwner(x) -} - -func (repo *Repository) mustOwner(e Engine) *User { - if err := repo.getOwner(e); err != nil { - return &User{ - Name: "error", - FullName: err.Error(), - } - } - - return repo.Owner -} - -func (repo *Repository) getOwnerName(e Engine) error { - if len(repo.OwnerName) > 0 { - return nil - } - - if repo.Owner != nil { - repo.OwnerName = repo.Owner.Name - return nil - } - - u := new(User) - has, err := e.ID(repo.OwnerID).Cols("name").Get(u) - if err != nil { - return err - } else if !has { - return ErrUserNotExist{repo.OwnerID, "", 0} - } - repo.OwnerName = u.Name - return nil -} - -// GetOwnerName returns the repository owner name -func (repo *Repository) GetOwnerName() error { - return repo.getOwnerName(x) -} - -func (repo *Repository) mustOwnerName(e Engine) string { - if err := repo.getOwnerName(e); err != nil { - log.Error(4, "Error loading repository owner name: %v", err) - return "error" - } - - return repo.OwnerName -} - -// ComposeMetas composes a map of metas for rendering external issue tracker URL. -func (repo *Repository) ComposeMetas() map[string]string { - unit, err := repo.GetUnit(UnitTypeExternalTracker) - if err != nil { - return nil - } - - if repo.ExternalMetas == nil { - repo.ExternalMetas = map[string]string{ - "format": unit.ExternalTrackerConfig().ExternalTrackerFormat, - "user": repo.MustOwner().Name, - "repo": repo.Name, - } - switch unit.ExternalTrackerConfig().ExternalTrackerStyle { - case markup.IssueNameStyleAlphanumeric: - repo.ExternalMetas["style"] = markup.IssueNameStyleAlphanumeric - default: - repo.ExternalMetas["style"] = markup.IssueNameStyleNumeric - } - - } - return repo.ExternalMetas -} - -// DeleteWiki removes the actual and local copy of repository wiki. -func (repo *Repository) DeleteWiki() error { - return repo.deleteWiki(x) -} - -func (repo *Repository) deleteWiki(e Engine) error { - wikiPaths := []string{repo.WikiPath(), repo.LocalWikiPath()} - for _, wikiPath := range wikiPaths { - removeAllWithNotice(e, "Delete repository wiki", wikiPath) - } - - _, err := e.Where("repo_id = ?", repo.ID).And("type = ?", UnitTypeWiki).Delete(new(RepoUnit)) - return err -} - -func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { - if err = repo.getOwner(e); err != nil { - return nil, err - } - - accesses := make([]*Access, 0, 10) - if err = e. - Where("repo_id = ? AND mode >= ?", repo.ID, AccessModeWrite). - Find(&accesses); err != nil { - return nil, err - } - - // Leave a seat for owner itself to append later, but if owner is an organization - // and just waste 1 unit is cheaper than re-allocate memory once. - users := make([]*User, 0, len(accesses)+1) - if len(accesses) > 0 { - userIDs := make([]int64, len(accesses)) - for i := 0; i < len(accesses); i++ { - userIDs[i] = accesses[i].UserID - } - - if err = e.In("id", userIDs).Find(&users); err != nil { - return nil, err - } - } - if !repo.Owner.IsOrganization() { - users = append(users, repo.Owner) - } - - return users, nil -} - -// GetAssignees returns all users that have write access and can be assigned to issues -// of the repository, -func (repo *Repository) GetAssignees() (_ []*User, err error) { - return repo.getAssignees(x) -} - -// GetMilestoneByID returns the milestone belongs to repository by given ID. -func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) { - return GetMilestoneByRepoID(repo.ID, milestoneID) -} - -// IssueStats returns number of open and closed repository issues by given filter mode. -func (repo *Repository) IssueStats(uid int64, filterMode int, isPull bool) (int64, int64) { - return GetRepoIssueStats(repo.ID, uid, filterMode, isPull) -} - -// GetMirror sets the repository mirror, returns an error upon failure -func (repo *Repository) GetMirror() (err error) { - repo.Mirror, err = GetMirrorByRepoID(repo.ID) - return err -} - -// GetBaseRepo populates repo.BaseRepo for a fork repository and -// returns an error on failure (NOTE: no error is returned for -// non-fork repositories, and BaseRepo will be left untouched) -func (repo *Repository) GetBaseRepo() (err error) { - return repo.getBaseRepo(x) -} - -func (repo *Repository) getBaseRepo(e Engine) (err error) { - if !repo.IsFork { - return nil - } - - repo.BaseRepo, err = getRepositoryByID(e, repo.ForkID) - return err -} - -func (repo *Repository) repoPath(e Engine) string { - return RepoPath(repo.mustOwnerName(e), repo.Name) -} - -// RepoPath returns the repository path -func (repo *Repository) RepoPath() string { - return repo.repoPath(x) -} - -// GitConfigPath returns the path to a repository's git config/ directory -func GitConfigPath(repoPath string) string { - return filepath.Join(repoPath, "config") -} - -// GitConfigPath returns the repository git config path -func (repo *Repository) GitConfigPath() string { - return GitConfigPath(repo.RepoPath()) -} - -// RelLink returns the repository relative link -func (repo *Repository) RelLink() string { - return "/" + repo.FullName() -} - -// Link returns the repository link -func (repo *Repository) Link() string { - return setting.AppSubURL + "/" + repo.FullName() -} - -// ComposeCompareURL returns the repository comparison URL -func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { - return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) -} - -// UpdateDefaultBranch updates the default branch -func (repo *Repository) UpdateDefaultBranch() error { - _, err := x.ID(repo.ID).Cols("default_branch").Update(repo) - return err -} - -// IsOwnedBy returns true when user owns this repository -func (repo *Repository) IsOwnedBy(userID int64) bool { - return repo.OwnerID == userID -} - -func (repo *Repository) updateSize(e Engine) error { - repoInfoSize, err := git.GetRepoSize(repo.repoPath(e)) - if err != nil { - return fmt.Errorf("UpdateSize: %v", err) - } - - repo.Size = repoInfoSize.Size + repoInfoSize.SizePack - _, err = e.ID(repo.ID).Cols("size").Update(repo) - return err -} - -// UpdateSize updates the repository size, calculating it using git.GetRepoSize -func (repo *Repository) UpdateSize() error { - return repo.updateSize(x) -} - -// CanUserFork returns true if specified user can fork repository. -func (repo *Repository) CanUserFork(user *User) (bool, error) { - if user == nil { - return false, nil - } - if repo.OwnerID != user.ID && !user.HasForkedRepo(repo.ID) { - return true, nil - } - if err := user.GetOwnedOrganizations(); err != nil { - return false, err - } - for _, org := range user.OwnedOrgs { - if repo.OwnerID != org.ID && !org.HasForkedRepo(repo.ID) { - return true, nil - } - } - return false, nil -} - -// CanEnablePulls returns true if repository meets the requirements of accepting pulls. -func (repo *Repository) CanEnablePulls() bool { - return !repo.IsMirror && !repo.IsEmpty -} - -// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled. -func (repo *Repository) AllowsPulls() bool { - return repo.CanEnablePulls() && repo.UnitEnabled(UnitTypePullRequests) -} - -// CanEnableEditor returns true if repository meets the requirements of web editor. -func (repo *Repository) CanEnableEditor() bool { - return !repo.IsMirror -} - -// GetWriters returns all users that have write access to the repository. -func (repo *Repository) GetWriters() (_ []*User, err error) { - return repo.getUsersWithAccessMode(x, AccessModeWrite) -} - -// getUsersWithAccessMode returns users that have at least given access mode to the repository. -func (repo *Repository) getUsersWithAccessMode(e Engine, mode AccessMode) (_ []*User, err error) { - if err = repo.getOwner(e); err != nil { - return nil, err - } - - accesses := make([]*Access, 0, 10) - if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { - return nil, err - } - - // Leave a seat for owner itself to append later, but if owner is an organization - // and just waste 1 unit is cheaper than re-allocate memory once. - users := make([]*User, 0, len(accesses)+1) - if len(accesses) > 0 { - userIDs := make([]int64, len(accesses)) - for i := 0; i < len(accesses); i++ { - userIDs[i] = accesses[i].UserID - } - - if err = e.In("id", userIDs).Find(&users); err != nil { - return nil, err - } - } - if !repo.Owner.IsOrganization() { - users = append(users, repo.Owner) - } - - 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 { - sanitize := func(s string) string { - return fmt.Sprintf(`%[1]s`, s) - } - return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize)) -} - -// LocalCopyPath returns the local repository copy path. -func LocalCopyPath() string { - if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { - return setting.Repository.Local.LocalCopyPath - } - return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) -} - -// LocalCopyPath returns the local repository copy path for the given repo. -func (repo *Repository) LocalCopyPath() string { - return path.Join(LocalCopyPath(), com.ToStr(repo.ID)) -} - -// UpdateLocalCopyBranch pulls latest changes of given branch from repoPath to localPath. -// It creates a new clone if local copy does not exist. -// This function checks out target branch by default, it is safe to assume subsequent -// operations are operating against target branch when caller has confidence for no race condition. -func UpdateLocalCopyBranch(repoPath, localPath, branch string) error { - if !com.IsExist(localPath) { - if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{ - Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, - Branch: branch, - }); err != nil { - return fmt.Errorf("git clone %s: %v", branch, err) - } - } else { - _, err := git.NewCommand("fetch", "origin").RunInDir(localPath) - if err != nil { - return fmt.Errorf("git fetch origin: %v", err) - } - if len(branch) > 0 { - if err := git.Checkout(localPath, git.CheckoutOptions{ - Branch: branch, - }); err != nil { - return fmt.Errorf("git checkout %s: %v", branch, err) - } - - if err := git.ResetHEAD(localPath, true, "origin/"+branch); err != nil { - return fmt.Errorf("git reset --hard origin/%s: %v", branch, err) - } - } - } - return nil -} - -// UpdateLocalCopyBranch makes sure local copy of repository in given branch is up-to-date. -func (repo *Repository) UpdateLocalCopyBranch(branch string) error { - return UpdateLocalCopyBranch(repo.RepoPath(), repo.LocalCopyPath(), branch) -} - -// PatchPath returns corresponding patch file path of repository by given issue ID. -func (repo *Repository) PatchPath(index int64) (string, error) { - return repo.patchPath(x, index) -} - -func (repo *Repository) patchPath(e Engine, index int64) (string, error) { - if err := repo.getOwner(e); err != nil { - return "", err - } - - return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil -} - -// SavePatch saves patch data to corresponding location by given issue ID. -func (repo *Repository) SavePatch(index int64, patch []byte) error { - return repo.savePatch(x, index, patch) -} - -func (repo *Repository) savePatch(e Engine, index int64, patch []byte) error { - patchPath, err := repo.patchPath(e, index) - if err != nil { - return fmt.Errorf("PatchPath: %v", err) - } - dir := filepath.Dir(patchPath) - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", dir, err) - } - - if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil { - return fmt.Errorf("WriteFile: %v", err) - } - - return nil -} - -func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { - has, err := e.Get(&Repository{ - OwnerID: u.ID, - LowerName: strings.ToLower(repoName), - }) - return has && com.IsDir(RepoPath(u.Name, repoName)), err -} - -// IsRepositoryExist returns true if the repository with given name under user has already existed. -func IsRepositoryExist(u *User, repoName string) (bool, error) { - return isRepositoryExist(x, u, repoName) -} - -// CloneLink represents different types of clone URLs of repository. -type CloneLink struct { - SSH string - HTTPS string - Git string -} - -// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name. -func ComposeHTTPSCloneURL(owner, repo string) string { - return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.QueryEscape(owner), url.QueryEscape(repo)) -} - -func (repo *Repository) cloneLink(e Engine, isWiki bool) *CloneLink { - repoName := repo.Name - if isWiki { - repoName += ".wiki" - } - - sshUser := setting.RunUser - if setting.SSH.StartBuiltinServer { - sshUser = setting.SSH.BuiltinServerUser - } - - repo.Owner = repo.mustOwner(e) - cl := new(CloneLink) - if setting.SSH.Port != 22 { - cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", sshUser, setting.SSH.Domain, setting.SSH.Port, repo.Owner.Name, repoName) - } else if setting.Repository.UseCompatSSHURI { - cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) - } else { - cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) - } - cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName) - return cl -} - -// CloneLink returns clone URLs of repository. -func (repo *Repository) CloneLink() (cl *CloneLink) { - return repo.cloneLink(x, false) -} - -// MigrateRepoOptions contains the repository migrate options -type MigrateRepoOptions struct { - Name string - Description string - IsPrivate bool - IsMirror bool - RemoteAddr string -} - -/* - GitHub, GitLab, Gogs: *.wiki.git - BitBucket: *.git/wiki -*/ -var commonWikiURLSuffixes = []string{".wiki.git", ".git/wiki"} - -// wikiRemoteURL returns accessible repository URL for wiki if exists. -// Otherwise, it returns an empty string. -func wikiRemoteURL(remote string) string { - remote = strings.TrimSuffix(remote, ".git") - for _, suffix := range commonWikiURLSuffixes { - wikiURL := remote + suffix - if git.IsRepoURLAccessible(wikiURL) { - return wikiURL - } - } - return "" -} - -// MigrateRepository migrates a existing repository from other project hosting. -func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, error) { - repo, err := CreateRepository(doer, u, CreateRepoOptions{ - Name: opts.Name, - Description: opts.Description, - IsPrivate: opts.IsPrivate, - IsMirror: opts.IsMirror, - }) - if err != nil { - return nil, err - } - - repoPath := RepoPath(u.Name, opts.Name) - wikiPath := WikiPath(u.Name, opts.Name) - - if u.IsOrganization() { - t, err := u.GetOwnerTeam() - if err != nil { - return nil, err - } - repo.NumWatches = t.NumMembers - } else { - repo.NumWatches = 1 - } - - migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second - - if err := os.RemoveAll(repoPath); err != nil { - return repo, fmt.Errorf("Failed to remove %s: %v", repoPath, err) - } - - if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{ - Mirror: true, - Quiet: true, - Timeout: migrateTimeout, - }); err != nil { - return repo, fmt.Errorf("Clone: %v", err) - } - - wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) - if len(wikiRemotePath) > 0 { - if err := os.RemoveAll(wikiPath); err != nil { - return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) - } - - if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ - Mirror: true, - Quiet: true, - Timeout: migrateTimeout, - Branch: "master", - }); err != nil { - log.Warn("Clone wiki: %v", err) - if err := os.RemoveAll(wikiPath); err != nil { - return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) - } - } - } - - // Check if repository is empty. - _, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1") - 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) - } - } - - if !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) - } - if headBranch != nil { - repo.DefaultBranch = headBranch.Name - } - - if err = SyncReleasesWithTags(repo, gitRepo); err != nil { - log.Error(4, "Failed to synchronize tags to releases for repository: %v", err) - } - } - - if err = repo.UpdateSize(); err != nil { - log.Error(4, "Failed to update size for repository: %v", err) - } - - if opts.IsMirror { - if _, err = x.InsertOne(&Mirror{ - RepoID: repo.ID, - Interval: setting.Mirror.DefaultInterval, - EnablePrune: true, - NextUpdateUnix: util.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval), - }); err != nil { - return repo, fmt.Errorf("InsertOne: %v", err) - } - - repo.IsMirror = true - err = UpdateRepository(repo, false) - } else { - repo, err = CleanUpMigrateInfo(repo) - } - - if err != nil && !repo.IsEmpty { - UpdateRepoIndexer(repo) - } - - return repo, err -} - -// cleanUpMigrateGitConfig removes mirror info which prevents "push --all". -// This also removes possible user credentials. -func cleanUpMigrateGitConfig(configPath string) error { - cfg, err := ini.Load(configPath) - if err != nil { - return fmt.Errorf("open config file: %v", err) - } - cfg.DeleteSection("remote \"origin\"") - if err = cfg.SaveToIndent(configPath, "\t"); err != nil { - return fmt.Errorf("save config file: %v", err) - } - return nil -} - -// createDelegateHooks creates all the hooks scripts for the repo -func createDelegateHooks(repoPath string) (err error) { - var ( - hookNames = []string{"pre-receive", "update", "post-receive"} - hookTpls = []string{ - fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - } - giteaHookTpls = []string{ - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - } - ) - - hookDir := filepath.Join(repoPath, "hooks") - - for i, hookName := range hookNames { - oldHookPath := filepath.Join(hookDir, hookName) - newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") - - if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { - return fmt.Errorf("create hooks dir '%s': %v", filepath.Join(hookDir, hookName+".d"), err) - } - - // WARNING: This will override all old server-side hooks - if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { - return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err) - } - - if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil { - return fmt.Errorf("write new hook file '%s': %v", newHookPath, err) - } - } - - return nil -} - -// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. -func CleanUpMigrateInfo(repo *Repository) (*Repository, error) { - repoPath := repo.RepoPath() - if err := createDelegateHooks(repoPath); err != nil { - return repo, fmt.Errorf("createDelegateHooks: %v", err) - } - if repo.HasWiki() { - if err := createDelegateHooks(repo.WikiPath()); err != nil { - return repo, fmt.Errorf("createDelegateHooks.(wiki): %v", err) - } - } - - if err := cleanUpMigrateGitConfig(repo.GitConfigPath()); err != nil { - return repo, fmt.Errorf("cleanUpMigrateGitConfig: %v", err) - } - if repo.HasWiki() { - if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil { - return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %v", err) - } - } - - return repo, UpdateRepository(repo, false) -} - -// initRepoCommit temporarily changes with work directory. -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 { - 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), - "-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 { - return fmt.Errorf("git push: %s", stderr) - } - return nil -} - -// CreateRepoOptions contains the create repository options -type CreateRepoOptions struct { - Name string - Description string - Gitignores string - License string - Readme string - IsPrivate bool - IsMirror bool - AutoInit bool -} - -func getRepoInitFile(tp, name string) ([]byte, error) { - cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") - relPath := path.Join("options", tp, cleanedName) - - // Use custom file when available. - customPath := path.Join(setting.CustomPath, relPath) - if com.IsFile(customPath) { - return ioutil.ReadFile(customPath) - } - - switch tp { - case "readme": - return options.Readme(cleanedName) - case "gitignore": - return options.Gitignore(cleanedName) - case "license": - return options.License(cleanedName) - case "label": - return options.Labels(cleanedName) - default: - return []byte{}, fmt.Errorf("Invalid init file type") - } -} - -func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { - // 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, - ) - if err != nil { - return fmt.Errorf("git clone: %v - %s", err, stderr) - } - - // README - data, err := getRepoInitFile("readme", opts.Readme) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err) - } - - cloneLink := repo.cloneLink(e, false) - match := map[string]string{ - "Name": repo.Name, - "Description": repo.Description, - "CloneURL.SSH": cloneLink.SSH, - "CloneURL.HTTPS": cloneLink.HTTPS, - } - if err = ioutil.WriteFile(filepath.Join(tmpDir, "README.md"), - []byte(com.Expand(string(data), match)), 0644); err != nil { - return fmt.Errorf("write README.md: %v", err) - } - - // .gitignore - if len(opts.Gitignores) > 0 { - var buf bytes.Buffer - names := strings.Split(opts.Gitignores, ",") - for _, name := range names { - data, err = getRepoInitFile("gitignore", name) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", name, err) - } - buf.WriteString("# ---> " + name + "\n") - buf.Write(data) - buf.WriteString("\n") - } - - if buf.Len() > 0 { - if err = ioutil.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { - return fmt.Errorf("write .gitignore: %v", err) - } - } - } - - // LICENSE - if len(opts.License) > 0 { - data, err = getRepoInitFile("license", opts.License) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", opts.License, err) - } - - if err = ioutil.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { - return fmt.Errorf("write LICENSE: %v", err) - } - } - - return nil -} - -// InitRepository initializes README and .gitignore if needed. -func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) { - // Somehow the directory could exist. - if com.IsExist(repoPath) { - return fmt.Errorf("initRepository: path already exists: %s", repoPath) - } - - // Init git bare new repository. - if err = git.InitRepository(repoPath, true); err != nil { - return fmt.Errorf("InitRepository: %v", err) - } else if err = createDelegateHooks(repoPath); err != nil { - return fmt.Errorf("createDelegateHooks: %v", err) - } - - tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) - - // Initialize repository according to user's choice. - if opts.AutoInit { - - if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", tmpDir, err) - } - - defer os.RemoveAll(tmpDir) - - if err = prepareRepoCommit(e, repo, tmpDir, repoPath, opts); err != nil { - return fmt.Errorf("prepareRepoCommit: %v", err) - } - - // Apply changes and commit. - if err = initRepoCommit(tmpDir, u.NewGitSig()); err != nil { - return fmt.Errorf("initRepoCommit: %v", err) - } - } - - // Re-fetch the repository from database before updating it (else it would - // override changes that were done earlier with sql) - if repo, err = getRepositoryByID(e, repo.ID); err != nil { - return fmt.Errorf("getRepositoryByID: %v", err) - } - - if !opts.AutoInit { - repo.IsEmpty = true - } - - repo.DefaultBranch = "master" - if err = updateRepository(e, repo, false); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return nil -} - -var ( - reservedRepoNames = []string{".", ".."} - reservedRepoPatterns = []string{"*.git", "*.wiki"} -) - -// IsUsableRepoName returns true when repository is usable -func IsUsableRepoName(name string) error { - return isUsableName(reservedRepoNames, reservedRepoPatterns, name) -} - -func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err error) { - if err = IsUsableRepoName(repo.Name); err != nil { - return err - } - - has, err := isRepositoryExist(e, u, repo.Name) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{u.Name, repo.Name} - } - - if _, err = e.Insert(repo); err != nil { - return err - } - if err = deleteRepoRedirect(e, u.ID, repo.Name); err != nil { - return err - } - - // insert units for repo - var units = make([]RepoUnit, 0, len(defaultRepoUnits)) - for _, tp := range defaultRepoUnits { - if tp == UnitTypeIssues { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - Config: &IssuesConfig{ - EnableTimetracker: setting.Service.DefaultEnableTimetracking, - AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime, - EnableDependencies: setting.Service.DefaultEnableDependencies, - }, - }) - } else if tp == UnitTypePullRequests { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, - }) - } else { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - }) - } - - } - - if _, err = e.Insert(&units); err != nil { - return err - } - - u.NumRepos++ - // Remember visibility preference. - u.LastRepoVisibility = repo.IsPrivate - if err = updateUser(e, u); err != nil { - return fmt.Errorf("updateUser: %v", err) - } - - // Give access to all members in owner team. - if u.IsOrganization() { - t, err := u.getOwnerTeam(e) - if err != nil { - return fmt.Errorf("getOwnerTeam: %v", err) - } else if err = t.addRepository(e, repo); err != nil { - return fmt.Errorf("addRepository: %v", err) - } else if err = prepareWebhooks(e, repo, HookEventRepository, &api.RepositoryPayload{ - Action: api.HookRepoCreated, - Repository: repo.innerAPIFormat(e, AccessModeOwner, false), - Organization: u.APIFormat(), - Sender: doer.APIFormat(), - }); err != nil { - return fmt.Errorf("prepareWebhooks: %v", err) - } - go HookQueue.Add(repo.ID) - } else { - // Organization automatically called this in addRepository method. - if err = repo.recalculateAccesses(e); err != nil { - return fmt.Errorf("recalculateAccesses: %v", err) - } - } - - if setting.Service.AutoWatchNewRepos { - if err = watchRepo(e, doer.ID, repo.ID, true); err != nil { - return fmt.Errorf("watchRepo: %v", err) - } - } - if err = newRepoAction(e, doer, repo); err != nil { - return fmt.Errorf("newRepoAction: %v", err) - } - - return nil -} - -// CreateRepository creates a repository for the user/organization. -func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err error) { - if !doer.IsAdmin && !u.CanCreateRepo() { - return nil, ErrReachLimitOfRepo{u.MaxRepoCreation} - } - - repo := &Repository{ - OwnerID: u.ID, - Owner: u, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - IsPrivate: opts.IsPrivate, - IsFsckEnabled: !opts.IsMirror, - CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, u, repo); err != nil { - return nil, err - } - - // No need for init mirror. - if !opts.IsMirror { - repoPath := RepoPath(u.Name, repo.Name) - if err = initRepository(sess, repoPath, u, repo, opts); err != nil { - if err2 := os.RemoveAll(repoPath); err2 != nil { - log.Error(4, "initRepository: %v", err) - return nil, fmt.Errorf( - "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) - } - return nil, fmt.Errorf("initRepository: %v", err) - } - - _, stderr, err := process.GetManager().ExecDir(-1, - repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath), - "git", "update-server-info") - if err != nil { - return nil, errors.New("CreateRepository(git update-server-info): " + stderr) - } - } - - return repo, sess.Commit() -} - -func countRepositories(userID int64, private bool) int64 { - sess := x.Where("id > 0") - - if userID > 0 { - sess.And("owner_id = ?", userID) - } - if !private { - sess.And("is_private=?", false) - } - - count, err := sess.Count(new(Repository)) - if err != nil { - log.Error(4, "countRepositories: %v", err) - } - return count -} - -// CountRepositories returns number of repositories. -// Argument private only takes effect when it is false, -// set it true to count all repositories. -func CountRepositories(private bool) int64 { - return countRepositories(-1, private) -} - -// CountUserRepositories returns number of repositories user owns. -// Argument private only takes effect when it is false, -// set it true to count all repositories. -func CountUserRepositories(userID int64, private bool) int64 { - return countRepositories(userID, private) -} - -// RepoPath returns repository path by given user and repository name. -func RepoPath(userName, repoName string) string { - return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") -} - -// TransferOwnership transfers all corresponding setting from old user to new one. -func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error { - newOwner, err := GetUserByName(newOwnerName) - if err != nil { - return fmt.Errorf("get new owner '%s': %v", newOwnerName, err) - } - - // Check if new owner has repository with same name. - has, err := IsRepositoryExist(newOwner, repo.Name) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{newOwnerName, repo.Name} - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return fmt.Errorf("sess.Begin: %v", err) - } - - owner := repo.Owner - - // Note: we have to set value here to make sure recalculate accesses is based on - // new owner. - repo.OwnerID = newOwner.ID - repo.Owner = newOwner - - // Update repository. - if _, err := sess.ID(repo.ID).Update(repo); err != nil { - return fmt.Errorf("update owner: %v", err) - } - - // Remove redundant collaborators. - collaborators, err := repo.getCollaborators(sess) - if err != nil { - return fmt.Errorf("getCollaborators: %v", err) - } - - // Dummy object. - collaboration := &Collaboration{RepoID: repo.ID} - for _, c := range collaborators { - if c.ID != newOwner.ID { - isMember, err := isOrganizationMember(sess, newOwner.ID, c.ID) - if err != nil { - return fmt.Errorf("IsOrgMember: %v", err) - } else if !isMember { - continue - } - } - collaboration.UserID = c.ID - if _, err = sess.Delete(collaboration); err != nil { - return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) - } - } - - // Remove old team-repository relations. - if owner.IsOrganization() { - if err = owner.removeOrgRepo(sess, repo.ID); err != nil { - return fmt.Errorf("removeOrgRepo: %v", err) - } - } - - if newOwner.IsOrganization() { - t, err := newOwner.getOwnerTeam(sess) - if err != nil { - return fmt.Errorf("getOwnerTeam: %v", err) - } else if err = t.addRepository(sess, repo); err != nil { - return fmt.Errorf("add to owner team: %v", err) - } - } else { - // Organization called this in addRepository method. - if err = repo.recalculateAccesses(sess); err != nil { - return fmt.Errorf("recalculateAccesses: %v", err) - } - } - - // Update repository count. - if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil { - return fmt.Errorf("increase new owner repository count: %v", err) - } else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.ID); err != nil { - return fmt.Errorf("decrease old owner repository count: %v", err) - } - - if err = watchRepo(sess, doer.ID, repo.ID, true); err != nil { - return fmt.Errorf("watchRepo: %v", err) - } else if err = transferRepoAction(sess, doer, owner, repo); err != nil { - return fmt.Errorf("transferRepoAction: %v", err) - } - - // Rename remote repository to new path and delete local copy. - dir := UserPath(newOwner.Name) - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", dir, err) - } - - if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { - return fmt.Errorf("rename repository directory: %v", err) - } - removeAllWithNotice(sess, "Delete repository local copy", repo.LocalCopyPath()) - - // Rename remote wiki repository to new path and delete local copy. - wikiPath := WikiPath(owner.Name, repo.Name) - if com.IsExist(wikiPath) { - removeAllWithNotice(sess, "Delete repository wiki local copy", repo.LocalWikiPath()) - if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { - return fmt.Errorf("rename repository wiki: %v", err) - } - } - - return sess.Commit() -} - -// ChangeRepositoryName changes all corresponding setting from old repository name to new one. -func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) { - oldRepoName = strings.ToLower(oldRepoName) - newRepoName = strings.ToLower(newRepoName) - if err = IsUsableRepoName(newRepoName); err != nil { - return err - } - - has, err := IsRepositoryExist(u, newRepoName) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{u.Name, newRepoName} - } - - repo, err := GetRepositoryByName(u.ID, oldRepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName: %v", err) - } - - // Change repository directory name. We must lock the local copy of the - // repo so that we can atomically rename the repo path and updates the - // local copy's origin accordingly. - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - - newRepoPath := RepoPath(u.Name, newRepoName) - if err = os.Rename(repo.RepoPath(), newRepoPath); err != nil { - return fmt.Errorf("rename repository directory: %v", err) - } - - localPath := repo.LocalCopyPath() - if com.IsExist(localPath) { - _, err := git.NewCommand("remote", "set-url", "origin", newRepoPath).RunInDir(localPath) - if err != nil { - return fmt.Errorf("git remote set-url origin %s: %v", newRepoPath, err) - } - } - - wikiPath := repo.WikiPath() - if com.IsExist(wikiPath) { - if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { - return fmt.Errorf("rename repository wiki: %v", err) - } - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) - } - - return nil -} - -func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - return repos, e. - Where("fork_id=?", forkID). - Find(&repos) -} - -// GetRepositoriesByForkID returns all repositories with given fork ID. -func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) { - return getRepositoriesByForkID(x, forkID) -} - -func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) { - repo.LowerName = strings.ToLower(repo.Name) - - if len(repo.Description) > 255 { - repo.Description = repo.Description[:255] - } - if len(repo.Website) > 255 { - repo.Website = repo.Website[:255] - } - - if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { - return fmt.Errorf("update: %v", err) - } - - if visibilityChanged { - if err = repo.getOwner(e); err != nil { - return fmt.Errorf("getOwner: %v", err) - } - if repo.Owner.IsOrganization() { - // Organization repository need to recalculate access table when visibility is changed. - if err = repo.recalculateTeamAccesses(e, 0); err != nil { - return fmt.Errorf("recalculateTeamAccesses: %v", err) - } - } - - // If repo has become private, we need to set its actions to private. - if repo.IsPrivate { - _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{ - IsPrivate: true, - }) - if err != nil { - return err - } - } - - // Create/Remove git-daemon-export-ok for git-daemon... - daemonExportFile := path.Join(repo.repoPath(e), `git-daemon-export-ok`) - if repo.IsPrivate && com.IsExist(daemonExportFile) { - if err = os.Remove(daemonExportFile); err != nil { - log.Error(4, "Failed to remove %s: %v", daemonExportFile, err) - } - } else if !repo.IsPrivate && !com.IsExist(daemonExportFile) { - if f, err := os.Create(daemonExportFile); err != nil { - log.Error(4, "Failed to create %s: %v", daemonExportFile, err) - } else { - f.Close() - } - } - - forkRepos, err := getRepositoriesByForkID(e, repo.ID) - if err != nil { - return fmt.Errorf("getRepositoriesByForkID: %v", err) - } - for i := range forkRepos { - forkRepos[i].IsPrivate = repo.IsPrivate - if err = updateRepository(e, forkRepos[i], true); err != nil { - return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) - } - } - - if err = repo.updateSize(e); err != nil { - log.Error(4, "Failed to update size for repository: %v", err) - } - } - - return nil -} - -// UpdateRepository updates a repository -func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = updateRepository(sess, repo, visibilityChanged); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return sess.Commit() -} - -// UpdateRepositoryUnits updates a repository's units -func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Where("repo_id = ?", repo.ID).Delete(new(RepoUnit)); err != nil { - return err - } - - if _, err = sess.Insert(units); err != nil { - return err - } - - return sess.Commit() -} - -// DeleteRepository deletes a repository for a user or organization. -func DeleteRepository(doer *User, uid, repoID int64) error { - // In case is a organization. - org, err := GetUserByID(uid) - if err != nil { - return err - } - if org.IsOrganization() { - if err = org.GetTeams(); err != nil { - return err - } - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - repo := &Repository{ID: repoID, OwnerID: uid} - has, err := sess.Get(repo) - if err != nil { - return err - } else if !has { - return ErrRepoNotExist{repoID, uid, "", ""} - } - - // Delete Deploy Keys - deployKeys, err := listDeployKeys(sess, repo.ID) - if err != nil { - return fmt.Errorf("listDeployKeys: %v", err) - } - for _, dKey := range deployKeys { - if err := deleteDeployKey(sess, doer, dKey.ID); err != nil { - return fmt.Errorf("deleteDeployKeys: %v", err) - } - } - - if cnt, err := sess.ID(repoID).Delete(&Repository{}); err != nil { - return err - } else if cnt != 1 { - return ErrRepoNotExist{repoID, uid, "", ""} - } - - if org.IsOrganization() { - for _, t := range org.Teams { - if !t.hasRepository(sess, repoID) { - continue - } else if err = t.removeRepository(sess, repo, false); err != nil { - return err - } - } - } - - if err = deleteBeans(sess, - &Access{RepoID: repo.ID}, - &Action{RepoID: repo.ID}, - &Watch{RepoID: repoID}, - &Star{RepoID: repoID}, - &Mirror{RepoID: repoID}, - &Milestone{RepoID: repoID}, - &Release{RepoID: repoID}, - &Collaboration{RepoID: repoID}, - &PullRequest{BaseRepoID: repoID}, - &RepoUnit{RepoID: repoID}, - &RepoRedirect{RedirectRepoID: repoID}, - &Webhook{RepoID: repoID}, - &HookTask{RepoID: repoID}, - &Notification{RepoID: repoID}, - &CommitStatus{RepoID: repoID}, - ); err != nil { - return fmt.Errorf("deleteBeans: %v", err) - } - - deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repoID}) - // Delete comments and attachments - if _, err = sess.In("issue_id", deleteCond). - Delete(&Comment{}); err != nil { - return err - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueUser{}); err != nil { - return err - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&Reaction{}); err != nil { - return err - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueWatch{}); err != nil { - return err - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&Stopwatch{}); err != nil { - return err - } - - attachmentPaths := make([]string, 0, 20) - attachments := make([]*Attachment, 0, len(attachmentPaths)) - if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id"). - Where("issue.repo_id = ?", repoID). - Find(&attachments); err != nil { - return err - } - for j := range attachments { - attachmentPaths = append(attachmentPaths, attachments[j].LocalPath()) - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&Attachment{}); err != nil { - return err - } - - if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil { - return err - } - - if _, err = sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil { - return err - } - - if repo.IsFork { - if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { - return fmt.Errorf("decrease fork count: %v", err) - } - } - - if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { - return err - } - - // FIXME: Remove repository files should be executed after transaction succeed. - repoPath := repo.repoPath(sess) - removeAllWithNotice(sess, "Delete repository files", repoPath) - - repo.deleteWiki(sess) - - // Remove attachment files. - for i := range attachmentPaths { - removeAllWithNotice(sess, "Delete attachment", attachmentPaths[i]) - } - - // Remove LFS objects - var lfsObjects []*LFSMetaObject - if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { - return err - } - - for _, v := range lfsObjects { - count, err := sess.Count(&LFSMetaObject{Oid: v.Oid}) - if err != nil { - return err - } - - if count > 1 { - continue - } - - 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 - } - } - - if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { - return err - } - - if repo.NumForks > 0 { - if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { - log.Error(4, "reset 'fork_id' and 'is_fork': %v", err) - } - } - - if err = sess.Commit(); err != nil { - if len(deployKeys) > 0 { - // We need to rewrite the public keys because the commit failed - if err2 := RewriteAllPublicKeys(); err2 != nil { - return fmt.Errorf("Commit: %v SSH Keys: %v", err, err2) - } - } - return fmt.Errorf("Commit: %v", err) - } - - if org.IsOrganization() { - if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{ - Action: api.HookRepoDeleted, - Repository: repo.APIFormat(AccessModeOwner), - Organization: org.APIFormat(), - Sender: doer.APIFormat(), - }); err != nil { - return err - } - go HookQueue.Add(repo.ID) - } - - DeleteRepoFromIndexer(repo) - return nil -} - -// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame. -func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { - var repo Repository - has, err := x.Select("repository.*"). - Join("INNER", "`user`", "`user`.id = repository.owner_id"). - Where("repository.lower_name = ?", strings.ToLower(repoName)). - And("`user`.lower_name = ?", strings.ToLower(ownerName)). - Get(&repo) - if err != nil { - return nil, err - } else if !has { - return nil, ErrRepoNotExist{0, 0, ownerName, repoName} - } - return &repo, nil -} - -// GetRepositoryByName returns the repository by given name under user if exists. -func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { - repo := &Repository{ - OwnerID: ownerID, - LowerName: strings.ToLower(name), - } - has, err := x.Get(repo) - if err != nil { - return nil, err - } else if !has { - return nil, ErrRepoNotExist{0, ownerID, "", name} - } - return repo, err -} - -func getRepositoryByID(e Engine, id int64) (*Repository, error) { - repo := new(Repository) - has, err := e.ID(id).Get(repo) - if err != nil { - return nil, err - } else if !has { - return nil, ErrRepoNotExist{id, 0, "", ""} - } - return repo, nil -} - -// GetRepositoryByID returns the repository by given id if exists. -func GetRepositoryByID(id int64) (*Repository, error) { - return getRepositoryByID(x, id) -} - -// GetRepositoriesMapByIDs returns the repositories by given id slice. -func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { - var repos = make(map[int64]*Repository, len(ids)) - return repos, x.In("id", ids).Find(&repos) -} - -// GetUserRepositories returns a list of repositories of given user. -func GetUserRepositories(userID int64, private bool, page, pageSize int, orderBy string) ([]*Repository, error) { - if len(orderBy) == 0 { - orderBy = "updated_unix DESC" - } - - sess := x. - Where("owner_id = ?", userID). - OrderBy(orderBy) - if !private { - sess.And("is_private=?", false) - } - - if page <= 0 { - page = 1 - } - sess.Limit(pageSize, (page-1)*pageSize) - - repos := make([]*Repository, 0, pageSize) - return repos, sess.Find(&repos) -} - -// GetUserMirrorRepositories returns a list of mirror repositories of given user. -func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - return repos, x. - Where("owner_id = ?", userID). - And("is_mirror = ?", true). - Find(&repos) -} - -func getRepositoryCount(e Engine, u *User) (int64, error) { - return e.Count(&Repository{OwnerID: u.ID}) -} - -func getPublicRepositoryCount(e Engine, u *User) (int64, error) { - return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID}) -} - -func getPrivateRepositoryCount(e Engine, u *User) (int64, error) { - return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID}) -} - -// GetRepositoryCount returns the total number of repositories of user. -func GetRepositoryCount(u *User) (int64, error) { - return getRepositoryCount(x, u) -} - -// GetPublicRepositoryCount returns the total number of public repositories of user. -func GetPublicRepositoryCount(u *User) (int64, error) { - return getPublicRepositoryCount(x, u) -} - -// GetPrivateRepositoryCount returns the total number of private repositories of user. -func GetPrivateRepositoryCount(u *User) (int64, error) { - return getPrivateRepositoryCount(x, u) -} - -// DeleteRepositoryArchives deletes all repositories' archives. -func DeleteRepositoryArchives() error { - return x. - Where("id > 0"). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives")) - }) -} - -// DeleteOldRepositoryArchives deletes old repository archives. -func DeleteOldRepositoryArchives() { - if !taskStatusTable.StartIfNotRunning(archiveCleanup) { - return - } - defer taskStatusTable.Stop(archiveCleanup) - - log.Trace("Doing: ArchiveCleanup") - - if err := x.Where("id > 0").Iterate(new(Repository), deleteOldRepositoryArchives); err != nil { - log.Error(4, "ArchiveClean: %v", err) - } -} - -func deleteOldRepositoryArchives(idx int, bean interface{}) error { - repo := bean.(*Repository) - basePath := filepath.Join(repo.RepoPath(), "archives") - - for _, ty := range []string{"zip", "targz"} { - path := filepath.Join(basePath, ty) - file, err := os.Open(path) - if err != nil { - if !os.IsNotExist(err) { - log.Warn("Unable to open directory %s: %v", path, err) - return err - } - - // If the directory doesn't exist, that's okay. - continue - } - - files, err := file.Readdir(0) - file.Close() - if err != nil { - log.Warn("Unable to read directory %s: %v", path, err) - return err - } - - minimumOldestTime := time.Now().Add(-setting.Cron.ArchiveCleanup.OlderThan) - for _, info := range files { - if info.ModTime().Before(minimumOldestTime) && !info.IsDir() { - toDelete := filepath.Join(path, info.Name()) - // This is a best-effort purge, so we do not check error codes to confirm removal. - if err = os.Remove(toDelete); err != nil { - log.Trace("Unable to delete %s, but proceeding: %v", toDelete, err) - } - } - } - } - - return nil -} - -func gatherMissingRepoRecords() ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - if err := x. - Where("id > 0"). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if !com.IsDir(repo.RepoPath()) { - repos = append(repos, repo) - } - return nil - }); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { - return nil, fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - return repos, nil -} - -// DeleteMissingRepositories deletes all repository records that lost Git files. -func DeleteMissingRepositories(doer *User) error { - repos, err := gatherMissingRepoRecords() - if err != nil { - return fmt.Errorf("gatherMissingRepoRecords: %v", err) - } - - if len(repos) == 0 { - return nil - } - - for _, repo := range repos { - log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) - if err := DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil { - return fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - } - return nil -} - -// ReinitMissingRepositories reinitializes all repository records that lost Git files. -func ReinitMissingRepositories() error { - repos, err := gatherMissingRepoRecords() - if err != nil { - return fmt.Errorf("gatherMissingRepoRecords: %v", err) - } - - if len(repos) == 0 { - return nil - } - - for _, repo := range repos { - log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) - if err := git.InitRepository(repo.RepoPath(), true); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil { - return fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - } - return nil -} - -// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks -// to make sure the binary and custom conf path are up-to-date. -func SyncRepositoryHooks() error { - return x.Cols("owner_id", "name").Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil { - return fmt.Errorf("SyncRepositoryHook: %v", err) - } - if bean.(*Repository).HasWiki() { - if err := createDelegateHooks(bean.(*Repository).WikiPath()); err != nil { - return fmt.Errorf("SyncRepositoryHook: %v", err) - } - } - return nil - }) -} - -// Prevent duplicate running tasks. -var taskStatusTable = sync.NewStatusTable() - -const ( - mirrorUpdate = "mirror_update" - gitFsck = "git_fsck" - checkRepos = "check_repos" - archiveCleanup = "archive_cleanup" -) - -// GitFsck calls 'git fsck' to check repository health. -func GitFsck() { - if !taskStatusTable.StartIfNotRunning(gitFsck) { - return - } - defer taskStatusTable.Stop(gitFsck) - - log.Trace("Doing: GitFsck") - - if err := x. - Where("id>0 AND is_fsck_enabled=?", true).BufferSize(setting.IterateBufferSize). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - repoPath := repo.RepoPath() - log.Trace("Running health check on repository %s", repoPath) - if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { - desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err) - log.Warn(desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error(4, "CreateRepositoryNotice: %v", err) - } - } - return nil - }); err != nil { - log.Error(4, "GitFsck: %v", err) - } - log.Trace("Finished: GitFsck") -} - -// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository -func GitGcRepos() error { - args := append([]string{"gc"}, setting.Git.GCArgs...) - return x. - Where("id > 0").BufferSize(setting.IterateBufferSize). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if err := repo.GetOwner(); err != nil { - return err - } - _, stderr, err := process.GetManager().ExecDir( - time.Duration(setting.Git.Timeout.GC)*time.Second, - RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection", - "git", args...) - if err != nil { - return fmt.Errorf("%v: %v", err, stderr) - } - return nil - }) -} - -type repoChecker struct { - querySQL, correctSQL string - desc string -} - -func repoStatsCheck(checker *repoChecker) { - results, err := x.Query(checker.querySQL) - if err != nil { - log.Error(4, "Select %s: %v", checker.desc, err) - return - } - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating %s: %d", checker.desc, id) - _, err = x.Exec(checker.correctSQL, id, id) - if err != nil { - log.Error(4, "Update %s[%d]: %v", checker.desc, id, err) - } - } -} - -// CheckRepoStats checks the repository stats -func CheckRepoStats() { - if !taskStatusTable.StartIfNotRunning(checkRepos) { - return - } - defer taskStatusTable.Stop(checkRepos) - - log.Trace("Doing: CheckRepoStats") - - checkers := []*repoChecker{ - // Repository.NumWatches - { - "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)", - "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?", - "repository count 'num_watches'", - }, - // Repository.NumStars - { - "SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)", - "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", - "repository count 'num_stars'", - }, - // Label.NumIssues - { - "SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)", - "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", - "label count 'num_issues'", - }, - // User.NumRepos - { - "SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)", - "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", - "user count 'num_repos'", - }, - // Issue.NumComments - { - "SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)", - "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", - "issue count 'num_comments'", - }, - } - for i := range checkers { - repoStatsCheck(checkers[i]) - } - - // ***** START: Repository.NumClosedIssues ***** - desc := "repository count 'num_closed_issues'" - results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false) - if err != nil { - log.Error(4, "Select %s: %v", desc, err) - } else { - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating %s: %d", desc, id) - _, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) - if err != nil { - log.Error(4, "Update %s[%d]: %v", desc, id, err) - } - } - } - // ***** END: Repository.NumClosedIssues ***** - - // FIXME: use checker when stop supporting old fork repo format. - // ***** START: Repository.NumForks ***** - results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") - if err != nil { - log.Error(4, "Select repository count 'num_forks': %v", err) - } else { - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating repository count 'num_forks': %d", id) - - repo, err := GetRepositoryByID(id) - if err != nil { - log.Error(4, "GetRepositoryByID[%d]: %v", id, err) - continue - } - - rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) - if err != nil { - log.Error(4, "Select count of forks[%d]: %v", repo.ID, err) - continue - } - repo.NumForks = int(parseCountResult(rawResult)) - - if err = UpdateRepository(repo, false); err != nil { - log.Error(4, "UpdateRepository[%d]: %v", id, err) - continue - } - } - } - // ***** END: Repository.NumForks ***** -} - -// SetArchiveRepoState sets if a repo is archived -func (repo *Repository) SetArchiveRepoState(isArchived bool) (err error) { - repo.IsArchived = isArchived - _, err = x.Where("id = ?", repo.ID).Cols("is_archived").Update(repo) - return -} - -// ___________ __ -// \_ _____/__________| | __ -// | __)/ _ \_ __ \ |/ / -// | \( <_> ) | \/ < -// \___ / \____/|__| |__|_ \ -// \/ \/ - -// HasForkedRepo checks if given user has already forked a repository with given ID. -func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { - repo := new(Repository) - has, _ := x. - Where("owner_id=? AND fork_id=?", ownerID, repoID). - Get(repo) - return repo, has -} - -// ForkRepository forks a repository -func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) { - forkedRepo, err := oldRepo.GetUserFork(u.ID) - if err != nil { - return nil, err - } - if forkedRepo != nil { - return nil, ErrRepoAlreadyExist{ - Uname: u.Name, - Name: forkedRepo.Name, - } - } - - repo := &Repository{ - OwnerID: u.ID, - Owner: u, - Name: name, - LowerName: strings.ToLower(name), - Description: desc, - DefaultBranch: oldRepo.DefaultBranch, - IsPrivate: oldRepo.IsPrivate, - IsFork: true, - ForkID: oldRepo.ID, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, u, repo); err != nil { - return nil, err - } - - if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil { - return nil, err - } - - 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) - 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") - if err != nil { - return nil, fmt.Errorf("git update-server-info: %v", stderr) - } - - if err = createDelegateHooks(repoPath); err != nil { - return nil, fmt.Errorf("createDelegateHooks: %v", err) - } - - //Commit repo to get Fork ID - err = sess.Commit() - if err != nil { - return nil, err - } - - oldMode, _ := AccessLevel(doer, oldRepo) - mode, _ := AccessLevel(doer, repo) - - if err = PrepareWebhooks(oldRepo, HookEventFork, &api.ForkPayload{ - Forkee: oldRepo.APIFormat(oldMode), - Repo: repo.APIFormat(mode), - Sender: doer.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err) - } else { - go HookQueue.Add(oldRepo.ID) - } - - if err = repo.UpdateSize(); err != nil { - log.Error(4, "Failed to update size for repository: %v", err) - } - - // Copy LFS meta objects in new session - sess2 := x.NewSession() - defer sess2.Close() - if err = sess2.Begin(); err != nil { - return nil, err - } - - var lfsObjects []*LFSMetaObject - - if err = sess2.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { - return nil, err - } - - for _, v := range lfsObjects { - v.ID = 0 - v.RepositoryID = repo.ID - if _, err = sess2.Insert(v); err != nil { - return nil, err - } - } - - return repo, sess2.Commit() -} - -// GetForks returns all the forks of the repository -func (repo *Repository) GetForks() ([]*Repository, error) { - forks := make([]*Repository, 0, repo.NumForks) - return forks, x.Find(&forks, &Repository{ForkID: repo.ID}) -} - -// GetUserFork return user forked repository from this repository, if not forked return nil -func (repo *Repository) GetUserFork(userID int64) (*Repository, error) { - var forkedRepo Repository - has, err := x.Where("fork_id = ?", repo.ID).And("owner_id = ?", userID).Get(&forkedRepo) - if err != nil { - return nil, err - } - if !has { - return nil, nil - } - return &forkedRepo, nil -} +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea 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 models + +import ( + "bytes" + "errors" + "fmt" + "html/template" + "io/ioutil" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "sort" + "strings" + "time" + + "code.gitea.io/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/options" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sync" + "code.gitea.io/gitea/modules/util" + api "code.gitea.io/sdk/gitea" + + "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" +) + +var repoWorkingPool = sync.NewExclusivePool() + +var ( + // ErrMirrorNotExist mirror does not exist error + ErrMirrorNotExist = errors.New("Mirror does not exist") + + // ErrNameEmpty name is empty error + ErrNameEmpty = errors.New("Name is empty") +) + +var ( + // Gitignores contains the gitiginore files + Gitignores []string + + // Licenses contains the license files + Licenses []string + + // Readmes contains the readme files + Readmes []string + + // LabelTemplates contains the label template files + LabelTemplates []string + + // ItemsPerPage maximum items per page in forks, watchers and stars of a repo + ItemsPerPage = 40 +) + +// 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) + for i, t := range types { + files, err := options.Dir(t) + if err != nil { + log.Fatal(4, "Failed to get %s files: %v", t, err) + } + customPath := path.Join(setting.CustomPath, "options", t) + if com.IsDir(customPath) { + customFiles, err := com.StatDir(customPath) + if err != nil { + log.Fatal(4, "Failed to get custom %s files: %v", t, err) + } + + for _, f := range customFiles { + if !com.IsSliceContainsStr(files, f) { + files = append(files, f) + } + } + } + typeFiles[i] = files + } + + Gitignores = typeFiles[0] + Licenses = typeFiles[1] + Readmes = typeFiles[2] + LabelTemplates = typeFiles[3] + sort.Strings(Gitignores) + sort.Strings(Licenses) + sort.Strings(Readmes) + sort.Strings(LabelTemplates) + + // Filter out invalid names and promote preferred licenses. + sortedLicenses := make([]string, 0, len(Licenses)) + for _, name := range setting.Repository.PreferredLicenses { + if com.IsSliceContainsStr(Licenses, name) { + sortedLicenses = append(sortedLicenses, name) + } + } + for _, name := range Licenses { + if !com.IsSliceContainsStr(setting.Repository.PreferredLicenses, name) { + sortedLicenses = append(sortedLicenses, name) + } + } + Licenses = sortedLicenses +} + +// NewRepoContext creates a new repository context +func NewRepoContext() { + zip.Verbose = false + + // Check Git installation. + if _, err := exec.LookPath("git"); err != nil { + log.Fatal(4, "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(4, "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(4, "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(4, "Failed to set git %s(%s): %s", configKey, gerr, stderr) + } + log.Info("Git config %s set to %s", configKey, defaultValue) + } else { + log.Fatal(4, "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(4, "Failed to execute 'git config --global core.quotepath false': %s", stderr) + } + + RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) +} + +// Repository represents a git repository. +type Repository struct { + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"UNIQUE(s)"` + OwnerName string `xorm:"-"` + Owner *User `xorm:"-"` + LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` + Name string `xorm:"INDEX NOT NULL"` + Description string + Website string + DefaultBranch string + + NumWatches int + NumStars int + NumForks int + NumIssues int + NumClosedIssues int + NumOpenIssues int `xorm:"-"` + NumPulls int + NumClosedPulls int + NumOpenPulls int `xorm:"-"` + NumMilestones int `xorm:"NOT NULL DEFAULT 0"` + NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` + NumOpenMilestones int `xorm:"-"` + NumReleases int `xorm:"-"` + + IsPrivate bool `xorm:"INDEX"` + IsEmpty bool `xorm:"INDEX"` + IsArchived bool `xorm:"INDEX"` + + IsMirror bool `xorm:"INDEX"` + *Mirror `xorm:"-"` + + ExternalMetas map[string]string `xorm:"-"` + Units []*RepoUnit `xorm:"-"` + + IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` + ForkID int64 `xorm:"INDEX"` + BaseRepo *Repository `xorm:"-"` + Size int64 `xorm:"NOT NULL DEFAULT 0"` + IndexerStatus *RepoIndexerStatus `xorm:"-"` + IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` + CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` + Topics []string `xorm:"TEXT JSON"` + + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` +} + +// AfterLoad is invoked from XORM after setting the values of all fields of this object. +func (repo *Repository) AfterLoad() { + // FIXME: use models migration to solve all at once. + if len(repo.DefaultBranch) == 0 { + repo.DefaultBranch = "master" + } + + repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues + repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls + repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones +} + +// MustOwner always returns a valid *User object to avoid +// conceptually impossible error handling. +// It creates a fake object that contains error details +// when error occurs. +func (repo *Repository) MustOwner() *User { + return repo.mustOwner(x) +} + +// MustOwnerName always returns valid owner name to avoid +// conceptually impossible error handling. +// It returns "error" and logs error details when error +// occurs. +func (repo *Repository) MustOwnerName() string { + return repo.mustOwnerName(x) +} + +// FullName returns the repository full name +func (repo *Repository) FullName() string { + return repo.MustOwnerName() + "/" + repo.Name +} + +// HTMLURL returns the repository HTML URL +func (repo *Repository) HTMLURL() string { + return setting.AppURL + repo.FullName() +} + +// APIURL returns the repository API URL +func (repo *Repository) APIURL() string { + return setting.AppURL + path.Join("api/v1/repos", repo.FullName()) +} + +// APIFormat converts a Repository to api.Repository +func (repo *Repository) APIFormat(mode AccessMode) *api.Repository { + return repo.innerAPIFormat(x, mode, false) +} + +// GetCommitsCountCacheKey returns cache key used for commits count caching. +func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string { + var prefix string + if isRef { + prefix = "ref" + } else { + prefix = "commit" + } + return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName) +} + +func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) *api.Repository { + var parent *api.Repository + + cloneLink := repo.cloneLink(e, false) + permission := &api.Permission{ + Admin: mode >= AccessModeAdmin, + Push: mode >= AccessModeWrite, + Pull: mode >= AccessModeRead, + } + if !isParent { + err := repo.getBaseRepo(e) + if err != nil { + log.Error(4, "APIFormat: %v", err) + } + if repo.BaseRepo != nil { + parent = repo.BaseRepo.innerAPIFormat(e, mode, true) + } + } + 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, + } +} + +func (repo *Repository) getUnits(e Engine) (err error) { + if repo.Units != nil { + return nil + } + + repo.Units, err = getUnitsByRepoID(e, repo.ID) + return err +} + +// CheckUnitUser check whether user could visit the unit of this repository +func (repo *Repository) CheckUnitUser(userID int64, isAdmin bool, unitType UnitType) bool { + return repo.checkUnitUser(x, userID, isAdmin, unitType) +} + +func (repo *Repository) checkUnitUser(e Engine, userID int64, isAdmin bool, unitType UnitType) bool { + if isAdmin { + return true + } + user, err := getUserByID(e, userID) + if err != nil { + return false + } + perm, err := getUserRepoPermission(e, repo, user) + if err != nil { + return false + } + + return perm.CanRead(unitType) +} + +// UnitEnabled if this repository has the given unit enabled +func (repo *Repository) UnitEnabled(tp UnitType) bool { + if err := repo.getUnits(x); err != nil { + log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error()) + } + for _, unit := range repo.Units { + if unit.Type == tp { + return true + } + } + return false +} + +var ( + // ErrUnitNotExist organization does not exist + ErrUnitNotExist = errors.New("Unit does not exist") +) + +// MustGetUnit always returns a RepoUnit object +func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit { + ru, err := repo.GetUnit(tp) + if err == nil { + return ru + } + + if tp == UnitTypeExternalWiki { + return &RepoUnit{ + Type: tp, + Config: new(ExternalWikiConfig), + } + } else if tp == UnitTypeExternalTracker { + return &RepoUnit{ + Type: tp, + Config: new(ExternalTrackerConfig), + } + } else if tp == UnitTypePullRequests { + return &RepoUnit{ + Type: tp, + Config: new(PullRequestsConfig), + } + } + return &RepoUnit{ + Type: tp, + Config: new(UnitConfig), + } +} + +// GetUnit returns a RepoUnit object +func (repo *Repository) GetUnit(tp UnitType) (*RepoUnit, error) { + return repo.getUnit(x, tp) +} + +func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) { + if err := repo.getUnits(e); err != nil { + return nil, err + } + for _, unit := range repo.Units { + if unit.Type == tp { + return unit, nil + } + } + return nil, ErrUnitNotExist +} + +func (repo *Repository) getOwner(e Engine) (err error) { + if repo.Owner != nil { + return nil + } + + repo.Owner, err = getUserByID(e, repo.OwnerID) + return err +} + +// GetOwner returns the repository owner +func (repo *Repository) GetOwner() error { + return repo.getOwner(x) +} + +func (repo *Repository) mustOwner(e Engine) *User { + if err := repo.getOwner(e); err != nil { + return &User{ + Name: "error", + FullName: err.Error(), + } + } + + return repo.Owner +} + +func (repo *Repository) getOwnerName(e Engine) error { + if len(repo.OwnerName) > 0 { + return nil + } + + if repo.Owner != nil { + repo.OwnerName = repo.Owner.Name + return nil + } + + u := new(User) + has, err := e.ID(repo.OwnerID).Cols("name").Get(u) + if err != nil { + return err + } else if !has { + return ErrUserNotExist{repo.OwnerID, "", 0} + } + repo.OwnerName = u.Name + return nil +} + +// GetOwnerName returns the repository owner name +func (repo *Repository) GetOwnerName() error { + return repo.getOwnerName(x) +} + +func (repo *Repository) mustOwnerName(e Engine) string { + if err := repo.getOwnerName(e); err != nil { + log.Error(4, "Error loading repository owner name: %v", err) + return "error" + } + + return repo.OwnerName +} + +// ComposeMetas composes a map of metas for rendering external issue tracker URL. +func (repo *Repository) ComposeMetas() map[string]string { + unit, err := repo.GetUnit(UnitTypeExternalTracker) + if err != nil { + return nil + } + + if repo.ExternalMetas == nil { + repo.ExternalMetas = map[string]string{ + "format": unit.ExternalTrackerConfig().ExternalTrackerFormat, + "user": repo.MustOwner().Name, + "repo": repo.Name, + } + switch unit.ExternalTrackerConfig().ExternalTrackerStyle { + case markup.IssueNameStyleAlphanumeric: + repo.ExternalMetas["style"] = markup.IssueNameStyleAlphanumeric + default: + repo.ExternalMetas["style"] = markup.IssueNameStyleNumeric + } + + } + return repo.ExternalMetas +} + +// DeleteWiki removes the actual and local copy of repository wiki. +func (repo *Repository) DeleteWiki() error { + return repo.deleteWiki(x) +} + +func (repo *Repository) deleteWiki(e Engine) error { + wikiPaths := []string{repo.WikiPath(), repo.LocalWikiPath()} + for _, wikiPath := range wikiPaths { + removeAllWithNotice(e, "Delete repository wiki", wikiPath) + } + + _, err := e.Where("repo_id = ?", repo.ID).And("type = ?", UnitTypeWiki).Delete(new(RepoUnit)) + return err +} + +func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { + if err = repo.getOwner(e); err != nil { + return nil, err + } + + accesses := make([]*Access, 0, 10) + if err = e. + Where("repo_id = ? AND mode >= ?", repo.ID, AccessModeWrite). + Find(&accesses); err != nil { + return nil, err + } + + // Leave a seat for owner itself to append later, but if owner is an organization + // and just waste 1 unit is cheaper than re-allocate memory once. + users := make([]*User, 0, len(accesses)+1) + if len(accesses) > 0 { + userIDs := make([]int64, len(accesses)) + for i := 0; i < len(accesses); i++ { + userIDs[i] = accesses[i].UserID + } + + if err = e.In("id", userIDs).Find(&users); err != nil { + return nil, err + } + } + if !repo.Owner.IsOrganization() { + users = append(users, repo.Owner) + } + + return users, nil +} + +// GetAssignees returns all users that have write access and can be assigned to issues +// of the repository, +func (repo *Repository) GetAssignees() (_ []*User, err error) { + return repo.getAssignees(x) +} + +// GetMilestoneByID returns the milestone belongs to repository by given ID. +func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) { + return GetMilestoneByRepoID(repo.ID, milestoneID) +} + +// IssueStats returns number of open and closed repository issues by given filter mode. +func (repo *Repository) IssueStats(uid int64, filterMode int, isPull bool) (int64, int64) { + return GetRepoIssueStats(repo.ID, uid, filterMode, isPull) +} + +// GetMirror sets the repository mirror, returns an error upon failure +func (repo *Repository) GetMirror() (err error) { + repo.Mirror, err = GetMirrorByRepoID(repo.ID) + return err +} + +// GetBaseRepo populates repo.BaseRepo for a fork repository and +// returns an error on failure (NOTE: no error is returned for +// non-fork repositories, and BaseRepo will be left untouched) +func (repo *Repository) GetBaseRepo() (err error) { + return repo.getBaseRepo(x) +} + +func (repo *Repository) getBaseRepo(e Engine) (err error) { + if !repo.IsFork { + return nil + } + + repo.BaseRepo, err = getRepositoryByID(e, repo.ForkID) + return err +} + +func (repo *Repository) repoPath(e Engine) string { + return RepoPath(repo.mustOwnerName(e), repo.Name) +} + +// RepoPath returns the repository path +func (repo *Repository) RepoPath() string { + return repo.repoPath(x) +} + +// GitConfigPath returns the path to a repository's git config/ directory +func GitConfigPath(repoPath string) string { + return filepath.Join(repoPath, "config") +} + +// GitConfigPath returns the repository git config path +func (repo *Repository) GitConfigPath() string { + return GitConfigPath(repo.RepoPath()) +} + +// RelLink returns the repository relative link +func (repo *Repository) RelLink() string { + return "/" + repo.FullName() +} + +// Link returns the repository link +func (repo *Repository) Link() string { + return setting.AppSubURL + "/" + repo.FullName() +} + +// ComposeCompareURL returns the repository comparison URL +func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { + return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) +} + +// UpdateDefaultBranch updates the default branch +func (repo *Repository) UpdateDefaultBranch() error { + _, err := x.ID(repo.ID).Cols("default_branch").Update(repo) + return err +} + +// IsOwnedBy returns true when user owns this repository +func (repo *Repository) IsOwnedBy(userID int64) bool { + return repo.OwnerID == userID +} + +func (repo *Repository) updateSize(e Engine) error { + repoInfoSize, err := git.GetRepoSize(repo.repoPath(e)) + if err != nil { + return fmt.Errorf("UpdateSize: %v", err) + } + + repo.Size = repoInfoSize.Size + repoInfoSize.SizePack + _, err = e.ID(repo.ID).Cols("size").Update(repo) + return err +} + +// UpdateSize updates the repository size, calculating it using git.GetRepoSize +func (repo *Repository) UpdateSize() error { + return repo.updateSize(x) +} + +// CanUserFork returns true if specified user can fork repository. +func (repo *Repository) CanUserFork(user *User) (bool, error) { + if user == nil { + return false, nil + } + if repo.OwnerID != user.ID && !user.HasForkedRepo(repo.ID) { + return true, nil + } + if err := user.GetOwnedOrganizations(); err != nil { + return false, err + } + for _, org := range user.OwnedOrgs { + if repo.OwnerID != org.ID && !org.HasForkedRepo(repo.ID) { + return true, nil + } + } + return false, nil +} + +// CanEnablePulls returns true if repository meets the requirements of accepting pulls. +func (repo *Repository) CanEnablePulls() bool { + return !repo.IsMirror && !repo.IsEmpty +} + +// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled. +func (repo *Repository) AllowsPulls() bool { + return repo.CanEnablePulls() && repo.UnitEnabled(UnitTypePullRequests) +} + +// CanEnableEditor returns true if repository meets the requirements of web editor. +func (repo *Repository) CanEnableEditor() bool { + return !repo.IsMirror +} + +// GetWriters returns all users that have write access to the repository. +func (repo *Repository) GetWriters() (_ []*User, err error) { + return repo.getUsersWithAccessMode(x, AccessModeWrite) +} + +// getUsersWithAccessMode returns users that have at least given access mode to the repository. +func (repo *Repository) getUsersWithAccessMode(e Engine, mode AccessMode) (_ []*User, err error) { + if err = repo.getOwner(e); err != nil { + return nil, err + } + + accesses := make([]*Access, 0, 10) + if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { + return nil, err + } + + // Leave a seat for owner itself to append later, but if owner is an organization + // and just waste 1 unit is cheaper than re-allocate memory once. + users := make([]*User, 0, len(accesses)+1) + if len(accesses) > 0 { + userIDs := make([]int64, len(accesses)) + for i := 0; i < len(accesses); i++ { + userIDs[i] = accesses[i].UserID + } + + if err = e.In("id", userIDs).Find(&users); err != nil { + return nil, err + } + } + if !repo.Owner.IsOrganization() { + users = append(users, repo.Owner) + } + + 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 { + sanitize := func(s string) string { + return fmt.Sprintf(`%[1]s`, s) + } + return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize)) +} + +// LocalCopyPath returns the local repository copy path. +func LocalCopyPath() string { + if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { + return setting.Repository.Local.LocalCopyPath + } + return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) +} + +// LocalCopyPath returns the local repository copy path for the given repo. +func (repo *Repository) LocalCopyPath() string { + return path.Join(LocalCopyPath(), com.ToStr(repo.ID)) +} + +// UpdateLocalCopyBranch pulls latest changes of given branch from repoPath to localPath. +// It creates a new clone if local copy does not exist. +// This function checks out target branch by default, it is safe to assume subsequent +// operations are operating against target branch when caller has confidence for no race condition. +func UpdateLocalCopyBranch(repoPath, localPath, branch string) error { + if !com.IsExist(localPath) { + if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{ + Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, + Branch: branch, + }); err != nil { + return fmt.Errorf("git clone %s: %v", branch, err) + } + } else { + _, err := git.NewCommand("fetch", "origin").RunInDir(localPath) + if err != nil { + return fmt.Errorf("git fetch origin: %v", err) + } + if len(branch) > 0 { + if err := git.Checkout(localPath, git.CheckoutOptions{ + Branch: branch, + }); err != nil { + return fmt.Errorf("git checkout %s: %v", branch, err) + } + + if err := git.ResetHEAD(localPath, true, "origin/"+branch); err != nil { + return fmt.Errorf("git reset --hard origin/%s: %v", branch, err) + } + } + } + return nil +} + +// UpdateLocalCopyBranch makes sure local copy of repository in given branch is up-to-date. +func (repo *Repository) UpdateLocalCopyBranch(branch string) error { + return UpdateLocalCopyBranch(repo.RepoPath(), repo.LocalCopyPath(), branch) +} + +// PatchPath returns corresponding patch file path of repository by given issue ID. +func (repo *Repository) PatchPath(index int64) (string, error) { + return repo.patchPath(x, index) +} + +func (repo *Repository) patchPath(e Engine, index int64) (string, error) { + if err := repo.getOwner(e); err != nil { + return "", err + } + + return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil +} + +// SavePatch saves patch data to corresponding location by given issue ID. +func (repo *Repository) SavePatch(index int64, patch []byte) error { + return repo.savePatch(x, index, patch) +} + +func (repo *Repository) savePatch(e Engine, index int64, patch []byte) error { + patchPath, err := repo.patchPath(e, index) + if err != nil { + return fmt.Errorf("PatchPath: %v", err) + } + dir := filepath.Dir(patchPath) + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", dir, err) + } + + if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil { + return fmt.Errorf("WriteFile: %v", err) + } + + return nil +} + +func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { + has, err := e.Get(&Repository{ + OwnerID: u.ID, + LowerName: strings.ToLower(repoName), + }) + return has && com.IsDir(RepoPath(u.Name, repoName)), err +} + +// IsRepositoryExist returns true if the repository with given name under user has already existed. +func IsRepositoryExist(u *User, repoName string) (bool, error) { + return isRepositoryExist(x, u, repoName) +} + +// CloneLink represents different types of clone URLs of repository. +type CloneLink struct { + SSH string + HTTPS string + Git string +} + +// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name. +func ComposeHTTPSCloneURL(owner, repo string) string { + return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.QueryEscape(owner), url.QueryEscape(repo)) +} + +func (repo *Repository) cloneLink(e Engine, isWiki bool) *CloneLink { + repoName := repo.Name + if isWiki { + repoName += ".wiki" + } + + sshUser := setting.RunUser + if setting.SSH.StartBuiltinServer { + sshUser = setting.SSH.BuiltinServerUser + } + + repo.Owner = repo.mustOwner(e) + cl := new(CloneLink) + if setting.SSH.Port != 22 { + cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", sshUser, setting.SSH.Domain, setting.SSH.Port, repo.Owner.Name, repoName) + } else if setting.Repository.UseCompatSSHURI { + cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) + } else { + cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) + } + cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName) + return cl +} + +// CloneLink returns clone URLs of repository. +func (repo *Repository) CloneLink() (cl *CloneLink) { + return repo.cloneLink(x, false) +} + +// MigrateRepoOptions contains the repository migrate options +type MigrateRepoOptions struct { + Name string + Description string + IsPrivate bool + IsMirror bool + RemoteAddr string +} + +/* + GitHub, GitLab, Gogs: *.wiki.git + BitBucket: *.git/wiki +*/ +var commonWikiURLSuffixes = []string{".wiki.git", ".git/wiki"} + +// wikiRemoteURL returns accessible repository URL for wiki if exists. +// Otherwise, it returns an empty string. +func wikiRemoteURL(remote string) string { + remote = strings.TrimSuffix(remote, ".git") + for _, suffix := range commonWikiURLSuffixes { + wikiURL := remote + suffix + if git.IsRepoURLAccessible(wikiURL) { + return wikiURL + } + } + return "" +} + +// MigrateRepository migrates a existing repository from other project hosting. +func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, error) { + repo, err := CreateRepository(doer, u, CreateRepoOptions{ + Name: opts.Name, + Description: opts.Description, + IsPrivate: opts.IsPrivate, + IsMirror: opts.IsMirror, + }) + if err != nil { + return nil, err + } + + repoPath := RepoPath(u.Name, opts.Name) + wikiPath := WikiPath(u.Name, opts.Name) + + if u.IsOrganization() { + t, err := u.GetOwnerTeam() + if err != nil { + return nil, err + } + repo.NumWatches = t.NumMembers + } else { + repo.NumWatches = 1 + } + + migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second + + if err := os.RemoveAll(repoPath); err != nil { + return repo, fmt.Errorf("Failed to remove %s: %v", repoPath, err) + } + + if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{ + Mirror: true, + Quiet: true, + Timeout: migrateTimeout, + }); err != nil { + return repo, fmt.Errorf("Clone: %v", err) + } + + wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) + if len(wikiRemotePath) > 0 { + if err := os.RemoveAll(wikiPath); err != nil { + return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) + } + + if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ + Mirror: true, + Quiet: true, + Timeout: migrateTimeout, + Branch: "master", + }); err != nil { + log.Warn("Clone wiki: %v", err) + if err := os.RemoveAll(wikiPath); err != nil { + return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) + } + } + } + + // Check if repository is empty. + _, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1") + 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) + } + } + + if !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) + } + if headBranch != nil { + repo.DefaultBranch = headBranch.Name + } + + if err = SyncReleasesWithTags(repo, gitRepo); err != nil { + log.Error(4, "Failed to synchronize tags to releases for repository: %v", err) + } + } + + if err = repo.UpdateSize(); err != nil { + log.Error(4, "Failed to update size for repository: %v", err) + } + + if opts.IsMirror { + if _, err = x.InsertOne(&Mirror{ + RepoID: repo.ID, + Interval: setting.Mirror.DefaultInterval, + EnablePrune: true, + NextUpdateUnix: util.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval), + }); err != nil { + return repo, fmt.Errorf("InsertOne: %v", err) + } + + repo.IsMirror = true + err = UpdateRepository(repo, false) + } else { + repo, err = CleanUpMigrateInfo(repo) + } + + if err != nil && !repo.IsEmpty { + UpdateRepoIndexer(repo) + } + + return repo, err +} + +// cleanUpMigrateGitConfig removes mirror info which prevents "push --all". +// This also removes possible user credentials. +func cleanUpMigrateGitConfig(configPath string) error { + cfg, err := ini.Load(configPath) + if err != nil { + return fmt.Errorf("open config file: %v", err) + } + cfg.DeleteSection("remote \"origin\"") + if err = cfg.SaveToIndent(configPath, "\t"); err != nil { + return fmt.Errorf("save config file: %v", err) + } + return nil +} + +// createDelegateHooks creates all the hooks scripts for the repo +func createDelegateHooks(repoPath string) (err error) { + var ( + hookNames = []string{"pre-receive", "update", "post-receive"} + hookTpls = []string{ + fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + } + giteaHookTpls = []string{ + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + } + ) + + hookDir := filepath.Join(repoPath, "hooks") + + for i, hookName := range hookNames { + oldHookPath := filepath.Join(hookDir, hookName) + newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") + + if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { + return fmt.Errorf("create hooks dir '%s': %v", filepath.Join(hookDir, hookName+".d"), err) + } + + // WARNING: This will override all old server-side hooks + if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { + return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err) + } + + if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil { + return fmt.Errorf("write new hook file '%s': %v", newHookPath, err) + } + } + + return nil +} + +// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. +func CleanUpMigrateInfo(repo *Repository) (*Repository, error) { + repoPath := repo.RepoPath() + if err := createDelegateHooks(repoPath); err != nil { + return repo, fmt.Errorf("createDelegateHooks: %v", err) + } + if repo.HasWiki() { + if err := createDelegateHooks(repo.WikiPath()); err != nil { + return repo, fmt.Errorf("createDelegateHooks.(wiki): %v", err) + } + } + + if err := cleanUpMigrateGitConfig(repo.GitConfigPath()); err != nil { + return repo, fmt.Errorf("cleanUpMigrateGitConfig: %v", err) + } + if repo.HasWiki() { + if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil { + return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %v", err) + } + } + + return repo, UpdateRepository(repo, false) +} + +// initRepoCommit temporarily changes with work directory. +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 { + 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), + "-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 { + return fmt.Errorf("git push: %s", stderr) + } + return nil +} + +// CreateRepoOptions contains the create repository options +type CreateRepoOptions struct { + Name string + Description string + Gitignores string + IssueLabels string + License string + Readme string + IsPrivate bool + IsMirror bool + AutoInit bool +} + +func getRepoInitFile(tp, name string) ([]byte, error) { + cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") + relPath := path.Join("options", tp, cleanedName) + + // Use custom file when available. + customPath := path.Join(setting.CustomPath, relPath) + if com.IsFile(customPath) { + return ioutil.ReadFile(customPath) + } + + switch tp { + case "readme": + return options.Readme(cleanedName) + case "gitignore": + return options.Gitignore(cleanedName) + case "license": + return options.License(cleanedName) + case "label": + return options.Labels(cleanedName) + default: + return []byte{}, fmt.Errorf("Invalid init file type") + } +} + +func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { + // 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, + ) + if err != nil { + return fmt.Errorf("git clone: %v - %s", err, stderr) + } + + // README + data, err := getRepoInitFile("readme", opts.Readme) + if err != nil { + return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err) + } + + cloneLink := repo.cloneLink(e, false) + match := map[string]string{ + "Name": repo.Name, + "Description": repo.Description, + "CloneURL.SSH": cloneLink.SSH, + "CloneURL.HTTPS": cloneLink.HTTPS, + } + if err = ioutil.WriteFile(filepath.Join(tmpDir, "README.md"), + []byte(com.Expand(string(data), match)), 0644); err != nil { + return fmt.Errorf("write README.md: %v", err) + } + + // .gitignore + if len(opts.Gitignores) > 0 { + var buf bytes.Buffer + names := strings.Split(opts.Gitignores, ",") + for _, name := range names { + data, err = getRepoInitFile("gitignore", name) + if err != nil { + return fmt.Errorf("getRepoInitFile[%s]: %v", name, err) + } + buf.WriteString("# ---> " + name + "\n") + buf.Write(data) + buf.WriteString("\n") + } + + if buf.Len() > 0 { + if err = ioutil.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { + return fmt.Errorf("write .gitignore: %v", err) + } + } + } + + // Issue Labels + if len(opts.IssueLabels) > 0 { + list, err := GetLabelTemplateFile(opts.IssueLabels) + if err != nil { + return fmt.Errorf("GetLabelTemplateFile: %v", err) + } + + labels := make([]*Label, len(list)) + for i := 0; i < len(list); i++ { + labels[i] = &Label{ + RepoID: repo.ID, + Name: list[i][0], + Description: list[i][2], + Color: list[i][1], + } + } + for _, label := range labels { + if err = newLabel(e, label); err != nil { + return fmt.Errorf("newLabel: %v", err) + } + } + + } + + // LICENSE + if len(opts.License) > 0 { + data, err = getRepoInitFile("license", opts.License) + if err != nil { + return fmt.Errorf("getRepoInitFile[%s]: %v", opts.License, err) + } + + if err = ioutil.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { + return fmt.Errorf("write LICENSE: %v", err) + } + } + + return nil +} + +// InitRepository initializes README and .gitignore if needed. +func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) { + // Somehow the directory could exist. + if com.IsExist(repoPath) { + return fmt.Errorf("initRepository: path already exists: %s", repoPath) + } + + // Init git bare new repository. + if err = git.InitRepository(repoPath, true); err != nil { + return fmt.Errorf("InitRepository: %v", err) + } else if err = createDelegateHooks(repoPath); err != nil { + return fmt.Errorf("createDelegateHooks: %v", err) + } + + tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) + + // Initialize repository according to user's choice. + if opts.AutoInit { + + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", tmpDir, err) + } + + defer os.RemoveAll(tmpDir) + + if err = prepareRepoCommit(e, repo, tmpDir, repoPath, opts); err != nil { + return fmt.Errorf("prepareRepoCommit: %v", err) + } + + // Apply changes and commit. + if err = initRepoCommit(tmpDir, u.NewGitSig()); err != nil { + return fmt.Errorf("initRepoCommit: %v", err) + } + } + + // Re-fetch the repository from database before updating it (else it would + // override changes that were done earlier with sql) + if repo, err = getRepositoryByID(e, repo.ID); err != nil { + return fmt.Errorf("getRepositoryByID: %v", err) + } + + if !opts.AutoInit { + repo.IsEmpty = true + } + + repo.DefaultBranch = "master" + if err = updateRepository(e, repo, false); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return nil +} + +var ( + reservedRepoNames = []string{".", ".."} + reservedRepoPatterns = []string{"*.git", "*.wiki"} +) + +// IsUsableRepoName returns true when repository is usable +func IsUsableRepoName(name string) error { + return isUsableName(reservedRepoNames, reservedRepoPatterns, name) +} + +func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err error) { + if err = IsUsableRepoName(repo.Name); err != nil { + return err + } + + has, err := isRepositoryExist(e, u, repo.Name) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{u.Name, repo.Name} + } + + if _, err = e.Insert(repo); err != nil { + return err + } + if err = deleteRepoRedirect(e, u.ID, repo.Name); err != nil { + return err + } + + // insert units for repo + var units = make([]RepoUnit, 0, len(defaultRepoUnits)) + for _, tp := range defaultRepoUnits { + if tp == UnitTypeIssues { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Config: &IssuesConfig{ + EnableTimetracker: setting.Service.DefaultEnableTimetracking, + AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime, + EnableDependencies: setting.Service.DefaultEnableDependencies, + }, + }) + } else if tp == UnitTypePullRequests { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, + }) + } else { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + }) + } + + } + + if _, err = e.Insert(&units); err != nil { + return err + } + + u.NumRepos++ + // Remember visibility preference. + u.LastRepoVisibility = repo.IsPrivate + if err = updateUser(e, u); err != nil { + return fmt.Errorf("updateUser: %v", err) + } + + // Give access to all members in owner team. + if u.IsOrganization() { + t, err := u.getOwnerTeam(e) + if err != nil { + return fmt.Errorf("getOwnerTeam: %v", err) + } else if err = t.addRepository(e, repo); err != nil { + return fmt.Errorf("addRepository: %v", err) + } else if err = prepareWebhooks(e, repo, HookEventRepository, &api.RepositoryPayload{ + Action: api.HookRepoCreated, + Repository: repo.innerAPIFormat(e, AccessModeOwner, false), + Organization: u.APIFormat(), + Sender: doer.APIFormat(), + }); err != nil { + return fmt.Errorf("prepareWebhooks: %v", err) + } + go HookQueue.Add(repo.ID) + } else { + // Organization automatically called this in addRepository method. + if err = repo.recalculateAccesses(e); err != nil { + return fmt.Errorf("recalculateAccesses: %v", err) + } + } + + if setting.Service.AutoWatchNewRepos { + if err = watchRepo(e, doer.ID, repo.ID, true); err != nil { + return fmt.Errorf("watchRepo: %v", err) + } + } + if err = newRepoAction(e, doer, repo); err != nil { + return fmt.Errorf("newRepoAction: %v", err) + } + + return nil +} + +// CreateRepository creates a repository for the user/organization. +func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err error) { + if !doer.IsAdmin && !u.CanCreateRepo() { + return nil, ErrReachLimitOfRepo{u.MaxRepoCreation} + } + + repo := &Repository{ + OwnerID: u.ID, + Owner: u, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + IsPrivate: opts.IsPrivate, + IsFsckEnabled: !opts.IsMirror, + CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return nil, err + } + + if err = createRepository(sess, doer, u, repo); err != nil { + return nil, err + } + + // No need for init mirror. + if !opts.IsMirror { + repoPath := RepoPath(u.Name, repo.Name) + if err = initRepository(sess, repoPath, u, repo, opts); err != nil { + if err2 := os.RemoveAll(repoPath); err2 != nil { + log.Error(4, "initRepository: %v", err) + return nil, fmt.Errorf( + "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) + } + return nil, fmt.Errorf("initRepository: %v", err) + } + + _, stderr, err := process.GetManager().ExecDir(-1, + repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath), + "git", "update-server-info") + if err != nil { + return nil, errors.New("CreateRepository(git update-server-info): " + stderr) + } + } + + return repo, sess.Commit() +} + +func countRepositories(userID int64, private bool) int64 { + sess := x.Where("id > 0") + + if userID > 0 { + sess.And("owner_id = ?", userID) + } + if !private { + sess.And("is_private=?", false) + } + + count, err := sess.Count(new(Repository)) + if err != nil { + log.Error(4, "countRepositories: %v", err) + } + return count +} + +// CountRepositories returns number of repositories. +// Argument private only takes effect when it is false, +// set it true to count all repositories. +func CountRepositories(private bool) int64 { + return countRepositories(-1, private) +} + +// CountUserRepositories returns number of repositories user owns. +// Argument private only takes effect when it is false, +// set it true to count all repositories. +func CountUserRepositories(userID int64, private bool) int64 { + return countRepositories(userID, private) +} + +// RepoPath returns repository path by given user and repository name. +func RepoPath(userName, repoName string) string { + return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") +} + +// TransferOwnership transfers all corresponding setting from old user to new one. +func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error { + newOwner, err := GetUserByName(newOwnerName) + if err != nil { + return fmt.Errorf("get new owner '%s': %v", newOwnerName, err) + } + + // Check if new owner has repository with same name. + has, err := IsRepositoryExist(newOwner, repo.Name) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{newOwnerName, repo.Name} + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return fmt.Errorf("sess.Begin: %v", err) + } + + owner := repo.Owner + + // Note: we have to set value here to make sure recalculate accesses is based on + // new owner. + repo.OwnerID = newOwner.ID + repo.Owner = newOwner + + // Update repository. + if _, err := sess.ID(repo.ID).Update(repo); err != nil { + return fmt.Errorf("update owner: %v", err) + } + + // Remove redundant collaborators. + collaborators, err := repo.getCollaborators(sess) + if err != nil { + return fmt.Errorf("getCollaborators: %v", err) + } + + // Dummy object. + collaboration := &Collaboration{RepoID: repo.ID} + for _, c := range collaborators { + if c.ID != newOwner.ID { + isMember, err := isOrganizationMember(sess, newOwner.ID, c.ID) + if err != nil { + return fmt.Errorf("IsOrgMember: %v", err) + } else if !isMember { + continue + } + } + collaboration.UserID = c.ID + if _, err = sess.Delete(collaboration); err != nil { + return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) + } + } + + // Remove old team-repository relations. + if owner.IsOrganization() { + if err = owner.removeOrgRepo(sess, repo.ID); err != nil { + return fmt.Errorf("removeOrgRepo: %v", err) + } + } + + if newOwner.IsOrganization() { + t, err := newOwner.getOwnerTeam(sess) + if err != nil { + return fmt.Errorf("getOwnerTeam: %v", err) + } else if err = t.addRepository(sess, repo); err != nil { + return fmt.Errorf("add to owner team: %v", err) + } + } else { + // Organization called this in addRepository method. + if err = repo.recalculateAccesses(sess); err != nil { + return fmt.Errorf("recalculateAccesses: %v", err) + } + } + + // Update repository count. + if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil { + return fmt.Errorf("increase new owner repository count: %v", err) + } else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.ID); err != nil { + return fmt.Errorf("decrease old owner repository count: %v", err) + } + + if err = watchRepo(sess, doer.ID, repo.ID, true); err != nil { + return fmt.Errorf("watchRepo: %v", err) + } else if err = transferRepoAction(sess, doer, owner, repo); err != nil { + return fmt.Errorf("transferRepoAction: %v", err) + } + + // Rename remote repository to new path and delete local copy. + dir := UserPath(newOwner.Name) + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", dir, err) + } + + if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { + return fmt.Errorf("rename repository directory: %v", err) + } + removeAllWithNotice(sess, "Delete repository local copy", repo.LocalCopyPath()) + + // Rename remote wiki repository to new path and delete local copy. + wikiPath := WikiPath(owner.Name, repo.Name) + if com.IsExist(wikiPath) { + removeAllWithNotice(sess, "Delete repository wiki local copy", repo.LocalWikiPath()) + if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { + return fmt.Errorf("rename repository wiki: %v", err) + } + } + + return sess.Commit() +} + +// ChangeRepositoryName changes all corresponding setting from old repository name to new one. +func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) { + oldRepoName = strings.ToLower(oldRepoName) + newRepoName = strings.ToLower(newRepoName) + if err = IsUsableRepoName(newRepoName); err != nil { + return err + } + + has, err := IsRepositoryExist(u, newRepoName) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{u.Name, newRepoName} + } + + repo, err := GetRepositoryByName(u.ID, oldRepoName) + if err != nil { + return fmt.Errorf("GetRepositoryByName: %v", err) + } + + // Change repository directory name. We must lock the local copy of the + // repo so that we can atomically rename the repo path and updates the + // local copy's origin accordingly. + repoWorkingPool.CheckIn(com.ToStr(repo.ID)) + defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) + + newRepoPath := RepoPath(u.Name, newRepoName) + if err = os.Rename(repo.RepoPath(), newRepoPath); err != nil { + return fmt.Errorf("rename repository directory: %v", err) + } + + localPath := repo.LocalCopyPath() + if com.IsExist(localPath) { + _, err := git.NewCommand("remote", "set-url", "origin", newRepoPath).RunInDir(localPath) + if err != nil { + return fmt.Errorf("git remote set-url origin %s: %v", newRepoPath, err) + } + } + + wikiPath := repo.WikiPath() + if com.IsExist(wikiPath) { + if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { + return fmt.Errorf("rename repository wiki: %v", err) + } + RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) + } + + return nil +} + +func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, e. + Where("fork_id=?", forkID). + Find(&repos) +} + +// GetRepositoriesByForkID returns all repositories with given fork ID. +func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) { + return getRepositoriesByForkID(x, forkID) +} + +func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) { + repo.LowerName = strings.ToLower(repo.Name) + + if len(repo.Description) > 255 { + repo.Description = repo.Description[:255] + } + if len(repo.Website) > 255 { + repo.Website = repo.Website[:255] + } + + if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { + return fmt.Errorf("update: %v", err) + } + + if visibilityChanged { + if err = repo.getOwner(e); err != nil { + return fmt.Errorf("getOwner: %v", err) + } + if repo.Owner.IsOrganization() { + // Organization repository need to recalculate access table when visibility is changed. + if err = repo.recalculateTeamAccesses(e, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %v", err) + } + } + + // If repo has become private, we need to set its actions to private. + if repo.IsPrivate { + _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{ + IsPrivate: true, + }) + if err != nil { + return err + } + } + + // Create/Remove git-daemon-export-ok for git-daemon... + daemonExportFile := path.Join(repo.repoPath(e), `git-daemon-export-ok`) + if repo.IsPrivate && com.IsExist(daemonExportFile) { + if err = os.Remove(daemonExportFile); err != nil { + log.Error(4, "Failed to remove %s: %v", daemonExportFile, err) + } + } else if !repo.IsPrivate && !com.IsExist(daemonExportFile) { + if f, err := os.Create(daemonExportFile); err != nil { + log.Error(4, "Failed to create %s: %v", daemonExportFile, err) + } else { + f.Close() + } + } + + forkRepos, err := getRepositoriesByForkID(e, repo.ID) + if err != nil { + return fmt.Errorf("getRepositoriesByForkID: %v", err) + } + for i := range forkRepos { + forkRepos[i].IsPrivate = repo.IsPrivate + if err = updateRepository(e, forkRepos[i], true); err != nil { + return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) + } + } + + if err = repo.updateSize(e); err != nil { + log.Error(4, "Failed to update size for repository: %v", err) + } + } + + return nil +} + +// UpdateRepository updates a repository +func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + if err = updateRepository(sess, repo, visibilityChanged); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return sess.Commit() +} + +// UpdateRepositoryUnits updates a repository's units +func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + if _, err = sess.Where("repo_id = ?", repo.ID).Delete(new(RepoUnit)); err != nil { + return err + } + + if _, err = sess.Insert(units); err != nil { + return err + } + + return sess.Commit() +} + +// DeleteRepository deletes a repository for a user or organization. +func DeleteRepository(doer *User, uid, repoID int64) error { + // In case is a organization. + org, err := GetUserByID(uid) + if err != nil { + return err + } + if org.IsOrganization() { + if err = org.GetTeams(); err != nil { + return err + } + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + repo := &Repository{ID: repoID, OwnerID: uid} + has, err := sess.Get(repo) + if err != nil { + return err + } else if !has { + return ErrRepoNotExist{repoID, uid, "", ""} + } + + // Delete Deploy Keys + deployKeys, err := listDeployKeys(sess, repo.ID) + if err != nil { + return fmt.Errorf("listDeployKeys: %v", err) + } + for _, dKey := range deployKeys { + if err := deleteDeployKey(sess, doer, dKey.ID); err != nil { + return fmt.Errorf("deleteDeployKeys: %v", err) + } + } + + if cnt, err := sess.ID(repoID).Delete(&Repository{}); err != nil { + return err + } else if cnt != 1 { + return ErrRepoNotExist{repoID, uid, "", ""} + } + + if org.IsOrganization() { + for _, t := range org.Teams { + if !t.hasRepository(sess, repoID) { + continue + } else if err = t.removeRepository(sess, repo, false); err != nil { + return err + } + } + } + + if err = deleteBeans(sess, + &Access{RepoID: repo.ID}, + &Action{RepoID: repo.ID}, + &Watch{RepoID: repoID}, + &Star{RepoID: repoID}, + &Mirror{RepoID: repoID}, + &Milestone{RepoID: repoID}, + &Release{RepoID: repoID}, + &Collaboration{RepoID: repoID}, + &PullRequest{BaseRepoID: repoID}, + &RepoUnit{RepoID: repoID}, + &RepoRedirect{RedirectRepoID: repoID}, + &Webhook{RepoID: repoID}, + &HookTask{RepoID: repoID}, + &Notification{RepoID: repoID}, + &CommitStatus{RepoID: repoID}, + ); err != nil { + return fmt.Errorf("deleteBeans: %v", err) + } + + deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repoID}) + // Delete comments and attachments + if _, err = sess.In("issue_id", deleteCond). + Delete(&Comment{}); err != nil { + return err + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&IssueUser{}); err != nil { + return err + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&Reaction{}); err != nil { + return err + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&IssueWatch{}); err != nil { + return err + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&Stopwatch{}); err != nil { + return err + } + + attachmentPaths := make([]string, 0, 20) + attachments := make([]*Attachment, 0, len(attachmentPaths)) + if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id"). + Where("issue.repo_id = ?", repoID). + Find(&attachments); err != nil { + return err + } + for j := range attachments { + attachmentPaths = append(attachmentPaths, attachments[j].LocalPath()) + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&Attachment{}); err != nil { + return err + } + + if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil { + return err + } + + if _, err = sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil { + return err + } + + if repo.IsFork { + if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { + return fmt.Errorf("decrease fork count: %v", err) + } + } + + if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { + return err + } + + // FIXME: Remove repository files should be executed after transaction succeed. + repoPath := repo.repoPath(sess) + removeAllWithNotice(sess, "Delete repository files", repoPath) + + repo.deleteWiki(sess) + + // Remove attachment files. + for i := range attachmentPaths { + removeAllWithNotice(sess, "Delete attachment", attachmentPaths[i]) + } + + // Remove LFS objects + var lfsObjects []*LFSMetaObject + if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { + return err + } + + for _, v := range lfsObjects { + count, err := sess.Count(&LFSMetaObject{Oid: v.Oid}) + if err != nil { + return err + } + + if count > 1 { + continue + } + + 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 + } + } + + if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { + return err + } + + if repo.NumForks > 0 { + if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { + log.Error(4, "reset 'fork_id' and 'is_fork': %v", err) + } + } + + if err = sess.Commit(); err != nil { + if len(deployKeys) > 0 { + // We need to rewrite the public keys because the commit failed + if err2 := RewriteAllPublicKeys(); err2 != nil { + return fmt.Errorf("Commit: %v SSH Keys: %v", err, err2) + } + } + return fmt.Errorf("Commit: %v", err) + } + + if org.IsOrganization() { + if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{ + Action: api.HookRepoDeleted, + Repository: repo.APIFormat(AccessModeOwner), + Organization: org.APIFormat(), + Sender: doer.APIFormat(), + }); err != nil { + return err + } + go HookQueue.Add(repo.ID) + } + + DeleteRepoFromIndexer(repo) + return nil +} + +// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame. +func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { + var repo Repository + has, err := x.Select("repository.*"). + Join("INNER", "`user`", "`user`.id = repository.owner_id"). + Where("repository.lower_name = ?", strings.ToLower(repoName)). + And("`user`.lower_name = ?", strings.ToLower(ownerName)). + Get(&repo) + if err != nil { + return nil, err + } else if !has { + return nil, ErrRepoNotExist{0, 0, ownerName, repoName} + } + return &repo, nil +} + +// GetRepositoryByName returns the repository by given name under user if exists. +func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { + repo := &Repository{ + OwnerID: ownerID, + LowerName: strings.ToLower(name), + } + has, err := x.Get(repo) + if err != nil { + return nil, err + } else if !has { + return nil, ErrRepoNotExist{0, ownerID, "", name} + } + return repo, err +} + +func getRepositoryByID(e Engine, id int64) (*Repository, error) { + repo := new(Repository) + has, err := e.ID(id).Get(repo) + if err != nil { + return nil, err + } else if !has { + return nil, ErrRepoNotExist{id, 0, "", ""} + } + return repo, nil +} + +// GetRepositoryByID returns the repository by given id if exists. +func GetRepositoryByID(id int64) (*Repository, error) { + return getRepositoryByID(x, id) +} + +// GetRepositoriesMapByIDs returns the repositories by given id slice. +func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { + var repos = make(map[int64]*Repository, len(ids)) + return repos, x.In("id", ids).Find(&repos) +} + +// GetUserRepositories returns a list of repositories of given user. +func GetUserRepositories(userID int64, private bool, page, pageSize int, orderBy string) ([]*Repository, error) { + if len(orderBy) == 0 { + orderBy = "updated_unix DESC" + } + + sess := x. + Where("owner_id = ?", userID). + OrderBy(orderBy) + if !private { + sess.And("is_private=?", false) + } + + if page <= 0 { + page = 1 + } + sess.Limit(pageSize, (page-1)*pageSize) + + repos := make([]*Repository, 0, pageSize) + return repos, sess.Find(&repos) +} + +// GetUserMirrorRepositories returns a list of mirror repositories of given user. +func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, x. + Where("owner_id = ?", userID). + And("is_mirror = ?", true). + Find(&repos) +} + +func getRepositoryCount(e Engine, u *User) (int64, error) { + return e.Count(&Repository{OwnerID: u.ID}) +} + +func getPublicRepositoryCount(e Engine, u *User) (int64, error) { + return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID}) +} + +func getPrivateRepositoryCount(e Engine, u *User) (int64, error) { + return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID}) +} + +// GetRepositoryCount returns the total number of repositories of user. +func GetRepositoryCount(u *User) (int64, error) { + return getRepositoryCount(x, u) +} + +// GetPublicRepositoryCount returns the total number of public repositories of user. +func GetPublicRepositoryCount(u *User) (int64, error) { + return getPublicRepositoryCount(x, u) +} + +// GetPrivateRepositoryCount returns the total number of private repositories of user. +func GetPrivateRepositoryCount(u *User) (int64, error) { + return getPrivateRepositoryCount(x, u) +} + +// DeleteRepositoryArchives deletes all repositories' archives. +func DeleteRepositoryArchives() error { + return x. + Where("id > 0"). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repo := bean.(*Repository) + return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives")) + }) +} + +// DeleteOldRepositoryArchives deletes old repository archives. +func DeleteOldRepositoryArchives() { + if !taskStatusTable.StartIfNotRunning(archiveCleanup) { + return + } + defer taskStatusTable.Stop(archiveCleanup) + + log.Trace("Doing: ArchiveCleanup") + + if err := x.Where("id > 0").Iterate(new(Repository), deleteOldRepositoryArchives); err != nil { + log.Error(4, "ArchiveClean: %v", err) + } +} + +func deleteOldRepositoryArchives(idx int, bean interface{}) error { + repo := bean.(*Repository) + basePath := filepath.Join(repo.RepoPath(), "archives") + + for _, ty := range []string{"zip", "targz"} { + path := filepath.Join(basePath, ty) + file, err := os.Open(path) + if err != nil { + if !os.IsNotExist(err) { + log.Warn("Unable to open directory %s: %v", path, err) + return err + } + + // If the directory doesn't exist, that's okay. + continue + } + + files, err := file.Readdir(0) + file.Close() + if err != nil { + log.Warn("Unable to read directory %s: %v", path, err) + return err + } + + minimumOldestTime := time.Now().Add(-setting.Cron.ArchiveCleanup.OlderThan) + for _, info := range files { + if info.ModTime().Before(minimumOldestTime) && !info.IsDir() { + toDelete := filepath.Join(path, info.Name()) + // This is a best-effort purge, so we do not check error codes to confirm removal. + if err = os.Remove(toDelete); err != nil { + log.Trace("Unable to delete %s, but proceeding: %v", toDelete, err) + } + } + } + } + + return nil +} + +func gatherMissingRepoRecords() ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + if err := x. + Where("id > 0"). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repo := bean.(*Repository) + if !com.IsDir(repo.RepoPath()) { + repos = append(repos, repo) + } + return nil + }); err != nil { + if err2 := CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { + return nil, fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + return repos, nil +} + +// DeleteMissingRepositories deletes all repository records that lost Git files. +func DeleteMissingRepositories(doer *User) error { + repos, err := gatherMissingRepoRecords() + if err != nil { + return fmt.Errorf("gatherMissingRepoRecords: %v", err) + } + + if len(repos) == 0 { + return nil + } + + for _, repo := range repos { + log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) + if err := DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { + if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil { + return fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + } + return nil +} + +// ReinitMissingRepositories reinitializes all repository records that lost Git files. +func ReinitMissingRepositories() error { + repos, err := gatherMissingRepoRecords() + if err != nil { + return fmt.Errorf("gatherMissingRepoRecords: %v", err) + } + + if len(repos) == 0 { + return nil + } + + for _, repo := range repos { + log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) + if err := git.InitRepository(repo.RepoPath(), true); err != nil { + if err2 := CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil { + return fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + } + return nil +} + +// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks +// to make sure the binary and custom conf path are up-to-date. +func SyncRepositoryHooks() error { + return x.Cols("owner_id", "name").Where("id > 0").Iterate(new(Repository), + func(idx int, bean interface{}) error { + if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil { + return fmt.Errorf("SyncRepositoryHook: %v", err) + } + if bean.(*Repository).HasWiki() { + if err := createDelegateHooks(bean.(*Repository).WikiPath()); err != nil { + return fmt.Errorf("SyncRepositoryHook: %v", err) + } + } + return nil + }) +} + +// Prevent duplicate running tasks. +var taskStatusTable = sync.NewStatusTable() + +const ( + mirrorUpdate = "mirror_update" + gitFsck = "git_fsck" + checkRepos = "check_repos" + archiveCleanup = "archive_cleanup" +) + +// GitFsck calls 'git fsck' to check repository health. +func GitFsck() { + if !taskStatusTable.StartIfNotRunning(gitFsck) { + return + } + defer taskStatusTable.Stop(gitFsck) + + log.Trace("Doing: GitFsck") + + if err := x. + Where("id>0 AND is_fsck_enabled=?", true).BufferSize(setting.IterateBufferSize). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repo := bean.(*Repository) + repoPath := repo.RepoPath() + log.Trace("Running health check on repository %s", repoPath) + if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { + desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err) + log.Warn(desc) + if err = CreateRepositoryNotice(desc); err != nil { + log.Error(4, "CreateRepositoryNotice: %v", err) + } + } + return nil + }); err != nil { + log.Error(4, "GitFsck: %v", err) + } + log.Trace("Finished: GitFsck") +} + +// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository +func GitGcRepos() error { + args := append([]string{"gc"}, setting.Git.GCArgs...) + return x. + Where("id > 0").BufferSize(setting.IterateBufferSize). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repo := bean.(*Repository) + if err := repo.GetOwner(); err != nil { + return err + } + _, stderr, err := process.GetManager().ExecDir( + time.Duration(setting.Git.Timeout.GC)*time.Second, + RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection", + "git", args...) + if err != nil { + return fmt.Errorf("%v: %v", err, stderr) + } + return nil + }) +} + +type repoChecker struct { + querySQL, correctSQL string + desc string +} + +func repoStatsCheck(checker *repoChecker) { + results, err := x.Query(checker.querySQL) + if err != nil { + log.Error(4, "Select %s: %v", checker.desc, err) + return + } + for _, result := range results { + id := com.StrTo(result["id"]).MustInt64() + log.Trace("Updating %s: %d", checker.desc, id) + _, err = x.Exec(checker.correctSQL, id, id) + if err != nil { + log.Error(4, "Update %s[%d]: %v", checker.desc, id, err) + } + } +} + +// CheckRepoStats checks the repository stats +func CheckRepoStats() { + if !taskStatusTable.StartIfNotRunning(checkRepos) { + return + } + defer taskStatusTable.Stop(checkRepos) + + log.Trace("Doing: CheckRepoStats") + + checkers := []*repoChecker{ + // Repository.NumWatches + { + "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)", + "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?", + "repository count 'num_watches'", + }, + // Repository.NumStars + { + "SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)", + "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", + "repository count 'num_stars'", + }, + // Label.NumIssues + { + "SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)", + "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", + "label count 'num_issues'", + }, + // User.NumRepos + { + "SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)", + "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", + "user count 'num_repos'", + }, + // Issue.NumComments + { + "SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)", + "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", + "issue count 'num_comments'", + }, + } + for i := range checkers { + repoStatsCheck(checkers[i]) + } + + // ***** START: Repository.NumClosedIssues ***** + desc := "repository count 'num_closed_issues'" + results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false) + if err != nil { + log.Error(4, "Select %s: %v", desc, err) + } else { + for _, result := range results { + id := com.StrTo(result["id"]).MustInt64() + log.Trace("Updating %s: %d", desc, id) + _, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) + if err != nil { + log.Error(4, "Update %s[%d]: %v", desc, id, err) + } + } + } + // ***** END: Repository.NumClosedIssues ***** + + // FIXME: use checker when stop supporting old fork repo format. + // ***** START: Repository.NumForks ***** + results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") + if err != nil { + log.Error(4, "Select repository count 'num_forks': %v", err) + } else { + for _, result := range results { + id := com.StrTo(result["id"]).MustInt64() + log.Trace("Updating repository count 'num_forks': %d", id) + + repo, err := GetRepositoryByID(id) + if err != nil { + log.Error(4, "GetRepositoryByID[%d]: %v", id, err) + continue + } + + rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) + if err != nil { + log.Error(4, "Select count of forks[%d]: %v", repo.ID, err) + continue + } + repo.NumForks = int(parseCountResult(rawResult)) + + if err = UpdateRepository(repo, false); err != nil { + log.Error(4, "UpdateRepository[%d]: %v", id, err) + continue + } + } + } + // ***** END: Repository.NumForks ***** +} + +// SetArchiveRepoState sets if a repo is archived +func (repo *Repository) SetArchiveRepoState(isArchived bool) (err error) { + repo.IsArchived = isArchived + _, err = x.Where("id = ?", repo.ID).Cols("is_archived").Update(repo) + return +} + +// ___________ __ +// \_ _____/__________| | __ +// | __)/ _ \_ __ \ |/ / +// | \( <_> ) | \/ < +// \___ / \____/|__| |__|_ \ +// \/ \/ + +// HasForkedRepo checks if given user has already forked a repository with given ID. +func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { + repo := new(Repository) + has, _ := x. + Where("owner_id=? AND fork_id=?", ownerID, repoID). + Get(repo) + return repo, has +} + +// ForkRepository forks a repository +func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) { + forkedRepo, err := oldRepo.GetUserFork(u.ID) + if err != nil { + return nil, err + } + if forkedRepo != nil { + return nil, ErrRepoAlreadyExist{ + Uname: u.Name, + Name: forkedRepo.Name, + } + } + + repo := &Repository{ + OwnerID: u.ID, + Owner: u, + Name: name, + LowerName: strings.ToLower(name), + Description: desc, + DefaultBranch: oldRepo.DefaultBranch, + IsPrivate: oldRepo.IsPrivate, + IsFork: true, + ForkID: oldRepo.ID, + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return nil, err + } + + if err = createRepository(sess, doer, u, repo); err != nil { + return nil, err + } + + if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil { + return nil, err + } + + 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) + 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") + if err != nil { + return nil, fmt.Errorf("git update-server-info: %v", stderr) + } + + if err = createDelegateHooks(repoPath); err != nil { + return nil, fmt.Errorf("createDelegateHooks: %v", err) + } + + //Commit repo to get Fork ID + err = sess.Commit() + if err != nil { + return nil, err + } + + oldMode, _ := AccessLevel(doer, oldRepo) + mode, _ := AccessLevel(doer, repo) + + if err = PrepareWebhooks(oldRepo, HookEventFork, &api.ForkPayload{ + Forkee: oldRepo.APIFormat(oldMode), + Repo: repo.APIFormat(mode), + Sender: doer.APIFormat(), + }); err != nil { + log.Error(2, "PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err) + } else { + go HookQueue.Add(oldRepo.ID) + } + + if err = repo.UpdateSize(); err != nil { + log.Error(4, "Failed to update size for repository: %v", err) + } + + // Copy LFS meta objects in new session + sess2 := x.NewSession() + defer sess2.Close() + if err = sess2.Begin(); err != nil { + return nil, err + } + + var lfsObjects []*LFSMetaObject + + if err = sess2.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { + return nil, err + } + + for _, v := range lfsObjects { + v.ID = 0 + v.RepositoryID = repo.ID + if _, err = sess2.Insert(v); err != nil { + return nil, err + } + } + + return repo, sess2.Commit() +} + +// GetForks returns all the forks of the repository +func (repo *Repository) GetForks() ([]*Repository, error) { + forks := make([]*Repository, 0, repo.NumForks) + return forks, x.Find(&forks, &Repository{ForkID: repo.ID}) +} + +// GetUserFork return user forked repository from this repository, if not forked return nil +func (repo *Repository) GetUserFork(userID int64) (*Repository, error) { + var forkedRepo Repository + has, err := x.Where("fork_id = ?", repo.ID).And("owner_id = ?", userID).Get(&forkedRepo) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + return &forkedRepo, nil +} diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 1a67f2b884ebb..e32d5a4b25fc7 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -1,604 +1,605 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Copyright 2017 The Gitea 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 auth - -import ( - "net/url" - "strings" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/routers/utils" - - "github.com/Unknwon/com" - "github.com/go-macaron/binding" - macaron "gopkg.in/macaron.v1" -) - -// _______________________________________ _________.______________________ _______________.___. -// \______ \_ _____/\______ \_____ \ / _____/| \__ ___/\_____ \\______ \__ | | -// | _/| __)_ | ___// | \ \_____ \ | | | | / | \| _// | | -// | | \| \ | | / | \/ \| | | | / | \ | \\____ | -// |____|_ /_______ / |____| \_______ /_______ /|___| |____| \_______ /____|_ // ______| -// \/ \/ \/ \/ \/ \/ \/ - -// CreateRepoForm form for creating repository -type CreateRepoForm struct { - UID int64 `binding:"Required"` - RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` - Private bool - Description string `binding:"MaxSize(255)"` - AutoInit bool - Gitignores string - License string - Readme string -} - -// Validate validates the fields -func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// MigrateRepoForm form for migrating repository -type MigrateRepoForm struct { - // required: true - CloneAddr string `json:"clone_addr" binding:"Required"` - AuthUsername string `json:"auth_username"` - AuthPassword string `json:"auth_password"` - // required: true - UID int64 `json:"uid" binding:"Required"` - // required: true - RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` - Mirror bool `json:"mirror"` - Private bool `json:"private"` - Description string `json:"description" binding:"MaxSize(255)"` -} - -// Validate validates the fields -func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ParseRemoteAddr checks if given remote address is valid, -// and returns composed URL with needed username and password. -// It also checks if given user has permission when remote address -// is actually a local path. -func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { - remoteAddr := strings.TrimSpace(f.CloneAddr) - - // Remote address can be HTTP/HTTPS/Git URL or local path. - if strings.HasPrefix(remoteAddr, "http://") || - strings.HasPrefix(remoteAddr, "https://") || - strings.HasPrefix(remoteAddr, "git://") { - u, err := url.Parse(remoteAddr) - if err != nil { - return "", models.ErrInvalidCloneAddr{IsURLError: true} - } - if len(f.AuthUsername)+len(f.AuthPassword) > 0 { - u.User = url.UserPassword(f.AuthUsername, f.AuthPassword) - } - remoteAddr = u.String() - } else if !user.CanImportLocal() { - return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true} - } else if !com.IsDir(remoteAddr) { - return "", models.ErrInvalidCloneAddr{IsInvalidPath: true} - } - - return remoteAddr, nil -} - -// RepoSettingForm form for changing repository settings -type RepoSettingForm struct { - RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` - Description string `binding:"MaxSize(255)"` - Website string `binding:"ValidUrl;MaxSize(255)"` - Interval string - MirrorAddress string - Private bool - EnablePrune bool - - // Advanced settings - EnableWiki bool - EnableExternalWiki bool - ExternalWikiURL string - EnableIssues bool - EnableExternalTracker bool - ExternalTrackerURL string - TrackerURLFormat string - TrackerIssueStyle string - EnablePulls bool - PullsIgnoreWhitespace bool - PullsAllowMerge bool - PullsAllowRebase bool - PullsAllowRebaseMerge bool - PullsAllowSquash bool - EnableTimetracker bool - AllowOnlyContributorsToTrackTime bool - EnableIssueDependencies bool - IsArchived bool - - // Admin settings - EnableHealthCheck bool - EnableCloseIssuesViaCommitInAnyBranch bool -} - -// Validate validates the fields -func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// __________ .__ -// \______ \____________ ____ ____ | |__ -// | | _/\_ __ \__ \ / \_/ ___\| | \ -// | | \ | | \// __ \| | \ \___| Y \ -// |______ / |__| (____ /___| /\___ >___| / -// \/ \/ \/ \/ \/ - -// ProtectBranchForm form for changing protected branch settings -type ProtectBranchForm struct { - Protected bool - EnableWhitelist bool - WhitelistUsers string - WhitelistTeams string - EnableMergeWhitelist bool - MergeWhitelistUsers string - MergeWhitelistTeams string - RequiredApprovals int64 - ApprovalsWhitelistUsers string - ApprovalsWhitelistTeams string -} - -// Validate validates the fields -func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// __ __ ___. .__ .__ __ -// / \ / \ ____\_ |__ | |__ | |__ ____ | | __ -// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ / -// \ /\ ___/| \_\ \ Y \ Y ( <_> ) < -// \__/\ / \___ >___ /___| /___| /\____/|__|_ \ -// \/ \/ \/ \/ \/ \/ - -// WebhookForm form for changing web hook -type WebhookForm struct { - Events string - Create bool - Delete bool - Fork bool - Issues bool - IssueComment bool - Release bool - Push bool - PullRequest bool - Repository bool - Active bool -} - -// PushOnly if the hook will be triggered when push -func (f WebhookForm) PushOnly() bool { - return f.Events == "push_only" -} - -// SendEverything if the hook will be triggered any event -func (f WebhookForm) SendEverything() bool { - return f.Events == "send_everything" -} - -// ChooseEvents if the hook will be triggered choose events -func (f WebhookForm) ChooseEvents() bool { - return f.Events == "choose_events" -} - -// NewWebhookForm form for creating web hook -type NewWebhookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - ContentType int `binding:"Required"` - Secret string - WebhookForm -} - -// Validate validates the fields -func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// NewGogshookForm form for creating gogs hook -type NewGogshookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - ContentType int `binding:"Required"` - Secret string - WebhookForm -} - -// Validate validates the fields -func (f *NewGogshookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// NewSlackHookForm form for creating slack hook -type NewSlackHookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - Channel string `binding:"Required"` - Username string - IconURL string - Color string - WebhookForm -} - -// Validate validates the fields -func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// HasInvalidChannel validates the channel name is in the right format -func (f NewSlackHookForm) HasInvalidChannel() bool { - return !utils.IsValidSlackChannel(f.Channel) -} - -// NewDiscordHookForm form for creating discord hook -type NewDiscordHookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - Username string - IconURL string - WebhookForm -} - -// Validate validates the fields -func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// NewDingtalkHookForm form for creating dingtalk hook -type NewDingtalkHookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - WebhookForm -} - -// Validate validates the fields -func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// .___ -// | | ______ ________ __ ____ -// | |/ ___// ___/ | \_/ __ \ -// | |\___ \ \___ \| | /\ ___/ -// |___/____ >____ >____/ \___ > -// \/ \/ \/ - -// CreateIssueForm form for creating issue -type CreateIssueForm struct { - Title string `binding:"Required;MaxSize(255)"` - LabelIDs string `form:"label_ids"` - AssigneeIDs string `form:"assignee_ids"` - Ref string `form:"ref"` - MilestoneID int64 - AssigneeID int64 - Content string - Files []string -} - -// Validate validates the fields -func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// CreateCommentForm form for creating comment -type CreateCommentForm struct { - Content string - Status string `binding:"OmitEmpty;In(reopen,close)"` - Files []string -} - -// Validate validates the fields -func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ReactionForm form for adding and removing reaction -type ReactionForm struct { - Content string `binding:"Required;In(+1,-1,laugh,confused,heart,hooray)"` -} - -// Validate validates the fields -func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// _____ .__.__ __ -// / \ |__| | ____ _______/ |_ ____ ____ ____ -// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ -// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/ -// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ > -// \/ \/ \/ \/ \/ - -// CreateMilestoneForm form for creating milestone -type CreateMilestoneForm struct { - Title string `binding:"Required;MaxSize(50)"` - Content string - Deadline string -} - -// Validate validates the fields -func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// .____ ___. .__ -// | | _____ \_ |__ ____ | | -// | | \__ \ | __ \_/ __ \| | -// | |___ / __ \| \_\ \ ___/| |__ -// |_______ (____ /___ /\___ >____/ -// \/ \/ \/ \/ - -// CreateLabelForm form for creating label -type CreateLabelForm struct { - ID int64 - Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` - Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` - Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"` -} - -// Validate validates the fields -func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// InitializeLabelsForm form for initializing labels -type InitializeLabelsForm struct { - TemplateName string `binding:"Required"` -} - -// Validate validates the fields -func (f *InitializeLabelsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// __________ .__ .__ __________ __ -// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_ -// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ -// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | | -// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__| -// \/ \/ |__| \/ \/ - -// MergePullRequestForm form for merging Pull Request -// swagger:model MergePullRequestOption -type MergePullRequestForm struct { - // required: true - // enum: merge,rebase,rebase-merge,squash - Do string `binding:"Required;In(merge,rebase,rebase-merge,squash)"` - MergeTitleField string - MergeMessageField string -} - -// Validate validates the fields -func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// CodeCommentForm form for adding code comments for PRs -type CodeCommentForm struct { - Content string `binding:"Required"` - Side string `binding:"Required;In(previous,proposed)"` - Line int64 - TreePath string `form:"path" binding:"Required"` - IsReview bool `form:"is_review"` - Reply int64 `form:"reply"` -} - -// Validate validates the fields -func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// SubmitReviewForm for submitting a finished code review -type SubmitReviewForm struct { - Content string - Type string `binding:"Required;In(approve,comment,reject)"` -} - -// Validate validates the fields -func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ReviewType will return the corresponding reviewtype for type -func (f SubmitReviewForm) ReviewType() models.ReviewType { - switch f.Type { - case "approve": - return models.ReviewTypeApprove - case "comment": - return models.ReviewTypeComment - case "reject": - return models.ReviewTypeReject - default: - return models.ReviewTypeUnknown - } -} - -// HasEmptyContent checks if the content of the review form is empty. -func (f SubmitReviewForm) HasEmptyContent() bool { - reviewType := f.ReviewType() - - return (reviewType == models.ReviewTypeComment || reviewType == models.ReviewTypeReject) && - len(strings.TrimSpace(f.Content)) == 0 -} - -// __________ .__ -// \______ \ ____ | | ____ _____ ______ ____ -// | _// __ \| | _/ __ \\__ \ / ___// __ \ -// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/ -// |____|_ /\___ >____/\___ >____ /____ >\___ > -// \/ \/ \/ \/ \/ \/ - -// NewReleaseForm form for creating release -type NewReleaseForm struct { - TagName string `binding:"Required;GitRefName"` - Target string `form:"tag_target" binding:"Required"` - Title string `binding:"Required"` - Content string - Draft string - Prerelease bool - Files []string -} - -// Validate validates the fields -func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// EditReleaseForm form for changing release -type EditReleaseForm struct { - Title string `form:"title" binding:"Required"` - Content string `form:"content"` - Draft string `form:"draft"` - Prerelease bool `form:"prerelease"` - Files []string -} - -// Validate validates the fields -func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// __ __.__ __ .__ -// / \ / \__| | _|__| -// \ \/\/ / | |/ / | -// \ /| | <| | -// \__/\ / |__|__|_ \__| -// \/ \/ - -// NewWikiForm form for creating wiki -type NewWikiForm struct { - Title string `binding:"Required"` - Content string `binding:"Required"` - Message string -} - -// Validate validates the fields -// FIXME: use code generation to generate this method. -func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ___________ .___.__ __ -// \_ _____/ __| _/|__|/ |_ -// | __)_ / __ | | \ __\ -// | \/ /_/ | | || | -// /_______ /\____ | |__||__| -// \/ \/ - -// EditRepoFileForm form for changing repository file -type EditRepoFileForm struct { - TreePath string `binding:"Required;MaxSize(500)"` - Content string `binding:"Required"` - CommitSummary string `binding:"MaxSize(100)"` - CommitMessage string - CommitChoice string `binding:"Required;MaxSize(50)"` - NewBranchName string `binding:"GitRefName;MaxSize(100)"` - LastCommit string -} - -// Validate validates the fields -func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// EditPreviewDiffForm form for changing preview diff -type EditPreviewDiffForm struct { - Content string -} - -// Validate validates the fields -func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ____ ___ .__ .___ -// | | \______ | | _________ __| _/ -// | | /\____ \| | / _ \__ \ / __ | -// | | / | |_> > |_( <_> ) __ \_/ /_/ | -// |______/ | __/|____/\____(____ /\____ | -// |__| \/ \/ -// - -// UploadRepoFileForm form for uploading repository file -type UploadRepoFileForm struct { - TreePath string `binding:"MaxSize(500)"` - CommitSummary string `binding:"MaxSize(100)"` - CommitMessage string - CommitChoice string `binding:"Required;MaxSize(50)"` - NewBranchName string `binding:"GitRefName;MaxSize(100)"` - Files []string -} - -// Validate validates the fields -func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// RemoveUploadFileForm form for removing uploaded file -type RemoveUploadFileForm struct { - File string `binding:"Required;MaxSize(50)"` -} - -// Validate validates the fields -func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ________ .__ __ -// \______ \ ____ | | _____/ |_ ____ -// | | \_/ __ \| | _/ __ \ __\/ __ \ -// | ` \ ___/| |_\ ___/| | \ ___/ -// /_______ /\___ >____/\___ >__| \___ > -// \/ \/ \/ \/ - -// DeleteRepoFileForm form for deleting repository file -type DeleteRepoFileForm struct { - CommitSummary string `binding:"MaxSize(100)"` - CommitMessage string - CommitChoice string `binding:"Required;MaxSize(50)"` - NewBranchName string `binding:"GitRefName;MaxSize(100)"` -} - -// Validate validates the fields -func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ___________.__ ___________ __ -// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________ -// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ -// | | | | Y Y \ ___/ | | | | \// __ \\ \___| <\ ___/| | \/ -// |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__| -// \/ \/ \/ \/ \/ \/ - -// AddTimeManuallyForm form that adds spent time manually. -type AddTimeManuallyForm struct { - Hours int `binding:"Range(0,1000)"` - Minutes int `binding:"Range(0,1000)"` -} - -// Validate validates the fields -func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// SaveTopicForm form for save topics for repository -type SaveTopicForm struct { - Topics []string `binding:"topics;Required;"` -} - -// DeadlineForm hold the validation rules for deadlines -type DeadlineForm struct { - DateString string `form:"date" binding:"Required;Size(10)"` -} - -// Validate validates the fields -func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea 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 auth + +import ( + "net/url" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/routers/utils" + + "github.com/Unknwon/com" + "github.com/go-macaron/binding" + macaron "gopkg.in/macaron.v1" +) + +// _______________________________________ _________.______________________ _______________.___. +// \______ \_ _____/\______ \_____ \ / _____/| \__ ___/\_____ \\______ \__ | | +// | _/| __)_ | ___// | \ \_____ \ | | | | / | \| _// | | +// | | \| \ | | / | \/ \| | | | / | \ | \\____ | +// |____|_ /_______ / |____| \_______ /_______ /|___| |____| \_______ /____|_ // ______| +// \/ \/ \/ \/ \/ \/ \/ + +// CreateRepoForm form for creating repository +type CreateRepoForm struct { + UID int64 `binding:"Required"` + RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Private bool + Description string `binding:"MaxSize(255)"` + AutoInit bool + Gitignores string + IssueLabels string + License string + Readme string +} + +// Validate validates the fields +func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// MigrateRepoForm form for migrating repository +type MigrateRepoForm struct { + // required: true + CloneAddr string `json:"clone_addr" binding:"Required"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + // required: true + UID int64 `json:"uid" binding:"Required"` + // required: true + RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` + Mirror bool `json:"mirror"` + Private bool `json:"private"` + Description string `json:"description" binding:"MaxSize(255)"` +} + +// Validate validates the fields +func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ParseRemoteAddr checks if given remote address is valid, +// and returns composed URL with needed username and password. +// It also checks if given user has permission when remote address +// is actually a local path. +func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { + remoteAddr := strings.TrimSpace(f.CloneAddr) + + // Remote address can be HTTP/HTTPS/Git URL or local path. + if strings.HasPrefix(remoteAddr, "http://") || + strings.HasPrefix(remoteAddr, "https://") || + strings.HasPrefix(remoteAddr, "git://") { + u, err := url.Parse(remoteAddr) + if err != nil { + return "", models.ErrInvalidCloneAddr{IsURLError: true} + } + if len(f.AuthUsername)+len(f.AuthPassword) > 0 { + u.User = url.UserPassword(f.AuthUsername, f.AuthPassword) + } + remoteAddr = u.String() + } else if !user.CanImportLocal() { + return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true} + } else if !com.IsDir(remoteAddr) { + return "", models.ErrInvalidCloneAddr{IsInvalidPath: true} + } + + return remoteAddr, nil +} + +// RepoSettingForm form for changing repository settings +type RepoSettingForm struct { + RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Description string `binding:"MaxSize(255)"` + Website string `binding:"ValidUrl;MaxSize(255)"` + Interval string + MirrorAddress string + Private bool + EnablePrune bool + + // Advanced settings + EnableWiki bool + EnableExternalWiki bool + ExternalWikiURL string + EnableIssues bool + EnableExternalTracker bool + ExternalTrackerURL string + TrackerURLFormat string + TrackerIssueStyle string + EnablePulls bool + PullsIgnoreWhitespace bool + PullsAllowMerge bool + PullsAllowRebase bool + PullsAllowRebaseMerge bool + PullsAllowSquash bool + EnableTimetracker bool + AllowOnlyContributorsToTrackTime bool + EnableIssueDependencies bool + IsArchived bool + + // Admin settings + EnableHealthCheck bool + EnableCloseIssuesViaCommitInAnyBranch bool +} + +// Validate validates the fields +func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// __________ .__ +// \______ \____________ ____ ____ | |__ +// | | _/\_ __ \__ \ / \_/ ___\| | \ +// | | \ | | \// __ \| | \ \___| Y \ +// |______ / |__| (____ /___| /\___ >___| / +// \/ \/ \/ \/ \/ + +// ProtectBranchForm form for changing protected branch settings +type ProtectBranchForm struct { + Protected bool + EnableWhitelist bool + WhitelistUsers string + WhitelistTeams string + EnableMergeWhitelist bool + MergeWhitelistUsers string + MergeWhitelistTeams string + RequiredApprovals int64 + ApprovalsWhitelistUsers string + ApprovalsWhitelistTeams string +} + +// Validate validates the fields +func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// __ __ ___. .__ .__ __ +// / \ / \ ____\_ |__ | |__ | |__ ____ | | __ +// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ / +// \ /\ ___/| \_\ \ Y \ Y ( <_> ) < +// \__/\ / \___ >___ /___| /___| /\____/|__|_ \ +// \/ \/ \/ \/ \/ \/ + +// WebhookForm form for changing web hook +type WebhookForm struct { + Events string + Create bool + Delete bool + Fork bool + Issues bool + IssueComment bool + Release bool + Push bool + PullRequest bool + Repository bool + Active bool +} + +// PushOnly if the hook will be triggered when push +func (f WebhookForm) PushOnly() bool { + return f.Events == "push_only" +} + +// SendEverything if the hook will be triggered any event +func (f WebhookForm) SendEverything() bool { + return f.Events == "send_everything" +} + +// ChooseEvents if the hook will be triggered choose events +func (f WebhookForm) ChooseEvents() bool { + return f.Events == "choose_events" +} + +// NewWebhookForm form for creating web hook +type NewWebhookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + ContentType int `binding:"Required"` + Secret string + WebhookForm +} + +// Validate validates the fields +func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// NewGogshookForm form for creating gogs hook +type NewGogshookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + ContentType int `binding:"Required"` + Secret string + WebhookForm +} + +// Validate validates the fields +func (f *NewGogshookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// NewSlackHookForm form for creating slack hook +type NewSlackHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + Channel string `binding:"Required"` + Username string + IconURL string + Color string + WebhookForm +} + +// Validate validates the fields +func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// HasInvalidChannel validates the channel name is in the right format +func (f NewSlackHookForm) HasInvalidChannel() bool { + return !utils.IsValidSlackChannel(f.Channel) +} + +// NewDiscordHookForm form for creating discord hook +type NewDiscordHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + Username string + IconURL string + WebhookForm +} + +// Validate validates the fields +func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// NewDingtalkHookForm form for creating dingtalk hook +type NewDingtalkHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + WebhookForm +} + +// Validate validates the fields +func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// .___ +// | | ______ ________ __ ____ +// | |/ ___// ___/ | \_/ __ \ +// | |\___ \ \___ \| | /\ ___/ +// |___/____ >____ >____/ \___ > +// \/ \/ \/ + +// CreateIssueForm form for creating issue +type CreateIssueForm struct { + Title string `binding:"Required;MaxSize(255)"` + LabelIDs string `form:"label_ids"` + AssigneeIDs string `form:"assignee_ids"` + Ref string `form:"ref"` + MilestoneID int64 + AssigneeID int64 + Content string + Files []string +} + +// Validate validates the fields +func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// CreateCommentForm form for creating comment +type CreateCommentForm struct { + Content string + Status string `binding:"OmitEmpty;In(reopen,close)"` + Files []string +} + +// Validate validates the fields +func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ReactionForm form for adding and removing reaction +type ReactionForm struct { + Content string `binding:"Required;In(+1,-1,laugh,confused,heart,hooray)"` +} + +// Validate validates the fields +func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// _____ .__.__ __ +// / \ |__| | ____ _______/ |_ ____ ____ ____ +// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ +// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/ +// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ > +// \/ \/ \/ \/ \/ + +// CreateMilestoneForm form for creating milestone +type CreateMilestoneForm struct { + Title string `binding:"Required;MaxSize(50)"` + Content string + Deadline string +} + +// Validate validates the fields +func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// .____ ___. .__ +// | | _____ \_ |__ ____ | | +// | | \__ \ | __ \_/ __ \| | +// | |___ / __ \| \_\ \ ___/| |__ +// |_______ (____ /___ /\___ >____/ +// \/ \/ \/ \/ + +// CreateLabelForm form for creating label +type CreateLabelForm struct { + ID int64 + Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` + Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` + Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"` +} + +// Validate validates the fields +func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// InitializeLabelsForm form for initializing labels +type InitializeLabelsForm struct { + TemplateName string `binding:"Required"` +} + +// Validate validates the fields +func (f *InitializeLabelsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// __________ .__ .__ __________ __ +// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_ +// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ +// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | | +// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__| +// \/ \/ |__| \/ \/ + +// MergePullRequestForm form for merging Pull Request +// swagger:model MergePullRequestOption +type MergePullRequestForm struct { + // required: true + // enum: merge,rebase,rebase-merge,squash + Do string `binding:"Required;In(merge,rebase,rebase-merge,squash)"` + MergeTitleField string + MergeMessageField string +} + +// Validate validates the fields +func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// CodeCommentForm form for adding code comments for PRs +type CodeCommentForm struct { + Content string `binding:"Required"` + Side string `binding:"Required;In(previous,proposed)"` + Line int64 + TreePath string `form:"path" binding:"Required"` + IsReview bool `form:"is_review"` + Reply int64 `form:"reply"` +} + +// Validate validates the fields +func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// SubmitReviewForm for submitting a finished code review +type SubmitReviewForm struct { + Content string + Type string `binding:"Required;In(approve,comment,reject)"` +} + +// Validate validates the fields +func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ReviewType will return the corresponding reviewtype for type +func (f SubmitReviewForm) ReviewType() models.ReviewType { + switch f.Type { + case "approve": + return models.ReviewTypeApprove + case "comment": + return models.ReviewTypeComment + case "reject": + return models.ReviewTypeReject + default: + return models.ReviewTypeUnknown + } +} + +// HasEmptyContent checks if the content of the review form is empty. +func (f SubmitReviewForm) HasEmptyContent() bool { + reviewType := f.ReviewType() + + return (reviewType == models.ReviewTypeComment || reviewType == models.ReviewTypeReject) && + len(strings.TrimSpace(f.Content)) == 0 +} + +// __________ .__ +// \______ \ ____ | | ____ _____ ______ ____ +// | _// __ \| | _/ __ \\__ \ / ___// __ \ +// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/ +// |____|_ /\___ >____/\___ >____ /____ >\___ > +// \/ \/ \/ \/ \/ \/ + +// NewReleaseForm form for creating release +type NewReleaseForm struct { + TagName string `binding:"Required;GitRefName"` + Target string `form:"tag_target" binding:"Required"` + Title string `binding:"Required"` + Content string + Draft string + Prerelease bool + Files []string +} + +// Validate validates the fields +func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// EditReleaseForm form for changing release +type EditReleaseForm struct { + Title string `form:"title" binding:"Required"` + Content string `form:"content"` + Draft string `form:"draft"` + Prerelease bool `form:"prerelease"` + Files []string +} + +// Validate validates the fields +func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// __ __.__ __ .__ +// / \ / \__| | _|__| +// \ \/\/ / | |/ / | +// \ /| | <| | +// \__/\ / |__|__|_ \__| +// \/ \/ + +// NewWikiForm form for creating wiki +type NewWikiForm struct { + Title string `binding:"Required"` + Content string `binding:"Required"` + Message string +} + +// Validate validates the fields +// FIXME: use code generation to generate this method. +func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ___________ .___.__ __ +// \_ _____/ __| _/|__|/ |_ +// | __)_ / __ | | \ __\ +// | \/ /_/ | | || | +// /_______ /\____ | |__||__| +// \/ \/ + +// EditRepoFileForm form for changing repository file +type EditRepoFileForm struct { + TreePath string `binding:"Required;MaxSize(500)"` + Content string `binding:"Required"` + CommitSummary string `binding:"MaxSize(100)"` + CommitMessage string + CommitChoice string `binding:"Required;MaxSize(50)"` + NewBranchName string `binding:"GitRefName;MaxSize(100)"` + LastCommit string +} + +// Validate validates the fields +func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// EditPreviewDiffForm form for changing preview diff +type EditPreviewDiffForm struct { + Content string +} + +// Validate validates the fields +func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ____ ___ .__ .___ +// | | \______ | | _________ __| _/ +// | | /\____ \| | / _ \__ \ / __ | +// | | / | |_> > |_( <_> ) __ \_/ /_/ | +// |______/ | __/|____/\____(____ /\____ | +// |__| \/ \/ +// + +// UploadRepoFileForm form for uploading repository file +type UploadRepoFileForm struct { + TreePath string `binding:"MaxSize(500)"` + CommitSummary string `binding:"MaxSize(100)"` + CommitMessage string + CommitChoice string `binding:"Required;MaxSize(50)"` + NewBranchName string `binding:"GitRefName;MaxSize(100)"` + Files []string +} + +// Validate validates the fields +func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// RemoveUploadFileForm form for removing uploaded file +type RemoveUploadFileForm struct { + File string `binding:"Required;MaxSize(50)"` +} + +// Validate validates the fields +func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ________ .__ __ +// \______ \ ____ | | _____/ |_ ____ +// | | \_/ __ \| | _/ __ \ __\/ __ \ +// | ` \ ___/| |_\ ___/| | \ ___/ +// /_______ /\___ >____/\___ >__| \___ > +// \/ \/ \/ \/ + +// DeleteRepoFileForm form for deleting repository file +type DeleteRepoFileForm struct { + CommitSummary string `binding:"MaxSize(100)"` + CommitMessage string + CommitChoice string `binding:"Required;MaxSize(50)"` + NewBranchName string `binding:"GitRefName;MaxSize(100)"` +} + +// Validate validates the fields +func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ___________.__ ___________ __ +// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________ +// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ +// | | | | Y Y \ ___/ | | | | \// __ \\ \___| <\ ___/| | \/ +// |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__| +// \/ \/ \/ \/ \/ \/ + +// AddTimeManuallyForm form that adds spent time manually. +type AddTimeManuallyForm struct { + Hours int `binding:"Range(0,1000)"` + Minutes int `binding:"Range(0,1000)"` +} + +// Validate validates the fields +func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// SaveTopicForm form for save topics for repository +type SaveTopicForm struct { + Topics []string `binding:"topics;Required;"` +} + +// DeadlineForm hold the validation rules for deadlines +type DeadlineForm struct { + DateString string `form:"date" binding:"Required;Size(10)"` +} + +// Validate validates the fields +func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ebbf73b05da5c..ce1f543e3190c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1,1773 +1,1775 @@ -app_desc = A painless, self-hosted Git service - -home = Home -dashboard = Dashboard -explore = Explore -help = Help -sign_in = Sign In -sign_in_with = Sign In With -sign_out = Sign Out -sign_up = Register -link_account = Link Account -register = Register -website = Website -version = Version -page = Page -template = Template -language = Language -notifications = Notifications -create_new = Create… -user_profile_and_more = Profile and Settings… -signed_in_as = Signed in as -enable_javascript = This website works better with JavaScript. - -username = Username -email = Email Address -password = Password -re_type = Re-Type Password -captcha = CAPTCHA -twofa = Two-Factor Authentication -twofa_scratch = Two-Factor Scratch Code -passcode = Passcode - -u2f_insert_key = Insert your security key -u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it. -u2f_press_button = Please press the button on your security key… -u2f_use_twofa = Use a two-factor code from your phone -u2f_error = Could not read your security key. -u2f_unsupported_browser = Your browser does not support U2F security keys. -u2f_error_1 = An unknown error occurred. Please retry. -u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL. -u2f_error_3 = The server could not process your request. -u2f_error_4 = The security key is not permitted for this request. Please make sure that the key is not already registered. -u2f_error_5 = Timeout reached before your key could be read. Please reload this page and retry. -u2f_reload = Reload - -repository = Repository -organization = Organization -mirror = Mirror -new_repo = New Repository -new_migrate = New Migration -new_mirror = New Mirror -new_fork = New Repository Fork -new_org = New Organization -manage_org = Manage Organizations -admin_panel = Site Administration -account_settings = Account Settings -settings = Settings -your_profile = Profile -your_starred = Starred -your_settings = Settings - -all = All -sources = Sources -mirrors = Mirrors -collaborative = Collaborative -forks = Forks - -activities = Activities -pull_requests = Pull Requests -issues = Issues - -cancel = Cancel - -write = Write -preview = Preview -loading = Loading… - -[install] -install = Installation -title = Initial Configuration -docker_helper = If you run Gitea inside Docker, please read the documentation before changing any settings. -requite_db_desc = Gitea requires MySQL, PostgreSQL, MSSQL or SQLite3. -db_title = Database Settings -db_type = Database Type -host = Host -user = Username -password = Password -db_name = Database Name -db_helper = Note to MySQL users: please use the InnoDB storage engine and the 'utf8_general_ci' character set. -ssl_mode = SSL -path = Path -sqlite_helper = File path for the SQLite3 database.
Enter an absolute path if you run Gitea as a service. -err_empty_db_path = The SQLite3 database path cannot be empty. -no_admin_and_disable_registration = You cannot disable user self-registration without creating an administrator account. -err_empty_admin_password = The administrator password cannot be empty. - -general_title = General Settings -app_name = Site Title -app_name_helper = You can enter your company name here. -repo_path = Repository Root Path -repo_path_helper = Remote Git repositories will be saved to this directory. -lfs_path = Git LFS Root Path -lfs_path_helper = Files tracked by Git LFS will be stored in this directory. Leave empty to disable. -run_user = Run As Username -run_user_helper = Enter the operating system username that Gitea runs as. Note that this user must have access to the repository root path. -domain = SSH Server Domain -domain_helper = Domain or host address for SSH clone URLs. -ssh_port = SSH Server Port -ssh_port_helper = Port number your SSH server listens on. Leave empty to disable. -http_port = Gitea HTTP Listen Port -http_port_helper = Port number the Giteas web server will listen on. -app_url = Gitea Base URL -app_url_helper = Base address for HTTP(S) clone URLs and email notifications. -log_root_path = Log Path -log_root_path_helper = Log files will be written to this directory. - -optional_title = Optional Settings -email_title = Email Settings -smtp_host = SMTP Host -smtp_from = Send Email As -smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" format. -mailer_user = SMTP Username -mailer_password = SMTP Password -register_confirm = Require Email Confirmation to Register -mail_notify = Enable Email Notifications -server_service_title = Server and Third-Party Service Settings -offline_mode = Enable Local Mode -offline_mode_popup = Disable third-party content delivery networks and serve all resources locally. -disable_gravatar = Disable Gravatar -disable_gravatar_popup = Disable Gravatar and third-party avatar sources. A default avatar will be used unless a user locally uploads an avatar. -federated_avatar_lookup = Enable Federated Avatars -federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar. -disable_registration = Disable Self-Registration -disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts. -allow_only_external_registration_popup = Allow Registration Only Through External Services -openid_signin = Enable OpenID Sign-In -openid_signin_popup = Enable user sign-in via OpenID. -openid_signup = Enable OpenID Self-Registration -openid_signup_popup = Enable OpenID-based user self-registration. -enable_captcha = Enable CAPTCHA -enable_captcha_popup = Require a CAPTCHA for user self-registration. -require_sign_in_view = Require Sign-In to View Pages -require_sign_in_view_popup = Limit page access to signed-in users. Visitors will only see the 'sign in' and registration pages. -admin_setting_desc = Creating an administrator account is optional. The first registered user will automatically become an administrator. -admin_title = Administrator Account Settings -admin_name = Administrator Username -admin_password = Password -confirm_password = Confirm Password -admin_email = Email Address -install_btn_confirm = Install Gitea -test_git_failed = Could not test 'git' command: %v -sqlite3_not_available = This Gitea version does not support SQLite3. Please download the official binary version from %s (not the 'gobuild' version). -invalid_db_setting = The database settings are invalid: %v -invalid_repo_path = The repository root path is invalid: %v -run_user_not_match = The 'run as' username is not the current username: %s -> %s -save_config_failed = Failed to save configuration: %v -invalid_admin_setting = Administrator account setting is invalid: %v -install_success = Welcome! Thank you for choosing Gitea. Have fun and take care! -invalid_log_root_path = The log path is invalid: %v -default_keep_email_private = Hide Email Addresses by Default -default_keep_email_private_popup = Hide email addresses of new user accounts by default. -default_allow_create_organization = Allow Creation of Organizations by Default -default_allow_create_organization_popup = Allow new user accounts to create organizations by default. -default_enable_timetracking = Enable Time Tracking by Default -default_enable_timetracking_popup = Enable time tracking for new repositories by default. -no_reply_address = Hidden Email Domain -no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'. - -[home] -uname_holder = Username or Email Address -password_holder = Password -switch_dashboard_context = Switch Dashboard Context -my_repos = Repositories -show_more_repos = Show more repositories… -collaborative_repos = Collaborative Repositories -my_orgs = My Organizations -my_mirrors = My Mirrors -view_home = View %s -search_repos = Find a repository… - -issues.in_your_repos = In your repositories - -[explore] -repos = Repositories -users = Users -organizations = Organizations -search = Search -code = Code -repo_no_results = No matching repositories found. -user_no_results = No matching users found. -org_no_results = No matching organizations found. -code_no_results = No source code matching your search term found. -code_search_results = Search results for '%s' - -[auth] -create_new_account = Register Account -register_helper_msg = Already have an account? Sign in now! -social_register_helper_msg = Already have an account? Link it now! -disable_register_prompt = Registration is disabled. Please contact your site administrator. -disable_register_mail = Email confirmation for registration is disabled. -remember_me = Remember Me -forgot_password_title= Forgot Password -forgot_password = Forgot password? -sign_up_now = Need an account? Register now. -sign_up_successful = Account was successfully created. -confirmation_mail_sent_prompt = A new confirmation email has been sent to %s. Please check your inbox within the next %s to complete the registration process. -must_change_password = Update your password -allow_password_change = Require user to change password (recommended) -reset_password_mail_sent_prompt = A confirmation email has been sent to %s. Please check your inbox within the next %s to complete the password reset process. -active_your_account = Activate Your Account -account_activated = Account has been activated -prohibit_login = Sign In Prohibited -prohibit_login_desc = Your account is prohibited to sign in, please contact your site administrator. -resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again. -has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (%s). If you haven't received a confirmation email or need to resend a new one, please click on the button below. -resend_mail = Click here to resend your activation email -email_not_associate = The email address is not associated with any account. -send_reset_mail = Click here to resend your password reset email -reset_password = Reset Your Password -invalid_code = Your confirmation code is invalid or has expired. -reset_password_helper = Click here to reset your password -password_too_short = Password length cannot be less than %d characters. -non_local_account = Non-local users can not update their password through the Gitea web interface. -verify = Verify -scratch_code = Scratch code -use_scratch_code = Use a scratch code -twofa_scratch_used = You have used your scratch code. You have been redirected to the two-factor settings page so you may remove your device enrollment or generate a new scratch code. -twofa_passcode_incorrect = Your passcode is incorrect. If you misplaced your device, use your scratch code to sign in. -twofa_scratch_token_incorrect = Your scratch code is incorrect. -login_userpass = Sign In -login_openid = OpenID -oauth_signup_tab = Register New Account -oauth_signup_title = Add Email and Password (for Account Recovery) -oauth_signup_submit = Complete Account -oauth_signin_tab = Link to Existing Account -oauth_signin_title = Sign In to Authorize Linked Account -oauth_signin_submit = Link Account -openid_connect_submit = Connect -openid_connect_title = Connect to an existing account -openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here. -openid_register_title = Create new account -openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here. -openid_signin_desc = Enter your OpenID URI. For example: https://anne.me, bob.openid.org.cn or gnusocial.net/carry. -disable_forgot_password_mail = Password reset is disabled. Please contact your site administrator. -email_domain_blacklisted = You cannot register with your email address. - -[mail] -activate_account = Please activate your account -activate_email = Verify your email address -reset_password = Reset your password -register_success = Registration successful -register_notify = Welcome to Gitea - -[modal] -yes = Yes -no = No -modify = Update - -[form] -UserName = Username -RepoName = Repository name -Email = Email address -Password = Password -Retype = Re-Type Password -SSHTitle = SSH key name -HttpsUrl = HTTPS URL -PayloadUrl = Payload URL -TeamName = Team name -AuthName = Authorization name -AdminEmail = Admin email - -NewBranchName = New branch name -CommitSummary = Commit summary -CommitMessage = Commit message -CommitChoice = Commit choice -TreeName = File path -Content = Content - -require_error = ` cannot be empty.` -alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` -alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` -git_ref_name_error = ` must be a well-formed Git reference name.` -size_error = ` must be size %s.` -min_size_error = ` must contain at least %s characters.` -max_size_error = ` must contain at most %s characters.` -email_error = ` is not a valid email address.` -url_error = ` is not a valid URL.` -include_error = ` must contain substring '%s'.` -unknown_error = Unknown error: -captcha_incorrect = The CAPTCHA code is incorrect. -password_not_match = The passwords do not match. - -username_been_taken = The username is already taken. -repo_name_been_taken = The repository name is already used. -org_name_been_taken = The organization name is already taken. -team_name_been_taken = The team name is already taken. -team_no_units_error = Allow access to at least one repository section. -email_been_used = The email address is already used. -openid_been_used = The OpenID address '%s' is already used. -username_password_incorrect = Username or password is incorrect. -enterred_invalid_repo_name = The repository name you entered is incorrect. -enterred_invalid_owner_name = The new owner name is not valid. -enterred_invalid_password = The password you entered is incorrect. -user_not_exist = The user does not exist. -last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team. -cannot_add_org_to_team = An organization cannot be added as a team member. - -invalid_ssh_key = Can not verify your SSH key: %s -invalid_gpg_key = Can not verify your GPG key: %s -unable_verify_ssh_key = "Can not verify the SSH key; double-check it for mistakes." -auth_failed = Authentication failed: %v - -still_own_repo = "Your account owns one or more repositories; delete or transfer them first." -still_has_org = "Your account is a member of one or more organizations; leave them first." -org_still_own_repo = "This organization still owns one or more repositories; delete or transfer them first." - -target_branch_not_exist = Target branch does not exist. - -[user] -change_avatar = Change your avatar… -join_on = Joined on -repositories = Repositories -activity = Public Activity -followers = Followers -starred = Starred Repositories -following = Following -follow = Follow -unfollow = Unfollow -heatmap.loading = Loading Heatmap… - -form.name_reserved = The username '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. - -[settings] -profile = Profile -account = Account -password = Password -security = Security -avatar = Avatar -ssh_gpg_keys = SSH / GPG Keys -social = Social Accounts -applications = Applications -orgs = Manage Organizations -repos = Repositories -delete = Delete Account -twofa = Two-Factor Authentication -account_link = Linked Accounts -organization = Organizations -uid = Uid -u2f = Security Keys - -public_profile = Public Profile -profile_desc = Your email address will be used for notifications and other operations. -password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details. -full_name = Full Name -website = Website -location = Location -update_theme = Update Theme -update_profile = Update Profile -update_profile_success = Your profile has been updated. -change_username = Your username has been changed. -change_username_prompt = Note: username changes also change your account URL. -continue = Continue -cancel = Cancel -language = Language -ui = Theme - -lookup_avatar_by_mail = Look Up Avatar by Email Address -federated_avatar_lookup = Federated Avatar Lookup -enable_custom_avatar = Use Custom Avatar -choose_new_avatar = Choose new avatar -update_avatar = Update Avatar -delete_current_avatar = Delete Current Avatar -uploaded_avatar_not_a_image = The uploaded file is not an image. -update_avatar_success = Your avatar has been updated. - -change_password = Update Password -old_password = Current Password -new_password = New Password -retype_new_password = Re-Type New Password -password_incorrect = The current password is incorrect. -change_password_success = Your password has been updated. Sign in using your new password from now on. -password_change_disabled = Non-local users can not update their password through the Gitea web interface. - -emails = Email Addresses -manage_emails = Manage Email Addresses -manage_themes = Select default theme -manage_openid = Manage OpenID Addresses -email_desc = Your primary email address will be used for notifications and other operations. -theme_desc = This will be your default theme across the site. -primary = Primary -primary_email = Make Primary -delete_email = Remove -email_deletion = Remove Email Address -email_deletion_desc = The email address and related information will be removed from your account. Git commits by this email address will remain unchanged. Continue? -email_deletion_success = The email address has been removed. -theme_update_success = Your theme was updated. -theme_update_error = The selected theme does not exist. -openid_deletion = Remove OpenID Address -openid_deletion_desc = Removing this OpenID address from your account will prevent you from signing in with it. Continue? -openid_deletion_success = The OpenID address has been removed. -add_new_email = Add New Email Address -add_new_openid = Add New OpenID URI -add_email = Add Email Address -add_openid = Add OpenID URI -add_email_confirmation_sent = A confirmation email has been sent to '%s'. Please check your inbox within the next %s to confirm your email address. -add_email_success = The new email address has been added. -add_openid_success = The new OpenID address has been added. -keep_email_private = Hide Email Address -keep_email_private_popup = Your email address will be hidden from other users. -openid_desc = OpenID lets you delegate authentication to an external provider. - -manage_ssh_keys = Manage SSH Keys -manage_gpg_keys = Manage GPG Keys -add_key = Add Key -ssh_desc = These public SSH keys are associated with your account. The corresponding private keys allow full access to your repositories. -gpg_desc = These public GPG keys are associated with your account. Keep your private keys safe as they allow commits to be verified. -ssh_helper = Need help? Have a look at GitHub's guide to create your own SSH keys or solve common problems you may encounter using SSH. -gpg_helper = Need help? Have a look at GitHub's guide about GPG. -add_new_key = Add SSH Key -add_new_gpg_key = Add GPG Key -ssh_key_been_used = This SSH key has already been added to the server. -ssh_key_name_used = An SSH key with same name is already added to your account. -gpg_key_id_used = A public GPG key with same ID already exists. -gpg_no_key_email_found = This GPG key is not usable with any email address associated with your account. -subkeys = Subkeys -key_id = Key ID -key_name = Key Name -key_content = Content -add_key_success = The SSH key '%s' has been added. -add_gpg_key_success = The GPG key '%s' has been added. -delete_key = Remove -ssh_key_deletion = Remove SSH Key -gpg_key_deletion = Remove GPG Key -ssh_key_deletion_desc = Removing an SSH key revokes its access to your account. Continue? -gpg_key_deletion_desc = Removing a GPG key un-verifies commits signed by it. Continue? -ssh_key_deletion_success = The SSH key has been removed. -gpg_key_deletion_success = The GPG key has been removed. -add_on = Added on -valid_until = Valid until -valid_forever = Valid forever -last_used = Last used on -no_activity = No recent activity -can_read_info = Read -can_write_info = Write -key_state_desc = This key has been used in the last 7 days -token_state_desc = This token has been used in the last 7 days -show_openid = Show on profile -hide_openid = Hide from profile -ssh_disabled = SSH Disabled - -manage_social = Manage Associated Social Accounts -social_desc = These social accounts are linked to your Gitea account. Make sure you recognize all of them as they can be used to sign in to your Gitea account. -unbind = Unlink -unbind_success = The social account has been unlinked from your Gitea account. - -manage_access_token = Manage Access Tokens -generate_new_token = Generate New Token -tokens_desc = These tokens grant access to your account using the Gitea API. -new_token_desc = Applications using a token have full access to your account. -token_name = Token Name -generate_token = Generate Token -generate_token_success = Your new token has been generated. Copy it now as it will not be shown again. -delete_token = Delete -access_token_deletion = Delete Access Token -access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. Continue? -delete_token_success = The token has been deleted. Applications using it no longer have access to your account. - -twofa_desc = Two-factor authentication enhances the security of your account. -twofa_is_enrolled = Your account is currently enrolled in two-factor authentication. -twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication. -twofa_disable = Disable Two-Factor Authentication -twofa_scratch_token_regenerate = Regenerate Scratch Token -twofa_scratch_token_regenerated = Your scratch token is now %s. Store it in a safe place. -twofa_enroll = Enroll into Two-Factor Authentication -twofa_disable_note = You can disable two-factor authentication if needed. -twofa_disable_desc = Disabling two-factor authentication will make your account less secure. Continue? -regenerate_scratch_token_desc = If you misplaced your scratch token or have already used it to sign in you can reset it here. -twofa_disabled = Two-factor authentication has been disabled. -scan_this_image = Scan this image with your authentication application: -or_enter_secret = Or enter the secret: %s -then_enter_passcode = And enter the passcode shown in the application: -passcode_invalid = The passcode is incorrect. Try again. -twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once! - -u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the FIDO U2F standard. -u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys. -u2f_register_key = Add Security Key -u2f_nickname = Nickname -u2f_press_button = Press the button on your security key to register it. -u2f_delete_key = Remove Security Key -u2f_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue? - -manage_account_links = Manage Linked Accounts -manage_account_links_desc = These external accounts are linked to your Gitea account. -account_links_not_available = There are currently no external accounts linked to your Gitea account. -remove_account_link = Remove Linked Account -remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue? -remove_account_link_success = The linked account has been removed. - -orgs_none = You are not a member of any organizations. -repos_none = You do not own any repositories - -delete_account = Delete Your Account -delete_prompt = This operation will permanently delete your user account. It CAN NOT be undone. -confirm_delete_account = Confirm Deletion -delete_account_title = Delete User Account -delete_account_desc = Are you sure you want to permanently delete this user account? - -[repo] -owner = Owner -repo_name = Repository Name -repo_name_helper = Good repository names use short, memorable and unique keywords. -visibility = Visibility -visibility_helper = Make Repository Private -visibility_helper_forced = Your site administrator forces new repositories to be private. -visibility_fork_helper = (Changing this will affect all forks.) -clone_helper = Need help cloning? Visit Help. -fork_repo = Fork Repository -fork_from = Fork From -fork_visibility_helper = The visibility of a forked repository cannot be changed. -repo_desc = Description -repo_lang = Language -repo_gitignore_helper = Select .gitignore templates. -license = License -license_helper = Select a license file. -readme = README -readme_helper = Select a README file template. -auto_init = Initialize Repository (Adds .gitignore, License and README) -create_repo = Create Repository -default_branch = Default Branch -mirror_prune = Prune -mirror_prune_desc = Remove obsolete remote-tracking references -mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable automatic sync. -mirror_interval_invalid = The mirror interval is not valid. -mirror_address = Clone From URL -mirror_address_desc = Include any required authorization credentials in the URL. -mirror_last_synced = Last Synchronized -watchers = Watchers -stargazers = Stargazers -forks = Forks -pick_reaction = Pick your reaction -reactions_more = and %d more - -archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. -archive.issue.nocomment = This repo is archived. You cannot comment on issues. -archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. - -form.reach_limit_of_creation = You have already reached your limit of %d repositories. -form.name_reserved = The repository name '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. - -need_auth = Clone Authorization -migrate_type = Migration Type -migrate_type_helper = This repository will be a mirror -migrate_repo = Migrate Repository -migrate.clone_address = Migrate / Clone From URL -migrate.clone_address_desc = The HTTP(S) or Git 'clone' URL of an existing repository -migrate.clone_local_path = or a local server path -migrate.permission_denied = You are not allowed to import local repositories. -migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory." -migrate.failed = Migration failed: %v -migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. - -mirror_from = mirror of -forked_from = forked from -fork_from_self = You cannot fork a repository you own. -fork_guest_user = Sign in to fork this repository. -copy_link = Copy -copy_link_success = Link has been copied -copy_link_error = Use ⌘C or Ctrl-C to copy -copied = Copied OK -unwatch = Unwatch -watch = Watch -unstar = Unstar -star = Star -fork = Fork -download_archive = Download Repository - -no_desc = No Description -quick_guide = Quick Guide -clone_this_repo = Clone this repository -create_new_repo_command = Creating a new repository on the command line -push_exist_repo = Pushing an existing repository from the command line -empty_message = This repository does not contain any content. - -code = Code -code.desc = Access source code, files, commits and branches. -branch = Branch -tree = Tree -filter_branch_and_tag = Filter branch or tag -branches = Branches -tags = Tags -issues = Issues -pulls = Pull Requests -labels = Labels -milestones = Milestones -commits = Commits -commit = Commit -releases = Releases -file_raw = Raw -file_history = History -file_view_raw = View Raw -file_permalink = Permalink -file_too_large = The file is too large to be shown. -video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag. -audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag. -stored_lfs = Stored with Git LFS -commit_graph = Commit Graph - -editor.new_file = New File -editor.upload_file = Upload File -editor.edit_file = Edit File -editor.preview_changes = Preview Changes -editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface. -editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface. -editor.edit_this_file = Edit File -editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file. -editor.fork_before_edit = You must fork this repository to make or propose changes to this file. -editor.delete_this_file = Delete File -editor.must_have_write_access = You must have write access to make or propose changes to this file. -editor.file_delete_success = File '%s' has been deleted. -editor.name_your_file = Name your file… -editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field. -editor.or = or -editor.cancel_lower = Cancel -editor.commit_changes = Commit Changes -editor.add_tmpl = Add '%s/' -editor.add = Add '%s' -editor.update = Update '%s' -editor.delete = Delete '%s' -editor.commit_message_desc = Add an optional extended description… -editor.commit_directly_to_this_branch = Commit directly to the %s branch. -editor.create_new_branch = Create a new branch for this commit and start a pull request. -editor.new_branch_name_desc = New branch name… -editor.cancel = Cancel -editor.filename_cannot_be_empty = The filename cannot be empty. -editor.branch_already_exists = Branch '%s' already exists in this repository. -editor.directory_is_a_file = Directory name '%s' is already used as a filename in this repository. -editor.file_is_a_symlink = '%s' is a symbolic link. Symbolic links cannot be edited in the web editor -editor.filename_is_a_directory = Filename '%s' is already used as a directory name in this repository. -editor.file_editing_no_longer_exists = The file being edited, '%s', no longer exists in this repository. -editor.file_changed_while_editing = The file contents have changed since you started editing. Click here to see them or Commit Changes again to overwrite them. -editor.file_already_exists = A file named '%s' already exists in this repository. -editor.no_changes_to_show = There are no changes to show. -editor.fail_to_update_file = Failed to update/create file '%s' with error: %v -editor.add_subdir = Add a directory… -editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v -editor.upload_files_to_dir = Upload files to '%s' -editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s'. - -commits.desc = Browse source code change history. -commits.commits = Commits -commits.search = Search commits… -commits.find = Search -commits.search_all = All Branches -commits.author = Author -commits.message = Message -commits.date = Date -commits.older = Older -commits.newer = Newer -commits.signed_by = Signed by -commits.gpg_key_id = GPG Key ID - -ext_issues = Ext. Issues -ext_issues.desc = Link to an external issue tracker. - -issues.desc = Organize bug reports, tasks and milestones. -issues.new = New Issue -issues.new.title_empty = Title cannot be empty -issues.new.labels = Labels -issues.new.no_label = No Label -issues.new.clear_labels = Clear labels -issues.new.milestone = Milestone -issues.new.no_milestone = No Milestone -issues.new.clear_milestone = Clear milestone -issues.new.open_milestone = Open Milestones -issues.new.closed_milestone = Closed Milestones -issues.new.assignees = Assignees -issues.new.clear_assignees = Clear assignees -issues.new.no_assignees = No Assignees -issues.no_ref = No Branch/Tag Specified -issues.create = Create Issue -issues.new_label = New Label -issues.new_label_placeholder = Label name -issues.new_label_desc_placeholder = Description -issues.create_label = Create Label -issues.label_templates.title = Load a predefined set of labels -issues.label_templates.info = No labels exist yet. Create a label with 'New Label' or use a predefined label set: -issues.label_templates.helper = Select a label set -issues.label_templates.use = Use Label Set -issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v -issues.add_label_at = added the
%s
label %s -issues.remove_label_at = removed the
%s
label %s -issues.add_milestone_at = `added this to the %s milestone %s` -issues.change_milestone_at = `modified the milestone from %s to %s %s` -issues.remove_milestone_at = `removed this from the %s milestone %s` -issues.deleted_milestone = `(deleted)` -issues.self_assign_at = `self-assigned this %s` -issues.add_assignee_at = `was assigned by %s %s` -issues.remove_assignee_at = `was unassigned by %s %s` -issues.remove_self_assignment = `removed their assignment %s` -issues.change_title_at = `changed title from %s to %s %s` -issues.delete_branch_at = `deleted branch %s %s` -issues.open_tab = %d Open -issues.close_tab = %d Closed -issues.filter_label = Label -issues.filter_label_no_select = All labels -issues.filter_milestone = Milestone -issues.filter_milestone_no_select = All milestones -issues.filter_assignee = Assignee -issues.filter_assginee_no_select = All assignees -issues.filter_type = Type -issues.filter_type.all_issues = All issues -issues.filter_type.assigned_to_you = Assigned to you -issues.filter_type.created_by_you = Created by you -issues.filter_type.mentioning_you = Mentioning you -issues.filter_sort = Sort -issues.filter_sort.latest = Newest -issues.filter_sort.oldest = Oldest -issues.filter_sort.recentupdate = Recently updated -issues.filter_sort.leastupdate = Least recently updated -issues.filter_sort.mostcomment = Most commented -issues.filter_sort.leastcomment = Least commented -issues.filter_sort.moststars = Most stars -issues.filter_sort.feweststars = Fewest stars -issues.filter_sort.mostforks = Most forks -issues.filter_sort.fewestforks = Fewest forks -issues.action_open = Open -issues.action_close = Close -issues.action_label = Label -issues.action_milestone = Milestone -issues.action_milestone_no_select = No milestone -issues.action_assignee = Assignee -issues.action_assignee_no_select = No assignee -issues.opened_by = opened %[1]s by %[3]s -pulls.merged_by = merged %[1]s by %[3]s -issues.closed_by = closed %[1]s by %[3]s -issues.opened_by_fake = opened %[1]s by %[2]s -issues.previous = Previous -issues.next = Next -issues.open_title = Open -issues.closed_title = Closed -issues.num_comments = %d comments -issues.commented_at = `commented %s` -issues.delete_comment_confirm = Are you sure you want to delete this comment? -issues.no_content = There is no content yet. -issues.close_issue = Close -issues.close_comment_issue = Comment and Close -issues.reopen_issue = Reopen -issues.reopen_comment_issue = Comment and Reopen -issues.create_comment = Comment -issues.closed_at = `closed %[2]s` -issues.reopened_at = `reopened %[2]s` -issues.commit_ref_at = `referenced this issue from a commit %[2]s` -issues.poster = Poster -issues.collaborator = Collaborator -issues.owner = Owner -issues.sign_in_require_desc = Sign in to join this conversation. -issues.edit = Edit -issues.cancel = Cancel -issues.save = Save -issues.label_title = Label name -issues.label_description = Label description -issues.label_color = Label color -issues.label_count = %d labels -issues.label_open_issues = %d open issues -issues.label_edit = Edit -issues.label_delete = Delete -issues.label_modify = Edit Label -issues.label_deletion = Delete Label -issues.label_deletion_desc = Deleting a label removes it from all issues. Continue? -issues.label_deletion_success = The label has been deleted. -issues.label.filter_sort.alphabetically = Alphabetically -issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically -issues.label.filter_sort.by_size = Size -issues.label.filter_sort.reverse_by_size = Reverse size -issues.num_participants = %d Participants -issues.attachment.open_tab = `Click to see "%s" in a new tab` -issues.attachment.download = `Click to download "%s"` -issues.subscribe = Subscribe -issues.unsubscribe = Unsubscribe -issues.tracker = Time Tracker -issues.start_tracking_short = Start -issues.start_tracking = Start Time Tracking -issues.start_tracking_history = `started working %s` -issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed -issues.tracking_already_started = `You have already started time tracking on this issue!` -issues.stop_tracking = Stop -issues.stop_tracking_history = `stopped working %s` -issues.add_time = Manually Add Time -issues.add_time_short = Add Time -issues.add_time_cancel = Cancel -issues.add_time_history = `added spent time %s` -issues.add_time_hours = Hours -issues.add_time_minutes = Minutes -issues.add_time_sum_to_small = No time was entered. -issues.cancel_tracking = Cancel -issues.cancel_tracking_history = `cancelled time tracking %s` -issues.time_spent_total = Total Time Spent -issues.time_spent_from_all_authors = `Total Time Spent: %s` -issues.due_date = Due Date -issues.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." -issues.error_modifying_due_date = "Failed to modify the due date." -issues.error_removing_due_date = "Failed to remove the due date." -issues.due_date_form = "yyyy-mm-dd" -issues.due_date_form_add = "Add due date" -issues.due_date_form_edit = "Edit" -issues.due_date_form_remove = "Remove" -issues.due_date_not_writer = "You need repository write access to update an issue's due date." -issues.due_date_not_set = "No due date set." -issues.due_date_added = "added the due date %s %s" -issues.due_date_modified = "modified the due date to %s from %s %s" -issues.due_date_remove = "removed the due date %s %s" -issues.due_date_overdue = "Overdue" -issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'." -issues.dependency.title = Dependencies -issues.dependency.issue_no_dependencies = This issue currently doesn't have any dependencies. -issues.dependency.pr_no_dependencies = This pull request currently doesn't have any dependencies. -issues.dependency.add = Add dependency… -issues.dependency.cancel = Cancel -issues.dependency.remove = Remove -issues.dependency.remove_info = Remove this dependency -issues.dependency.added_dependency = `%[2]s added a new dependency %[3]s` -issues.dependency.removed_dependency = `%[2]s removed a dependency %[3]s` -issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues -issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues -issues.dependency.issue_close_blocks = This issue blocks closing of the following issues -issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues -issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it. -issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it. -issues.dependency.blocks_short = Blocks -issues.dependency.blocked_by_short = Depends on -issues.dependency.remove_header = Remove Dependency -issues.dependency.issue_remove_text = This will remove the dependency from this issue. Continue? -issues.dependency.pr_remove_text = This will remove the dependency from this pull request. Continue? -issues.dependency.setting = Enable Dependencies For Issues and Pull Requests -issues.dependency.add_error_same_issue = You cannot make an issue depend on itself. -issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist. -issues.dependency.add_error_dep_not_exist = Dependency does not exist. -issues.dependency.add_error_dep_exists = Dependency already exists. -issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other. -issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository. -issues.review.self.approval = You cannot approve your own pull request. -issues.review.self.rejection = You cannot request changes on your own pull request. -issues.review.approve = "approved these changes %s" -issues.review.comment = "reviewed %s" -issues.review.content.empty = You need to leave a comment indicating the requested change(s). -issues.review.reject = "requested changes %s" -issues.review.pending = Pending -issues.review.review = Review -issues.review.reviewers = Reviewers -issues.review.show_outdated = Show outdated -issues.review.hide_outdated = Hide outdated - -pulls.desc = Enable merge requests and code reviews. -pulls.new = New Pull Request -pulls.compare_changes = New Pull Request -pulls.compare_changes_desc = Select the branch to merge into and the branch to pull from. -pulls.compare_base = merge into -pulls.compare_compare = pull from -pulls.filter_branch = Filter branch -pulls.no_results = No results found. -pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request. -pulls.has_pull_request = `A pull request between these branches already exists: %[2]s#%[3]d` -pulls.create = Create Pull Request -pulls.title_desc = wants to merge %[1]d commits from %[2]s into %[3]s -pulls.merged_title_desc = merged %[1]d commits from %[2]s into %[3]s %[4]s -pulls.tab_conversation = Conversation -pulls.tab_commits = Commits -pulls.tab_files = Files Changed -pulls.reopen_to_merge = Please reopen this pull request to perform a merge. -pulls.merged = Merged -pulls.has_merged = The pull request has been merged. -pulls.title_wip_desc = `Start the title with %s to prevent the pull request from being merged accidentally.` -pulls.cannot_merge_work_in_progress = This pull request is marked as a work in progress. Remove the %s prefix from the title when it's ready -pulls.data_broken = This pull request is broken due to missing fork information. -pulls.files_conflicted = This pull request has changes conflicting with the target branch. -pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." -pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." -pulls.can_auto_merge_desc = This pull request can be merged automatically. -pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts. -pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. -pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. -pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. -pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. -pulls.merge_pull_request = Merge Pull Request -pulls.rebase_merge_pull_request = Rebase and Merge -pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff) -pulls.squash_merge_pull_request = Squash and Merge -pulls.invalid_merge_option = You cannot use this merge option for this pull request. -pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.` - -milestones.new = New Milestone -milestones.open_tab = %d Open -milestones.close_tab = %d Closed -milestones.closed = Closed %s -milestones.no_due_date = No due date -milestones.open = Open -milestones.close = Close -milestones.new_subheader = Milestones organize issues and track progress. -milestones.completeness = %d%% Completed -milestones.create = Create Milestone -milestones.title = Title -milestones.desc = Description -milestones.due_date = Due Date (optional) -milestones.clear = Clear -milestones.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." -milestones.create_success = The milestone '%s' has been created. -milestones.edit = Edit Milestone -milestones.edit_subheader = Milestones organize issues and track progress. -milestones.cancel = Cancel -milestones.modify = Update Milestone -milestones.edit_success = Milestone '%s' has been updated. -milestones.deletion = Delete Milestone -milestones.deletion_desc = Deleting a milestone removes it from all related issues. Continue? -milestones.deletion_success = The milestone has been deleted. -milestones.filter_sort.closest_due_date = Closest due date -milestones.filter_sort.furthest_due_date = Furthest due date -milestones.filter_sort.least_complete = Least complete -milestones.filter_sort.most_complete = Most complete -milestones.filter_sort.most_issues = Most issues -milestones.filter_sort.least_issues = Least issues - -ext_wiki = Ext. Wiki -ext_wiki.desc = Link to an external wiki. - -wiki = Wiki -wiki.welcome = Welcome to the Wiki. -wiki.welcome_desc = The wiki lets you write and share documentation with collaborators. -wiki.desc = Write and share documentation with collaborators. -wiki.create_first_page = Create the First Page -wiki.page = Page -wiki.filter_page = Filter page -wiki.new_page = Page -wiki.default_commit_message = Write a note about this page update (optional). -wiki.save_page = Save Page -wiki.last_commit_info = %s edited this page %s -wiki.edit_page_button = Edit -wiki.new_page_button = New Page -wiki.delete_page_button = Delete Page -wiki.delete_page_notice_1 = Deleting the wiki page '%s' cannot be undone. Continue? -wiki.page_already_exists = A wiki page with the same name already exists. -wiki.reserved_page = The wiki page name '%s' is reserved. -wiki.pages = Pages -wiki.last_updated = Last updated %s - -activity = Activity -activity.period.filter_label = Period: -activity.period.daily = 1 day -activity.period.halfweekly = 3 days -activity.period.weekly = 1 week -activity.period.monthly = 1 month -activity.overview = Overview -activity.active_prs_count_1 = %d Active Pull Request -activity.active_prs_count_n = %d Active Pull Requests -activity.merged_prs_count_1 = Merged Pull Request -activity.merged_prs_count_n = Merged Pull Requests -activity.opened_prs_count_1 = Proposed Pull Request -activity.opened_prs_count_n = Proposed Pull Requests -activity.title.user_1 = %d user -activity.title.user_n = %d users -activity.title.prs_1 = %d Pull request -activity.title.prs_n = %d Pull requests -activity.title.prs_merged_by = %s merged by %s -activity.title.prs_opened_by = %s proposed by %s -activity.merged_prs_label = Merged -activity.opened_prs_label = Proposed -activity.active_issues_count_1 = %d Active Issue -activity.active_issues_count_n = %d Active Issues -activity.closed_issues_count_1 = Closed Issue -activity.closed_issues_count_n = Closed Issues -activity.title.issues_1 = %d Issue -activity.title.issues_n = %d Issues -activity.title.issues_closed_by = %s closed by %s -activity.title.issues_created_by = %s created by %s -activity.closed_issue_label = Closed -activity.new_issues_count_1 = New Issue -activity.new_issues_count_n = New Issues -activity.new_issue_label = Opened -activity.title.unresolved_conv_1 = %d Unresolved Conversation -activity.title.unresolved_conv_n = %d Unresolved Conversations -activity.unresolved_conv_desc = These recently changed issues and pull requests have not been resolved yet. -activity.unresolved_conv_label = Open -activity.title.releases_1 = %d Release -activity.title.releases_n = %d Releases -activity.title.releases_published_by = %s published by %s -activity.published_release_label = Published - -search = Search -search.search_repo = Search repository -search.results = Search results for "%s" in %s - -settings = Settings -settings.desc = Settings is where you can manage the settings for the repository -settings.options = Repository -settings.collaboration = Collaborators -settings.collaboration.admin = Administrator -settings.collaboration.write = Write -settings.collaboration.read = Read -settings.collaboration.undefined = Undefined -settings.hooks = Webhooks -settings.githooks = Git Hooks -settings.basic_settings = Basic Settings -settings.mirror_settings = Mirror Settings -settings.sync_mirror = Synchronize Now -settings.mirror_sync_in_progress = Mirror synchronization is in progress. Check back in a minute. -settings.site = Website -settings.update_settings = Update Settings -settings.advanced_settings = Advanced Settings -settings.wiki_desc = Enable Repository Wiki -settings.use_internal_wiki = Use Built-In Wiki -settings.use_external_wiki = Use External Wiki -settings.external_wiki_url = External Wiki URL -settings.external_wiki_url_error = The external wiki URL is not a valid URL. -settings.external_wiki_url_desc = Visitors are redirected to the external wiki URL when clicking the wiki tab. -settings.issues_desc = Enable Repository Issue Tracker -settings.use_internal_issue_tracker = Use Built-In Issue Tracker -settings.use_external_issue_tracker = Use External Issue Tracker -settings.external_tracker_url = External Issue Tracker URL -settings.external_tracker_url_error = The external issue tracker URL is not a valid URL. -settings.external_tracker_url_desc = Visitors are redirected to the external issue tracker URL when clicking on the issues tab. -settings.tracker_url_format = External Issue Tracker URL Format -settings.tracker_url_format_error = The external issue tracker URL format is not a valid URL. -settings.tracker_issue_style = External Issue Tracker Number Format -settings.tracker_issue_style.numeric = Numeric -settings.tracker_issue_style.alphanumeric = Alphanumeric -settings.tracker_url_format_desc = Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. -settings.enable_timetracker = Enable Time Tracking -settings.allow_only_contributors_to_track_time = Let Only Contributors Track Time -settings.pulls_desc = Enable Repository Pull Requests -settings.pulls.ignore_whitespace = Ignore Whitespace for Conflicts -settings.pulls.allow_merge_commits = Enable Commit Merging -settings.pulls.allow_rebase_merge = Enable Rebasing to Merge Commits -settings.pulls.allow_rebase_merge_commit = Enable Rebasing with explicit merge commits (--no-ff) -settings.pulls.allow_squash_commits = Enable Squashing to Merge Commits -settings.admin_settings = Administrator Settings -settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) -settings.admin_enable_close_issues_via_commit_in_any_branch = Close an issue via a commit made in a non default branch -settings.danger_zone = Danger Zone -settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name. -settings.convert = Convert to Regular Repository -settings.convert_desc = You can convert this mirror into a regular repository. This cannot be undone. -settings.convert_notices_1 = This operation will convert the mirror into a regular repository and cannot be undone. -settings.convert_confirm = Convert Repository -settings.convert_succeed = The mirror has been converted into a regular repository. -settings.transfer = Transfer Ownership -settings.transfer_desc = Transfer this repository to a user or to an organization for which you have administrator rights. -settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user. -settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own. -settings.transfer_form_title = Enter the repository name as confirmation: -settings.wiki_delete = Delete Wiki Data -settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone. -settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s. -settings.confirm_wiki_delete = Delete Wiki Data -settings.wiki_deletion_success = The repository wiki data has been deleted. -settings.delete = Delete This Repository -settings.delete_desc = Deleting a repository is permanent and cannot be undone. -settings.delete_notices_1 = - This operation CANNOT be undone. -settings.delete_notices_2 = - This operation will permanently delete the %s repository including code, issues, comments, wiki data and collaborator settings. -settings.delete_notices_fork_1 = - Forks of this repository will become independent after deletion. -settings.deletion_success = The repository has been deleted. -settings.update_settings_success = The repository settings have been updated. -settings.transfer_owner = New Owner -settings.make_transfer = Perform Transfer -settings.transfer_succeed = The repository has been transferred. -settings.confirm_delete = Delete Repository -settings.add_collaborator = Add Collaborator -settings.add_collaborator_success = The collaborator has been added. -settings.add_collaborator_inactive_user = Can not add an inactive user as a collaborator. -settings.add_collaborator_duplicate = The collaborator is already added to this repository. -settings.delete_collaborator = Remove -settings.collaborator_deletion = Remove Collaborator -settings.collaborator_deletion_desc = Removing a collaborator will revoke their access to this repository. Continue? -settings.remove_collaborator_success = The collaborator has been removed. -settings.search_user_placeholder = Search user… -settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator. -settings.add_webhook = Add Webhook -settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character. -settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the webhooks guide. -settings.webhook_deletion = Remove Webhook -settings.webhook_deletion_desc = Removing a webhook deletes its settings and delivery history. Continue? -settings.webhook_deletion_success = The webhook has been removed. -settings.webhook.test_delivery = Test Delivery -settings.webhook.test_delivery_desc = Test this webhook with a fake event. -settings.webhook.test_delivery_success = A fake event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history. -settings.webhook.request = Request -settings.webhook.response = Response -settings.webhook.headers = Headers -settings.webhook.payload = Content -settings.webhook.body = Body -settings.githooks_desc = "Git hooks are powered by Git itself. You can edit hook files below to set up custom operations." -settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook. -settings.githook_name = Hook Name -settings.githook_content = Hook Content -settings.update_githook = Update Hook -settings.add_webhook_desc = Gitea will send POST requests with a specified content type to the target URL. Read more in the webhooks guide. -settings.payload_url = Target URL -settings.content_type = POST Content Type -settings.secret = Secret -settings.slack_username = Username -settings.slack_icon_url = Icon URL -settings.discord_username = Username -settings.discord_icon_url = Icon URL -settings.slack_color = Color -settings.event_desc = Trigger On: -settings.event_push_only = Push Events -settings.event_send_everything = All Events -settings.event_choose = Custom Events… -settings.event_create = Create -settings.event_create_desc = Branch or tag created. -settings.event_delete = Delete -settings.event_delete_desc = Branch or tag deleted -settings.event_fork = Fork -settings.event_fork_desc = Repository forked -settings.event_issues = Issues -settings.event_issues_desc = Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned. -settings.event_issue_comment = Issue Comment -settings.event_issue_comment_desc = Issue comment created, edited, or deleted. -settings.event_release = Release -settings.event_release_desc = Release published, updated or deleted in a repository. -settings.event_pull_request = Pull Request -settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized. -settings.event_push = Push -settings.event_push_desc = Git push to a repository. -settings.event_repository = Repository -settings.event_repository_desc = Repository created or deleted. -settings.active = Active -settings.active_helper = Information about triggered events will be sent to this webhook URL. -settings.add_hook_success = The webhook has been added. -settings.update_webhook = Update Webhook -settings.update_hook_success = The webhook has been updated. -settings.delete_webhook = Remove Webhook -settings.recent_deliveries = Recent Deliveries -settings.hook_type = Hook Type -settings.add_slack_hook_desc = Integrate Slack into your repository. -settings.slack_token = Token -settings.slack_domain = Domain -settings.slack_channel = Channel -settings.add_discord_hook_desc = Integrate Discord into your repository. -settings.add_dingtalk_hook_desc = Integrate Dingtalk into your repository. -settings.deploy_keys = Deploy Keys -settings.add_deploy_key = Add Deploy Key -settings.deploy_key_desc = Deploy keys have read-only pull access to the repository. -settings.is_writable = Enable Write Access -settings.is_writable_info = Allow this deploy key to push to the repository. -settings.no_deploy_keys = There are no deploy keys yet. -settings.title = Title -settings.deploy_key_content = Content -settings.key_been_used = A deploy key with identical content is already in use. -settings.key_name_used = A deploy key with the same name already exists. -settings.add_key_success = The deploy key '%s' has been added. -settings.deploy_key_deletion = Remove Deploy Key -settings.deploy_key_deletion_desc = Removing a deploy key will revoke its access to this repository. Continue? -settings.deploy_key_deletion_success = The deploy key has been removed. -settings.branches = Branches -settings.protected_branch = Branch Protection -settings.protected_branch_can_push = Allow push? -settings.protected_branch_can_push_yes = You can push -settings.protected_branch_can_push_no = You can not push -settings.branch_protection = Branch Protection for Branch '%s' -settings.protect_this_branch = Enable Branch Protection -settings.protect_this_branch_desc = Prevent deletion and disable Git force pushing to the branch. -settings.protect_whitelist_committers = Enable Push Whitelist -settings.protect_whitelist_committers_desc = Allow whitelisted users or teams to bypass push restrictions. -settings.protect_whitelist_users = Whitelisted users for pushing: -settings.protect_whitelist_search_users = Search users… -settings.protect_whitelist_teams = Whitelisted teams for pushing: -settings.protect_whitelist_search_teams = Search teams… -settings.protect_merge_whitelist_committers = Enable Merge Whitelist -settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch. -settings.protect_merge_whitelist_users = Whitelisted users for merging: -settings.protect_merge_whitelist_teams = Whitelisted teams for merging: -settings.protect_required_approvals = Required approvals: -settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews of whitelisted users or teams. -settings.protect_approvals_whitelist_users = Whitelisted reviewers: -settings.protect_approvals_whitelist_teams = Whitelisted teams for reviews: -settings.add_protected_branch = Enable protection -settings.delete_protected_branch = Disable protection -settings.update_protect_branch_success = Branch protection for branch '%s' has been updated. -settings.remove_protected_branch_success = Branch protection for branch '%s' has been disabled. -settings.protected_branch_deletion = Disable Branch Protection -settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? -settings.default_branch_desc = Select a default repository branch for pull requests and code commits: -settings.choose_branch = Choose a branch… -settings.no_protected_branch = There are no protected branches. -settings.edit_protected_branch = Edit -settings.protected_branch_required_approvals_min = Required approvals cannot be negative. -settings.archive.button = Archive Repo -settings.archive.header = Archive This Repo -settings.archive.text = Archiving the repo will make it entirely read-only. It is hidden from the dashboard, cannot be committed to and no issues or pull-requests can be created. -settings.archive.success = The repo was successfully archived. -settings.archive.error = An error occured while trying to archive the repo. See the log for more details. -settings.archive.error_ismirror = You cannot archive a mirrored repo. -settings.archive.branchsettings_unavailable = Branch settings are not available if the repo is archived. -settings.unarchive.button = Un-Archive Repo -settings.unarchive.header = Un-Archive This Repo -settings.unarchive.text = Un-Archiving the repo will restore its ability to recieve commits and pushes, as well as new issues and pull-requests. -settings.unarchive.success = The repo was successfully un-archived. -settings.unarchive.error = An error occured while trying to un-archive the repo. See the log for more details. - -diff.browse_source = Browse Source -diff.parent = parent -diff.commit = commit -diff.data_not_available = Diff Content Not Available -diff.show_diff_stats = Show Diff Stats -diff.show_split_view = Split View -diff.show_unified_view = Unified View -diff.whitespace_button = Whitespace -diff.whitespace_show_everything = Show all changes -diff.whitespace_ignore_all_whitespace = Ignore whitespace when comparing lines -diff.whitespace_ignore_amount_changes = Ignore changes in amount of whitespace -diff.whitespace_ignore_at_eol = Ignore changes in whitespace at EOL -diff.stats_desc = %d changed files with %d additions and %d deletions -diff.bin = BIN -diff.view_file = View File -diff.file_suppressed = File diff suppressed because it is too large -diff.too_many_files = Some files were not shown because too many files changed in this diff -diff.comment.placeholder = Leave a comment -diff.comment.markdown_info = Styling with markdown is supported. -diff.comment.add_single_comment = Add single comment -diff.comment.add_review_comment = Add comment -diff.comment.start_review = Start review -diff.comment.reply = Reply -diff.review = Review -diff.review.header = Submit review -diff.review.placeholder = Review comment -diff.review.comment = Comment -diff.review.approve = Approve -diff.review.reject = Request changes - -releases.desc = Track project versions and downloads. -release.releases = Releases -release.new_release = New Release -release.draft = Draft -release.prerelease = Pre-Release -release.stable = Stable -release.edit = edit -release.ahead = %d commits to %s since this release -release.source_code = Source Code -release.new_subheader = Releases organize project versions. -release.edit_subheader = Releases organize project versions. -release.tag_name = Tag name -release.target = Target -release.tag_helper = Choose an existing tag or create a new tag. -release.title = Title -release.content = Content -release.prerelease_desc = Mark as Pre-Release -release.prerelease_helper = Mark this release unsuitable for production use. -release.cancel = Cancel -release.publish = Publish Release -release.save_draft = Save Draft -release.edit_release = Update Release -release.delete_release = Delete Release -release.deletion = Delete Release -release.deletion_desc = Deleting a release removes its Git tag from the repository. Repository contents and history remain unchanged. Continue? -release.deletion_success = The release has been deleted. -release.tag_name_already_exist = A release with this tag name already exists. -release.tag_name_invalid = The tag name is not valid. -release.downloads = Downloads - -branch.name = Branch Name -branch.search = Search branches -branch.already_exists = A branch named '%s' already exists. -branch.delete_head = Delete -branch.delete = Delete Branch '%s' -branch.delete_html = Delete Branch -branch.delete_desc = Deleting a branch is permanent. It CANNOT be undone. Continue? -branch.deletion_success = Branch '%s' has been deleted. -branch.deletion_failed = Failed to delete branch '%s'. -branch.delete_branch_has_new_commits = Branch '%s' cannot be deleted because new commits have been added after merging. -branch.create_branch = Create branch %s -branch.create_from = from '%s' -branch.create_success = Branch '%s' has been created. -branch.branch_already_exists = Branch '%s' already exists in this repository. -branch.branch_name_conflict = Branch name '%s' conflicts with the already existing branch '%s'. -branch.tag_collision = Branch '%s' cannot be created as a tag with same name already exists in the repository. -branch.deleted_by = Deleted by %s -branch.restore_success = Branch '%s' has been restored. -branch.restore_failed = Failed to restore branch '%s'. -branch.protected_deletion_failed = Branch '%s' is protected. It cannot be deleted. - -topic.manage_topics = Manage Topics -topic.done = Done -topic.count_prompt = You can not select more than 25 topics -topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long. - -[org] -org_name_holder = Organization Name -org_full_name_holder = Organization Full Name -org_name_helper = Organization names should be short and memorable. -create_org = Create Organization -repo_updated = Updated -people = People -teams = Teams -lower_members = members -lower_repositories = repositories -create_new_team = New Team -create_team = Create Team -org_desc = Description -team_name = Team Name -team_desc = Description -team_name_helper = Team names should be short and memorable. -team_desc_helper = Describe the purpose or role of the team. -team_permission_desc = Permission -team_unit_desc = Allow Access to Repository Sections - -form.name_reserved = The organization name '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. -form.create_org_not_allowed = You are not allowed to create an organization. - -settings = Settings -settings.options = Organization -settings.full_name = Full Name -settings.website = Website -settings.location = Location -settings.update_settings = Update Settings -settings.update_setting_success = Organization settings have been updated. -settings.change_orgname_prompt = Note: changing the organization name also changes the organization's URL. -settings.update_avatar_success = The organization's avatar has been updated. -settings.delete = Delete Organization -settings.delete_account = Delete This Organization -settings.delete_prompt = The organization will be permanently removed. This CANNOT be undone! -settings.confirm_delete_account = Confirm Deletion -settings.delete_org_title = Delete Organization -settings.delete_org_desc = This organization will be deleted permanently. Continue? -settings.hooks_desc = Add webhooks which will be triggered for all repositories under this organization. - -members.membership_visibility = Membership Visibility: -members.public = Visible -members.public_helper = make hidden -members.private = Hidden -members.private_helper = make visible -members.member_role = Member Role: -members.owner = Owner -members.member = Member -members.remove = Remove -members.leave = Leave -members.invite_desc = Add a new member to %s: -members.invite_now = Invite Now - -teams.join = Join -teams.leave = Leave -teams.read_access = Read Access -teams.read_access_helper = Members can view and clone team repositories. -teams.write_access = Write Access -teams.write_access_helper = Members can read and push to team repositories. -teams.admin_access = Administrator Access -teams.admin_access_helper = Members can pull and push to team repositories and add collaborators to them. -teams.no_desc = This team has no description -teams.settings = Settings -teams.owners_permission_desc = Owners have full access to all repositories and have administrator access to the organization. -teams.members = Team Members -teams.update_settings = Update Settings -teams.delete_team = Delete Team -teams.add_team_member = Add Team Member -teams.delete_team_title = Delete Team -teams.delete_team_desc = Deleting a team revokes repository access from its members. Continue? -teams.delete_team_success = The team has been deleted. -teams.read_permission_desc = This team grants Read access: members can view and clone team repositories. -teams.write_permission_desc = This team grants Write access: members can read from and push to team repositories. -teams.admin_permission_desc = This team grants Admin access: members can read from, push to and add collaborators to team repositories. -teams.repositories = Team Repositories -teams.search_repo_placeholder = Search repository… -teams.add_team_repository = Add Team Repository -teams.remove_repo = Remove -teams.add_nonexistent_repo = "The repository you're trying to add does not exist; please create it first." -teams.add_duplicate_users = User is already a team member. -teams.repos.none = No repositories could be accessed by this team. -teams.members.none = No members on this team. - -[admin] -dashboard = Dashboard -users = User Accounts -organizations = Organizations -repositories = Repositories -authentication = Authentication Sources -config = Configuration -notices = System Notices -monitor = Monitoring -first_page = First -last_page = Last -total = Total: %d - -dashboard.statistic = Summary -dashboard.operations = Maintenance Operations -dashboard.system_status = System Status -dashboard.statistic_info = The Gitea database holds %d users, %d organizations, %d public keys, %d repositories, %d watches, %d stars, %d actions, %d accesses, %d issues, %d comments, %d social accounts, %d follows, %d mirrors, %d releases, %d authentication sources, %d webhooks, %d milestones, %d labels, %d hook tasks, %d teams, %d update tasks, %d attachments. -dashboard.operation_name = Operation Name -dashboard.operation_switch = Switch -dashboard.operation_run = Run -dashboard.clean_unbind_oauth = Clean unbound OAuth connections -dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted. -dashboard.delete_inactivate_accounts = Delete all unactivated accounts -dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted. -dashboard.delete_repo_archives = Delete all repository archives -dashboard.delete_repo_archives_success = All repository archives have been deleted. -dashboard.delete_missing_repos = Delete all repositories missing their Git files -dashboard.delete_missing_repos_success = All repositories missing their Git files have been deleted. -dashboard.git_gc_repos = Garbage collect all repositories -dashboard.git_gc_repos_success = All repositories have finished garbage collection. -dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.) -dashboard.resync_all_sshkeys_success = The public SSH keys controlled by Gitea have been updated. -dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories. -dashboard.resync_all_hooks_success = All pre-receive, update and post-receive repository hooks have been resynchronized. -dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist -dashboard.reinit_missing_repos_success = All missing Git repositories for which records existed have been reinitialized. -dashboard.sync_external_users = Synchronize external user data -dashboard.sync_external_users_started = External user data synchronization has started. -dashboard.git_fsck = Execute health checks on all repositories -dashboard.git_fsck_started = Repository health checks have started. -dashboard.server_uptime = Server Uptime -dashboard.current_goroutine = Current Goroutines -dashboard.current_memory_usage = Current Memory Usage -dashboard.total_memory_allocated = Total Memory Allocated -dashboard.memory_obtained = Memory Obtained -dashboard.pointer_lookup_times = Pointer Lookup Times -dashboard.memory_allocate_times = Memory Allocations -dashboard.memory_free_times = Memory Frees -dashboard.current_heap_usage = Current Heap Usage -dashboard.heap_memory_obtained = Heap Memory Obtained -dashboard.heap_memory_idle = Heap Memory Idle -dashboard.heap_memory_in_use = Heap Memory In Use -dashboard.heap_memory_released = Heap Memory Released -dashboard.heap_objects = Heap Objects -dashboard.bootstrap_stack_usage = Bootstrap Stack Usage -dashboard.stack_memory_obtained = Stack Memory Obtained -dashboard.mspan_structures_usage = MSpan Structures Usage -dashboard.mspan_structures_obtained = MSpan Structures Obtained -dashboard.mcache_structures_usage = MCache Structures Usage -dashboard.mcache_structures_obtained = MCache Structures Obtained -dashboard.profiling_bucket_hash_table_obtained = Profiling Bucket Hash Table Obtained -dashboard.gc_metadata_obtained = GC Metadata Obtained -dashboard.other_system_allocation_obtained = Other System Allocation Obtained -dashboard.next_gc_recycle = Next GC Recycle -dashboard.last_gc_time = Since Last GC Time -dashboard.total_gc_time = Total GC Pause -dashboard.total_gc_pause = Total GC Pause -dashboard.last_gc_pause = Last GC Pause -dashboard.gc_times = GC Times - -users.user_manage_panel = User Account Management -users.new_account = Create User Account -users.name = Username -users.activated = Activated -users.admin = Admin -users.repos = Repos -users.created = Created -users.last_login = Last Sign-In -users.never_login = Never Signed-In -users.send_register_notify = Send User Registration Notification -users.new_success = The user account '%s' has been created. -users.edit = Edit -users.auth_source = Authentication Source -users.local = Local -users.auth_login_name = Authentication Sign-In Name -users.password_helper = Leave the password empty to keep it unchanged. -users.update_profile_success = The user account has been updated. -users.edit_account = Edit User Account -users.max_repo_creation = Maximal Number of Repositories -users.max_repo_creation_desc = (Enter -1 to use the global default limit.) -users.is_activated = User Account Is Activated -users.prohibit_login = Disable Sign-In -users.is_admin = Is Administrator -users.allow_git_hook = May Create Git Hooks -users.allow_import_local = May Import Local Repositories -users.allow_create_organization = May Create Organizations -users.update_profile = Update User Account -users.delete_account = Delete User Account -users.still_own_repo = This user still owns one or more repositories. Delete or transfer these repositories first. -users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. -users.deletion_success = The user account has been deleted. - -orgs.org_manage_panel = Organization Management -orgs.name = Name -orgs.teams = Teams -orgs.members = Members -orgs.new_orga = New Organization - -repos.repo_manage_panel = Repository Management -repos.owner = Owner -repos.name = Name -repos.private = Private -repos.watches = Watches -repos.stars = Stars -repos.forks = Forks -repos.issues = Issues -repos.size = Size - -auths.auth_manage_panel = Authentication Source Management -auths.new = Add Authentication Source -auths.name = Name -auths.type = Type -auths.enabled = Enabled -auths.syncenabled = Enable User Synchronization -auths.updated = Updated -auths.auth_type = Authentication Type -auths.auth_name = Authentication Name -auths.security_protocol = Security Protocol -auths.domain = Domain -auths.host = Host -auths.port = Port -auths.bind_dn = Bind DN -auths.bind_password = Bind Password -auths.bind_password_helper = Warning: This password is stored in plain text. Use a read-only account if possible. -auths.user_base = User Search Base -auths.user_dn = User DN -auths.attribute_username = Username Attribute -auths.attribute_username_placeholder = Leave empty to use the username entered in Gitea. -auths.attribute_name = First Name Attribute -auths.attribute_surname = Surname Attribute -auths.attribute_mail = Email Attribute -auths.attribute_ssh_public_key = Public SSH Key Attribute -auths.attributes_in_bind = Fetch Attributes in Bind DN Context -auths.use_paged_search = Use Paged Search -auths.search_page_size = Page Size -auths.filter = User Filter -auths.admin_filter = Admin Filter -auths.ms_ad_sa = MS AD Search Attributes -auths.smtp_auth = SMTP Authentication Type -auths.smtphost = SMTP Host -auths.smtpport = SMTP Port -auths.allowed_domains = Allowed Domains -auths.allowed_domains_helper = Leave empty to allow all domains. Separate multiple domains with a comma (','). -auths.enable_tls = Enable TLS Encryption -auths.skip_tls_verify = Skip TLS Verify -auths.pam_service_name = PAM Service Name -auths.oauth2_provider = OAuth2 Provider -auths.oauth2_clientID = Client ID (Key) -auths.oauth2_clientSecret = Client Secret -auths.openIdConnectAutoDiscoveryURL = OpenID Connect Auto Discovery URL -auths.oauth2_use_custom_url = Use Custom URLs Instead of Default URLs -auths.oauth2_tokenURL = Token URL -auths.oauth2_authURL = Authorize URL -auths.oauth2_profileURL = Profile URL -auths.oauth2_emailURL = Email URL -auths.enable_auto_register = Enable Auto Registration -auths.tips = Tips -auths.tips.oauth2.general = OAuth2 Authentication -auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be: /user/oauth2//callback -auths.tip.oauth2_provider = OAuth2 Provider -auths.tip.bitbucket = Register a new OAuth consumer on https://bitbucket.org/account/user//oauth-consumers/new and add the permission 'Account' - 'Read' -auths.tip.dropbox = Create a new application at https://www.dropbox.com/developers/apps -auths.tip.facebook = Register a new application at https://developers.facebook.com/apps and add the product "Facebook Login" -auths.tip.github = Register a new OAuth application on https://github.com/settings/applications/new -auths.tip.gitlab = Register a new application on https://gitlab.com/profile/applications -auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console at https://console.developers.google.com/ -auths.tip.openid_connect = Use the OpenID Connect Discovery URL (/.well-known/openid-configuration) to specify the endpoints -auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled -auths.tip.discord = Register a new application on https://discordapp.com/developers/applications/me -auths.edit = Edit Authentication Source -auths.activated = This Authentication Source is Activated -auths.new_success = The authentication '%s' has been added. -auths.update_success = The authentication source has been updated. -auths.update = Update Authentication Source -auths.delete = Delete Authentication Source -auths.delete_auth_title = Delete Authentication Source -auths.delete_auth_desc = Deleting an authentication source prevents users from using it to sign in. Continue? -auths.still_in_used = The authentication source is still in use. Convert or delete any users using this authentication source first. -auths.deletion_success = The authentication source has been deleted. -auths.login_source_exist = The authentication source '%s' already exists. - -config.server_config = Server Configuration -config.app_name = Site Title -config.app_ver = Gitea Version -config.app_url = Gitea Base URL -config.custom_conf = Configuration File Path -config.domain = SSH Server Domain -config.offline_mode = Local Mode -config.disable_router_log = Disable Router Log -config.run_user = Run As Username -config.run_mode = Run Mode -config.git_version = Git Version -config.repo_root_path = Repository Root Path -config.lfs_root_path = LFS Root Path -config.static_file_root_path = Static File Root Path -config.log_file_root_path = Log Path -config.script_type = Script Type -config.reverse_auth_user = Reverse Authentication User - -config.ssh_config = SSH Configuration -config.ssh_enabled = Enabled -config.ssh_start_builtin_server = Use Built-In Server -config.ssh_domain = Server Domain -config.ssh_port = Port -config.ssh_listen_port = Listen Port -config.ssh_root_path = Root Path -config.ssh_key_test_path = Key Test Path -config.ssh_keygen_path = Keygen ('ssh-keygen') Path -config.ssh_minimum_key_size_check = Minimum Key Size Check -config.ssh_minimum_key_sizes = Minimum Key Sizes - -config.db_config = Database Configuration -config.db_type = Type -config.db_host = Host -config.db_name = Name -config.db_user = Username -config.db_ssl_mode = SSL -config.db_path = Path - -config.service_config = Service Configuration -config.register_email_confirm = Require Email Confirmation to Register -config.disable_register = Disable Self-Registration -config.allow_only_external_registration = Allow Registration Only Through External Services -config.enable_openid_signup = Enable OpenID Self-Registration -config.enable_openid_signin = Enable OpenID Sign-In -config.show_registration_button = Show Register Button -config.require_sign_in_view = Require Sign-In to View Pages -config.mail_notify = Enable Email Notifications -config.disable_key_size_check = Disable Minimum Key Size Check -config.enable_captcha = Enable CAPTCHA -config.active_code_lives = Active Code Lives -config.reset_password_code_lives = Reset Password Code Expiry Time -config.default_keep_email_private = Hide Email Addresses by Default -config.default_allow_create_organization = Allow Creation of Organizations by Default -config.enable_timetracking = Enable Time Tracking -config.default_enable_timetracking = Enable Time Tracking by Default -config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time -config.no_reply_address = Hidden Email Domain -config.default_enable_dependencies = Enable Issue Dependencies by Default - -config.webhook_config = Webhook Configuration -config.queue_length = Queue Length -config.deliver_timeout = Deliver Timeout -config.skip_tls_verify = Skip TLS Verification - -config.mailer_config = SMTP Mailer Configuration -config.mailer_enabled = Enabled -config.mailer_disable_helo = Disable HELO -config.mailer_name = Name -config.mailer_host = Host -config.mailer_user = User -config.mailer_use_sendmail = Use Sendmail -config.mailer_sendmail_path = Sendmail Path -config.mailer_sendmail_args = Extra Arguments to Sendmail -config.send_test_mail = Send Testing Email -config.test_mail_failed = Failed to send a testing email to '%s': %v -config.test_mail_sent = A testing email has been sent to '%s'. - -config.oauth_config = OAuth Configuration -config.oauth_enabled = Enabled - -config.cache_config = Cache Configuration -config.cache_adapter = Cache Adapter -config.cache_interval = Cache Interval -config.cache_conn = Cache Connection - -config.session_config = Session Configuration -config.session_provider = Session Provider -config.provider_config = Provider Config -config.cookie_name = Cookie Name -config.enable_set_cookie = Enable Set Cookie -config.gc_interval_time = GC Interval Time -config.session_life_time = Session Life Time -config.https_only = HTTPS Only -config.cookie_life_time = Cookie Life Time - -config.picture_config = Picture and Avatar Configuration -config.picture_service = Picture Service -config.disable_gravatar = Disable Gravatar -config.enable_federated_avatar = Enable Federated Avatars - -config.git_config = Git Configuration -config.git_disable_diff_highlight = Disable Diff Syntax Highlight -config.git_max_diff_lines = Max Diff Lines (for a single file) -config.git_max_diff_line_characters = Max Diff Characters (for a single line) -config.git_max_diff_files = Max Diff Files (to be shown) -config.git_gc_args = GC Arguments -config.git_migrate_timeout = Migration Timeout -config.git_mirror_timeout = Mirror Update Timeout -config.git_clone_timeout = Clone Operation Timeout -config.git_pull_timeout = Pull Operation Timeout -config.git_gc_timeout = GC Operation Timeout - -config.log_config = Log Configuration -config.log_mode = Log Mode - -monitor.cron = Cron Tasks -monitor.name = Name -monitor.schedule = Schedule -monitor.next = Next Time -monitor.previous = Previous Time -monitor.execute_times = Executions -monitor.process = Running Processes -monitor.desc = Description -monitor.start = Start Time -monitor.execute_time = Execution Time - -notices.system_notice_list = System Notices -notices.view_detail_header = View Notice Details -notices.actions = Actions -notices.select_all = Select All -notices.deselect_all = Deselect All -notices.inverse_selection = Inverse Selection -notices.delete_selected = Delete Selected -notices.delete_all = Delete All Notices -notices.type = Type -notices.type_1 = Repository -notices.desc = Description -notices.op = Op. -notices.delete_success = The system notices have been deleted. - -[action] -create_repo = created repository %s -rename_repo = renamed repository from %[1]s to %[3]s -commit_repo = pushed to %[3]s at %[4]s -create_issue = `opened issue %s#%[2]s` -close_issue = `closed issue %s#%[2]s` -reopen_issue = `reopened issue %s#%[2]s` -create_pull_request = `created pull request %s#%[2]s` -close_pull_request = `closed pull request %s#%[2]s` -reopen_pull_request = `reopened pull request %s#%[2]s` -comment_issue = `commented on issue %s#%[2]s` -merge_pull_request = `merged pull request %s#%[2]s` -transfer_repo = transferred repository %s to %s -push_tag = pushed tag %[2]s to %[3]s -delete_tag = deleted tag %[2]s from %[3]s -delete_branch = deleted branch %[2]s from %[3]s -compare_commits = Compare %d commits -mirror_sync_push = synced commits to %[3]s at %[4]s from mirror -mirror_sync_create = synced new reference %[2]s to %[3]s from mirror -mirror_sync_delete = synced and deleted reference %[2]s at %[3]s from mirror - -[tool] -ago = %s ago -from_now = %s from now -now = now -future = future -1s = 1 second -1m = 1 minute -1h = 1 hour -1d = 1 day -1w = 1 week -1mon = 1 month -1y = 1 year -seconds = %d seconds -minutes = %d minutes -hours = %d hours -days = %d days -weeks = %d weeks -months = %d months -years = %d years -raw_seconds = seconds -raw_minutes = minutes - -[dropzone] -default_message = Drop files or click here to upload. -invalid_input_type = You can not upload files of this type. -file_too_big = File size ({{filesize}} MB) exceeds the maximum size of ({{maxFilesize}} MB). -remove_file = Remove file - -[notification] -notifications = Notifications -unread = Unread -read = Read -no_unread = No unread notifications. -no_read = No read notifications. -pin = Pin notification -mark_as_read = Mark as read -mark_as_unread = Mark as unread -mark_all_as_read = Mark all as read - -[gpg] -error.extract_sign = Failed to extract signature -error.generate_hash = Failed to generate hash of commit -error.no_committer_account = No account linked to committer's email address -error.no_gpg_keys_found = "No known key found for this signature in database" -error.not_signed_commit = "Not a signed commit" -error.failed_retrieval_gpg_keys = "Failed to retrieve any key attached to the committer's account" - -[units] -error.no_unit_allowed_repo = You are not allowed to access any section of this repository. -error.unit_not_allowed = You are not allowed to access this repository section. +app_desc = A painless, self-hosted Git service + +home = Home +dashboard = Dashboard +explore = Explore +help = Help +sign_in = Sign In +sign_in_with = Sign In With +sign_out = Sign Out +sign_up = Register +link_account = Link Account +register = Register +website = Website +version = Version +page = Page +template = Template +language = Language +notifications = Notifications +create_new = Create… +user_profile_and_more = Profile and Settings… +signed_in_as = Signed in as +enable_javascript = This website works better with JavaScript. + +username = Username +email = Email Address +password = Password +re_type = Re-Type Password +captcha = CAPTCHA +twofa = Two-Factor Authentication +twofa_scratch = Two-Factor Scratch Code +passcode = Passcode + +u2f_insert_key = Insert your security key +u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it. +u2f_press_button = Please press the button on your security key… +u2f_use_twofa = Use a two-factor code from your phone +u2f_error = Could not read your security key. +u2f_unsupported_browser = Your browser does not support U2F security keys. +u2f_error_1 = An unknown error occurred. Please retry. +u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL. +u2f_error_3 = The server could not process your request. +u2f_error_4 = The security key is not permitted for this request. Please make sure that the key is not already registered. +u2f_error_5 = Timeout reached before your key could be read. Please reload this page and retry. +u2f_reload = Reload + +repository = Repository +organization = Organization +mirror = Mirror +new_repo = New Repository +new_migrate = New Migration +new_mirror = New Mirror +new_fork = New Repository Fork +new_org = New Organization +manage_org = Manage Organizations +admin_panel = Site Administration +account_settings = Account Settings +settings = Settings +your_profile = Profile +your_starred = Starred +your_settings = Settings + +all = All +sources = Sources +mirrors = Mirrors +collaborative = Collaborative +forks = Forks + +activities = Activities +pull_requests = Pull Requests +issues = Issues + +cancel = Cancel + +write = Write +preview = Preview +loading = Loading… + +[install] +install = Installation +title = Initial Configuration +docker_helper = If you run Gitea inside Docker, please read the documentation before changing any settings. +requite_db_desc = Gitea requires MySQL, PostgreSQL, MSSQL or SQLite3. +db_title = Database Settings +db_type = Database Type +host = Host +user = Username +password = Password +db_name = Database Name +db_helper = Note to MySQL users: please use the InnoDB storage engine and the 'utf8_general_ci' character set. +ssl_mode = SSL +path = Path +sqlite_helper = File path for the SQLite3 database.
Enter an absolute path if you run Gitea as a service. +err_empty_db_path = The SQLite3 database path cannot be empty. +no_admin_and_disable_registration = You cannot disable user self-registration without creating an administrator account. +err_empty_admin_password = The administrator password cannot be empty. + +general_title = General Settings +app_name = Site Title +app_name_helper = You can enter your company name here. +repo_path = Repository Root Path +repo_path_helper = Remote Git repositories will be saved to this directory. +lfs_path = Git LFS Root Path +lfs_path_helper = Files tracked by Git LFS will be stored in this directory. Leave empty to disable. +run_user = Run As Username +run_user_helper = Enter the operating system username that Gitea runs as. Note that this user must have access to the repository root path. +domain = SSH Server Domain +domain_helper = Domain or host address for SSH clone URLs. +ssh_port = SSH Server Port +ssh_port_helper = Port number your SSH server listens on. Leave empty to disable. +http_port = Gitea HTTP Listen Port +http_port_helper = Port number the Giteas web server will listen on. +app_url = Gitea Base URL +app_url_helper = Base address for HTTP(S) clone URLs and email notifications. +log_root_path = Log Path +log_root_path_helper = Log files will be written to this directory. + +optional_title = Optional Settings +email_title = Email Settings +smtp_host = SMTP Host +smtp_from = Send Email As +smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" format. +mailer_user = SMTP Username +mailer_password = SMTP Password +register_confirm = Require Email Confirmation to Register +mail_notify = Enable Email Notifications +server_service_title = Server and Third-Party Service Settings +offline_mode = Enable Local Mode +offline_mode_popup = Disable third-party content delivery networks and serve all resources locally. +disable_gravatar = Disable Gravatar +disable_gravatar_popup = Disable Gravatar and third-party avatar sources. A default avatar will be used unless a user locally uploads an avatar. +federated_avatar_lookup = Enable Federated Avatars +federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar. +disable_registration = Disable Self-Registration +disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts. +allow_only_external_registration_popup = Allow Registration Only Through External Services +openid_signin = Enable OpenID Sign-In +openid_signin_popup = Enable user sign-in via OpenID. +openid_signup = Enable OpenID Self-Registration +openid_signup_popup = Enable OpenID-based user self-registration. +enable_captcha = Enable CAPTCHA +enable_captcha_popup = Require a CAPTCHA for user self-registration. +require_sign_in_view = Require Sign-In to View Pages +require_sign_in_view_popup = Limit page access to signed-in users. Visitors will only see the 'sign in' and registration pages. +admin_setting_desc = Creating an administrator account is optional. The first registered user will automatically become an administrator. +admin_title = Administrator Account Settings +admin_name = Administrator Username +admin_password = Password +confirm_password = Confirm Password +admin_email = Email Address +install_btn_confirm = Install Gitea +test_git_failed = Could not test 'git' command: %v +sqlite3_not_available = This Gitea version does not support SQLite3. Please download the official binary version from %s (not the 'gobuild' version). +invalid_db_setting = The database settings are invalid: %v +invalid_repo_path = The repository root path is invalid: %v +run_user_not_match = The 'run as' username is not the current username: %s -> %s +save_config_failed = Failed to save configuration: %v +invalid_admin_setting = Administrator account setting is invalid: %v +install_success = Welcome! Thank you for choosing Gitea. Have fun and take care! +invalid_log_root_path = The log path is invalid: %v +default_keep_email_private = Hide Email Addresses by Default +default_keep_email_private_popup = Hide email addresses of new user accounts by default. +default_allow_create_organization = Allow Creation of Organizations by Default +default_allow_create_organization_popup = Allow new user accounts to create organizations by default. +default_enable_timetracking = Enable Time Tracking by Default +default_enable_timetracking_popup = Enable time tracking for new repositories by default. +no_reply_address = Hidden Email Domain +no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'. + +[home] +uname_holder = Username or Email Address +password_holder = Password +switch_dashboard_context = Switch Dashboard Context +my_repos = Repositories +show_more_repos = Show more repositories… +collaborative_repos = Collaborative Repositories +my_orgs = My Organizations +my_mirrors = My Mirrors +view_home = View %s +search_repos = Find a repository… + +issues.in_your_repos = In your repositories + +[explore] +repos = Repositories +users = Users +organizations = Organizations +search = Search +code = Code +repo_no_results = No matching repositories found. +user_no_results = No matching users found. +org_no_results = No matching organizations found. +code_no_results = No source code matching your search term found. +code_search_results = Search results for '%s' + +[auth] +create_new_account = Register Account +register_helper_msg = Already have an account? Sign in now! +social_register_helper_msg = Already have an account? Link it now! +disable_register_prompt = Registration is disabled. Please contact your site administrator. +disable_register_mail = Email confirmation for registration is disabled. +remember_me = Remember Me +forgot_password_title= Forgot Password +forgot_password = Forgot password? +sign_up_now = Need an account? Register now. +sign_up_successful = Account was successfully created. +confirmation_mail_sent_prompt = A new confirmation email has been sent to %s. Please check your inbox within the next %s to complete the registration process. +must_change_password = Update your password +allow_password_change = Require user to change password (recommended) +reset_password_mail_sent_prompt = A confirmation email has been sent to %s. Please check your inbox within the next %s to complete the password reset process. +active_your_account = Activate Your Account +account_activated = Account has been activated +prohibit_login = Sign In Prohibited +prohibit_login_desc = Your account is prohibited to sign in, please contact your site administrator. +resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again. +has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (%s). If you haven't received a confirmation email or need to resend a new one, please click on the button below. +resend_mail = Click here to resend your activation email +email_not_associate = The email address is not associated with any account. +send_reset_mail = Click here to resend your password reset email +reset_password = Reset Your Password +invalid_code = Your confirmation code is invalid or has expired. +reset_password_helper = Click here to reset your password +password_too_short = Password length cannot be less than %d characters. +non_local_account = Non-local users can not update their password through the Gitea web interface. +verify = Verify +scratch_code = Scratch code +use_scratch_code = Use a scratch code +twofa_scratch_used = You have used your scratch code. You have been redirected to the two-factor settings page so you may remove your device enrollment or generate a new scratch code. +twofa_passcode_incorrect = Your passcode is incorrect. If you misplaced your device, use your scratch code to sign in. +twofa_scratch_token_incorrect = Your scratch code is incorrect. +login_userpass = Sign In +login_openid = OpenID +oauth_signup_tab = Register New Account +oauth_signup_title = Add Email and Password (for Account Recovery) +oauth_signup_submit = Complete Account +oauth_signin_tab = Link to Existing Account +oauth_signin_title = Sign In to Authorize Linked Account +oauth_signin_submit = Link Account +openid_connect_submit = Connect +openid_connect_title = Connect to an existing account +openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here. +openid_register_title = Create new account +openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here. +openid_signin_desc = Enter your OpenID URI. For example: https://anne.me, bob.openid.org.cn or gnusocial.net/carry. +disable_forgot_password_mail = Password reset is disabled. Please contact your site administrator. +email_domain_blacklisted = You cannot register with your email address. + +[mail] +activate_account = Please activate your account +activate_email = Verify your email address +reset_password = Reset your password +register_success = Registration successful +register_notify = Welcome to Gitea + +[modal] +yes = Yes +no = No +modify = Update + +[form] +UserName = Username +RepoName = Repository name +Email = Email address +Password = Password +Retype = Re-Type Password +SSHTitle = SSH key name +HttpsUrl = HTTPS URL +PayloadUrl = Payload URL +TeamName = Team name +AuthName = Authorization name +AdminEmail = Admin email + +NewBranchName = New branch name +CommitSummary = Commit summary +CommitMessage = Commit message +CommitChoice = Commit choice +TreeName = File path +Content = Content + +require_error = ` cannot be empty.` +alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` +alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` +git_ref_name_error = ` must be a well-formed Git reference name.` +size_error = ` must be size %s.` +min_size_error = ` must contain at least %s characters.` +max_size_error = ` must contain at most %s characters.` +email_error = ` is not a valid email address.` +url_error = ` is not a valid URL.` +include_error = ` must contain substring '%s'.` +unknown_error = Unknown error: +captcha_incorrect = The CAPTCHA code is incorrect. +password_not_match = The passwords do not match. + +username_been_taken = The username is already taken. +repo_name_been_taken = The repository name is already used. +org_name_been_taken = The organization name is already taken. +team_name_been_taken = The team name is already taken. +team_no_units_error = Allow access to at least one repository section. +email_been_used = The email address is already used. +openid_been_used = The OpenID address '%s' is already used. +username_password_incorrect = Username or password is incorrect. +enterred_invalid_repo_name = The repository name you entered is incorrect. +enterred_invalid_owner_name = The new owner name is not valid. +enterred_invalid_password = The password you entered is incorrect. +user_not_exist = The user does not exist. +last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team. +cannot_add_org_to_team = An organization cannot be added as a team member. + +invalid_ssh_key = Can not verify your SSH key: %s +invalid_gpg_key = Can not verify your GPG key: %s +unable_verify_ssh_key = "Can not verify the SSH key; double-check it for mistakes." +auth_failed = Authentication failed: %v + +still_own_repo = "Your account owns one or more repositories; delete or transfer them first." +still_has_org = "Your account is a member of one or more organizations; leave them first." +org_still_own_repo = "This organization still owns one or more repositories; delete or transfer them first." + +target_branch_not_exist = Target branch does not exist. + +[user] +change_avatar = Change your avatar… +join_on = Joined on +repositories = Repositories +activity = Public Activity +followers = Followers +starred = Starred Repositories +following = Following +follow = Follow +unfollow = Unfollow +heatmap.loading = Loading Heatmap… + +form.name_reserved = The username '%s' is reserved. +form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. + +[settings] +profile = Profile +account = Account +password = Password +security = Security +avatar = Avatar +ssh_gpg_keys = SSH / GPG Keys +social = Social Accounts +applications = Applications +orgs = Manage Organizations +repos = Repositories +delete = Delete Account +twofa = Two-Factor Authentication +account_link = Linked Accounts +organization = Organizations +uid = Uid +u2f = Security Keys + +public_profile = Public Profile +profile_desc = Your email address will be used for notifications and other operations. +password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details. +full_name = Full Name +website = Website +location = Location +update_theme = Update Theme +update_profile = Update Profile +update_profile_success = Your profile has been updated. +change_username = Your username has been changed. +change_username_prompt = Note: username changes also change your account URL. +continue = Continue +cancel = Cancel +language = Language +ui = Theme + +lookup_avatar_by_mail = Look Up Avatar by Email Address +federated_avatar_lookup = Federated Avatar Lookup +enable_custom_avatar = Use Custom Avatar +choose_new_avatar = Choose new avatar +update_avatar = Update Avatar +delete_current_avatar = Delete Current Avatar +uploaded_avatar_not_a_image = The uploaded file is not an image. +update_avatar_success = Your avatar has been updated. + +change_password = Update Password +old_password = Current Password +new_password = New Password +retype_new_password = Re-Type New Password +password_incorrect = The current password is incorrect. +change_password_success = Your password has been updated. Sign in using your new password from now on. +password_change_disabled = Non-local users can not update their password through the Gitea web interface. + +emails = Email Addresses +manage_emails = Manage Email Addresses +manage_themes = Select default theme +manage_openid = Manage OpenID Addresses +email_desc = Your primary email address will be used for notifications and other operations. +theme_desc = This will be your default theme across the site. +primary = Primary +primary_email = Make Primary +delete_email = Remove +email_deletion = Remove Email Address +email_deletion_desc = The email address and related information will be removed from your account. Git commits by this email address will remain unchanged. Continue? +email_deletion_success = The email address has been removed. +theme_update_success = Your theme was updated. +theme_update_error = The selected theme does not exist. +openid_deletion = Remove OpenID Address +openid_deletion_desc = Removing this OpenID address from your account will prevent you from signing in with it. Continue? +openid_deletion_success = The OpenID address has been removed. +add_new_email = Add New Email Address +add_new_openid = Add New OpenID URI +add_email = Add Email Address +add_openid = Add OpenID URI +add_email_confirmation_sent = A confirmation email has been sent to '%s'. Please check your inbox within the next %s to confirm your email address. +add_email_success = The new email address has been added. +add_openid_success = The new OpenID address has been added. +keep_email_private = Hide Email Address +keep_email_private_popup = Your email address will be hidden from other users. +openid_desc = OpenID lets you delegate authentication to an external provider. + +manage_ssh_keys = Manage SSH Keys +manage_gpg_keys = Manage GPG Keys +add_key = Add Key +ssh_desc = These public SSH keys are associated with your account. The corresponding private keys allow full access to your repositories. +gpg_desc = These public GPG keys are associated with your account. Keep your private keys safe as they allow commits to be verified. +ssh_helper = Need help? Have a look at GitHub's guide to create your own SSH keys or solve common problems you may encounter using SSH. +gpg_helper = Need help? Have a look at GitHub's guide about GPG. +add_new_key = Add SSH Key +add_new_gpg_key = Add GPG Key +ssh_key_been_used = This SSH key has already been added to the server. +ssh_key_name_used = An SSH key with same name is already added to your account. +gpg_key_id_used = A public GPG key with same ID already exists. +gpg_no_key_email_found = This GPG key is not usable with any email address associated with your account. +subkeys = Subkeys +key_id = Key ID +key_name = Key Name +key_content = Content +add_key_success = The SSH key '%s' has been added. +add_gpg_key_success = The GPG key '%s' has been added. +delete_key = Remove +ssh_key_deletion = Remove SSH Key +gpg_key_deletion = Remove GPG Key +ssh_key_deletion_desc = Removing an SSH key revokes its access to your account. Continue? +gpg_key_deletion_desc = Removing a GPG key un-verifies commits signed by it. Continue? +ssh_key_deletion_success = The SSH key has been removed. +gpg_key_deletion_success = The GPG key has been removed. +add_on = Added on +valid_until = Valid until +valid_forever = Valid forever +last_used = Last used on +no_activity = No recent activity +can_read_info = Read +can_write_info = Write +key_state_desc = This key has been used in the last 7 days +token_state_desc = This token has been used in the last 7 days +show_openid = Show on profile +hide_openid = Hide from profile +ssh_disabled = SSH Disabled + +manage_social = Manage Associated Social Accounts +social_desc = These social accounts are linked to your Gitea account. Make sure you recognize all of them as they can be used to sign in to your Gitea account. +unbind = Unlink +unbind_success = The social account has been unlinked from your Gitea account. + +manage_access_token = Manage Access Tokens +generate_new_token = Generate New Token +tokens_desc = These tokens grant access to your account using the Gitea API. +new_token_desc = Applications using a token have full access to your account. +token_name = Token Name +generate_token = Generate Token +generate_token_success = Your new token has been generated. Copy it now as it will not be shown again. +delete_token = Delete +access_token_deletion = Delete Access Token +access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. Continue? +delete_token_success = The token has been deleted. Applications using it no longer have access to your account. + +twofa_desc = Two-factor authentication enhances the security of your account. +twofa_is_enrolled = Your account is currently enrolled in two-factor authentication. +twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication. +twofa_disable = Disable Two-Factor Authentication +twofa_scratch_token_regenerate = Regenerate Scratch Token +twofa_scratch_token_regenerated = Your scratch token is now %s. Store it in a safe place. +twofa_enroll = Enroll into Two-Factor Authentication +twofa_disable_note = You can disable two-factor authentication if needed. +twofa_disable_desc = Disabling two-factor authentication will make your account less secure. Continue? +regenerate_scratch_token_desc = If you misplaced your scratch token or have already used it to sign in you can reset it here. +twofa_disabled = Two-factor authentication has been disabled. +scan_this_image = Scan this image with your authentication application: +or_enter_secret = Or enter the secret: %s +then_enter_passcode = And enter the passcode shown in the application: +passcode_invalid = The passcode is incorrect. Try again. +twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once! + +u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the FIDO U2F standard. +u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys. +u2f_register_key = Add Security Key +u2f_nickname = Nickname +u2f_press_button = Press the button on your security key to register it. +u2f_delete_key = Remove Security Key +u2f_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue? + +manage_account_links = Manage Linked Accounts +manage_account_links_desc = These external accounts are linked to your Gitea account. +account_links_not_available = There are currently no external accounts linked to your Gitea account. +remove_account_link = Remove Linked Account +remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue? +remove_account_link_success = The linked account has been removed. + +orgs_none = You are not a member of any organizations. +repos_none = You do not own any repositories + +delete_account = Delete Your Account +delete_prompt = This operation will permanently delete your user account. It CAN NOT be undone. +confirm_delete_account = Confirm Deletion +delete_account_title = Delete User Account +delete_account_desc = Are you sure you want to permanently delete this user account? + +[repo] +owner = Owner +repo_name = Repository Name +repo_name_helper = Good repository names use short, memorable and unique keywords. +visibility = Visibility +visibility_helper = Make Repository Private +visibility_helper_forced = Your site administrator forces new repositories to be private. +visibility_fork_helper = (Changing this will affect all forks.) +clone_helper = Need help cloning? Visit Help. +fork_repo = Fork Repository +fork_from = Fork From +fork_visibility_helper = The visibility of a forked repository cannot be changed. +repo_desc = Description +repo_lang = Language +repo_gitignore_helper = Select .gitignore templates. +issue_labels = Issue Labels +issue_labels_helper = Select an issue label set. +license = License +license_helper = Select a license file. +readme = README +readme_helper = Select a README file template. +auto_init = Initialize Repository (Adds .gitignore, Issue Labels, License and README) +create_repo = Create Repository +default_branch = Default Branch +mirror_prune = Prune +mirror_prune_desc = Remove obsolete remote-tracking references +mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable automatic sync. +mirror_interval_invalid = The mirror interval is not valid. +mirror_address = Clone From URL +mirror_address_desc = Include any required authorization credentials in the URL. +mirror_last_synced = Last Synchronized +watchers = Watchers +stargazers = Stargazers +forks = Forks +pick_reaction = Pick your reaction +reactions_more = and %d more + +archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. +archive.issue.nocomment = This repo is archived. You cannot comment on issues. +archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. + +form.reach_limit_of_creation = You have already reached your limit of %d repositories. +form.name_reserved = The repository name '%s' is reserved. +form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. + +need_auth = Clone Authorization +migrate_type = Migration Type +migrate_type_helper = This repository will be a mirror +migrate_repo = Migrate Repository +migrate.clone_address = Migrate / Clone From URL +migrate.clone_address_desc = The HTTP(S) or Git 'clone' URL of an existing repository +migrate.clone_local_path = or a local server path +migrate.permission_denied = You are not allowed to import local repositories. +migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory." +migrate.failed = Migration failed: %v +migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. + +mirror_from = mirror of +forked_from = forked from +fork_from_self = You cannot fork a repository you own. +fork_guest_user = Sign in to fork this repository. +copy_link = Copy +copy_link_success = Link has been copied +copy_link_error = Use ⌘C or Ctrl-C to copy +copied = Copied OK +unwatch = Unwatch +watch = Watch +unstar = Unstar +star = Star +fork = Fork +download_archive = Download Repository + +no_desc = No Description +quick_guide = Quick Guide +clone_this_repo = Clone this repository +create_new_repo_command = Creating a new repository on the command line +push_exist_repo = Pushing an existing repository from the command line +empty_message = This repository does not contain any content. + +code = Code +code.desc = Access source code, files, commits and branches. +branch = Branch +tree = Tree +filter_branch_and_tag = Filter branch or tag +branches = Branches +tags = Tags +issues = Issues +pulls = Pull Requests +labels = Labels +milestones = Milestones +commits = Commits +commit = Commit +releases = Releases +file_raw = Raw +file_history = History +file_view_raw = View Raw +file_permalink = Permalink +file_too_large = The file is too large to be shown. +video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag. +audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag. +stored_lfs = Stored with Git LFS +commit_graph = Commit Graph + +editor.new_file = New File +editor.upload_file = Upload File +editor.edit_file = Edit File +editor.preview_changes = Preview Changes +editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface. +editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface. +editor.edit_this_file = Edit File +editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file. +editor.fork_before_edit = You must fork this repository to make or propose changes to this file. +editor.delete_this_file = Delete File +editor.must_have_write_access = You must have write access to make or propose changes to this file. +editor.file_delete_success = File '%s' has been deleted. +editor.name_your_file = Name your file… +editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field. +editor.or = or +editor.cancel_lower = Cancel +editor.commit_changes = Commit Changes +editor.add_tmpl = Add '%s/' +editor.add = Add '%s' +editor.update = Update '%s' +editor.delete = Delete '%s' +editor.commit_message_desc = Add an optional extended description… +editor.commit_directly_to_this_branch = Commit directly to the %s branch. +editor.create_new_branch = Create a new branch for this commit and start a pull request. +editor.new_branch_name_desc = New branch name… +editor.cancel = Cancel +editor.filename_cannot_be_empty = The filename cannot be empty. +editor.branch_already_exists = Branch '%s' already exists in this repository. +editor.directory_is_a_file = Directory name '%s' is already used as a filename in this repository. +editor.file_is_a_symlink = '%s' is a symbolic link. Symbolic links cannot be edited in the web editor +editor.filename_is_a_directory = Filename '%s' is already used as a directory name in this repository. +editor.file_editing_no_longer_exists = The file being edited, '%s', no longer exists in this repository. +editor.file_changed_while_editing = The file contents have changed since you started editing. Click here to see them or Commit Changes again to overwrite them. +editor.file_already_exists = A file named '%s' already exists in this repository. +editor.no_changes_to_show = There are no changes to show. +editor.fail_to_update_file = Failed to update/create file '%s' with error: %v +editor.add_subdir = Add a directory… +editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v +editor.upload_files_to_dir = Upload files to '%s' +editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s'. + +commits.desc = Browse source code change history. +commits.commits = Commits +commits.search = Search commits… +commits.find = Search +commits.search_all = All Branches +commits.author = Author +commits.message = Message +commits.date = Date +commits.older = Older +commits.newer = Newer +commits.signed_by = Signed by +commits.gpg_key_id = GPG Key ID + +ext_issues = Ext. Issues +ext_issues.desc = Link to an external issue tracker. + +issues.desc = Organize bug reports, tasks and milestones. +issues.new = New Issue +issues.new.title_empty = Title cannot be empty +issues.new.labels = Labels +issues.new.no_label = No Label +issues.new.clear_labels = Clear labels +issues.new.milestone = Milestone +issues.new.no_milestone = No Milestone +issues.new.clear_milestone = Clear milestone +issues.new.open_milestone = Open Milestones +issues.new.closed_milestone = Closed Milestones +issues.new.assignees = Assignees +issues.new.clear_assignees = Clear assignees +issues.new.no_assignees = No Assignees +issues.no_ref = No Branch/Tag Specified +issues.create = Create Issue +issues.new_label = New Label +issues.new_label_placeholder = Label name +issues.new_label_desc_placeholder = Description +issues.create_label = Create Label +issues.label_templates.title = Load a predefined set of labels +issues.label_templates.info = No labels exist yet. Create a label with 'New Label' or use a predefined label set: +issues.label_templates.helper = Select a label set +issues.label_templates.use = Use Label Set +issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v +issues.add_label_at = added the
%s
label %s +issues.remove_label_at = removed the
%s
label %s +issues.add_milestone_at = `added this to the %s milestone %s` +issues.change_milestone_at = `modified the milestone from %s to %s %s` +issues.remove_milestone_at = `removed this from the %s milestone %s` +issues.deleted_milestone = `(deleted)` +issues.self_assign_at = `self-assigned this %s` +issues.add_assignee_at = `was assigned by %s %s` +issues.remove_assignee_at = `was unassigned by %s %s` +issues.remove_self_assignment = `removed their assignment %s` +issues.change_title_at = `changed title from %s to %s %s` +issues.delete_branch_at = `deleted branch %s %s` +issues.open_tab = %d Open +issues.close_tab = %d Closed +issues.filter_label = Label +issues.filter_label_no_select = All labels +issues.filter_milestone = Milestone +issues.filter_milestone_no_select = All milestones +issues.filter_assignee = Assignee +issues.filter_assginee_no_select = All assignees +issues.filter_type = Type +issues.filter_type.all_issues = All issues +issues.filter_type.assigned_to_you = Assigned to you +issues.filter_type.created_by_you = Created by you +issues.filter_type.mentioning_you = Mentioning you +issues.filter_sort = Sort +issues.filter_sort.latest = Newest +issues.filter_sort.oldest = Oldest +issues.filter_sort.recentupdate = Recently updated +issues.filter_sort.leastupdate = Least recently updated +issues.filter_sort.mostcomment = Most commented +issues.filter_sort.leastcomment = Least commented +issues.filter_sort.moststars = Most stars +issues.filter_sort.feweststars = Fewest stars +issues.filter_sort.mostforks = Most forks +issues.filter_sort.fewestforks = Fewest forks +issues.action_open = Open +issues.action_close = Close +issues.action_label = Label +issues.action_milestone = Milestone +issues.action_milestone_no_select = No milestone +issues.action_assignee = Assignee +issues.action_assignee_no_select = No assignee +issues.opened_by = opened %[1]s by %[3]s +pulls.merged_by = merged %[1]s by %[3]s +issues.closed_by = closed %[1]s by %[3]s +issues.opened_by_fake = opened %[1]s by %[2]s +issues.previous = Previous +issues.next = Next +issues.open_title = Open +issues.closed_title = Closed +issues.num_comments = %d comments +issues.commented_at = `commented %s` +issues.delete_comment_confirm = Are you sure you want to delete this comment? +issues.no_content = There is no content yet. +issues.close_issue = Close +issues.close_comment_issue = Comment and Close +issues.reopen_issue = Reopen +issues.reopen_comment_issue = Comment and Reopen +issues.create_comment = Comment +issues.closed_at = `closed %[2]s` +issues.reopened_at = `reopened %[2]s` +issues.commit_ref_at = `referenced this issue from a commit %[2]s` +issues.poster = Poster +issues.collaborator = Collaborator +issues.owner = Owner +issues.sign_in_require_desc = Sign in to join this conversation. +issues.edit = Edit +issues.cancel = Cancel +issues.save = Save +issues.label_title = Label name +issues.label_description = Label description +issues.label_color = Label color +issues.label_count = %d labels +issues.label_open_issues = %d open issues +issues.label_edit = Edit +issues.label_delete = Delete +issues.label_modify = Edit Label +issues.label_deletion = Delete Label +issues.label_deletion_desc = Deleting a label removes it from all issues. Continue? +issues.label_deletion_success = The label has been deleted. +issues.label.filter_sort.alphabetically = Alphabetically +issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically +issues.label.filter_sort.by_size = Size +issues.label.filter_sort.reverse_by_size = Reverse size +issues.num_participants = %d Participants +issues.attachment.open_tab = `Click to see "%s" in a new tab` +issues.attachment.download = `Click to download "%s"` +issues.subscribe = Subscribe +issues.unsubscribe = Unsubscribe +issues.tracker = Time Tracker +issues.start_tracking_short = Start +issues.start_tracking = Start Time Tracking +issues.start_tracking_history = `started working %s` +issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed +issues.tracking_already_started = `You have already started time tracking on this issue!` +issues.stop_tracking = Stop +issues.stop_tracking_history = `stopped working %s` +issues.add_time = Manually Add Time +issues.add_time_short = Add Time +issues.add_time_cancel = Cancel +issues.add_time_history = `added spent time %s` +issues.add_time_hours = Hours +issues.add_time_minutes = Minutes +issues.add_time_sum_to_small = No time was entered. +issues.cancel_tracking = Cancel +issues.cancel_tracking_history = `cancelled time tracking %s` +issues.time_spent_total = Total Time Spent +issues.time_spent_from_all_authors = `Total Time Spent: %s` +issues.due_date = Due Date +issues.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." +issues.error_modifying_due_date = "Failed to modify the due date." +issues.error_removing_due_date = "Failed to remove the due date." +issues.due_date_form = "yyyy-mm-dd" +issues.due_date_form_add = "Add due date" +issues.due_date_form_edit = "Edit" +issues.due_date_form_remove = "Remove" +issues.due_date_not_writer = "You need repository write access to update an issue's due date." +issues.due_date_not_set = "No due date set." +issues.due_date_added = "added the due date %s %s" +issues.due_date_modified = "modified the due date to %s from %s %s" +issues.due_date_remove = "removed the due date %s %s" +issues.due_date_overdue = "Overdue" +issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'." +issues.dependency.title = Dependencies +issues.dependency.issue_no_dependencies = This issue currently doesn't have any dependencies. +issues.dependency.pr_no_dependencies = This pull request currently doesn't have any dependencies. +issues.dependency.add = Add dependency… +issues.dependency.cancel = Cancel +issues.dependency.remove = Remove +issues.dependency.remove_info = Remove this dependency +issues.dependency.added_dependency = `%[2]s added a new dependency %[3]s` +issues.dependency.removed_dependency = `%[2]s removed a dependency %[3]s` +issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues +issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues +issues.dependency.issue_close_blocks = This issue blocks closing of the following issues +issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues +issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it. +issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it. +issues.dependency.blocks_short = Blocks +issues.dependency.blocked_by_short = Depends on +issues.dependency.remove_header = Remove Dependency +issues.dependency.issue_remove_text = This will remove the dependency from this issue. Continue? +issues.dependency.pr_remove_text = This will remove the dependency from this pull request. Continue? +issues.dependency.setting = Enable Dependencies For Issues and Pull Requests +issues.dependency.add_error_same_issue = You cannot make an issue depend on itself. +issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist. +issues.dependency.add_error_dep_not_exist = Dependency does not exist. +issues.dependency.add_error_dep_exists = Dependency already exists. +issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other. +issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository. +issues.review.self.approval = You cannot approve your own pull request. +issues.review.self.rejection = You cannot request changes on your own pull request. +issues.review.approve = "approved these changes %s" +issues.review.comment = "reviewed %s" +issues.review.content.empty = You need to leave a comment indicating the requested change(s). +issues.review.reject = "requested changes %s" +issues.review.pending = Pending +issues.review.review = Review +issues.review.reviewers = Reviewers +issues.review.show_outdated = Show outdated +issues.review.hide_outdated = Hide outdated + +pulls.desc = Enable merge requests and code reviews. +pulls.new = New Pull Request +pulls.compare_changes = New Pull Request +pulls.compare_changes_desc = Select the branch to merge into and the branch to pull from. +pulls.compare_base = merge into +pulls.compare_compare = pull from +pulls.filter_branch = Filter branch +pulls.no_results = No results found. +pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request. +pulls.has_pull_request = `A pull request between these branches already exists: %[2]s#%[3]d` +pulls.create = Create Pull Request +pulls.title_desc = wants to merge %[1]d commits from %[2]s into %[3]s +pulls.merged_title_desc = merged %[1]d commits from %[2]s into %[3]s %[4]s +pulls.tab_conversation = Conversation +pulls.tab_commits = Commits +pulls.tab_files = Files Changed +pulls.reopen_to_merge = Please reopen this pull request to perform a merge. +pulls.merged = Merged +pulls.has_merged = The pull request has been merged. +pulls.title_wip_desc = `Start the title with %s to prevent the pull request from being merged accidentally.` +pulls.cannot_merge_work_in_progress = This pull request is marked as a work in progress. Remove the %s prefix from the title when it's ready +pulls.data_broken = This pull request is broken due to missing fork information. +pulls.files_conflicted = This pull request has changes conflicting with the target branch. +pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." +pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." +pulls.can_auto_merge_desc = This pull request can be merged automatically. +pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts. +pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. +pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. +pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. +pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. +pulls.merge_pull_request = Merge Pull Request +pulls.rebase_merge_pull_request = Rebase and Merge +pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff) +pulls.squash_merge_pull_request = Squash and Merge +pulls.invalid_merge_option = You cannot use this merge option for this pull request. +pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.` + +milestones.new = New Milestone +milestones.open_tab = %d Open +milestones.close_tab = %d Closed +milestones.closed = Closed %s +milestones.no_due_date = No due date +milestones.open = Open +milestones.close = Close +milestones.new_subheader = Milestones organize issues and track progress. +milestones.completeness = %d%% Completed +milestones.create = Create Milestone +milestones.title = Title +milestones.desc = Description +milestones.due_date = Due Date (optional) +milestones.clear = Clear +milestones.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." +milestones.create_success = The milestone '%s' has been created. +milestones.edit = Edit Milestone +milestones.edit_subheader = Milestones organize issues and track progress. +milestones.cancel = Cancel +milestones.modify = Update Milestone +milestones.edit_success = Milestone '%s' has been updated. +milestones.deletion = Delete Milestone +milestones.deletion_desc = Deleting a milestone removes it from all related issues. Continue? +milestones.deletion_success = The milestone has been deleted. +milestones.filter_sort.closest_due_date = Closest due date +milestones.filter_sort.furthest_due_date = Furthest due date +milestones.filter_sort.least_complete = Least complete +milestones.filter_sort.most_complete = Most complete +milestones.filter_sort.most_issues = Most issues +milestones.filter_sort.least_issues = Least issues + +ext_wiki = Ext. Wiki +ext_wiki.desc = Link to an external wiki. + +wiki = Wiki +wiki.welcome = Welcome to the Wiki. +wiki.welcome_desc = The wiki lets you write and share documentation with collaborators. +wiki.desc = Write and share documentation with collaborators. +wiki.create_first_page = Create the First Page +wiki.page = Page +wiki.filter_page = Filter page +wiki.new_page = Page +wiki.default_commit_message = Write a note about this page update (optional). +wiki.save_page = Save Page +wiki.last_commit_info = %s edited this page %s +wiki.edit_page_button = Edit +wiki.new_page_button = New Page +wiki.delete_page_button = Delete Page +wiki.delete_page_notice_1 = Deleting the wiki page '%s' cannot be undone. Continue? +wiki.page_already_exists = A wiki page with the same name already exists. +wiki.reserved_page = The wiki page name '%s' is reserved. +wiki.pages = Pages +wiki.last_updated = Last updated %s + +activity = Activity +activity.period.filter_label = Period: +activity.period.daily = 1 day +activity.period.halfweekly = 3 days +activity.period.weekly = 1 week +activity.period.monthly = 1 month +activity.overview = Overview +activity.active_prs_count_1 = %d Active Pull Request +activity.active_prs_count_n = %d Active Pull Requests +activity.merged_prs_count_1 = Merged Pull Request +activity.merged_prs_count_n = Merged Pull Requests +activity.opened_prs_count_1 = Proposed Pull Request +activity.opened_prs_count_n = Proposed Pull Requests +activity.title.user_1 = %d user +activity.title.user_n = %d users +activity.title.prs_1 = %d Pull request +activity.title.prs_n = %d Pull requests +activity.title.prs_merged_by = %s merged by %s +activity.title.prs_opened_by = %s proposed by %s +activity.merged_prs_label = Merged +activity.opened_prs_label = Proposed +activity.active_issues_count_1 = %d Active Issue +activity.active_issues_count_n = %d Active Issues +activity.closed_issues_count_1 = Closed Issue +activity.closed_issues_count_n = Closed Issues +activity.title.issues_1 = %d Issue +activity.title.issues_n = %d Issues +activity.title.issues_closed_by = %s closed by %s +activity.title.issues_created_by = %s created by %s +activity.closed_issue_label = Closed +activity.new_issues_count_1 = New Issue +activity.new_issues_count_n = New Issues +activity.new_issue_label = Opened +activity.title.unresolved_conv_1 = %d Unresolved Conversation +activity.title.unresolved_conv_n = %d Unresolved Conversations +activity.unresolved_conv_desc = These recently changed issues and pull requests have not been resolved yet. +activity.unresolved_conv_label = Open +activity.title.releases_1 = %d Release +activity.title.releases_n = %d Releases +activity.title.releases_published_by = %s published by %s +activity.published_release_label = Published + +search = Search +search.search_repo = Search repository +search.results = Search results for "%s" in %s + +settings = Settings +settings.desc = Settings is where you can manage the settings for the repository +settings.options = Repository +settings.collaboration = Collaborators +settings.collaboration.admin = Administrator +settings.collaboration.write = Write +settings.collaboration.read = Read +settings.collaboration.undefined = Undefined +settings.hooks = Webhooks +settings.githooks = Git Hooks +settings.basic_settings = Basic Settings +settings.mirror_settings = Mirror Settings +settings.sync_mirror = Synchronize Now +settings.mirror_sync_in_progress = Mirror synchronization is in progress. Check back in a minute. +settings.site = Website +settings.update_settings = Update Settings +settings.advanced_settings = Advanced Settings +settings.wiki_desc = Enable Repository Wiki +settings.use_internal_wiki = Use Built-In Wiki +settings.use_external_wiki = Use External Wiki +settings.external_wiki_url = External Wiki URL +settings.external_wiki_url_error = The external wiki URL is not a valid URL. +settings.external_wiki_url_desc = Visitors are redirected to the external wiki URL when clicking the wiki tab. +settings.issues_desc = Enable Repository Issue Tracker +settings.use_internal_issue_tracker = Use Built-In Issue Tracker +settings.use_external_issue_tracker = Use External Issue Tracker +settings.external_tracker_url = External Issue Tracker URL +settings.external_tracker_url_error = The external issue tracker URL is not a valid URL. +settings.external_tracker_url_desc = Visitors are redirected to the external issue tracker URL when clicking on the issues tab. +settings.tracker_url_format = External Issue Tracker URL Format +settings.tracker_url_format_error = The external issue tracker URL format is not a valid URL. +settings.tracker_issue_style = External Issue Tracker Number Format +settings.tracker_issue_style.numeric = Numeric +settings.tracker_issue_style.alphanumeric = Alphanumeric +settings.tracker_url_format_desc = Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. +settings.enable_timetracker = Enable Time Tracking +settings.allow_only_contributors_to_track_time = Let Only Contributors Track Time +settings.pulls_desc = Enable Repository Pull Requests +settings.pulls.ignore_whitespace = Ignore Whitespace for Conflicts +settings.pulls.allow_merge_commits = Enable Commit Merging +settings.pulls.allow_rebase_merge = Enable Rebasing to Merge Commits +settings.pulls.allow_rebase_merge_commit = Enable Rebasing with explicit merge commits (--no-ff) +settings.pulls.allow_squash_commits = Enable Squashing to Merge Commits +settings.admin_settings = Administrator Settings +settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) +settings.admin_enable_close_issues_via_commit_in_any_branch = Close an issue via a commit made in a non default branch +settings.danger_zone = Danger Zone +settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name. +settings.convert = Convert to Regular Repository +settings.convert_desc = You can convert this mirror into a regular repository. This cannot be undone. +settings.convert_notices_1 = This operation will convert the mirror into a regular repository and cannot be undone. +settings.convert_confirm = Convert Repository +settings.convert_succeed = The mirror has been converted into a regular repository. +settings.transfer = Transfer Ownership +settings.transfer_desc = Transfer this repository to a user or to an organization for which you have administrator rights. +settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user. +settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own. +settings.transfer_form_title = Enter the repository name as confirmation: +settings.wiki_delete = Delete Wiki Data +settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone. +settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s. +settings.confirm_wiki_delete = Delete Wiki Data +settings.wiki_deletion_success = The repository wiki data has been deleted. +settings.delete = Delete This Repository +settings.delete_desc = Deleting a repository is permanent and cannot be undone. +settings.delete_notices_1 = - This operation CANNOT be undone. +settings.delete_notices_2 = - This operation will permanently delete the %s repository including code, issues, comments, wiki data and collaborator settings. +settings.delete_notices_fork_1 = - Forks of this repository will become independent after deletion. +settings.deletion_success = The repository has been deleted. +settings.update_settings_success = The repository settings have been updated. +settings.transfer_owner = New Owner +settings.make_transfer = Perform Transfer +settings.transfer_succeed = The repository has been transferred. +settings.confirm_delete = Delete Repository +settings.add_collaborator = Add Collaborator +settings.add_collaborator_success = The collaborator has been added. +settings.add_collaborator_inactive_user = Can not add an inactive user as a collaborator. +settings.add_collaborator_duplicate = The collaborator is already added to this repository. +settings.delete_collaborator = Remove +settings.collaborator_deletion = Remove Collaborator +settings.collaborator_deletion_desc = Removing a collaborator will revoke their access to this repository. Continue? +settings.remove_collaborator_success = The collaborator has been removed. +settings.search_user_placeholder = Search user… +settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator. +settings.add_webhook = Add Webhook +settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character. +settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the webhooks guide. +settings.webhook_deletion = Remove Webhook +settings.webhook_deletion_desc = Removing a webhook deletes its settings and delivery history. Continue? +settings.webhook_deletion_success = The webhook has been removed. +settings.webhook.test_delivery = Test Delivery +settings.webhook.test_delivery_desc = Test this webhook with a fake event. +settings.webhook.test_delivery_success = A fake event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history. +settings.webhook.request = Request +settings.webhook.response = Response +settings.webhook.headers = Headers +settings.webhook.payload = Content +settings.webhook.body = Body +settings.githooks_desc = "Git hooks are powered by Git itself. You can edit hook files below to set up custom operations." +settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook. +settings.githook_name = Hook Name +settings.githook_content = Hook Content +settings.update_githook = Update Hook +settings.add_webhook_desc = Gitea will send POST requests with a specified content type to the target URL. Read more in the webhooks guide. +settings.payload_url = Target URL +settings.content_type = POST Content Type +settings.secret = Secret +settings.slack_username = Username +settings.slack_icon_url = Icon URL +settings.discord_username = Username +settings.discord_icon_url = Icon URL +settings.slack_color = Color +settings.event_desc = Trigger On: +settings.event_push_only = Push Events +settings.event_send_everything = All Events +settings.event_choose = Custom Events… +settings.event_create = Create +settings.event_create_desc = Branch or tag created. +settings.event_delete = Delete +settings.event_delete_desc = Branch or tag deleted +settings.event_fork = Fork +settings.event_fork_desc = Repository forked +settings.event_issues = Issues +settings.event_issues_desc = Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned. +settings.event_issue_comment = Issue Comment +settings.event_issue_comment_desc = Issue comment created, edited, or deleted. +settings.event_release = Release +settings.event_release_desc = Release published, updated or deleted in a repository. +settings.event_pull_request = Pull Request +settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized. +settings.event_push = Push +settings.event_push_desc = Git push to a repository. +settings.event_repository = Repository +settings.event_repository_desc = Repository created or deleted. +settings.active = Active +settings.active_helper = Information about triggered events will be sent to this webhook URL. +settings.add_hook_success = The webhook has been added. +settings.update_webhook = Update Webhook +settings.update_hook_success = The webhook has been updated. +settings.delete_webhook = Remove Webhook +settings.recent_deliveries = Recent Deliveries +settings.hook_type = Hook Type +settings.add_slack_hook_desc = Integrate Slack into your repository. +settings.slack_token = Token +settings.slack_domain = Domain +settings.slack_channel = Channel +settings.add_discord_hook_desc = Integrate Discord into your repository. +settings.add_dingtalk_hook_desc = Integrate Dingtalk into your repository. +settings.deploy_keys = Deploy Keys +settings.add_deploy_key = Add Deploy Key +settings.deploy_key_desc = Deploy keys have read-only pull access to the repository. +settings.is_writable = Enable Write Access +settings.is_writable_info = Allow this deploy key to push to the repository. +settings.no_deploy_keys = There are no deploy keys yet. +settings.title = Title +settings.deploy_key_content = Content +settings.key_been_used = A deploy key with identical content is already in use. +settings.key_name_used = A deploy key with the same name already exists. +settings.add_key_success = The deploy key '%s' has been added. +settings.deploy_key_deletion = Remove Deploy Key +settings.deploy_key_deletion_desc = Removing a deploy key will revoke its access to this repository. Continue? +settings.deploy_key_deletion_success = The deploy key has been removed. +settings.branches = Branches +settings.protected_branch = Branch Protection +settings.protected_branch_can_push = Allow push? +settings.protected_branch_can_push_yes = You can push +settings.protected_branch_can_push_no = You can not push +settings.branch_protection = Branch Protection for Branch '%s' +settings.protect_this_branch = Enable Branch Protection +settings.protect_this_branch_desc = Prevent deletion and disable Git force pushing to the branch. +settings.protect_whitelist_committers = Enable Push Whitelist +settings.protect_whitelist_committers_desc = Allow whitelisted users or teams to bypass push restrictions. +settings.protect_whitelist_users = Whitelisted users for pushing: +settings.protect_whitelist_search_users = Search users… +settings.protect_whitelist_teams = Whitelisted teams for pushing: +settings.protect_whitelist_search_teams = Search teams… +settings.protect_merge_whitelist_committers = Enable Merge Whitelist +settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch. +settings.protect_merge_whitelist_users = Whitelisted users for merging: +settings.protect_merge_whitelist_teams = Whitelisted teams for merging: +settings.protect_required_approvals = Required approvals: +settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews of whitelisted users or teams. +settings.protect_approvals_whitelist_users = Whitelisted reviewers: +settings.protect_approvals_whitelist_teams = Whitelisted teams for reviews: +settings.add_protected_branch = Enable protection +settings.delete_protected_branch = Disable protection +settings.update_protect_branch_success = Branch protection for branch '%s' has been updated. +settings.remove_protected_branch_success = Branch protection for branch '%s' has been disabled. +settings.protected_branch_deletion = Disable Branch Protection +settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? +settings.default_branch_desc = Select a default repository branch for pull requests and code commits: +settings.choose_branch = Choose a branch… +settings.no_protected_branch = There are no protected branches. +settings.edit_protected_branch = Edit +settings.protected_branch_required_approvals_min = Required approvals cannot be negative. +settings.archive.button = Archive Repo +settings.archive.header = Archive This Repo +settings.archive.text = Archiving the repo will make it entirely read-only. It is hidden from the dashboard, cannot be committed to and no issues or pull-requests can be created. +settings.archive.success = The repo was successfully archived. +settings.archive.error = An error occured while trying to archive the repo. See the log for more details. +settings.archive.error_ismirror = You cannot archive a mirrored repo. +settings.archive.branchsettings_unavailable = Branch settings are not available if the repo is archived. +settings.unarchive.button = Un-Archive Repo +settings.unarchive.header = Un-Archive This Repo +settings.unarchive.text = Un-Archiving the repo will restore its ability to recieve commits and pushes, as well as new issues and pull-requests. +settings.unarchive.success = The repo was successfully un-archived. +settings.unarchive.error = An error occured while trying to un-archive the repo. See the log for more details. + +diff.browse_source = Browse Source +diff.parent = parent +diff.commit = commit +diff.data_not_available = Diff Content Not Available +diff.show_diff_stats = Show Diff Stats +diff.show_split_view = Split View +diff.show_unified_view = Unified View +diff.whitespace_button = Whitespace +diff.whitespace_show_everything = Show all changes +diff.whitespace_ignore_all_whitespace = Ignore whitespace when comparing lines +diff.whitespace_ignore_amount_changes = Ignore changes in amount of whitespace +diff.whitespace_ignore_at_eol = Ignore changes in whitespace at EOL +diff.stats_desc = %d changed files with %d additions and %d deletions +diff.bin = BIN +diff.view_file = View File +diff.file_suppressed = File diff suppressed because it is too large +diff.too_many_files = Some files were not shown because too many files changed in this diff +diff.comment.placeholder = Leave a comment +diff.comment.markdown_info = Styling with markdown is supported. +diff.comment.add_single_comment = Add single comment +diff.comment.add_review_comment = Add comment +diff.comment.start_review = Start review +diff.comment.reply = Reply +diff.review = Review +diff.review.header = Submit review +diff.review.placeholder = Review comment +diff.review.comment = Comment +diff.review.approve = Approve +diff.review.reject = Request changes + +releases.desc = Track project versions and downloads. +release.releases = Releases +release.new_release = New Release +release.draft = Draft +release.prerelease = Pre-Release +release.stable = Stable +release.edit = edit +release.ahead = %d commits to %s since this release +release.source_code = Source Code +release.new_subheader = Releases organize project versions. +release.edit_subheader = Releases organize project versions. +release.tag_name = Tag name +release.target = Target +release.tag_helper = Choose an existing tag or create a new tag. +release.title = Title +release.content = Content +release.prerelease_desc = Mark as Pre-Release +release.prerelease_helper = Mark this release unsuitable for production use. +release.cancel = Cancel +release.publish = Publish Release +release.save_draft = Save Draft +release.edit_release = Update Release +release.delete_release = Delete Release +release.deletion = Delete Release +release.deletion_desc = Deleting a release removes its Git tag from the repository. Repository contents and history remain unchanged. Continue? +release.deletion_success = The release has been deleted. +release.tag_name_already_exist = A release with this tag name already exists. +release.tag_name_invalid = The tag name is not valid. +release.downloads = Downloads + +branch.name = Branch Name +branch.search = Search branches +branch.already_exists = A branch named '%s' already exists. +branch.delete_head = Delete +branch.delete = Delete Branch '%s' +branch.delete_html = Delete Branch +branch.delete_desc = Deleting a branch is permanent. It CANNOT be undone. Continue? +branch.deletion_success = Branch '%s' has been deleted. +branch.deletion_failed = Failed to delete branch '%s'. +branch.delete_branch_has_new_commits = Branch '%s' cannot be deleted because new commits have been added after merging. +branch.create_branch = Create branch %s +branch.create_from = from '%s' +branch.create_success = Branch '%s' has been created. +branch.branch_already_exists = Branch '%s' already exists in this repository. +branch.branch_name_conflict = Branch name '%s' conflicts with the already existing branch '%s'. +branch.tag_collision = Branch '%s' cannot be created as a tag with same name already exists in the repository. +branch.deleted_by = Deleted by %s +branch.restore_success = Branch '%s' has been restored. +branch.restore_failed = Failed to restore branch '%s'. +branch.protected_deletion_failed = Branch '%s' is protected. It cannot be deleted. + +topic.manage_topics = Manage Topics +topic.done = Done +topic.count_prompt = You can not select more than 25 topics +topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long. + +[org] +org_name_holder = Organization Name +org_full_name_holder = Organization Full Name +org_name_helper = Organization names should be short and memorable. +create_org = Create Organization +repo_updated = Updated +people = People +teams = Teams +lower_members = members +lower_repositories = repositories +create_new_team = New Team +create_team = Create Team +org_desc = Description +team_name = Team Name +team_desc = Description +team_name_helper = Team names should be short and memorable. +team_desc_helper = Describe the purpose or role of the team. +team_permission_desc = Permission +team_unit_desc = Allow Access to Repository Sections + +form.name_reserved = The organization name '%s' is reserved. +form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. +form.create_org_not_allowed = You are not allowed to create an organization. + +settings = Settings +settings.options = Organization +settings.full_name = Full Name +settings.website = Website +settings.location = Location +settings.update_settings = Update Settings +settings.update_setting_success = Organization settings have been updated. +settings.change_orgname_prompt = Note: changing the organization name also changes the organization's URL. +settings.update_avatar_success = The organization's avatar has been updated. +settings.delete = Delete Organization +settings.delete_account = Delete This Organization +settings.delete_prompt = The organization will be permanently removed. This CANNOT be undone! +settings.confirm_delete_account = Confirm Deletion +settings.delete_org_title = Delete Organization +settings.delete_org_desc = This organization will be deleted permanently. Continue? +settings.hooks_desc = Add webhooks which will be triggered for all repositories under this organization. + +members.membership_visibility = Membership Visibility: +members.public = Visible +members.public_helper = make hidden +members.private = Hidden +members.private_helper = make visible +members.member_role = Member Role: +members.owner = Owner +members.member = Member +members.remove = Remove +members.leave = Leave +members.invite_desc = Add a new member to %s: +members.invite_now = Invite Now + +teams.join = Join +teams.leave = Leave +teams.read_access = Read Access +teams.read_access_helper = Members can view and clone team repositories. +teams.write_access = Write Access +teams.write_access_helper = Members can read and push to team repositories. +teams.admin_access = Administrator Access +teams.admin_access_helper = Members can pull and push to team repositories and add collaborators to them. +teams.no_desc = This team has no description +teams.settings = Settings +teams.owners_permission_desc = Owners have full access to all repositories and have administrator access to the organization. +teams.members = Team Members +teams.update_settings = Update Settings +teams.delete_team = Delete Team +teams.add_team_member = Add Team Member +teams.delete_team_title = Delete Team +teams.delete_team_desc = Deleting a team revokes repository access from its members. Continue? +teams.delete_team_success = The team has been deleted. +teams.read_permission_desc = This team grants Read access: members can view and clone team repositories. +teams.write_permission_desc = This team grants Write access: members can read from and push to team repositories. +teams.admin_permission_desc = This team grants Admin access: members can read from, push to and add collaborators to team repositories. +teams.repositories = Team Repositories +teams.search_repo_placeholder = Search repository… +teams.add_team_repository = Add Team Repository +teams.remove_repo = Remove +teams.add_nonexistent_repo = "The repository you're trying to add does not exist; please create it first." +teams.add_duplicate_users = User is already a team member. +teams.repos.none = No repositories could be accessed by this team. +teams.members.none = No members on this team. + +[admin] +dashboard = Dashboard +users = User Accounts +organizations = Organizations +repositories = Repositories +authentication = Authentication Sources +config = Configuration +notices = System Notices +monitor = Monitoring +first_page = First +last_page = Last +total = Total: %d + +dashboard.statistic = Summary +dashboard.operations = Maintenance Operations +dashboard.system_status = System Status +dashboard.statistic_info = The Gitea database holds %d users, %d organizations, %d public keys, %d repositories, %d watches, %d stars, %d actions, %d accesses, %d issues, %d comments, %d social accounts, %d follows, %d mirrors, %d releases, %d authentication sources, %d webhooks, %d milestones, %d labels, %d hook tasks, %d teams, %d update tasks, %d attachments. +dashboard.operation_name = Operation Name +dashboard.operation_switch = Switch +dashboard.operation_run = Run +dashboard.clean_unbind_oauth = Clean unbound OAuth connections +dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted. +dashboard.delete_inactivate_accounts = Delete all unactivated accounts +dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted. +dashboard.delete_repo_archives = Delete all repository archives +dashboard.delete_repo_archives_success = All repository archives have been deleted. +dashboard.delete_missing_repos = Delete all repositories missing their Git files +dashboard.delete_missing_repos_success = All repositories missing their Git files have been deleted. +dashboard.git_gc_repos = Garbage collect all repositories +dashboard.git_gc_repos_success = All repositories have finished garbage collection. +dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.) +dashboard.resync_all_sshkeys_success = The public SSH keys controlled by Gitea have been updated. +dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories. +dashboard.resync_all_hooks_success = All pre-receive, update and post-receive repository hooks have been resynchronized. +dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist +dashboard.reinit_missing_repos_success = All missing Git repositories for which records existed have been reinitialized. +dashboard.sync_external_users = Synchronize external user data +dashboard.sync_external_users_started = External user data synchronization has started. +dashboard.git_fsck = Execute health checks on all repositories +dashboard.git_fsck_started = Repository health checks have started. +dashboard.server_uptime = Server Uptime +dashboard.current_goroutine = Current Goroutines +dashboard.current_memory_usage = Current Memory Usage +dashboard.total_memory_allocated = Total Memory Allocated +dashboard.memory_obtained = Memory Obtained +dashboard.pointer_lookup_times = Pointer Lookup Times +dashboard.memory_allocate_times = Memory Allocations +dashboard.memory_free_times = Memory Frees +dashboard.current_heap_usage = Current Heap Usage +dashboard.heap_memory_obtained = Heap Memory Obtained +dashboard.heap_memory_idle = Heap Memory Idle +dashboard.heap_memory_in_use = Heap Memory In Use +dashboard.heap_memory_released = Heap Memory Released +dashboard.heap_objects = Heap Objects +dashboard.bootstrap_stack_usage = Bootstrap Stack Usage +dashboard.stack_memory_obtained = Stack Memory Obtained +dashboard.mspan_structures_usage = MSpan Structures Usage +dashboard.mspan_structures_obtained = MSpan Structures Obtained +dashboard.mcache_structures_usage = MCache Structures Usage +dashboard.mcache_structures_obtained = MCache Structures Obtained +dashboard.profiling_bucket_hash_table_obtained = Profiling Bucket Hash Table Obtained +dashboard.gc_metadata_obtained = GC Metadata Obtained +dashboard.other_system_allocation_obtained = Other System Allocation Obtained +dashboard.next_gc_recycle = Next GC Recycle +dashboard.last_gc_time = Since Last GC Time +dashboard.total_gc_time = Total GC Pause +dashboard.total_gc_pause = Total GC Pause +dashboard.last_gc_pause = Last GC Pause +dashboard.gc_times = GC Times + +users.user_manage_panel = User Account Management +users.new_account = Create User Account +users.name = Username +users.activated = Activated +users.admin = Admin +users.repos = Repos +users.created = Created +users.last_login = Last Sign-In +users.never_login = Never Signed-In +users.send_register_notify = Send User Registration Notification +users.new_success = The user account '%s' has been created. +users.edit = Edit +users.auth_source = Authentication Source +users.local = Local +users.auth_login_name = Authentication Sign-In Name +users.password_helper = Leave the password empty to keep it unchanged. +users.update_profile_success = The user account has been updated. +users.edit_account = Edit User Account +users.max_repo_creation = Maximal Number of Repositories +users.max_repo_creation_desc = (Enter -1 to use the global default limit.) +users.is_activated = User Account Is Activated +users.prohibit_login = Disable Sign-In +users.is_admin = Is Administrator +users.allow_git_hook = May Create Git Hooks +users.allow_import_local = May Import Local Repositories +users.allow_create_organization = May Create Organizations +users.update_profile = Update User Account +users.delete_account = Delete User Account +users.still_own_repo = This user still owns one or more repositories. Delete or transfer these repositories first. +users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. +users.deletion_success = The user account has been deleted. + +orgs.org_manage_panel = Organization Management +orgs.name = Name +orgs.teams = Teams +orgs.members = Members +orgs.new_orga = New Organization + +repos.repo_manage_panel = Repository Management +repos.owner = Owner +repos.name = Name +repos.private = Private +repos.watches = Watches +repos.stars = Stars +repos.forks = Forks +repos.issues = Issues +repos.size = Size + +auths.auth_manage_panel = Authentication Source Management +auths.new = Add Authentication Source +auths.name = Name +auths.type = Type +auths.enabled = Enabled +auths.syncenabled = Enable User Synchronization +auths.updated = Updated +auths.auth_type = Authentication Type +auths.auth_name = Authentication Name +auths.security_protocol = Security Protocol +auths.domain = Domain +auths.host = Host +auths.port = Port +auths.bind_dn = Bind DN +auths.bind_password = Bind Password +auths.bind_password_helper = Warning: This password is stored in plain text. Use a read-only account if possible. +auths.user_base = User Search Base +auths.user_dn = User DN +auths.attribute_username = Username Attribute +auths.attribute_username_placeholder = Leave empty to use the username entered in Gitea. +auths.attribute_name = First Name Attribute +auths.attribute_surname = Surname Attribute +auths.attribute_mail = Email Attribute +auths.attribute_ssh_public_key = Public SSH Key Attribute +auths.attributes_in_bind = Fetch Attributes in Bind DN Context +auths.use_paged_search = Use Paged Search +auths.search_page_size = Page Size +auths.filter = User Filter +auths.admin_filter = Admin Filter +auths.ms_ad_sa = MS AD Search Attributes +auths.smtp_auth = SMTP Authentication Type +auths.smtphost = SMTP Host +auths.smtpport = SMTP Port +auths.allowed_domains = Allowed Domains +auths.allowed_domains_helper = Leave empty to allow all domains. Separate multiple domains with a comma (','). +auths.enable_tls = Enable TLS Encryption +auths.skip_tls_verify = Skip TLS Verify +auths.pam_service_name = PAM Service Name +auths.oauth2_provider = OAuth2 Provider +auths.oauth2_clientID = Client ID (Key) +auths.oauth2_clientSecret = Client Secret +auths.openIdConnectAutoDiscoveryURL = OpenID Connect Auto Discovery URL +auths.oauth2_use_custom_url = Use Custom URLs Instead of Default URLs +auths.oauth2_tokenURL = Token URL +auths.oauth2_authURL = Authorize URL +auths.oauth2_profileURL = Profile URL +auths.oauth2_emailURL = Email URL +auths.enable_auto_register = Enable Auto Registration +auths.tips = Tips +auths.tips.oauth2.general = OAuth2 Authentication +auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be: /user/oauth2//callback +auths.tip.oauth2_provider = OAuth2 Provider +auths.tip.bitbucket = Register a new OAuth consumer on https://bitbucket.org/account/user//oauth-consumers/new and add the permission 'Account' - 'Read' +auths.tip.dropbox = Create a new application at https://www.dropbox.com/developers/apps +auths.tip.facebook = Register a new application at https://developers.facebook.com/apps and add the product "Facebook Login" +auths.tip.github = Register a new OAuth application on https://github.com/settings/applications/new +auths.tip.gitlab = Register a new application on https://gitlab.com/profile/applications +auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console at https://console.developers.google.com/ +auths.tip.openid_connect = Use the OpenID Connect Discovery URL (/.well-known/openid-configuration) to specify the endpoints +auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled +auths.tip.discord = Register a new application on https://discordapp.com/developers/applications/me +auths.edit = Edit Authentication Source +auths.activated = This Authentication Source is Activated +auths.new_success = The authentication '%s' has been added. +auths.update_success = The authentication source has been updated. +auths.update = Update Authentication Source +auths.delete = Delete Authentication Source +auths.delete_auth_title = Delete Authentication Source +auths.delete_auth_desc = Deleting an authentication source prevents users from using it to sign in. Continue? +auths.still_in_used = The authentication source is still in use. Convert or delete any users using this authentication source first. +auths.deletion_success = The authentication source has been deleted. +auths.login_source_exist = The authentication source '%s' already exists. + +config.server_config = Server Configuration +config.app_name = Site Title +config.app_ver = Gitea Version +config.app_url = Gitea Base URL +config.custom_conf = Configuration File Path +config.domain = SSH Server Domain +config.offline_mode = Local Mode +config.disable_router_log = Disable Router Log +config.run_user = Run As Username +config.run_mode = Run Mode +config.git_version = Git Version +config.repo_root_path = Repository Root Path +config.lfs_root_path = LFS Root Path +config.static_file_root_path = Static File Root Path +config.log_file_root_path = Log Path +config.script_type = Script Type +config.reverse_auth_user = Reverse Authentication User + +config.ssh_config = SSH Configuration +config.ssh_enabled = Enabled +config.ssh_start_builtin_server = Use Built-In Server +config.ssh_domain = Server Domain +config.ssh_port = Port +config.ssh_listen_port = Listen Port +config.ssh_root_path = Root Path +config.ssh_key_test_path = Key Test Path +config.ssh_keygen_path = Keygen ('ssh-keygen') Path +config.ssh_minimum_key_size_check = Minimum Key Size Check +config.ssh_minimum_key_sizes = Minimum Key Sizes + +config.db_config = Database Configuration +config.db_type = Type +config.db_host = Host +config.db_name = Name +config.db_user = Username +config.db_ssl_mode = SSL +config.db_path = Path + +config.service_config = Service Configuration +config.register_email_confirm = Require Email Confirmation to Register +config.disable_register = Disable Self-Registration +config.allow_only_external_registration = Allow Registration Only Through External Services +config.enable_openid_signup = Enable OpenID Self-Registration +config.enable_openid_signin = Enable OpenID Sign-In +config.show_registration_button = Show Register Button +config.require_sign_in_view = Require Sign-In to View Pages +config.mail_notify = Enable Email Notifications +config.disable_key_size_check = Disable Minimum Key Size Check +config.enable_captcha = Enable CAPTCHA +config.active_code_lives = Active Code Lives +config.reset_password_code_lives = Reset Password Code Expiry Time +config.default_keep_email_private = Hide Email Addresses by Default +config.default_allow_create_organization = Allow Creation of Organizations by Default +config.enable_timetracking = Enable Time Tracking +config.default_enable_timetracking = Enable Time Tracking by Default +config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time +config.no_reply_address = Hidden Email Domain +config.default_enable_dependencies = Enable Issue Dependencies by Default + +config.webhook_config = Webhook Configuration +config.queue_length = Queue Length +config.deliver_timeout = Deliver Timeout +config.skip_tls_verify = Skip TLS Verification + +config.mailer_config = SMTP Mailer Configuration +config.mailer_enabled = Enabled +config.mailer_disable_helo = Disable HELO +config.mailer_name = Name +config.mailer_host = Host +config.mailer_user = User +config.mailer_use_sendmail = Use Sendmail +config.mailer_sendmail_path = Sendmail Path +config.mailer_sendmail_args = Extra Arguments to Sendmail +config.send_test_mail = Send Testing Email +config.test_mail_failed = Failed to send a testing email to '%s': %v +config.test_mail_sent = A testing email has been sent to '%s'. + +config.oauth_config = OAuth Configuration +config.oauth_enabled = Enabled + +config.cache_config = Cache Configuration +config.cache_adapter = Cache Adapter +config.cache_interval = Cache Interval +config.cache_conn = Cache Connection + +config.session_config = Session Configuration +config.session_provider = Session Provider +config.provider_config = Provider Config +config.cookie_name = Cookie Name +config.enable_set_cookie = Enable Set Cookie +config.gc_interval_time = GC Interval Time +config.session_life_time = Session Life Time +config.https_only = HTTPS Only +config.cookie_life_time = Cookie Life Time + +config.picture_config = Picture and Avatar Configuration +config.picture_service = Picture Service +config.disable_gravatar = Disable Gravatar +config.enable_federated_avatar = Enable Federated Avatars + +config.git_config = Git Configuration +config.git_disable_diff_highlight = Disable Diff Syntax Highlight +config.git_max_diff_lines = Max Diff Lines (for a single file) +config.git_max_diff_line_characters = Max Diff Characters (for a single line) +config.git_max_diff_files = Max Diff Files (to be shown) +config.git_gc_args = GC Arguments +config.git_migrate_timeout = Migration Timeout +config.git_mirror_timeout = Mirror Update Timeout +config.git_clone_timeout = Clone Operation Timeout +config.git_pull_timeout = Pull Operation Timeout +config.git_gc_timeout = GC Operation Timeout + +config.log_config = Log Configuration +config.log_mode = Log Mode + +monitor.cron = Cron Tasks +monitor.name = Name +monitor.schedule = Schedule +monitor.next = Next Time +monitor.previous = Previous Time +monitor.execute_times = Executions +monitor.process = Running Processes +monitor.desc = Description +monitor.start = Start Time +monitor.execute_time = Execution Time + +notices.system_notice_list = System Notices +notices.view_detail_header = View Notice Details +notices.actions = Actions +notices.select_all = Select All +notices.deselect_all = Deselect All +notices.inverse_selection = Inverse Selection +notices.delete_selected = Delete Selected +notices.delete_all = Delete All Notices +notices.type = Type +notices.type_1 = Repository +notices.desc = Description +notices.op = Op. +notices.delete_success = The system notices have been deleted. + +[action] +create_repo = created repository %s +rename_repo = renamed repository from %[1]s to %[3]s +commit_repo = pushed to %[3]s at %[4]s +create_issue = `opened issue %s#%[2]s` +close_issue = `closed issue %s#%[2]s` +reopen_issue = `reopened issue %s#%[2]s` +create_pull_request = `created pull request %s#%[2]s` +close_pull_request = `closed pull request %s#%[2]s` +reopen_pull_request = `reopened pull request %s#%[2]s` +comment_issue = `commented on issue %s#%[2]s` +merge_pull_request = `merged pull request %s#%[2]s` +transfer_repo = transferred repository %s to %s +push_tag = pushed tag %[2]s to %[3]s +delete_tag = deleted tag %[2]s from %[3]s +delete_branch = deleted branch %[2]s from %[3]s +compare_commits = Compare %d commits +mirror_sync_push = synced commits to %[3]s at %[4]s from mirror +mirror_sync_create = synced new reference %[2]s to %[3]s from mirror +mirror_sync_delete = synced and deleted reference %[2]s at %[3]s from mirror + +[tool] +ago = %s ago +from_now = %s from now +now = now +future = future +1s = 1 second +1m = 1 minute +1h = 1 hour +1d = 1 day +1w = 1 week +1mon = 1 month +1y = 1 year +seconds = %d seconds +minutes = %d minutes +hours = %d hours +days = %d days +weeks = %d weeks +months = %d months +years = %d years +raw_seconds = seconds +raw_minutes = minutes + +[dropzone] +default_message = Drop files or click here to upload. +invalid_input_type = You can not upload files of this type. +file_too_big = File size ({{filesize}} MB) exceeds the maximum size of ({{maxFilesize}} MB). +remove_file = Remove file + +[notification] +notifications = Notifications +unread = Unread +read = Read +no_unread = No unread notifications. +no_read = No read notifications. +pin = Pin notification +mark_as_read = Mark as read +mark_as_unread = Mark as unread +mark_all_as_read = Mark all as read + +[gpg] +error.extract_sign = Failed to extract signature +error.generate_hash = Failed to generate hash of commit +error.no_committer_account = No account linked to committer's email address +error.no_gpg_keys_found = "No known key found for this signature in database" +error.not_signed_commit = "Not a signed commit" +error.failed_retrieval_gpg_keys = "Failed to retrieve any key attached to the committer's account" + +[units] +error.no_unit_allowed_repo = You are not allowed to access any section of this repository. +error.unit_not_allowed = You are not allowed to access this repository section. diff --git a/routers/repo/repo.go b/routers/repo/repo.go index 960961a5e569c..de16739c11593 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -1,416 +1,419 @@ -// 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" - - "code.gitea.io/git" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/auth" - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" -) - -const ( - tplCreate base.TplName = "repo/create" - tplMigrate base.TplName = "repo/migrate" -) - -// MustBeNotEmpty render when a repo is a empty git dir -func MustBeNotEmpty(ctx *context.Context) { - if ctx.Repo.Repository.IsEmpty { - ctx.NotFound("MustBeNotEmpty", nil) - } -} - -// MustBeEditable check that repo can be edited -func MustBeEditable(ctx *context.Context) { - if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { - ctx.NotFound("", nil) - return - } -} - -// MustBeAbleToUpload check that repo can be uploaded to -func MustBeAbleToUpload(ctx *context.Context) { - if !setting.Repository.Upload.Enabled { - ctx.NotFound("", nil) - } -} - -func checkContextUser(ctx *context.Context, uid int64) *models.User { - orgs, err := models.GetOwnedOrgsByUserIDDesc(ctx.User.ID, "updated_unix") - if err != nil { - ctx.ServerError("GetOwnedOrgsByUserIDDesc", err) - return nil - } - ctx.Data["Orgs"] = orgs - - // Not equal means current user is an organization. - if uid == ctx.User.ID || uid == 0 { - return ctx.User - } - - org, err := models.GetUserByID(uid) - if models.IsErrUserNotExist(err) { - return ctx.User - } - - if err != nil { - ctx.ServerError("GetUserByID", fmt.Errorf("[%d]: %v", uid, err)) - return nil - } - - // Check ownership of organization. - if !org.IsOrganization() { - ctx.Error(403) - return nil - } - if !ctx.User.IsAdmin { - isOwner, err := org.IsOwnedBy(ctx.User.ID) - if err != nil { - ctx.ServerError("IsOwnedBy", err) - return nil - } else if !isOwner { - ctx.Error(403) - return nil - } - } - return org -} - -func getRepoPrivate(ctx *context.Context) bool { - switch strings.ToLower(setting.Repository.DefaultPrivate) { - case setting.RepoCreatingLastUserVisibility: - return ctx.User.LastRepoVisibility - case setting.RepoCreatingPrivate: - return true - case setting.RepoCreatingPublic: - return false - default: - return ctx.User.LastRepoVisibility - } -} - -// Create render creating repository page -func Create(ctx *context.Context) { - if !ctx.User.CanCreateRepo() { - ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", ctx.User.MaxCreationLimit()), tplCreate, nil) - } - - ctx.Data["Title"] = ctx.Tr("new_repo") - - // Give default value for template to render. - ctx.Data["Gitignores"] = models.Gitignores - ctx.Data["Licenses"] = models.Licenses - ctx.Data["Readmes"] = models.Readmes - ctx.Data["readme"] = "Default" - ctx.Data["private"] = getRepoPrivate(ctx) - ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - - ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - ctx.HTML(200, tplCreate) -} - -func handleCreateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) { - switch { - case models.IsErrReachLimitOfRepo(err): - ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form) - case models.IsErrRepoAlreadyExist(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) - case models.IsErrNameReserved(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) - case models.IsErrNamePatternNotAllowed(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) - default: - ctx.ServerError(name, err) - } -} - -// CreatePost response for creating repository -func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { - ctx.Data["Title"] = ctx.Tr("new_repo") - - ctx.Data["Gitignores"] = models.Gitignores - ctx.Data["Licenses"] = models.Licenses - ctx.Data["Readmes"] = models.Readmes - - ctxUser := checkContextUser(ctx, form.UID) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - if ctx.HasError() { - ctx.HTML(200, tplCreate) - return - } - - repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ - Name: form.RepoName, - Description: form.Description, - Gitignores: form.Gitignores, - License: form.License, - Readme: form.Readme, - IsPrivate: form.Private || setting.Repository.ForcePrivate, - AutoInit: form.AutoInit, - }) - if err == nil { - log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) - ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name) - return - } - - if repo != nil { - if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil { - log.Error(4, "DeleteRepository: %v", errDelete) - } - } - - handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) -} - -// Migrate render migration of repository page -func Migrate(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("new_migrate") - ctx.Data["private"] = getRepoPrivate(ctx) - ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - ctx.Data["mirror"] = ctx.Query("mirror") == "1" - ctx.Data["LFSActive"] = setting.LFS.StartServer - - ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - ctx.HTML(200, tplMigrate) -} - -// MigratePost response for migrating from external git repository -func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { - ctx.Data["Title"] = ctx.Tr("new_migrate") - - ctxUser := checkContextUser(ctx, form.UID) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - if ctx.HasError() { - ctx.HTML(200, tplMigrate) - return - } - - remoteAddr, err := form.ParseRemoteAddr(ctx.User) - if err != nil { - if models.IsErrInvalidCloneAddr(err) { - ctx.Data["Err_CloneAddr"] = true - addrErr := err.(models.ErrInvalidCloneAddr) - switch { - case addrErr.IsURLError: - ctx.RenderWithErr(ctx.Tr("form.url_error"), tplMigrate, &form) - case addrErr.IsPermissionDenied: - ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplMigrate, &form) - case addrErr.IsInvalidPath: - ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplMigrate, &form) - default: - ctx.ServerError("Unknown error", err) - } - } else { - ctx.ServerError("ParseRemoteAddr", err) - } - return - } - - repo, err := models.MigrateRepository(ctx.User, ctxUser, models.MigrateRepoOptions{ - Name: form.RepoName, - Description: form.Description, - IsPrivate: form.Private || setting.Repository.ForcePrivate, - IsMirror: form.Mirror, - RemoteAddr: remoteAddr, - }) - if err == nil { - log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, form.RepoName) - ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + form.RepoName) - return - } - - // remoteAddr may contain credentials, so we sanitize it - err = util.URLSanitizedError(err, remoteAddr) - - if repo != nil { - if errDelete := models.DeleteRepository(ctx.User, 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") { - ctx.Data["Err_Auth"] = true - ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tplMigrate, &form) - return - } else if strings.Contains(err.Error(), "fatal:") { - ctx.Data["Err_CloneAddr"] = true - ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tplMigrate, &form) - return - } - - handleCreateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) -} - -// Action response for actions to a repository -func Action(ctx *context.Context) { - var err error - switch ctx.Params(":action") { - case "watch": - err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) - case "unwatch": - err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) - case "star": - err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) - case "unstar": - err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) - case "desc": // FIXME: this is not used - if !ctx.Repo.IsOwner() { - ctx.Error(404) - return - } - - ctx.Repo.Repository.Description = ctx.Query("desc") - ctx.Repo.Repository.Website = ctx.Query("site") - err = models.UpdateRepository(ctx.Repo.Repository, false) - } - - if err != nil { - ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) - return - } - - ctx.RedirectToFirst(ctx.Query("redirect_to"), ctx.Repo.RepoLink) -} - -// RedirectDownload return a file based on the following infos: -func RedirectDownload(ctx *context.Context) { - var ( - vTag = ctx.Params("vTag") - fileName = ctx.Params("fileName") - ) - tagNames := []string{vTag} - curRepo := ctx.Repo.Repository - releases, err := models.GetReleasesByRepoIDAndNames(curRepo.ID, tagNames) - if err != nil { - if models.IsErrAttachmentNotExist(err) { - ctx.Error(404) - return - } - ctx.ServerError("RedirectDownload", err) - return - } - if len(releases) == 1 { - release := releases[0] - att, err := models.GetAttachmentByReleaseIDFileName(release.ID, fileName) - if err != nil { - ctx.Error(404) - return - } - if att != nil { - ctx.Redirect(setting.AppSubURL + "/attachments/" + att.UUID) - return - } - } - ctx.Error(404) -} - -// Download download an archive of a repository -func Download(ctx *context.Context) { - var ( - uri = ctx.Params("*") - refName string - ext string - archivePath string - archiveType git.ArchiveType - ) - - switch { - case strings.HasSuffix(uri, ".zip"): - ext = ".zip" - archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/zip") - archiveType = git.ZIP - case strings.HasSuffix(uri, ".tar.gz"): - ext = ".tar.gz" - archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/targz") - archiveType = git.TARGZ - default: - log.Trace("Unknown format: %s", uri) - ctx.Error(404) - return - } - refName = strings.TrimSuffix(uri, ext) - - if !com.IsDir(archivePath) { - if err := os.MkdirAll(archivePath, os.ModePerm); err != nil { - ctx.ServerError("Download -> os.MkdirAll(archivePath)", err) - return - } - } - - // Get corresponding commit. - var ( - commit *git.Commit - err error - ) - gitRepo := ctx.Repo.GitRepo - if gitRepo.IsBranchExist(refName) { - commit, err = gitRepo.GetBranchCommit(refName) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return - } - } else if gitRepo.IsTagExist(refName) { - commit, err = gitRepo.GetTagCommit(refName) - if err != nil { - ctx.ServerError("GetTagCommit", err) - return - } - } else if len(refName) >= 4 && len(refName) <= 40 { - commit, err = gitRepo.GetCommit(refName) - if err != nil { - ctx.NotFound("GetCommit", nil) - return - } - } else { - ctx.NotFound("Download", nil) - return - } - - archivePath = path.Join(archivePath, base.ShortSha(commit.ID.String())+ext) - if !com.IsFile(archivePath) { - if err := commit.CreateArchive(archivePath, archiveType); err != nil { - ctx.ServerError("Download -> CreateArchive "+archivePath, err) - return - } - } - - ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext) -} +// 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" + + "code.gitea.io/git" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +const ( + tplCreate base.TplName = "repo/create" + tplMigrate base.TplName = "repo/migrate" +) + +// MustBeNotEmpty render when a repo is a empty git dir +func MustBeNotEmpty(ctx *context.Context) { + if ctx.Repo.Repository.IsEmpty { + ctx.NotFound("MustBeNotEmpty", nil) + } +} + +// MustBeEditable check that repo can be edited +func MustBeEditable(ctx *context.Context) { + if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { + ctx.NotFound("", nil) + return + } +} + +// MustBeAbleToUpload check that repo can be uploaded to +func MustBeAbleToUpload(ctx *context.Context) { + if !setting.Repository.Upload.Enabled { + ctx.NotFound("", nil) + } +} + +func checkContextUser(ctx *context.Context, uid int64) *models.User { + orgs, err := models.GetOwnedOrgsByUserIDDesc(ctx.User.ID, "updated_unix") + if err != nil { + ctx.ServerError("GetOwnedOrgsByUserIDDesc", err) + return nil + } + ctx.Data["Orgs"] = orgs + + // Not equal means current user is an organization. + if uid == ctx.User.ID || uid == 0 { + return ctx.User + } + + org, err := models.GetUserByID(uid) + if models.IsErrUserNotExist(err) { + return ctx.User + } + + if err != nil { + ctx.ServerError("GetUserByID", fmt.Errorf("[%d]: %v", uid, err)) + return nil + } + + // Check ownership of organization. + if !org.IsOrganization() { + ctx.Error(403) + return nil + } + if !ctx.User.IsAdmin { + isOwner, err := org.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.ServerError("IsOwnedBy", err) + return nil + } else if !isOwner { + ctx.Error(403) + return nil + } + } + return org +} + +func getRepoPrivate(ctx *context.Context) bool { + switch strings.ToLower(setting.Repository.DefaultPrivate) { + case setting.RepoCreatingLastUserVisibility: + return ctx.User.LastRepoVisibility + case setting.RepoCreatingPrivate: + return true + case setting.RepoCreatingPublic: + return false + default: + return ctx.User.LastRepoVisibility + } +} + +// Create render creating repository page +func Create(ctx *context.Context) { + if !ctx.User.CanCreateRepo() { + ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", ctx.User.MaxCreationLimit()), tplCreate, nil) + } + + ctx.Data["Title"] = ctx.Tr("new_repo") + + // Give default value for template to render. + ctx.Data["Gitignores"] = models.Gitignores + ctx.Data["LabelTemplates"] = models.LabelTemplates + ctx.Data["Licenses"] = models.Licenses + ctx.Data["Readmes"] = models.Readmes + ctx.Data["readme"] = "Default" + ctx.Data["private"] = getRepoPrivate(ctx) + ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate + + ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + ctx.HTML(200, tplCreate) +} + +func handleCreateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) { + switch { + case models.IsErrReachLimitOfRepo(err): + ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form) + case models.IsErrRepoAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) + case models.IsErrNameReserved(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) + case models.IsErrNamePatternNotAllowed(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) + default: + ctx.ServerError(name, err) + } +} + +// CreatePost response for creating repository +func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { + ctx.Data["Title"] = ctx.Tr("new_repo") + + ctx.Data["Gitignores"] = models.Gitignores + ctx.Data["LabelTemplates"] = models.LabelTemplates + ctx.Data["Licenses"] = models.Licenses + ctx.Data["Readmes"] = models.Readmes + + ctxUser := checkContextUser(ctx, form.UID) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + if ctx.HasError() { + ctx.HTML(200, tplCreate) + return + } + + repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + Name: form.RepoName, + Description: form.Description, + Gitignores: form.Gitignores, + IssueLabels: form.IssueLabels, + License: form.License, + Readme: form.Readme, + IsPrivate: form.Private || setting.Repository.ForcePrivate, + AutoInit: form.AutoInit, + }) + if err == nil { + log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) + ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name) + return + } + + if repo != nil { + if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil { + log.Error(4, "DeleteRepository: %v", errDelete) + } + } + + handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) +} + +// Migrate render migration of repository page +func Migrate(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("new_migrate") + ctx.Data["private"] = getRepoPrivate(ctx) + ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate + ctx.Data["mirror"] = ctx.Query("mirror") == "1" + ctx.Data["LFSActive"] = setting.LFS.StartServer + + ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + ctx.HTML(200, tplMigrate) +} + +// MigratePost response for migrating from external git repository +func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { + ctx.Data["Title"] = ctx.Tr("new_migrate") + + ctxUser := checkContextUser(ctx, form.UID) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + if ctx.HasError() { + ctx.HTML(200, tplMigrate) + return + } + + remoteAddr, err := form.ParseRemoteAddr(ctx.User) + if err != nil { + if models.IsErrInvalidCloneAddr(err) { + ctx.Data["Err_CloneAddr"] = true + addrErr := err.(models.ErrInvalidCloneAddr) + switch { + case addrErr.IsURLError: + ctx.RenderWithErr(ctx.Tr("form.url_error"), tplMigrate, &form) + case addrErr.IsPermissionDenied: + ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplMigrate, &form) + case addrErr.IsInvalidPath: + ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplMigrate, &form) + default: + ctx.ServerError("Unknown error", err) + } + } else { + ctx.ServerError("ParseRemoteAddr", err) + } + return + } + + repo, err := models.MigrateRepository(ctx.User, ctxUser, models.MigrateRepoOptions{ + Name: form.RepoName, + Description: form.Description, + IsPrivate: form.Private || setting.Repository.ForcePrivate, + IsMirror: form.Mirror, + RemoteAddr: remoteAddr, + }) + if err == nil { + log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, form.RepoName) + ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + form.RepoName) + return + } + + // remoteAddr may contain credentials, so we sanitize it + err = util.URLSanitizedError(err, remoteAddr) + + if repo != nil { + if errDelete := models.DeleteRepository(ctx.User, 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") { + ctx.Data["Err_Auth"] = true + ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tplMigrate, &form) + return + } else if strings.Contains(err.Error(), "fatal:") { + ctx.Data["Err_CloneAddr"] = true + ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tplMigrate, &form) + return + } + + handleCreateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) +} + +// Action response for actions to a repository +func Action(ctx *context.Context) { + var err error + switch ctx.Params(":action") { + case "watch": + err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) + case "unwatch": + err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) + case "star": + err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) + case "unstar": + err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) + case "desc": // FIXME: this is not used + if !ctx.Repo.IsOwner() { + ctx.Error(404) + return + } + + ctx.Repo.Repository.Description = ctx.Query("desc") + ctx.Repo.Repository.Website = ctx.Query("site") + err = models.UpdateRepository(ctx.Repo.Repository, false) + } + + if err != nil { + ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) + return + } + + ctx.RedirectToFirst(ctx.Query("redirect_to"), ctx.Repo.RepoLink) +} + +// RedirectDownload return a file based on the following infos: +func RedirectDownload(ctx *context.Context) { + var ( + vTag = ctx.Params("vTag") + fileName = ctx.Params("fileName") + ) + tagNames := []string{vTag} + curRepo := ctx.Repo.Repository + releases, err := models.GetReleasesByRepoIDAndNames(curRepo.ID, tagNames) + if err != nil { + if models.IsErrAttachmentNotExist(err) { + ctx.Error(404) + return + } + ctx.ServerError("RedirectDownload", err) + return + } + if len(releases) == 1 { + release := releases[0] + att, err := models.GetAttachmentByReleaseIDFileName(release.ID, fileName) + if err != nil { + ctx.Error(404) + return + } + if att != nil { + ctx.Redirect(setting.AppSubURL + "/attachments/" + att.UUID) + return + } + } + ctx.Error(404) +} + +// Download download an archive of a repository +func Download(ctx *context.Context) { + var ( + uri = ctx.Params("*") + refName string + ext string + archivePath string + archiveType git.ArchiveType + ) + + switch { + case strings.HasSuffix(uri, ".zip"): + ext = ".zip" + archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/zip") + archiveType = git.ZIP + case strings.HasSuffix(uri, ".tar.gz"): + ext = ".tar.gz" + archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/targz") + archiveType = git.TARGZ + default: + log.Trace("Unknown format: %s", uri) + ctx.Error(404) + return + } + refName = strings.TrimSuffix(uri, ext) + + if !com.IsDir(archivePath) { + if err := os.MkdirAll(archivePath, os.ModePerm); err != nil { + ctx.ServerError("Download -> os.MkdirAll(archivePath)", err) + return + } + } + + // Get corresponding commit. + var ( + commit *git.Commit + err error + ) + gitRepo := ctx.Repo.GitRepo + if gitRepo.IsBranchExist(refName) { + commit, err = gitRepo.GetBranchCommit(refName) + if err != nil { + ctx.ServerError("GetBranchCommit", err) + return + } + } else if gitRepo.IsTagExist(refName) { + commit, err = gitRepo.GetTagCommit(refName) + if err != nil { + ctx.ServerError("GetTagCommit", err) + return + } + } else if len(refName) >= 4 && len(refName) <= 40 { + commit, err = gitRepo.GetCommit(refName) + if err != nil { + ctx.NotFound("GetCommit", nil) + return + } + } else { + ctx.NotFound("Download", nil) + return + } + + archivePath = path.Join(archivePath, base.ShortSha(commit.ID.String())+ext) + if !com.IsFile(archivePath) { + if err := commit.CreateArchive(archivePath, archiveType); err != nil { + ctx.ServerError("Download -> CreateArchive "+archivePath, err) + return + } + } + + ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext) +} diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index aa20f07760aa4..55c421084a716 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -1,116 +1,129 @@ -{{template "base/head" .}} -
-
-
-
- {{.CsrfTokenHtml}} -

- {{.i18n.Tr "new_repo"}} -

-
- {{template "base/alert" .}} -
- - -
- -
- - - {{.i18n.Tr "repo.repo_name_helper"}} -
-
- -
- {{if .IsForcedPrivate}} - - - {{else}} - - - {{end}} -
-
-
- - -
- -
- -
- - -
-
- - -
- -
- - -
-
-
- - -
-
- -
- - - {{.i18n.Tr "cancel"}} -
-
-
-
-
-
-{{template "base/footer" .}} +{{template "base/head" .}} +
+
+
+
+ {{.CsrfTokenHtml}} +

+ {{.i18n.Tr "new_repo"}} +

+
+ {{template "base/alert" .}} +
+ + +
+ +
+ + + {{.i18n.Tr "repo.repo_name_helper"}} +
+
+ +
+ {{if .IsForcedPrivate}} + + + {{else}} + + + {{end}} +
+
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ + +
+
+ +
+ + + {{.i18n.Tr "cancel"}} +
+
+
+
+
+
+{{template "base/footer" .}} From 1a63e8e7573e431e4e718cb86a2dc06ce938cfba Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 13 Feb 2019 13:08:49 -0600 Subject: [PATCH 2/7] Fix CRLF --- models/repo.go | 5082 +++++++++++++++---------------- modules/auth/repo_form.go | 1210 ++++---- options/locale/locale_en-US.ini | 3550 ++++++++++----------- routers/repo/repo.go | 838 ++--- templates/repo/create.tmpl | 258 +- 5 files changed, 5469 insertions(+), 5469 deletions(-) diff --git a/models/repo.go b/models/repo.go index 07345edc8c5a8..8be96ccbf782d 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1,2541 +1,2541 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Copyright 2017 The Gitea 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 models - -import ( - "bytes" - "errors" - "fmt" - "html/template" - "io/ioutil" - "net/url" - "os" - "os/exec" - "path" - "path/filepath" - "regexp" - "sort" - "strings" - "time" - - "code.gitea.io/git" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/process" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/sync" - "code.gitea.io/gitea/modules/util" - api "code.gitea.io/sdk/gitea" - - "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" -) - -var repoWorkingPool = sync.NewExclusivePool() - -var ( - // ErrMirrorNotExist mirror does not exist error - ErrMirrorNotExist = errors.New("Mirror does not exist") - - // ErrNameEmpty name is empty error - ErrNameEmpty = errors.New("Name is empty") -) - -var ( - // Gitignores contains the gitiginore files - Gitignores []string - - // Licenses contains the license files - Licenses []string - - // Readmes contains the readme files - Readmes []string - - // LabelTemplates contains the label template files - LabelTemplates []string - - // ItemsPerPage maximum items per page in forks, watchers and stars of a repo - ItemsPerPage = 40 -) - -// 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) - for i, t := range types { - files, err := options.Dir(t) - if err != nil { - log.Fatal(4, "Failed to get %s files: %v", t, err) - } - customPath := path.Join(setting.CustomPath, "options", t) - if com.IsDir(customPath) { - customFiles, err := com.StatDir(customPath) - if err != nil { - log.Fatal(4, "Failed to get custom %s files: %v", t, err) - } - - for _, f := range customFiles { - if !com.IsSliceContainsStr(files, f) { - files = append(files, f) - } - } - } - typeFiles[i] = files - } - - Gitignores = typeFiles[0] - Licenses = typeFiles[1] - Readmes = typeFiles[2] - LabelTemplates = typeFiles[3] - sort.Strings(Gitignores) - sort.Strings(Licenses) - sort.Strings(Readmes) - sort.Strings(LabelTemplates) - - // Filter out invalid names and promote preferred licenses. - sortedLicenses := make([]string, 0, len(Licenses)) - for _, name := range setting.Repository.PreferredLicenses { - if com.IsSliceContainsStr(Licenses, name) { - sortedLicenses = append(sortedLicenses, name) - } - } - for _, name := range Licenses { - if !com.IsSliceContainsStr(setting.Repository.PreferredLicenses, name) { - sortedLicenses = append(sortedLicenses, name) - } - } - Licenses = sortedLicenses -} - -// NewRepoContext creates a new repository context -func NewRepoContext() { - zip.Verbose = false - - // Check Git installation. - if _, err := exec.LookPath("git"); err != nil { - log.Fatal(4, "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(4, "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(4, "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(4, "Failed to set git %s(%s): %s", configKey, gerr, stderr) - } - log.Info("Git config %s set to %s", configKey, defaultValue) - } else { - log.Fatal(4, "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(4, "Failed to execute 'git config --global core.quotepath false': %s", stderr) - } - - RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) -} - -// Repository represents a git repository. -type Repository struct { - ID int64 `xorm:"pk autoincr"` - OwnerID int64 `xorm:"UNIQUE(s)"` - OwnerName string `xorm:"-"` - Owner *User `xorm:"-"` - LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"INDEX NOT NULL"` - Description string - Website string - DefaultBranch string - - NumWatches int - NumStars int - NumForks int - NumIssues int - NumClosedIssues int - NumOpenIssues int `xorm:"-"` - NumPulls int - NumClosedPulls int - NumOpenPulls int `xorm:"-"` - NumMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumOpenMilestones int `xorm:"-"` - NumReleases int `xorm:"-"` - - IsPrivate bool `xorm:"INDEX"` - IsEmpty bool `xorm:"INDEX"` - IsArchived bool `xorm:"INDEX"` - - IsMirror bool `xorm:"INDEX"` - *Mirror `xorm:"-"` - - ExternalMetas map[string]string `xorm:"-"` - Units []*RepoUnit `xorm:"-"` - - IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` - ForkID int64 `xorm:"INDEX"` - BaseRepo *Repository `xorm:"-"` - Size int64 `xorm:"NOT NULL DEFAULT 0"` - IndexerStatus *RepoIndexerStatus `xorm:"-"` - IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` - CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` - Topics []string `xorm:"TEXT JSON"` - - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` -} - -// AfterLoad is invoked from XORM after setting the values of all fields of this object. -func (repo *Repository) AfterLoad() { - // FIXME: use models migration to solve all at once. - if len(repo.DefaultBranch) == 0 { - repo.DefaultBranch = "master" - } - - repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues - repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls - repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones -} - -// MustOwner always returns a valid *User object to avoid -// conceptually impossible error handling. -// It creates a fake object that contains error details -// when error occurs. -func (repo *Repository) MustOwner() *User { - return repo.mustOwner(x) -} - -// MustOwnerName always returns valid owner name to avoid -// conceptually impossible error handling. -// It returns "error" and logs error details when error -// occurs. -func (repo *Repository) MustOwnerName() string { - return repo.mustOwnerName(x) -} - -// FullName returns the repository full name -func (repo *Repository) FullName() string { - return repo.MustOwnerName() + "/" + repo.Name -} - -// HTMLURL returns the repository HTML URL -func (repo *Repository) HTMLURL() string { - return setting.AppURL + repo.FullName() -} - -// APIURL returns the repository API URL -func (repo *Repository) APIURL() string { - return setting.AppURL + path.Join("api/v1/repos", repo.FullName()) -} - -// APIFormat converts a Repository to api.Repository -func (repo *Repository) APIFormat(mode AccessMode) *api.Repository { - return repo.innerAPIFormat(x, mode, false) -} - -// GetCommitsCountCacheKey returns cache key used for commits count caching. -func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string { - var prefix string - if isRef { - prefix = "ref" - } else { - prefix = "commit" - } - return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName) -} - -func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) *api.Repository { - var parent *api.Repository - - cloneLink := repo.cloneLink(e, false) - permission := &api.Permission{ - Admin: mode >= AccessModeAdmin, - Push: mode >= AccessModeWrite, - Pull: mode >= AccessModeRead, - } - if !isParent { - err := repo.getBaseRepo(e) - if err != nil { - log.Error(4, "APIFormat: %v", err) - } - if repo.BaseRepo != nil { - parent = repo.BaseRepo.innerAPIFormat(e, mode, true) - } - } - 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, - } -} - -func (repo *Repository) getUnits(e Engine) (err error) { - if repo.Units != nil { - return nil - } - - repo.Units, err = getUnitsByRepoID(e, repo.ID) - return err -} - -// CheckUnitUser check whether user could visit the unit of this repository -func (repo *Repository) CheckUnitUser(userID int64, isAdmin bool, unitType UnitType) bool { - return repo.checkUnitUser(x, userID, isAdmin, unitType) -} - -func (repo *Repository) checkUnitUser(e Engine, userID int64, isAdmin bool, unitType UnitType) bool { - if isAdmin { - return true - } - user, err := getUserByID(e, userID) - if err != nil { - return false - } - perm, err := getUserRepoPermission(e, repo, user) - if err != nil { - return false - } - - return perm.CanRead(unitType) -} - -// UnitEnabled if this repository has the given unit enabled -func (repo *Repository) UnitEnabled(tp UnitType) bool { - if err := repo.getUnits(x); err != nil { - log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error()) - } - for _, unit := range repo.Units { - if unit.Type == tp { - return true - } - } - return false -} - -var ( - // ErrUnitNotExist organization does not exist - ErrUnitNotExist = errors.New("Unit does not exist") -) - -// MustGetUnit always returns a RepoUnit object -func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit { - ru, err := repo.GetUnit(tp) - if err == nil { - return ru - } - - if tp == UnitTypeExternalWiki { - return &RepoUnit{ - Type: tp, - Config: new(ExternalWikiConfig), - } - } else if tp == UnitTypeExternalTracker { - return &RepoUnit{ - Type: tp, - Config: new(ExternalTrackerConfig), - } - } else if tp == UnitTypePullRequests { - return &RepoUnit{ - Type: tp, - Config: new(PullRequestsConfig), - } - } - return &RepoUnit{ - Type: tp, - Config: new(UnitConfig), - } -} - -// GetUnit returns a RepoUnit object -func (repo *Repository) GetUnit(tp UnitType) (*RepoUnit, error) { - return repo.getUnit(x, tp) -} - -func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) { - if err := repo.getUnits(e); err != nil { - return nil, err - } - for _, unit := range repo.Units { - if unit.Type == tp { - return unit, nil - } - } - return nil, ErrUnitNotExist -} - -func (repo *Repository) getOwner(e Engine) (err error) { - if repo.Owner != nil { - return nil - } - - repo.Owner, err = getUserByID(e, repo.OwnerID) - return err -} - -// GetOwner returns the repository owner -func (repo *Repository) GetOwner() error { - return repo.getOwner(x) -} - -func (repo *Repository) mustOwner(e Engine) *User { - if err := repo.getOwner(e); err != nil { - return &User{ - Name: "error", - FullName: err.Error(), - } - } - - return repo.Owner -} - -func (repo *Repository) getOwnerName(e Engine) error { - if len(repo.OwnerName) > 0 { - return nil - } - - if repo.Owner != nil { - repo.OwnerName = repo.Owner.Name - return nil - } - - u := new(User) - has, err := e.ID(repo.OwnerID).Cols("name").Get(u) - if err != nil { - return err - } else if !has { - return ErrUserNotExist{repo.OwnerID, "", 0} - } - repo.OwnerName = u.Name - return nil -} - -// GetOwnerName returns the repository owner name -func (repo *Repository) GetOwnerName() error { - return repo.getOwnerName(x) -} - -func (repo *Repository) mustOwnerName(e Engine) string { - if err := repo.getOwnerName(e); err != nil { - log.Error(4, "Error loading repository owner name: %v", err) - return "error" - } - - return repo.OwnerName -} - -// ComposeMetas composes a map of metas for rendering external issue tracker URL. -func (repo *Repository) ComposeMetas() map[string]string { - unit, err := repo.GetUnit(UnitTypeExternalTracker) - if err != nil { - return nil - } - - if repo.ExternalMetas == nil { - repo.ExternalMetas = map[string]string{ - "format": unit.ExternalTrackerConfig().ExternalTrackerFormat, - "user": repo.MustOwner().Name, - "repo": repo.Name, - } - switch unit.ExternalTrackerConfig().ExternalTrackerStyle { - case markup.IssueNameStyleAlphanumeric: - repo.ExternalMetas["style"] = markup.IssueNameStyleAlphanumeric - default: - repo.ExternalMetas["style"] = markup.IssueNameStyleNumeric - } - - } - return repo.ExternalMetas -} - -// DeleteWiki removes the actual and local copy of repository wiki. -func (repo *Repository) DeleteWiki() error { - return repo.deleteWiki(x) -} - -func (repo *Repository) deleteWiki(e Engine) error { - wikiPaths := []string{repo.WikiPath(), repo.LocalWikiPath()} - for _, wikiPath := range wikiPaths { - removeAllWithNotice(e, "Delete repository wiki", wikiPath) - } - - _, err := e.Where("repo_id = ?", repo.ID).And("type = ?", UnitTypeWiki).Delete(new(RepoUnit)) - return err -} - -func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { - if err = repo.getOwner(e); err != nil { - return nil, err - } - - accesses := make([]*Access, 0, 10) - if err = e. - Where("repo_id = ? AND mode >= ?", repo.ID, AccessModeWrite). - Find(&accesses); err != nil { - return nil, err - } - - // Leave a seat for owner itself to append later, but if owner is an organization - // and just waste 1 unit is cheaper than re-allocate memory once. - users := make([]*User, 0, len(accesses)+1) - if len(accesses) > 0 { - userIDs := make([]int64, len(accesses)) - for i := 0; i < len(accesses); i++ { - userIDs[i] = accesses[i].UserID - } - - if err = e.In("id", userIDs).Find(&users); err != nil { - return nil, err - } - } - if !repo.Owner.IsOrganization() { - users = append(users, repo.Owner) - } - - return users, nil -} - -// GetAssignees returns all users that have write access and can be assigned to issues -// of the repository, -func (repo *Repository) GetAssignees() (_ []*User, err error) { - return repo.getAssignees(x) -} - -// GetMilestoneByID returns the milestone belongs to repository by given ID. -func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) { - return GetMilestoneByRepoID(repo.ID, milestoneID) -} - -// IssueStats returns number of open and closed repository issues by given filter mode. -func (repo *Repository) IssueStats(uid int64, filterMode int, isPull bool) (int64, int64) { - return GetRepoIssueStats(repo.ID, uid, filterMode, isPull) -} - -// GetMirror sets the repository mirror, returns an error upon failure -func (repo *Repository) GetMirror() (err error) { - repo.Mirror, err = GetMirrorByRepoID(repo.ID) - return err -} - -// GetBaseRepo populates repo.BaseRepo for a fork repository and -// returns an error on failure (NOTE: no error is returned for -// non-fork repositories, and BaseRepo will be left untouched) -func (repo *Repository) GetBaseRepo() (err error) { - return repo.getBaseRepo(x) -} - -func (repo *Repository) getBaseRepo(e Engine) (err error) { - if !repo.IsFork { - return nil - } - - repo.BaseRepo, err = getRepositoryByID(e, repo.ForkID) - return err -} - -func (repo *Repository) repoPath(e Engine) string { - return RepoPath(repo.mustOwnerName(e), repo.Name) -} - -// RepoPath returns the repository path -func (repo *Repository) RepoPath() string { - return repo.repoPath(x) -} - -// GitConfigPath returns the path to a repository's git config/ directory -func GitConfigPath(repoPath string) string { - return filepath.Join(repoPath, "config") -} - -// GitConfigPath returns the repository git config path -func (repo *Repository) GitConfigPath() string { - return GitConfigPath(repo.RepoPath()) -} - -// RelLink returns the repository relative link -func (repo *Repository) RelLink() string { - return "/" + repo.FullName() -} - -// Link returns the repository link -func (repo *Repository) Link() string { - return setting.AppSubURL + "/" + repo.FullName() -} - -// ComposeCompareURL returns the repository comparison URL -func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { - return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) -} - -// UpdateDefaultBranch updates the default branch -func (repo *Repository) UpdateDefaultBranch() error { - _, err := x.ID(repo.ID).Cols("default_branch").Update(repo) - return err -} - -// IsOwnedBy returns true when user owns this repository -func (repo *Repository) IsOwnedBy(userID int64) bool { - return repo.OwnerID == userID -} - -func (repo *Repository) updateSize(e Engine) error { - repoInfoSize, err := git.GetRepoSize(repo.repoPath(e)) - if err != nil { - return fmt.Errorf("UpdateSize: %v", err) - } - - repo.Size = repoInfoSize.Size + repoInfoSize.SizePack - _, err = e.ID(repo.ID).Cols("size").Update(repo) - return err -} - -// UpdateSize updates the repository size, calculating it using git.GetRepoSize -func (repo *Repository) UpdateSize() error { - return repo.updateSize(x) -} - -// CanUserFork returns true if specified user can fork repository. -func (repo *Repository) CanUserFork(user *User) (bool, error) { - if user == nil { - return false, nil - } - if repo.OwnerID != user.ID && !user.HasForkedRepo(repo.ID) { - return true, nil - } - if err := user.GetOwnedOrganizations(); err != nil { - return false, err - } - for _, org := range user.OwnedOrgs { - if repo.OwnerID != org.ID && !org.HasForkedRepo(repo.ID) { - return true, nil - } - } - return false, nil -} - -// CanEnablePulls returns true if repository meets the requirements of accepting pulls. -func (repo *Repository) CanEnablePulls() bool { - return !repo.IsMirror && !repo.IsEmpty -} - -// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled. -func (repo *Repository) AllowsPulls() bool { - return repo.CanEnablePulls() && repo.UnitEnabled(UnitTypePullRequests) -} - -// CanEnableEditor returns true if repository meets the requirements of web editor. -func (repo *Repository) CanEnableEditor() bool { - return !repo.IsMirror -} - -// GetWriters returns all users that have write access to the repository. -func (repo *Repository) GetWriters() (_ []*User, err error) { - return repo.getUsersWithAccessMode(x, AccessModeWrite) -} - -// getUsersWithAccessMode returns users that have at least given access mode to the repository. -func (repo *Repository) getUsersWithAccessMode(e Engine, mode AccessMode) (_ []*User, err error) { - if err = repo.getOwner(e); err != nil { - return nil, err - } - - accesses := make([]*Access, 0, 10) - if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { - return nil, err - } - - // Leave a seat for owner itself to append later, but if owner is an organization - // and just waste 1 unit is cheaper than re-allocate memory once. - users := make([]*User, 0, len(accesses)+1) - if len(accesses) > 0 { - userIDs := make([]int64, len(accesses)) - for i := 0; i < len(accesses); i++ { - userIDs[i] = accesses[i].UserID - } - - if err = e.In("id", userIDs).Find(&users); err != nil { - return nil, err - } - } - if !repo.Owner.IsOrganization() { - users = append(users, repo.Owner) - } - - 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 { - sanitize := func(s string) string { - return fmt.Sprintf(`%[1]s`, s) - } - return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize)) -} - -// LocalCopyPath returns the local repository copy path. -func LocalCopyPath() string { - if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { - return setting.Repository.Local.LocalCopyPath - } - return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) -} - -// LocalCopyPath returns the local repository copy path for the given repo. -func (repo *Repository) LocalCopyPath() string { - return path.Join(LocalCopyPath(), com.ToStr(repo.ID)) -} - -// UpdateLocalCopyBranch pulls latest changes of given branch from repoPath to localPath. -// It creates a new clone if local copy does not exist. -// This function checks out target branch by default, it is safe to assume subsequent -// operations are operating against target branch when caller has confidence for no race condition. -func UpdateLocalCopyBranch(repoPath, localPath, branch string) error { - if !com.IsExist(localPath) { - if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{ - Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, - Branch: branch, - }); err != nil { - return fmt.Errorf("git clone %s: %v", branch, err) - } - } else { - _, err := git.NewCommand("fetch", "origin").RunInDir(localPath) - if err != nil { - return fmt.Errorf("git fetch origin: %v", err) - } - if len(branch) > 0 { - if err := git.Checkout(localPath, git.CheckoutOptions{ - Branch: branch, - }); err != nil { - return fmt.Errorf("git checkout %s: %v", branch, err) - } - - if err := git.ResetHEAD(localPath, true, "origin/"+branch); err != nil { - return fmt.Errorf("git reset --hard origin/%s: %v", branch, err) - } - } - } - return nil -} - -// UpdateLocalCopyBranch makes sure local copy of repository in given branch is up-to-date. -func (repo *Repository) UpdateLocalCopyBranch(branch string) error { - return UpdateLocalCopyBranch(repo.RepoPath(), repo.LocalCopyPath(), branch) -} - -// PatchPath returns corresponding patch file path of repository by given issue ID. -func (repo *Repository) PatchPath(index int64) (string, error) { - return repo.patchPath(x, index) -} - -func (repo *Repository) patchPath(e Engine, index int64) (string, error) { - if err := repo.getOwner(e); err != nil { - return "", err - } - - return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil -} - -// SavePatch saves patch data to corresponding location by given issue ID. -func (repo *Repository) SavePatch(index int64, patch []byte) error { - return repo.savePatch(x, index, patch) -} - -func (repo *Repository) savePatch(e Engine, index int64, patch []byte) error { - patchPath, err := repo.patchPath(e, index) - if err != nil { - return fmt.Errorf("PatchPath: %v", err) - } - dir := filepath.Dir(patchPath) - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", dir, err) - } - - if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil { - return fmt.Errorf("WriteFile: %v", err) - } - - return nil -} - -func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { - has, err := e.Get(&Repository{ - OwnerID: u.ID, - LowerName: strings.ToLower(repoName), - }) - return has && com.IsDir(RepoPath(u.Name, repoName)), err -} - -// IsRepositoryExist returns true if the repository with given name under user has already existed. -func IsRepositoryExist(u *User, repoName string) (bool, error) { - return isRepositoryExist(x, u, repoName) -} - -// CloneLink represents different types of clone URLs of repository. -type CloneLink struct { - SSH string - HTTPS string - Git string -} - -// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name. -func ComposeHTTPSCloneURL(owner, repo string) string { - return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.QueryEscape(owner), url.QueryEscape(repo)) -} - -func (repo *Repository) cloneLink(e Engine, isWiki bool) *CloneLink { - repoName := repo.Name - if isWiki { - repoName += ".wiki" - } - - sshUser := setting.RunUser - if setting.SSH.StartBuiltinServer { - sshUser = setting.SSH.BuiltinServerUser - } - - repo.Owner = repo.mustOwner(e) - cl := new(CloneLink) - if setting.SSH.Port != 22 { - cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", sshUser, setting.SSH.Domain, setting.SSH.Port, repo.Owner.Name, repoName) - } else if setting.Repository.UseCompatSSHURI { - cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) - } else { - cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) - } - cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName) - return cl -} - -// CloneLink returns clone URLs of repository. -func (repo *Repository) CloneLink() (cl *CloneLink) { - return repo.cloneLink(x, false) -} - -// MigrateRepoOptions contains the repository migrate options -type MigrateRepoOptions struct { - Name string - Description string - IsPrivate bool - IsMirror bool - RemoteAddr string -} - -/* - GitHub, GitLab, Gogs: *.wiki.git - BitBucket: *.git/wiki -*/ -var commonWikiURLSuffixes = []string{".wiki.git", ".git/wiki"} - -// wikiRemoteURL returns accessible repository URL for wiki if exists. -// Otherwise, it returns an empty string. -func wikiRemoteURL(remote string) string { - remote = strings.TrimSuffix(remote, ".git") - for _, suffix := range commonWikiURLSuffixes { - wikiURL := remote + suffix - if git.IsRepoURLAccessible(wikiURL) { - return wikiURL - } - } - return "" -} - -// MigrateRepository migrates a existing repository from other project hosting. -func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, error) { - repo, err := CreateRepository(doer, u, CreateRepoOptions{ - Name: opts.Name, - Description: opts.Description, - IsPrivate: opts.IsPrivate, - IsMirror: opts.IsMirror, - }) - if err != nil { - return nil, err - } - - repoPath := RepoPath(u.Name, opts.Name) - wikiPath := WikiPath(u.Name, opts.Name) - - if u.IsOrganization() { - t, err := u.GetOwnerTeam() - if err != nil { - return nil, err - } - repo.NumWatches = t.NumMembers - } else { - repo.NumWatches = 1 - } - - migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second - - if err := os.RemoveAll(repoPath); err != nil { - return repo, fmt.Errorf("Failed to remove %s: %v", repoPath, err) - } - - if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{ - Mirror: true, - Quiet: true, - Timeout: migrateTimeout, - }); err != nil { - return repo, fmt.Errorf("Clone: %v", err) - } - - wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) - if len(wikiRemotePath) > 0 { - if err := os.RemoveAll(wikiPath); err != nil { - return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) - } - - if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ - Mirror: true, - Quiet: true, - Timeout: migrateTimeout, - Branch: "master", - }); err != nil { - log.Warn("Clone wiki: %v", err) - if err := os.RemoveAll(wikiPath); err != nil { - return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) - } - } - } - - // Check if repository is empty. - _, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1") - 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) - } - } - - if !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) - } - if headBranch != nil { - repo.DefaultBranch = headBranch.Name - } - - if err = SyncReleasesWithTags(repo, gitRepo); err != nil { - log.Error(4, "Failed to synchronize tags to releases for repository: %v", err) - } - } - - if err = repo.UpdateSize(); err != nil { - log.Error(4, "Failed to update size for repository: %v", err) - } - - if opts.IsMirror { - if _, err = x.InsertOne(&Mirror{ - RepoID: repo.ID, - Interval: setting.Mirror.DefaultInterval, - EnablePrune: true, - NextUpdateUnix: util.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval), - }); err != nil { - return repo, fmt.Errorf("InsertOne: %v", err) - } - - repo.IsMirror = true - err = UpdateRepository(repo, false) - } else { - repo, err = CleanUpMigrateInfo(repo) - } - - if err != nil && !repo.IsEmpty { - UpdateRepoIndexer(repo) - } - - return repo, err -} - -// cleanUpMigrateGitConfig removes mirror info which prevents "push --all". -// This also removes possible user credentials. -func cleanUpMigrateGitConfig(configPath string) error { - cfg, err := ini.Load(configPath) - if err != nil { - return fmt.Errorf("open config file: %v", err) - } - cfg.DeleteSection("remote \"origin\"") - if err = cfg.SaveToIndent(configPath, "\t"); err != nil { - return fmt.Errorf("save config file: %v", err) - } - return nil -} - -// createDelegateHooks creates all the hooks scripts for the repo -func createDelegateHooks(repoPath string) (err error) { - var ( - hookNames = []string{"pre-receive", "update", "post-receive"} - hookTpls = []string{ - fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - } - giteaHookTpls = []string{ - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - } - ) - - hookDir := filepath.Join(repoPath, "hooks") - - for i, hookName := range hookNames { - oldHookPath := filepath.Join(hookDir, hookName) - newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") - - if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { - return fmt.Errorf("create hooks dir '%s': %v", filepath.Join(hookDir, hookName+".d"), err) - } - - // WARNING: This will override all old server-side hooks - if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { - return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err) - } - - if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil { - return fmt.Errorf("write new hook file '%s': %v", newHookPath, err) - } - } - - return nil -} - -// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. -func CleanUpMigrateInfo(repo *Repository) (*Repository, error) { - repoPath := repo.RepoPath() - if err := createDelegateHooks(repoPath); err != nil { - return repo, fmt.Errorf("createDelegateHooks: %v", err) - } - if repo.HasWiki() { - if err := createDelegateHooks(repo.WikiPath()); err != nil { - return repo, fmt.Errorf("createDelegateHooks.(wiki): %v", err) - } - } - - if err := cleanUpMigrateGitConfig(repo.GitConfigPath()); err != nil { - return repo, fmt.Errorf("cleanUpMigrateGitConfig: %v", err) - } - if repo.HasWiki() { - if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil { - return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %v", err) - } - } - - return repo, UpdateRepository(repo, false) -} - -// initRepoCommit temporarily changes with work directory. -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 { - 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), - "-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 { - return fmt.Errorf("git push: %s", stderr) - } - return nil -} - -// CreateRepoOptions contains the create repository options -type CreateRepoOptions struct { - Name string - Description string - Gitignores string - IssueLabels string - License string - Readme string - IsPrivate bool - IsMirror bool - AutoInit bool -} - -func getRepoInitFile(tp, name string) ([]byte, error) { - cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") - relPath := path.Join("options", tp, cleanedName) - - // Use custom file when available. - customPath := path.Join(setting.CustomPath, relPath) - if com.IsFile(customPath) { - return ioutil.ReadFile(customPath) - } - - switch tp { - case "readme": - return options.Readme(cleanedName) - case "gitignore": - return options.Gitignore(cleanedName) - case "license": - return options.License(cleanedName) - case "label": - return options.Labels(cleanedName) - default: - return []byte{}, fmt.Errorf("Invalid init file type") - } -} - -func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { - // 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, - ) - if err != nil { - return fmt.Errorf("git clone: %v - %s", err, stderr) - } - - // README - data, err := getRepoInitFile("readme", opts.Readme) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err) - } - - cloneLink := repo.cloneLink(e, false) - match := map[string]string{ - "Name": repo.Name, - "Description": repo.Description, - "CloneURL.SSH": cloneLink.SSH, - "CloneURL.HTTPS": cloneLink.HTTPS, - } - if err = ioutil.WriteFile(filepath.Join(tmpDir, "README.md"), - []byte(com.Expand(string(data), match)), 0644); err != nil { - return fmt.Errorf("write README.md: %v", err) - } - - // .gitignore - if len(opts.Gitignores) > 0 { - var buf bytes.Buffer - names := strings.Split(opts.Gitignores, ",") - for _, name := range names { - data, err = getRepoInitFile("gitignore", name) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", name, err) - } - buf.WriteString("# ---> " + name + "\n") - buf.Write(data) - buf.WriteString("\n") - } - - if buf.Len() > 0 { - if err = ioutil.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { - return fmt.Errorf("write .gitignore: %v", err) - } - } - } - - // Issue Labels - if len(opts.IssueLabels) > 0 { - list, err := GetLabelTemplateFile(opts.IssueLabels) - if err != nil { - return fmt.Errorf("GetLabelTemplateFile: %v", err) - } - - labels := make([]*Label, len(list)) - for i := 0; i < len(list); i++ { - labels[i] = &Label{ - RepoID: repo.ID, - Name: list[i][0], - Description: list[i][2], - Color: list[i][1], - } - } - for _, label := range labels { - if err = newLabel(e, label); err != nil { - return fmt.Errorf("newLabel: %v", err) - } - } - - } - - // LICENSE - if len(opts.License) > 0 { - data, err = getRepoInitFile("license", opts.License) - if err != nil { - return fmt.Errorf("getRepoInitFile[%s]: %v", opts.License, err) - } - - if err = ioutil.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { - return fmt.Errorf("write LICENSE: %v", err) - } - } - - return nil -} - -// InitRepository initializes README and .gitignore if needed. -func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) { - // Somehow the directory could exist. - if com.IsExist(repoPath) { - return fmt.Errorf("initRepository: path already exists: %s", repoPath) - } - - // Init git bare new repository. - if err = git.InitRepository(repoPath, true); err != nil { - return fmt.Errorf("InitRepository: %v", err) - } else if err = createDelegateHooks(repoPath); err != nil { - return fmt.Errorf("createDelegateHooks: %v", err) - } - - tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) - - // Initialize repository according to user's choice. - if opts.AutoInit { - - if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", tmpDir, err) - } - - defer os.RemoveAll(tmpDir) - - if err = prepareRepoCommit(e, repo, tmpDir, repoPath, opts); err != nil { - return fmt.Errorf("prepareRepoCommit: %v", err) - } - - // Apply changes and commit. - if err = initRepoCommit(tmpDir, u.NewGitSig()); err != nil { - return fmt.Errorf("initRepoCommit: %v", err) - } - } - - // Re-fetch the repository from database before updating it (else it would - // override changes that were done earlier with sql) - if repo, err = getRepositoryByID(e, repo.ID); err != nil { - return fmt.Errorf("getRepositoryByID: %v", err) - } - - if !opts.AutoInit { - repo.IsEmpty = true - } - - repo.DefaultBranch = "master" - if err = updateRepository(e, repo, false); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return nil -} - -var ( - reservedRepoNames = []string{".", ".."} - reservedRepoPatterns = []string{"*.git", "*.wiki"} -) - -// IsUsableRepoName returns true when repository is usable -func IsUsableRepoName(name string) error { - return isUsableName(reservedRepoNames, reservedRepoPatterns, name) -} - -func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err error) { - if err = IsUsableRepoName(repo.Name); err != nil { - return err - } - - has, err := isRepositoryExist(e, u, repo.Name) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{u.Name, repo.Name} - } - - if _, err = e.Insert(repo); err != nil { - return err - } - if err = deleteRepoRedirect(e, u.ID, repo.Name); err != nil { - return err - } - - // insert units for repo - var units = make([]RepoUnit, 0, len(defaultRepoUnits)) - for _, tp := range defaultRepoUnits { - if tp == UnitTypeIssues { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - Config: &IssuesConfig{ - EnableTimetracker: setting.Service.DefaultEnableTimetracking, - AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime, - EnableDependencies: setting.Service.DefaultEnableDependencies, - }, - }) - } else if tp == UnitTypePullRequests { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, - }) - } else { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - }) - } - - } - - if _, err = e.Insert(&units); err != nil { - return err - } - - u.NumRepos++ - // Remember visibility preference. - u.LastRepoVisibility = repo.IsPrivate - if err = updateUser(e, u); err != nil { - return fmt.Errorf("updateUser: %v", err) - } - - // Give access to all members in owner team. - if u.IsOrganization() { - t, err := u.getOwnerTeam(e) - if err != nil { - return fmt.Errorf("getOwnerTeam: %v", err) - } else if err = t.addRepository(e, repo); err != nil { - return fmt.Errorf("addRepository: %v", err) - } else if err = prepareWebhooks(e, repo, HookEventRepository, &api.RepositoryPayload{ - Action: api.HookRepoCreated, - Repository: repo.innerAPIFormat(e, AccessModeOwner, false), - Organization: u.APIFormat(), - Sender: doer.APIFormat(), - }); err != nil { - return fmt.Errorf("prepareWebhooks: %v", err) - } - go HookQueue.Add(repo.ID) - } else { - // Organization automatically called this in addRepository method. - if err = repo.recalculateAccesses(e); err != nil { - return fmt.Errorf("recalculateAccesses: %v", err) - } - } - - if setting.Service.AutoWatchNewRepos { - if err = watchRepo(e, doer.ID, repo.ID, true); err != nil { - return fmt.Errorf("watchRepo: %v", err) - } - } - if err = newRepoAction(e, doer, repo); err != nil { - return fmt.Errorf("newRepoAction: %v", err) - } - - return nil -} - -// CreateRepository creates a repository for the user/organization. -func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err error) { - if !doer.IsAdmin && !u.CanCreateRepo() { - return nil, ErrReachLimitOfRepo{u.MaxRepoCreation} - } - - repo := &Repository{ - OwnerID: u.ID, - Owner: u, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - IsPrivate: opts.IsPrivate, - IsFsckEnabled: !opts.IsMirror, - CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, u, repo); err != nil { - return nil, err - } - - // No need for init mirror. - if !opts.IsMirror { - repoPath := RepoPath(u.Name, repo.Name) - if err = initRepository(sess, repoPath, u, repo, opts); err != nil { - if err2 := os.RemoveAll(repoPath); err2 != nil { - log.Error(4, "initRepository: %v", err) - return nil, fmt.Errorf( - "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) - } - return nil, fmt.Errorf("initRepository: %v", err) - } - - _, stderr, err := process.GetManager().ExecDir(-1, - repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath), - "git", "update-server-info") - if err != nil { - return nil, errors.New("CreateRepository(git update-server-info): " + stderr) - } - } - - return repo, sess.Commit() -} - -func countRepositories(userID int64, private bool) int64 { - sess := x.Where("id > 0") - - if userID > 0 { - sess.And("owner_id = ?", userID) - } - if !private { - sess.And("is_private=?", false) - } - - count, err := sess.Count(new(Repository)) - if err != nil { - log.Error(4, "countRepositories: %v", err) - } - return count -} - -// CountRepositories returns number of repositories. -// Argument private only takes effect when it is false, -// set it true to count all repositories. -func CountRepositories(private bool) int64 { - return countRepositories(-1, private) -} - -// CountUserRepositories returns number of repositories user owns. -// Argument private only takes effect when it is false, -// set it true to count all repositories. -func CountUserRepositories(userID int64, private bool) int64 { - return countRepositories(userID, private) -} - -// RepoPath returns repository path by given user and repository name. -func RepoPath(userName, repoName string) string { - return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") -} - -// TransferOwnership transfers all corresponding setting from old user to new one. -func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error { - newOwner, err := GetUserByName(newOwnerName) - if err != nil { - return fmt.Errorf("get new owner '%s': %v", newOwnerName, err) - } - - // Check if new owner has repository with same name. - has, err := IsRepositoryExist(newOwner, repo.Name) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{newOwnerName, repo.Name} - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return fmt.Errorf("sess.Begin: %v", err) - } - - owner := repo.Owner - - // Note: we have to set value here to make sure recalculate accesses is based on - // new owner. - repo.OwnerID = newOwner.ID - repo.Owner = newOwner - - // Update repository. - if _, err := sess.ID(repo.ID).Update(repo); err != nil { - return fmt.Errorf("update owner: %v", err) - } - - // Remove redundant collaborators. - collaborators, err := repo.getCollaborators(sess) - if err != nil { - return fmt.Errorf("getCollaborators: %v", err) - } - - // Dummy object. - collaboration := &Collaboration{RepoID: repo.ID} - for _, c := range collaborators { - if c.ID != newOwner.ID { - isMember, err := isOrganizationMember(sess, newOwner.ID, c.ID) - if err != nil { - return fmt.Errorf("IsOrgMember: %v", err) - } else if !isMember { - continue - } - } - collaboration.UserID = c.ID - if _, err = sess.Delete(collaboration); err != nil { - return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) - } - } - - // Remove old team-repository relations. - if owner.IsOrganization() { - if err = owner.removeOrgRepo(sess, repo.ID); err != nil { - return fmt.Errorf("removeOrgRepo: %v", err) - } - } - - if newOwner.IsOrganization() { - t, err := newOwner.getOwnerTeam(sess) - if err != nil { - return fmt.Errorf("getOwnerTeam: %v", err) - } else if err = t.addRepository(sess, repo); err != nil { - return fmt.Errorf("add to owner team: %v", err) - } - } else { - // Organization called this in addRepository method. - if err = repo.recalculateAccesses(sess); err != nil { - return fmt.Errorf("recalculateAccesses: %v", err) - } - } - - // Update repository count. - if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil { - return fmt.Errorf("increase new owner repository count: %v", err) - } else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.ID); err != nil { - return fmt.Errorf("decrease old owner repository count: %v", err) - } - - if err = watchRepo(sess, doer.ID, repo.ID, true); err != nil { - return fmt.Errorf("watchRepo: %v", err) - } else if err = transferRepoAction(sess, doer, owner, repo); err != nil { - return fmt.Errorf("transferRepoAction: %v", err) - } - - // Rename remote repository to new path and delete local copy. - dir := UserPath(newOwner.Name) - - if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", dir, err) - } - - if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { - return fmt.Errorf("rename repository directory: %v", err) - } - removeAllWithNotice(sess, "Delete repository local copy", repo.LocalCopyPath()) - - // Rename remote wiki repository to new path and delete local copy. - wikiPath := WikiPath(owner.Name, repo.Name) - if com.IsExist(wikiPath) { - removeAllWithNotice(sess, "Delete repository wiki local copy", repo.LocalWikiPath()) - if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { - return fmt.Errorf("rename repository wiki: %v", err) - } - } - - return sess.Commit() -} - -// ChangeRepositoryName changes all corresponding setting from old repository name to new one. -func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) { - oldRepoName = strings.ToLower(oldRepoName) - newRepoName = strings.ToLower(newRepoName) - if err = IsUsableRepoName(newRepoName); err != nil { - return err - } - - has, err := IsRepositoryExist(u, newRepoName) - if err != nil { - return fmt.Errorf("IsRepositoryExist: %v", err) - } else if has { - return ErrRepoAlreadyExist{u.Name, newRepoName} - } - - repo, err := GetRepositoryByName(u.ID, oldRepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName: %v", err) - } - - // Change repository directory name. We must lock the local copy of the - // repo so that we can atomically rename the repo path and updates the - // local copy's origin accordingly. - repoWorkingPool.CheckIn(com.ToStr(repo.ID)) - defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - - newRepoPath := RepoPath(u.Name, newRepoName) - if err = os.Rename(repo.RepoPath(), newRepoPath); err != nil { - return fmt.Errorf("rename repository directory: %v", err) - } - - localPath := repo.LocalCopyPath() - if com.IsExist(localPath) { - _, err := git.NewCommand("remote", "set-url", "origin", newRepoPath).RunInDir(localPath) - if err != nil { - return fmt.Errorf("git remote set-url origin %s: %v", newRepoPath, err) - } - } - - wikiPath := repo.WikiPath() - if com.IsExist(wikiPath) { - if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { - return fmt.Errorf("rename repository wiki: %v", err) - } - RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) - } - - return nil -} - -func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - return repos, e. - Where("fork_id=?", forkID). - Find(&repos) -} - -// GetRepositoriesByForkID returns all repositories with given fork ID. -func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) { - return getRepositoriesByForkID(x, forkID) -} - -func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) { - repo.LowerName = strings.ToLower(repo.Name) - - if len(repo.Description) > 255 { - repo.Description = repo.Description[:255] - } - if len(repo.Website) > 255 { - repo.Website = repo.Website[:255] - } - - if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { - return fmt.Errorf("update: %v", err) - } - - if visibilityChanged { - if err = repo.getOwner(e); err != nil { - return fmt.Errorf("getOwner: %v", err) - } - if repo.Owner.IsOrganization() { - // Organization repository need to recalculate access table when visibility is changed. - if err = repo.recalculateTeamAccesses(e, 0); err != nil { - return fmt.Errorf("recalculateTeamAccesses: %v", err) - } - } - - // If repo has become private, we need to set its actions to private. - if repo.IsPrivate { - _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{ - IsPrivate: true, - }) - if err != nil { - return err - } - } - - // Create/Remove git-daemon-export-ok for git-daemon... - daemonExportFile := path.Join(repo.repoPath(e), `git-daemon-export-ok`) - if repo.IsPrivate && com.IsExist(daemonExportFile) { - if err = os.Remove(daemonExportFile); err != nil { - log.Error(4, "Failed to remove %s: %v", daemonExportFile, err) - } - } else if !repo.IsPrivate && !com.IsExist(daemonExportFile) { - if f, err := os.Create(daemonExportFile); err != nil { - log.Error(4, "Failed to create %s: %v", daemonExportFile, err) - } else { - f.Close() - } - } - - forkRepos, err := getRepositoriesByForkID(e, repo.ID) - if err != nil { - return fmt.Errorf("getRepositoriesByForkID: %v", err) - } - for i := range forkRepos { - forkRepos[i].IsPrivate = repo.IsPrivate - if err = updateRepository(e, forkRepos[i], true); err != nil { - return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) - } - } - - if err = repo.updateSize(e); err != nil { - log.Error(4, "Failed to update size for repository: %v", err) - } - } - - return nil -} - -// UpdateRepository updates a repository -func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = updateRepository(sess, repo, visibilityChanged); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return sess.Commit() -} - -// UpdateRepositoryUnits updates a repository's units -func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Where("repo_id = ?", repo.ID).Delete(new(RepoUnit)); err != nil { - return err - } - - if _, err = sess.Insert(units); err != nil { - return err - } - - return sess.Commit() -} - -// DeleteRepository deletes a repository for a user or organization. -func DeleteRepository(doer *User, uid, repoID int64) error { - // In case is a organization. - org, err := GetUserByID(uid) - if err != nil { - return err - } - if org.IsOrganization() { - if err = org.GetTeams(); err != nil { - return err - } - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - repo := &Repository{ID: repoID, OwnerID: uid} - has, err := sess.Get(repo) - if err != nil { - return err - } else if !has { - return ErrRepoNotExist{repoID, uid, "", ""} - } - - // Delete Deploy Keys - deployKeys, err := listDeployKeys(sess, repo.ID) - if err != nil { - return fmt.Errorf("listDeployKeys: %v", err) - } - for _, dKey := range deployKeys { - if err := deleteDeployKey(sess, doer, dKey.ID); err != nil { - return fmt.Errorf("deleteDeployKeys: %v", err) - } - } - - if cnt, err := sess.ID(repoID).Delete(&Repository{}); err != nil { - return err - } else if cnt != 1 { - return ErrRepoNotExist{repoID, uid, "", ""} - } - - if org.IsOrganization() { - for _, t := range org.Teams { - if !t.hasRepository(sess, repoID) { - continue - } else if err = t.removeRepository(sess, repo, false); err != nil { - return err - } - } - } - - if err = deleteBeans(sess, - &Access{RepoID: repo.ID}, - &Action{RepoID: repo.ID}, - &Watch{RepoID: repoID}, - &Star{RepoID: repoID}, - &Mirror{RepoID: repoID}, - &Milestone{RepoID: repoID}, - &Release{RepoID: repoID}, - &Collaboration{RepoID: repoID}, - &PullRequest{BaseRepoID: repoID}, - &RepoUnit{RepoID: repoID}, - &RepoRedirect{RedirectRepoID: repoID}, - &Webhook{RepoID: repoID}, - &HookTask{RepoID: repoID}, - &Notification{RepoID: repoID}, - &CommitStatus{RepoID: repoID}, - ); err != nil { - return fmt.Errorf("deleteBeans: %v", err) - } - - deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repoID}) - // Delete comments and attachments - if _, err = sess.In("issue_id", deleteCond). - Delete(&Comment{}); err != nil { - return err - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueUser{}); err != nil { - return err - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&Reaction{}); err != nil { - return err - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueWatch{}); err != nil { - return err - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&Stopwatch{}); err != nil { - return err - } - - attachmentPaths := make([]string, 0, 20) - attachments := make([]*Attachment, 0, len(attachmentPaths)) - if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id"). - Where("issue.repo_id = ?", repoID). - Find(&attachments); err != nil { - return err - } - for j := range attachments { - attachmentPaths = append(attachmentPaths, attachments[j].LocalPath()) - } - - if _, err = sess.In("issue_id", deleteCond). - Delete(&Attachment{}); err != nil { - return err - } - - if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil { - return err - } - - if _, err = sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil { - return err - } - - if repo.IsFork { - if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { - return fmt.Errorf("decrease fork count: %v", err) - } - } - - if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { - return err - } - - // FIXME: Remove repository files should be executed after transaction succeed. - repoPath := repo.repoPath(sess) - removeAllWithNotice(sess, "Delete repository files", repoPath) - - repo.deleteWiki(sess) - - // Remove attachment files. - for i := range attachmentPaths { - removeAllWithNotice(sess, "Delete attachment", attachmentPaths[i]) - } - - // Remove LFS objects - var lfsObjects []*LFSMetaObject - if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { - return err - } - - for _, v := range lfsObjects { - count, err := sess.Count(&LFSMetaObject{Oid: v.Oid}) - if err != nil { - return err - } - - if count > 1 { - continue - } - - 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 - } - } - - if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { - return err - } - - if repo.NumForks > 0 { - if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { - log.Error(4, "reset 'fork_id' and 'is_fork': %v", err) - } - } - - if err = sess.Commit(); err != nil { - if len(deployKeys) > 0 { - // We need to rewrite the public keys because the commit failed - if err2 := RewriteAllPublicKeys(); err2 != nil { - return fmt.Errorf("Commit: %v SSH Keys: %v", err, err2) - } - } - return fmt.Errorf("Commit: %v", err) - } - - if org.IsOrganization() { - if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{ - Action: api.HookRepoDeleted, - Repository: repo.APIFormat(AccessModeOwner), - Organization: org.APIFormat(), - Sender: doer.APIFormat(), - }); err != nil { - return err - } - go HookQueue.Add(repo.ID) - } - - DeleteRepoFromIndexer(repo) - return nil -} - -// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame. -func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { - var repo Repository - has, err := x.Select("repository.*"). - Join("INNER", "`user`", "`user`.id = repository.owner_id"). - Where("repository.lower_name = ?", strings.ToLower(repoName)). - And("`user`.lower_name = ?", strings.ToLower(ownerName)). - Get(&repo) - if err != nil { - return nil, err - } else if !has { - return nil, ErrRepoNotExist{0, 0, ownerName, repoName} - } - return &repo, nil -} - -// GetRepositoryByName returns the repository by given name under user if exists. -func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { - repo := &Repository{ - OwnerID: ownerID, - LowerName: strings.ToLower(name), - } - has, err := x.Get(repo) - if err != nil { - return nil, err - } else if !has { - return nil, ErrRepoNotExist{0, ownerID, "", name} - } - return repo, err -} - -func getRepositoryByID(e Engine, id int64) (*Repository, error) { - repo := new(Repository) - has, err := e.ID(id).Get(repo) - if err != nil { - return nil, err - } else if !has { - return nil, ErrRepoNotExist{id, 0, "", ""} - } - return repo, nil -} - -// GetRepositoryByID returns the repository by given id if exists. -func GetRepositoryByID(id int64) (*Repository, error) { - return getRepositoryByID(x, id) -} - -// GetRepositoriesMapByIDs returns the repositories by given id slice. -func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { - var repos = make(map[int64]*Repository, len(ids)) - return repos, x.In("id", ids).Find(&repos) -} - -// GetUserRepositories returns a list of repositories of given user. -func GetUserRepositories(userID int64, private bool, page, pageSize int, orderBy string) ([]*Repository, error) { - if len(orderBy) == 0 { - orderBy = "updated_unix DESC" - } - - sess := x. - Where("owner_id = ?", userID). - OrderBy(orderBy) - if !private { - sess.And("is_private=?", false) - } - - if page <= 0 { - page = 1 - } - sess.Limit(pageSize, (page-1)*pageSize) - - repos := make([]*Repository, 0, pageSize) - return repos, sess.Find(&repos) -} - -// GetUserMirrorRepositories returns a list of mirror repositories of given user. -func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - return repos, x. - Where("owner_id = ?", userID). - And("is_mirror = ?", true). - Find(&repos) -} - -func getRepositoryCount(e Engine, u *User) (int64, error) { - return e.Count(&Repository{OwnerID: u.ID}) -} - -func getPublicRepositoryCount(e Engine, u *User) (int64, error) { - return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID}) -} - -func getPrivateRepositoryCount(e Engine, u *User) (int64, error) { - return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID}) -} - -// GetRepositoryCount returns the total number of repositories of user. -func GetRepositoryCount(u *User) (int64, error) { - return getRepositoryCount(x, u) -} - -// GetPublicRepositoryCount returns the total number of public repositories of user. -func GetPublicRepositoryCount(u *User) (int64, error) { - return getPublicRepositoryCount(x, u) -} - -// GetPrivateRepositoryCount returns the total number of private repositories of user. -func GetPrivateRepositoryCount(u *User) (int64, error) { - return getPrivateRepositoryCount(x, u) -} - -// DeleteRepositoryArchives deletes all repositories' archives. -func DeleteRepositoryArchives() error { - return x. - Where("id > 0"). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives")) - }) -} - -// DeleteOldRepositoryArchives deletes old repository archives. -func DeleteOldRepositoryArchives() { - if !taskStatusTable.StartIfNotRunning(archiveCleanup) { - return - } - defer taskStatusTable.Stop(archiveCleanup) - - log.Trace("Doing: ArchiveCleanup") - - if err := x.Where("id > 0").Iterate(new(Repository), deleteOldRepositoryArchives); err != nil { - log.Error(4, "ArchiveClean: %v", err) - } -} - -func deleteOldRepositoryArchives(idx int, bean interface{}) error { - repo := bean.(*Repository) - basePath := filepath.Join(repo.RepoPath(), "archives") - - for _, ty := range []string{"zip", "targz"} { - path := filepath.Join(basePath, ty) - file, err := os.Open(path) - if err != nil { - if !os.IsNotExist(err) { - log.Warn("Unable to open directory %s: %v", path, err) - return err - } - - // If the directory doesn't exist, that's okay. - continue - } - - files, err := file.Readdir(0) - file.Close() - if err != nil { - log.Warn("Unable to read directory %s: %v", path, err) - return err - } - - minimumOldestTime := time.Now().Add(-setting.Cron.ArchiveCleanup.OlderThan) - for _, info := range files { - if info.ModTime().Before(minimumOldestTime) && !info.IsDir() { - toDelete := filepath.Join(path, info.Name()) - // This is a best-effort purge, so we do not check error codes to confirm removal. - if err = os.Remove(toDelete); err != nil { - log.Trace("Unable to delete %s, but proceeding: %v", toDelete, err) - } - } - } - } - - return nil -} - -func gatherMissingRepoRecords() ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - if err := x. - Where("id > 0"). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if !com.IsDir(repo.RepoPath()) { - repos = append(repos, repo) - } - return nil - }); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { - return nil, fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - return repos, nil -} - -// DeleteMissingRepositories deletes all repository records that lost Git files. -func DeleteMissingRepositories(doer *User) error { - repos, err := gatherMissingRepoRecords() - if err != nil { - return fmt.Errorf("gatherMissingRepoRecords: %v", err) - } - - if len(repos) == 0 { - return nil - } - - for _, repo := range repos { - log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) - if err := DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil { - return fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - } - return nil -} - -// ReinitMissingRepositories reinitializes all repository records that lost Git files. -func ReinitMissingRepositories() error { - repos, err := gatherMissingRepoRecords() - if err != nil { - return fmt.Errorf("gatherMissingRepoRecords: %v", err) - } - - if len(repos) == 0 { - return nil - } - - for _, repo := range repos { - log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) - if err := git.InitRepository(repo.RepoPath(), true); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil { - return fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - } - return nil -} - -// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks -// to make sure the binary and custom conf path are up-to-date. -func SyncRepositoryHooks() error { - return x.Cols("owner_id", "name").Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil { - return fmt.Errorf("SyncRepositoryHook: %v", err) - } - if bean.(*Repository).HasWiki() { - if err := createDelegateHooks(bean.(*Repository).WikiPath()); err != nil { - return fmt.Errorf("SyncRepositoryHook: %v", err) - } - } - return nil - }) -} - -// Prevent duplicate running tasks. -var taskStatusTable = sync.NewStatusTable() - -const ( - mirrorUpdate = "mirror_update" - gitFsck = "git_fsck" - checkRepos = "check_repos" - archiveCleanup = "archive_cleanup" -) - -// GitFsck calls 'git fsck' to check repository health. -func GitFsck() { - if !taskStatusTable.StartIfNotRunning(gitFsck) { - return - } - defer taskStatusTable.Stop(gitFsck) - - log.Trace("Doing: GitFsck") - - if err := x. - Where("id>0 AND is_fsck_enabled=?", true).BufferSize(setting.IterateBufferSize). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - repoPath := repo.RepoPath() - log.Trace("Running health check on repository %s", repoPath) - if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { - desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err) - log.Warn(desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error(4, "CreateRepositoryNotice: %v", err) - } - } - return nil - }); err != nil { - log.Error(4, "GitFsck: %v", err) - } - log.Trace("Finished: GitFsck") -} - -// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository -func GitGcRepos() error { - args := append([]string{"gc"}, setting.Git.GCArgs...) - return x. - Where("id > 0").BufferSize(setting.IterateBufferSize). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if err := repo.GetOwner(); err != nil { - return err - } - _, stderr, err := process.GetManager().ExecDir( - time.Duration(setting.Git.Timeout.GC)*time.Second, - RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection", - "git", args...) - if err != nil { - return fmt.Errorf("%v: %v", err, stderr) - } - return nil - }) -} - -type repoChecker struct { - querySQL, correctSQL string - desc string -} - -func repoStatsCheck(checker *repoChecker) { - results, err := x.Query(checker.querySQL) - if err != nil { - log.Error(4, "Select %s: %v", checker.desc, err) - return - } - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating %s: %d", checker.desc, id) - _, err = x.Exec(checker.correctSQL, id, id) - if err != nil { - log.Error(4, "Update %s[%d]: %v", checker.desc, id, err) - } - } -} - -// CheckRepoStats checks the repository stats -func CheckRepoStats() { - if !taskStatusTable.StartIfNotRunning(checkRepos) { - return - } - defer taskStatusTable.Stop(checkRepos) - - log.Trace("Doing: CheckRepoStats") - - checkers := []*repoChecker{ - // Repository.NumWatches - { - "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)", - "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?", - "repository count 'num_watches'", - }, - // Repository.NumStars - { - "SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)", - "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", - "repository count 'num_stars'", - }, - // Label.NumIssues - { - "SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)", - "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", - "label count 'num_issues'", - }, - // User.NumRepos - { - "SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)", - "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", - "user count 'num_repos'", - }, - // Issue.NumComments - { - "SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)", - "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", - "issue count 'num_comments'", - }, - } - for i := range checkers { - repoStatsCheck(checkers[i]) - } - - // ***** START: Repository.NumClosedIssues ***** - desc := "repository count 'num_closed_issues'" - results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false) - if err != nil { - log.Error(4, "Select %s: %v", desc, err) - } else { - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating %s: %d", desc, id) - _, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) - if err != nil { - log.Error(4, "Update %s[%d]: %v", desc, id, err) - } - } - } - // ***** END: Repository.NumClosedIssues ***** - - // FIXME: use checker when stop supporting old fork repo format. - // ***** START: Repository.NumForks ***** - results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") - if err != nil { - log.Error(4, "Select repository count 'num_forks': %v", err) - } else { - for _, result := range results { - id := com.StrTo(result["id"]).MustInt64() - log.Trace("Updating repository count 'num_forks': %d", id) - - repo, err := GetRepositoryByID(id) - if err != nil { - log.Error(4, "GetRepositoryByID[%d]: %v", id, err) - continue - } - - rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) - if err != nil { - log.Error(4, "Select count of forks[%d]: %v", repo.ID, err) - continue - } - repo.NumForks = int(parseCountResult(rawResult)) - - if err = UpdateRepository(repo, false); err != nil { - log.Error(4, "UpdateRepository[%d]: %v", id, err) - continue - } - } - } - // ***** END: Repository.NumForks ***** -} - -// SetArchiveRepoState sets if a repo is archived -func (repo *Repository) SetArchiveRepoState(isArchived bool) (err error) { - repo.IsArchived = isArchived - _, err = x.Where("id = ?", repo.ID).Cols("is_archived").Update(repo) - return -} - -// ___________ __ -// \_ _____/__________| | __ -// | __)/ _ \_ __ \ |/ / -// | \( <_> ) | \/ < -// \___ / \____/|__| |__|_ \ -// \/ \/ - -// HasForkedRepo checks if given user has already forked a repository with given ID. -func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { - repo := new(Repository) - has, _ := x. - Where("owner_id=? AND fork_id=?", ownerID, repoID). - Get(repo) - return repo, has -} - -// ForkRepository forks a repository -func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) { - forkedRepo, err := oldRepo.GetUserFork(u.ID) - if err != nil { - return nil, err - } - if forkedRepo != nil { - return nil, ErrRepoAlreadyExist{ - Uname: u.Name, - Name: forkedRepo.Name, - } - } - - repo := &Repository{ - OwnerID: u.ID, - Owner: u, - Name: name, - LowerName: strings.ToLower(name), - Description: desc, - DefaultBranch: oldRepo.DefaultBranch, - IsPrivate: oldRepo.IsPrivate, - IsFork: true, - ForkID: oldRepo.ID, - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(sess, doer, u, repo); err != nil { - return nil, err - } - - if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil { - return nil, err - } - - 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) - 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") - if err != nil { - return nil, fmt.Errorf("git update-server-info: %v", stderr) - } - - if err = createDelegateHooks(repoPath); err != nil { - return nil, fmt.Errorf("createDelegateHooks: %v", err) - } - - //Commit repo to get Fork ID - err = sess.Commit() - if err != nil { - return nil, err - } - - oldMode, _ := AccessLevel(doer, oldRepo) - mode, _ := AccessLevel(doer, repo) - - if err = PrepareWebhooks(oldRepo, HookEventFork, &api.ForkPayload{ - Forkee: oldRepo.APIFormat(oldMode), - Repo: repo.APIFormat(mode), - Sender: doer.APIFormat(), - }); err != nil { - log.Error(2, "PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err) - } else { - go HookQueue.Add(oldRepo.ID) - } - - if err = repo.UpdateSize(); err != nil { - log.Error(4, "Failed to update size for repository: %v", err) - } - - // Copy LFS meta objects in new session - sess2 := x.NewSession() - defer sess2.Close() - if err = sess2.Begin(); err != nil { - return nil, err - } - - var lfsObjects []*LFSMetaObject - - if err = sess2.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { - return nil, err - } - - for _, v := range lfsObjects { - v.ID = 0 - v.RepositoryID = repo.ID - if _, err = sess2.Insert(v); err != nil { - return nil, err - } - } - - return repo, sess2.Commit() -} - -// GetForks returns all the forks of the repository -func (repo *Repository) GetForks() ([]*Repository, error) { - forks := make([]*Repository, 0, repo.NumForks) - return forks, x.Find(&forks, &Repository{ForkID: repo.ID}) -} - -// GetUserFork return user forked repository from this repository, if not forked return nil -func (repo *Repository) GetUserFork(userID int64) (*Repository, error) { - var forkedRepo Repository - has, err := x.Where("fork_id = ?", repo.ID).And("owner_id = ?", userID).Get(&forkedRepo) - if err != nil { - return nil, err - } - if !has { - return nil, nil - } - return &forkedRepo, nil -} +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea 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 models + +import ( + "bytes" + "errors" + "fmt" + "html/template" + "io/ioutil" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "sort" + "strings" + "time" + + "code.gitea.io/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/options" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/sync" + "code.gitea.io/gitea/modules/util" + api "code.gitea.io/sdk/gitea" + + "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" +) + +var repoWorkingPool = sync.NewExclusivePool() + +var ( + // ErrMirrorNotExist mirror does not exist error + ErrMirrorNotExist = errors.New("Mirror does not exist") + + // ErrNameEmpty name is empty error + ErrNameEmpty = errors.New("Name is empty") +) + +var ( + // Gitignores contains the gitiginore files + Gitignores []string + + // Licenses contains the license files + Licenses []string + + // Readmes contains the readme files + Readmes []string + + // LabelTemplates contains the label template files + LabelTemplates []string + + // ItemsPerPage maximum items per page in forks, watchers and stars of a repo + ItemsPerPage = 40 +) + +// 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) + for i, t := range types { + files, err := options.Dir(t) + if err != nil { + log.Fatal(4, "Failed to get %s files: %v", t, err) + } + customPath := path.Join(setting.CustomPath, "options", t) + if com.IsDir(customPath) { + customFiles, err := com.StatDir(customPath) + if err != nil { + log.Fatal(4, "Failed to get custom %s files: %v", t, err) + } + + for _, f := range customFiles { + if !com.IsSliceContainsStr(files, f) { + files = append(files, f) + } + } + } + typeFiles[i] = files + } + + Gitignores = typeFiles[0] + Licenses = typeFiles[1] + Readmes = typeFiles[2] + LabelTemplates = typeFiles[3] + sort.Strings(Gitignores) + sort.Strings(Licenses) + sort.Strings(Readmes) + sort.Strings(LabelTemplates) + + // Filter out invalid names and promote preferred licenses. + sortedLicenses := make([]string, 0, len(Licenses)) + for _, name := range setting.Repository.PreferredLicenses { + if com.IsSliceContainsStr(Licenses, name) { + sortedLicenses = append(sortedLicenses, name) + } + } + for _, name := range Licenses { + if !com.IsSliceContainsStr(setting.Repository.PreferredLicenses, name) { + sortedLicenses = append(sortedLicenses, name) + } + } + Licenses = sortedLicenses +} + +// NewRepoContext creates a new repository context +func NewRepoContext() { + zip.Verbose = false + + // Check Git installation. + if _, err := exec.LookPath("git"); err != nil { + log.Fatal(4, "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(4, "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(4, "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(4, "Failed to set git %s(%s): %s", configKey, gerr, stderr) + } + log.Info("Git config %s set to %s", configKey, defaultValue) + } else { + log.Fatal(4, "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(4, "Failed to execute 'git config --global core.quotepath false': %s", stderr) + } + + RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) +} + +// Repository represents a git repository. +type Repository struct { + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"UNIQUE(s)"` + OwnerName string `xorm:"-"` + Owner *User `xorm:"-"` + LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` + Name string `xorm:"INDEX NOT NULL"` + Description string + Website string + DefaultBranch string + + NumWatches int + NumStars int + NumForks int + NumIssues int + NumClosedIssues int + NumOpenIssues int `xorm:"-"` + NumPulls int + NumClosedPulls int + NumOpenPulls int `xorm:"-"` + NumMilestones int `xorm:"NOT NULL DEFAULT 0"` + NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` + NumOpenMilestones int `xorm:"-"` + NumReleases int `xorm:"-"` + + IsPrivate bool `xorm:"INDEX"` + IsEmpty bool `xorm:"INDEX"` + IsArchived bool `xorm:"INDEX"` + + IsMirror bool `xorm:"INDEX"` + *Mirror `xorm:"-"` + + ExternalMetas map[string]string `xorm:"-"` + Units []*RepoUnit `xorm:"-"` + + IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` + ForkID int64 `xorm:"INDEX"` + BaseRepo *Repository `xorm:"-"` + Size int64 `xorm:"NOT NULL DEFAULT 0"` + IndexerStatus *RepoIndexerStatus `xorm:"-"` + IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` + CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` + Topics []string `xorm:"TEXT JSON"` + + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` +} + +// AfterLoad is invoked from XORM after setting the values of all fields of this object. +func (repo *Repository) AfterLoad() { + // FIXME: use models migration to solve all at once. + if len(repo.DefaultBranch) == 0 { + repo.DefaultBranch = "master" + } + + repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues + repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls + repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones +} + +// MustOwner always returns a valid *User object to avoid +// conceptually impossible error handling. +// It creates a fake object that contains error details +// when error occurs. +func (repo *Repository) MustOwner() *User { + return repo.mustOwner(x) +} + +// MustOwnerName always returns valid owner name to avoid +// conceptually impossible error handling. +// It returns "error" and logs error details when error +// occurs. +func (repo *Repository) MustOwnerName() string { + return repo.mustOwnerName(x) +} + +// FullName returns the repository full name +func (repo *Repository) FullName() string { + return repo.MustOwnerName() + "/" + repo.Name +} + +// HTMLURL returns the repository HTML URL +func (repo *Repository) HTMLURL() string { + return setting.AppURL + repo.FullName() +} + +// APIURL returns the repository API URL +func (repo *Repository) APIURL() string { + return setting.AppURL + path.Join("api/v1/repos", repo.FullName()) +} + +// APIFormat converts a Repository to api.Repository +func (repo *Repository) APIFormat(mode AccessMode) *api.Repository { + return repo.innerAPIFormat(x, mode, false) +} + +// GetCommitsCountCacheKey returns cache key used for commits count caching. +func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool) string { + var prefix string + if isRef { + prefix = "ref" + } else { + prefix = "commit" + } + return fmt.Sprintf("commits-count-%d-%s-%s", repo.ID, prefix, contextName) +} + +func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) *api.Repository { + var parent *api.Repository + + cloneLink := repo.cloneLink(e, false) + permission := &api.Permission{ + Admin: mode >= AccessModeAdmin, + Push: mode >= AccessModeWrite, + Pull: mode >= AccessModeRead, + } + if !isParent { + err := repo.getBaseRepo(e) + if err != nil { + log.Error(4, "APIFormat: %v", err) + } + if repo.BaseRepo != nil { + parent = repo.BaseRepo.innerAPIFormat(e, mode, true) + } + } + 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, + } +} + +func (repo *Repository) getUnits(e Engine) (err error) { + if repo.Units != nil { + return nil + } + + repo.Units, err = getUnitsByRepoID(e, repo.ID) + return err +} + +// CheckUnitUser check whether user could visit the unit of this repository +func (repo *Repository) CheckUnitUser(userID int64, isAdmin bool, unitType UnitType) bool { + return repo.checkUnitUser(x, userID, isAdmin, unitType) +} + +func (repo *Repository) checkUnitUser(e Engine, userID int64, isAdmin bool, unitType UnitType) bool { + if isAdmin { + return true + } + user, err := getUserByID(e, userID) + if err != nil { + return false + } + perm, err := getUserRepoPermission(e, repo, user) + if err != nil { + return false + } + + return perm.CanRead(unitType) +} + +// UnitEnabled if this repository has the given unit enabled +func (repo *Repository) UnitEnabled(tp UnitType) bool { + if err := repo.getUnits(x); err != nil { + log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error()) + } + for _, unit := range repo.Units { + if unit.Type == tp { + return true + } + } + return false +} + +var ( + // ErrUnitNotExist organization does not exist + ErrUnitNotExist = errors.New("Unit does not exist") +) + +// MustGetUnit always returns a RepoUnit object +func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit { + ru, err := repo.GetUnit(tp) + if err == nil { + return ru + } + + if tp == UnitTypeExternalWiki { + return &RepoUnit{ + Type: tp, + Config: new(ExternalWikiConfig), + } + } else if tp == UnitTypeExternalTracker { + return &RepoUnit{ + Type: tp, + Config: new(ExternalTrackerConfig), + } + } else if tp == UnitTypePullRequests { + return &RepoUnit{ + Type: tp, + Config: new(PullRequestsConfig), + } + } + return &RepoUnit{ + Type: tp, + Config: new(UnitConfig), + } +} + +// GetUnit returns a RepoUnit object +func (repo *Repository) GetUnit(tp UnitType) (*RepoUnit, error) { + return repo.getUnit(x, tp) +} + +func (repo *Repository) getUnit(e Engine, tp UnitType) (*RepoUnit, error) { + if err := repo.getUnits(e); err != nil { + return nil, err + } + for _, unit := range repo.Units { + if unit.Type == tp { + return unit, nil + } + } + return nil, ErrUnitNotExist +} + +func (repo *Repository) getOwner(e Engine) (err error) { + if repo.Owner != nil { + return nil + } + + repo.Owner, err = getUserByID(e, repo.OwnerID) + return err +} + +// GetOwner returns the repository owner +func (repo *Repository) GetOwner() error { + return repo.getOwner(x) +} + +func (repo *Repository) mustOwner(e Engine) *User { + if err := repo.getOwner(e); err != nil { + return &User{ + Name: "error", + FullName: err.Error(), + } + } + + return repo.Owner +} + +func (repo *Repository) getOwnerName(e Engine) error { + if len(repo.OwnerName) > 0 { + return nil + } + + if repo.Owner != nil { + repo.OwnerName = repo.Owner.Name + return nil + } + + u := new(User) + has, err := e.ID(repo.OwnerID).Cols("name").Get(u) + if err != nil { + return err + } else if !has { + return ErrUserNotExist{repo.OwnerID, "", 0} + } + repo.OwnerName = u.Name + return nil +} + +// GetOwnerName returns the repository owner name +func (repo *Repository) GetOwnerName() error { + return repo.getOwnerName(x) +} + +func (repo *Repository) mustOwnerName(e Engine) string { + if err := repo.getOwnerName(e); err != nil { + log.Error(4, "Error loading repository owner name: %v", err) + return "error" + } + + return repo.OwnerName +} + +// ComposeMetas composes a map of metas for rendering external issue tracker URL. +func (repo *Repository) ComposeMetas() map[string]string { + unit, err := repo.GetUnit(UnitTypeExternalTracker) + if err != nil { + return nil + } + + if repo.ExternalMetas == nil { + repo.ExternalMetas = map[string]string{ + "format": unit.ExternalTrackerConfig().ExternalTrackerFormat, + "user": repo.MustOwner().Name, + "repo": repo.Name, + } + switch unit.ExternalTrackerConfig().ExternalTrackerStyle { + case markup.IssueNameStyleAlphanumeric: + repo.ExternalMetas["style"] = markup.IssueNameStyleAlphanumeric + default: + repo.ExternalMetas["style"] = markup.IssueNameStyleNumeric + } + + } + return repo.ExternalMetas +} + +// DeleteWiki removes the actual and local copy of repository wiki. +func (repo *Repository) DeleteWiki() error { + return repo.deleteWiki(x) +} + +func (repo *Repository) deleteWiki(e Engine) error { + wikiPaths := []string{repo.WikiPath(), repo.LocalWikiPath()} + for _, wikiPath := range wikiPaths { + removeAllWithNotice(e, "Delete repository wiki", wikiPath) + } + + _, err := e.Where("repo_id = ?", repo.ID).And("type = ?", UnitTypeWiki).Delete(new(RepoUnit)) + return err +} + +func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { + if err = repo.getOwner(e); err != nil { + return nil, err + } + + accesses := make([]*Access, 0, 10) + if err = e. + Where("repo_id = ? AND mode >= ?", repo.ID, AccessModeWrite). + Find(&accesses); err != nil { + return nil, err + } + + // Leave a seat for owner itself to append later, but if owner is an organization + // and just waste 1 unit is cheaper than re-allocate memory once. + users := make([]*User, 0, len(accesses)+1) + if len(accesses) > 0 { + userIDs := make([]int64, len(accesses)) + for i := 0; i < len(accesses); i++ { + userIDs[i] = accesses[i].UserID + } + + if err = e.In("id", userIDs).Find(&users); err != nil { + return nil, err + } + } + if !repo.Owner.IsOrganization() { + users = append(users, repo.Owner) + } + + return users, nil +} + +// GetAssignees returns all users that have write access and can be assigned to issues +// of the repository, +func (repo *Repository) GetAssignees() (_ []*User, err error) { + return repo.getAssignees(x) +} + +// GetMilestoneByID returns the milestone belongs to repository by given ID. +func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) { + return GetMilestoneByRepoID(repo.ID, milestoneID) +} + +// IssueStats returns number of open and closed repository issues by given filter mode. +func (repo *Repository) IssueStats(uid int64, filterMode int, isPull bool) (int64, int64) { + return GetRepoIssueStats(repo.ID, uid, filterMode, isPull) +} + +// GetMirror sets the repository mirror, returns an error upon failure +func (repo *Repository) GetMirror() (err error) { + repo.Mirror, err = GetMirrorByRepoID(repo.ID) + return err +} + +// GetBaseRepo populates repo.BaseRepo for a fork repository and +// returns an error on failure (NOTE: no error is returned for +// non-fork repositories, and BaseRepo will be left untouched) +func (repo *Repository) GetBaseRepo() (err error) { + return repo.getBaseRepo(x) +} + +func (repo *Repository) getBaseRepo(e Engine) (err error) { + if !repo.IsFork { + return nil + } + + repo.BaseRepo, err = getRepositoryByID(e, repo.ForkID) + return err +} + +func (repo *Repository) repoPath(e Engine) string { + return RepoPath(repo.mustOwnerName(e), repo.Name) +} + +// RepoPath returns the repository path +func (repo *Repository) RepoPath() string { + return repo.repoPath(x) +} + +// GitConfigPath returns the path to a repository's git config/ directory +func GitConfigPath(repoPath string) string { + return filepath.Join(repoPath, "config") +} + +// GitConfigPath returns the repository git config path +func (repo *Repository) GitConfigPath() string { + return GitConfigPath(repo.RepoPath()) +} + +// RelLink returns the repository relative link +func (repo *Repository) RelLink() string { + return "/" + repo.FullName() +} + +// Link returns the repository link +func (repo *Repository) Link() string { + return setting.AppSubURL + "/" + repo.FullName() +} + +// ComposeCompareURL returns the repository comparison URL +func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { + return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) +} + +// UpdateDefaultBranch updates the default branch +func (repo *Repository) UpdateDefaultBranch() error { + _, err := x.ID(repo.ID).Cols("default_branch").Update(repo) + return err +} + +// IsOwnedBy returns true when user owns this repository +func (repo *Repository) IsOwnedBy(userID int64) bool { + return repo.OwnerID == userID +} + +func (repo *Repository) updateSize(e Engine) error { + repoInfoSize, err := git.GetRepoSize(repo.repoPath(e)) + if err != nil { + return fmt.Errorf("UpdateSize: %v", err) + } + + repo.Size = repoInfoSize.Size + repoInfoSize.SizePack + _, err = e.ID(repo.ID).Cols("size").Update(repo) + return err +} + +// UpdateSize updates the repository size, calculating it using git.GetRepoSize +func (repo *Repository) UpdateSize() error { + return repo.updateSize(x) +} + +// CanUserFork returns true if specified user can fork repository. +func (repo *Repository) CanUserFork(user *User) (bool, error) { + if user == nil { + return false, nil + } + if repo.OwnerID != user.ID && !user.HasForkedRepo(repo.ID) { + return true, nil + } + if err := user.GetOwnedOrganizations(); err != nil { + return false, err + } + for _, org := range user.OwnedOrgs { + if repo.OwnerID != org.ID && !org.HasForkedRepo(repo.ID) { + return true, nil + } + } + return false, nil +} + +// CanEnablePulls returns true if repository meets the requirements of accepting pulls. +func (repo *Repository) CanEnablePulls() bool { + return !repo.IsMirror && !repo.IsEmpty +} + +// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled. +func (repo *Repository) AllowsPulls() bool { + return repo.CanEnablePulls() && repo.UnitEnabled(UnitTypePullRequests) +} + +// CanEnableEditor returns true if repository meets the requirements of web editor. +func (repo *Repository) CanEnableEditor() bool { + return !repo.IsMirror +} + +// GetWriters returns all users that have write access to the repository. +func (repo *Repository) GetWriters() (_ []*User, err error) { + return repo.getUsersWithAccessMode(x, AccessModeWrite) +} + +// getUsersWithAccessMode returns users that have at least given access mode to the repository. +func (repo *Repository) getUsersWithAccessMode(e Engine, mode AccessMode) (_ []*User, err error) { + if err = repo.getOwner(e); err != nil { + return nil, err + } + + accesses := make([]*Access, 0, 10) + if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { + return nil, err + } + + // Leave a seat for owner itself to append later, but if owner is an organization + // and just waste 1 unit is cheaper than re-allocate memory once. + users := make([]*User, 0, len(accesses)+1) + if len(accesses) > 0 { + userIDs := make([]int64, len(accesses)) + for i := 0; i < len(accesses); i++ { + userIDs[i] = accesses[i].UserID + } + + if err = e.In("id", userIDs).Find(&users); err != nil { + return nil, err + } + } + if !repo.Owner.IsOrganization() { + users = append(users, repo.Owner) + } + + 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 { + sanitize := func(s string) string { + return fmt.Sprintf(`%[1]s`, s) + } + return template.HTML(descPattern.ReplaceAllStringFunc(markup.Sanitize(repo.Description), sanitize)) +} + +// LocalCopyPath returns the local repository copy path. +func LocalCopyPath() string { + if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { + return setting.Repository.Local.LocalCopyPath + } + return path.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) +} + +// LocalCopyPath returns the local repository copy path for the given repo. +func (repo *Repository) LocalCopyPath() string { + return path.Join(LocalCopyPath(), com.ToStr(repo.ID)) +} + +// UpdateLocalCopyBranch pulls latest changes of given branch from repoPath to localPath. +// It creates a new clone if local copy does not exist. +// This function checks out target branch by default, it is safe to assume subsequent +// operations are operating against target branch when caller has confidence for no race condition. +func UpdateLocalCopyBranch(repoPath, localPath, branch string) error { + if !com.IsExist(localPath) { + if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{ + Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second, + Branch: branch, + }); err != nil { + return fmt.Errorf("git clone %s: %v", branch, err) + } + } else { + _, err := git.NewCommand("fetch", "origin").RunInDir(localPath) + if err != nil { + return fmt.Errorf("git fetch origin: %v", err) + } + if len(branch) > 0 { + if err := git.Checkout(localPath, git.CheckoutOptions{ + Branch: branch, + }); err != nil { + return fmt.Errorf("git checkout %s: %v", branch, err) + } + + if err := git.ResetHEAD(localPath, true, "origin/"+branch); err != nil { + return fmt.Errorf("git reset --hard origin/%s: %v", branch, err) + } + } + } + return nil +} + +// UpdateLocalCopyBranch makes sure local copy of repository in given branch is up-to-date. +func (repo *Repository) UpdateLocalCopyBranch(branch string) error { + return UpdateLocalCopyBranch(repo.RepoPath(), repo.LocalCopyPath(), branch) +} + +// PatchPath returns corresponding patch file path of repository by given issue ID. +func (repo *Repository) PatchPath(index int64) (string, error) { + return repo.patchPath(x, index) +} + +func (repo *Repository) patchPath(e Engine, index int64) (string, error) { + if err := repo.getOwner(e); err != nil { + return "", err + } + + return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil +} + +// SavePatch saves patch data to corresponding location by given issue ID. +func (repo *Repository) SavePatch(index int64, patch []byte) error { + return repo.savePatch(x, index, patch) +} + +func (repo *Repository) savePatch(e Engine, index int64, patch []byte) error { + patchPath, err := repo.patchPath(e, index) + if err != nil { + return fmt.Errorf("PatchPath: %v", err) + } + dir := filepath.Dir(patchPath) + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", dir, err) + } + + if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil { + return fmt.Errorf("WriteFile: %v", err) + } + + return nil +} + +func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { + has, err := e.Get(&Repository{ + OwnerID: u.ID, + LowerName: strings.ToLower(repoName), + }) + return has && com.IsDir(RepoPath(u.Name, repoName)), err +} + +// IsRepositoryExist returns true if the repository with given name under user has already existed. +func IsRepositoryExist(u *User, repoName string) (bool, error) { + return isRepositoryExist(x, u, repoName) +} + +// CloneLink represents different types of clone URLs of repository. +type CloneLink struct { + SSH string + HTTPS string + Git string +} + +// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name. +func ComposeHTTPSCloneURL(owner, repo string) string { + return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.QueryEscape(owner), url.QueryEscape(repo)) +} + +func (repo *Repository) cloneLink(e Engine, isWiki bool) *CloneLink { + repoName := repo.Name + if isWiki { + repoName += ".wiki" + } + + sshUser := setting.RunUser + if setting.SSH.StartBuiltinServer { + sshUser = setting.SSH.BuiltinServerUser + } + + repo.Owner = repo.mustOwner(e) + cl := new(CloneLink) + if setting.SSH.Port != 22 { + cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", sshUser, setting.SSH.Domain, setting.SSH.Port, repo.Owner.Name, repoName) + } else if setting.Repository.UseCompatSSHURI { + cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) + } else { + cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) + } + cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName) + return cl +} + +// CloneLink returns clone URLs of repository. +func (repo *Repository) CloneLink() (cl *CloneLink) { + return repo.cloneLink(x, false) +} + +// MigrateRepoOptions contains the repository migrate options +type MigrateRepoOptions struct { + Name string + Description string + IsPrivate bool + IsMirror bool + RemoteAddr string +} + +/* + GitHub, GitLab, Gogs: *.wiki.git + BitBucket: *.git/wiki +*/ +var commonWikiURLSuffixes = []string{".wiki.git", ".git/wiki"} + +// wikiRemoteURL returns accessible repository URL for wiki if exists. +// Otherwise, it returns an empty string. +func wikiRemoteURL(remote string) string { + remote = strings.TrimSuffix(remote, ".git") + for _, suffix := range commonWikiURLSuffixes { + wikiURL := remote + suffix + if git.IsRepoURLAccessible(wikiURL) { + return wikiURL + } + } + return "" +} + +// MigrateRepository migrates a existing repository from other project hosting. +func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, error) { + repo, err := CreateRepository(doer, u, CreateRepoOptions{ + Name: opts.Name, + Description: opts.Description, + IsPrivate: opts.IsPrivate, + IsMirror: opts.IsMirror, + }) + if err != nil { + return nil, err + } + + repoPath := RepoPath(u.Name, opts.Name) + wikiPath := WikiPath(u.Name, opts.Name) + + if u.IsOrganization() { + t, err := u.GetOwnerTeam() + if err != nil { + return nil, err + } + repo.NumWatches = t.NumMembers + } else { + repo.NumWatches = 1 + } + + migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second + + if err := os.RemoveAll(repoPath); err != nil { + return repo, fmt.Errorf("Failed to remove %s: %v", repoPath, err) + } + + if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{ + Mirror: true, + Quiet: true, + Timeout: migrateTimeout, + }); err != nil { + return repo, fmt.Errorf("Clone: %v", err) + } + + wikiRemotePath := wikiRemoteURL(opts.RemoteAddr) + if len(wikiRemotePath) > 0 { + if err := os.RemoveAll(wikiPath); err != nil { + return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) + } + + if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{ + Mirror: true, + Quiet: true, + Timeout: migrateTimeout, + Branch: "master", + }); err != nil { + log.Warn("Clone wiki: %v", err) + if err := os.RemoveAll(wikiPath); err != nil { + return repo, fmt.Errorf("Failed to remove %s: %v", wikiPath, err) + } + } + } + + // Check if repository is empty. + _, stderr, err := com.ExecCmdDir(repoPath, "git", "log", "-1") + 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) + } + } + + if !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) + } + if headBranch != nil { + repo.DefaultBranch = headBranch.Name + } + + if err = SyncReleasesWithTags(repo, gitRepo); err != nil { + log.Error(4, "Failed to synchronize tags to releases for repository: %v", err) + } + } + + if err = repo.UpdateSize(); err != nil { + log.Error(4, "Failed to update size for repository: %v", err) + } + + if opts.IsMirror { + if _, err = x.InsertOne(&Mirror{ + RepoID: repo.ID, + Interval: setting.Mirror.DefaultInterval, + EnablePrune: true, + NextUpdateUnix: util.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval), + }); err != nil { + return repo, fmt.Errorf("InsertOne: %v", err) + } + + repo.IsMirror = true + err = UpdateRepository(repo, false) + } else { + repo, err = CleanUpMigrateInfo(repo) + } + + if err != nil && !repo.IsEmpty { + UpdateRepoIndexer(repo) + } + + return repo, err +} + +// cleanUpMigrateGitConfig removes mirror info which prevents "push --all". +// This also removes possible user credentials. +func cleanUpMigrateGitConfig(configPath string) error { + cfg, err := ini.Load(configPath) + if err != nil { + return fmt.Errorf("open config file: %v", err) + } + cfg.DeleteSection("remote \"origin\"") + if err = cfg.SaveToIndent(configPath, "\t"); err != nil { + return fmt.Errorf("save config file: %v", err) + } + return nil +} + +// createDelegateHooks creates all the hooks scripts for the repo +func createDelegateHooks(repoPath string) (err error) { + var ( + hookNames = []string{"pre-receive", "update", "post-receive"} + hookTpls = []string{ + fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + } + giteaHookTpls = []string{ + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + } + ) + + hookDir := filepath.Join(repoPath, "hooks") + + for i, hookName := range hookNames { + oldHookPath := filepath.Join(hookDir, hookName) + newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") + + if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { + return fmt.Errorf("create hooks dir '%s': %v", filepath.Join(hookDir, hookName+".d"), err) + } + + // WARNING: This will override all old server-side hooks + if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { + return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err) + } + + if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil { + return fmt.Errorf("write new hook file '%s': %v", newHookPath, err) + } + } + + return nil +} + +// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. +func CleanUpMigrateInfo(repo *Repository) (*Repository, error) { + repoPath := repo.RepoPath() + if err := createDelegateHooks(repoPath); err != nil { + return repo, fmt.Errorf("createDelegateHooks: %v", err) + } + if repo.HasWiki() { + if err := createDelegateHooks(repo.WikiPath()); err != nil { + return repo, fmt.Errorf("createDelegateHooks.(wiki): %v", err) + } + } + + if err := cleanUpMigrateGitConfig(repo.GitConfigPath()); err != nil { + return repo, fmt.Errorf("cleanUpMigrateGitConfig: %v", err) + } + if repo.HasWiki() { + if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil { + return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %v", err) + } + } + + return repo, UpdateRepository(repo, false) +} + +// initRepoCommit temporarily changes with work directory. +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 { + 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), + "-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 { + return fmt.Errorf("git push: %s", stderr) + } + return nil +} + +// CreateRepoOptions contains the create repository options +type CreateRepoOptions struct { + Name string + Description string + Gitignores string + IssueLabels string + License string + Readme string + IsPrivate bool + IsMirror bool + AutoInit bool +} + +func getRepoInitFile(tp, name string) ([]byte, error) { + cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") + relPath := path.Join("options", tp, cleanedName) + + // Use custom file when available. + customPath := path.Join(setting.CustomPath, relPath) + if com.IsFile(customPath) { + return ioutil.ReadFile(customPath) + } + + switch tp { + case "readme": + return options.Readme(cleanedName) + case "gitignore": + return options.Gitignore(cleanedName) + case "license": + return options.License(cleanedName) + case "label": + return options.Labels(cleanedName) + default: + return []byte{}, fmt.Errorf("Invalid init file type") + } +} + +func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { + // 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, + ) + if err != nil { + return fmt.Errorf("git clone: %v - %s", err, stderr) + } + + // README + data, err := getRepoInitFile("readme", opts.Readme) + if err != nil { + return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err) + } + + cloneLink := repo.cloneLink(e, false) + match := map[string]string{ + "Name": repo.Name, + "Description": repo.Description, + "CloneURL.SSH": cloneLink.SSH, + "CloneURL.HTTPS": cloneLink.HTTPS, + } + if err = ioutil.WriteFile(filepath.Join(tmpDir, "README.md"), + []byte(com.Expand(string(data), match)), 0644); err != nil { + return fmt.Errorf("write README.md: %v", err) + } + + // .gitignore + if len(opts.Gitignores) > 0 { + var buf bytes.Buffer + names := strings.Split(opts.Gitignores, ",") + for _, name := range names { + data, err = getRepoInitFile("gitignore", name) + if err != nil { + return fmt.Errorf("getRepoInitFile[%s]: %v", name, err) + } + buf.WriteString("# ---> " + name + "\n") + buf.Write(data) + buf.WriteString("\n") + } + + if buf.Len() > 0 { + if err = ioutil.WriteFile(filepath.Join(tmpDir, ".gitignore"), buf.Bytes(), 0644); err != nil { + return fmt.Errorf("write .gitignore: %v", err) + } + } + } + + // Issue Labels + if len(opts.IssueLabels) > 0 { + list, err := GetLabelTemplateFile(opts.IssueLabels) + if err != nil { + return fmt.Errorf("GetLabelTemplateFile: %v", err) + } + + labels := make([]*Label, len(list)) + for i := 0; i < len(list); i++ { + labels[i] = &Label{ + RepoID: repo.ID, + Name: list[i][0], + Description: list[i][2], + Color: list[i][1], + } + } + for _, label := range labels { + if err = newLabel(e, label); err != nil { + return fmt.Errorf("newLabel: %v", err) + } + } + + } + + // LICENSE + if len(opts.License) > 0 { + data, err = getRepoInitFile("license", opts.License) + if err != nil { + return fmt.Errorf("getRepoInitFile[%s]: %v", opts.License, err) + } + + if err = ioutil.WriteFile(filepath.Join(tmpDir, "LICENSE"), data, 0644); err != nil { + return fmt.Errorf("write LICENSE: %v", err) + } + } + + return nil +} + +// InitRepository initializes README and .gitignore if needed. +func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) { + // Somehow the directory could exist. + if com.IsExist(repoPath) { + return fmt.Errorf("initRepository: path already exists: %s", repoPath) + } + + // Init git bare new repository. + if err = git.InitRepository(repoPath, true); err != nil { + return fmt.Errorf("InitRepository: %v", err) + } else if err = createDelegateHooks(repoPath); err != nil { + return fmt.Errorf("createDelegateHooks: %v", err) + } + + tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) + + // Initialize repository according to user's choice. + if opts.AutoInit { + + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", tmpDir, err) + } + + defer os.RemoveAll(tmpDir) + + if err = prepareRepoCommit(e, repo, tmpDir, repoPath, opts); err != nil { + return fmt.Errorf("prepareRepoCommit: %v", err) + } + + // Apply changes and commit. + if err = initRepoCommit(tmpDir, u.NewGitSig()); err != nil { + return fmt.Errorf("initRepoCommit: %v", err) + } + } + + // Re-fetch the repository from database before updating it (else it would + // override changes that were done earlier with sql) + if repo, err = getRepositoryByID(e, repo.ID); err != nil { + return fmt.Errorf("getRepositoryByID: %v", err) + } + + if !opts.AutoInit { + repo.IsEmpty = true + } + + repo.DefaultBranch = "master" + if err = updateRepository(e, repo, false); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return nil +} + +var ( + reservedRepoNames = []string{".", ".."} + reservedRepoPatterns = []string{"*.git", "*.wiki"} +) + +// IsUsableRepoName returns true when repository is usable +func IsUsableRepoName(name string) error { + return isUsableName(reservedRepoNames, reservedRepoPatterns, name) +} + +func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err error) { + if err = IsUsableRepoName(repo.Name); err != nil { + return err + } + + has, err := isRepositoryExist(e, u, repo.Name) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{u.Name, repo.Name} + } + + if _, err = e.Insert(repo); err != nil { + return err + } + if err = deleteRepoRedirect(e, u.ID, repo.Name); err != nil { + return err + } + + // insert units for repo + var units = make([]RepoUnit, 0, len(defaultRepoUnits)) + for _, tp := range defaultRepoUnits { + if tp == UnitTypeIssues { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Config: &IssuesConfig{ + EnableTimetracker: setting.Service.DefaultEnableTimetracking, + AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime, + EnableDependencies: setting.Service.DefaultEnableDependencies, + }, + }) + } else if tp == UnitTypePullRequests { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, + }) + } else { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + }) + } + + } + + if _, err = e.Insert(&units); err != nil { + return err + } + + u.NumRepos++ + // Remember visibility preference. + u.LastRepoVisibility = repo.IsPrivate + if err = updateUser(e, u); err != nil { + return fmt.Errorf("updateUser: %v", err) + } + + // Give access to all members in owner team. + if u.IsOrganization() { + t, err := u.getOwnerTeam(e) + if err != nil { + return fmt.Errorf("getOwnerTeam: %v", err) + } else if err = t.addRepository(e, repo); err != nil { + return fmt.Errorf("addRepository: %v", err) + } else if err = prepareWebhooks(e, repo, HookEventRepository, &api.RepositoryPayload{ + Action: api.HookRepoCreated, + Repository: repo.innerAPIFormat(e, AccessModeOwner, false), + Organization: u.APIFormat(), + Sender: doer.APIFormat(), + }); err != nil { + return fmt.Errorf("prepareWebhooks: %v", err) + } + go HookQueue.Add(repo.ID) + } else { + // Organization automatically called this in addRepository method. + if err = repo.recalculateAccesses(e); err != nil { + return fmt.Errorf("recalculateAccesses: %v", err) + } + } + + if setting.Service.AutoWatchNewRepos { + if err = watchRepo(e, doer.ID, repo.ID, true); err != nil { + return fmt.Errorf("watchRepo: %v", err) + } + } + if err = newRepoAction(e, doer, repo); err != nil { + return fmt.Errorf("newRepoAction: %v", err) + } + + return nil +} + +// CreateRepository creates a repository for the user/organization. +func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err error) { + if !doer.IsAdmin && !u.CanCreateRepo() { + return nil, ErrReachLimitOfRepo{u.MaxRepoCreation} + } + + repo := &Repository{ + OwnerID: u.ID, + Owner: u, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + IsPrivate: opts.IsPrivate, + IsFsckEnabled: !opts.IsMirror, + CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return nil, err + } + + if err = createRepository(sess, doer, u, repo); err != nil { + return nil, err + } + + // No need for init mirror. + if !opts.IsMirror { + repoPath := RepoPath(u.Name, repo.Name) + if err = initRepository(sess, repoPath, u, repo, opts); err != nil { + if err2 := os.RemoveAll(repoPath); err2 != nil { + log.Error(4, "initRepository: %v", err) + return nil, fmt.Errorf( + "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) + } + return nil, fmt.Errorf("initRepository: %v", err) + } + + _, stderr, err := process.GetManager().ExecDir(-1, + repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath), + "git", "update-server-info") + if err != nil { + return nil, errors.New("CreateRepository(git update-server-info): " + stderr) + } + } + + return repo, sess.Commit() +} + +func countRepositories(userID int64, private bool) int64 { + sess := x.Where("id > 0") + + if userID > 0 { + sess.And("owner_id = ?", userID) + } + if !private { + sess.And("is_private=?", false) + } + + count, err := sess.Count(new(Repository)) + if err != nil { + log.Error(4, "countRepositories: %v", err) + } + return count +} + +// CountRepositories returns number of repositories. +// Argument private only takes effect when it is false, +// set it true to count all repositories. +func CountRepositories(private bool) int64 { + return countRepositories(-1, private) +} + +// CountUserRepositories returns number of repositories user owns. +// Argument private only takes effect when it is false, +// set it true to count all repositories. +func CountUserRepositories(userID int64, private bool) int64 { + return countRepositories(userID, private) +} + +// RepoPath returns repository path by given user and repository name. +func RepoPath(userName, repoName string) string { + return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") +} + +// TransferOwnership transfers all corresponding setting from old user to new one. +func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error { + newOwner, err := GetUserByName(newOwnerName) + if err != nil { + return fmt.Errorf("get new owner '%s': %v", newOwnerName, err) + } + + // Check if new owner has repository with same name. + has, err := IsRepositoryExist(newOwner, repo.Name) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{newOwnerName, repo.Name} + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return fmt.Errorf("sess.Begin: %v", err) + } + + owner := repo.Owner + + // Note: we have to set value here to make sure recalculate accesses is based on + // new owner. + repo.OwnerID = newOwner.ID + repo.Owner = newOwner + + // Update repository. + if _, err := sess.ID(repo.ID).Update(repo); err != nil { + return fmt.Errorf("update owner: %v", err) + } + + // Remove redundant collaborators. + collaborators, err := repo.getCollaborators(sess) + if err != nil { + return fmt.Errorf("getCollaborators: %v", err) + } + + // Dummy object. + collaboration := &Collaboration{RepoID: repo.ID} + for _, c := range collaborators { + if c.ID != newOwner.ID { + isMember, err := isOrganizationMember(sess, newOwner.ID, c.ID) + if err != nil { + return fmt.Errorf("IsOrgMember: %v", err) + } else if !isMember { + continue + } + } + collaboration.UserID = c.ID + if _, err = sess.Delete(collaboration); err != nil { + return fmt.Errorf("remove collaborator '%d': %v", c.ID, err) + } + } + + // Remove old team-repository relations. + if owner.IsOrganization() { + if err = owner.removeOrgRepo(sess, repo.ID); err != nil { + return fmt.Errorf("removeOrgRepo: %v", err) + } + } + + if newOwner.IsOrganization() { + t, err := newOwner.getOwnerTeam(sess) + if err != nil { + return fmt.Errorf("getOwnerTeam: %v", err) + } else if err = t.addRepository(sess, repo); err != nil { + return fmt.Errorf("add to owner team: %v", err) + } + } else { + // Organization called this in addRepository method. + if err = repo.recalculateAccesses(sess); err != nil { + return fmt.Errorf("recalculateAccesses: %v", err) + } + } + + // Update repository count. + if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil { + return fmt.Errorf("increase new owner repository count: %v", err) + } else if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", owner.ID); err != nil { + return fmt.Errorf("decrease old owner repository count: %v", err) + } + + if err = watchRepo(sess, doer.ID, repo.ID, true); err != nil { + return fmt.Errorf("watchRepo: %v", err) + } else if err = transferRepoAction(sess, doer, owner, repo); err != nil { + return fmt.Errorf("transferRepoAction: %v", err) + } + + // Rename remote repository to new path and delete local copy. + dir := UserPath(newOwner.Name) + + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", dir, err) + } + + if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil { + return fmt.Errorf("rename repository directory: %v", err) + } + removeAllWithNotice(sess, "Delete repository local copy", repo.LocalCopyPath()) + + // Rename remote wiki repository to new path and delete local copy. + wikiPath := WikiPath(owner.Name, repo.Name) + if com.IsExist(wikiPath) { + removeAllWithNotice(sess, "Delete repository wiki local copy", repo.LocalWikiPath()) + if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil { + return fmt.Errorf("rename repository wiki: %v", err) + } + } + + return sess.Commit() +} + +// ChangeRepositoryName changes all corresponding setting from old repository name to new one. +func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) { + oldRepoName = strings.ToLower(oldRepoName) + newRepoName = strings.ToLower(newRepoName) + if err = IsUsableRepoName(newRepoName); err != nil { + return err + } + + has, err := IsRepositoryExist(u, newRepoName) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{u.Name, newRepoName} + } + + repo, err := GetRepositoryByName(u.ID, oldRepoName) + if err != nil { + return fmt.Errorf("GetRepositoryByName: %v", err) + } + + // Change repository directory name. We must lock the local copy of the + // repo so that we can atomically rename the repo path and updates the + // local copy's origin accordingly. + repoWorkingPool.CheckIn(com.ToStr(repo.ID)) + defer repoWorkingPool.CheckOut(com.ToStr(repo.ID)) + + newRepoPath := RepoPath(u.Name, newRepoName) + if err = os.Rename(repo.RepoPath(), newRepoPath); err != nil { + return fmt.Errorf("rename repository directory: %v", err) + } + + localPath := repo.LocalCopyPath() + if com.IsExist(localPath) { + _, err := git.NewCommand("remote", "set-url", "origin", newRepoPath).RunInDir(localPath) + if err != nil { + return fmt.Errorf("git remote set-url origin %s: %v", newRepoPath, err) + } + } + + wikiPath := repo.WikiPath() + if com.IsExist(wikiPath) { + if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil { + return fmt.Errorf("rename repository wiki: %v", err) + } + RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath()) + } + + return nil +} + +func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, e. + Where("fork_id=?", forkID). + Find(&repos) +} + +// GetRepositoriesByForkID returns all repositories with given fork ID. +func GetRepositoriesByForkID(forkID int64) ([]*Repository, error) { + return getRepositoriesByForkID(x, forkID) +} + +func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err error) { + repo.LowerName = strings.ToLower(repo.Name) + + if len(repo.Description) > 255 { + repo.Description = repo.Description[:255] + } + if len(repo.Website) > 255 { + repo.Website = repo.Website[:255] + } + + if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { + return fmt.Errorf("update: %v", err) + } + + if visibilityChanged { + if err = repo.getOwner(e); err != nil { + return fmt.Errorf("getOwner: %v", err) + } + if repo.Owner.IsOrganization() { + // Organization repository need to recalculate access table when visibility is changed. + if err = repo.recalculateTeamAccesses(e, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %v", err) + } + } + + // If repo has become private, we need to set its actions to private. + if repo.IsPrivate { + _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&Action{ + IsPrivate: true, + }) + if err != nil { + return err + } + } + + // Create/Remove git-daemon-export-ok for git-daemon... + daemonExportFile := path.Join(repo.repoPath(e), `git-daemon-export-ok`) + if repo.IsPrivate && com.IsExist(daemonExportFile) { + if err = os.Remove(daemonExportFile); err != nil { + log.Error(4, "Failed to remove %s: %v", daemonExportFile, err) + } + } else if !repo.IsPrivate && !com.IsExist(daemonExportFile) { + if f, err := os.Create(daemonExportFile); err != nil { + log.Error(4, "Failed to create %s: %v", daemonExportFile, err) + } else { + f.Close() + } + } + + forkRepos, err := getRepositoriesByForkID(e, repo.ID) + if err != nil { + return fmt.Errorf("getRepositoriesByForkID: %v", err) + } + for i := range forkRepos { + forkRepos[i].IsPrivate = repo.IsPrivate + if err = updateRepository(e, forkRepos[i], true); err != nil { + return fmt.Errorf("updateRepository[%d]: %v", forkRepos[i].ID, err) + } + } + + if err = repo.updateSize(e); err != nil { + log.Error(4, "Failed to update size for repository: %v", err) + } + } + + return nil +} + +// UpdateRepository updates a repository +func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + if err = updateRepository(sess, repo, visibilityChanged); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return sess.Commit() +} + +// UpdateRepositoryUnits updates a repository's units +func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + if _, err = sess.Where("repo_id = ?", repo.ID).Delete(new(RepoUnit)); err != nil { + return err + } + + if _, err = sess.Insert(units); err != nil { + return err + } + + return sess.Commit() +} + +// DeleteRepository deletes a repository for a user or organization. +func DeleteRepository(doer *User, uid, repoID int64) error { + // In case is a organization. + org, err := GetUserByID(uid) + if err != nil { + return err + } + if org.IsOrganization() { + if err = org.GetTeams(); err != nil { + return err + } + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + repo := &Repository{ID: repoID, OwnerID: uid} + has, err := sess.Get(repo) + if err != nil { + return err + } else if !has { + return ErrRepoNotExist{repoID, uid, "", ""} + } + + // Delete Deploy Keys + deployKeys, err := listDeployKeys(sess, repo.ID) + if err != nil { + return fmt.Errorf("listDeployKeys: %v", err) + } + for _, dKey := range deployKeys { + if err := deleteDeployKey(sess, doer, dKey.ID); err != nil { + return fmt.Errorf("deleteDeployKeys: %v", err) + } + } + + if cnt, err := sess.ID(repoID).Delete(&Repository{}); err != nil { + return err + } else if cnt != 1 { + return ErrRepoNotExist{repoID, uid, "", ""} + } + + if org.IsOrganization() { + for _, t := range org.Teams { + if !t.hasRepository(sess, repoID) { + continue + } else if err = t.removeRepository(sess, repo, false); err != nil { + return err + } + } + } + + if err = deleteBeans(sess, + &Access{RepoID: repo.ID}, + &Action{RepoID: repo.ID}, + &Watch{RepoID: repoID}, + &Star{RepoID: repoID}, + &Mirror{RepoID: repoID}, + &Milestone{RepoID: repoID}, + &Release{RepoID: repoID}, + &Collaboration{RepoID: repoID}, + &PullRequest{BaseRepoID: repoID}, + &RepoUnit{RepoID: repoID}, + &RepoRedirect{RedirectRepoID: repoID}, + &Webhook{RepoID: repoID}, + &HookTask{RepoID: repoID}, + &Notification{RepoID: repoID}, + &CommitStatus{RepoID: repoID}, + ); err != nil { + return fmt.Errorf("deleteBeans: %v", err) + } + + deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repoID}) + // Delete comments and attachments + if _, err = sess.In("issue_id", deleteCond). + Delete(&Comment{}); err != nil { + return err + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&IssueUser{}); err != nil { + return err + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&Reaction{}); err != nil { + return err + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&IssueWatch{}); err != nil { + return err + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&Stopwatch{}); err != nil { + return err + } + + attachmentPaths := make([]string, 0, 20) + attachments := make([]*Attachment, 0, len(attachmentPaths)) + if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id"). + Where("issue.repo_id = ?", repoID). + Find(&attachments); err != nil { + return err + } + for j := range attachments { + attachmentPaths = append(attachmentPaths, attachments[j].LocalPath()) + } + + if _, err = sess.In("issue_id", deleteCond). + Delete(&Attachment{}); err != nil { + return err + } + + if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil { + return err + } + + if _, err = sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil { + return err + } + + if repo.IsFork { + if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { + return fmt.Errorf("decrease fork count: %v", err) + } + } + + if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { + return err + } + + // FIXME: Remove repository files should be executed after transaction succeed. + repoPath := repo.repoPath(sess) + removeAllWithNotice(sess, "Delete repository files", repoPath) + + repo.deleteWiki(sess) + + // Remove attachment files. + for i := range attachmentPaths { + removeAllWithNotice(sess, "Delete attachment", attachmentPaths[i]) + } + + // Remove LFS objects + var lfsObjects []*LFSMetaObject + if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { + return err + } + + for _, v := range lfsObjects { + count, err := sess.Count(&LFSMetaObject{Oid: v.Oid}) + if err != nil { + return err + } + + if count > 1 { + continue + } + + 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 + } + } + + if _, err := sess.Delete(&LFSMetaObject{RepositoryID: repoID}); err != nil { + return err + } + + if repo.NumForks > 0 { + if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { + log.Error(4, "reset 'fork_id' and 'is_fork': %v", err) + } + } + + if err = sess.Commit(); err != nil { + if len(deployKeys) > 0 { + // We need to rewrite the public keys because the commit failed + if err2 := RewriteAllPublicKeys(); err2 != nil { + return fmt.Errorf("Commit: %v SSH Keys: %v", err, err2) + } + } + return fmt.Errorf("Commit: %v", err) + } + + if org.IsOrganization() { + if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{ + Action: api.HookRepoDeleted, + Repository: repo.APIFormat(AccessModeOwner), + Organization: org.APIFormat(), + Sender: doer.APIFormat(), + }); err != nil { + return err + } + go HookQueue.Add(repo.ID) + } + + DeleteRepoFromIndexer(repo) + return nil +} + +// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame. +func GetRepositoryByOwnerAndName(ownerName, repoName string) (*Repository, error) { + var repo Repository + has, err := x.Select("repository.*"). + Join("INNER", "`user`", "`user`.id = repository.owner_id"). + Where("repository.lower_name = ?", strings.ToLower(repoName)). + And("`user`.lower_name = ?", strings.ToLower(ownerName)). + Get(&repo) + if err != nil { + return nil, err + } else if !has { + return nil, ErrRepoNotExist{0, 0, ownerName, repoName} + } + return &repo, nil +} + +// GetRepositoryByName returns the repository by given name under user if exists. +func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { + repo := &Repository{ + OwnerID: ownerID, + LowerName: strings.ToLower(name), + } + has, err := x.Get(repo) + if err != nil { + return nil, err + } else if !has { + return nil, ErrRepoNotExist{0, ownerID, "", name} + } + return repo, err +} + +func getRepositoryByID(e Engine, id int64) (*Repository, error) { + repo := new(Repository) + has, err := e.ID(id).Get(repo) + if err != nil { + return nil, err + } else if !has { + return nil, ErrRepoNotExist{id, 0, "", ""} + } + return repo, nil +} + +// GetRepositoryByID returns the repository by given id if exists. +func GetRepositoryByID(id int64) (*Repository, error) { + return getRepositoryByID(x, id) +} + +// GetRepositoriesMapByIDs returns the repositories by given id slice. +func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { + var repos = make(map[int64]*Repository, len(ids)) + return repos, x.In("id", ids).Find(&repos) +} + +// GetUserRepositories returns a list of repositories of given user. +func GetUserRepositories(userID int64, private bool, page, pageSize int, orderBy string) ([]*Repository, error) { + if len(orderBy) == 0 { + orderBy = "updated_unix DESC" + } + + sess := x. + Where("owner_id = ?", userID). + OrderBy(orderBy) + if !private { + sess.And("is_private=?", false) + } + + if page <= 0 { + page = 1 + } + sess.Limit(pageSize, (page-1)*pageSize) + + repos := make([]*Repository, 0, pageSize) + return repos, sess.Find(&repos) +} + +// GetUserMirrorRepositories returns a list of mirror repositories of given user. +func GetUserMirrorRepositories(userID int64) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, x. + Where("owner_id = ?", userID). + And("is_mirror = ?", true). + Find(&repos) +} + +func getRepositoryCount(e Engine, u *User) (int64, error) { + return e.Count(&Repository{OwnerID: u.ID}) +} + +func getPublicRepositoryCount(e Engine, u *User) (int64, error) { + return e.Where("is_private = ?", false).Count(&Repository{OwnerID: u.ID}) +} + +func getPrivateRepositoryCount(e Engine, u *User) (int64, error) { + return e.Where("is_private = ?", true).Count(&Repository{OwnerID: u.ID}) +} + +// GetRepositoryCount returns the total number of repositories of user. +func GetRepositoryCount(u *User) (int64, error) { + return getRepositoryCount(x, u) +} + +// GetPublicRepositoryCount returns the total number of public repositories of user. +func GetPublicRepositoryCount(u *User) (int64, error) { + return getPublicRepositoryCount(x, u) +} + +// GetPrivateRepositoryCount returns the total number of private repositories of user. +func GetPrivateRepositoryCount(u *User) (int64, error) { + return getPrivateRepositoryCount(x, u) +} + +// DeleteRepositoryArchives deletes all repositories' archives. +func DeleteRepositoryArchives() error { + return x. + Where("id > 0"). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repo := bean.(*Repository) + return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives")) + }) +} + +// DeleteOldRepositoryArchives deletes old repository archives. +func DeleteOldRepositoryArchives() { + if !taskStatusTable.StartIfNotRunning(archiveCleanup) { + return + } + defer taskStatusTable.Stop(archiveCleanup) + + log.Trace("Doing: ArchiveCleanup") + + if err := x.Where("id > 0").Iterate(new(Repository), deleteOldRepositoryArchives); err != nil { + log.Error(4, "ArchiveClean: %v", err) + } +} + +func deleteOldRepositoryArchives(idx int, bean interface{}) error { + repo := bean.(*Repository) + basePath := filepath.Join(repo.RepoPath(), "archives") + + for _, ty := range []string{"zip", "targz"} { + path := filepath.Join(basePath, ty) + file, err := os.Open(path) + if err != nil { + if !os.IsNotExist(err) { + log.Warn("Unable to open directory %s: %v", path, err) + return err + } + + // If the directory doesn't exist, that's okay. + continue + } + + files, err := file.Readdir(0) + file.Close() + if err != nil { + log.Warn("Unable to read directory %s: %v", path, err) + return err + } + + minimumOldestTime := time.Now().Add(-setting.Cron.ArchiveCleanup.OlderThan) + for _, info := range files { + if info.ModTime().Before(minimumOldestTime) && !info.IsDir() { + toDelete := filepath.Join(path, info.Name()) + // This is a best-effort purge, so we do not check error codes to confirm removal. + if err = os.Remove(toDelete); err != nil { + log.Trace("Unable to delete %s, but proceeding: %v", toDelete, err) + } + } + } + } + + return nil +} + +func gatherMissingRepoRecords() ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + if err := x. + Where("id > 0"). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repo := bean.(*Repository) + if !com.IsDir(repo.RepoPath()) { + repos = append(repos, repo) + } + return nil + }); err != nil { + if err2 := CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { + return nil, fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + return repos, nil +} + +// DeleteMissingRepositories deletes all repository records that lost Git files. +func DeleteMissingRepositories(doer *User) error { + repos, err := gatherMissingRepoRecords() + if err != nil { + return fmt.Errorf("gatherMissingRepoRecords: %v", err) + } + + if len(repos) == 0 { + return nil + } + + for _, repo := range repos { + log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) + if err := DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { + if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil { + return fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + } + return nil +} + +// ReinitMissingRepositories reinitializes all repository records that lost Git files. +func ReinitMissingRepositories() error { + repos, err := gatherMissingRepoRecords() + if err != nil { + return fmt.Errorf("gatherMissingRepoRecords: %v", err) + } + + if len(repos) == 0 { + return nil + } + + for _, repo := range repos { + log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) + if err := git.InitRepository(repo.RepoPath(), true); err != nil { + if err2 := CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil { + return fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + } + return nil +} + +// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks +// to make sure the binary and custom conf path are up-to-date. +func SyncRepositoryHooks() error { + return x.Cols("owner_id", "name").Where("id > 0").Iterate(new(Repository), + func(idx int, bean interface{}) error { + if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil { + return fmt.Errorf("SyncRepositoryHook: %v", err) + } + if bean.(*Repository).HasWiki() { + if err := createDelegateHooks(bean.(*Repository).WikiPath()); err != nil { + return fmt.Errorf("SyncRepositoryHook: %v", err) + } + } + return nil + }) +} + +// Prevent duplicate running tasks. +var taskStatusTable = sync.NewStatusTable() + +const ( + mirrorUpdate = "mirror_update" + gitFsck = "git_fsck" + checkRepos = "check_repos" + archiveCleanup = "archive_cleanup" +) + +// GitFsck calls 'git fsck' to check repository health. +func GitFsck() { + if !taskStatusTable.StartIfNotRunning(gitFsck) { + return + } + defer taskStatusTable.Stop(gitFsck) + + log.Trace("Doing: GitFsck") + + if err := x. + Where("id>0 AND is_fsck_enabled=?", true).BufferSize(setting.IterateBufferSize). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repo := bean.(*Repository) + repoPath := repo.RepoPath() + log.Trace("Running health check on repository %s", repoPath) + if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { + desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err) + log.Warn(desc) + if err = CreateRepositoryNotice(desc); err != nil { + log.Error(4, "CreateRepositoryNotice: %v", err) + } + } + return nil + }); err != nil { + log.Error(4, "GitFsck: %v", err) + } + log.Trace("Finished: GitFsck") +} + +// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository +func GitGcRepos() error { + args := append([]string{"gc"}, setting.Git.GCArgs...) + return x. + Where("id > 0").BufferSize(setting.IterateBufferSize). + Iterate(new(Repository), + func(idx int, bean interface{}) error { + repo := bean.(*Repository) + if err := repo.GetOwner(); err != nil { + return err + } + _, stderr, err := process.GetManager().ExecDir( + time.Duration(setting.Git.Timeout.GC)*time.Second, + RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection", + "git", args...) + if err != nil { + return fmt.Errorf("%v: %v", err, stderr) + } + return nil + }) +} + +type repoChecker struct { + querySQL, correctSQL string + desc string +} + +func repoStatsCheck(checker *repoChecker) { + results, err := x.Query(checker.querySQL) + if err != nil { + log.Error(4, "Select %s: %v", checker.desc, err) + return + } + for _, result := range results { + id := com.StrTo(result["id"]).MustInt64() + log.Trace("Updating %s: %d", checker.desc, id) + _, err = x.Exec(checker.correctSQL, id, id) + if err != nil { + log.Error(4, "Update %s[%d]: %v", checker.desc, id, err) + } + } +} + +// CheckRepoStats checks the repository stats +func CheckRepoStats() { + if !taskStatusTable.StartIfNotRunning(checkRepos) { + return + } + defer taskStatusTable.Stop(checkRepos) + + log.Trace("Doing: CheckRepoStats") + + checkers := []*repoChecker{ + // Repository.NumWatches + { + "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)", + "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?", + "repository count 'num_watches'", + }, + // Repository.NumStars + { + "SELECT repo.id FROM `repository` repo WHERE repo.num_stars!=(SELECT COUNT(*) FROM `star` WHERE repo_id=repo.id)", + "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", + "repository count 'num_stars'", + }, + // Label.NumIssues + { + "SELECT label.id FROM `label` WHERE label.num_issues!=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=label.id)", + "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", + "label count 'num_issues'", + }, + // User.NumRepos + { + "SELECT `user`.id FROM `user` WHERE `user`.num_repos!=(SELECT COUNT(*) FROM `repository` WHERE owner_id=`user`.id)", + "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", + "user count 'num_repos'", + }, + // Issue.NumComments + { + "SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)", + "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", + "issue count 'num_comments'", + }, + } + for i := range checkers { + repoStatsCheck(checkers[i]) + } + + // ***** START: Repository.NumClosedIssues ***** + desc := "repository count 'num_closed_issues'" + results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false) + if err != nil { + log.Error(4, "Select %s: %v", desc, err) + } else { + for _, result := range results { + id := com.StrTo(result["id"]).MustInt64() + log.Trace("Updating %s: %d", desc, id) + _, err = x.Exec("UPDATE `repository` SET num_closed_issues=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, false, id) + if err != nil { + log.Error(4, "Update %s[%d]: %v", desc, id, err) + } + } + } + // ***** END: Repository.NumClosedIssues ***** + + // FIXME: use checker when stop supporting old fork repo format. + // ***** START: Repository.NumForks ***** + results, err = x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)") + if err != nil { + log.Error(4, "Select repository count 'num_forks': %v", err) + } else { + for _, result := range results { + id := com.StrTo(result["id"]).MustInt64() + log.Trace("Updating repository count 'num_forks': %d", id) + + repo, err := GetRepositoryByID(id) + if err != nil { + log.Error(4, "GetRepositoryByID[%d]: %v", id, err) + continue + } + + rawResult, err := x.Query("SELECT COUNT(*) FROM `repository` WHERE fork_id=?", repo.ID) + if err != nil { + log.Error(4, "Select count of forks[%d]: %v", repo.ID, err) + continue + } + repo.NumForks = int(parseCountResult(rawResult)) + + if err = UpdateRepository(repo, false); err != nil { + log.Error(4, "UpdateRepository[%d]: %v", id, err) + continue + } + } + } + // ***** END: Repository.NumForks ***** +} + +// SetArchiveRepoState sets if a repo is archived +func (repo *Repository) SetArchiveRepoState(isArchived bool) (err error) { + repo.IsArchived = isArchived + _, err = x.Where("id = ?", repo.ID).Cols("is_archived").Update(repo) + return +} + +// ___________ __ +// \_ _____/__________| | __ +// | __)/ _ \_ __ \ |/ / +// | \( <_> ) | \/ < +// \___ / \____/|__| |__|_ \ +// \/ \/ + +// HasForkedRepo checks if given user has already forked a repository with given ID. +func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) { + repo := new(Repository) + has, _ := x. + Where("owner_id=? AND fork_id=?", ownerID, repoID). + Get(repo) + return repo, has +} + +// ForkRepository forks a repository +func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) { + forkedRepo, err := oldRepo.GetUserFork(u.ID) + if err != nil { + return nil, err + } + if forkedRepo != nil { + return nil, ErrRepoAlreadyExist{ + Uname: u.Name, + Name: forkedRepo.Name, + } + } + + repo := &Repository{ + OwnerID: u.ID, + Owner: u, + Name: name, + LowerName: strings.ToLower(name), + Description: desc, + DefaultBranch: oldRepo.DefaultBranch, + IsPrivate: oldRepo.IsPrivate, + IsFork: true, + ForkID: oldRepo.ID, + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return nil, err + } + + if err = createRepository(sess, doer, u, repo); err != nil { + return nil, err + } + + if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil { + return nil, err + } + + 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) + 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") + if err != nil { + return nil, fmt.Errorf("git update-server-info: %v", stderr) + } + + if err = createDelegateHooks(repoPath); err != nil { + return nil, fmt.Errorf("createDelegateHooks: %v", err) + } + + //Commit repo to get Fork ID + err = sess.Commit() + if err != nil { + return nil, err + } + + oldMode, _ := AccessLevel(doer, oldRepo) + mode, _ := AccessLevel(doer, repo) + + if err = PrepareWebhooks(oldRepo, HookEventFork, &api.ForkPayload{ + Forkee: oldRepo.APIFormat(oldMode), + Repo: repo.APIFormat(mode), + Sender: doer.APIFormat(), + }); err != nil { + log.Error(2, "PrepareWebhooks [repo_id: %d]: %v", oldRepo.ID, err) + } else { + go HookQueue.Add(oldRepo.ID) + } + + if err = repo.UpdateSize(); err != nil { + log.Error(4, "Failed to update size for repository: %v", err) + } + + // Copy LFS meta objects in new session + sess2 := x.NewSession() + defer sess2.Close() + if err = sess2.Begin(); err != nil { + return nil, err + } + + var lfsObjects []*LFSMetaObject + + if err = sess2.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { + return nil, err + } + + for _, v := range lfsObjects { + v.ID = 0 + v.RepositoryID = repo.ID + if _, err = sess2.Insert(v); err != nil { + return nil, err + } + } + + return repo, sess2.Commit() +} + +// GetForks returns all the forks of the repository +func (repo *Repository) GetForks() ([]*Repository, error) { + forks := make([]*Repository, 0, repo.NumForks) + return forks, x.Find(&forks, &Repository{ForkID: repo.ID}) +} + +// GetUserFork return user forked repository from this repository, if not forked return nil +func (repo *Repository) GetUserFork(userID int64) (*Repository, error) { + var forkedRepo Repository + has, err := x.Where("fork_id = ?", repo.ID).And("owner_id = ?", userID).Get(&forkedRepo) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + return &forkedRepo, nil +} diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index e32d5a4b25fc7..1595070f70177 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -1,605 +1,605 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Copyright 2017 The Gitea 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 auth - -import ( - "net/url" - "strings" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/routers/utils" - - "github.com/Unknwon/com" - "github.com/go-macaron/binding" - macaron "gopkg.in/macaron.v1" -) - -// _______________________________________ _________.______________________ _______________.___. -// \______ \_ _____/\______ \_____ \ / _____/| \__ ___/\_____ \\______ \__ | | -// | _/| __)_ | ___// | \ \_____ \ | | | | / | \| _// | | -// | | \| \ | | / | \/ \| | | | / | \ | \\____ | -// |____|_ /_______ / |____| \_______ /_______ /|___| |____| \_______ /____|_ // ______| -// \/ \/ \/ \/ \/ \/ \/ - -// CreateRepoForm form for creating repository -type CreateRepoForm struct { - UID int64 `binding:"Required"` - RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` - Private bool - Description string `binding:"MaxSize(255)"` - AutoInit bool - Gitignores string - IssueLabels string - License string - Readme string -} - -// Validate validates the fields -func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// MigrateRepoForm form for migrating repository -type MigrateRepoForm struct { - // required: true - CloneAddr string `json:"clone_addr" binding:"Required"` - AuthUsername string `json:"auth_username"` - AuthPassword string `json:"auth_password"` - // required: true - UID int64 `json:"uid" binding:"Required"` - // required: true - RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` - Mirror bool `json:"mirror"` - Private bool `json:"private"` - Description string `json:"description" binding:"MaxSize(255)"` -} - -// Validate validates the fields -func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ParseRemoteAddr checks if given remote address is valid, -// and returns composed URL with needed username and password. -// It also checks if given user has permission when remote address -// is actually a local path. -func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { - remoteAddr := strings.TrimSpace(f.CloneAddr) - - // Remote address can be HTTP/HTTPS/Git URL or local path. - if strings.HasPrefix(remoteAddr, "http://") || - strings.HasPrefix(remoteAddr, "https://") || - strings.HasPrefix(remoteAddr, "git://") { - u, err := url.Parse(remoteAddr) - if err != nil { - return "", models.ErrInvalidCloneAddr{IsURLError: true} - } - if len(f.AuthUsername)+len(f.AuthPassword) > 0 { - u.User = url.UserPassword(f.AuthUsername, f.AuthPassword) - } - remoteAddr = u.String() - } else if !user.CanImportLocal() { - return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true} - } else if !com.IsDir(remoteAddr) { - return "", models.ErrInvalidCloneAddr{IsInvalidPath: true} - } - - return remoteAddr, nil -} - -// RepoSettingForm form for changing repository settings -type RepoSettingForm struct { - RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` - Description string `binding:"MaxSize(255)"` - Website string `binding:"ValidUrl;MaxSize(255)"` - Interval string - MirrorAddress string - Private bool - EnablePrune bool - - // Advanced settings - EnableWiki bool - EnableExternalWiki bool - ExternalWikiURL string - EnableIssues bool - EnableExternalTracker bool - ExternalTrackerURL string - TrackerURLFormat string - TrackerIssueStyle string - EnablePulls bool - PullsIgnoreWhitespace bool - PullsAllowMerge bool - PullsAllowRebase bool - PullsAllowRebaseMerge bool - PullsAllowSquash bool - EnableTimetracker bool - AllowOnlyContributorsToTrackTime bool - EnableIssueDependencies bool - IsArchived bool - - // Admin settings - EnableHealthCheck bool - EnableCloseIssuesViaCommitInAnyBranch bool -} - -// Validate validates the fields -func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// __________ .__ -// \______ \____________ ____ ____ | |__ -// | | _/\_ __ \__ \ / \_/ ___\| | \ -// | | \ | | \// __ \| | \ \___| Y \ -// |______ / |__| (____ /___| /\___ >___| / -// \/ \/ \/ \/ \/ - -// ProtectBranchForm form for changing protected branch settings -type ProtectBranchForm struct { - Protected bool - EnableWhitelist bool - WhitelistUsers string - WhitelistTeams string - EnableMergeWhitelist bool - MergeWhitelistUsers string - MergeWhitelistTeams string - RequiredApprovals int64 - ApprovalsWhitelistUsers string - ApprovalsWhitelistTeams string -} - -// Validate validates the fields -func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// __ __ ___. .__ .__ __ -// / \ / \ ____\_ |__ | |__ | |__ ____ | | __ -// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ / -// \ /\ ___/| \_\ \ Y \ Y ( <_> ) < -// \__/\ / \___ >___ /___| /___| /\____/|__|_ \ -// \/ \/ \/ \/ \/ \/ - -// WebhookForm form for changing web hook -type WebhookForm struct { - Events string - Create bool - Delete bool - Fork bool - Issues bool - IssueComment bool - Release bool - Push bool - PullRequest bool - Repository bool - Active bool -} - -// PushOnly if the hook will be triggered when push -func (f WebhookForm) PushOnly() bool { - return f.Events == "push_only" -} - -// SendEverything if the hook will be triggered any event -func (f WebhookForm) SendEverything() bool { - return f.Events == "send_everything" -} - -// ChooseEvents if the hook will be triggered choose events -func (f WebhookForm) ChooseEvents() bool { - return f.Events == "choose_events" -} - -// NewWebhookForm form for creating web hook -type NewWebhookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - ContentType int `binding:"Required"` - Secret string - WebhookForm -} - -// Validate validates the fields -func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// NewGogshookForm form for creating gogs hook -type NewGogshookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - ContentType int `binding:"Required"` - Secret string - WebhookForm -} - -// Validate validates the fields -func (f *NewGogshookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// NewSlackHookForm form for creating slack hook -type NewSlackHookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - Channel string `binding:"Required"` - Username string - IconURL string - Color string - WebhookForm -} - -// Validate validates the fields -func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// HasInvalidChannel validates the channel name is in the right format -func (f NewSlackHookForm) HasInvalidChannel() bool { - return !utils.IsValidSlackChannel(f.Channel) -} - -// NewDiscordHookForm form for creating discord hook -type NewDiscordHookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - Username string - IconURL string - WebhookForm -} - -// Validate validates the fields -func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// NewDingtalkHookForm form for creating dingtalk hook -type NewDingtalkHookForm struct { - PayloadURL string `binding:"Required;ValidUrl"` - WebhookForm -} - -// Validate validates the fields -func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// .___ -// | | ______ ________ __ ____ -// | |/ ___// ___/ | \_/ __ \ -// | |\___ \ \___ \| | /\ ___/ -// |___/____ >____ >____/ \___ > -// \/ \/ \/ - -// CreateIssueForm form for creating issue -type CreateIssueForm struct { - Title string `binding:"Required;MaxSize(255)"` - LabelIDs string `form:"label_ids"` - AssigneeIDs string `form:"assignee_ids"` - Ref string `form:"ref"` - MilestoneID int64 - AssigneeID int64 - Content string - Files []string -} - -// Validate validates the fields -func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// CreateCommentForm form for creating comment -type CreateCommentForm struct { - Content string - Status string `binding:"OmitEmpty;In(reopen,close)"` - Files []string -} - -// Validate validates the fields -func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ReactionForm form for adding and removing reaction -type ReactionForm struct { - Content string `binding:"Required;In(+1,-1,laugh,confused,heart,hooray)"` -} - -// Validate validates the fields -func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// _____ .__.__ __ -// / \ |__| | ____ _______/ |_ ____ ____ ____ -// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ -// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/ -// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ > -// \/ \/ \/ \/ \/ - -// CreateMilestoneForm form for creating milestone -type CreateMilestoneForm struct { - Title string `binding:"Required;MaxSize(50)"` - Content string - Deadline string -} - -// Validate validates the fields -func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// .____ ___. .__ -// | | _____ \_ |__ ____ | | -// | | \__ \ | __ \_/ __ \| | -// | |___ / __ \| \_\ \ ___/| |__ -// |_______ (____ /___ /\___ >____/ -// \/ \/ \/ \/ - -// CreateLabelForm form for creating label -type CreateLabelForm struct { - ID int64 - Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` - Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` - Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"` -} - -// Validate validates the fields -func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// InitializeLabelsForm form for initializing labels -type InitializeLabelsForm struct { - TemplateName string `binding:"Required"` -} - -// Validate validates the fields -func (f *InitializeLabelsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// __________ .__ .__ __________ __ -// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_ -// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ -// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | | -// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__| -// \/ \/ |__| \/ \/ - -// MergePullRequestForm form for merging Pull Request -// swagger:model MergePullRequestOption -type MergePullRequestForm struct { - // required: true - // enum: merge,rebase,rebase-merge,squash - Do string `binding:"Required;In(merge,rebase,rebase-merge,squash)"` - MergeTitleField string - MergeMessageField string -} - -// Validate validates the fields -func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// CodeCommentForm form for adding code comments for PRs -type CodeCommentForm struct { - Content string `binding:"Required"` - Side string `binding:"Required;In(previous,proposed)"` - Line int64 - TreePath string `form:"path" binding:"Required"` - IsReview bool `form:"is_review"` - Reply int64 `form:"reply"` -} - -// Validate validates the fields -func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// SubmitReviewForm for submitting a finished code review -type SubmitReviewForm struct { - Content string - Type string `binding:"Required;In(approve,comment,reject)"` -} - -// Validate validates the fields -func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ReviewType will return the corresponding reviewtype for type -func (f SubmitReviewForm) ReviewType() models.ReviewType { - switch f.Type { - case "approve": - return models.ReviewTypeApprove - case "comment": - return models.ReviewTypeComment - case "reject": - return models.ReviewTypeReject - default: - return models.ReviewTypeUnknown - } -} - -// HasEmptyContent checks if the content of the review form is empty. -func (f SubmitReviewForm) HasEmptyContent() bool { - reviewType := f.ReviewType() - - return (reviewType == models.ReviewTypeComment || reviewType == models.ReviewTypeReject) && - len(strings.TrimSpace(f.Content)) == 0 -} - -// __________ .__ -// \______ \ ____ | | ____ _____ ______ ____ -// | _// __ \| | _/ __ \\__ \ / ___// __ \ -// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/ -// |____|_ /\___ >____/\___ >____ /____ >\___ > -// \/ \/ \/ \/ \/ \/ - -// NewReleaseForm form for creating release -type NewReleaseForm struct { - TagName string `binding:"Required;GitRefName"` - Target string `form:"tag_target" binding:"Required"` - Title string `binding:"Required"` - Content string - Draft string - Prerelease bool - Files []string -} - -// Validate validates the fields -func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// EditReleaseForm form for changing release -type EditReleaseForm struct { - Title string `form:"title" binding:"Required"` - Content string `form:"content"` - Draft string `form:"draft"` - Prerelease bool `form:"prerelease"` - Files []string -} - -// Validate validates the fields -func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// __ __.__ __ .__ -// / \ / \__| | _|__| -// \ \/\/ / | |/ / | -// \ /| | <| | -// \__/\ / |__|__|_ \__| -// \/ \/ - -// NewWikiForm form for creating wiki -type NewWikiForm struct { - Title string `binding:"Required"` - Content string `binding:"Required"` - Message string -} - -// Validate validates the fields -// FIXME: use code generation to generate this method. -func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ___________ .___.__ __ -// \_ _____/ __| _/|__|/ |_ -// | __)_ / __ | | \ __\ -// | \/ /_/ | | || | -// /_______ /\____ | |__||__| -// \/ \/ - -// EditRepoFileForm form for changing repository file -type EditRepoFileForm struct { - TreePath string `binding:"Required;MaxSize(500)"` - Content string `binding:"Required"` - CommitSummary string `binding:"MaxSize(100)"` - CommitMessage string - CommitChoice string `binding:"Required;MaxSize(50)"` - NewBranchName string `binding:"GitRefName;MaxSize(100)"` - LastCommit string -} - -// Validate validates the fields -func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// EditPreviewDiffForm form for changing preview diff -type EditPreviewDiffForm struct { - Content string -} - -// Validate validates the fields -func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ____ ___ .__ .___ -// | | \______ | | _________ __| _/ -// | | /\____ \| | / _ \__ \ / __ | -// | | / | |_> > |_( <_> ) __ \_/ /_/ | -// |______/ | __/|____/\____(____ /\____ | -// |__| \/ \/ -// - -// UploadRepoFileForm form for uploading repository file -type UploadRepoFileForm struct { - TreePath string `binding:"MaxSize(500)"` - CommitSummary string `binding:"MaxSize(100)"` - CommitMessage string - CommitChoice string `binding:"Required;MaxSize(50)"` - NewBranchName string `binding:"GitRefName;MaxSize(100)"` - Files []string -} - -// Validate validates the fields -func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// RemoveUploadFileForm form for removing uploaded file -type RemoveUploadFileForm struct { - File string `binding:"Required;MaxSize(50)"` -} - -// Validate validates the fields -func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ________ .__ __ -// \______ \ ____ | | _____/ |_ ____ -// | | \_/ __ \| | _/ __ \ __\/ __ \ -// | ` \ ___/| |_\ ___/| | \ ___/ -// /_______ /\___ >____/\___ >__| \___ > -// \/ \/ \/ \/ - -// DeleteRepoFileForm form for deleting repository file -type DeleteRepoFileForm struct { - CommitSummary string `binding:"MaxSize(100)"` - CommitMessage string - CommitChoice string `binding:"Required;MaxSize(50)"` - NewBranchName string `binding:"GitRefName;MaxSize(100)"` -} - -// Validate validates the fields -func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// ___________.__ ___________ __ -// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________ -// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ -// | | | | Y Y \ ___/ | | | | \// __ \\ \___| <\ ___/| | \/ -// |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__| -// \/ \/ \/ \/ \/ \/ - -// AddTimeManuallyForm form that adds spent time manually. -type AddTimeManuallyForm struct { - Hours int `binding:"Range(0,1000)"` - Minutes int `binding:"Range(0,1000)"` -} - -// Validate validates the fields -func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} - -// SaveTopicForm form for save topics for repository -type SaveTopicForm struct { - Topics []string `binding:"topics;Required;"` -} - -// DeadlineForm hold the validation rules for deadlines -type DeadlineForm struct { - DateString string `form:"date" binding:"Required;Size(10)"` -} - -// Validate validates the fields -func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea 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 auth + +import ( + "net/url" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/routers/utils" + + "github.com/Unknwon/com" + "github.com/go-macaron/binding" + macaron "gopkg.in/macaron.v1" +) + +// _______________________________________ _________.______________________ _______________.___. +// \______ \_ _____/\______ \_____ \ / _____/| \__ ___/\_____ \\______ \__ | | +// | _/| __)_ | ___// | \ \_____ \ | | | | / | \| _// | | +// | | \| \ | | / | \/ \| | | | / | \ | \\____ | +// |____|_ /_______ / |____| \_______ /_______ /|___| |____| \_______ /____|_ // ______| +// \/ \/ \/ \/ \/ \/ \/ + +// CreateRepoForm form for creating repository +type CreateRepoForm struct { + UID int64 `binding:"Required"` + RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Private bool + Description string `binding:"MaxSize(255)"` + AutoInit bool + Gitignores string + IssueLabels string + License string + Readme string +} + +// Validate validates the fields +func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// MigrateRepoForm form for migrating repository +type MigrateRepoForm struct { + // required: true + CloneAddr string `json:"clone_addr" binding:"Required"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + // required: true + UID int64 `json:"uid" binding:"Required"` + // required: true + RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` + Mirror bool `json:"mirror"` + Private bool `json:"private"` + Description string `json:"description" binding:"MaxSize(255)"` +} + +// Validate validates the fields +func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ParseRemoteAddr checks if given remote address is valid, +// and returns composed URL with needed username and password. +// It also checks if given user has permission when remote address +// is actually a local path. +func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { + remoteAddr := strings.TrimSpace(f.CloneAddr) + + // Remote address can be HTTP/HTTPS/Git URL or local path. + if strings.HasPrefix(remoteAddr, "http://") || + strings.HasPrefix(remoteAddr, "https://") || + strings.HasPrefix(remoteAddr, "git://") { + u, err := url.Parse(remoteAddr) + if err != nil { + return "", models.ErrInvalidCloneAddr{IsURLError: true} + } + if len(f.AuthUsername)+len(f.AuthPassword) > 0 { + u.User = url.UserPassword(f.AuthUsername, f.AuthPassword) + } + remoteAddr = u.String() + } else if !user.CanImportLocal() { + return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true} + } else if !com.IsDir(remoteAddr) { + return "", models.ErrInvalidCloneAddr{IsInvalidPath: true} + } + + return remoteAddr, nil +} + +// RepoSettingForm form for changing repository settings +type RepoSettingForm struct { + RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Description string `binding:"MaxSize(255)"` + Website string `binding:"ValidUrl;MaxSize(255)"` + Interval string + MirrorAddress string + Private bool + EnablePrune bool + + // Advanced settings + EnableWiki bool + EnableExternalWiki bool + ExternalWikiURL string + EnableIssues bool + EnableExternalTracker bool + ExternalTrackerURL string + TrackerURLFormat string + TrackerIssueStyle string + EnablePulls bool + PullsIgnoreWhitespace bool + PullsAllowMerge bool + PullsAllowRebase bool + PullsAllowRebaseMerge bool + PullsAllowSquash bool + EnableTimetracker bool + AllowOnlyContributorsToTrackTime bool + EnableIssueDependencies bool + IsArchived bool + + // Admin settings + EnableHealthCheck bool + EnableCloseIssuesViaCommitInAnyBranch bool +} + +// Validate validates the fields +func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// __________ .__ +// \______ \____________ ____ ____ | |__ +// | | _/\_ __ \__ \ / \_/ ___\| | \ +// | | \ | | \// __ \| | \ \___| Y \ +// |______ / |__| (____ /___| /\___ >___| / +// \/ \/ \/ \/ \/ + +// ProtectBranchForm form for changing protected branch settings +type ProtectBranchForm struct { + Protected bool + EnableWhitelist bool + WhitelistUsers string + WhitelistTeams string + EnableMergeWhitelist bool + MergeWhitelistUsers string + MergeWhitelistTeams string + RequiredApprovals int64 + ApprovalsWhitelistUsers string + ApprovalsWhitelistTeams string +} + +// Validate validates the fields +func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// __ __ ___. .__ .__ __ +// / \ / \ ____\_ |__ | |__ | |__ ____ | | __ +// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ / +// \ /\ ___/| \_\ \ Y \ Y ( <_> ) < +// \__/\ / \___ >___ /___| /___| /\____/|__|_ \ +// \/ \/ \/ \/ \/ \/ + +// WebhookForm form for changing web hook +type WebhookForm struct { + Events string + Create bool + Delete bool + Fork bool + Issues bool + IssueComment bool + Release bool + Push bool + PullRequest bool + Repository bool + Active bool +} + +// PushOnly if the hook will be triggered when push +func (f WebhookForm) PushOnly() bool { + return f.Events == "push_only" +} + +// SendEverything if the hook will be triggered any event +func (f WebhookForm) SendEverything() bool { + return f.Events == "send_everything" +} + +// ChooseEvents if the hook will be triggered choose events +func (f WebhookForm) ChooseEvents() bool { + return f.Events == "choose_events" +} + +// NewWebhookForm form for creating web hook +type NewWebhookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + ContentType int `binding:"Required"` + Secret string + WebhookForm +} + +// Validate validates the fields +func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// NewGogshookForm form for creating gogs hook +type NewGogshookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + ContentType int `binding:"Required"` + Secret string + WebhookForm +} + +// Validate validates the fields +func (f *NewGogshookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// NewSlackHookForm form for creating slack hook +type NewSlackHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + Channel string `binding:"Required"` + Username string + IconURL string + Color string + WebhookForm +} + +// Validate validates the fields +func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// HasInvalidChannel validates the channel name is in the right format +func (f NewSlackHookForm) HasInvalidChannel() bool { + return !utils.IsValidSlackChannel(f.Channel) +} + +// NewDiscordHookForm form for creating discord hook +type NewDiscordHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + Username string + IconURL string + WebhookForm +} + +// Validate validates the fields +func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// NewDingtalkHookForm form for creating dingtalk hook +type NewDingtalkHookForm struct { + PayloadURL string `binding:"Required;ValidUrl"` + WebhookForm +} + +// Validate validates the fields +func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// .___ +// | | ______ ________ __ ____ +// | |/ ___// ___/ | \_/ __ \ +// | |\___ \ \___ \| | /\ ___/ +// |___/____ >____ >____/ \___ > +// \/ \/ \/ + +// CreateIssueForm form for creating issue +type CreateIssueForm struct { + Title string `binding:"Required;MaxSize(255)"` + LabelIDs string `form:"label_ids"` + AssigneeIDs string `form:"assignee_ids"` + Ref string `form:"ref"` + MilestoneID int64 + AssigneeID int64 + Content string + Files []string +} + +// Validate validates the fields +func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// CreateCommentForm form for creating comment +type CreateCommentForm struct { + Content string + Status string `binding:"OmitEmpty;In(reopen,close)"` + Files []string +} + +// Validate validates the fields +func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ReactionForm form for adding and removing reaction +type ReactionForm struct { + Content string `binding:"Required;In(+1,-1,laugh,confused,heart,hooray)"` +} + +// Validate validates the fields +func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// _____ .__.__ __ +// / \ |__| | ____ _______/ |_ ____ ____ ____ +// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ +// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/ +// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ > +// \/ \/ \/ \/ \/ + +// CreateMilestoneForm form for creating milestone +type CreateMilestoneForm struct { + Title string `binding:"Required;MaxSize(50)"` + Content string + Deadline string +} + +// Validate validates the fields +func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// .____ ___. .__ +// | | _____ \_ |__ ____ | | +// | | \__ \ | __ \_/ __ \| | +// | |___ / __ \| \_\ \ ___/| |__ +// |_______ (____ /___ /\___ >____/ +// \/ \/ \/ \/ + +// CreateLabelForm form for creating label +type CreateLabelForm struct { + ID int64 + Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` + Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` + Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"` +} + +// Validate validates the fields +func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// InitializeLabelsForm form for initializing labels +type InitializeLabelsForm struct { + TemplateName string `binding:"Required"` +} + +// Validate validates the fields +func (f *InitializeLabelsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// __________ .__ .__ __________ __ +// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_ +// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ +// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | | +// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__| +// \/ \/ |__| \/ \/ + +// MergePullRequestForm form for merging Pull Request +// swagger:model MergePullRequestOption +type MergePullRequestForm struct { + // required: true + // enum: merge,rebase,rebase-merge,squash + Do string `binding:"Required;In(merge,rebase,rebase-merge,squash)"` + MergeTitleField string + MergeMessageField string +} + +// Validate validates the fields +func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// CodeCommentForm form for adding code comments for PRs +type CodeCommentForm struct { + Content string `binding:"Required"` + Side string `binding:"Required;In(previous,proposed)"` + Line int64 + TreePath string `form:"path" binding:"Required"` + IsReview bool `form:"is_review"` + Reply int64 `form:"reply"` +} + +// Validate validates the fields +func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// SubmitReviewForm for submitting a finished code review +type SubmitReviewForm struct { + Content string + Type string `binding:"Required;In(approve,comment,reject)"` +} + +// Validate validates the fields +func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ReviewType will return the corresponding reviewtype for type +func (f SubmitReviewForm) ReviewType() models.ReviewType { + switch f.Type { + case "approve": + return models.ReviewTypeApprove + case "comment": + return models.ReviewTypeComment + case "reject": + return models.ReviewTypeReject + default: + return models.ReviewTypeUnknown + } +} + +// HasEmptyContent checks if the content of the review form is empty. +func (f SubmitReviewForm) HasEmptyContent() bool { + reviewType := f.ReviewType() + + return (reviewType == models.ReviewTypeComment || reviewType == models.ReviewTypeReject) && + len(strings.TrimSpace(f.Content)) == 0 +} + +// __________ .__ +// \______ \ ____ | | ____ _____ ______ ____ +// | _// __ \| | _/ __ \\__ \ / ___// __ \ +// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/ +// |____|_ /\___ >____/\___ >____ /____ >\___ > +// \/ \/ \/ \/ \/ \/ + +// NewReleaseForm form for creating release +type NewReleaseForm struct { + TagName string `binding:"Required;GitRefName"` + Target string `form:"tag_target" binding:"Required"` + Title string `binding:"Required"` + Content string + Draft string + Prerelease bool + Files []string +} + +// Validate validates the fields +func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// EditReleaseForm form for changing release +type EditReleaseForm struct { + Title string `form:"title" binding:"Required"` + Content string `form:"content"` + Draft string `form:"draft"` + Prerelease bool `form:"prerelease"` + Files []string +} + +// Validate validates the fields +func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// __ __.__ __ .__ +// / \ / \__| | _|__| +// \ \/\/ / | |/ / | +// \ /| | <| | +// \__/\ / |__|__|_ \__| +// \/ \/ + +// NewWikiForm form for creating wiki +type NewWikiForm struct { + Title string `binding:"Required"` + Content string `binding:"Required"` + Message string +} + +// Validate validates the fields +// FIXME: use code generation to generate this method. +func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ___________ .___.__ __ +// \_ _____/ __| _/|__|/ |_ +// | __)_ / __ | | \ __\ +// | \/ /_/ | | || | +// /_______ /\____ | |__||__| +// \/ \/ + +// EditRepoFileForm form for changing repository file +type EditRepoFileForm struct { + TreePath string `binding:"Required;MaxSize(500)"` + Content string `binding:"Required"` + CommitSummary string `binding:"MaxSize(100)"` + CommitMessage string + CommitChoice string `binding:"Required;MaxSize(50)"` + NewBranchName string `binding:"GitRefName;MaxSize(100)"` + LastCommit string +} + +// Validate validates the fields +func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// EditPreviewDiffForm form for changing preview diff +type EditPreviewDiffForm struct { + Content string +} + +// Validate validates the fields +func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ____ ___ .__ .___ +// | | \______ | | _________ __| _/ +// | | /\____ \| | / _ \__ \ / __ | +// | | / | |_> > |_( <_> ) __ \_/ /_/ | +// |______/ | __/|____/\____(____ /\____ | +// |__| \/ \/ +// + +// UploadRepoFileForm form for uploading repository file +type UploadRepoFileForm struct { + TreePath string `binding:"MaxSize(500)"` + CommitSummary string `binding:"MaxSize(100)"` + CommitMessage string + CommitChoice string `binding:"Required;MaxSize(50)"` + NewBranchName string `binding:"GitRefName;MaxSize(100)"` + Files []string +} + +// Validate validates the fields +func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// RemoveUploadFileForm form for removing uploaded file +type RemoveUploadFileForm struct { + File string `binding:"Required;MaxSize(50)"` +} + +// Validate validates the fields +func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ________ .__ __ +// \______ \ ____ | | _____/ |_ ____ +// | | \_/ __ \| | _/ __ \ __\/ __ \ +// | ` \ ___/| |_\ ___/| | \ ___/ +// /_______ /\___ >____/\___ >__| \___ > +// \/ \/ \/ \/ + +// DeleteRepoFileForm form for deleting repository file +type DeleteRepoFileForm struct { + CommitSummary string `binding:"MaxSize(100)"` + CommitMessage string + CommitChoice string `binding:"Required;MaxSize(50)"` + NewBranchName string `binding:"GitRefName;MaxSize(100)"` +} + +// Validate validates the fields +func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// ___________.__ ___________ __ +// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________ +// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \ +// | | | | Y Y \ ___/ | | | | \// __ \\ \___| <\ ___/| | \/ +// |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__| +// \/ \/ \/ \/ \/ \/ + +// AddTimeManuallyForm form that adds spent time manually. +type AddTimeManuallyForm struct { + Hours int `binding:"Range(0,1000)"` + Minutes int `binding:"Range(0,1000)"` +} + +// Validate validates the fields +func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +// SaveTopicForm form for save topics for repository +type SaveTopicForm struct { + Topics []string `binding:"topics;Required;"` +} + +// DeadlineForm hold the validation rules for deadlines +type DeadlineForm struct { + DateString string `form:"date" binding:"Required;Size(10)"` +} + +// Validate validates the fields +func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ce1f543e3190c..6550490b7d54d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1,1775 +1,1775 @@ -app_desc = A painless, self-hosted Git service - -home = Home -dashboard = Dashboard -explore = Explore -help = Help -sign_in = Sign In -sign_in_with = Sign In With -sign_out = Sign Out -sign_up = Register -link_account = Link Account -register = Register -website = Website -version = Version -page = Page -template = Template -language = Language -notifications = Notifications -create_new = Create… -user_profile_and_more = Profile and Settings… -signed_in_as = Signed in as -enable_javascript = This website works better with JavaScript. - -username = Username -email = Email Address -password = Password -re_type = Re-Type Password -captcha = CAPTCHA -twofa = Two-Factor Authentication -twofa_scratch = Two-Factor Scratch Code -passcode = Passcode - -u2f_insert_key = Insert your security key -u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it. -u2f_press_button = Please press the button on your security key… -u2f_use_twofa = Use a two-factor code from your phone -u2f_error = Could not read your security key. -u2f_unsupported_browser = Your browser does not support U2F security keys. -u2f_error_1 = An unknown error occurred. Please retry. -u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL. -u2f_error_3 = The server could not process your request. -u2f_error_4 = The security key is not permitted for this request. Please make sure that the key is not already registered. -u2f_error_5 = Timeout reached before your key could be read. Please reload this page and retry. -u2f_reload = Reload - -repository = Repository -organization = Organization -mirror = Mirror -new_repo = New Repository -new_migrate = New Migration -new_mirror = New Mirror -new_fork = New Repository Fork -new_org = New Organization -manage_org = Manage Organizations -admin_panel = Site Administration -account_settings = Account Settings -settings = Settings -your_profile = Profile -your_starred = Starred -your_settings = Settings - -all = All -sources = Sources -mirrors = Mirrors -collaborative = Collaborative -forks = Forks - -activities = Activities -pull_requests = Pull Requests -issues = Issues - -cancel = Cancel - -write = Write -preview = Preview -loading = Loading… - -[install] -install = Installation -title = Initial Configuration -docker_helper = If you run Gitea inside Docker, please read the documentation before changing any settings. -requite_db_desc = Gitea requires MySQL, PostgreSQL, MSSQL or SQLite3. -db_title = Database Settings -db_type = Database Type -host = Host -user = Username -password = Password -db_name = Database Name -db_helper = Note to MySQL users: please use the InnoDB storage engine and the 'utf8_general_ci' character set. -ssl_mode = SSL -path = Path -sqlite_helper = File path for the SQLite3 database.
Enter an absolute path if you run Gitea as a service. -err_empty_db_path = The SQLite3 database path cannot be empty. -no_admin_and_disable_registration = You cannot disable user self-registration without creating an administrator account. -err_empty_admin_password = The administrator password cannot be empty. - -general_title = General Settings -app_name = Site Title -app_name_helper = You can enter your company name here. -repo_path = Repository Root Path -repo_path_helper = Remote Git repositories will be saved to this directory. -lfs_path = Git LFS Root Path -lfs_path_helper = Files tracked by Git LFS will be stored in this directory. Leave empty to disable. -run_user = Run As Username -run_user_helper = Enter the operating system username that Gitea runs as. Note that this user must have access to the repository root path. -domain = SSH Server Domain -domain_helper = Domain or host address for SSH clone URLs. -ssh_port = SSH Server Port -ssh_port_helper = Port number your SSH server listens on. Leave empty to disable. -http_port = Gitea HTTP Listen Port -http_port_helper = Port number the Giteas web server will listen on. -app_url = Gitea Base URL -app_url_helper = Base address for HTTP(S) clone URLs and email notifications. -log_root_path = Log Path -log_root_path_helper = Log files will be written to this directory. - -optional_title = Optional Settings -email_title = Email Settings -smtp_host = SMTP Host -smtp_from = Send Email As -smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" format. -mailer_user = SMTP Username -mailer_password = SMTP Password -register_confirm = Require Email Confirmation to Register -mail_notify = Enable Email Notifications -server_service_title = Server and Third-Party Service Settings -offline_mode = Enable Local Mode -offline_mode_popup = Disable third-party content delivery networks and serve all resources locally. -disable_gravatar = Disable Gravatar -disable_gravatar_popup = Disable Gravatar and third-party avatar sources. A default avatar will be used unless a user locally uploads an avatar. -federated_avatar_lookup = Enable Federated Avatars -federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar. -disable_registration = Disable Self-Registration -disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts. -allow_only_external_registration_popup = Allow Registration Only Through External Services -openid_signin = Enable OpenID Sign-In -openid_signin_popup = Enable user sign-in via OpenID. -openid_signup = Enable OpenID Self-Registration -openid_signup_popup = Enable OpenID-based user self-registration. -enable_captcha = Enable CAPTCHA -enable_captcha_popup = Require a CAPTCHA for user self-registration. -require_sign_in_view = Require Sign-In to View Pages -require_sign_in_view_popup = Limit page access to signed-in users. Visitors will only see the 'sign in' and registration pages. -admin_setting_desc = Creating an administrator account is optional. The first registered user will automatically become an administrator. -admin_title = Administrator Account Settings -admin_name = Administrator Username -admin_password = Password -confirm_password = Confirm Password -admin_email = Email Address -install_btn_confirm = Install Gitea -test_git_failed = Could not test 'git' command: %v -sqlite3_not_available = This Gitea version does not support SQLite3. Please download the official binary version from %s (not the 'gobuild' version). -invalid_db_setting = The database settings are invalid: %v -invalid_repo_path = The repository root path is invalid: %v -run_user_not_match = The 'run as' username is not the current username: %s -> %s -save_config_failed = Failed to save configuration: %v -invalid_admin_setting = Administrator account setting is invalid: %v -install_success = Welcome! Thank you for choosing Gitea. Have fun and take care! -invalid_log_root_path = The log path is invalid: %v -default_keep_email_private = Hide Email Addresses by Default -default_keep_email_private_popup = Hide email addresses of new user accounts by default. -default_allow_create_organization = Allow Creation of Organizations by Default -default_allow_create_organization_popup = Allow new user accounts to create organizations by default. -default_enable_timetracking = Enable Time Tracking by Default -default_enable_timetracking_popup = Enable time tracking for new repositories by default. -no_reply_address = Hidden Email Domain -no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'. - -[home] -uname_holder = Username or Email Address -password_holder = Password -switch_dashboard_context = Switch Dashboard Context -my_repos = Repositories -show_more_repos = Show more repositories… -collaborative_repos = Collaborative Repositories -my_orgs = My Organizations -my_mirrors = My Mirrors -view_home = View %s -search_repos = Find a repository… - -issues.in_your_repos = In your repositories - -[explore] -repos = Repositories -users = Users -organizations = Organizations -search = Search -code = Code -repo_no_results = No matching repositories found. -user_no_results = No matching users found. -org_no_results = No matching organizations found. -code_no_results = No source code matching your search term found. -code_search_results = Search results for '%s' - -[auth] -create_new_account = Register Account -register_helper_msg = Already have an account? Sign in now! -social_register_helper_msg = Already have an account? Link it now! -disable_register_prompt = Registration is disabled. Please contact your site administrator. -disable_register_mail = Email confirmation for registration is disabled. -remember_me = Remember Me -forgot_password_title= Forgot Password -forgot_password = Forgot password? -sign_up_now = Need an account? Register now. -sign_up_successful = Account was successfully created. -confirmation_mail_sent_prompt = A new confirmation email has been sent to %s. Please check your inbox within the next %s to complete the registration process. -must_change_password = Update your password -allow_password_change = Require user to change password (recommended) -reset_password_mail_sent_prompt = A confirmation email has been sent to %s. Please check your inbox within the next %s to complete the password reset process. -active_your_account = Activate Your Account -account_activated = Account has been activated -prohibit_login = Sign In Prohibited -prohibit_login_desc = Your account is prohibited to sign in, please contact your site administrator. -resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again. -has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (%s). If you haven't received a confirmation email or need to resend a new one, please click on the button below. -resend_mail = Click here to resend your activation email -email_not_associate = The email address is not associated with any account. -send_reset_mail = Click here to resend your password reset email -reset_password = Reset Your Password -invalid_code = Your confirmation code is invalid or has expired. -reset_password_helper = Click here to reset your password -password_too_short = Password length cannot be less than %d characters. -non_local_account = Non-local users can not update their password through the Gitea web interface. -verify = Verify -scratch_code = Scratch code -use_scratch_code = Use a scratch code -twofa_scratch_used = You have used your scratch code. You have been redirected to the two-factor settings page so you may remove your device enrollment or generate a new scratch code. -twofa_passcode_incorrect = Your passcode is incorrect. If you misplaced your device, use your scratch code to sign in. -twofa_scratch_token_incorrect = Your scratch code is incorrect. -login_userpass = Sign In -login_openid = OpenID -oauth_signup_tab = Register New Account -oauth_signup_title = Add Email and Password (for Account Recovery) -oauth_signup_submit = Complete Account -oauth_signin_tab = Link to Existing Account -oauth_signin_title = Sign In to Authorize Linked Account -oauth_signin_submit = Link Account -openid_connect_submit = Connect -openid_connect_title = Connect to an existing account -openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here. -openid_register_title = Create new account -openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here. -openid_signin_desc = Enter your OpenID URI. For example: https://anne.me, bob.openid.org.cn or gnusocial.net/carry. -disable_forgot_password_mail = Password reset is disabled. Please contact your site administrator. -email_domain_blacklisted = You cannot register with your email address. - -[mail] -activate_account = Please activate your account -activate_email = Verify your email address -reset_password = Reset your password -register_success = Registration successful -register_notify = Welcome to Gitea - -[modal] -yes = Yes -no = No -modify = Update - -[form] -UserName = Username -RepoName = Repository name -Email = Email address -Password = Password -Retype = Re-Type Password -SSHTitle = SSH key name -HttpsUrl = HTTPS URL -PayloadUrl = Payload URL -TeamName = Team name -AuthName = Authorization name -AdminEmail = Admin email - -NewBranchName = New branch name -CommitSummary = Commit summary -CommitMessage = Commit message -CommitChoice = Commit choice -TreeName = File path -Content = Content - -require_error = ` cannot be empty.` -alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` -alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` -git_ref_name_error = ` must be a well-formed Git reference name.` -size_error = ` must be size %s.` -min_size_error = ` must contain at least %s characters.` -max_size_error = ` must contain at most %s characters.` -email_error = ` is not a valid email address.` -url_error = ` is not a valid URL.` -include_error = ` must contain substring '%s'.` -unknown_error = Unknown error: -captcha_incorrect = The CAPTCHA code is incorrect. -password_not_match = The passwords do not match. - -username_been_taken = The username is already taken. -repo_name_been_taken = The repository name is already used. -org_name_been_taken = The organization name is already taken. -team_name_been_taken = The team name is already taken. -team_no_units_error = Allow access to at least one repository section. -email_been_used = The email address is already used. -openid_been_used = The OpenID address '%s' is already used. -username_password_incorrect = Username or password is incorrect. -enterred_invalid_repo_name = The repository name you entered is incorrect. -enterred_invalid_owner_name = The new owner name is not valid. -enterred_invalid_password = The password you entered is incorrect. -user_not_exist = The user does not exist. -last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team. -cannot_add_org_to_team = An organization cannot be added as a team member. - -invalid_ssh_key = Can not verify your SSH key: %s -invalid_gpg_key = Can not verify your GPG key: %s -unable_verify_ssh_key = "Can not verify the SSH key; double-check it for mistakes." -auth_failed = Authentication failed: %v - -still_own_repo = "Your account owns one or more repositories; delete or transfer them first." -still_has_org = "Your account is a member of one or more organizations; leave them first." -org_still_own_repo = "This organization still owns one or more repositories; delete or transfer them first." - -target_branch_not_exist = Target branch does not exist. - -[user] -change_avatar = Change your avatar… -join_on = Joined on -repositories = Repositories -activity = Public Activity -followers = Followers -starred = Starred Repositories -following = Following -follow = Follow -unfollow = Unfollow -heatmap.loading = Loading Heatmap… - -form.name_reserved = The username '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. - -[settings] -profile = Profile -account = Account -password = Password -security = Security -avatar = Avatar -ssh_gpg_keys = SSH / GPG Keys -social = Social Accounts -applications = Applications -orgs = Manage Organizations -repos = Repositories -delete = Delete Account -twofa = Two-Factor Authentication -account_link = Linked Accounts -organization = Organizations -uid = Uid -u2f = Security Keys - -public_profile = Public Profile -profile_desc = Your email address will be used for notifications and other operations. -password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details. -full_name = Full Name -website = Website -location = Location -update_theme = Update Theme -update_profile = Update Profile -update_profile_success = Your profile has been updated. -change_username = Your username has been changed. -change_username_prompt = Note: username changes also change your account URL. -continue = Continue -cancel = Cancel -language = Language -ui = Theme - -lookup_avatar_by_mail = Look Up Avatar by Email Address -federated_avatar_lookup = Federated Avatar Lookup -enable_custom_avatar = Use Custom Avatar -choose_new_avatar = Choose new avatar -update_avatar = Update Avatar -delete_current_avatar = Delete Current Avatar -uploaded_avatar_not_a_image = The uploaded file is not an image. -update_avatar_success = Your avatar has been updated. - -change_password = Update Password -old_password = Current Password -new_password = New Password -retype_new_password = Re-Type New Password -password_incorrect = The current password is incorrect. -change_password_success = Your password has been updated. Sign in using your new password from now on. -password_change_disabled = Non-local users can not update their password through the Gitea web interface. - -emails = Email Addresses -manage_emails = Manage Email Addresses -manage_themes = Select default theme -manage_openid = Manage OpenID Addresses -email_desc = Your primary email address will be used for notifications and other operations. -theme_desc = This will be your default theme across the site. -primary = Primary -primary_email = Make Primary -delete_email = Remove -email_deletion = Remove Email Address -email_deletion_desc = The email address and related information will be removed from your account. Git commits by this email address will remain unchanged. Continue? -email_deletion_success = The email address has been removed. -theme_update_success = Your theme was updated. -theme_update_error = The selected theme does not exist. -openid_deletion = Remove OpenID Address -openid_deletion_desc = Removing this OpenID address from your account will prevent you from signing in with it. Continue? -openid_deletion_success = The OpenID address has been removed. -add_new_email = Add New Email Address -add_new_openid = Add New OpenID URI -add_email = Add Email Address -add_openid = Add OpenID URI -add_email_confirmation_sent = A confirmation email has been sent to '%s'. Please check your inbox within the next %s to confirm your email address. -add_email_success = The new email address has been added. -add_openid_success = The new OpenID address has been added. -keep_email_private = Hide Email Address -keep_email_private_popup = Your email address will be hidden from other users. -openid_desc = OpenID lets you delegate authentication to an external provider. - -manage_ssh_keys = Manage SSH Keys -manage_gpg_keys = Manage GPG Keys -add_key = Add Key -ssh_desc = These public SSH keys are associated with your account. The corresponding private keys allow full access to your repositories. -gpg_desc = These public GPG keys are associated with your account. Keep your private keys safe as they allow commits to be verified. -ssh_helper = Need help? Have a look at GitHub's guide to create your own SSH keys or solve common problems you may encounter using SSH. -gpg_helper = Need help? Have a look at GitHub's guide about GPG. -add_new_key = Add SSH Key -add_new_gpg_key = Add GPG Key -ssh_key_been_used = This SSH key has already been added to the server. -ssh_key_name_used = An SSH key with same name is already added to your account. -gpg_key_id_used = A public GPG key with same ID already exists. -gpg_no_key_email_found = This GPG key is not usable with any email address associated with your account. -subkeys = Subkeys -key_id = Key ID -key_name = Key Name -key_content = Content -add_key_success = The SSH key '%s' has been added. -add_gpg_key_success = The GPG key '%s' has been added. -delete_key = Remove -ssh_key_deletion = Remove SSH Key -gpg_key_deletion = Remove GPG Key -ssh_key_deletion_desc = Removing an SSH key revokes its access to your account. Continue? -gpg_key_deletion_desc = Removing a GPG key un-verifies commits signed by it. Continue? -ssh_key_deletion_success = The SSH key has been removed. -gpg_key_deletion_success = The GPG key has been removed. -add_on = Added on -valid_until = Valid until -valid_forever = Valid forever -last_used = Last used on -no_activity = No recent activity -can_read_info = Read -can_write_info = Write -key_state_desc = This key has been used in the last 7 days -token_state_desc = This token has been used in the last 7 days -show_openid = Show on profile -hide_openid = Hide from profile -ssh_disabled = SSH Disabled - -manage_social = Manage Associated Social Accounts -social_desc = These social accounts are linked to your Gitea account. Make sure you recognize all of them as they can be used to sign in to your Gitea account. -unbind = Unlink -unbind_success = The social account has been unlinked from your Gitea account. - -manage_access_token = Manage Access Tokens -generate_new_token = Generate New Token -tokens_desc = These tokens grant access to your account using the Gitea API. -new_token_desc = Applications using a token have full access to your account. -token_name = Token Name -generate_token = Generate Token -generate_token_success = Your new token has been generated. Copy it now as it will not be shown again. -delete_token = Delete -access_token_deletion = Delete Access Token -access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. Continue? -delete_token_success = The token has been deleted. Applications using it no longer have access to your account. - -twofa_desc = Two-factor authentication enhances the security of your account. -twofa_is_enrolled = Your account is currently enrolled in two-factor authentication. -twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication. -twofa_disable = Disable Two-Factor Authentication -twofa_scratch_token_regenerate = Regenerate Scratch Token -twofa_scratch_token_regenerated = Your scratch token is now %s. Store it in a safe place. -twofa_enroll = Enroll into Two-Factor Authentication -twofa_disable_note = You can disable two-factor authentication if needed. -twofa_disable_desc = Disabling two-factor authentication will make your account less secure. Continue? -regenerate_scratch_token_desc = If you misplaced your scratch token or have already used it to sign in you can reset it here. -twofa_disabled = Two-factor authentication has been disabled. -scan_this_image = Scan this image with your authentication application: -or_enter_secret = Or enter the secret: %s -then_enter_passcode = And enter the passcode shown in the application: -passcode_invalid = The passcode is incorrect. Try again. -twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once! - -u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the FIDO U2F standard. -u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys. -u2f_register_key = Add Security Key -u2f_nickname = Nickname -u2f_press_button = Press the button on your security key to register it. -u2f_delete_key = Remove Security Key -u2f_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue? - -manage_account_links = Manage Linked Accounts -manage_account_links_desc = These external accounts are linked to your Gitea account. -account_links_not_available = There are currently no external accounts linked to your Gitea account. -remove_account_link = Remove Linked Account -remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue? -remove_account_link_success = The linked account has been removed. - -orgs_none = You are not a member of any organizations. -repos_none = You do not own any repositories - -delete_account = Delete Your Account -delete_prompt = This operation will permanently delete your user account. It CAN NOT be undone. -confirm_delete_account = Confirm Deletion -delete_account_title = Delete User Account -delete_account_desc = Are you sure you want to permanently delete this user account? - -[repo] -owner = Owner -repo_name = Repository Name -repo_name_helper = Good repository names use short, memorable and unique keywords. -visibility = Visibility -visibility_helper = Make Repository Private -visibility_helper_forced = Your site administrator forces new repositories to be private. -visibility_fork_helper = (Changing this will affect all forks.) -clone_helper = Need help cloning? Visit Help. -fork_repo = Fork Repository -fork_from = Fork From -fork_visibility_helper = The visibility of a forked repository cannot be changed. -repo_desc = Description -repo_lang = Language -repo_gitignore_helper = Select .gitignore templates. -issue_labels = Issue Labels -issue_labels_helper = Select an issue label set. -license = License -license_helper = Select a license file. -readme = README -readme_helper = Select a README file template. -auto_init = Initialize Repository (Adds .gitignore, Issue Labels, License and README) -create_repo = Create Repository -default_branch = Default Branch -mirror_prune = Prune -mirror_prune_desc = Remove obsolete remote-tracking references -mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable automatic sync. -mirror_interval_invalid = The mirror interval is not valid. -mirror_address = Clone From URL -mirror_address_desc = Include any required authorization credentials in the URL. -mirror_last_synced = Last Synchronized -watchers = Watchers -stargazers = Stargazers -forks = Forks -pick_reaction = Pick your reaction -reactions_more = and %d more - -archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. -archive.issue.nocomment = This repo is archived. You cannot comment on issues. -archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. - -form.reach_limit_of_creation = You have already reached your limit of %d repositories. -form.name_reserved = The repository name '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. - -need_auth = Clone Authorization -migrate_type = Migration Type -migrate_type_helper = This repository will be a mirror -migrate_repo = Migrate Repository -migrate.clone_address = Migrate / Clone From URL -migrate.clone_address_desc = The HTTP(S) or Git 'clone' URL of an existing repository -migrate.clone_local_path = or a local server path -migrate.permission_denied = You are not allowed to import local repositories. -migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory." -migrate.failed = Migration failed: %v -migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. - -mirror_from = mirror of -forked_from = forked from -fork_from_self = You cannot fork a repository you own. -fork_guest_user = Sign in to fork this repository. -copy_link = Copy -copy_link_success = Link has been copied -copy_link_error = Use ⌘C or Ctrl-C to copy -copied = Copied OK -unwatch = Unwatch -watch = Watch -unstar = Unstar -star = Star -fork = Fork -download_archive = Download Repository - -no_desc = No Description -quick_guide = Quick Guide -clone_this_repo = Clone this repository -create_new_repo_command = Creating a new repository on the command line -push_exist_repo = Pushing an existing repository from the command line -empty_message = This repository does not contain any content. - -code = Code -code.desc = Access source code, files, commits and branches. -branch = Branch -tree = Tree -filter_branch_and_tag = Filter branch or tag -branches = Branches -tags = Tags -issues = Issues -pulls = Pull Requests -labels = Labels -milestones = Milestones -commits = Commits -commit = Commit -releases = Releases -file_raw = Raw -file_history = History -file_view_raw = View Raw -file_permalink = Permalink -file_too_large = The file is too large to be shown. -video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag. -audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag. -stored_lfs = Stored with Git LFS -commit_graph = Commit Graph - -editor.new_file = New File -editor.upload_file = Upload File -editor.edit_file = Edit File -editor.preview_changes = Preview Changes -editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface. -editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface. -editor.edit_this_file = Edit File -editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file. -editor.fork_before_edit = You must fork this repository to make or propose changes to this file. -editor.delete_this_file = Delete File -editor.must_have_write_access = You must have write access to make or propose changes to this file. -editor.file_delete_success = File '%s' has been deleted. -editor.name_your_file = Name your file… -editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field. -editor.or = or -editor.cancel_lower = Cancel -editor.commit_changes = Commit Changes -editor.add_tmpl = Add '%s/' -editor.add = Add '%s' -editor.update = Update '%s' -editor.delete = Delete '%s' -editor.commit_message_desc = Add an optional extended description… -editor.commit_directly_to_this_branch = Commit directly to the %s branch. -editor.create_new_branch = Create a new branch for this commit and start a pull request. -editor.new_branch_name_desc = New branch name… -editor.cancel = Cancel -editor.filename_cannot_be_empty = The filename cannot be empty. -editor.branch_already_exists = Branch '%s' already exists in this repository. -editor.directory_is_a_file = Directory name '%s' is already used as a filename in this repository. -editor.file_is_a_symlink = '%s' is a symbolic link. Symbolic links cannot be edited in the web editor -editor.filename_is_a_directory = Filename '%s' is already used as a directory name in this repository. -editor.file_editing_no_longer_exists = The file being edited, '%s', no longer exists in this repository. -editor.file_changed_while_editing = The file contents have changed since you started editing. Click here to see them or Commit Changes again to overwrite them. -editor.file_already_exists = A file named '%s' already exists in this repository. -editor.no_changes_to_show = There are no changes to show. -editor.fail_to_update_file = Failed to update/create file '%s' with error: %v -editor.add_subdir = Add a directory… -editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v -editor.upload_files_to_dir = Upload files to '%s' -editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s'. - -commits.desc = Browse source code change history. -commits.commits = Commits -commits.search = Search commits… -commits.find = Search -commits.search_all = All Branches -commits.author = Author -commits.message = Message -commits.date = Date -commits.older = Older -commits.newer = Newer -commits.signed_by = Signed by -commits.gpg_key_id = GPG Key ID - -ext_issues = Ext. Issues -ext_issues.desc = Link to an external issue tracker. - -issues.desc = Organize bug reports, tasks and milestones. -issues.new = New Issue -issues.new.title_empty = Title cannot be empty -issues.new.labels = Labels -issues.new.no_label = No Label -issues.new.clear_labels = Clear labels -issues.new.milestone = Milestone -issues.new.no_milestone = No Milestone -issues.new.clear_milestone = Clear milestone -issues.new.open_milestone = Open Milestones -issues.new.closed_milestone = Closed Milestones -issues.new.assignees = Assignees -issues.new.clear_assignees = Clear assignees -issues.new.no_assignees = No Assignees -issues.no_ref = No Branch/Tag Specified -issues.create = Create Issue -issues.new_label = New Label -issues.new_label_placeholder = Label name -issues.new_label_desc_placeholder = Description -issues.create_label = Create Label -issues.label_templates.title = Load a predefined set of labels -issues.label_templates.info = No labels exist yet. Create a label with 'New Label' or use a predefined label set: -issues.label_templates.helper = Select a label set -issues.label_templates.use = Use Label Set -issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v -issues.add_label_at = added the
%s
label %s -issues.remove_label_at = removed the
%s
label %s -issues.add_milestone_at = `added this to the %s milestone %s` -issues.change_milestone_at = `modified the milestone from %s to %s %s` -issues.remove_milestone_at = `removed this from the %s milestone %s` -issues.deleted_milestone = `(deleted)` -issues.self_assign_at = `self-assigned this %s` -issues.add_assignee_at = `was assigned by %s %s` -issues.remove_assignee_at = `was unassigned by %s %s` -issues.remove_self_assignment = `removed their assignment %s` -issues.change_title_at = `changed title from %s to %s %s` -issues.delete_branch_at = `deleted branch %s %s` -issues.open_tab = %d Open -issues.close_tab = %d Closed -issues.filter_label = Label -issues.filter_label_no_select = All labels -issues.filter_milestone = Milestone -issues.filter_milestone_no_select = All milestones -issues.filter_assignee = Assignee -issues.filter_assginee_no_select = All assignees -issues.filter_type = Type -issues.filter_type.all_issues = All issues -issues.filter_type.assigned_to_you = Assigned to you -issues.filter_type.created_by_you = Created by you -issues.filter_type.mentioning_you = Mentioning you -issues.filter_sort = Sort -issues.filter_sort.latest = Newest -issues.filter_sort.oldest = Oldest -issues.filter_sort.recentupdate = Recently updated -issues.filter_sort.leastupdate = Least recently updated -issues.filter_sort.mostcomment = Most commented -issues.filter_sort.leastcomment = Least commented -issues.filter_sort.moststars = Most stars -issues.filter_sort.feweststars = Fewest stars -issues.filter_sort.mostforks = Most forks -issues.filter_sort.fewestforks = Fewest forks -issues.action_open = Open -issues.action_close = Close -issues.action_label = Label -issues.action_milestone = Milestone -issues.action_milestone_no_select = No milestone -issues.action_assignee = Assignee -issues.action_assignee_no_select = No assignee -issues.opened_by = opened %[1]s by %[3]s -pulls.merged_by = merged %[1]s by %[3]s -issues.closed_by = closed %[1]s by %[3]s -issues.opened_by_fake = opened %[1]s by %[2]s -issues.previous = Previous -issues.next = Next -issues.open_title = Open -issues.closed_title = Closed -issues.num_comments = %d comments -issues.commented_at = `commented %s` -issues.delete_comment_confirm = Are you sure you want to delete this comment? -issues.no_content = There is no content yet. -issues.close_issue = Close -issues.close_comment_issue = Comment and Close -issues.reopen_issue = Reopen -issues.reopen_comment_issue = Comment and Reopen -issues.create_comment = Comment -issues.closed_at = `closed %[2]s` -issues.reopened_at = `reopened %[2]s` -issues.commit_ref_at = `referenced this issue from a commit %[2]s` -issues.poster = Poster -issues.collaborator = Collaborator -issues.owner = Owner -issues.sign_in_require_desc = Sign in to join this conversation. -issues.edit = Edit -issues.cancel = Cancel -issues.save = Save -issues.label_title = Label name -issues.label_description = Label description -issues.label_color = Label color -issues.label_count = %d labels -issues.label_open_issues = %d open issues -issues.label_edit = Edit -issues.label_delete = Delete -issues.label_modify = Edit Label -issues.label_deletion = Delete Label -issues.label_deletion_desc = Deleting a label removes it from all issues. Continue? -issues.label_deletion_success = The label has been deleted. -issues.label.filter_sort.alphabetically = Alphabetically -issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically -issues.label.filter_sort.by_size = Size -issues.label.filter_sort.reverse_by_size = Reverse size -issues.num_participants = %d Participants -issues.attachment.open_tab = `Click to see "%s" in a new tab` -issues.attachment.download = `Click to download "%s"` -issues.subscribe = Subscribe -issues.unsubscribe = Unsubscribe -issues.tracker = Time Tracker -issues.start_tracking_short = Start -issues.start_tracking = Start Time Tracking -issues.start_tracking_history = `started working %s` -issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed -issues.tracking_already_started = `You have already started time tracking on this issue!` -issues.stop_tracking = Stop -issues.stop_tracking_history = `stopped working %s` -issues.add_time = Manually Add Time -issues.add_time_short = Add Time -issues.add_time_cancel = Cancel -issues.add_time_history = `added spent time %s` -issues.add_time_hours = Hours -issues.add_time_minutes = Minutes -issues.add_time_sum_to_small = No time was entered. -issues.cancel_tracking = Cancel -issues.cancel_tracking_history = `cancelled time tracking %s` -issues.time_spent_total = Total Time Spent -issues.time_spent_from_all_authors = `Total Time Spent: %s` -issues.due_date = Due Date -issues.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." -issues.error_modifying_due_date = "Failed to modify the due date." -issues.error_removing_due_date = "Failed to remove the due date." -issues.due_date_form = "yyyy-mm-dd" -issues.due_date_form_add = "Add due date" -issues.due_date_form_edit = "Edit" -issues.due_date_form_remove = "Remove" -issues.due_date_not_writer = "You need repository write access to update an issue's due date." -issues.due_date_not_set = "No due date set." -issues.due_date_added = "added the due date %s %s" -issues.due_date_modified = "modified the due date to %s from %s %s" -issues.due_date_remove = "removed the due date %s %s" -issues.due_date_overdue = "Overdue" -issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'." -issues.dependency.title = Dependencies -issues.dependency.issue_no_dependencies = This issue currently doesn't have any dependencies. -issues.dependency.pr_no_dependencies = This pull request currently doesn't have any dependencies. -issues.dependency.add = Add dependency… -issues.dependency.cancel = Cancel -issues.dependency.remove = Remove -issues.dependency.remove_info = Remove this dependency -issues.dependency.added_dependency = `%[2]s added a new dependency %[3]s` -issues.dependency.removed_dependency = `%[2]s removed a dependency %[3]s` -issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues -issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues -issues.dependency.issue_close_blocks = This issue blocks closing of the following issues -issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues -issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it. -issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it. -issues.dependency.blocks_short = Blocks -issues.dependency.blocked_by_short = Depends on -issues.dependency.remove_header = Remove Dependency -issues.dependency.issue_remove_text = This will remove the dependency from this issue. Continue? -issues.dependency.pr_remove_text = This will remove the dependency from this pull request. Continue? -issues.dependency.setting = Enable Dependencies For Issues and Pull Requests -issues.dependency.add_error_same_issue = You cannot make an issue depend on itself. -issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist. -issues.dependency.add_error_dep_not_exist = Dependency does not exist. -issues.dependency.add_error_dep_exists = Dependency already exists. -issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other. -issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository. -issues.review.self.approval = You cannot approve your own pull request. -issues.review.self.rejection = You cannot request changes on your own pull request. -issues.review.approve = "approved these changes %s" -issues.review.comment = "reviewed %s" -issues.review.content.empty = You need to leave a comment indicating the requested change(s). -issues.review.reject = "requested changes %s" -issues.review.pending = Pending -issues.review.review = Review -issues.review.reviewers = Reviewers -issues.review.show_outdated = Show outdated -issues.review.hide_outdated = Hide outdated - -pulls.desc = Enable merge requests and code reviews. -pulls.new = New Pull Request -pulls.compare_changes = New Pull Request -pulls.compare_changes_desc = Select the branch to merge into and the branch to pull from. -pulls.compare_base = merge into -pulls.compare_compare = pull from -pulls.filter_branch = Filter branch -pulls.no_results = No results found. -pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request. -pulls.has_pull_request = `A pull request between these branches already exists: %[2]s#%[3]d` -pulls.create = Create Pull Request -pulls.title_desc = wants to merge %[1]d commits from %[2]s into %[3]s -pulls.merged_title_desc = merged %[1]d commits from %[2]s into %[3]s %[4]s -pulls.tab_conversation = Conversation -pulls.tab_commits = Commits -pulls.tab_files = Files Changed -pulls.reopen_to_merge = Please reopen this pull request to perform a merge. -pulls.merged = Merged -pulls.has_merged = The pull request has been merged. -pulls.title_wip_desc = `Start the title with %s to prevent the pull request from being merged accidentally.` -pulls.cannot_merge_work_in_progress = This pull request is marked as a work in progress. Remove the %s prefix from the title when it's ready -pulls.data_broken = This pull request is broken due to missing fork information. -pulls.files_conflicted = This pull request has changes conflicting with the target branch. -pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." -pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." -pulls.can_auto_merge_desc = This pull request can be merged automatically. -pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts. -pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. -pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. -pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. -pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. -pulls.merge_pull_request = Merge Pull Request -pulls.rebase_merge_pull_request = Rebase and Merge -pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff) -pulls.squash_merge_pull_request = Squash and Merge -pulls.invalid_merge_option = You cannot use this merge option for this pull request. -pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.` - -milestones.new = New Milestone -milestones.open_tab = %d Open -milestones.close_tab = %d Closed -milestones.closed = Closed %s -milestones.no_due_date = No due date -milestones.open = Open -milestones.close = Close -milestones.new_subheader = Milestones organize issues and track progress. -milestones.completeness = %d%% Completed -milestones.create = Create Milestone -milestones.title = Title -milestones.desc = Description -milestones.due_date = Due Date (optional) -milestones.clear = Clear -milestones.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." -milestones.create_success = The milestone '%s' has been created. -milestones.edit = Edit Milestone -milestones.edit_subheader = Milestones organize issues and track progress. -milestones.cancel = Cancel -milestones.modify = Update Milestone -milestones.edit_success = Milestone '%s' has been updated. -milestones.deletion = Delete Milestone -milestones.deletion_desc = Deleting a milestone removes it from all related issues. Continue? -milestones.deletion_success = The milestone has been deleted. -milestones.filter_sort.closest_due_date = Closest due date -milestones.filter_sort.furthest_due_date = Furthest due date -milestones.filter_sort.least_complete = Least complete -milestones.filter_sort.most_complete = Most complete -milestones.filter_sort.most_issues = Most issues -milestones.filter_sort.least_issues = Least issues - -ext_wiki = Ext. Wiki -ext_wiki.desc = Link to an external wiki. - -wiki = Wiki -wiki.welcome = Welcome to the Wiki. -wiki.welcome_desc = The wiki lets you write and share documentation with collaborators. -wiki.desc = Write and share documentation with collaborators. -wiki.create_first_page = Create the First Page -wiki.page = Page -wiki.filter_page = Filter page -wiki.new_page = Page -wiki.default_commit_message = Write a note about this page update (optional). -wiki.save_page = Save Page -wiki.last_commit_info = %s edited this page %s -wiki.edit_page_button = Edit -wiki.new_page_button = New Page -wiki.delete_page_button = Delete Page -wiki.delete_page_notice_1 = Deleting the wiki page '%s' cannot be undone. Continue? -wiki.page_already_exists = A wiki page with the same name already exists. -wiki.reserved_page = The wiki page name '%s' is reserved. -wiki.pages = Pages -wiki.last_updated = Last updated %s - -activity = Activity -activity.period.filter_label = Period: -activity.period.daily = 1 day -activity.period.halfweekly = 3 days -activity.period.weekly = 1 week -activity.period.monthly = 1 month -activity.overview = Overview -activity.active_prs_count_1 = %d Active Pull Request -activity.active_prs_count_n = %d Active Pull Requests -activity.merged_prs_count_1 = Merged Pull Request -activity.merged_prs_count_n = Merged Pull Requests -activity.opened_prs_count_1 = Proposed Pull Request -activity.opened_prs_count_n = Proposed Pull Requests -activity.title.user_1 = %d user -activity.title.user_n = %d users -activity.title.prs_1 = %d Pull request -activity.title.prs_n = %d Pull requests -activity.title.prs_merged_by = %s merged by %s -activity.title.prs_opened_by = %s proposed by %s -activity.merged_prs_label = Merged -activity.opened_prs_label = Proposed -activity.active_issues_count_1 = %d Active Issue -activity.active_issues_count_n = %d Active Issues -activity.closed_issues_count_1 = Closed Issue -activity.closed_issues_count_n = Closed Issues -activity.title.issues_1 = %d Issue -activity.title.issues_n = %d Issues -activity.title.issues_closed_by = %s closed by %s -activity.title.issues_created_by = %s created by %s -activity.closed_issue_label = Closed -activity.new_issues_count_1 = New Issue -activity.new_issues_count_n = New Issues -activity.new_issue_label = Opened -activity.title.unresolved_conv_1 = %d Unresolved Conversation -activity.title.unresolved_conv_n = %d Unresolved Conversations -activity.unresolved_conv_desc = These recently changed issues and pull requests have not been resolved yet. -activity.unresolved_conv_label = Open -activity.title.releases_1 = %d Release -activity.title.releases_n = %d Releases -activity.title.releases_published_by = %s published by %s -activity.published_release_label = Published - -search = Search -search.search_repo = Search repository -search.results = Search results for "%s" in %s - -settings = Settings -settings.desc = Settings is where you can manage the settings for the repository -settings.options = Repository -settings.collaboration = Collaborators -settings.collaboration.admin = Administrator -settings.collaboration.write = Write -settings.collaboration.read = Read -settings.collaboration.undefined = Undefined -settings.hooks = Webhooks -settings.githooks = Git Hooks -settings.basic_settings = Basic Settings -settings.mirror_settings = Mirror Settings -settings.sync_mirror = Synchronize Now -settings.mirror_sync_in_progress = Mirror synchronization is in progress. Check back in a minute. -settings.site = Website -settings.update_settings = Update Settings -settings.advanced_settings = Advanced Settings -settings.wiki_desc = Enable Repository Wiki -settings.use_internal_wiki = Use Built-In Wiki -settings.use_external_wiki = Use External Wiki -settings.external_wiki_url = External Wiki URL -settings.external_wiki_url_error = The external wiki URL is not a valid URL. -settings.external_wiki_url_desc = Visitors are redirected to the external wiki URL when clicking the wiki tab. -settings.issues_desc = Enable Repository Issue Tracker -settings.use_internal_issue_tracker = Use Built-In Issue Tracker -settings.use_external_issue_tracker = Use External Issue Tracker -settings.external_tracker_url = External Issue Tracker URL -settings.external_tracker_url_error = The external issue tracker URL is not a valid URL. -settings.external_tracker_url_desc = Visitors are redirected to the external issue tracker URL when clicking on the issues tab. -settings.tracker_url_format = External Issue Tracker URL Format -settings.tracker_url_format_error = The external issue tracker URL format is not a valid URL. -settings.tracker_issue_style = External Issue Tracker Number Format -settings.tracker_issue_style.numeric = Numeric -settings.tracker_issue_style.alphanumeric = Alphanumeric -settings.tracker_url_format_desc = Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. -settings.enable_timetracker = Enable Time Tracking -settings.allow_only_contributors_to_track_time = Let Only Contributors Track Time -settings.pulls_desc = Enable Repository Pull Requests -settings.pulls.ignore_whitespace = Ignore Whitespace for Conflicts -settings.pulls.allow_merge_commits = Enable Commit Merging -settings.pulls.allow_rebase_merge = Enable Rebasing to Merge Commits -settings.pulls.allow_rebase_merge_commit = Enable Rebasing with explicit merge commits (--no-ff) -settings.pulls.allow_squash_commits = Enable Squashing to Merge Commits -settings.admin_settings = Administrator Settings -settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) -settings.admin_enable_close_issues_via_commit_in_any_branch = Close an issue via a commit made in a non default branch -settings.danger_zone = Danger Zone -settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name. -settings.convert = Convert to Regular Repository -settings.convert_desc = You can convert this mirror into a regular repository. This cannot be undone. -settings.convert_notices_1 = This operation will convert the mirror into a regular repository and cannot be undone. -settings.convert_confirm = Convert Repository -settings.convert_succeed = The mirror has been converted into a regular repository. -settings.transfer = Transfer Ownership -settings.transfer_desc = Transfer this repository to a user or to an organization for which you have administrator rights. -settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user. -settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own. -settings.transfer_form_title = Enter the repository name as confirmation: -settings.wiki_delete = Delete Wiki Data -settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone. -settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s. -settings.confirm_wiki_delete = Delete Wiki Data -settings.wiki_deletion_success = The repository wiki data has been deleted. -settings.delete = Delete This Repository -settings.delete_desc = Deleting a repository is permanent and cannot be undone. -settings.delete_notices_1 = - This operation CANNOT be undone. -settings.delete_notices_2 = - This operation will permanently delete the %s repository including code, issues, comments, wiki data and collaborator settings. -settings.delete_notices_fork_1 = - Forks of this repository will become independent after deletion. -settings.deletion_success = The repository has been deleted. -settings.update_settings_success = The repository settings have been updated. -settings.transfer_owner = New Owner -settings.make_transfer = Perform Transfer -settings.transfer_succeed = The repository has been transferred. -settings.confirm_delete = Delete Repository -settings.add_collaborator = Add Collaborator -settings.add_collaborator_success = The collaborator has been added. -settings.add_collaborator_inactive_user = Can not add an inactive user as a collaborator. -settings.add_collaborator_duplicate = The collaborator is already added to this repository. -settings.delete_collaborator = Remove -settings.collaborator_deletion = Remove Collaborator -settings.collaborator_deletion_desc = Removing a collaborator will revoke their access to this repository. Continue? -settings.remove_collaborator_success = The collaborator has been removed. -settings.search_user_placeholder = Search user… -settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator. -settings.add_webhook = Add Webhook -settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character. -settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the webhooks guide. -settings.webhook_deletion = Remove Webhook -settings.webhook_deletion_desc = Removing a webhook deletes its settings and delivery history. Continue? -settings.webhook_deletion_success = The webhook has been removed. -settings.webhook.test_delivery = Test Delivery -settings.webhook.test_delivery_desc = Test this webhook with a fake event. -settings.webhook.test_delivery_success = A fake event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history. -settings.webhook.request = Request -settings.webhook.response = Response -settings.webhook.headers = Headers -settings.webhook.payload = Content -settings.webhook.body = Body -settings.githooks_desc = "Git hooks are powered by Git itself. You can edit hook files below to set up custom operations." -settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook. -settings.githook_name = Hook Name -settings.githook_content = Hook Content -settings.update_githook = Update Hook -settings.add_webhook_desc = Gitea will send POST requests with a specified content type to the target URL. Read more in the webhooks guide. -settings.payload_url = Target URL -settings.content_type = POST Content Type -settings.secret = Secret -settings.slack_username = Username -settings.slack_icon_url = Icon URL -settings.discord_username = Username -settings.discord_icon_url = Icon URL -settings.slack_color = Color -settings.event_desc = Trigger On: -settings.event_push_only = Push Events -settings.event_send_everything = All Events -settings.event_choose = Custom Events… -settings.event_create = Create -settings.event_create_desc = Branch or tag created. -settings.event_delete = Delete -settings.event_delete_desc = Branch or tag deleted -settings.event_fork = Fork -settings.event_fork_desc = Repository forked -settings.event_issues = Issues -settings.event_issues_desc = Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned. -settings.event_issue_comment = Issue Comment -settings.event_issue_comment_desc = Issue comment created, edited, or deleted. -settings.event_release = Release -settings.event_release_desc = Release published, updated or deleted in a repository. -settings.event_pull_request = Pull Request -settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized. -settings.event_push = Push -settings.event_push_desc = Git push to a repository. -settings.event_repository = Repository -settings.event_repository_desc = Repository created or deleted. -settings.active = Active -settings.active_helper = Information about triggered events will be sent to this webhook URL. -settings.add_hook_success = The webhook has been added. -settings.update_webhook = Update Webhook -settings.update_hook_success = The webhook has been updated. -settings.delete_webhook = Remove Webhook -settings.recent_deliveries = Recent Deliveries -settings.hook_type = Hook Type -settings.add_slack_hook_desc = Integrate Slack into your repository. -settings.slack_token = Token -settings.slack_domain = Domain -settings.slack_channel = Channel -settings.add_discord_hook_desc = Integrate Discord into your repository. -settings.add_dingtalk_hook_desc = Integrate Dingtalk into your repository. -settings.deploy_keys = Deploy Keys -settings.add_deploy_key = Add Deploy Key -settings.deploy_key_desc = Deploy keys have read-only pull access to the repository. -settings.is_writable = Enable Write Access -settings.is_writable_info = Allow this deploy key to push to the repository. -settings.no_deploy_keys = There are no deploy keys yet. -settings.title = Title -settings.deploy_key_content = Content -settings.key_been_used = A deploy key with identical content is already in use. -settings.key_name_used = A deploy key with the same name already exists. -settings.add_key_success = The deploy key '%s' has been added. -settings.deploy_key_deletion = Remove Deploy Key -settings.deploy_key_deletion_desc = Removing a deploy key will revoke its access to this repository. Continue? -settings.deploy_key_deletion_success = The deploy key has been removed. -settings.branches = Branches -settings.protected_branch = Branch Protection -settings.protected_branch_can_push = Allow push? -settings.protected_branch_can_push_yes = You can push -settings.protected_branch_can_push_no = You can not push -settings.branch_protection = Branch Protection for Branch '%s' -settings.protect_this_branch = Enable Branch Protection -settings.protect_this_branch_desc = Prevent deletion and disable Git force pushing to the branch. -settings.protect_whitelist_committers = Enable Push Whitelist -settings.protect_whitelist_committers_desc = Allow whitelisted users or teams to bypass push restrictions. -settings.protect_whitelist_users = Whitelisted users for pushing: -settings.protect_whitelist_search_users = Search users… -settings.protect_whitelist_teams = Whitelisted teams for pushing: -settings.protect_whitelist_search_teams = Search teams… -settings.protect_merge_whitelist_committers = Enable Merge Whitelist -settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch. -settings.protect_merge_whitelist_users = Whitelisted users for merging: -settings.protect_merge_whitelist_teams = Whitelisted teams for merging: -settings.protect_required_approvals = Required approvals: -settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews of whitelisted users or teams. -settings.protect_approvals_whitelist_users = Whitelisted reviewers: -settings.protect_approvals_whitelist_teams = Whitelisted teams for reviews: -settings.add_protected_branch = Enable protection -settings.delete_protected_branch = Disable protection -settings.update_protect_branch_success = Branch protection for branch '%s' has been updated. -settings.remove_protected_branch_success = Branch protection for branch '%s' has been disabled. -settings.protected_branch_deletion = Disable Branch Protection -settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? -settings.default_branch_desc = Select a default repository branch for pull requests and code commits: -settings.choose_branch = Choose a branch… -settings.no_protected_branch = There are no protected branches. -settings.edit_protected_branch = Edit -settings.protected_branch_required_approvals_min = Required approvals cannot be negative. -settings.archive.button = Archive Repo -settings.archive.header = Archive This Repo -settings.archive.text = Archiving the repo will make it entirely read-only. It is hidden from the dashboard, cannot be committed to and no issues or pull-requests can be created. -settings.archive.success = The repo was successfully archived. -settings.archive.error = An error occured while trying to archive the repo. See the log for more details. -settings.archive.error_ismirror = You cannot archive a mirrored repo. -settings.archive.branchsettings_unavailable = Branch settings are not available if the repo is archived. -settings.unarchive.button = Un-Archive Repo -settings.unarchive.header = Un-Archive This Repo -settings.unarchive.text = Un-Archiving the repo will restore its ability to recieve commits and pushes, as well as new issues and pull-requests. -settings.unarchive.success = The repo was successfully un-archived. -settings.unarchive.error = An error occured while trying to un-archive the repo. See the log for more details. - -diff.browse_source = Browse Source -diff.parent = parent -diff.commit = commit -diff.data_not_available = Diff Content Not Available -diff.show_diff_stats = Show Diff Stats -diff.show_split_view = Split View -diff.show_unified_view = Unified View -diff.whitespace_button = Whitespace -diff.whitespace_show_everything = Show all changes -diff.whitespace_ignore_all_whitespace = Ignore whitespace when comparing lines -diff.whitespace_ignore_amount_changes = Ignore changes in amount of whitespace -diff.whitespace_ignore_at_eol = Ignore changes in whitespace at EOL -diff.stats_desc = %d changed files with %d additions and %d deletions -diff.bin = BIN -diff.view_file = View File -diff.file_suppressed = File diff suppressed because it is too large -diff.too_many_files = Some files were not shown because too many files changed in this diff -diff.comment.placeholder = Leave a comment -diff.comment.markdown_info = Styling with markdown is supported. -diff.comment.add_single_comment = Add single comment -diff.comment.add_review_comment = Add comment -diff.comment.start_review = Start review -diff.comment.reply = Reply -diff.review = Review -diff.review.header = Submit review -diff.review.placeholder = Review comment -diff.review.comment = Comment -diff.review.approve = Approve -diff.review.reject = Request changes - -releases.desc = Track project versions and downloads. -release.releases = Releases -release.new_release = New Release -release.draft = Draft -release.prerelease = Pre-Release -release.stable = Stable -release.edit = edit -release.ahead = %d commits to %s since this release -release.source_code = Source Code -release.new_subheader = Releases organize project versions. -release.edit_subheader = Releases organize project versions. -release.tag_name = Tag name -release.target = Target -release.tag_helper = Choose an existing tag or create a new tag. -release.title = Title -release.content = Content -release.prerelease_desc = Mark as Pre-Release -release.prerelease_helper = Mark this release unsuitable for production use. -release.cancel = Cancel -release.publish = Publish Release -release.save_draft = Save Draft -release.edit_release = Update Release -release.delete_release = Delete Release -release.deletion = Delete Release -release.deletion_desc = Deleting a release removes its Git tag from the repository. Repository contents and history remain unchanged. Continue? -release.deletion_success = The release has been deleted. -release.tag_name_already_exist = A release with this tag name already exists. -release.tag_name_invalid = The tag name is not valid. -release.downloads = Downloads - -branch.name = Branch Name -branch.search = Search branches -branch.already_exists = A branch named '%s' already exists. -branch.delete_head = Delete -branch.delete = Delete Branch '%s' -branch.delete_html = Delete Branch -branch.delete_desc = Deleting a branch is permanent. It CANNOT be undone. Continue? -branch.deletion_success = Branch '%s' has been deleted. -branch.deletion_failed = Failed to delete branch '%s'. -branch.delete_branch_has_new_commits = Branch '%s' cannot be deleted because new commits have been added after merging. -branch.create_branch = Create branch %s -branch.create_from = from '%s' -branch.create_success = Branch '%s' has been created. -branch.branch_already_exists = Branch '%s' already exists in this repository. -branch.branch_name_conflict = Branch name '%s' conflicts with the already existing branch '%s'. -branch.tag_collision = Branch '%s' cannot be created as a tag with same name already exists in the repository. -branch.deleted_by = Deleted by %s -branch.restore_success = Branch '%s' has been restored. -branch.restore_failed = Failed to restore branch '%s'. -branch.protected_deletion_failed = Branch '%s' is protected. It cannot be deleted. - -topic.manage_topics = Manage Topics -topic.done = Done -topic.count_prompt = You can not select more than 25 topics -topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long. - -[org] -org_name_holder = Organization Name -org_full_name_holder = Organization Full Name -org_name_helper = Organization names should be short and memorable. -create_org = Create Organization -repo_updated = Updated -people = People -teams = Teams -lower_members = members -lower_repositories = repositories -create_new_team = New Team -create_team = Create Team -org_desc = Description -team_name = Team Name -team_desc = Description -team_name_helper = Team names should be short and memorable. -team_desc_helper = Describe the purpose or role of the team. -team_permission_desc = Permission -team_unit_desc = Allow Access to Repository Sections - -form.name_reserved = The organization name '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. -form.create_org_not_allowed = You are not allowed to create an organization. - -settings = Settings -settings.options = Organization -settings.full_name = Full Name -settings.website = Website -settings.location = Location -settings.update_settings = Update Settings -settings.update_setting_success = Organization settings have been updated. -settings.change_orgname_prompt = Note: changing the organization name also changes the organization's URL. -settings.update_avatar_success = The organization's avatar has been updated. -settings.delete = Delete Organization -settings.delete_account = Delete This Organization -settings.delete_prompt = The organization will be permanently removed. This CANNOT be undone! -settings.confirm_delete_account = Confirm Deletion -settings.delete_org_title = Delete Organization -settings.delete_org_desc = This organization will be deleted permanently. Continue? -settings.hooks_desc = Add webhooks which will be triggered for all repositories under this organization. - -members.membership_visibility = Membership Visibility: -members.public = Visible -members.public_helper = make hidden -members.private = Hidden -members.private_helper = make visible -members.member_role = Member Role: -members.owner = Owner -members.member = Member -members.remove = Remove -members.leave = Leave -members.invite_desc = Add a new member to %s: -members.invite_now = Invite Now - -teams.join = Join -teams.leave = Leave -teams.read_access = Read Access -teams.read_access_helper = Members can view and clone team repositories. -teams.write_access = Write Access -teams.write_access_helper = Members can read and push to team repositories. -teams.admin_access = Administrator Access -teams.admin_access_helper = Members can pull and push to team repositories and add collaborators to them. -teams.no_desc = This team has no description -teams.settings = Settings -teams.owners_permission_desc = Owners have full access to all repositories and have administrator access to the organization. -teams.members = Team Members -teams.update_settings = Update Settings -teams.delete_team = Delete Team -teams.add_team_member = Add Team Member -teams.delete_team_title = Delete Team -teams.delete_team_desc = Deleting a team revokes repository access from its members. Continue? -teams.delete_team_success = The team has been deleted. -teams.read_permission_desc = This team grants Read access: members can view and clone team repositories. -teams.write_permission_desc = This team grants Write access: members can read from and push to team repositories. -teams.admin_permission_desc = This team grants Admin access: members can read from, push to and add collaborators to team repositories. -teams.repositories = Team Repositories -teams.search_repo_placeholder = Search repository… -teams.add_team_repository = Add Team Repository -teams.remove_repo = Remove -teams.add_nonexistent_repo = "The repository you're trying to add does not exist; please create it first." -teams.add_duplicate_users = User is already a team member. -teams.repos.none = No repositories could be accessed by this team. -teams.members.none = No members on this team. - -[admin] -dashboard = Dashboard -users = User Accounts -organizations = Organizations -repositories = Repositories -authentication = Authentication Sources -config = Configuration -notices = System Notices -monitor = Monitoring -first_page = First -last_page = Last -total = Total: %d - -dashboard.statistic = Summary -dashboard.operations = Maintenance Operations -dashboard.system_status = System Status -dashboard.statistic_info = The Gitea database holds %d users, %d organizations, %d public keys, %d repositories, %d watches, %d stars, %d actions, %d accesses, %d issues, %d comments, %d social accounts, %d follows, %d mirrors, %d releases, %d authentication sources, %d webhooks, %d milestones, %d labels, %d hook tasks, %d teams, %d update tasks, %d attachments. -dashboard.operation_name = Operation Name -dashboard.operation_switch = Switch -dashboard.operation_run = Run -dashboard.clean_unbind_oauth = Clean unbound OAuth connections -dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted. -dashboard.delete_inactivate_accounts = Delete all unactivated accounts -dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted. -dashboard.delete_repo_archives = Delete all repository archives -dashboard.delete_repo_archives_success = All repository archives have been deleted. -dashboard.delete_missing_repos = Delete all repositories missing their Git files -dashboard.delete_missing_repos_success = All repositories missing their Git files have been deleted. -dashboard.git_gc_repos = Garbage collect all repositories -dashboard.git_gc_repos_success = All repositories have finished garbage collection. -dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.) -dashboard.resync_all_sshkeys_success = The public SSH keys controlled by Gitea have been updated. -dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories. -dashboard.resync_all_hooks_success = All pre-receive, update and post-receive repository hooks have been resynchronized. -dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist -dashboard.reinit_missing_repos_success = All missing Git repositories for which records existed have been reinitialized. -dashboard.sync_external_users = Synchronize external user data -dashboard.sync_external_users_started = External user data synchronization has started. -dashboard.git_fsck = Execute health checks on all repositories -dashboard.git_fsck_started = Repository health checks have started. -dashboard.server_uptime = Server Uptime -dashboard.current_goroutine = Current Goroutines -dashboard.current_memory_usage = Current Memory Usage -dashboard.total_memory_allocated = Total Memory Allocated -dashboard.memory_obtained = Memory Obtained -dashboard.pointer_lookup_times = Pointer Lookup Times -dashboard.memory_allocate_times = Memory Allocations -dashboard.memory_free_times = Memory Frees -dashboard.current_heap_usage = Current Heap Usage -dashboard.heap_memory_obtained = Heap Memory Obtained -dashboard.heap_memory_idle = Heap Memory Idle -dashboard.heap_memory_in_use = Heap Memory In Use -dashboard.heap_memory_released = Heap Memory Released -dashboard.heap_objects = Heap Objects -dashboard.bootstrap_stack_usage = Bootstrap Stack Usage -dashboard.stack_memory_obtained = Stack Memory Obtained -dashboard.mspan_structures_usage = MSpan Structures Usage -dashboard.mspan_structures_obtained = MSpan Structures Obtained -dashboard.mcache_structures_usage = MCache Structures Usage -dashboard.mcache_structures_obtained = MCache Structures Obtained -dashboard.profiling_bucket_hash_table_obtained = Profiling Bucket Hash Table Obtained -dashboard.gc_metadata_obtained = GC Metadata Obtained -dashboard.other_system_allocation_obtained = Other System Allocation Obtained -dashboard.next_gc_recycle = Next GC Recycle -dashboard.last_gc_time = Since Last GC Time -dashboard.total_gc_time = Total GC Pause -dashboard.total_gc_pause = Total GC Pause -dashboard.last_gc_pause = Last GC Pause -dashboard.gc_times = GC Times - -users.user_manage_panel = User Account Management -users.new_account = Create User Account -users.name = Username -users.activated = Activated -users.admin = Admin -users.repos = Repos -users.created = Created -users.last_login = Last Sign-In -users.never_login = Never Signed-In -users.send_register_notify = Send User Registration Notification -users.new_success = The user account '%s' has been created. -users.edit = Edit -users.auth_source = Authentication Source -users.local = Local -users.auth_login_name = Authentication Sign-In Name -users.password_helper = Leave the password empty to keep it unchanged. -users.update_profile_success = The user account has been updated. -users.edit_account = Edit User Account -users.max_repo_creation = Maximal Number of Repositories -users.max_repo_creation_desc = (Enter -1 to use the global default limit.) -users.is_activated = User Account Is Activated -users.prohibit_login = Disable Sign-In -users.is_admin = Is Administrator -users.allow_git_hook = May Create Git Hooks -users.allow_import_local = May Import Local Repositories -users.allow_create_organization = May Create Organizations -users.update_profile = Update User Account -users.delete_account = Delete User Account -users.still_own_repo = This user still owns one or more repositories. Delete or transfer these repositories first. -users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. -users.deletion_success = The user account has been deleted. - -orgs.org_manage_panel = Organization Management -orgs.name = Name -orgs.teams = Teams -orgs.members = Members -orgs.new_orga = New Organization - -repos.repo_manage_panel = Repository Management -repos.owner = Owner -repos.name = Name -repos.private = Private -repos.watches = Watches -repos.stars = Stars -repos.forks = Forks -repos.issues = Issues -repos.size = Size - -auths.auth_manage_panel = Authentication Source Management -auths.new = Add Authentication Source -auths.name = Name -auths.type = Type -auths.enabled = Enabled -auths.syncenabled = Enable User Synchronization -auths.updated = Updated -auths.auth_type = Authentication Type -auths.auth_name = Authentication Name -auths.security_protocol = Security Protocol -auths.domain = Domain -auths.host = Host -auths.port = Port -auths.bind_dn = Bind DN -auths.bind_password = Bind Password -auths.bind_password_helper = Warning: This password is stored in plain text. Use a read-only account if possible. -auths.user_base = User Search Base -auths.user_dn = User DN -auths.attribute_username = Username Attribute -auths.attribute_username_placeholder = Leave empty to use the username entered in Gitea. -auths.attribute_name = First Name Attribute -auths.attribute_surname = Surname Attribute -auths.attribute_mail = Email Attribute -auths.attribute_ssh_public_key = Public SSH Key Attribute -auths.attributes_in_bind = Fetch Attributes in Bind DN Context -auths.use_paged_search = Use Paged Search -auths.search_page_size = Page Size -auths.filter = User Filter -auths.admin_filter = Admin Filter -auths.ms_ad_sa = MS AD Search Attributes -auths.smtp_auth = SMTP Authentication Type -auths.smtphost = SMTP Host -auths.smtpport = SMTP Port -auths.allowed_domains = Allowed Domains -auths.allowed_domains_helper = Leave empty to allow all domains. Separate multiple domains with a comma (','). -auths.enable_tls = Enable TLS Encryption -auths.skip_tls_verify = Skip TLS Verify -auths.pam_service_name = PAM Service Name -auths.oauth2_provider = OAuth2 Provider -auths.oauth2_clientID = Client ID (Key) -auths.oauth2_clientSecret = Client Secret -auths.openIdConnectAutoDiscoveryURL = OpenID Connect Auto Discovery URL -auths.oauth2_use_custom_url = Use Custom URLs Instead of Default URLs -auths.oauth2_tokenURL = Token URL -auths.oauth2_authURL = Authorize URL -auths.oauth2_profileURL = Profile URL -auths.oauth2_emailURL = Email URL -auths.enable_auto_register = Enable Auto Registration -auths.tips = Tips -auths.tips.oauth2.general = OAuth2 Authentication -auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be: /user/oauth2//callback -auths.tip.oauth2_provider = OAuth2 Provider -auths.tip.bitbucket = Register a new OAuth consumer on https://bitbucket.org/account/user//oauth-consumers/new and add the permission 'Account' - 'Read' -auths.tip.dropbox = Create a new application at https://www.dropbox.com/developers/apps -auths.tip.facebook = Register a new application at https://developers.facebook.com/apps and add the product "Facebook Login" -auths.tip.github = Register a new OAuth application on https://github.com/settings/applications/new -auths.tip.gitlab = Register a new application on https://gitlab.com/profile/applications -auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console at https://console.developers.google.com/ -auths.tip.openid_connect = Use the OpenID Connect Discovery URL (/.well-known/openid-configuration) to specify the endpoints -auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled -auths.tip.discord = Register a new application on https://discordapp.com/developers/applications/me -auths.edit = Edit Authentication Source -auths.activated = This Authentication Source is Activated -auths.new_success = The authentication '%s' has been added. -auths.update_success = The authentication source has been updated. -auths.update = Update Authentication Source -auths.delete = Delete Authentication Source -auths.delete_auth_title = Delete Authentication Source -auths.delete_auth_desc = Deleting an authentication source prevents users from using it to sign in. Continue? -auths.still_in_used = The authentication source is still in use. Convert or delete any users using this authentication source first. -auths.deletion_success = The authentication source has been deleted. -auths.login_source_exist = The authentication source '%s' already exists. - -config.server_config = Server Configuration -config.app_name = Site Title -config.app_ver = Gitea Version -config.app_url = Gitea Base URL -config.custom_conf = Configuration File Path -config.domain = SSH Server Domain -config.offline_mode = Local Mode -config.disable_router_log = Disable Router Log -config.run_user = Run As Username -config.run_mode = Run Mode -config.git_version = Git Version -config.repo_root_path = Repository Root Path -config.lfs_root_path = LFS Root Path -config.static_file_root_path = Static File Root Path -config.log_file_root_path = Log Path -config.script_type = Script Type -config.reverse_auth_user = Reverse Authentication User - -config.ssh_config = SSH Configuration -config.ssh_enabled = Enabled -config.ssh_start_builtin_server = Use Built-In Server -config.ssh_domain = Server Domain -config.ssh_port = Port -config.ssh_listen_port = Listen Port -config.ssh_root_path = Root Path -config.ssh_key_test_path = Key Test Path -config.ssh_keygen_path = Keygen ('ssh-keygen') Path -config.ssh_minimum_key_size_check = Minimum Key Size Check -config.ssh_minimum_key_sizes = Minimum Key Sizes - -config.db_config = Database Configuration -config.db_type = Type -config.db_host = Host -config.db_name = Name -config.db_user = Username -config.db_ssl_mode = SSL -config.db_path = Path - -config.service_config = Service Configuration -config.register_email_confirm = Require Email Confirmation to Register -config.disable_register = Disable Self-Registration -config.allow_only_external_registration = Allow Registration Only Through External Services -config.enable_openid_signup = Enable OpenID Self-Registration -config.enable_openid_signin = Enable OpenID Sign-In -config.show_registration_button = Show Register Button -config.require_sign_in_view = Require Sign-In to View Pages -config.mail_notify = Enable Email Notifications -config.disable_key_size_check = Disable Minimum Key Size Check -config.enable_captcha = Enable CAPTCHA -config.active_code_lives = Active Code Lives -config.reset_password_code_lives = Reset Password Code Expiry Time -config.default_keep_email_private = Hide Email Addresses by Default -config.default_allow_create_organization = Allow Creation of Organizations by Default -config.enable_timetracking = Enable Time Tracking -config.default_enable_timetracking = Enable Time Tracking by Default -config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time -config.no_reply_address = Hidden Email Domain -config.default_enable_dependencies = Enable Issue Dependencies by Default - -config.webhook_config = Webhook Configuration -config.queue_length = Queue Length -config.deliver_timeout = Deliver Timeout -config.skip_tls_verify = Skip TLS Verification - -config.mailer_config = SMTP Mailer Configuration -config.mailer_enabled = Enabled -config.mailer_disable_helo = Disable HELO -config.mailer_name = Name -config.mailer_host = Host -config.mailer_user = User -config.mailer_use_sendmail = Use Sendmail -config.mailer_sendmail_path = Sendmail Path -config.mailer_sendmail_args = Extra Arguments to Sendmail -config.send_test_mail = Send Testing Email -config.test_mail_failed = Failed to send a testing email to '%s': %v -config.test_mail_sent = A testing email has been sent to '%s'. - -config.oauth_config = OAuth Configuration -config.oauth_enabled = Enabled - -config.cache_config = Cache Configuration -config.cache_adapter = Cache Adapter -config.cache_interval = Cache Interval -config.cache_conn = Cache Connection - -config.session_config = Session Configuration -config.session_provider = Session Provider -config.provider_config = Provider Config -config.cookie_name = Cookie Name -config.enable_set_cookie = Enable Set Cookie -config.gc_interval_time = GC Interval Time -config.session_life_time = Session Life Time -config.https_only = HTTPS Only -config.cookie_life_time = Cookie Life Time - -config.picture_config = Picture and Avatar Configuration -config.picture_service = Picture Service -config.disable_gravatar = Disable Gravatar -config.enable_federated_avatar = Enable Federated Avatars - -config.git_config = Git Configuration -config.git_disable_diff_highlight = Disable Diff Syntax Highlight -config.git_max_diff_lines = Max Diff Lines (for a single file) -config.git_max_diff_line_characters = Max Diff Characters (for a single line) -config.git_max_diff_files = Max Diff Files (to be shown) -config.git_gc_args = GC Arguments -config.git_migrate_timeout = Migration Timeout -config.git_mirror_timeout = Mirror Update Timeout -config.git_clone_timeout = Clone Operation Timeout -config.git_pull_timeout = Pull Operation Timeout -config.git_gc_timeout = GC Operation Timeout - -config.log_config = Log Configuration -config.log_mode = Log Mode - -monitor.cron = Cron Tasks -monitor.name = Name -monitor.schedule = Schedule -monitor.next = Next Time -monitor.previous = Previous Time -monitor.execute_times = Executions -monitor.process = Running Processes -monitor.desc = Description -monitor.start = Start Time -monitor.execute_time = Execution Time - -notices.system_notice_list = System Notices -notices.view_detail_header = View Notice Details -notices.actions = Actions -notices.select_all = Select All -notices.deselect_all = Deselect All -notices.inverse_selection = Inverse Selection -notices.delete_selected = Delete Selected -notices.delete_all = Delete All Notices -notices.type = Type -notices.type_1 = Repository -notices.desc = Description -notices.op = Op. -notices.delete_success = The system notices have been deleted. - -[action] -create_repo = created repository %s -rename_repo = renamed repository from %[1]s to %[3]s -commit_repo = pushed to %[3]s at %[4]s -create_issue = `opened issue %s#%[2]s` -close_issue = `closed issue %s#%[2]s` -reopen_issue = `reopened issue %s#%[2]s` -create_pull_request = `created pull request %s#%[2]s` -close_pull_request = `closed pull request %s#%[2]s` -reopen_pull_request = `reopened pull request %s#%[2]s` -comment_issue = `commented on issue %s#%[2]s` -merge_pull_request = `merged pull request %s#%[2]s` -transfer_repo = transferred repository %s to %s -push_tag = pushed tag %[2]s to %[3]s -delete_tag = deleted tag %[2]s from %[3]s -delete_branch = deleted branch %[2]s from %[3]s -compare_commits = Compare %d commits -mirror_sync_push = synced commits to %[3]s at %[4]s from mirror -mirror_sync_create = synced new reference %[2]s to %[3]s from mirror -mirror_sync_delete = synced and deleted reference %[2]s at %[3]s from mirror - -[tool] -ago = %s ago -from_now = %s from now -now = now -future = future -1s = 1 second -1m = 1 minute -1h = 1 hour -1d = 1 day -1w = 1 week -1mon = 1 month -1y = 1 year -seconds = %d seconds -minutes = %d minutes -hours = %d hours -days = %d days -weeks = %d weeks -months = %d months -years = %d years -raw_seconds = seconds -raw_minutes = minutes - -[dropzone] -default_message = Drop files or click here to upload. -invalid_input_type = You can not upload files of this type. -file_too_big = File size ({{filesize}} MB) exceeds the maximum size of ({{maxFilesize}} MB). -remove_file = Remove file - -[notification] -notifications = Notifications -unread = Unread -read = Read -no_unread = No unread notifications. -no_read = No read notifications. -pin = Pin notification -mark_as_read = Mark as read -mark_as_unread = Mark as unread -mark_all_as_read = Mark all as read - -[gpg] -error.extract_sign = Failed to extract signature -error.generate_hash = Failed to generate hash of commit -error.no_committer_account = No account linked to committer's email address -error.no_gpg_keys_found = "No known key found for this signature in database" -error.not_signed_commit = "Not a signed commit" -error.failed_retrieval_gpg_keys = "Failed to retrieve any key attached to the committer's account" - -[units] -error.no_unit_allowed_repo = You are not allowed to access any section of this repository. -error.unit_not_allowed = You are not allowed to access this repository section. +app_desc = A painless, self-hosted Git service + +home = Home +dashboard = Dashboard +explore = Explore +help = Help +sign_in = Sign In +sign_in_with = Sign In With +sign_out = Sign Out +sign_up = Register +link_account = Link Account +register = Register +website = Website +version = Version +page = Page +template = Template +language = Language +notifications = Notifications +create_new = Create… +user_profile_and_more = Profile and Settings… +signed_in_as = Signed in as +enable_javascript = This website works better with JavaScript. + +username = Username +email = Email Address +password = Password +re_type = Re-Type Password +captcha = CAPTCHA +twofa = Two-Factor Authentication +twofa_scratch = Two-Factor Scratch Code +passcode = Passcode + +u2f_insert_key = Insert your security key +u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it. +u2f_press_button = Please press the button on your security key… +u2f_use_twofa = Use a two-factor code from your phone +u2f_error = Could not read your security key. +u2f_unsupported_browser = Your browser does not support U2F security keys. +u2f_error_1 = An unknown error occurred. Please retry. +u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL. +u2f_error_3 = The server could not process your request. +u2f_error_4 = The security key is not permitted for this request. Please make sure that the key is not already registered. +u2f_error_5 = Timeout reached before your key could be read. Please reload this page and retry. +u2f_reload = Reload + +repository = Repository +organization = Organization +mirror = Mirror +new_repo = New Repository +new_migrate = New Migration +new_mirror = New Mirror +new_fork = New Repository Fork +new_org = New Organization +manage_org = Manage Organizations +admin_panel = Site Administration +account_settings = Account Settings +settings = Settings +your_profile = Profile +your_starred = Starred +your_settings = Settings + +all = All +sources = Sources +mirrors = Mirrors +collaborative = Collaborative +forks = Forks + +activities = Activities +pull_requests = Pull Requests +issues = Issues + +cancel = Cancel + +write = Write +preview = Preview +loading = Loading… + +[install] +install = Installation +title = Initial Configuration +docker_helper = If you run Gitea inside Docker, please read the documentation before changing any settings. +requite_db_desc = Gitea requires MySQL, PostgreSQL, MSSQL or SQLite3. +db_title = Database Settings +db_type = Database Type +host = Host +user = Username +password = Password +db_name = Database Name +db_helper = Note to MySQL users: please use the InnoDB storage engine and the 'utf8_general_ci' character set. +ssl_mode = SSL +path = Path +sqlite_helper = File path for the SQLite3 database.
Enter an absolute path if you run Gitea as a service. +err_empty_db_path = The SQLite3 database path cannot be empty. +no_admin_and_disable_registration = You cannot disable user self-registration without creating an administrator account. +err_empty_admin_password = The administrator password cannot be empty. + +general_title = General Settings +app_name = Site Title +app_name_helper = You can enter your company name here. +repo_path = Repository Root Path +repo_path_helper = Remote Git repositories will be saved to this directory. +lfs_path = Git LFS Root Path +lfs_path_helper = Files tracked by Git LFS will be stored in this directory. Leave empty to disable. +run_user = Run As Username +run_user_helper = Enter the operating system username that Gitea runs as. Note that this user must have access to the repository root path. +domain = SSH Server Domain +domain_helper = Domain or host address for SSH clone URLs. +ssh_port = SSH Server Port +ssh_port_helper = Port number your SSH server listens on. Leave empty to disable. +http_port = Gitea HTTP Listen Port +http_port_helper = Port number the Giteas web server will listen on. +app_url = Gitea Base URL +app_url_helper = Base address for HTTP(S) clone URLs and email notifications. +log_root_path = Log Path +log_root_path_helper = Log files will be written to this directory. + +optional_title = Optional Settings +email_title = Email Settings +smtp_host = SMTP Host +smtp_from = Send Email As +smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" format. +mailer_user = SMTP Username +mailer_password = SMTP Password +register_confirm = Require Email Confirmation to Register +mail_notify = Enable Email Notifications +server_service_title = Server and Third-Party Service Settings +offline_mode = Enable Local Mode +offline_mode_popup = Disable third-party content delivery networks and serve all resources locally. +disable_gravatar = Disable Gravatar +disable_gravatar_popup = Disable Gravatar and third-party avatar sources. A default avatar will be used unless a user locally uploads an avatar. +federated_avatar_lookup = Enable Federated Avatars +federated_avatar_lookup_popup = Enable federated avatar lookup using Libravatar. +disable_registration = Disable Self-Registration +disable_registration_popup = Disable user self-registration. Only administrators will be able to create new user accounts. +allow_only_external_registration_popup = Allow Registration Only Through External Services +openid_signin = Enable OpenID Sign-In +openid_signin_popup = Enable user sign-in via OpenID. +openid_signup = Enable OpenID Self-Registration +openid_signup_popup = Enable OpenID-based user self-registration. +enable_captcha = Enable CAPTCHA +enable_captcha_popup = Require a CAPTCHA for user self-registration. +require_sign_in_view = Require Sign-In to View Pages +require_sign_in_view_popup = Limit page access to signed-in users. Visitors will only see the 'sign in' and registration pages. +admin_setting_desc = Creating an administrator account is optional. The first registered user will automatically become an administrator. +admin_title = Administrator Account Settings +admin_name = Administrator Username +admin_password = Password +confirm_password = Confirm Password +admin_email = Email Address +install_btn_confirm = Install Gitea +test_git_failed = Could not test 'git' command: %v +sqlite3_not_available = This Gitea version does not support SQLite3. Please download the official binary version from %s (not the 'gobuild' version). +invalid_db_setting = The database settings are invalid: %v +invalid_repo_path = The repository root path is invalid: %v +run_user_not_match = The 'run as' username is not the current username: %s -> %s +save_config_failed = Failed to save configuration: %v +invalid_admin_setting = Administrator account setting is invalid: %v +install_success = Welcome! Thank you for choosing Gitea. Have fun and take care! +invalid_log_root_path = The log path is invalid: %v +default_keep_email_private = Hide Email Addresses by Default +default_keep_email_private_popup = Hide email addresses of new user accounts by default. +default_allow_create_organization = Allow Creation of Organizations by Default +default_allow_create_organization_popup = Allow new user accounts to create organizations by default. +default_enable_timetracking = Enable Time Tracking by Default +default_enable_timetracking_popup = Enable time tracking for new repositories by default. +no_reply_address = Hidden Email Domain +no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'. + +[home] +uname_holder = Username or Email Address +password_holder = Password +switch_dashboard_context = Switch Dashboard Context +my_repos = Repositories +show_more_repos = Show more repositories… +collaborative_repos = Collaborative Repositories +my_orgs = My Organizations +my_mirrors = My Mirrors +view_home = View %s +search_repos = Find a repository… + +issues.in_your_repos = In your repositories + +[explore] +repos = Repositories +users = Users +organizations = Organizations +search = Search +code = Code +repo_no_results = No matching repositories found. +user_no_results = No matching users found. +org_no_results = No matching organizations found. +code_no_results = No source code matching your search term found. +code_search_results = Search results for '%s' + +[auth] +create_new_account = Register Account +register_helper_msg = Already have an account? Sign in now! +social_register_helper_msg = Already have an account? Link it now! +disable_register_prompt = Registration is disabled. Please contact your site administrator. +disable_register_mail = Email confirmation for registration is disabled. +remember_me = Remember Me +forgot_password_title= Forgot Password +forgot_password = Forgot password? +sign_up_now = Need an account? Register now. +sign_up_successful = Account was successfully created. +confirmation_mail_sent_prompt = A new confirmation email has been sent to %s. Please check your inbox within the next %s to complete the registration process. +must_change_password = Update your password +allow_password_change = Require user to change password (recommended) +reset_password_mail_sent_prompt = A confirmation email has been sent to %s. Please check your inbox within the next %s to complete the password reset process. +active_your_account = Activate Your Account +account_activated = Account has been activated +prohibit_login = Sign In Prohibited +prohibit_login_desc = Your account is prohibited to sign in, please contact your site administrator. +resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again. +has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (%s). If you haven't received a confirmation email or need to resend a new one, please click on the button below. +resend_mail = Click here to resend your activation email +email_not_associate = The email address is not associated with any account. +send_reset_mail = Click here to resend your password reset email +reset_password = Reset Your Password +invalid_code = Your confirmation code is invalid or has expired. +reset_password_helper = Click here to reset your password +password_too_short = Password length cannot be less than %d characters. +non_local_account = Non-local users can not update their password through the Gitea web interface. +verify = Verify +scratch_code = Scratch code +use_scratch_code = Use a scratch code +twofa_scratch_used = You have used your scratch code. You have been redirected to the two-factor settings page so you may remove your device enrollment or generate a new scratch code. +twofa_passcode_incorrect = Your passcode is incorrect. If you misplaced your device, use your scratch code to sign in. +twofa_scratch_token_incorrect = Your scratch code is incorrect. +login_userpass = Sign In +login_openid = OpenID +oauth_signup_tab = Register New Account +oauth_signup_title = Add Email and Password (for Account Recovery) +oauth_signup_submit = Complete Account +oauth_signin_tab = Link to Existing Account +oauth_signin_title = Sign In to Authorize Linked Account +oauth_signin_submit = Link Account +openid_connect_submit = Connect +openid_connect_title = Connect to an existing account +openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here. +openid_register_title = Create new account +openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here. +openid_signin_desc = Enter your OpenID URI. For example: https://anne.me, bob.openid.org.cn or gnusocial.net/carry. +disable_forgot_password_mail = Password reset is disabled. Please contact your site administrator. +email_domain_blacklisted = You cannot register with your email address. + +[mail] +activate_account = Please activate your account +activate_email = Verify your email address +reset_password = Reset your password +register_success = Registration successful +register_notify = Welcome to Gitea + +[modal] +yes = Yes +no = No +modify = Update + +[form] +UserName = Username +RepoName = Repository name +Email = Email address +Password = Password +Retype = Re-Type Password +SSHTitle = SSH key name +HttpsUrl = HTTPS URL +PayloadUrl = Payload URL +TeamName = Team name +AuthName = Authorization name +AdminEmail = Admin email + +NewBranchName = New branch name +CommitSummary = Commit summary +CommitMessage = Commit message +CommitChoice = Commit choice +TreeName = File path +Content = Content + +require_error = ` cannot be empty.` +alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` +alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` +git_ref_name_error = ` must be a well-formed Git reference name.` +size_error = ` must be size %s.` +min_size_error = ` must contain at least %s characters.` +max_size_error = ` must contain at most %s characters.` +email_error = ` is not a valid email address.` +url_error = ` is not a valid URL.` +include_error = ` must contain substring '%s'.` +unknown_error = Unknown error: +captcha_incorrect = The CAPTCHA code is incorrect. +password_not_match = The passwords do not match. + +username_been_taken = The username is already taken. +repo_name_been_taken = The repository name is already used. +org_name_been_taken = The organization name is already taken. +team_name_been_taken = The team name is already taken. +team_no_units_error = Allow access to at least one repository section. +email_been_used = The email address is already used. +openid_been_used = The OpenID address '%s' is already used. +username_password_incorrect = Username or password is incorrect. +enterred_invalid_repo_name = The repository name you entered is incorrect. +enterred_invalid_owner_name = The new owner name is not valid. +enterred_invalid_password = The password you entered is incorrect. +user_not_exist = The user does not exist. +last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team. +cannot_add_org_to_team = An organization cannot be added as a team member. + +invalid_ssh_key = Can not verify your SSH key: %s +invalid_gpg_key = Can not verify your GPG key: %s +unable_verify_ssh_key = "Can not verify the SSH key; double-check it for mistakes." +auth_failed = Authentication failed: %v + +still_own_repo = "Your account owns one or more repositories; delete or transfer them first." +still_has_org = "Your account is a member of one or more organizations; leave them first." +org_still_own_repo = "This organization still owns one or more repositories; delete or transfer them first." + +target_branch_not_exist = Target branch does not exist. + +[user] +change_avatar = Change your avatar… +join_on = Joined on +repositories = Repositories +activity = Public Activity +followers = Followers +starred = Starred Repositories +following = Following +follow = Follow +unfollow = Unfollow +heatmap.loading = Loading Heatmap… + +form.name_reserved = The username '%s' is reserved. +form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. + +[settings] +profile = Profile +account = Account +password = Password +security = Security +avatar = Avatar +ssh_gpg_keys = SSH / GPG Keys +social = Social Accounts +applications = Applications +orgs = Manage Organizations +repos = Repositories +delete = Delete Account +twofa = Two-Factor Authentication +account_link = Linked Accounts +organization = Organizations +uid = Uid +u2f = Security Keys + +public_profile = Public Profile +profile_desc = Your email address will be used for notifications and other operations. +password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details. +full_name = Full Name +website = Website +location = Location +update_theme = Update Theme +update_profile = Update Profile +update_profile_success = Your profile has been updated. +change_username = Your username has been changed. +change_username_prompt = Note: username changes also change your account URL. +continue = Continue +cancel = Cancel +language = Language +ui = Theme + +lookup_avatar_by_mail = Look Up Avatar by Email Address +federated_avatar_lookup = Federated Avatar Lookup +enable_custom_avatar = Use Custom Avatar +choose_new_avatar = Choose new avatar +update_avatar = Update Avatar +delete_current_avatar = Delete Current Avatar +uploaded_avatar_not_a_image = The uploaded file is not an image. +update_avatar_success = Your avatar has been updated. + +change_password = Update Password +old_password = Current Password +new_password = New Password +retype_new_password = Re-Type New Password +password_incorrect = The current password is incorrect. +change_password_success = Your password has been updated. Sign in using your new password from now on. +password_change_disabled = Non-local users can not update their password through the Gitea web interface. + +emails = Email Addresses +manage_emails = Manage Email Addresses +manage_themes = Select default theme +manage_openid = Manage OpenID Addresses +email_desc = Your primary email address will be used for notifications and other operations. +theme_desc = This will be your default theme across the site. +primary = Primary +primary_email = Make Primary +delete_email = Remove +email_deletion = Remove Email Address +email_deletion_desc = The email address and related information will be removed from your account. Git commits by this email address will remain unchanged. Continue? +email_deletion_success = The email address has been removed. +theme_update_success = Your theme was updated. +theme_update_error = The selected theme does not exist. +openid_deletion = Remove OpenID Address +openid_deletion_desc = Removing this OpenID address from your account will prevent you from signing in with it. Continue? +openid_deletion_success = The OpenID address has been removed. +add_new_email = Add New Email Address +add_new_openid = Add New OpenID URI +add_email = Add Email Address +add_openid = Add OpenID URI +add_email_confirmation_sent = A confirmation email has been sent to '%s'. Please check your inbox within the next %s to confirm your email address. +add_email_success = The new email address has been added. +add_openid_success = The new OpenID address has been added. +keep_email_private = Hide Email Address +keep_email_private_popup = Your email address will be hidden from other users. +openid_desc = OpenID lets you delegate authentication to an external provider. + +manage_ssh_keys = Manage SSH Keys +manage_gpg_keys = Manage GPG Keys +add_key = Add Key +ssh_desc = These public SSH keys are associated with your account. The corresponding private keys allow full access to your repositories. +gpg_desc = These public GPG keys are associated with your account. Keep your private keys safe as they allow commits to be verified. +ssh_helper = Need help? Have a look at GitHub's guide to create your own SSH keys or solve common problems you may encounter using SSH. +gpg_helper = Need help? Have a look at GitHub's guide about GPG. +add_new_key = Add SSH Key +add_new_gpg_key = Add GPG Key +ssh_key_been_used = This SSH key has already been added to the server. +ssh_key_name_used = An SSH key with same name is already added to your account. +gpg_key_id_used = A public GPG key with same ID already exists. +gpg_no_key_email_found = This GPG key is not usable with any email address associated with your account. +subkeys = Subkeys +key_id = Key ID +key_name = Key Name +key_content = Content +add_key_success = The SSH key '%s' has been added. +add_gpg_key_success = The GPG key '%s' has been added. +delete_key = Remove +ssh_key_deletion = Remove SSH Key +gpg_key_deletion = Remove GPG Key +ssh_key_deletion_desc = Removing an SSH key revokes its access to your account. Continue? +gpg_key_deletion_desc = Removing a GPG key un-verifies commits signed by it. Continue? +ssh_key_deletion_success = The SSH key has been removed. +gpg_key_deletion_success = The GPG key has been removed. +add_on = Added on +valid_until = Valid until +valid_forever = Valid forever +last_used = Last used on +no_activity = No recent activity +can_read_info = Read +can_write_info = Write +key_state_desc = This key has been used in the last 7 days +token_state_desc = This token has been used in the last 7 days +show_openid = Show on profile +hide_openid = Hide from profile +ssh_disabled = SSH Disabled + +manage_social = Manage Associated Social Accounts +social_desc = These social accounts are linked to your Gitea account. Make sure you recognize all of them as they can be used to sign in to your Gitea account. +unbind = Unlink +unbind_success = The social account has been unlinked from your Gitea account. + +manage_access_token = Manage Access Tokens +generate_new_token = Generate New Token +tokens_desc = These tokens grant access to your account using the Gitea API. +new_token_desc = Applications using a token have full access to your account. +token_name = Token Name +generate_token = Generate Token +generate_token_success = Your new token has been generated. Copy it now as it will not be shown again. +delete_token = Delete +access_token_deletion = Delete Access Token +access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. Continue? +delete_token_success = The token has been deleted. Applications using it no longer have access to your account. + +twofa_desc = Two-factor authentication enhances the security of your account. +twofa_is_enrolled = Your account is currently enrolled in two-factor authentication. +twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication. +twofa_disable = Disable Two-Factor Authentication +twofa_scratch_token_regenerate = Regenerate Scratch Token +twofa_scratch_token_regenerated = Your scratch token is now %s. Store it in a safe place. +twofa_enroll = Enroll into Two-Factor Authentication +twofa_disable_note = You can disable two-factor authentication if needed. +twofa_disable_desc = Disabling two-factor authentication will make your account less secure. Continue? +regenerate_scratch_token_desc = If you misplaced your scratch token or have already used it to sign in you can reset it here. +twofa_disabled = Two-factor authentication has been disabled. +scan_this_image = Scan this image with your authentication application: +or_enter_secret = Or enter the secret: %s +then_enter_passcode = And enter the passcode shown in the application: +passcode_invalid = The passcode is incorrect. Try again. +twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once! + +u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the FIDO U2F standard. +u2f_require_twofa = Your account must be enrolled in two-factor authentication to use security keys. +u2f_register_key = Add Security Key +u2f_nickname = Nickname +u2f_press_button = Press the button on your security key to register it. +u2f_delete_key = Remove Security Key +u2f_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue? + +manage_account_links = Manage Linked Accounts +manage_account_links_desc = These external accounts are linked to your Gitea account. +account_links_not_available = There are currently no external accounts linked to your Gitea account. +remove_account_link = Remove Linked Account +remove_account_link_desc = Removing a linked account will revoke its access to your Gitea account. Continue? +remove_account_link_success = The linked account has been removed. + +orgs_none = You are not a member of any organizations. +repos_none = You do not own any repositories + +delete_account = Delete Your Account +delete_prompt = This operation will permanently delete your user account. It CAN NOT be undone. +confirm_delete_account = Confirm Deletion +delete_account_title = Delete User Account +delete_account_desc = Are you sure you want to permanently delete this user account? + +[repo] +owner = Owner +repo_name = Repository Name +repo_name_helper = Good repository names use short, memorable and unique keywords. +visibility = Visibility +visibility_helper = Make Repository Private +visibility_helper_forced = Your site administrator forces new repositories to be private. +visibility_fork_helper = (Changing this will affect all forks.) +clone_helper = Need help cloning? Visit Help. +fork_repo = Fork Repository +fork_from = Fork From +fork_visibility_helper = The visibility of a forked repository cannot be changed. +repo_desc = Description +repo_lang = Language +repo_gitignore_helper = Select .gitignore templates. +issue_labels = Issue Labels +issue_labels_helper = Select an issue label set. +license = License +license_helper = Select a license file. +readme = README +readme_helper = Select a README file template. +auto_init = Initialize Repository (Adds .gitignore, Issue Labels, License and README) +create_repo = Create Repository +default_branch = Default Branch +mirror_prune = Prune +mirror_prune_desc = Remove obsolete remote-tracking references +mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable automatic sync. +mirror_interval_invalid = The mirror interval is not valid. +mirror_address = Clone From URL +mirror_address_desc = Include any required authorization credentials in the URL. +mirror_last_synced = Last Synchronized +watchers = Watchers +stargazers = Stargazers +forks = Forks +pick_reaction = Pick your reaction +reactions_more = and %d more + +archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. +archive.issue.nocomment = This repo is archived. You cannot comment on issues. +archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. + +form.reach_limit_of_creation = You have already reached your limit of %d repositories. +form.name_reserved = The repository name '%s' is reserved. +form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. + +need_auth = Clone Authorization +migrate_type = Migration Type +migrate_type_helper = This repository will be a mirror +migrate_repo = Migrate Repository +migrate.clone_address = Migrate / Clone From URL +migrate.clone_address_desc = The HTTP(S) or Git 'clone' URL of an existing repository +migrate.clone_local_path = or a local server path +migrate.permission_denied = You are not allowed to import local repositories. +migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory." +migrate.failed = Migration failed: %v +migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead. + +mirror_from = mirror of +forked_from = forked from +fork_from_self = You cannot fork a repository you own. +fork_guest_user = Sign in to fork this repository. +copy_link = Copy +copy_link_success = Link has been copied +copy_link_error = Use ⌘C or Ctrl-C to copy +copied = Copied OK +unwatch = Unwatch +watch = Watch +unstar = Unstar +star = Star +fork = Fork +download_archive = Download Repository + +no_desc = No Description +quick_guide = Quick Guide +clone_this_repo = Clone this repository +create_new_repo_command = Creating a new repository on the command line +push_exist_repo = Pushing an existing repository from the command line +empty_message = This repository does not contain any content. + +code = Code +code.desc = Access source code, files, commits and branches. +branch = Branch +tree = Tree +filter_branch_and_tag = Filter branch or tag +branches = Branches +tags = Tags +issues = Issues +pulls = Pull Requests +labels = Labels +milestones = Milestones +commits = Commits +commit = Commit +releases = Releases +file_raw = Raw +file_history = History +file_view_raw = View Raw +file_permalink = Permalink +file_too_large = The file is too large to be shown. +video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag. +audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag. +stored_lfs = Stored with Git LFS +commit_graph = Commit Graph + +editor.new_file = New File +editor.upload_file = Upload File +editor.edit_file = Edit File +editor.preview_changes = Preview Changes +editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface. +editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface. +editor.edit_this_file = Edit File +editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file. +editor.fork_before_edit = You must fork this repository to make or propose changes to this file. +editor.delete_this_file = Delete File +editor.must_have_write_access = You must have write access to make or propose changes to this file. +editor.file_delete_success = File '%s' has been deleted. +editor.name_your_file = Name your file… +editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field. +editor.or = or +editor.cancel_lower = Cancel +editor.commit_changes = Commit Changes +editor.add_tmpl = Add '%s/' +editor.add = Add '%s' +editor.update = Update '%s' +editor.delete = Delete '%s' +editor.commit_message_desc = Add an optional extended description… +editor.commit_directly_to_this_branch = Commit directly to the %s branch. +editor.create_new_branch = Create a new branch for this commit and start a pull request. +editor.new_branch_name_desc = New branch name… +editor.cancel = Cancel +editor.filename_cannot_be_empty = The filename cannot be empty. +editor.branch_already_exists = Branch '%s' already exists in this repository. +editor.directory_is_a_file = Directory name '%s' is already used as a filename in this repository. +editor.file_is_a_symlink = '%s' is a symbolic link. Symbolic links cannot be edited in the web editor +editor.filename_is_a_directory = Filename '%s' is already used as a directory name in this repository. +editor.file_editing_no_longer_exists = The file being edited, '%s', no longer exists in this repository. +editor.file_changed_while_editing = The file contents have changed since you started editing. Click here to see them or Commit Changes again to overwrite them. +editor.file_already_exists = A file named '%s' already exists in this repository. +editor.no_changes_to_show = There are no changes to show. +editor.fail_to_update_file = Failed to update/create file '%s' with error: %v +editor.add_subdir = Add a directory… +editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v +editor.upload_files_to_dir = Upload files to '%s' +editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s'. + +commits.desc = Browse source code change history. +commits.commits = Commits +commits.search = Search commits… +commits.find = Search +commits.search_all = All Branches +commits.author = Author +commits.message = Message +commits.date = Date +commits.older = Older +commits.newer = Newer +commits.signed_by = Signed by +commits.gpg_key_id = GPG Key ID + +ext_issues = Ext. Issues +ext_issues.desc = Link to an external issue tracker. + +issues.desc = Organize bug reports, tasks and milestones. +issues.new = New Issue +issues.new.title_empty = Title cannot be empty +issues.new.labels = Labels +issues.new.no_label = No Label +issues.new.clear_labels = Clear labels +issues.new.milestone = Milestone +issues.new.no_milestone = No Milestone +issues.new.clear_milestone = Clear milestone +issues.new.open_milestone = Open Milestones +issues.new.closed_milestone = Closed Milestones +issues.new.assignees = Assignees +issues.new.clear_assignees = Clear assignees +issues.new.no_assignees = No Assignees +issues.no_ref = No Branch/Tag Specified +issues.create = Create Issue +issues.new_label = New Label +issues.new_label_placeholder = Label name +issues.new_label_desc_placeholder = Description +issues.create_label = Create Label +issues.label_templates.title = Load a predefined set of labels +issues.label_templates.info = No labels exist yet. Create a label with 'New Label' or use a predefined label set: +issues.label_templates.helper = Select a label set +issues.label_templates.use = Use Label Set +issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v +issues.add_label_at = added the
%s
label %s +issues.remove_label_at = removed the
%s
label %s +issues.add_milestone_at = `added this to the %s milestone %s` +issues.change_milestone_at = `modified the milestone from %s to %s %s` +issues.remove_milestone_at = `removed this from the %s milestone %s` +issues.deleted_milestone = `(deleted)` +issues.self_assign_at = `self-assigned this %s` +issues.add_assignee_at = `was assigned by %s %s` +issues.remove_assignee_at = `was unassigned by %s %s` +issues.remove_self_assignment = `removed their assignment %s` +issues.change_title_at = `changed title from %s to %s %s` +issues.delete_branch_at = `deleted branch %s %s` +issues.open_tab = %d Open +issues.close_tab = %d Closed +issues.filter_label = Label +issues.filter_label_no_select = All labels +issues.filter_milestone = Milestone +issues.filter_milestone_no_select = All milestones +issues.filter_assignee = Assignee +issues.filter_assginee_no_select = All assignees +issues.filter_type = Type +issues.filter_type.all_issues = All issues +issues.filter_type.assigned_to_you = Assigned to you +issues.filter_type.created_by_you = Created by you +issues.filter_type.mentioning_you = Mentioning you +issues.filter_sort = Sort +issues.filter_sort.latest = Newest +issues.filter_sort.oldest = Oldest +issues.filter_sort.recentupdate = Recently updated +issues.filter_sort.leastupdate = Least recently updated +issues.filter_sort.mostcomment = Most commented +issues.filter_sort.leastcomment = Least commented +issues.filter_sort.moststars = Most stars +issues.filter_sort.feweststars = Fewest stars +issues.filter_sort.mostforks = Most forks +issues.filter_sort.fewestforks = Fewest forks +issues.action_open = Open +issues.action_close = Close +issues.action_label = Label +issues.action_milestone = Milestone +issues.action_milestone_no_select = No milestone +issues.action_assignee = Assignee +issues.action_assignee_no_select = No assignee +issues.opened_by = opened %[1]s by %[3]s +pulls.merged_by = merged %[1]s by %[3]s +issues.closed_by = closed %[1]s by %[3]s +issues.opened_by_fake = opened %[1]s by %[2]s +issues.previous = Previous +issues.next = Next +issues.open_title = Open +issues.closed_title = Closed +issues.num_comments = %d comments +issues.commented_at = `commented %s` +issues.delete_comment_confirm = Are you sure you want to delete this comment? +issues.no_content = There is no content yet. +issues.close_issue = Close +issues.close_comment_issue = Comment and Close +issues.reopen_issue = Reopen +issues.reopen_comment_issue = Comment and Reopen +issues.create_comment = Comment +issues.closed_at = `closed %[2]s` +issues.reopened_at = `reopened %[2]s` +issues.commit_ref_at = `referenced this issue from a commit %[2]s` +issues.poster = Poster +issues.collaborator = Collaborator +issues.owner = Owner +issues.sign_in_require_desc = Sign in to join this conversation. +issues.edit = Edit +issues.cancel = Cancel +issues.save = Save +issues.label_title = Label name +issues.label_description = Label description +issues.label_color = Label color +issues.label_count = %d labels +issues.label_open_issues = %d open issues +issues.label_edit = Edit +issues.label_delete = Delete +issues.label_modify = Edit Label +issues.label_deletion = Delete Label +issues.label_deletion_desc = Deleting a label removes it from all issues. Continue? +issues.label_deletion_success = The label has been deleted. +issues.label.filter_sort.alphabetically = Alphabetically +issues.label.filter_sort.reverse_alphabetically = Reverse alphabetically +issues.label.filter_sort.by_size = Size +issues.label.filter_sort.reverse_by_size = Reverse size +issues.num_participants = %d Participants +issues.attachment.open_tab = `Click to see "%s" in a new tab` +issues.attachment.download = `Click to download "%s"` +issues.subscribe = Subscribe +issues.unsubscribe = Unsubscribe +issues.tracker = Time Tracker +issues.start_tracking_short = Start +issues.start_tracking = Start Time Tracking +issues.start_tracking_history = `started working %s` +issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed +issues.tracking_already_started = `You have already started time tracking on this issue!` +issues.stop_tracking = Stop +issues.stop_tracking_history = `stopped working %s` +issues.add_time = Manually Add Time +issues.add_time_short = Add Time +issues.add_time_cancel = Cancel +issues.add_time_history = `added spent time %s` +issues.add_time_hours = Hours +issues.add_time_minutes = Minutes +issues.add_time_sum_to_small = No time was entered. +issues.cancel_tracking = Cancel +issues.cancel_tracking_history = `cancelled time tracking %s` +issues.time_spent_total = Total Time Spent +issues.time_spent_from_all_authors = `Total Time Spent: %s` +issues.due_date = Due Date +issues.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." +issues.error_modifying_due_date = "Failed to modify the due date." +issues.error_removing_due_date = "Failed to remove the due date." +issues.due_date_form = "yyyy-mm-dd" +issues.due_date_form_add = "Add due date" +issues.due_date_form_edit = "Edit" +issues.due_date_form_remove = "Remove" +issues.due_date_not_writer = "You need repository write access to update an issue's due date." +issues.due_date_not_set = "No due date set." +issues.due_date_added = "added the due date %s %s" +issues.due_date_modified = "modified the due date to %s from %s %s" +issues.due_date_remove = "removed the due date %s %s" +issues.due_date_overdue = "Overdue" +issues.due_date_invalid = "The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'." +issues.dependency.title = Dependencies +issues.dependency.issue_no_dependencies = This issue currently doesn't have any dependencies. +issues.dependency.pr_no_dependencies = This pull request currently doesn't have any dependencies. +issues.dependency.add = Add dependency… +issues.dependency.cancel = Cancel +issues.dependency.remove = Remove +issues.dependency.remove_info = Remove this dependency +issues.dependency.added_dependency = `%[2]s added a new dependency %[3]s` +issues.dependency.removed_dependency = `%[2]s removed a dependency %[3]s` +issues.dependency.issue_closing_blockedby = Closing this pull request is blocked by the following issues +issues.dependency.pr_closing_blockedby = Closing this issue is blocked by the following issues +issues.dependency.issue_close_blocks = This issue blocks closing of the following issues +issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues +issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it. +issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it. +issues.dependency.blocks_short = Blocks +issues.dependency.blocked_by_short = Depends on +issues.dependency.remove_header = Remove Dependency +issues.dependency.issue_remove_text = This will remove the dependency from this issue. Continue? +issues.dependency.pr_remove_text = This will remove the dependency from this pull request. Continue? +issues.dependency.setting = Enable Dependencies For Issues and Pull Requests +issues.dependency.add_error_same_issue = You cannot make an issue depend on itself. +issues.dependency.add_error_dep_issue_not_exist = Dependent issue does not exist. +issues.dependency.add_error_dep_not_exist = Dependency does not exist. +issues.dependency.add_error_dep_exists = Dependency already exists. +issues.dependency.add_error_cannot_create_circular = You cannot create a dependency with two issues blocking each other. +issues.dependency.add_error_dep_not_same_repo = Both issues must be in the same repository. +issues.review.self.approval = You cannot approve your own pull request. +issues.review.self.rejection = You cannot request changes on your own pull request. +issues.review.approve = "approved these changes %s" +issues.review.comment = "reviewed %s" +issues.review.content.empty = You need to leave a comment indicating the requested change(s). +issues.review.reject = "requested changes %s" +issues.review.pending = Pending +issues.review.review = Review +issues.review.reviewers = Reviewers +issues.review.show_outdated = Show outdated +issues.review.hide_outdated = Hide outdated + +pulls.desc = Enable merge requests and code reviews. +pulls.new = New Pull Request +pulls.compare_changes = New Pull Request +pulls.compare_changes_desc = Select the branch to merge into and the branch to pull from. +pulls.compare_base = merge into +pulls.compare_compare = pull from +pulls.filter_branch = Filter branch +pulls.no_results = No results found. +pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request. +pulls.has_pull_request = `A pull request between these branches already exists: %[2]s#%[3]d` +pulls.create = Create Pull Request +pulls.title_desc = wants to merge %[1]d commits from %[2]s into %[3]s +pulls.merged_title_desc = merged %[1]d commits from %[2]s into %[3]s %[4]s +pulls.tab_conversation = Conversation +pulls.tab_commits = Commits +pulls.tab_files = Files Changed +pulls.reopen_to_merge = Please reopen this pull request to perform a merge. +pulls.merged = Merged +pulls.has_merged = The pull request has been merged. +pulls.title_wip_desc = `Start the title with %s to prevent the pull request from being merged accidentally.` +pulls.cannot_merge_work_in_progress = This pull request is marked as a work in progress. Remove the %s prefix from the title when it's ready +pulls.data_broken = This pull request is broken due to missing fork information. +pulls.files_conflicted = This pull request has changes conflicting with the target branch. +pulls.is_checking = "Merge conflict checking is in progress. Try again in few moments." +pulls.blocked_by_approvals = "This Pull Request doesn't have enough approvals yet. %d of %d approvals granted." +pulls.can_auto_merge_desc = This pull request can be merged automatically. +pulls.cannot_auto_merge_desc = This pull request cannot be merged automatically due to conflicts. +pulls.cannot_auto_merge_helper = Merge manually to resolve the conflicts. +pulls.no_merge_desc = This pull request cannot be merged because all repository merge options are disabled. +pulls.no_merge_helper = Enable merge options in the repository settings or merge the pull request manually. +pulls.no_merge_wip = This pull request can not be merged because it is marked as being a work in progress. +pulls.merge_pull_request = Merge Pull Request +pulls.rebase_merge_pull_request = Rebase and Merge +pulls.rebase_merge_commit_pull_request = Rebase and Merge (--no-ff) +pulls.squash_merge_pull_request = Squash and Merge +pulls.invalid_merge_option = You cannot use this merge option for this pull request. +pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.` + +milestones.new = New Milestone +milestones.open_tab = %d Open +milestones.close_tab = %d Closed +milestones.closed = Closed %s +milestones.no_due_date = No due date +milestones.open = Open +milestones.close = Close +milestones.new_subheader = Milestones organize issues and track progress. +milestones.completeness = %d%% Completed +milestones.create = Create Milestone +milestones.title = Title +milestones.desc = Description +milestones.due_date = Due Date (optional) +milestones.clear = Clear +milestones.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." +milestones.create_success = The milestone '%s' has been created. +milestones.edit = Edit Milestone +milestones.edit_subheader = Milestones organize issues and track progress. +milestones.cancel = Cancel +milestones.modify = Update Milestone +milestones.edit_success = Milestone '%s' has been updated. +milestones.deletion = Delete Milestone +milestones.deletion_desc = Deleting a milestone removes it from all related issues. Continue? +milestones.deletion_success = The milestone has been deleted. +milestones.filter_sort.closest_due_date = Closest due date +milestones.filter_sort.furthest_due_date = Furthest due date +milestones.filter_sort.least_complete = Least complete +milestones.filter_sort.most_complete = Most complete +milestones.filter_sort.most_issues = Most issues +milestones.filter_sort.least_issues = Least issues + +ext_wiki = Ext. Wiki +ext_wiki.desc = Link to an external wiki. + +wiki = Wiki +wiki.welcome = Welcome to the Wiki. +wiki.welcome_desc = The wiki lets you write and share documentation with collaborators. +wiki.desc = Write and share documentation with collaborators. +wiki.create_first_page = Create the First Page +wiki.page = Page +wiki.filter_page = Filter page +wiki.new_page = Page +wiki.default_commit_message = Write a note about this page update (optional). +wiki.save_page = Save Page +wiki.last_commit_info = %s edited this page %s +wiki.edit_page_button = Edit +wiki.new_page_button = New Page +wiki.delete_page_button = Delete Page +wiki.delete_page_notice_1 = Deleting the wiki page '%s' cannot be undone. Continue? +wiki.page_already_exists = A wiki page with the same name already exists. +wiki.reserved_page = The wiki page name '%s' is reserved. +wiki.pages = Pages +wiki.last_updated = Last updated %s + +activity = Activity +activity.period.filter_label = Period: +activity.period.daily = 1 day +activity.period.halfweekly = 3 days +activity.period.weekly = 1 week +activity.period.monthly = 1 month +activity.overview = Overview +activity.active_prs_count_1 = %d Active Pull Request +activity.active_prs_count_n = %d Active Pull Requests +activity.merged_prs_count_1 = Merged Pull Request +activity.merged_prs_count_n = Merged Pull Requests +activity.opened_prs_count_1 = Proposed Pull Request +activity.opened_prs_count_n = Proposed Pull Requests +activity.title.user_1 = %d user +activity.title.user_n = %d users +activity.title.prs_1 = %d Pull request +activity.title.prs_n = %d Pull requests +activity.title.prs_merged_by = %s merged by %s +activity.title.prs_opened_by = %s proposed by %s +activity.merged_prs_label = Merged +activity.opened_prs_label = Proposed +activity.active_issues_count_1 = %d Active Issue +activity.active_issues_count_n = %d Active Issues +activity.closed_issues_count_1 = Closed Issue +activity.closed_issues_count_n = Closed Issues +activity.title.issues_1 = %d Issue +activity.title.issues_n = %d Issues +activity.title.issues_closed_by = %s closed by %s +activity.title.issues_created_by = %s created by %s +activity.closed_issue_label = Closed +activity.new_issues_count_1 = New Issue +activity.new_issues_count_n = New Issues +activity.new_issue_label = Opened +activity.title.unresolved_conv_1 = %d Unresolved Conversation +activity.title.unresolved_conv_n = %d Unresolved Conversations +activity.unresolved_conv_desc = These recently changed issues and pull requests have not been resolved yet. +activity.unresolved_conv_label = Open +activity.title.releases_1 = %d Release +activity.title.releases_n = %d Releases +activity.title.releases_published_by = %s published by %s +activity.published_release_label = Published + +search = Search +search.search_repo = Search repository +search.results = Search results for "%s" in %s + +settings = Settings +settings.desc = Settings is where you can manage the settings for the repository +settings.options = Repository +settings.collaboration = Collaborators +settings.collaboration.admin = Administrator +settings.collaboration.write = Write +settings.collaboration.read = Read +settings.collaboration.undefined = Undefined +settings.hooks = Webhooks +settings.githooks = Git Hooks +settings.basic_settings = Basic Settings +settings.mirror_settings = Mirror Settings +settings.sync_mirror = Synchronize Now +settings.mirror_sync_in_progress = Mirror synchronization is in progress. Check back in a minute. +settings.site = Website +settings.update_settings = Update Settings +settings.advanced_settings = Advanced Settings +settings.wiki_desc = Enable Repository Wiki +settings.use_internal_wiki = Use Built-In Wiki +settings.use_external_wiki = Use External Wiki +settings.external_wiki_url = External Wiki URL +settings.external_wiki_url_error = The external wiki URL is not a valid URL. +settings.external_wiki_url_desc = Visitors are redirected to the external wiki URL when clicking the wiki tab. +settings.issues_desc = Enable Repository Issue Tracker +settings.use_internal_issue_tracker = Use Built-In Issue Tracker +settings.use_external_issue_tracker = Use External Issue Tracker +settings.external_tracker_url = External Issue Tracker URL +settings.external_tracker_url_error = The external issue tracker URL is not a valid URL. +settings.external_tracker_url_desc = Visitors are redirected to the external issue tracker URL when clicking on the issues tab. +settings.tracker_url_format = External Issue Tracker URL Format +settings.tracker_url_format_error = The external issue tracker URL format is not a valid URL. +settings.tracker_issue_style = External Issue Tracker Number Format +settings.tracker_issue_style.numeric = Numeric +settings.tracker_issue_style.alphanumeric = Alphanumeric +settings.tracker_url_format_desc = Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. +settings.enable_timetracker = Enable Time Tracking +settings.allow_only_contributors_to_track_time = Let Only Contributors Track Time +settings.pulls_desc = Enable Repository Pull Requests +settings.pulls.ignore_whitespace = Ignore Whitespace for Conflicts +settings.pulls.allow_merge_commits = Enable Commit Merging +settings.pulls.allow_rebase_merge = Enable Rebasing to Merge Commits +settings.pulls.allow_rebase_merge_commit = Enable Rebasing with explicit merge commits (--no-ff) +settings.pulls.allow_squash_commits = Enable Squashing to Merge Commits +settings.admin_settings = Administrator Settings +settings.admin_enable_health_check = Enable Repository Health Checks (git fsck) +settings.admin_enable_close_issues_via_commit_in_any_branch = Close an issue via a commit made in a non default branch +settings.danger_zone = Danger Zone +settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name. +settings.convert = Convert to Regular Repository +settings.convert_desc = You can convert this mirror into a regular repository. This cannot be undone. +settings.convert_notices_1 = This operation will convert the mirror into a regular repository and cannot be undone. +settings.convert_confirm = Convert Repository +settings.convert_succeed = The mirror has been converted into a regular repository. +settings.transfer = Transfer Ownership +settings.transfer_desc = Transfer this repository to a user or to an organization for which you have administrator rights. +settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user. +settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own. +settings.transfer_form_title = Enter the repository name as confirmation: +settings.wiki_delete = Delete Wiki Data +settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone. +settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s. +settings.confirm_wiki_delete = Delete Wiki Data +settings.wiki_deletion_success = The repository wiki data has been deleted. +settings.delete = Delete This Repository +settings.delete_desc = Deleting a repository is permanent and cannot be undone. +settings.delete_notices_1 = - This operation CANNOT be undone. +settings.delete_notices_2 = - This operation will permanently delete the %s repository including code, issues, comments, wiki data and collaborator settings. +settings.delete_notices_fork_1 = - Forks of this repository will become independent after deletion. +settings.deletion_success = The repository has been deleted. +settings.update_settings_success = The repository settings have been updated. +settings.transfer_owner = New Owner +settings.make_transfer = Perform Transfer +settings.transfer_succeed = The repository has been transferred. +settings.confirm_delete = Delete Repository +settings.add_collaborator = Add Collaborator +settings.add_collaborator_success = The collaborator has been added. +settings.add_collaborator_inactive_user = Can not add an inactive user as a collaborator. +settings.add_collaborator_duplicate = The collaborator is already added to this repository. +settings.delete_collaborator = Remove +settings.collaborator_deletion = Remove Collaborator +settings.collaborator_deletion_desc = Removing a collaborator will revoke their access to this repository. Continue? +settings.remove_collaborator_success = The collaborator has been removed. +settings.search_user_placeholder = Search user… +settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator. +settings.add_webhook = Add Webhook +settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character. +settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the webhooks guide. +settings.webhook_deletion = Remove Webhook +settings.webhook_deletion_desc = Removing a webhook deletes its settings and delivery history. Continue? +settings.webhook_deletion_success = The webhook has been removed. +settings.webhook.test_delivery = Test Delivery +settings.webhook.test_delivery_desc = Test this webhook with a fake event. +settings.webhook.test_delivery_success = A fake event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history. +settings.webhook.request = Request +settings.webhook.response = Response +settings.webhook.headers = Headers +settings.webhook.payload = Content +settings.webhook.body = Body +settings.githooks_desc = "Git hooks are powered by Git itself. You can edit hook files below to set up custom operations." +settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook. +settings.githook_name = Hook Name +settings.githook_content = Hook Content +settings.update_githook = Update Hook +settings.add_webhook_desc = Gitea will send POST requests with a specified content type to the target URL. Read more in the webhooks guide. +settings.payload_url = Target URL +settings.content_type = POST Content Type +settings.secret = Secret +settings.slack_username = Username +settings.slack_icon_url = Icon URL +settings.discord_username = Username +settings.discord_icon_url = Icon URL +settings.slack_color = Color +settings.event_desc = Trigger On: +settings.event_push_only = Push Events +settings.event_send_everything = All Events +settings.event_choose = Custom Events… +settings.event_create = Create +settings.event_create_desc = Branch or tag created. +settings.event_delete = Delete +settings.event_delete_desc = Branch or tag deleted +settings.event_fork = Fork +settings.event_fork_desc = Repository forked +settings.event_issues = Issues +settings.event_issues_desc = Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned. +settings.event_issue_comment = Issue Comment +settings.event_issue_comment_desc = Issue comment created, edited, or deleted. +settings.event_release = Release +settings.event_release_desc = Release published, updated or deleted in a repository. +settings.event_pull_request = Pull Request +settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, approved, rejected, review comment, assigned, unassigned, label updated, label cleared or synchronized. +settings.event_push = Push +settings.event_push_desc = Git push to a repository. +settings.event_repository = Repository +settings.event_repository_desc = Repository created or deleted. +settings.active = Active +settings.active_helper = Information about triggered events will be sent to this webhook URL. +settings.add_hook_success = The webhook has been added. +settings.update_webhook = Update Webhook +settings.update_hook_success = The webhook has been updated. +settings.delete_webhook = Remove Webhook +settings.recent_deliveries = Recent Deliveries +settings.hook_type = Hook Type +settings.add_slack_hook_desc = Integrate Slack into your repository. +settings.slack_token = Token +settings.slack_domain = Domain +settings.slack_channel = Channel +settings.add_discord_hook_desc = Integrate Discord into your repository. +settings.add_dingtalk_hook_desc = Integrate Dingtalk into your repository. +settings.deploy_keys = Deploy Keys +settings.add_deploy_key = Add Deploy Key +settings.deploy_key_desc = Deploy keys have read-only pull access to the repository. +settings.is_writable = Enable Write Access +settings.is_writable_info = Allow this deploy key to push to the repository. +settings.no_deploy_keys = There are no deploy keys yet. +settings.title = Title +settings.deploy_key_content = Content +settings.key_been_used = A deploy key with identical content is already in use. +settings.key_name_used = A deploy key with the same name already exists. +settings.add_key_success = The deploy key '%s' has been added. +settings.deploy_key_deletion = Remove Deploy Key +settings.deploy_key_deletion_desc = Removing a deploy key will revoke its access to this repository. Continue? +settings.deploy_key_deletion_success = The deploy key has been removed. +settings.branches = Branches +settings.protected_branch = Branch Protection +settings.protected_branch_can_push = Allow push? +settings.protected_branch_can_push_yes = You can push +settings.protected_branch_can_push_no = You can not push +settings.branch_protection = Branch Protection for Branch '%s' +settings.protect_this_branch = Enable Branch Protection +settings.protect_this_branch_desc = Prevent deletion and disable Git force pushing to the branch. +settings.protect_whitelist_committers = Enable Push Whitelist +settings.protect_whitelist_committers_desc = Allow whitelisted users or teams to bypass push restrictions. +settings.protect_whitelist_users = Whitelisted users for pushing: +settings.protect_whitelist_search_users = Search users… +settings.protect_whitelist_teams = Whitelisted teams for pushing: +settings.protect_whitelist_search_teams = Search teams… +settings.protect_merge_whitelist_committers = Enable Merge Whitelist +settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch. +settings.protect_merge_whitelist_users = Whitelisted users for merging: +settings.protect_merge_whitelist_teams = Whitelisted teams for merging: +settings.protect_required_approvals = Required approvals: +settings.protect_required_approvals_desc = Allow only to merge pull request with enough positive reviews of whitelisted users or teams. +settings.protect_approvals_whitelist_users = Whitelisted reviewers: +settings.protect_approvals_whitelist_teams = Whitelisted teams for reviews: +settings.add_protected_branch = Enable protection +settings.delete_protected_branch = Disable protection +settings.update_protect_branch_success = Branch protection for branch '%s' has been updated. +settings.remove_protected_branch_success = Branch protection for branch '%s' has been disabled. +settings.protected_branch_deletion = Disable Branch Protection +settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? +settings.default_branch_desc = Select a default repository branch for pull requests and code commits: +settings.choose_branch = Choose a branch… +settings.no_protected_branch = There are no protected branches. +settings.edit_protected_branch = Edit +settings.protected_branch_required_approvals_min = Required approvals cannot be negative. +settings.archive.button = Archive Repo +settings.archive.header = Archive This Repo +settings.archive.text = Archiving the repo will make it entirely read-only. It is hidden from the dashboard, cannot be committed to and no issues or pull-requests can be created. +settings.archive.success = The repo was successfully archived. +settings.archive.error = An error occured while trying to archive the repo. See the log for more details. +settings.archive.error_ismirror = You cannot archive a mirrored repo. +settings.archive.branchsettings_unavailable = Branch settings are not available if the repo is archived. +settings.unarchive.button = Un-Archive Repo +settings.unarchive.header = Un-Archive This Repo +settings.unarchive.text = Un-Archiving the repo will restore its ability to recieve commits and pushes, as well as new issues and pull-requests. +settings.unarchive.success = The repo was successfully un-archived. +settings.unarchive.error = An error occured while trying to un-archive the repo. See the log for more details. + +diff.browse_source = Browse Source +diff.parent = parent +diff.commit = commit +diff.data_not_available = Diff Content Not Available +diff.show_diff_stats = Show Diff Stats +diff.show_split_view = Split View +diff.show_unified_view = Unified View +diff.whitespace_button = Whitespace +diff.whitespace_show_everything = Show all changes +diff.whitespace_ignore_all_whitespace = Ignore whitespace when comparing lines +diff.whitespace_ignore_amount_changes = Ignore changes in amount of whitespace +diff.whitespace_ignore_at_eol = Ignore changes in whitespace at EOL +diff.stats_desc = %d changed files with %d additions and %d deletions +diff.bin = BIN +diff.view_file = View File +diff.file_suppressed = File diff suppressed because it is too large +diff.too_many_files = Some files were not shown because too many files changed in this diff +diff.comment.placeholder = Leave a comment +diff.comment.markdown_info = Styling with markdown is supported. +diff.comment.add_single_comment = Add single comment +diff.comment.add_review_comment = Add comment +diff.comment.start_review = Start review +diff.comment.reply = Reply +diff.review = Review +diff.review.header = Submit review +diff.review.placeholder = Review comment +diff.review.comment = Comment +diff.review.approve = Approve +diff.review.reject = Request changes + +releases.desc = Track project versions and downloads. +release.releases = Releases +release.new_release = New Release +release.draft = Draft +release.prerelease = Pre-Release +release.stable = Stable +release.edit = edit +release.ahead = %d commits to %s since this release +release.source_code = Source Code +release.new_subheader = Releases organize project versions. +release.edit_subheader = Releases organize project versions. +release.tag_name = Tag name +release.target = Target +release.tag_helper = Choose an existing tag or create a new tag. +release.title = Title +release.content = Content +release.prerelease_desc = Mark as Pre-Release +release.prerelease_helper = Mark this release unsuitable for production use. +release.cancel = Cancel +release.publish = Publish Release +release.save_draft = Save Draft +release.edit_release = Update Release +release.delete_release = Delete Release +release.deletion = Delete Release +release.deletion_desc = Deleting a release removes its Git tag from the repository. Repository contents and history remain unchanged. Continue? +release.deletion_success = The release has been deleted. +release.tag_name_already_exist = A release with this tag name already exists. +release.tag_name_invalid = The tag name is not valid. +release.downloads = Downloads + +branch.name = Branch Name +branch.search = Search branches +branch.already_exists = A branch named '%s' already exists. +branch.delete_head = Delete +branch.delete = Delete Branch '%s' +branch.delete_html = Delete Branch +branch.delete_desc = Deleting a branch is permanent. It CANNOT be undone. Continue? +branch.deletion_success = Branch '%s' has been deleted. +branch.deletion_failed = Failed to delete branch '%s'. +branch.delete_branch_has_new_commits = Branch '%s' cannot be deleted because new commits have been added after merging. +branch.create_branch = Create branch %s +branch.create_from = from '%s' +branch.create_success = Branch '%s' has been created. +branch.branch_already_exists = Branch '%s' already exists in this repository. +branch.branch_name_conflict = Branch name '%s' conflicts with the already existing branch '%s'. +branch.tag_collision = Branch '%s' cannot be created as a tag with same name already exists in the repository. +branch.deleted_by = Deleted by %s +branch.restore_success = Branch '%s' has been restored. +branch.restore_failed = Failed to restore branch '%s'. +branch.protected_deletion_failed = Branch '%s' is protected. It cannot be deleted. + +topic.manage_topics = Manage Topics +topic.done = Done +topic.count_prompt = You can not select more than 25 topics +topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long. + +[org] +org_name_holder = Organization Name +org_full_name_holder = Organization Full Name +org_name_helper = Organization names should be short and memorable. +create_org = Create Organization +repo_updated = Updated +people = People +teams = Teams +lower_members = members +lower_repositories = repositories +create_new_team = New Team +create_team = Create Team +org_desc = Description +team_name = Team Name +team_desc = Description +team_name_helper = Team names should be short and memorable. +team_desc_helper = Describe the purpose or role of the team. +team_permission_desc = Permission +team_unit_desc = Allow Access to Repository Sections + +form.name_reserved = The organization name '%s' is reserved. +form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. +form.create_org_not_allowed = You are not allowed to create an organization. + +settings = Settings +settings.options = Organization +settings.full_name = Full Name +settings.website = Website +settings.location = Location +settings.update_settings = Update Settings +settings.update_setting_success = Organization settings have been updated. +settings.change_orgname_prompt = Note: changing the organization name also changes the organization's URL. +settings.update_avatar_success = The organization's avatar has been updated. +settings.delete = Delete Organization +settings.delete_account = Delete This Organization +settings.delete_prompt = The organization will be permanently removed. This CANNOT be undone! +settings.confirm_delete_account = Confirm Deletion +settings.delete_org_title = Delete Organization +settings.delete_org_desc = This organization will be deleted permanently. Continue? +settings.hooks_desc = Add webhooks which will be triggered for all repositories under this organization. + +members.membership_visibility = Membership Visibility: +members.public = Visible +members.public_helper = make hidden +members.private = Hidden +members.private_helper = make visible +members.member_role = Member Role: +members.owner = Owner +members.member = Member +members.remove = Remove +members.leave = Leave +members.invite_desc = Add a new member to %s: +members.invite_now = Invite Now + +teams.join = Join +teams.leave = Leave +teams.read_access = Read Access +teams.read_access_helper = Members can view and clone team repositories. +teams.write_access = Write Access +teams.write_access_helper = Members can read and push to team repositories. +teams.admin_access = Administrator Access +teams.admin_access_helper = Members can pull and push to team repositories and add collaborators to them. +teams.no_desc = This team has no description +teams.settings = Settings +teams.owners_permission_desc = Owners have full access to all repositories and have administrator access to the organization. +teams.members = Team Members +teams.update_settings = Update Settings +teams.delete_team = Delete Team +teams.add_team_member = Add Team Member +teams.delete_team_title = Delete Team +teams.delete_team_desc = Deleting a team revokes repository access from its members. Continue? +teams.delete_team_success = The team has been deleted. +teams.read_permission_desc = This team grants Read access: members can view and clone team repositories. +teams.write_permission_desc = This team grants Write access: members can read from and push to team repositories. +teams.admin_permission_desc = This team grants Admin access: members can read from, push to and add collaborators to team repositories. +teams.repositories = Team Repositories +teams.search_repo_placeholder = Search repository… +teams.add_team_repository = Add Team Repository +teams.remove_repo = Remove +teams.add_nonexistent_repo = "The repository you're trying to add does not exist; please create it first." +teams.add_duplicate_users = User is already a team member. +teams.repos.none = No repositories could be accessed by this team. +teams.members.none = No members on this team. + +[admin] +dashboard = Dashboard +users = User Accounts +organizations = Organizations +repositories = Repositories +authentication = Authentication Sources +config = Configuration +notices = System Notices +monitor = Monitoring +first_page = First +last_page = Last +total = Total: %d + +dashboard.statistic = Summary +dashboard.operations = Maintenance Operations +dashboard.system_status = System Status +dashboard.statistic_info = The Gitea database holds %d users, %d organizations, %d public keys, %d repositories, %d watches, %d stars, %d actions, %d accesses, %d issues, %d comments, %d social accounts, %d follows, %d mirrors, %d releases, %d authentication sources, %d webhooks, %d milestones, %d labels, %d hook tasks, %d teams, %d update tasks, %d attachments. +dashboard.operation_name = Operation Name +dashboard.operation_switch = Switch +dashboard.operation_run = Run +dashboard.clean_unbind_oauth = Clean unbound OAuth connections +dashboard.clean_unbind_oauth_success = All unbound OAuth connections have been deleted. +dashboard.delete_inactivate_accounts = Delete all unactivated accounts +dashboard.delete_inactivate_accounts_success = All unactivated accounts have been deleted. +dashboard.delete_repo_archives = Delete all repository archives +dashboard.delete_repo_archives_success = All repository archives have been deleted. +dashboard.delete_missing_repos = Delete all repositories missing their Git files +dashboard.delete_missing_repos_success = All repositories missing their Git files have been deleted. +dashboard.git_gc_repos = Garbage collect all repositories +dashboard.git_gc_repos_success = All repositories have finished garbage collection. +dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.) +dashboard.resync_all_sshkeys_success = The public SSH keys controlled by Gitea have been updated. +dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories. +dashboard.resync_all_hooks_success = All pre-receive, update and post-receive repository hooks have been resynchronized. +dashboard.reinit_missing_repos = Reinitialize all missing Git repositories for which records exist +dashboard.reinit_missing_repos_success = All missing Git repositories for which records existed have been reinitialized. +dashboard.sync_external_users = Synchronize external user data +dashboard.sync_external_users_started = External user data synchronization has started. +dashboard.git_fsck = Execute health checks on all repositories +dashboard.git_fsck_started = Repository health checks have started. +dashboard.server_uptime = Server Uptime +dashboard.current_goroutine = Current Goroutines +dashboard.current_memory_usage = Current Memory Usage +dashboard.total_memory_allocated = Total Memory Allocated +dashboard.memory_obtained = Memory Obtained +dashboard.pointer_lookup_times = Pointer Lookup Times +dashboard.memory_allocate_times = Memory Allocations +dashboard.memory_free_times = Memory Frees +dashboard.current_heap_usage = Current Heap Usage +dashboard.heap_memory_obtained = Heap Memory Obtained +dashboard.heap_memory_idle = Heap Memory Idle +dashboard.heap_memory_in_use = Heap Memory In Use +dashboard.heap_memory_released = Heap Memory Released +dashboard.heap_objects = Heap Objects +dashboard.bootstrap_stack_usage = Bootstrap Stack Usage +dashboard.stack_memory_obtained = Stack Memory Obtained +dashboard.mspan_structures_usage = MSpan Structures Usage +dashboard.mspan_structures_obtained = MSpan Structures Obtained +dashboard.mcache_structures_usage = MCache Structures Usage +dashboard.mcache_structures_obtained = MCache Structures Obtained +dashboard.profiling_bucket_hash_table_obtained = Profiling Bucket Hash Table Obtained +dashboard.gc_metadata_obtained = GC Metadata Obtained +dashboard.other_system_allocation_obtained = Other System Allocation Obtained +dashboard.next_gc_recycle = Next GC Recycle +dashboard.last_gc_time = Since Last GC Time +dashboard.total_gc_time = Total GC Pause +dashboard.total_gc_pause = Total GC Pause +dashboard.last_gc_pause = Last GC Pause +dashboard.gc_times = GC Times + +users.user_manage_panel = User Account Management +users.new_account = Create User Account +users.name = Username +users.activated = Activated +users.admin = Admin +users.repos = Repos +users.created = Created +users.last_login = Last Sign-In +users.never_login = Never Signed-In +users.send_register_notify = Send User Registration Notification +users.new_success = The user account '%s' has been created. +users.edit = Edit +users.auth_source = Authentication Source +users.local = Local +users.auth_login_name = Authentication Sign-In Name +users.password_helper = Leave the password empty to keep it unchanged. +users.update_profile_success = The user account has been updated. +users.edit_account = Edit User Account +users.max_repo_creation = Maximal Number of Repositories +users.max_repo_creation_desc = (Enter -1 to use the global default limit.) +users.is_activated = User Account Is Activated +users.prohibit_login = Disable Sign-In +users.is_admin = Is Administrator +users.allow_git_hook = May Create Git Hooks +users.allow_import_local = May Import Local Repositories +users.allow_create_organization = May Create Organizations +users.update_profile = Update User Account +users.delete_account = Delete User Account +users.still_own_repo = This user still owns one or more repositories. Delete or transfer these repositories first. +users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. +users.deletion_success = The user account has been deleted. + +orgs.org_manage_panel = Organization Management +orgs.name = Name +orgs.teams = Teams +orgs.members = Members +orgs.new_orga = New Organization + +repos.repo_manage_panel = Repository Management +repos.owner = Owner +repos.name = Name +repos.private = Private +repos.watches = Watches +repos.stars = Stars +repos.forks = Forks +repos.issues = Issues +repos.size = Size + +auths.auth_manage_panel = Authentication Source Management +auths.new = Add Authentication Source +auths.name = Name +auths.type = Type +auths.enabled = Enabled +auths.syncenabled = Enable User Synchronization +auths.updated = Updated +auths.auth_type = Authentication Type +auths.auth_name = Authentication Name +auths.security_protocol = Security Protocol +auths.domain = Domain +auths.host = Host +auths.port = Port +auths.bind_dn = Bind DN +auths.bind_password = Bind Password +auths.bind_password_helper = Warning: This password is stored in plain text. Use a read-only account if possible. +auths.user_base = User Search Base +auths.user_dn = User DN +auths.attribute_username = Username Attribute +auths.attribute_username_placeholder = Leave empty to use the username entered in Gitea. +auths.attribute_name = First Name Attribute +auths.attribute_surname = Surname Attribute +auths.attribute_mail = Email Attribute +auths.attribute_ssh_public_key = Public SSH Key Attribute +auths.attributes_in_bind = Fetch Attributes in Bind DN Context +auths.use_paged_search = Use Paged Search +auths.search_page_size = Page Size +auths.filter = User Filter +auths.admin_filter = Admin Filter +auths.ms_ad_sa = MS AD Search Attributes +auths.smtp_auth = SMTP Authentication Type +auths.smtphost = SMTP Host +auths.smtpport = SMTP Port +auths.allowed_domains = Allowed Domains +auths.allowed_domains_helper = Leave empty to allow all domains. Separate multiple domains with a comma (','). +auths.enable_tls = Enable TLS Encryption +auths.skip_tls_verify = Skip TLS Verify +auths.pam_service_name = PAM Service Name +auths.oauth2_provider = OAuth2 Provider +auths.oauth2_clientID = Client ID (Key) +auths.oauth2_clientSecret = Client Secret +auths.openIdConnectAutoDiscoveryURL = OpenID Connect Auto Discovery URL +auths.oauth2_use_custom_url = Use Custom URLs Instead of Default URLs +auths.oauth2_tokenURL = Token URL +auths.oauth2_authURL = Authorize URL +auths.oauth2_profileURL = Profile URL +auths.oauth2_emailURL = Email URL +auths.enable_auto_register = Enable Auto Registration +auths.tips = Tips +auths.tips.oauth2.general = OAuth2 Authentication +auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be: /user/oauth2//callback +auths.tip.oauth2_provider = OAuth2 Provider +auths.tip.bitbucket = Register a new OAuth consumer on https://bitbucket.org/account/user//oauth-consumers/new and add the permission 'Account' - 'Read' +auths.tip.dropbox = Create a new application at https://www.dropbox.com/developers/apps +auths.tip.facebook = Register a new application at https://developers.facebook.com/apps and add the product "Facebook Login" +auths.tip.github = Register a new OAuth application on https://github.com/settings/applications/new +auths.tip.gitlab = Register a new application on https://gitlab.com/profile/applications +auths.tip.google_plus = Obtain OAuth2 client credentials from the Google API console at https://console.developers.google.com/ +auths.tip.openid_connect = Use the OpenID Connect Discovery URL (/.well-known/openid-configuration) to specify the endpoints +auths.tip.twitter = Go to https://dev.twitter.com/apps, create an application and ensure that the “Allow this application to be used to Sign in with Twitter” option is enabled +auths.tip.discord = Register a new application on https://discordapp.com/developers/applications/me +auths.edit = Edit Authentication Source +auths.activated = This Authentication Source is Activated +auths.new_success = The authentication '%s' has been added. +auths.update_success = The authentication source has been updated. +auths.update = Update Authentication Source +auths.delete = Delete Authentication Source +auths.delete_auth_title = Delete Authentication Source +auths.delete_auth_desc = Deleting an authentication source prevents users from using it to sign in. Continue? +auths.still_in_used = The authentication source is still in use. Convert or delete any users using this authentication source first. +auths.deletion_success = The authentication source has been deleted. +auths.login_source_exist = The authentication source '%s' already exists. + +config.server_config = Server Configuration +config.app_name = Site Title +config.app_ver = Gitea Version +config.app_url = Gitea Base URL +config.custom_conf = Configuration File Path +config.domain = SSH Server Domain +config.offline_mode = Local Mode +config.disable_router_log = Disable Router Log +config.run_user = Run As Username +config.run_mode = Run Mode +config.git_version = Git Version +config.repo_root_path = Repository Root Path +config.lfs_root_path = LFS Root Path +config.static_file_root_path = Static File Root Path +config.log_file_root_path = Log Path +config.script_type = Script Type +config.reverse_auth_user = Reverse Authentication User + +config.ssh_config = SSH Configuration +config.ssh_enabled = Enabled +config.ssh_start_builtin_server = Use Built-In Server +config.ssh_domain = Server Domain +config.ssh_port = Port +config.ssh_listen_port = Listen Port +config.ssh_root_path = Root Path +config.ssh_key_test_path = Key Test Path +config.ssh_keygen_path = Keygen ('ssh-keygen') Path +config.ssh_minimum_key_size_check = Minimum Key Size Check +config.ssh_minimum_key_sizes = Minimum Key Sizes + +config.db_config = Database Configuration +config.db_type = Type +config.db_host = Host +config.db_name = Name +config.db_user = Username +config.db_ssl_mode = SSL +config.db_path = Path + +config.service_config = Service Configuration +config.register_email_confirm = Require Email Confirmation to Register +config.disable_register = Disable Self-Registration +config.allow_only_external_registration = Allow Registration Only Through External Services +config.enable_openid_signup = Enable OpenID Self-Registration +config.enable_openid_signin = Enable OpenID Sign-In +config.show_registration_button = Show Register Button +config.require_sign_in_view = Require Sign-In to View Pages +config.mail_notify = Enable Email Notifications +config.disable_key_size_check = Disable Minimum Key Size Check +config.enable_captcha = Enable CAPTCHA +config.active_code_lives = Active Code Lives +config.reset_password_code_lives = Reset Password Code Expiry Time +config.default_keep_email_private = Hide Email Addresses by Default +config.default_allow_create_organization = Allow Creation of Organizations by Default +config.enable_timetracking = Enable Time Tracking +config.default_enable_timetracking = Enable Time Tracking by Default +config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time +config.no_reply_address = Hidden Email Domain +config.default_enable_dependencies = Enable Issue Dependencies by Default + +config.webhook_config = Webhook Configuration +config.queue_length = Queue Length +config.deliver_timeout = Deliver Timeout +config.skip_tls_verify = Skip TLS Verification + +config.mailer_config = SMTP Mailer Configuration +config.mailer_enabled = Enabled +config.mailer_disable_helo = Disable HELO +config.mailer_name = Name +config.mailer_host = Host +config.mailer_user = User +config.mailer_use_sendmail = Use Sendmail +config.mailer_sendmail_path = Sendmail Path +config.mailer_sendmail_args = Extra Arguments to Sendmail +config.send_test_mail = Send Testing Email +config.test_mail_failed = Failed to send a testing email to '%s': %v +config.test_mail_sent = A testing email has been sent to '%s'. + +config.oauth_config = OAuth Configuration +config.oauth_enabled = Enabled + +config.cache_config = Cache Configuration +config.cache_adapter = Cache Adapter +config.cache_interval = Cache Interval +config.cache_conn = Cache Connection + +config.session_config = Session Configuration +config.session_provider = Session Provider +config.provider_config = Provider Config +config.cookie_name = Cookie Name +config.enable_set_cookie = Enable Set Cookie +config.gc_interval_time = GC Interval Time +config.session_life_time = Session Life Time +config.https_only = HTTPS Only +config.cookie_life_time = Cookie Life Time + +config.picture_config = Picture and Avatar Configuration +config.picture_service = Picture Service +config.disable_gravatar = Disable Gravatar +config.enable_federated_avatar = Enable Federated Avatars + +config.git_config = Git Configuration +config.git_disable_diff_highlight = Disable Diff Syntax Highlight +config.git_max_diff_lines = Max Diff Lines (for a single file) +config.git_max_diff_line_characters = Max Diff Characters (for a single line) +config.git_max_diff_files = Max Diff Files (to be shown) +config.git_gc_args = GC Arguments +config.git_migrate_timeout = Migration Timeout +config.git_mirror_timeout = Mirror Update Timeout +config.git_clone_timeout = Clone Operation Timeout +config.git_pull_timeout = Pull Operation Timeout +config.git_gc_timeout = GC Operation Timeout + +config.log_config = Log Configuration +config.log_mode = Log Mode + +monitor.cron = Cron Tasks +monitor.name = Name +monitor.schedule = Schedule +monitor.next = Next Time +monitor.previous = Previous Time +monitor.execute_times = Executions +monitor.process = Running Processes +monitor.desc = Description +monitor.start = Start Time +monitor.execute_time = Execution Time + +notices.system_notice_list = System Notices +notices.view_detail_header = View Notice Details +notices.actions = Actions +notices.select_all = Select All +notices.deselect_all = Deselect All +notices.inverse_selection = Inverse Selection +notices.delete_selected = Delete Selected +notices.delete_all = Delete All Notices +notices.type = Type +notices.type_1 = Repository +notices.desc = Description +notices.op = Op. +notices.delete_success = The system notices have been deleted. + +[action] +create_repo = created repository %s +rename_repo = renamed repository from %[1]s to %[3]s +commit_repo = pushed to %[3]s at %[4]s +create_issue = `opened issue %s#%[2]s` +close_issue = `closed issue %s#%[2]s` +reopen_issue = `reopened issue %s#%[2]s` +create_pull_request = `created pull request %s#%[2]s` +close_pull_request = `closed pull request %s#%[2]s` +reopen_pull_request = `reopened pull request %s#%[2]s` +comment_issue = `commented on issue %s#%[2]s` +merge_pull_request = `merged pull request %s#%[2]s` +transfer_repo = transferred repository %s to %s +push_tag = pushed tag %[2]s to %[3]s +delete_tag = deleted tag %[2]s from %[3]s +delete_branch = deleted branch %[2]s from %[3]s +compare_commits = Compare %d commits +mirror_sync_push = synced commits to %[3]s at %[4]s from mirror +mirror_sync_create = synced new reference %[2]s to %[3]s from mirror +mirror_sync_delete = synced and deleted reference %[2]s at %[3]s from mirror + +[tool] +ago = %s ago +from_now = %s from now +now = now +future = future +1s = 1 second +1m = 1 minute +1h = 1 hour +1d = 1 day +1w = 1 week +1mon = 1 month +1y = 1 year +seconds = %d seconds +minutes = %d minutes +hours = %d hours +days = %d days +weeks = %d weeks +months = %d months +years = %d years +raw_seconds = seconds +raw_minutes = minutes + +[dropzone] +default_message = Drop files or click here to upload. +invalid_input_type = You can not upload files of this type. +file_too_big = File size ({{filesize}} MB) exceeds the maximum size of ({{maxFilesize}} MB). +remove_file = Remove file + +[notification] +notifications = Notifications +unread = Unread +read = Read +no_unread = No unread notifications. +no_read = No read notifications. +pin = Pin notification +mark_as_read = Mark as read +mark_as_unread = Mark as unread +mark_all_as_read = Mark all as read + +[gpg] +error.extract_sign = Failed to extract signature +error.generate_hash = Failed to generate hash of commit +error.no_committer_account = No account linked to committer's email address +error.no_gpg_keys_found = "No known key found for this signature in database" +error.not_signed_commit = "Not a signed commit" +error.failed_retrieval_gpg_keys = "Failed to retrieve any key attached to the committer's account" + +[units] +error.no_unit_allowed_repo = You are not allowed to access any section of this repository. +error.unit_not_allowed = You are not allowed to access this repository section. diff --git a/routers/repo/repo.go b/routers/repo/repo.go index de16739c11593..5cb6efbbf7a39 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -1,419 +1,419 @@ -// 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" - - "code.gitea.io/git" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/auth" - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" -) - -const ( - tplCreate base.TplName = "repo/create" - tplMigrate base.TplName = "repo/migrate" -) - -// MustBeNotEmpty render when a repo is a empty git dir -func MustBeNotEmpty(ctx *context.Context) { - if ctx.Repo.Repository.IsEmpty { - ctx.NotFound("MustBeNotEmpty", nil) - } -} - -// MustBeEditable check that repo can be edited -func MustBeEditable(ctx *context.Context) { - if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { - ctx.NotFound("", nil) - return - } -} - -// MustBeAbleToUpload check that repo can be uploaded to -func MustBeAbleToUpload(ctx *context.Context) { - if !setting.Repository.Upload.Enabled { - ctx.NotFound("", nil) - } -} - -func checkContextUser(ctx *context.Context, uid int64) *models.User { - orgs, err := models.GetOwnedOrgsByUserIDDesc(ctx.User.ID, "updated_unix") - if err != nil { - ctx.ServerError("GetOwnedOrgsByUserIDDesc", err) - return nil - } - ctx.Data["Orgs"] = orgs - - // Not equal means current user is an organization. - if uid == ctx.User.ID || uid == 0 { - return ctx.User - } - - org, err := models.GetUserByID(uid) - if models.IsErrUserNotExist(err) { - return ctx.User - } - - if err != nil { - ctx.ServerError("GetUserByID", fmt.Errorf("[%d]: %v", uid, err)) - return nil - } - - // Check ownership of organization. - if !org.IsOrganization() { - ctx.Error(403) - return nil - } - if !ctx.User.IsAdmin { - isOwner, err := org.IsOwnedBy(ctx.User.ID) - if err != nil { - ctx.ServerError("IsOwnedBy", err) - return nil - } else if !isOwner { - ctx.Error(403) - return nil - } - } - return org -} - -func getRepoPrivate(ctx *context.Context) bool { - switch strings.ToLower(setting.Repository.DefaultPrivate) { - case setting.RepoCreatingLastUserVisibility: - return ctx.User.LastRepoVisibility - case setting.RepoCreatingPrivate: - return true - case setting.RepoCreatingPublic: - return false - default: - return ctx.User.LastRepoVisibility - } -} - -// Create render creating repository page -func Create(ctx *context.Context) { - if !ctx.User.CanCreateRepo() { - ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", ctx.User.MaxCreationLimit()), tplCreate, nil) - } - - ctx.Data["Title"] = ctx.Tr("new_repo") - - // Give default value for template to render. - ctx.Data["Gitignores"] = models.Gitignores - ctx.Data["LabelTemplates"] = models.LabelTemplates - ctx.Data["Licenses"] = models.Licenses - ctx.Data["Readmes"] = models.Readmes - ctx.Data["readme"] = "Default" - ctx.Data["private"] = getRepoPrivate(ctx) - ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - - ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - ctx.HTML(200, tplCreate) -} - -func handleCreateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) { - switch { - case models.IsErrReachLimitOfRepo(err): - ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form) - case models.IsErrRepoAlreadyExist(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) - case models.IsErrNameReserved(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) - case models.IsErrNamePatternNotAllowed(err): - ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) - default: - ctx.ServerError(name, err) - } -} - -// CreatePost response for creating repository -func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { - ctx.Data["Title"] = ctx.Tr("new_repo") - - ctx.Data["Gitignores"] = models.Gitignores - ctx.Data["LabelTemplates"] = models.LabelTemplates - ctx.Data["Licenses"] = models.Licenses - ctx.Data["Readmes"] = models.Readmes - - ctxUser := checkContextUser(ctx, form.UID) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - if ctx.HasError() { - ctx.HTML(200, tplCreate) - return - } - - repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ - Name: form.RepoName, - Description: form.Description, - Gitignores: form.Gitignores, - IssueLabels: form.IssueLabels, - License: form.License, - Readme: form.Readme, - IsPrivate: form.Private || setting.Repository.ForcePrivate, - AutoInit: form.AutoInit, - }) - if err == nil { - log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) - ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name) - return - } - - if repo != nil { - if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil { - log.Error(4, "DeleteRepository: %v", errDelete) - } - } - - handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) -} - -// Migrate render migration of repository page -func Migrate(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("new_migrate") - ctx.Data["private"] = getRepoPrivate(ctx) - ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - ctx.Data["mirror"] = ctx.Query("mirror") == "1" - ctx.Data["LFSActive"] = setting.LFS.StartServer - - ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - ctx.HTML(200, tplMigrate) -} - -// MigratePost response for migrating from external git repository -func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { - ctx.Data["Title"] = ctx.Tr("new_migrate") - - ctxUser := checkContextUser(ctx, form.UID) - if ctx.Written() { - return - } - ctx.Data["ContextUser"] = ctxUser - - if ctx.HasError() { - ctx.HTML(200, tplMigrate) - return - } - - remoteAddr, err := form.ParseRemoteAddr(ctx.User) - if err != nil { - if models.IsErrInvalidCloneAddr(err) { - ctx.Data["Err_CloneAddr"] = true - addrErr := err.(models.ErrInvalidCloneAddr) - switch { - case addrErr.IsURLError: - ctx.RenderWithErr(ctx.Tr("form.url_error"), tplMigrate, &form) - case addrErr.IsPermissionDenied: - ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplMigrate, &form) - case addrErr.IsInvalidPath: - ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplMigrate, &form) - default: - ctx.ServerError("Unknown error", err) - } - } else { - ctx.ServerError("ParseRemoteAddr", err) - } - return - } - - repo, err := models.MigrateRepository(ctx.User, ctxUser, models.MigrateRepoOptions{ - Name: form.RepoName, - Description: form.Description, - IsPrivate: form.Private || setting.Repository.ForcePrivate, - IsMirror: form.Mirror, - RemoteAddr: remoteAddr, - }) - if err == nil { - log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, form.RepoName) - ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + form.RepoName) - return - } - - // remoteAddr may contain credentials, so we sanitize it - err = util.URLSanitizedError(err, remoteAddr) - - if repo != nil { - if errDelete := models.DeleteRepository(ctx.User, 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") { - ctx.Data["Err_Auth"] = true - ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tplMigrate, &form) - return - } else if strings.Contains(err.Error(), "fatal:") { - ctx.Data["Err_CloneAddr"] = true - ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tplMigrate, &form) - return - } - - handleCreateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) -} - -// Action response for actions to a repository -func Action(ctx *context.Context) { - var err error - switch ctx.Params(":action") { - case "watch": - err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) - case "unwatch": - err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) - case "star": - err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) - case "unstar": - err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) - case "desc": // FIXME: this is not used - if !ctx.Repo.IsOwner() { - ctx.Error(404) - return - } - - ctx.Repo.Repository.Description = ctx.Query("desc") - ctx.Repo.Repository.Website = ctx.Query("site") - err = models.UpdateRepository(ctx.Repo.Repository, false) - } - - if err != nil { - ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) - return - } - - ctx.RedirectToFirst(ctx.Query("redirect_to"), ctx.Repo.RepoLink) -} - -// RedirectDownload return a file based on the following infos: -func RedirectDownload(ctx *context.Context) { - var ( - vTag = ctx.Params("vTag") - fileName = ctx.Params("fileName") - ) - tagNames := []string{vTag} - curRepo := ctx.Repo.Repository - releases, err := models.GetReleasesByRepoIDAndNames(curRepo.ID, tagNames) - if err != nil { - if models.IsErrAttachmentNotExist(err) { - ctx.Error(404) - return - } - ctx.ServerError("RedirectDownload", err) - return - } - if len(releases) == 1 { - release := releases[0] - att, err := models.GetAttachmentByReleaseIDFileName(release.ID, fileName) - if err != nil { - ctx.Error(404) - return - } - if att != nil { - ctx.Redirect(setting.AppSubURL + "/attachments/" + att.UUID) - return - } - } - ctx.Error(404) -} - -// Download download an archive of a repository -func Download(ctx *context.Context) { - var ( - uri = ctx.Params("*") - refName string - ext string - archivePath string - archiveType git.ArchiveType - ) - - switch { - case strings.HasSuffix(uri, ".zip"): - ext = ".zip" - archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/zip") - archiveType = git.ZIP - case strings.HasSuffix(uri, ".tar.gz"): - ext = ".tar.gz" - archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/targz") - archiveType = git.TARGZ - default: - log.Trace("Unknown format: %s", uri) - ctx.Error(404) - return - } - refName = strings.TrimSuffix(uri, ext) - - if !com.IsDir(archivePath) { - if err := os.MkdirAll(archivePath, os.ModePerm); err != nil { - ctx.ServerError("Download -> os.MkdirAll(archivePath)", err) - return - } - } - - // Get corresponding commit. - var ( - commit *git.Commit - err error - ) - gitRepo := ctx.Repo.GitRepo - if gitRepo.IsBranchExist(refName) { - commit, err = gitRepo.GetBranchCommit(refName) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return - } - } else if gitRepo.IsTagExist(refName) { - commit, err = gitRepo.GetTagCommit(refName) - if err != nil { - ctx.ServerError("GetTagCommit", err) - return - } - } else if len(refName) >= 4 && len(refName) <= 40 { - commit, err = gitRepo.GetCommit(refName) - if err != nil { - ctx.NotFound("GetCommit", nil) - return - } - } else { - ctx.NotFound("Download", nil) - return - } - - archivePath = path.Join(archivePath, base.ShortSha(commit.ID.String())+ext) - if !com.IsFile(archivePath) { - if err := commit.CreateArchive(archivePath, archiveType); err != nil { - ctx.ServerError("Download -> CreateArchive "+archivePath, err) - return - } - } - - ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext) -} +// 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" + + "code.gitea.io/git" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +const ( + tplCreate base.TplName = "repo/create" + tplMigrate base.TplName = "repo/migrate" +) + +// MustBeNotEmpty render when a repo is a empty git dir +func MustBeNotEmpty(ctx *context.Context) { + if ctx.Repo.Repository.IsEmpty { + ctx.NotFound("MustBeNotEmpty", nil) + } +} + +// MustBeEditable check that repo can be edited +func MustBeEditable(ctx *context.Context) { + if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { + ctx.NotFound("", nil) + return + } +} + +// MustBeAbleToUpload check that repo can be uploaded to +func MustBeAbleToUpload(ctx *context.Context) { + if !setting.Repository.Upload.Enabled { + ctx.NotFound("", nil) + } +} + +func checkContextUser(ctx *context.Context, uid int64) *models.User { + orgs, err := models.GetOwnedOrgsByUserIDDesc(ctx.User.ID, "updated_unix") + if err != nil { + ctx.ServerError("GetOwnedOrgsByUserIDDesc", err) + return nil + } + ctx.Data["Orgs"] = orgs + + // Not equal means current user is an organization. + if uid == ctx.User.ID || uid == 0 { + return ctx.User + } + + org, err := models.GetUserByID(uid) + if models.IsErrUserNotExist(err) { + return ctx.User + } + + if err != nil { + ctx.ServerError("GetUserByID", fmt.Errorf("[%d]: %v", uid, err)) + return nil + } + + // Check ownership of organization. + if !org.IsOrganization() { + ctx.Error(403) + return nil + } + if !ctx.User.IsAdmin { + isOwner, err := org.IsOwnedBy(ctx.User.ID) + if err != nil { + ctx.ServerError("IsOwnedBy", err) + return nil + } else if !isOwner { + ctx.Error(403) + return nil + } + } + return org +} + +func getRepoPrivate(ctx *context.Context) bool { + switch strings.ToLower(setting.Repository.DefaultPrivate) { + case setting.RepoCreatingLastUserVisibility: + return ctx.User.LastRepoVisibility + case setting.RepoCreatingPrivate: + return true + case setting.RepoCreatingPublic: + return false + default: + return ctx.User.LastRepoVisibility + } +} + +// Create render creating repository page +func Create(ctx *context.Context) { + if !ctx.User.CanCreateRepo() { + ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", ctx.User.MaxCreationLimit()), tplCreate, nil) + } + + ctx.Data["Title"] = ctx.Tr("new_repo") + + // Give default value for template to render. + ctx.Data["Gitignores"] = models.Gitignores + ctx.Data["LabelTemplates"] = models.LabelTemplates + ctx.Data["Licenses"] = models.Licenses + ctx.Data["Readmes"] = models.Readmes + ctx.Data["readme"] = "Default" + ctx.Data["private"] = getRepoPrivate(ctx) + ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate + + ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + ctx.HTML(200, tplCreate) +} + +func handleCreateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) { + switch { + case models.IsErrReachLimitOfRepo(err): + ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form) + case models.IsErrRepoAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) + case models.IsErrNameReserved(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) + case models.IsErrNamePatternNotAllowed(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) + default: + ctx.ServerError(name, err) + } +} + +// CreatePost response for creating repository +func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { + ctx.Data["Title"] = ctx.Tr("new_repo") + + ctx.Data["Gitignores"] = models.Gitignores + ctx.Data["LabelTemplates"] = models.LabelTemplates + ctx.Data["Licenses"] = models.Licenses + ctx.Data["Readmes"] = models.Readmes + + ctxUser := checkContextUser(ctx, form.UID) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + if ctx.HasError() { + ctx.HTML(200, tplCreate) + return + } + + repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ + Name: form.RepoName, + Description: form.Description, + Gitignores: form.Gitignores, + IssueLabels: form.IssueLabels, + License: form.License, + Readme: form.Readme, + IsPrivate: form.Private || setting.Repository.ForcePrivate, + AutoInit: form.AutoInit, + }) + if err == nil { + log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) + ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name) + return + } + + if repo != nil { + if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil { + log.Error(4, "DeleteRepository: %v", errDelete) + } + } + + handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) +} + +// Migrate render migration of repository page +func Migrate(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("new_migrate") + ctx.Data["private"] = getRepoPrivate(ctx) + ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate + ctx.Data["mirror"] = ctx.Query("mirror") == "1" + ctx.Data["LFSActive"] = setting.LFS.StartServer + + ctxUser := checkContextUser(ctx, ctx.QueryInt64("org")) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + ctx.HTML(200, tplMigrate) +} + +// MigratePost response for migrating from external git repository +func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { + ctx.Data["Title"] = ctx.Tr("new_migrate") + + ctxUser := checkContextUser(ctx, form.UID) + if ctx.Written() { + return + } + ctx.Data["ContextUser"] = ctxUser + + if ctx.HasError() { + ctx.HTML(200, tplMigrate) + return + } + + remoteAddr, err := form.ParseRemoteAddr(ctx.User) + if err != nil { + if models.IsErrInvalidCloneAddr(err) { + ctx.Data["Err_CloneAddr"] = true + addrErr := err.(models.ErrInvalidCloneAddr) + switch { + case addrErr.IsURLError: + ctx.RenderWithErr(ctx.Tr("form.url_error"), tplMigrate, &form) + case addrErr.IsPermissionDenied: + ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplMigrate, &form) + case addrErr.IsInvalidPath: + ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplMigrate, &form) + default: + ctx.ServerError("Unknown error", err) + } + } else { + ctx.ServerError("ParseRemoteAddr", err) + } + return + } + + repo, err := models.MigrateRepository(ctx.User, ctxUser, models.MigrateRepoOptions{ + Name: form.RepoName, + Description: form.Description, + IsPrivate: form.Private || setting.Repository.ForcePrivate, + IsMirror: form.Mirror, + RemoteAddr: remoteAddr, + }) + if err == nil { + log.Trace("Repository migrated [%d]: %s/%s", repo.ID, ctxUser.Name, form.RepoName) + ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + form.RepoName) + return + } + + // remoteAddr may contain credentials, so we sanitize it + err = util.URLSanitizedError(err, remoteAddr) + + if repo != nil { + if errDelete := models.DeleteRepository(ctx.User, 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") { + ctx.Data["Err_Auth"] = true + ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tplMigrate, &form) + return + } else if strings.Contains(err.Error(), "fatal:") { + ctx.Data["Err_CloneAddr"] = true + ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tplMigrate, &form) + return + } + + handleCreateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) +} + +// Action response for actions to a repository +func Action(ctx *context.Context) { + var err error + switch ctx.Params(":action") { + case "watch": + err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) + case "unwatch": + err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) + case "star": + err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true) + case "unstar": + err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false) + case "desc": // FIXME: this is not used + if !ctx.Repo.IsOwner() { + ctx.Error(404) + return + } + + ctx.Repo.Repository.Description = ctx.Query("desc") + ctx.Repo.Repository.Website = ctx.Query("site") + err = models.UpdateRepository(ctx.Repo.Repository, false) + } + + if err != nil { + ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) + return + } + + ctx.RedirectToFirst(ctx.Query("redirect_to"), ctx.Repo.RepoLink) +} + +// RedirectDownload return a file based on the following infos: +func RedirectDownload(ctx *context.Context) { + var ( + vTag = ctx.Params("vTag") + fileName = ctx.Params("fileName") + ) + tagNames := []string{vTag} + curRepo := ctx.Repo.Repository + releases, err := models.GetReleasesByRepoIDAndNames(curRepo.ID, tagNames) + if err != nil { + if models.IsErrAttachmentNotExist(err) { + ctx.Error(404) + return + } + ctx.ServerError("RedirectDownload", err) + return + } + if len(releases) == 1 { + release := releases[0] + att, err := models.GetAttachmentByReleaseIDFileName(release.ID, fileName) + if err != nil { + ctx.Error(404) + return + } + if att != nil { + ctx.Redirect(setting.AppSubURL + "/attachments/" + att.UUID) + return + } + } + ctx.Error(404) +} + +// Download download an archive of a repository +func Download(ctx *context.Context) { + var ( + uri = ctx.Params("*") + refName string + ext string + archivePath string + archiveType git.ArchiveType + ) + + switch { + case strings.HasSuffix(uri, ".zip"): + ext = ".zip" + archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/zip") + archiveType = git.ZIP + case strings.HasSuffix(uri, ".tar.gz"): + ext = ".tar.gz" + archivePath = path.Join(ctx.Repo.GitRepo.Path, "archives/targz") + archiveType = git.TARGZ + default: + log.Trace("Unknown format: %s", uri) + ctx.Error(404) + return + } + refName = strings.TrimSuffix(uri, ext) + + if !com.IsDir(archivePath) { + if err := os.MkdirAll(archivePath, os.ModePerm); err != nil { + ctx.ServerError("Download -> os.MkdirAll(archivePath)", err) + return + } + } + + // Get corresponding commit. + var ( + commit *git.Commit + err error + ) + gitRepo := ctx.Repo.GitRepo + if gitRepo.IsBranchExist(refName) { + commit, err = gitRepo.GetBranchCommit(refName) + if err != nil { + ctx.ServerError("GetBranchCommit", err) + return + } + } else if gitRepo.IsTagExist(refName) { + commit, err = gitRepo.GetTagCommit(refName) + if err != nil { + ctx.ServerError("GetTagCommit", err) + return + } + } else if len(refName) >= 4 && len(refName) <= 40 { + commit, err = gitRepo.GetCommit(refName) + if err != nil { + ctx.NotFound("GetCommit", nil) + return + } + } else { + ctx.NotFound("Download", nil) + return + } + + archivePath = path.Join(archivePath, base.ShortSha(commit.ID.String())+ext) + if !com.IsFile(archivePath) { + if err := commit.CreateArchive(archivePath, archiveType); err != nil { + ctx.ServerError("Download -> CreateArchive "+archivePath, err) + return + } + } + + ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext) +} diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 55c421084a716..ef9afb5d93a6b 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -1,129 +1,129 @@ -{{template "base/head" .}} -
-
-
-
- {{.CsrfTokenHtml}} -

- {{.i18n.Tr "new_repo"}} -

-
- {{template "base/alert" .}} -
- - -
- -
- - - {{.i18n.Tr "repo.repo_name_helper"}} -
-
- -
- {{if .IsForcedPrivate}} - - - {{else}} - - - {{end}} -
-
-
- - -
- -
- -
- - -
-
- - -
-
- - -
- -
- - -
-
-
- - -
-
- -
- - - {{.i18n.Tr "cancel"}} -
-
-
-
-
-
-{{template "base/footer" .}} +{{template "base/head" .}} +
+
+
+
+ {{.CsrfTokenHtml}} +

+ {{.i18n.Tr "new_repo"}} +

+
+ {{template "base/alert" .}} +
+ + +
+ +
+ + + {{.i18n.Tr "repo.repo_name_helper"}} +
+
+ +
+ {{if .IsForcedPrivate}} + + + {{else}} + + + {{end}} +
+
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ + +
+
+ +
+ + + {{.i18n.Tr "cancel"}} +
+
+
+
+
+
+{{template "base/footer" .}} From f6fd5e7ade4f761a423060da17931afd4aeca422 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 13 Feb 2019 13:19:11 -0600 Subject: [PATCH 3/7] Instead of hardcoding default, make it the helper --- templates/repo/create.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index ef9afb5d93a6b..4b7cc28bf4af5 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -75,7 +75,7 @@
{{.i18n.Tr "repo.issue_labels_helper"}}
+
+ + +
+
@@ -70,19 +84,6 @@
-
- - -