Merge master & resolve conflicts

This commit is contained in:
Manush Dodunekov 2020-01-12 18:06:10 +01:00
commit 095bfa6139
71 changed files with 1617 additions and 954 deletions

View file

@ -558,6 +558,6 @@ pr:
golangci-lint: golangci-lint:
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
export BINARY="golangci-lint"; \ export BINARY="golangci-lint"; \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.20.0; \ curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.22.2; \
fi fi
golangci-lint run --timeout 5m golangci-lint run --timeout 5m

130
cmd/doctor.go Normal file
View file

@ -0,0 +1,130 @@
// Copyright 2019 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 cmd
import (
"bufio"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
)
// CmdDoctor represents the available doctor sub-command.
var CmdDoctor = cli.Command{
Name: "doctor",
Usage: "Diagnose the problems",
Description: "A command to diagnose the problems of current gitea instance according the given configuration.",
Action: runDoctor,
}
type check struct {
title string
f func(ctx *cli.Context) ([]string, error)
}
// checklist represents list for all checks
var checklist = []check{
{
title: "Check if OpenSSH authorized_keys file id correct",
f: runDoctorLocationMoved,
},
// more checks please append here
}
func runDoctor(ctx *cli.Context) error {
err := initDB()
fmt.Println("Using app.ini at", setting.CustomConf)
if err != nil {
fmt.Println(err)
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
return nil
}
for i, check := range checklist {
fmt.Println("[", i+1, "]", check.title)
if messages, err := check.f(ctx); err != nil {
fmt.Println("Error:", err)
} else if len(messages) > 0 {
for _, message := range messages {
fmt.Println("-", message)
}
} else {
fmt.Println("OK.")
}
fmt.Println()
}
return nil
}
func exePath() (string, error) {
file, err := exec.LookPath(os.Args[0])
if err != nil {
return "", err
}
return filepath.Abs(file)
}
func runDoctorLocationMoved(ctx *cli.Context) ([]string, error) {
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
return nil, nil
}
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
f, err := os.Open(fPath)
if err != nil {
return nil, err
}
defer f.Close()
var firstline string
scanner := bufio.NewScanner(f)
for scanner.Scan() {
firstline = strings.TrimSpace(scanner.Text())
if len(firstline) == 0 || firstline[0] == '#' {
continue
}
break
}
// command="/Volumes/data/Projects/gitea/gitea/gitea --config
if len(firstline) > 0 {
exp := regexp.MustCompile(`^[ \t]*(?:command=")([^ ]+) --config='([^']+)' serv key-([^"]+)",(?:[^ ]+) ssh-rsa ([^ ]+) ([^ ]+)[ \t]*$`)
// command="/home/user/gitea --config='/home/user/etc/app.ini' serv key-999",option-1,option-2,option-n ssh-rsa public-key-value key-name
res := exp.FindStringSubmatch(firstline)
if res == nil {
return nil, errors.New("Unknow authorized_keys format")
}
giteaPath := res[1] // => /home/user/gitea
iniPath := res[2] // => /home/user/etc/app.ini
p, err := exePath()
if err != nil {
return nil, err
}
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
if len(giteaPath) > 0 && giteaPath != p {
return []string{fmt.Sprintf("Gitea exe path wants %s but %s on %s", p, giteaPath, fPath)}, nil
}
if len(iniPath) > 0 && iniPath != setting.CustomConf {
return []string{fmt.Sprintf("Gitea config path wants %s but %s on %s", setting.CustomConf, iniPath, fPath)}, nil
}
}
return nil, nil
}

View file

@ -8,10 +8,12 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -58,6 +60,85 @@ var (
} }
) )
type delayWriter struct {
internal io.Writer
buf *bytes.Buffer
timer *time.Timer
}
func newDelayWriter(internal io.Writer, delay time.Duration) *delayWriter {
timer := time.NewTimer(delay)
return &delayWriter{
internal: internal,
buf: &bytes.Buffer{},
timer: timer,
}
}
func (d *delayWriter) Write(p []byte) (n int, err error) {
if d.buf != nil {
select {
case <-d.timer.C:
_, err := d.internal.Write(d.buf.Bytes())
if err != nil {
return 0, err
}
d.buf = nil
return d.internal.Write(p)
default:
return d.buf.Write(p)
}
}
return d.internal.Write(p)
}
func (d *delayWriter) WriteString(s string) (n int, err error) {
if d.buf != nil {
select {
case <-d.timer.C:
_, err := d.internal.Write(d.buf.Bytes())
if err != nil {
return 0, err
}
d.buf = nil
return d.internal.Write([]byte(s))
default:
return d.buf.WriteString(s)
}
}
return d.internal.Write([]byte(s))
}
func (d *delayWriter) Close() error {
if d == nil {
return nil
}
stopped := d.timer.Stop()
if stopped {
return nil
}
select {
case <-d.timer.C:
default:
}
if d.buf == nil {
return nil
}
_, err := d.internal.Write(d.buf.Bytes())
d.buf = nil
return err
}
type nilWriter struct{}
func (n *nilWriter) Write(p []byte) (int, error) {
return len(p), nil
}
func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil
}
func runHookPreReceive(c *cli.Context) error { func runHookPreReceive(c *cli.Context) error {
if os.Getenv(models.EnvIsInternal) == "true" { if os.Getenv(models.EnvIsInternal) == "true" {
return nil return nil
@ -101,6 +182,18 @@ Gitea or set your environment appropriately.`, "")
total := 0 total := 0
lastline := 0 lastline := 0
var out io.Writer
out = &nilWriter{}
if setting.Git.VerbosePush {
if setting.Git.VerbosePushDelay > 0 {
dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
defer dWriter.Close()
out = dWriter
} else {
out = os.Stdout
}
}
for scanner.Scan() { for scanner.Scan() {
// TODO: support news feeds for wiki // TODO: support news feeds for wiki
if isWiki { if isWiki {
@ -124,12 +217,10 @@ Gitea or set your environment appropriately.`, "")
newCommitIDs[count] = newCommitID newCommitIDs[count] = newCommitID
refFullNames[count] = refFullName refFullNames[count] = refFullName
count++ count++
fmt.Fprintf(os.Stdout, "*") fmt.Fprintf(out, "*")
os.Stdout.Sync()
if count >= hookBatchSize { if count >= hookBatchSize {
fmt.Fprintf(os.Stdout, " Checking %d branches\n", count) fmt.Fprintf(out, " Checking %d branches\n", count)
os.Stdout.Sync()
hookOptions.OldCommitIDs = oldCommitIDs hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs hookOptions.NewCommitIDs = newCommitIDs
@ -147,12 +238,10 @@ Gitea or set your environment appropriately.`, "")
lastline = 0 lastline = 0
} }
} else { } else {
fmt.Fprintf(os.Stdout, ".") fmt.Fprintf(out, ".")
os.Stdout.Sync()
} }
if lastline >= hookBatchSize { if lastline >= hookBatchSize {
fmt.Fprintf(os.Stdout, "\n") fmt.Fprintf(out, "\n")
os.Stdout.Sync()
lastline = 0 lastline = 0
} }
} }
@ -162,8 +251,7 @@ Gitea or set your environment appropriately.`, "")
hookOptions.NewCommitIDs = newCommitIDs[:count] hookOptions.NewCommitIDs = newCommitIDs[:count]
hookOptions.RefFullNames = refFullNames[:count] hookOptions.RefFullNames = refFullNames[:count]
fmt.Fprintf(os.Stdout, " Checking %d branches\n", count) fmt.Fprintf(out, " Checking %d branches\n", count)
os.Stdout.Sync()
statusCode, msg := private.HookPreReceive(username, reponame, hookOptions) statusCode, msg := private.HookPreReceive(username, reponame, hookOptions)
switch statusCode { switch statusCode {
@ -173,14 +261,11 @@ Gitea or set your environment appropriately.`, "")
fail(msg, "") fail(msg, "")
} }
} else if lastline > 0 { } else if lastline > 0 {
fmt.Fprintf(os.Stdout, "\n") fmt.Fprintf(out, "\n")
os.Stdout.Sync()
lastline = 0 lastline = 0
} }
fmt.Fprintf(os.Stdout, "Checked %d references in total\n", total) fmt.Fprintf(out, "Checked %d references in total\n", total)
os.Stdout.Sync()
return nil return nil
} }
@ -206,6 +291,19 @@ Gitea or set your environment appropriately.`, "")
} }
} }
var out io.Writer
var dWriter *delayWriter
out = &nilWriter{}
if setting.Git.VerbosePush {
if setting.Git.VerbosePushDelay > 0 {
dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
defer dWriter.Close()
out = dWriter
} else {
out = os.Stdout
}
}
// the environment setted on serv command // the environment setted on serv command
repoUser := os.Getenv(models.EnvRepoUsername) repoUser := os.Getenv(models.EnvRepoUsername)
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true") isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
@ -241,7 +339,7 @@ Gitea or set your environment appropriately.`, "")
continue continue
} }
fmt.Fprintf(os.Stdout, ".") fmt.Fprintf(out, ".")
oldCommitIDs[count] = string(fields[0]) oldCommitIDs[count] = string(fields[0])
newCommitIDs[count] = string(fields[1]) newCommitIDs[count] = string(fields[1])
refFullNames[count] = string(fields[2]) refFullNames[count] = string(fields[2])
@ -250,16 +348,15 @@ Gitea or set your environment appropriately.`, "")
} }
count++ count++
total++ total++
os.Stdout.Sync()
if count >= hookBatchSize { if count >= hookBatchSize {
fmt.Fprintf(os.Stdout, " Processing %d references\n", count) fmt.Fprintf(out, " Processing %d references\n", count)
os.Stdout.Sync()
hookOptions.OldCommitIDs = oldCommitIDs hookOptions.OldCommitIDs = oldCommitIDs
hookOptions.NewCommitIDs = newCommitIDs hookOptions.NewCommitIDs = newCommitIDs
hookOptions.RefFullNames = refFullNames hookOptions.RefFullNames = refFullNames
resp, err := private.HookPostReceive(repoUser, repoName, hookOptions) resp, err := private.HookPostReceive(repoUser, repoName, hookOptions)
if resp == nil { if resp == nil {
_ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
fail("Internal Server Error", err) fail("Internal Server Error", err)
} }
@ -277,9 +374,9 @@ Gitea or set your environment appropriately.`, "")
fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
} }
} }
fmt.Fprintf(os.Stdout, "Processed %d references in total\n", total) fmt.Fprintf(out, "Processed %d references in total\n", total)
os.Stdout.Sync()
_ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
return nil return nil
} }
@ -288,19 +385,18 @@ Gitea or set your environment appropriately.`, "")
hookOptions.NewCommitIDs = newCommitIDs[:count] hookOptions.NewCommitIDs = newCommitIDs[:count]
hookOptions.RefFullNames = refFullNames[:count] hookOptions.RefFullNames = refFullNames[:count]
fmt.Fprintf(os.Stdout, " Processing %d references\n", count) fmt.Fprintf(out, " Processing %d references\n", count)
os.Stdout.Sync()
resp, err := private.HookPostReceive(repoUser, repoName, hookOptions) resp, err := private.HookPostReceive(repoUser, repoName, hookOptions)
if resp == nil { if resp == nil {
_ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
fail("Internal Server Error", err) fail("Internal Server Error", err)
} }
wasEmpty = wasEmpty || resp.RepoWasEmpty wasEmpty = wasEmpty || resp.RepoWasEmpty
results = append(results, resp.Results...) results = append(results, resp.Results...)
fmt.Fprintf(os.Stdout, "Processed %d references in total\n", total) fmt.Fprintf(out, "Processed %d references in total\n", total)
os.Stdout.Sync()
if wasEmpty && masterPushed { if wasEmpty && masterPushed {
// We need to tell the repo to reset the default branch to master // We need to tell the repo to reset the default branch to master
@ -309,7 +405,7 @@ Gitea or set your environment appropriately.`, "")
fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err) fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
} }
} }
_ = dWriter.Close()
hookPrintResults(results) hookPrintResults(results)
return nil return nil

View file

@ -522,6 +522,8 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false`
- `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view. - `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view.
- `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`. See more on http://git-scm.com/docs/git-gc/ - `GC_ARGS`: **\<empty\>**: Arguments for command `git gc`, e.g. `--aggressive --auto`. See more on http://git-scm.com/docs/git-gc/
- `ENABLE_AUTO_GIT_WIRE_PROTOCOL`: **true**: If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1 - `ENABLE_AUTO_GIT_WIRE_PROTOCOL`: **true**: If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1
- `VERBOSE_PUSH`: **true**: Print status information about pushes as they are being processed.
- `VERBOSE_PUSH_DELAY`: **5s**: Only print verbose information if push takes longer than this delay.
## Git - Timeout settings (`git.timeout`) ## Git - Timeout settings (`git.timeout`)
- `DEFAUlT`: **360**: Git operations default timeout seconds. - `DEFAUlT`: **360**: Git operations default timeout seconds.

View file

@ -289,3 +289,28 @@ This command is idempotent.
#### convert #### convert
Converts an existing MySQL database from utf8 to utf8mb4. Converts an existing MySQL database from utf8 to utf8mb4.
#### doctor
Diagnose the problems of current gitea instance according the given configuration.
Currently there are a check list below:
- Check if OpenSSH authorized_keys file id correct
When your gitea instance support OpenSSH, your gitea instance binary path will be written to `authorized_keys`
when there is any public key added or changed on your gitea instance.
Sometimes if you moved or renamed your gitea binary when upgrade and you haven't run `Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.)` on your Admin Panel. Then all pull/push via SSH will not be work.
This check will help you to check if it works well.
For contributors, if you want to add more checks, you can wrie ad new function like `func(ctx *cli.Context) ([]string, error)` and
append it to `doctor.go`.
```go
var checklist = []check{
{
title: "Check if OpenSSH authorized_keys file id correct",
f: runDoctorLocationMoved,
},
// more checks please append here
}
```
This function will receive a command line context and return a list of details about the problems or error.

View file

@ -7,6 +7,7 @@ package integrations
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"testing" "testing"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -120,3 +121,47 @@ func TestAPIEditIssue(t *testing.T) {
assert.Equal(t, body, issueAfter.Content) assert.Equal(t, body, issueAfter.Content)
assert.Equal(t, title, issueAfter.Title) assert.Equal(t, title, issueAfter.Title)
} }
func TestAPISearchIssue(t *testing.T) {
defer prepareTestEnv(t)()
session := loginUser(t, "user2")
token := getTokenForLoggedInUser(t, session)
link, _ := url.Parse("/api/v1/repos/issues/search")
req := NewRequest(t, "GET", link.String())
resp := session.MakeRequest(t, req, http.StatusOK)
var apiIssues []*api.Issue
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 8)
query := url.Values{}
query.Add("token", token)
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 8)
query.Add("state", "closed")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 2)
query.Set("state", "all")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 10) //there are more but 10 is page item limit
query.Add("page", "2")
link.RawQuery = query.Encode()
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
assert.Len(t, apiIssues, 0)
}

View file

@ -136,3 +136,17 @@ func TestAPIOrgDeny(t *testing.T) {
MakeRequest(t, req, http.StatusNotFound) MakeRequest(t, req, http.StatusNotFound)
}) })
} }
func TestAPIGetAll(t *testing.T) {
defer prepareTestEnv(t)()
req := NewRequestf(t, "GET", "/api/v1/orgs")
resp := MakeRequest(t, req, http.StatusOK)
var apiOrgList []*api.Organization
DecodeJSON(t, resp, &apiOrgList)
assert.Len(t, apiOrgList, 7)
assert.Equal(t, "org25", apiOrgList[0].FullName)
assert.Equal(t, "public", apiOrgList[0].Visibility)
}

View file

@ -68,6 +68,7 @@ arguments - which can alternatively be run by running the subcommand web.`
cmd.CmdMigrate, cmd.CmdMigrate,
cmd.CmdKeys, cmd.CmdKeys,
cmd.CmdConvert, cmd.CmdConvert,
cmd.CmdDoctor,
} }
// Now adjust these commands to add our global configuration options // Now adjust these commands to add our global configuration options

View file

@ -145,7 +145,7 @@ func (a *Action) GetActAvatar() string {
// GetRepoUserName returns the name of the action repository owner. // GetRepoUserName returns the name of the action repository owner.
func (a *Action) GetRepoUserName() string { func (a *Action) GetRepoUserName() string {
a.loadRepo() a.loadRepo()
return a.Repo.MustOwner().Name return a.Repo.OwnerName
} }
// ShortRepoUserName returns the name of the action repository owner // ShortRepoUserName returns the name of the action repository owner

View file

@ -1,6 +1,7 @@
- -
id: 1 id: 1
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: repo1 lower_name: repo1
name: repo1 name: repo1
is_private: false is_private: false
@ -16,6 +17,7 @@
- -
id: 2 id: 2
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: repo2 lower_name: repo2
name: repo2 name: repo2
is_private: true is_private: true
@ -30,6 +32,7 @@
- -
id: 3 id: 3
owner_id: 3 owner_id: 3
owner_name: user3
lower_name: repo3 lower_name: repo3
name: repo3 name: repo3
is_private: true is_private: true
@ -43,6 +46,7 @@
- -
id: 4 id: 4
owner_id: 5 owner_id: 5
owner_name: user5
lower_name: repo4 lower_name: repo4
name: repo4 name: repo4
is_private: false is_private: false
@ -56,6 +60,7 @@
- -
id: 5 id: 5
owner_id: 3 owner_id: 3
owner_name: user3
lower_name: repo5 lower_name: repo5
name: repo5 name: repo5
is_private: true is_private: true
@ -70,6 +75,7 @@
- -
id: 6 id: 6
owner_id: 10 owner_id: 10
owner_name: user10
lower_name: repo6 lower_name: repo6
name: repo6 name: repo6
is_private: true is_private: true
@ -83,6 +89,7 @@
- -
id: 7 id: 7
owner_id: 10 owner_id: 10
owner_name: user10
lower_name: repo7 lower_name: repo7
name: repo7 name: repo7
is_private: true is_private: true
@ -96,6 +103,7 @@
- -
id: 8 id: 8
owner_id: 10 owner_id: 10
owner_name: user10
lower_name: repo8 lower_name: repo8
name: repo8 name: repo8
is_private: false is_private: false
@ -109,6 +117,7 @@
- -
id: 9 id: 9
owner_id: 11 owner_id: 11
owner_name: user11
lower_name: repo9 lower_name: repo9
name: repo9 name: repo9
is_private: false is_private: false
@ -122,6 +131,7 @@
- -
id: 10 id: 10
owner_id: 12 owner_id: 12
owner_name: user12
lower_name: repo10 lower_name: repo10
name: repo10 name: repo10
is_private: false is_private: false
@ -137,6 +147,7 @@
id: 11 id: 11
fork_id: 10 fork_id: 10
owner_id: 13 owner_id: 13
owner_name: user13
lower_name: repo11 lower_name: repo11
name: repo11 name: repo11
is_private: false is_private: false
@ -150,6 +161,7 @@
- -
id: 12 id: 12
owner_id: 14 owner_id: 14
owner_name: user14
lower_name: test_repo_12 lower_name: test_repo_12
name: test_repo_12 name: test_repo_12
is_private: false is_private: false
@ -163,6 +175,7 @@
- -
id: 13 id: 13
owner_id: 14 owner_id: 14
owner_name: user14
lower_name: test_repo_13 lower_name: test_repo_13
name: test_repo_13 name: test_repo_13
is_private: true is_private: true
@ -176,6 +189,7 @@
- -
id: 14 id: 14
owner_id: 14 owner_id: 14
owner_name: user14
lower_name: test_repo_14 lower_name: test_repo_14
name: test_repo_14 name: test_repo_14
description: test_description_14 description: test_description_14
@ -190,6 +204,7 @@
- -
id: 15 id: 15
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: repo15 lower_name: repo15
name: repo15 name: repo15
is_empty: true is_empty: true
@ -198,6 +213,7 @@
- -
id: 16 id: 16
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: repo16 lower_name: repo16
name: repo16 name: repo16
is_private: true is_private: true
@ -211,6 +227,7 @@
- -
id: 17 id: 17
owner_id: 15 owner_id: 15
owner_name: user15
lower_name: big_test_public_1 lower_name: big_test_public_1
name: big_test_public_1 name: big_test_public_1
is_private: false is_private: false
@ -226,6 +243,7 @@
- -
id: 18 id: 18
owner_id: 15 owner_id: 15
owner_name: user15
lower_name: big_test_public_2 lower_name: big_test_public_2
name: big_test_public_2 name: big_test_public_2
is_private: false is_private: false
@ -240,6 +258,7 @@
- -
id: 19 id: 19
owner_id: 15 owner_id: 15
owner_name: user15
lower_name: big_test_private_1 lower_name: big_test_private_1
name: big_test_private_1 name: big_test_private_1
is_private: true is_private: true
@ -254,6 +273,7 @@
- -
id: 20 id: 20
owner_id: 15 owner_id: 15
owner_name: user15
lower_name: big_test_private_2 lower_name: big_test_private_2
name: big_test_private_2 name: big_test_private_2
is_private: true is_private: true
@ -268,6 +288,7 @@
- -
id: 21 id: 21
owner_id: 16 owner_id: 16
owner_name: user16
lower_name: big_test_public_3 lower_name: big_test_public_3
name: big_test_public_3 name: big_test_public_3
is_private: false is_private: false
@ -282,6 +303,7 @@
- -
id: 22 id: 22
owner_id: 16 owner_id: 16
owner_name: user16
lower_name: big_test_private_3 lower_name: big_test_private_3
name: big_test_private_3 name: big_test_private_3
is_private: true is_private: true
@ -296,6 +318,7 @@
- -
id: 23 id: 23
owner_id: 17 owner_id: 17
owner_name: user17
lower_name: big_test_public_4 lower_name: big_test_public_4
name: big_test_public_4 name: big_test_public_4
is_private: false is_private: false
@ -310,6 +333,7 @@
- -
id: 24 id: 24
owner_id: 17 owner_id: 17
owner_name: user17
lower_name: big_test_private_4 lower_name: big_test_private_4
name: big_test_private_4 name: big_test_private_4
is_private: true is_private: true
@ -324,6 +348,7 @@
- -
id: 25 id: 25
owner_id: 20 owner_id: 20
owner_name: user20
lower_name: big_test_public_mirror_5 lower_name: big_test_public_mirror_5
name: big_test_public_mirror_5 name: big_test_public_mirror_5
is_private: false is_private: false
@ -339,6 +364,7 @@
- -
id: 26 id: 26
owner_id: 20 owner_id: 20
owner_name: user20
lower_name: big_test_private_mirror_5 lower_name: big_test_private_mirror_5
name: big_test_private_mirror_5 name: big_test_private_mirror_5
is_private: true is_private: true
@ -354,6 +380,7 @@
- -
id: 27 id: 27
owner_id: 19 owner_id: 19
owner_name: user19
lower_name: big_test_public_mirror_6 lower_name: big_test_public_mirror_6
name: big_test_public_mirror_6 name: big_test_public_mirror_6
is_private: false is_private: false
@ -370,6 +397,7 @@
- -
id: 28 id: 28
owner_id: 19 owner_id: 19
owner_name: user19
lower_name: big_test_private_mirror_6 lower_name: big_test_private_mirror_6
name: big_test_private_mirror_6 name: big_test_private_mirror_6
is_private: true is_private: true
@ -387,6 +415,7 @@
id: 29 id: 29
fork_id: 27 fork_id: 27
owner_id: 20 owner_id: 20
owner_name: user20
lower_name: big_test_public_fork_7 lower_name: big_test_public_fork_7
name: big_test_public_fork_7 name: big_test_public_fork_7
is_private: false is_private: false
@ -402,6 +431,7 @@
id: 30 id: 30
fork_id: 28 fork_id: 28
owner_id: 20 owner_id: 20
owner_name: user20
lower_name: big_test_private_fork_7 lower_name: big_test_private_fork_7
name: big_test_private_fork_7 name: big_test_private_fork_7
is_private: true is_private: true
@ -416,6 +446,7 @@
- -
id: 31 id: 31
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: repo20 lower_name: repo20
name: repo20 name: repo20
num_stars: 0 num_stars: 0
@ -427,6 +458,7 @@
- -
id: 32 # org public repo id: 32 # org public repo
owner_id: 3 owner_id: 3
owner_name: user3
lower_name: repo21 lower_name: repo21
name: repo21 name: repo21
is_private: false is_private: false
@ -439,6 +471,7 @@
- -
id: 33 id: 33
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: utf8 lower_name: utf8
name: utf8 name: utf8
is_private: false is_private: false
@ -447,6 +480,7 @@
- -
id: 34 id: 34
owner_id: 21 owner_id: 21
owner_name: user21
lower_name: golang lower_name: golang
name: golang name: golang
is_private: false is_private: false
@ -459,6 +493,7 @@
- -
id: 35 id: 35
owner_id: 21 owner_id: 21
owner_name: user21
lower_name: graphql lower_name: graphql
name: graphql name: graphql
is_private: false is_private: false
@ -471,6 +506,7 @@
- -
id: 36 id: 36
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: commits_search_test lower_name: commits_search_test
name: commits_search_test name: commits_search_test
is_private: false is_private: false
@ -483,6 +519,7 @@
- -
id: 37 id: 37
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: git_hooks_test lower_name: git_hooks_test
name: git_hooks_test name: git_hooks_test
is_private: false is_private: false
@ -495,6 +532,7 @@
- -
id: 38 id: 38
owner_id: 22 owner_id: 22
owner_name: limited_org
lower_name: public_repo_on_limited_org lower_name: public_repo_on_limited_org
name: public_repo_on_limited_org name: public_repo_on_limited_org
is_private: false is_private: false
@ -507,6 +545,7 @@
- -
id: 39 id: 39
owner_id: 22 owner_id: 22
owner_name: limited_org
lower_name: private_repo_on_limited_org lower_name: private_repo_on_limited_org
name: private_repo_on_limited_org name: private_repo_on_limited_org
is_private: true is_private: true
@ -519,6 +558,7 @@
- -
id: 40 id: 40
owner_id: 23 owner_id: 23
owner_name: limited_org
lower_name: public_repo_on_private_org lower_name: public_repo_on_private_org
name: public_repo_on_private_org name: public_repo_on_private_org
is_private: false is_private: false
@ -531,6 +571,7 @@
- -
id: 41 id: 41
owner_id: 23 owner_id: 23
owner_name: limited_org
lower_name: private_repo_on_private_org lower_name: private_repo_on_private_org
name: private_repo_on_private_org name: private_repo_on_private_org
is_private: true is_private: true
@ -542,6 +583,7 @@
- -
id: 42 id: 42
owner_id: 2 owner_id: 2
owner_name: user2
lower_name: glob lower_name: glob
name: glob name: glob
is_private: false is_private: false
@ -554,6 +596,7 @@
- -
id: 43 id: 43
owner_id: 26 owner_id: 26
owner_name: org26
lower_name: repo26 lower_name: repo26
name: repo26 name: repo26
is_private: true is_private: true
@ -566,6 +609,7 @@
- -
id: 44 id: 44
owner_id: 27 owner_id: 27
owner_name: user27
lower_name: template1 lower_name: template1
name: template1 name: template1
is_private: false is_private: false
@ -579,6 +623,7 @@
- -
id: 45 id: 45
owner_id: 27 owner_id: 27
owner_name: user27
lower_name: template2 lower_name: template2
name: template2 name: template2
is_private: false is_private: false
@ -592,6 +637,7 @@
- -
id: 46 id: 46
owner_id: 26 owner_id: 26
owner_name: org26
lower_name: repo_external_tracker lower_name: repo_external_tracker
name: repo_external_tracker name: repo_external_tracker
is_private: false is_private: false
@ -604,6 +650,7 @@
- -
id: 47 id: 47
owner_id: 26 owner_id: 26
owner_name: org26
lower_name: repo_external_tracker_numeric lower_name: repo_external_tracker_numeric
name: repo_external_tracker_numeric name: repo_external_tracker_numeric
is_private: false is_private: false
@ -616,6 +663,7 @@
- -
id: 48 id: 48
owner_id: 26 owner_id: 26
owner_name: org26
lower_name: repo_external_tracker_alpha lower_name: repo_external_tracker_alpha
name: repo_external_tracker_alpha name: repo_external_tracker_alpha
is_private: false is_private: false

View file

@ -43,7 +43,7 @@ func FullPushingEnvironment(author, committer *User, repo *Repository, repoName
"GIT_COMMITTER_NAME="+committerSig.Name, "GIT_COMMITTER_NAME="+committerSig.Name,
"GIT_COMMITTER_EMAIL="+committerSig.Email, "GIT_COMMITTER_EMAIL="+committerSig.Email,
EnvRepoName+"="+repoName, EnvRepoName+"="+repoName,
EnvRepoUsername+"="+repo.MustOwnerName(), EnvRepoUsername+"="+repo.OwnerName,
EnvRepoIsWiki+"="+isWiki, EnvRepoIsWiki+"="+isWiki,
EnvPusherName+"="+committer.Name, EnvPusherName+"="+committer.Name,
EnvPusherID+"="+fmt.Sprintf("%d", committer.ID), EnvPusherID+"="+fmt.Sprintf("%d", committer.ID),

View file

@ -22,9 +22,9 @@ var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})")
// GetLabelTemplateFile loads the label template file by given name, // GetLabelTemplateFile loads the label template file by given name,
// then parses and returns a list of name-color pairs and optionally description. // then parses and returns a list of name-color pairs and optionally description.
func GetLabelTemplateFile(name string) ([][3]string, error) { func GetLabelTemplateFile(name string) ([][3]string, error) {
data, err := getRepoInitFile("label", name) data, err := GetRepoInitFile("label", name)
if err != nil { if err != nil {
return nil, fmt.Errorf("getRepoInitFile: %v", err) return nil, fmt.Errorf("GetRepoInitFile: %v", err)
} }
lines := strings.Split(string(data), "\n") lines := strings.Split(string(data), "\n")
@ -175,8 +175,8 @@ func initalizeLabels(e Engine, repoID int64, labelTemplate string) error {
} }
// InitalizeLabels adds a label set to a repository using a template // InitalizeLabels adds a label set to a repository using a template
func InitalizeLabels(repoID int64, labelTemplate string) error { func InitalizeLabels(ctx DBContext, repoID int64, labelTemplate string) error {
return initalizeLabels(x, repoID, labelTemplate) return initalizeLabels(ctx.e, repoID, labelTemplate)
} }
func newLabel(e Engine, label *Label) error { func newLabel(e Engine, label *Label) error {

View file

@ -295,6 +295,8 @@ var migrations = []Migration{
// v119 -> v120 // v119 -> v120
NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType), NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType),
// v120 -> v121 // v120 -> v121
NewMigration("Add owner_name on table repository", addOwnerNameOnRepository),
// v121 -> v122
NewMigration("add is_restricted column for users table", addIsRestricted), NewMigration("add is_restricted column for users table", addIsRestricted),
} }

View file

@ -4,14 +4,17 @@
package migrations package migrations
import "xorm.io/xorm" import (
"xorm.io/xorm"
)
func addIsRestricted(x *xorm.Engine) error { func addOwnerNameOnRepository(x *xorm.Engine) error {
// User see models/user.go type Repository struct {
type User struct { OwnerName string
ID int64 `xorm:"pk autoincr"`
IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
} }
if err := x.Sync2(new(Repository)); err != nil {
return x.Sync2(new(User)) return err
}
_, err := x.Exec("UPDATE repository SET owner_name = (SELECT name FROM `user` WHERE `user`.id = repository.owner_id)")
return err
} }

17
models/migrations/v121.go Normal file
View file

@ -0,0 +1,17 @@
// Copyright 2020 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 migrations
import "xorm.io/xorm"
func addIsRestricted(x *xorm.Engine) error {
// User see models/user.go
type User struct {
ID int64 `xorm:"pk autoincr"`
IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync2(new(User))
}

View file

@ -5,12 +5,9 @@
package models package models
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -377,133 +374,3 @@ func TestUsersInTeamsCount(t *testing.T) {
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4 test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5 test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
} }
func TestIncludesAllRepositoriesTeams(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testTeamRepositories := func(teamID int64, repoIds []int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
assert.NoError(t, team.GetRepositories(), "%s: GetRepositories", team.Name)
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
assert.Equal(t, len(repoIds), len(team.Repos), "%s: repo count", team.Name)
for i, rid := range repoIds {
if rid > 0 {
assert.True(t, team.HasRepository(rid), "%s: HasRepository(%d) %d", rid, i)
}
}
}
// Get an admin user.
user, err := GetUserByID(1)
assert.NoError(t, err, "GetUserByID")
// Create org.
org := &User{
Name: "All repo",
IsActive: true,
Type: UserTypeOrganization,
Visibility: structs.VisibleTypePublic,
}
assert.NoError(t, CreateOrganization(org, user), "CreateOrganization")
// Check Owner team.
ownerTeam, err := org.GetOwnerTeam()
assert.NoError(t, err, "GetOwnerTeam")
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
// Create repos.
repoIds := make([]int64, 0)
for i := 0; i < 3; i++ {
r, err := CreateRepository(user, org, CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
assert.NoError(t, err, "CreateRepository %d", i)
if r != nil {
repoIds = append(repoIds, r.ID)
}
}
// Get fresh copy of Owner team after creating repos.
ownerTeam, err = org.GetOwnerTeam()
assert.NoError(t, err, "GetOwnerTeam")
// Create teams and check repositories.
teams := []*Team{
ownerTeam,
{
OrgID: org.ID,
Name: "team one",
Authorize: AccessModeRead,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 2",
Authorize: AccessModeRead,
IncludesAllRepositories: false,
},
{
OrgID: org.ID,
Name: "team three",
Authorize: AccessModeWrite,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 4",
Authorize: AccessModeWrite,
IncludesAllRepositories: false,
},
}
teamRepos := [][]int64{
repoIds,
repoIds,
{},
repoIds,
{},
}
for i, team := range teams {
if i > 0 { // first team is Owner.
assert.NoError(t, NewTeam(team), "%s: NewTeam", team.Name)
}
testTeamRepositories(team.ID, teamRepos[i])
}
// Update teams and check repositories.
teams[3].IncludesAllRepositories = false
teams[4].IncludesAllRepositories = true
teamRepos[4] = repoIds
for i, team := range teams {
assert.NoError(t, UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name)
testTeamRepositories(team.ID, teamRepos[i])
}
// Create repo and check teams repositories.
org.Teams = nil // Reset teams to allow their reloading.
r, err := CreateRepository(user, org, CreateRepoOptions{Name: "repo-last"})
assert.NoError(t, err, "CreateRepository last")
if r != nil {
repoIds = append(repoIds, r.ID)
}
teamRepos[0] = repoIds
teamRepos[1] = repoIds
teamRepos[4] = repoIds
for i, team := range teams {
testTeamRepositories(team.ID, teamRepos[i])
}
// Remove repo and check teams repositories.
assert.NoError(t, DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository")
teamRepos[0] = repoIds[1:]
teamRepos[1] = repoIds[1:]
teamRepos[3] = repoIds[1:3]
teamRepos[4] = repoIds[1:]
for i, team := range teams {
testTeamRepositories(team.ID, teamRepos[i])
}
// Wipe created items.
for i, rid := range repoIds {
if i > 0 { // first repo already deleted.
assert.NoError(t, DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i)
}
}
assert.NoError(t, DeleteOrganization(org), "DeleteOrganization")
}

View file

@ -70,7 +70,7 @@ func (pr *PullRequest) MustHeadUserName() string {
log.Error("LoadHeadRepo: %v", err) log.Error("LoadHeadRepo: %v", err)
return "" return ""
} }
return pr.HeadRepo.MustOwnerName() return pr.HeadRepo.OwnerName
} }
// Note: don't try to get Issue because will end up recursive querying. // Note: don't try to get Issue because will end up recursive querying.

View file

@ -6,7 +6,6 @@
package models package models
import ( import (
"bytes"
"context" "context"
"crypto/md5" "crypto/md5"
"errors" "errors"
@ -38,7 +37,6 @@ import (
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/mcuadros/go-version"
"github.com/unknwon/com" "github.com/unknwon/com"
"xorm.io/builder" "xorm.io/builder"
) )
@ -149,9 +147,9 @@ const (
// Repository represents a git repository. // Repository represents a git repository.
type Repository struct { type Repository struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"UNIQUE(s) index"` OwnerID int64 `xorm:"UNIQUE(s) index"`
OwnerName string `xorm:"-"` OwnerName string
Owner *User `xorm:"-"` Owner *User `xorm:"-"`
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
Name string `xorm:"INDEX NOT NULL"` Name string `xorm:"INDEX NOT NULL"`
@ -252,17 +250,9 @@ func (repo *Repository) MustOwner() *User {
return repo.mustOwner(x) 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 // FullName returns the repository full name
func (repo *Repository) FullName() string { func (repo *Repository) FullName() string {
return repo.MustOwnerName() + "/" + repo.Name return repo.OwnerName + "/" + repo.Name
} }
// HTMLURL returns the repository HTML URL // HTMLURL returns the repository HTML URL
@ -294,7 +284,7 @@ func (repo *Repository) GetCommitsCountCacheKey(contextName string, isRef bool)
func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) *api.Repository { func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) *api.Repository {
var parent *api.Repository var parent *api.Repository
cloneLink := repo.cloneLink(e, false) cloneLink := repo.cloneLink(false)
permission := &api.Permission{ permission := &api.Permission{
Admin: mode >= AccessModeAdmin, Admin: mode >= AccessModeAdmin,
Push: mode >= AccessModeWrite, Push: mode >= AccessModeWrite,
@ -356,6 +346,8 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
allowSquash = config.AllowSquash allowSquash = config.AllowSquash
} }
repo.mustOwner(e)
return &api.Repository{ return &api.Repository{
ID: repo.ID, ID: repo.ID,
Owner: repo.Owner.APIFormat(), Owner: repo.Owner.APIFormat(),
@ -533,46 +525,11 @@ func (repo *Repository) mustOwner(e Engine) *User {
return repo.Owner 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("Error loading repository owner name: %v", err)
return "error"
}
return repo.OwnerName
}
// ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers. // ComposeMetas composes a map of metas for properly rendering issue links and external issue trackers.
func (repo *Repository) ComposeMetas() map[string]string { func (repo *Repository) ComposeMetas() map[string]string {
if repo.RenderingMetas == nil { if repo.RenderingMetas == nil {
metas := map[string]string{ metas := map[string]string{
"user": repo.MustOwner().Name, "user": repo.OwnerName,
"repo": repo.Name, "repo": repo.Name,
"repoPath": repo.RepoPath(), "repoPath": repo.RepoPath(),
} }
@ -588,6 +545,7 @@ func (repo *Repository) ComposeMetas() map[string]string {
} }
} }
repo.MustOwner()
if repo.Owner.IsOrganization() { if repo.Owner.IsOrganization() {
teams := make([]string, 0, 5) teams := make([]string, 0, 5)
_ = x.Table("team_repo"). _ = x.Table("team_repo").
@ -597,7 +555,7 @@ func (repo *Repository) ComposeMetas() map[string]string {
OrderBy("team.lower_name"). OrderBy("team.lower_name").
Find(&teams) Find(&teams)
metas["teams"] = "," + strings.Join(teams, ",") + "," metas["teams"] = "," + strings.Join(teams, ",") + ","
metas["org"] = repo.Owner.LowerName metas["org"] = strings.ToLower(repo.OwnerName)
} }
repo.RenderingMetas = metas repo.RenderingMetas = metas
@ -711,13 +669,9 @@ func (repo *Repository) getTemplateRepo(e Engine) (err error) {
return err return err
} }
func (repo *Repository) repoPath(e Engine) string {
return RepoPath(repo.mustOwnerName(e), repo.Name)
}
// RepoPath returns the repository path // RepoPath returns the repository path
func (repo *Repository) RepoPath() string { func (repo *Repository) RepoPath() string {
return repo.repoPath(x) return RepoPath(repo.OwnerName, repo.Name)
} }
// GitConfigPath returns the path to a repository's git config/ directory // GitConfigPath returns the path to a repository's git config/ directory
@ -742,7 +696,7 @@ func (repo *Repository) Link() string {
// ComposeCompareURL returns the repository comparison URL // ComposeCompareURL returns the repository comparison URL
func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) return fmt.Sprintf("%s/compare/%s...%s", repo.FullName(), oldCommitID, newCommitID)
} }
// UpdateDefaultBranch updates the default branch // UpdateDefaultBranch updates the default branch
@ -757,9 +711,9 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
} }
func (repo *Repository) updateSize(e Engine) error { func (repo *Repository) updateSize(e Engine) error {
size, err := util.GetDirectorySize(repo.repoPath(e)) size, err := util.GetDirectorySize(repo.RepoPath())
if err != nil { if err != nil {
return fmt.Errorf("UpdateSize: %v", err) return fmt.Errorf("updateSize: %v", err)
} }
repo.Size = size repo.Size = size
@ -768,8 +722,8 @@ func (repo *Repository) updateSize(e Engine) error {
} }
// UpdateSize updates the repository size, calculating it using util.GetDirectorySize // UpdateSize updates the repository size, calculating it using util.GetDirectorySize
func (repo *Repository) UpdateSize() error { func (repo *Repository) UpdateSize(ctx DBContext) error {
return repo.updateSize(x) return repo.updateSize(ctx.e)
} }
// CanUserFork returns true if specified user can fork repository. // CanUserFork returns true if specified user can fork repository.
@ -912,7 +866,7 @@ func ComposeHTTPSCloneURL(owner, repo string) string {
return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo)) return fmt.Sprintf("%s%s/%s.git", setting.AppURL, url.PathEscape(owner), url.PathEscape(repo))
} }
func (repo *Repository) cloneLink(e Engine, isWiki bool) *CloneLink { func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
repoName := repo.Name repoName := repo.Name
if isWiki { if isWiki {
repoName += ".wiki" repoName += ".wiki"
@ -923,22 +877,21 @@ func (repo *Repository) cloneLink(e Engine, isWiki bool) *CloneLink {
sshUser = setting.SSH.BuiltinServerUser sshUser = setting.SSH.BuiltinServerUser
} }
repo.Owner = repo.mustOwner(e)
cl := new(CloneLink) cl := new(CloneLink)
if setting.SSH.Port != 22 { 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) cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", sshUser, setting.SSH.Domain, setting.SSH.Port, repo.OwnerName, repoName)
} else if setting.Repository.UseCompatSSHURI { } else if setting.Repository.UseCompatSSHURI {
cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, setting.SSH.Domain, repo.OwnerName, repoName)
} else { } else {
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, setting.SSH.Domain, repo.Owner.Name, repoName) cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, setting.SSH.Domain, repo.OwnerName, repoName)
} }
cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName) cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName)
return cl return cl
} }
// CloneLink returns clone URLs of repository. // CloneLink returns clone URLs of repository.
func (repo *Repository) CloneLink() (cl *CloneLink) { func (repo *Repository) CloneLink() (cl *CloneLink) {
return repo.cloneLink(x, false) return repo.cloneLink(false)
} }
// CheckCreateRepository check if could created a repository // CheckCreateRepository check if could created a repository
@ -1011,64 +964,6 @@ func createDelegateHooks(repoPath string) (err error) {
return nil return nil
} }
// initRepoCommit temporarily changes with work directory.
func initRepoCommit(tmpPath string, repo *Repository, u *User) (err error) {
commitTimeStr := time.Now().Format(time.RFC3339)
sig := u.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
if stdout, err := git.NewCommand("add", "--all").
SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
RunInDir(tmpPath); err != nil {
log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)
return fmt.Errorf("git add --all: %v", err)
}
binVersion, err := git.BinVersion()
if err != nil {
return fmt.Errorf("Unable to get git version: %v", err)
}
args := []string{
"commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", "Initial commit",
}
if version.Compare(binVersion, "1.7.9", ">=") {
sign, keyID := SignInitialCommit(tmpPath, u)
if sign {
args = append(args, "-S"+keyID)
} else if version.Compare(binVersion, "2.0.0", ">=") {
args = append(args, "--no-gpg-sign")
}
}
if stdout, err := git.NewCommand(args...).
SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
RunInDirWithEnv(tmpPath, env); err != nil {
log.Error("Failed to commit: %v: Stdout: %s\nError: %v", args, stdout, err)
return fmt.Errorf("git commit: %v", err)
}
if stdout, err := git.NewCommand("push", "origin", "master").
SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)).
RunInDirWithEnv(tmpPath, InternalPushingEnvironment(u, repo)); err != nil {
log.Error("Failed to push back to master: Stdout: %s\nError: %v", stdout, err)
return fmt.Errorf("git push: %v", err)
}
return nil
}
// CreateRepoOptions contains the create repository options // CreateRepoOptions contains the create repository options
type CreateRepoOptions struct { type CreateRepoOptions struct {
Name string Name string
@ -1085,7 +980,8 @@ type CreateRepoOptions struct {
Status RepositoryStatus Status RepositoryStatus
} }
func getRepoInitFile(tp, name string) ([]byte, error) { // GetRepoInitFile returns repository init files
func GetRepoInitFile(tp, name string) ([]byte, error) {
cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
relPath := path.Join("options", tp, cleanedName) relPath := path.Join("options", tp, cleanedName)
@ -1109,140 +1005,6 @@ func getRepoInitFile(tp, name string) ([]byte, error) {
} }
} }
func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts CreateRepoOptions) error {
commitTimeStr := time.Now().Format(time.RFC3339)
authorSig := repo.Owner.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+authorSig.Name,
"GIT_COMMITTER_EMAIL="+authorSig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
// Clone to temporary path and do the init commit.
if stdout, err := git.NewCommand("clone", repoPath, tmpDir).
SetDescription(fmt.Sprintf("initRepository (git clone): %s to %s", repoPath, tmpDir)).
RunInDirWithEnv("", env); err != nil {
log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err)
return fmt.Errorf("git clone: %v", err)
}
// 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
}
func checkInitRepository(repoPath string) (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)
}
return nil
}
// InitRepository initializes README and .gitignore if needed.
func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) {
if err = checkInitRepository(repoPath); err != nil {
return err
}
// Initialize repository according to user's choice.
if opts.AutoInit {
tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name)
if err != nil {
return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.repoPath(e), 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, repo, u); 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 ( var (
reservedRepoNames = []string{".", ".."} reservedRepoNames = []string{".", ".."}
reservedRepoPatterns = []string{"*.git", "*.wiki"} reservedRepoPatterns = []string{"*.git", "*.wiki"}
@ -1253,22 +1015,23 @@ func IsUsableRepoName(name string) error {
return isUsableName(reservedRepoNames, reservedRepoPatterns, name) return isUsableName(reservedRepoNames, reservedRepoPatterns, name)
} }
func createRepository(e Engine, doer, u *User, repo *Repository) (err error) { // CreateRepository creates a repository for the user/organization.
func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) {
if err = IsUsableRepoName(repo.Name); err != nil { if err = IsUsableRepoName(repo.Name); err != nil {
return err return err
} }
has, err := isRepositoryExist(e, u, repo.Name) has, err := isRepositoryExist(ctx.e, u, repo.Name)
if err != nil { if err != nil {
return fmt.Errorf("IsRepositoryExist: %v", err) return fmt.Errorf("IsRepositoryExist: %v", err)
} else if has { } else if has {
return ErrRepoAlreadyExist{u.Name, repo.Name} return ErrRepoAlreadyExist{u.Name, repo.Name}
} }
if _, err = e.Insert(repo); err != nil { if _, err = ctx.e.Insert(repo); err != nil {
return err return err
} }
if err = deleteRepoRedirect(e, u.ID, repo.Name); err != nil { if err = deleteRepoRedirect(ctx.e, u.ID, repo.Name); err != nil {
return err return err
} }
@ -1297,20 +1060,19 @@ func createRepository(e Engine, doer, u *User, repo *Repository) (err error) {
Type: tp, Type: tp,
}) })
} }
} }
if _, err = e.Insert(&units); err != nil { if _, err = ctx.e.Insert(&units); err != nil {
return err return err
} }
// Remember visibility preference. // Remember visibility preference.
u.LastRepoVisibility = repo.IsPrivate u.LastRepoVisibility = repo.IsPrivate
if err = updateUserCols(e, u, "last_repo_visibility"); err != nil { if err = updateUserCols(ctx.e, u, "last_repo_visibility"); err != nil {
return fmt.Errorf("updateUser: %v", err) return fmt.Errorf("updateUser: %v", err)
} }
if _, err = e.Incr("num_repos").ID(u.ID).Update(new(User)); err != nil { if _, err = ctx.e.Incr("num_repos").ID(u.ID).Update(new(User)); err != nil {
return fmt.Errorf("increment user total_repos: %v", err) return fmt.Errorf("increment user total_repos: %v", err)
} }
u.NumRepos++ u.NumRepos++
@ -1322,106 +1084,41 @@ func createRepository(e Engine, doer, u *User, repo *Repository) (err error) {
} }
for _, t := range u.Teams { for _, t := range u.Teams {
if t.IncludesAllRepositories { if t.IncludesAllRepositories {
if err := t.addRepository(e, repo); err != nil { if err := t.addRepository(ctx.e, repo); err != nil {
return fmt.Errorf("addRepository: %v", err) return fmt.Errorf("addRepository: %v", err)
} }
} }
} }
if isAdmin, err := isUserRepoAdmin(e, repo, doer); err != nil { if isAdmin, err := isUserRepoAdmin(ctx.e, repo, doer); err != nil {
return fmt.Errorf("isUserRepoAdmin: %v", err) return fmt.Errorf("isUserRepoAdmin: %v", err)
} else if !isAdmin { } else if !isAdmin {
// Make creator repo admin if it wan't assigned automatically // Make creator repo admin if it wan't assigned automatically
if err = repo.addCollaborator(e, doer); err != nil { if err = repo.addCollaborator(ctx.e, doer); err != nil {
return fmt.Errorf("AddCollaborator: %v", err) return fmt.Errorf("AddCollaborator: %v", err)
} }
if err = repo.changeCollaborationAccessMode(e, doer.ID, AccessModeAdmin); err != nil { if err = repo.changeCollaborationAccessMode(ctx.e, doer.ID, AccessModeAdmin); err != nil {
return fmt.Errorf("ChangeCollaborationAccessMode: %v", err) return fmt.Errorf("ChangeCollaborationAccessMode: %v", err)
} }
} }
} else if err = repo.recalculateAccesses(e); err != nil { } else if err = repo.recalculateAccesses(ctx.e); err != nil {
// Organization automatically called this in addRepository method. // Organization automatically called this in addRepository method.
return fmt.Errorf("recalculateAccesses: %v", err) return fmt.Errorf("recalculateAccesses: %v", err)
} }
if setting.Service.AutoWatchNewRepos { if setting.Service.AutoWatchNewRepos {
if err = watchRepo(e, doer.ID, repo.ID, true); err != nil { if err = watchRepo(ctx.e, doer.ID, repo.ID, true); err != nil {
return fmt.Errorf("watchRepo: %v", err) return fmt.Errorf("watchRepo: %v", err)
} }
} }
if err = copyDefaultWebhooksToRepo(e, repo.ID); err != nil { if err = copyDefaultWebhooksToRepo(ctx.e, repo.ID); err != nil {
return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err) return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err)
} }
return nil 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,
OriginalURL: opts.OriginalURL,
OriginalServiceType: opts.GitServiceType,
IsPrivate: opts.IsPrivate,
IsFsckEnabled: !opts.IsMirror,
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status,
IsEmpty: !opts.AutoInit,
}
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("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)
}
// Initialize Issue Labels if selected
if len(opts.IssueLabels) > 0 {
if err = initalizeLabels(sess, repo.ID, opts.IssueLabels); err != nil {
return nil, fmt.Errorf("initalizeLabels: %v", err)
}
}
if stdout, err := git.NewCommand("update-server-info").
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
RunInDir(repoPath); err != nil {
log.Error("CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
return nil, fmt.Errorf("CreateRepository(git update-server-info): %v", err)
}
}
if err = sess.Commit(); err != nil {
return nil, err
}
return repo, err
}
func countRepositories(userID int64, private bool) int64 { func countRepositories(userID int64, private bool) int64 {
sess := x.Where("id > 0") sess := x.Where("id > 0")
@ -1458,6 +1155,12 @@ func RepoPath(userName, repoName string) string {
return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git")
} }
// IncrementRepoForkNum increment repository fork number
func IncrementRepoForkNum(ctx DBContext, repoID int64) error {
_, err := ctx.e.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID)
return err
}
// TransferOwnership transfers all corresponding setting from old user to new one. // TransferOwnership transfers all corresponding setting from old user to new one.
func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error { func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error {
newOwner, err := GetUserByName(newOwnerName) newOwner, err := GetUserByName(newOwnerName)
@ -1485,6 +1188,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
// new owner. // new owner.
repo.OwnerID = newOwner.ID repo.OwnerID = newOwner.ID
repo.Owner = newOwner repo.Owner = newOwner
repo.OwnerName = newOwner.Name
// Update repository. // Update repository.
if _, err := sess.ID(repo.ID).Update(repo); err != nil { if _, err := sess.ID(repo.ID).Update(repo); err != nil {
@ -1683,7 +1387,7 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
} }
// Create/Remove git-daemon-export-ok for git-daemon... // Create/Remove git-daemon-export-ok for git-daemon...
daemonExportFile := path.Join(repo.repoPath(e), `git-daemon-export-ok`) daemonExportFile := path.Join(repo.RepoPath(), `git-daemon-export-ok`)
if repo.IsPrivate && com.IsExist(daemonExportFile) { if repo.IsPrivate && com.IsExist(daemonExportFile) {
if err = os.Remove(daemonExportFile); err != nil { if err = os.Remove(daemonExportFile); err != nil {
log.Error("Failed to remove %s: %v", daemonExportFile, err) log.Error("Failed to remove %s: %v", daemonExportFile, err)
@ -1715,6 +1419,11 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
return nil return nil
} }
// UpdateRepositoryCtx updates a repository with db context
func UpdateRepositoryCtx(ctx DBContext, repo *Repository, visibilityChanged bool) error {
return updateRepository(ctx.e, repo, visibilityChanged)
}
// UpdateRepository updates a repository // UpdateRepository updates a repository
func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) {
sess := x.NewSession() sess := x.NewSession()
@ -1905,7 +1614,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
} }
// FIXME: Remove repository files should be executed after transaction succeed. // FIXME: Remove repository files should be executed after transaction succeed.
repoPath := repo.repoPath(sess) repoPath := repo.RepoPath()
removeAllWithNotice(sess, "Delete repository files", repoPath) removeAllWithNotice(sess, "Delete repository files", repoPath)
err = repo.deleteWiki(sess) err = repo.deleteWiki(sess)
@ -2030,6 +1739,11 @@ func GetRepositoryByID(id int64) (*Repository, error) {
return getRepositoryByID(x, id) return getRepositoryByID(x, id)
} }
// GetRepositoryByIDCtx returns the repository by given id if exists.
func GetRepositoryByIDCtx(ctx DBContext, id int64) (*Repository, error) {
return getRepositoryByID(ctx.e, id)
}
// GetRepositoriesMapByIDs returns the repositories by given id slice. // GetRepositoriesMapByIDs returns the repositories by given id slice.
func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) {
var repos = make(map[int64]*Repository, len(ids)) var repos = make(map[int64]*Repository, len(ids))
@ -2290,7 +2004,7 @@ func GitGcRepos() error {
SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())). SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())).
RunInDirTimeout( RunInDirTimeout(
time.Duration(setting.Git.Timeout.GC)*time.Second, time.Duration(setting.Git.Timeout.GC)*time.Second,
RepoPath(repo.Owner.Name, repo.Name)); err != nil { repo.RepoPath()); err != nil {
log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
return fmt.Errorf("Repository garbage collection failed: Error: %v", err) return fmt.Errorf("Repository garbage collection failed: Error: %v", err)
} }
@ -2479,20 +2193,16 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
} }
// CopyLFS copies LFS data from one repo to another // CopyLFS copies LFS data from one repo to another
func CopyLFS(newRepo, oldRepo *Repository) error { func CopyLFS(ctx DBContext, newRepo, oldRepo *Repository) error {
return copyLFS(x, newRepo, oldRepo)
}
func copyLFS(e Engine, newRepo, oldRepo *Repository) error {
var lfsObjects []*LFSMetaObject var lfsObjects []*LFSMetaObject
if err := e.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil { if err := ctx.e.Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil {
return err return err
} }
for _, v := range lfsObjects { for _, v := range lfsObjects {
v.ID = 0 v.ID = 0
v.RepositoryID = newRepo.ID v.RepositoryID = newRepo.ID
if _, err := e.Insert(v); err != nil { if _, err := ctx.e.Insert(v); err != nil {
return err return err
} }
} }
@ -2500,80 +2210,6 @@ func copyLFS(e Engine, newRepo, oldRepo *Repository) error {
return nil return nil
} }
// ForkRepository forks a repository
func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
forkedRepo, err := oldRepo.GetUserFork(owner.ID)
if err != nil {
return nil, err
}
if forkedRepo != nil {
return nil, ErrForkAlreadyExist{
Uname: owner.Name,
RepoName: oldRepo.FullName(),
ForkName: forkedRepo.FullName(),
}
}
repo := &Repository{
OwnerID: owner.ID,
Owner: owner,
Name: name,
LowerName: strings.ToLower(name),
Description: desc,
DefaultBranch: oldRepo.DefaultBranch,
IsPrivate: oldRepo.IsPrivate,
IsEmpty: oldRepo.IsEmpty,
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, owner, 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(owner.Name, repo.Name)
if stdout, err := git.NewCommand(
"clone", "--bare", oldRepo.repoPath(sess), repoPath).
SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", oldRepo.FullName(), repo.FullName())).
RunInDirTimeout(10*time.Minute, ""); err != nil {
log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, oldRepo, stdout, err)
return nil, fmt.Errorf("git clone: %v", err)
}
if stdout, err := git.NewCommand("update-server-info").
SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())).
RunInDir(repoPath); err != nil {
log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err)
return nil, fmt.Errorf("git update-server-info: %v", err)
}
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
}
if err = repo.UpdateSize(); err != nil {
log.Error("Failed to update size for repository: %v", err)
}
return repo, CopyLFS(repo, oldRepo)
}
// GetForks returns all the forks of the repository // GetForks returns all the forks of the repository
func (repo *Repository) GetForks() ([]*Repository, error) { func (repo *Repository) GetForks() ([]*Repository, error) {
forks := make([]*Repository, 0, repo.NumForks) forks := make([]*Repository, 0, repo.NumForks)

View file

@ -5,14 +5,8 @@
package models package models
import ( import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -71,186 +65,6 @@ func (gt GiteaTemplate) Globs() []glob.Glob {
return gt.globs return gt.globs
} }
func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) {
gtPath := filepath.Join(tmpDir, ".gitea", "template")
if _, err := os.Stat(gtPath); os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
content, err := ioutil.ReadFile(gtPath)
if err != nil {
return nil, err
}
gt := &GiteaTemplate{
Path: gtPath,
Content: content,
}
return gt, nil
}
func generateRepoCommit(e Engine, repo, templateRepo, generateRepo *Repository, tmpDir string) error {
commitTimeStr := time.Now().Format(time.RFC3339)
authorSig := repo.Owner.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+authorSig.Name,
"GIT_COMMITTER_EMAIL="+authorSig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
// Clone to temporary path and do the init commit.
templateRepoPath := templateRepo.repoPath(e)
if err := git.Clone(templateRepoPath, tmpDir, git.CloneRepoOptions{
Depth: 1,
}); err != nil {
return fmt.Errorf("git clone: %v", err)
}
if err := os.RemoveAll(path.Join(tmpDir, ".git")); err != nil {
return fmt.Errorf("remove git dir: %v", err)
}
// Variable expansion
gt, err := checkGiteaTemplate(tmpDir)
if err != nil {
return fmt.Errorf("checkGiteaTemplate: %v", err)
}
if gt != nil {
if err := os.Remove(gt.Path); err != nil {
return fmt.Errorf("remove .giteatemplate: %v", err)
}
// Avoid walking tree if there are no globs
if len(gt.Globs()) > 0 {
tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/"
if err := filepath.Walk(tmpDirSlash, func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
if info.IsDir() {
return nil
}
base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash)
for _, g := range gt.Globs() {
if g.Match(base) {
content, err := ioutil.ReadFile(path)
if err != nil {
return err
}
if err := ioutil.WriteFile(path,
[]byte(generateExpansion(string(content), templateRepo, generateRepo)),
0644); err != nil {
return err
}
break
}
}
return nil
}); err != nil {
return err
}
}
}
if err := git.InitRepository(tmpDir, false); err != nil {
return err
}
repoPath := repo.repoPath(e)
if stdout, err := git.NewCommand("remote", "add", "origin", repoPath).
SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", templateRepoPath, tmpDir)).
RunInDirWithEnv(tmpDir, env); err != nil {
log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err)
return fmt.Errorf("git remote add: %v", err)
}
return initRepoCommit(tmpDir, repo, repo.Owner)
}
// generateRepository initializes repository from template
func generateRepository(e Engine, repo, templateRepo, generateRepo *Repository) (err error) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name)
if err != nil {
return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.repoPath(e), err)
}
defer func() {
if err := os.RemoveAll(tmpDir); err != nil {
log.Error("RemoveAll: %v", err)
}
}()
if err = generateRepoCommit(e, repo, templateRepo, generateRepo, tmpDir); err != nil {
return fmt.Errorf("generateRepoCommit: %v", err)
}
// re-fetch repo
if repo, err = getRepositoryByID(e, repo.ID); err != nil {
return fmt.Errorf("getRepositoryByID: %v", err)
}
repo.DefaultBranch = "master"
if err = updateRepository(e, repo, false); err != nil {
return fmt.Errorf("updateRepository: %v", err)
}
return nil
}
// GenerateRepository generates a repository from a template
func GenerateRepository(ctx DBContext, doer, owner *User, templateRepo *Repository, opts GenerateRepoOptions) (_ *Repository, err error) {
generateRepo := &Repository{
OwnerID: owner.ID,
Owner: owner,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
IsPrivate: opts.Private,
IsEmpty: !opts.GitContent || templateRepo.IsEmpty,
IsFsckEnabled: templateRepo.IsFsckEnabled,
TemplateID: templateRepo.ID,
}
if err = createRepository(ctx.e, doer, owner, generateRepo); err != nil {
return nil, err
}
repoPath := RepoPath(owner.Name, generateRepo.Name)
if err = checkInitRepository(repoPath); err != nil {
return generateRepo, err
}
return generateRepo, nil
}
// GenerateGitContent generates git content from a template repository
func GenerateGitContent(ctx DBContext, templateRepo, generateRepo *Repository) error {
if err := generateRepository(ctx.e, generateRepo, templateRepo, generateRepo); err != nil {
return err
}
if err := generateRepo.updateSize(ctx.e); err != nil {
return fmt.Errorf("failed to update size for repository: %v", err)
}
if err := copyLFS(ctx.e, generateRepo, templateRepo); err != nil {
return fmt.Errorf("failed to copy LFS: %v", err)
}
return nil
}
// GenerateTopics generates topics from a template repository // GenerateTopics generates topics from a template repository
func GenerateTopics(ctx DBContext, templateRepo, generateRepo *Repository) error { func GenerateTopics(ctx DBContext, templateRepo, generateRepo *Repository) error {
for _, topic := range templateRepo.Topics { for _, topic := range templateRepo.Topics {
@ -263,13 +77,13 @@ func GenerateTopics(ctx DBContext, templateRepo, generateRepo *Repository) error
// GenerateGitHooks generates git hooks from a template repository // GenerateGitHooks generates git hooks from a template repository
func GenerateGitHooks(ctx DBContext, templateRepo, generateRepo *Repository) error { func GenerateGitHooks(ctx DBContext, templateRepo, generateRepo *Repository) error {
generateGitRepo, err := git.OpenRepository(generateRepo.repoPath(ctx.e)) generateGitRepo, err := git.OpenRepository(generateRepo.RepoPath())
if err != nil { if err != nil {
return err return err
} }
defer generateGitRepo.Close() defer generateGitRepo.Close()
templateGitRepo, err := git.OpenRepository(templateRepo.repoPath(ctx.e)) templateGitRepo, err := git.OpenRepository(templateRepo.RepoPath())
if err != nil { if err != nil {
return err return err
} }
@ -352,36 +166,3 @@ func GenerateIssueLabels(ctx DBContext, templateRepo, generateRepo *Repository)
} }
return nil return nil
} }
func generateExpansion(src string, templateRepo, generateRepo *Repository) string {
return os.Expand(src, func(key string) string {
switch key {
case "REPO_NAME":
return generateRepo.Name
case "TEMPLATE_NAME":
return templateRepo.Name
case "REPO_DESCRIPTION":
return generateRepo.Description
case "TEMPLATE_DESCRIPTION":
return templateRepo.Description
case "REPO_OWNER":
return generateRepo.MustOwnerName()
case "TEMPLATE_OWNER":
return templateRepo.MustOwnerName()
case "REPO_LINK":
return generateRepo.Link()
case "TEMPLATE_LINK":
return templateRepo.Link()
case "REPO_HTTPS_URL":
return generateRepo.CloneLink().HTTPS
case "TEMPLATE_HTTPS_URL":
return templateRepo.CloneLink().HTTPS
case "REPO_SSH_URL":
return generateRepo.CloneLink().SSH
case "TEMPLATE_SSH_URL":
return templateRepo.CloneLink().SSH
default:
return key
}
})
}

View file

@ -62,13 +62,13 @@ func (repo *Repository) GetIndexerStatus() error {
// UpdateIndexerStatus updates indexer status // UpdateIndexerStatus updates indexer status
func (repo *Repository) UpdateIndexerStatus(sha string) error { func (repo *Repository) UpdateIndexerStatus(sha string) error {
if err := repo.GetIndexerStatus(); err != nil { if err := repo.GetIndexerStatus(); err != nil {
return fmt.Errorf("UpdateIndexerStatus: Unable to getIndexerStatus for repo: %s/%s Error: %v", repo.MustOwnerName(), repo.Name, err) return fmt.Errorf("UpdateIndexerStatus: Unable to getIndexerStatus for repo: %s Error: %v", repo.FullName(), err)
} }
if len(repo.IndexerStatus.CommitSha) == 0 { if len(repo.IndexerStatus.CommitSha) == 0 {
repo.IndexerStatus.CommitSha = sha repo.IndexerStatus.CommitSha = sha
_, err := x.Insert(repo.IndexerStatus) _, err := x.Insert(repo.IndexerStatus)
if err != nil { if err != nil {
return fmt.Errorf("UpdateIndexerStatus: Unable to insert repoIndexerStatus for repo: %s/%s Sha: %s Error: %v", repo.MustOwnerName(), repo.Name, sha, err) return fmt.Errorf("UpdateIndexerStatus: Unable to insert repoIndexerStatus for repo: %s Sha: %s Error: %v", repo.FullName(), sha, err)
} }
return nil return nil
} }
@ -76,7 +76,7 @@ func (repo *Repository) UpdateIndexerStatus(sha string) error {
_, err := x.ID(repo.IndexerStatus.ID).Cols("commit_sha"). _, err := x.ID(repo.IndexerStatus.ID).Cols("commit_sha").
Update(repo.IndexerStatus) Update(repo.IndexerStatus)
if err != nil { if err != nil {
return fmt.Errorf("UpdateIndexerStatus: Unable to update repoIndexerStatus for repo: %s/%s Sha: %s Error: %v", repo.MustOwnerName(), repo.Name, sha, err) return fmt.Errorf("UpdateIndexerStatus: Unable to update repoIndexerStatus for repo: %s Sha: %s Error: %v", repo.FullName(), sha, err)
} }
return nil return nil
} }

View file

@ -164,10 +164,6 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss
return return
} }
if repo.Owner == nil {
repo.mustOwner(e)
}
var isCollaborator bool var isCollaborator bool
if user != nil { if user != nil {
isCollaborator, err = repo.isCollaborator(e, user.ID) isCollaborator, err = repo.isCollaborator(e, user.ID)
@ -176,6 +172,10 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss
} }
} }
if err = repo.getOwner(e); err != nil {
return
}
// Prevent strangers from checking out public repo of private orginization // Prevent strangers from checking out public repo of private orginization
// Allow user if they are collaborator of a repo within a private orginization but not a member of the orginization itself // Allow user if they are collaborator of a repo within a private orginization but not a member of the orginization itself
if repo.Owner.IsOrganization() && !HasOrgVisible(repo.Owner, user) && !isCollaborator { if repo.Owner.IsOrganization() && !HasOrgVisible(repo.Owner, user) && !isCollaborator {

View file

@ -22,6 +22,7 @@ func TestMetas(t *testing.T) {
repo := &Repository{Name: "testRepo"} repo := &Repository{Name: "testRepo"}
repo.Owner = &User{Name: "testOwner"} repo.Owner = &User{Name: "testOwner"}
repo.OwnerName = repo.Owner.Name
repo.Units = nil repo.Units = nil
@ -132,19 +133,6 @@ func TestGetUserFork(t *testing.T) {
assert.Nil(t, repo) assert.Nil(t, repo)
} }
func TestForkRepository(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
// user 13 has already forked repo10
user := AssertExistsAndLoadBean(t, &User{ID: 13}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository)
fork, err := ForkRepository(user, user, repo, "test", "test")
assert.Nil(t, fork)
assert.Error(t, err)
assert.True(t, IsErrForkAlreadyExist(err))
}
func TestRepoAPIURL(t *testing.T) { func TestRepoAPIURL(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository) repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository)

View file

@ -8,8 +8,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -169,57 +167,16 @@ func FindTasks(opts FindTaskOptions) ([]*Task, error) {
return tasks, err return tasks, err
} }
// CreateTask creates a task on database
func CreateTask(task *Task) error {
return createTask(x, task)
}
func createTask(e Engine, task *Task) error { func createTask(e Engine, task *Task) error {
_, err := e.Insert(task) _, err := e.Insert(task)
return err return err
} }
// CreateMigrateTask creates a migrate task
func CreateMigrateTask(doer, u *User, opts base.MigrateOptions) (*Task, error) {
bs, err := json.Marshal(&opts)
if err != nil {
return nil, err
}
var task = Task{
DoerID: doer.ID,
OwnerID: u.ID,
Type: structs.TaskTypeMigrateRepo,
Status: structs.TaskStatusQueue,
PayloadContent: string(bs),
}
if err := createTask(x, &task); err != nil {
return nil, err
}
repo, err := CreateRepository(doer, u, CreateRepoOptions{
Name: opts.RepoName,
Description: opts.Description,
OriginalURL: opts.OriginalURL,
GitServiceType: opts.GitServiceType,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: RepositoryBeingMigrated,
})
if err != nil {
task.EndTime = timeutil.TimeStampNow()
task.Status = structs.TaskStatusFailed
err2 := task.UpdateCols("end_time", "status")
if err2 != nil {
log.Error("UpdateCols Failed: %v", err2.Error())
}
return nil, err
}
task.RepoID = repo.ID
if err = task.UpdateCols("repo_id"); err != nil {
return nil, err
}
return &task, nil
}
// FinishMigrateTask updates database when migrate task finished // FinishMigrateTask updates database when migrate task finished
func FinishMigrateTask(task *Task) error { func FinishMigrateTask(task *Task) error {
task.Status = structs.TaskStatusFinished task.Status = structs.TaskStatusFinished

View file

@ -504,7 +504,7 @@ func (u *User) ValidatePassword(passwd string) bool {
// IsPasswordSet checks if the password is set or left empty // IsPasswordSet checks if the password is set or left empty
func (u *User) IsPasswordSet() bool { func (u *User) IsPasswordSet() bool {
return len(u.Passwd) > 0 return !u.ValidatePassword("")
} }
// UploadAvatar saves custom avatar for user. // UploadAvatar saves custom avatar for user.
@ -1470,7 +1470,7 @@ type SearchUserOptions struct {
UID int64 UID int64
OrderBy SearchOrderBy OrderBy SearchOrderBy
Page int Page int
Private bool // Include private orgs in search Visible []structs.VisibleType
Actor *User // The user doing the search Actor *User // The user doing the search
PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
IsActive util.OptionalBool IsActive util.OptionalBool
@ -1493,8 +1493,9 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
cond = cond.And(keywordCond) cond = cond.And(keywordCond)
} }
if !opts.Private { if len(opts.Visible) > 0 {
// user not logged in and so they won't be allowed to see non-public orgs cond = cond.And(builder.In("visibility", opts.Visible))
} else {
cond = cond.And(builder.In("visibility", structs.VisibleTypePublic)) cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
} }

View file

@ -1,4 +1,5 @@
// Copyright 2015 The Gogs Authors. All rights reserved. // Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -13,7 +14,7 @@ import (
// WikiCloneLink returns clone URLs of repository wiki. // WikiCloneLink returns clone URLs of repository wiki.
func (repo *Repository) WikiCloneLink() *CloneLink { func (repo *Repository) WikiCloneLink() *CloneLink {
return repo.cloneLink(x, true) return repo.cloneLink(true)
} }
// WikiPath returns wiki data path by given user and repository name. // WikiPath returns wiki data path by given user and repository name.
@ -23,7 +24,7 @@ func WikiPath(userName, repoName string) string {
// WikiPath returns wiki data path for given repository. // WikiPath returns wiki data path for given repository.
func (repo *Repository) WikiPath() string { func (repo *Repository) WikiPath() string {
return WikiPath(repo.MustOwnerName(), repo.Name) return WikiPath(repo.OwnerName, repo.Name)
} }
// HasWiki returns true if repository has wiki. // HasWiki returns true if repository has wiki.

View file

@ -256,7 +256,7 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) {
redirectPath := strings.Replace( redirectPath := strings.Replace(
ctx.Req.URL.Path, ctx.Req.URL.Path,
fmt.Sprintf("%s/%s", ownerName, previousRepoName), fmt.Sprintf("%s/%s", ownerName, previousRepoName),
fmt.Sprintf("%s/%s", repo.MustOwnerName(), repo.Name), repo.FullName(),
1, 1,
) )
if ctx.Req.URL.RawQuery != "" { if ctx.Req.URL.RawQuery != "" {

View file

@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -100,7 +101,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
var r *models.Repository var r *models.Repository
if opts.MigrateToRepoID <= 0 { if opts.MigrateToRepoID <= 0 {
r, err = models.CreateRepository(g.doer, owner, models.CreateRepoOptions{ r, err = repo_module.CreateRepository(g.doer, owner, models.CreateRepoOptions{
Name: g.repoName, Name: g.repoName,
Description: repo.Description, Description: repo.Description,
OriginalURL: repo.OriginalURL, OriginalURL: repo.OriginalURL,

View file

@ -6,6 +6,7 @@ package queue
import ( import (
"context" "context"
"sync"
"time" "time"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -33,6 +34,7 @@ type PersistableChannelQueueConfiguration struct {
type PersistableChannelQueue struct { type PersistableChannelQueue struct {
*ChannelQueue *ChannelQueue
delayedStarter delayedStarter
lock sync.Mutex
closed chan struct{} closed chan struct{}
} }

View file

@ -28,7 +28,6 @@ type WrappedQueueConfiguration struct {
} }
type delayedStarter struct { type delayedStarter struct {
lock sync.Mutex
internal Queue internal Queue
underlying Type underlying Type
cfg interface{} cfg interface{}
@ -62,7 +61,6 @@ func (q *delayedStarter) setInternal(atShutdown func(context.Context, func()), h
queue, err := NewQueue(q.underlying, handle, q.cfg, exemplar) queue, err := NewQueue(q.underlying, handle, q.cfg, exemplar)
if err == nil { if err == nil {
q.internal = queue q.internal = queue
q.lock.Unlock()
break break
} }
if err.Error() != "resource temporarily unavailable" { if err.Error() != "resource temporarily unavailable" {
@ -90,6 +88,7 @@ func (q *delayedStarter) setInternal(atShutdown func(context.Context, func()), h
// WrappedQueue wraps a delayed starting queue // WrappedQueue wraps a delayed starting queue
type WrappedQueue struct { type WrappedQueue struct {
delayedStarter delayedStarter
lock sync.Mutex
handle HandlerFunc handle HandlerFunc
exemplar interface{} exemplar interface{}
channel chan Data channel chan Data

View file

@ -458,7 +458,7 @@ func PushUpdate(repo *models.Repository, branch string, opts PushUpdateOptions)
} }
defer gitRepo.Close() defer gitRepo.Close()
if err = repo.UpdateSize(); err != nil { if err = repo.UpdateSize(models.DefaultDBContext()); err != nil {
log.Error("Failed to update size for repository: %v", err) log.Error("Failed to update size for repository: %v", err)
} }
@ -498,7 +498,7 @@ func PushUpdates(repo *models.Repository, optsList []*PushUpdateOptions) error {
if err != nil { if err != nil {
return fmt.Errorf("OpenRepository: %v", err) return fmt.Errorf("OpenRepository: %v", err)
} }
if err = repo.UpdateSize(); err != nil { if err = repo.UpdateSize(models.DefaultDBContext()); err != nil {
log.Error("Failed to update size for repository: %v", err) log.Error("Failed to update size for repository: %v", err)
} }

View file

@ -0,0 +1,77 @@
// Copyright 2019 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 repository
import (
"fmt"
"os"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// CreateRepository creates a repository for the user/organization.
func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *models.Repository, err error) {
if !doer.IsAdmin && !u.CanCreateRepo() {
return nil, models.ErrReachLimitOfRepo{
Limit: u.MaxRepoCreation,
}
}
repo := &models.Repository{
OwnerID: u.ID,
Owner: u,
OwnerName: u.Name,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
OriginalURL: opts.OriginalURL,
OriginalServiceType: opts.GitServiceType,
IsPrivate: opts.IsPrivate,
IsFsckEnabled: !opts.IsMirror,
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status,
IsEmpty: !opts.AutoInit,
}
err = models.WithTx(func(ctx models.DBContext) error {
if err = models.CreateRepository(ctx, doer, u, repo); err != nil {
return err
}
// No need for init mirror.
if !opts.IsMirror {
repoPath := models.RepoPath(u.Name, repo.Name)
if err = initRepository(ctx, repoPath, u, repo, opts); err != nil {
if err2 := os.RemoveAll(repoPath); err2 != nil {
log.Error("initRepository: %v", err)
return fmt.Errorf(
"delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2)
}
return fmt.Errorf("initRepository: %v", err)
}
// Initialize Issue Labels if selected
if len(opts.IssueLabels) > 0 {
if err = models.InitalizeLabels(ctx, repo.ID, opts.IssueLabels); err != nil {
return fmt.Errorf("initalizeLabels: %v", err)
}
}
if stdout, err := git.NewCommand("update-server-info").
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
RunInDir(repoPath); err != nil {
log.Error("CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
return fmt.Errorf("CreateRepository(git update-server-info): %v", err)
}
}
return nil
})
return repo, err
}

View file

@ -0,0 +1,145 @@
// Copyright 2019 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 repository
import (
"fmt"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestIncludesAllRepositoriesTeams(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
testTeamRepositories := func(teamID int64, repoIds []int64) {
team := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
assert.NoError(t, team.GetRepositories(), "%s: GetRepositories", team.Name)
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
assert.Equal(t, len(repoIds), len(team.Repos), "%s: repo count", team.Name)
for i, rid := range repoIds {
if rid > 0 {
assert.True(t, team.HasRepository(rid), "%s: HasRepository(%d) %d", rid, i)
}
}
}
// Get an admin user.
user, err := models.GetUserByID(1)
assert.NoError(t, err, "GetUserByID")
// Create org.
org := &models.User{
Name: "All repo",
IsActive: true,
Type: models.UserTypeOrganization,
Visibility: structs.VisibleTypePublic,
}
assert.NoError(t, models.CreateOrganization(org, user), "CreateOrganization")
// Check Owner team.
ownerTeam, err := org.GetOwnerTeam()
assert.NoError(t, err, "GetOwnerTeam")
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
// Create repos.
repoIds := make([]int64, 0)
for i := 0; i < 3; i++ {
r, err := CreateRepository(user, org, models.CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
assert.NoError(t, err, "CreateRepository %d", i)
if r != nil {
repoIds = append(repoIds, r.ID)
}
}
// Get fresh copy of Owner team after creating repos.
ownerTeam, err = org.GetOwnerTeam()
assert.NoError(t, err, "GetOwnerTeam")
// Create teams and check repositories.
teams := []*models.Team{
ownerTeam,
{
OrgID: org.ID,
Name: "team one",
Authorize: models.AccessModeRead,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 2",
Authorize: models.AccessModeRead,
IncludesAllRepositories: false,
},
{
OrgID: org.ID,
Name: "team three",
Authorize: models.AccessModeWrite,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 4",
Authorize: models.AccessModeWrite,
IncludesAllRepositories: false,
},
}
teamRepos := [][]int64{
repoIds,
repoIds,
{},
repoIds,
{},
}
for i, team := range teams {
if i > 0 { // first team is Owner.
assert.NoError(t, models.NewTeam(team), "%s: NewTeam", team.Name)
}
testTeamRepositories(team.ID, teamRepos[i])
}
// Update teams and check repositories.
teams[3].IncludesAllRepositories = false
teams[4].IncludesAllRepositories = true
teamRepos[4] = repoIds
for i, team := range teams {
assert.NoError(t, models.UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name)
testTeamRepositories(team.ID, teamRepos[i])
}
// Create repo and check teams repositories.
org.Teams = nil // Reset teams to allow their reloading.
r, err := CreateRepository(user, org, models.CreateRepoOptions{Name: "repo-last"})
assert.NoError(t, err, "CreateRepository last")
if r != nil {
repoIds = append(repoIds, r.ID)
}
teamRepos[0] = repoIds
teamRepos[1] = repoIds
teamRepos[4] = repoIds
for i, team := range teams {
testTeamRepositories(team.ID, teamRepos[i])
}
// Remove repo and check teams repositories.
assert.NoError(t, models.DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository")
teamRepos[0] = repoIds[1:]
teamRepos[1] = repoIds[1:]
teamRepos[3] = repoIds[1:3]
teamRepos[4] = repoIds[1:]
for i, team := range teams {
testTeamRepositories(team.ID, teamRepos[i])
}
// Wipe created items.
for i, rid := range repoIds {
if i > 0 { // first repo already deleted.
assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i)
}
}
assert.NoError(t, models.DeleteOrganization(org), "DeleteOrganization")
}

View file

@ -0,0 +1,87 @@
// Copyright 2019 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 repository
import (
"fmt"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
)
// ForkRepository forks a repository
func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc string) (_ *models.Repository, err error) {
forkedRepo, err := oldRepo.GetUserFork(owner.ID)
if err != nil {
return nil, err
}
if forkedRepo != nil {
return nil, models.ErrForkAlreadyExist{
Uname: owner.Name,
RepoName: oldRepo.FullName(),
ForkName: forkedRepo.FullName(),
}
}
repo := &models.Repository{
OwnerID: owner.ID,
Owner: owner,
OwnerName: owner.Name,
Name: name,
LowerName: strings.ToLower(name),
Description: desc,
DefaultBranch: oldRepo.DefaultBranch,
IsPrivate: oldRepo.IsPrivate,
IsEmpty: oldRepo.IsEmpty,
IsFork: true,
ForkID: oldRepo.ID,
}
oldRepoPath := oldRepo.RepoPath()
err = models.WithTx(func(ctx models.DBContext) error {
if err = models.CreateRepository(ctx, doer, owner, repo); err != nil {
return err
}
if err = models.IncrementRepoForkNum(ctx, oldRepo.ID); err != nil {
return err
}
repoPath := models.RepoPath(owner.Name, repo.Name)
if stdout, err := git.NewCommand(
"clone", "--bare", oldRepoPath, repoPath).
SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", oldRepo.FullName(), repo.FullName())).
RunInDirTimeout(10*time.Minute, ""); err != nil {
log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, oldRepo, stdout, err)
return fmt.Errorf("git clone: %v", err)
}
if stdout, err := git.NewCommand("update-server-info").
SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())).
RunInDir(repoPath); err != nil {
log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err)
return fmt.Errorf("git update-server-info: %v", err)
}
if err = models.CreateDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createDelegateHooks: %v", err)
}
return nil
})
if err != nil {
return nil, err
}
ctx := models.DefaultDBContext()
if err = repo.UpdateSize(ctx); err != nil {
log.Error("Failed to update size for repository: %v", err)
}
return repo, models.CopyLFS(ctx, repo, oldRepo)
}

View file

@ -0,0 +1,25 @@
// 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 repository
import (
"testing"
"code.gitea.io/gitea/models"
"github.com/stretchr/testify/assert"
)
func TestForkRepository(t *testing.T) {
assert.NoError(t, models.PrepareTestDatabase())
// user 13 has already forked repo10
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository)
fork, err := ForkRepository(user, user, repo, "test", "test")
assert.Nil(t, fork)
assert.Error(t, err)
assert.True(t, models.IsErrForkAlreadyExist(err))
}

View file

@ -0,0 +1,230 @@
// Copyright 2019 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 repository
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
)
func generateExpansion(src string, templateRepo, generateRepo *models.Repository) string {
return os.Expand(src, func(key string) string {
switch key {
case "REPO_NAME":
return generateRepo.Name
case "TEMPLATE_NAME":
return templateRepo.Name
case "REPO_DESCRIPTION":
return generateRepo.Description
case "TEMPLATE_DESCRIPTION":
return templateRepo.Description
case "REPO_OWNER":
return generateRepo.OwnerName
case "TEMPLATE_OWNER":
return templateRepo.OwnerName
case "REPO_LINK":
return generateRepo.Link()
case "TEMPLATE_LINK":
return templateRepo.Link()
case "REPO_HTTPS_URL":
return generateRepo.CloneLink().HTTPS
case "TEMPLATE_HTTPS_URL":
return templateRepo.CloneLink().HTTPS
case "REPO_SSH_URL":
return generateRepo.CloneLink().SSH
case "TEMPLATE_SSH_URL":
return templateRepo.CloneLink().SSH
default:
return key
}
})
}
func checkGiteaTemplate(tmpDir string) (*models.GiteaTemplate, error) {
gtPath := filepath.Join(tmpDir, ".gitea", "template")
if _, err := os.Stat(gtPath); os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
content, err := ioutil.ReadFile(gtPath)
if err != nil {
return nil, err
}
gt := &models.GiteaTemplate{
Path: gtPath,
Content: content,
}
return gt, nil
}
func generateRepoCommit(repo, templateRepo, generateRepo *models.Repository, tmpDir string) error {
commitTimeStr := time.Now().Format(time.RFC3339)
authorSig := repo.Owner.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+authorSig.Name,
"GIT_COMMITTER_EMAIL="+authorSig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
// Clone to temporary path and do the init commit.
templateRepoPath := templateRepo.RepoPath()
if err := git.Clone(templateRepoPath, tmpDir, git.CloneRepoOptions{
Depth: 1,
}); err != nil {
return fmt.Errorf("git clone: %v", err)
}
if err := os.RemoveAll(path.Join(tmpDir, ".git")); err != nil {
return fmt.Errorf("remove git dir: %v", err)
}
// Variable expansion
gt, err := checkGiteaTemplate(tmpDir)
if err != nil {
return fmt.Errorf("checkGiteaTemplate: %v", err)
}
if err := os.Remove(gt.Path); err != nil {
return fmt.Errorf("remove .giteatemplate: %v", err)
}
// Avoid walking tree if there are no globs
if len(gt.Globs()) > 0 {
tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/"
if err := filepath.Walk(tmpDirSlash, func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
if info.IsDir() {
return nil
}
base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash)
for _, g := range gt.Globs() {
if g.Match(base) {
content, err := ioutil.ReadFile(path)
if err != nil {
return err
}
if err := ioutil.WriteFile(path,
[]byte(generateExpansion(string(content), templateRepo, generateRepo)),
0644); err != nil {
return err
}
break
}
}
return nil
}); err != nil {
return err
}
}
if err := git.InitRepository(tmpDir, false); err != nil {
return err
}
repoPath := repo.RepoPath()
if stdout, err := git.NewCommand("remote", "add", "origin", repoPath).
SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", templateRepoPath, tmpDir)).
RunInDirWithEnv(tmpDir, env); err != nil {
log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err)
return fmt.Errorf("git remote add: %v", err)
}
return initRepoCommit(tmpDir, repo, repo.Owner)
}
func generateGitContent(ctx models.DBContext, repo, templateRepo, generateRepo *models.Repository) (err error) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name)
if err != nil {
return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err)
}
defer func() {
if err := os.RemoveAll(tmpDir); err != nil {
log.Error("RemoveAll: %v", err)
}
}()
if err = generateRepoCommit(repo, templateRepo, generateRepo, tmpDir); err != nil {
return fmt.Errorf("generateRepoCommit: %v", err)
}
// re-fetch repo
if repo, err = models.GetRepositoryByIDCtx(ctx, repo.ID); err != nil {
return fmt.Errorf("getRepositoryByID: %v", err)
}
repo.DefaultBranch = "master"
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil {
return fmt.Errorf("updateRepository: %v", err)
}
return nil
}
// GenerateGitContent generates git content from a template repository
func GenerateGitContent(ctx models.DBContext, templateRepo, generateRepo *models.Repository) error {
if err := generateGitContent(ctx, generateRepo, templateRepo, generateRepo); err != nil {
return err
}
if err := generateRepo.UpdateSize(ctx); err != nil {
return fmt.Errorf("failed to update size for repository: %v", err)
}
if err := models.CopyLFS(ctx, generateRepo, templateRepo); err != nil {
return fmt.Errorf("failed to copy LFS: %v", err)
}
return nil
}
// GenerateRepository generates a repository from a template
func GenerateRepository(ctx models.DBContext, doer, owner *models.User, templateRepo *models.Repository, opts models.GenerateRepoOptions) (_ *models.Repository, err error) {
generateRepo := &models.Repository{
OwnerID: owner.ID,
Owner: owner,
OwnerName: owner.Name,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
IsPrivate: opts.Private,
IsEmpty: !opts.GitContent || templateRepo.IsEmpty,
IsFsckEnabled: templateRepo.IsFsckEnabled,
TemplateID: templateRepo.ID,
}
if err = models.CreateRepository(ctx, doer, owner, generateRepo); err != nil {
return nil, err
}
repoPath := models.RepoPath(owner.Name, generateRepo.Name)
if err = checkInitRepository(repoPath); err != nil {
return generateRepo, err
}
return generateRepo, nil
}

214
modules/repository/init.go Normal file
View file

@ -0,0 +1,214 @@
// Copyright 2019 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 repository
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"github.com/mcuadros/go-version"
"github.com/unknwon/com"
)
func prepareRepoCommit(ctx models.DBContext, repo *models.Repository, tmpDir, repoPath string, opts models.CreateRepoOptions) error {
commitTimeStr := time.Now().Format(time.RFC3339)
authorSig := repo.Owner.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+authorSig.Name,
"GIT_COMMITTER_EMAIL="+authorSig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
// Clone to temporary path and do the init commit.
if stdout, err := git.NewCommand("clone", repoPath, tmpDir).
SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)).
RunInDirWithEnv("", env); err != nil {
log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err)
return fmt.Errorf("git clone: %v", err)
}
// README
data, err := models.GetRepoInitFile("readme", opts.Readme)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %v", opts.Readme, err)
}
cloneLink := repo.CloneLink()
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 = models.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 = models.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
}
// initRepoCommit temporarily changes with work directory.
func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User) (err error) {
commitTimeStr := time.Now().Format(time.RFC3339)
sig := u.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
if stdout, err := git.NewCommand("add", "--all").
SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
RunInDir(tmpPath); err != nil {
log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)
return fmt.Errorf("git add --all: %v", err)
}
binVersion, err := git.BinVersion()
if err != nil {
return fmt.Errorf("Unable to get git version: %v", err)
}
args := []string{
"commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", "Initial commit",
}
if version.Compare(binVersion, "1.7.9", ">=") {
sign, keyID := models.SignInitialCommit(tmpPath, u)
if sign {
args = append(args, "-S"+keyID)
} else if version.Compare(binVersion, "2.0.0", ">=") {
args = append(args, "--no-gpg-sign")
}
}
if stdout, err := git.NewCommand(args...).
SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
RunInDirWithEnv(tmpPath, env); err != nil {
log.Error("Failed to commit: %v: Stdout: %s\nError: %v", args, stdout, err)
return fmt.Errorf("git commit: %v", err)
}
if stdout, err := git.NewCommand("push", "origin", "master").
SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)).
RunInDirWithEnv(tmpPath, models.InternalPushingEnvironment(u, repo)); err != nil {
log.Error("Failed to push back to master: Stdout: %s\nError: %v", stdout, err)
return fmt.Errorf("git push: %v", err)
}
return nil
}
func checkInitRepository(repoPath string) (err error) {
// Somehow the directory could exist.
if com.IsExist(repoPath) {
return fmt.Errorf("checkInitRepository: path already exists: %s", repoPath)
}
// Init git bare new repository.
if err = git.InitRepository(repoPath, true); err != nil {
return fmt.Errorf("git.InitRepository: %v", err)
} else if err = models.CreateDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createDelegateHooks: %v", err)
}
return nil
}
// InitRepository initializes README and .gitignore if needed.
func initRepository(ctx models.DBContext, repoPath string, u *models.User, repo *models.Repository, opts models.CreateRepoOptions) (err error) {
if err = checkInitRepository(repoPath); err != nil {
return err
}
// Initialize repository according to user's choice.
if opts.AutoInit {
tmpDir, err := ioutil.TempDir(os.TempDir(), "gitea-"+repo.Name)
if err != nil {
return fmt.Errorf("Failed to create temp dir for repository %s: %v", repo.RepoPath(), err)
}
defer os.RemoveAll(tmpDir)
if err = prepareRepoCommit(ctx, repo, tmpDir, repoPath, opts); err != nil {
return fmt.Errorf("prepareRepoCommit: %v", err)
}
// Apply changes and commit.
if err = initRepoCommit(tmpDir, repo, u); 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 = models.GetRepositoryByIDCtx(ctx, repo.ID); err != nil {
return fmt.Errorf("getRepositoryByID: %v", err)
}
if !opts.AutoInit {
repo.IsEmpty = true
}
repo.DefaultBranch = "master"
if err = models.UpdateRepositoryCtx(ctx, repo, false); err != nil {
return fmt.Errorf("updateRepository: %v", err)
}
return nil
}

View file

@ -116,7 +116,7 @@ func MigrateRepositoryGitData(doer, u *models.User, repo *models.Repository, opt
} }
} }
if err = repo.UpdateSize(); err != nil { if err = repo.UpdateSize(models.DefaultDBContext()); err != nil {
log.Error("Failed to update size for repository: %v", err) log.Error("Failed to update size for repository: %v", err)
} }

View file

@ -21,6 +21,8 @@ var (
MaxGitDiffLines int MaxGitDiffLines int
MaxGitDiffLineCharacters int MaxGitDiffLineCharacters int
MaxGitDiffFiles int MaxGitDiffFiles int
VerbosePush bool
VerbosePushDelay time.Duration
GCArgs []string `ini:"GC_ARGS" delim:" "` GCArgs []string `ini:"GC_ARGS" delim:" "`
EnableAutoGitWireProtocol bool EnableAutoGitWireProtocol bool
Timeout struct { Timeout struct {
@ -36,6 +38,8 @@ var (
MaxGitDiffLines: 1000, MaxGitDiffLines: 1000,
MaxGitDiffLineCharacters: 5000, MaxGitDiffLineCharacters: 5000,
MaxGitDiffFiles: 100, MaxGitDiffFiles: 100,
VerbosePush: true,
VerbosePushDelay: 5 * time.Second,
GCArgs: []string{}, GCArgs: []string{},
EnableAutoGitWireProtocol: true, EnableAutoGitWireProtocol: true,
Timeout: struct { Timeout: struct {

View file

@ -5,6 +5,7 @@
package task package task
import ( import (
"encoding/json"
"fmt" "fmt"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -12,7 +13,9 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations/base" "code.gitea.io/gitea/modules/migrations/base"
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
) )
// taskQueue is a global queue of tasks // taskQueue is a global queue of tasks
@ -52,10 +55,56 @@ func handle(data ...queue.Data) {
// MigrateRepository add migration repository to task // MigrateRepository add migration repository to task
func MigrateRepository(doer, u *models.User, opts base.MigrateOptions) error { func MigrateRepository(doer, u *models.User, opts base.MigrateOptions) error {
task, err := models.CreateMigrateTask(doer, u, opts) task, err := CreateMigrateTask(doer, u, opts)
if err != nil { if err != nil {
return err return err
} }
return taskQueue.Push(task) return taskQueue.Push(task)
} }
// CreateMigrateTask creates a migrate task
func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models.Task, error) {
bs, err := json.Marshal(&opts)
if err != nil {
return nil, err
}
var task = models.Task{
DoerID: doer.ID,
OwnerID: u.ID,
Type: structs.TaskTypeMigrateRepo,
Status: structs.TaskStatusQueue,
PayloadContent: string(bs),
}
if err := models.CreateTask(&task); err != nil {
return nil, err
}
repo, err := repo_module.CreateRepository(doer, u, models.CreateRepoOptions{
Name: opts.RepoName,
Description: opts.Description,
OriginalURL: opts.OriginalURL,
GitServiceType: opts.GitServiceType,
IsPrivate: opts.Private,
IsMirror: opts.Mirror,
Status: models.RepositoryBeingMigrated,
})
if err != nil {
task.EndTime = timeutil.TimeStampNow()
task.Status = structs.TaskStatusFailed
err2 := task.UpdateCols("end_time", "status")
if err2 != nil {
log.Error("UpdateCols Failed: %v", err2.Error())
}
return nil, err
}
task.RepoID = repo.ID
if err = task.UpdateCols("repo_id"); err != nil {
return nil, err
}
return &task, nil
}

View file

@ -1036,7 +1036,6 @@ pulls.cannot_auto_merge_helper=Pro vyřešení konfliktů proveďte ruční slou
pulls.no_merge_desc=Tento požadavek na natažení nemůže být sloučen, protože všechny možnosti repozitáře na sloučení jsou zakázány. pulls.no_merge_desc=Tento požadavek na natažení nemůže být sloučen, protože všechny možnosti repozitáře na sloučení jsou zakázány.
pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení požadavku na natažení ručně. pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení požadavku na natažení ručně.
pulls.no_merge_wip=Požadavek na natažení nemůže být sloučen protože je označen jako nedokončený. pulls.no_merge_wip=Požadavek na natažení nemůže být sloučen protože je označen jako nedokončený.
pulls.no_merge_status_check=Tento požadavek na natažení nemůže být sloučen, protože ne všechny požadované kontroly stavu byly úspěšné.
pulls.merge_pull_request=Sloučit požadavek na natažení pulls.merge_pull_request=Sloučit požadavek na natažení
pulls.rebase_merge_pull_request=Rebase a sloučit pulls.rebase_merge_pull_request=Rebase a sloučit
pulls.rebase_merge_commit_pull_request=Rebase a sloučit (--no-ff) pulls.rebase_merge_commit_pull_request=Rebase a sloučit (--no-ff)

View file

@ -1061,7 +1061,6 @@ pulls.cannot_auto_merge_helper=Bitte manuell zusammenführen, um die Konflikte z
pulls.no_merge_desc=Dieser Pull-Request kann nicht gemerged werden, da keine Mergeoptionen aktiviert sind. pulls.no_merge_desc=Dieser Pull-Request kann nicht gemerged werden, da keine Mergeoptionen aktiviert sind.
pulls.no_merge_helper=Aktiviere Mergeoptionen in den Repositoryeinstellungen oder merge den Pull-Request manuell. pulls.no_merge_helper=Aktiviere Mergeoptionen in den Repositoryeinstellungen oder merge den Pull-Request manuell.
pulls.no_merge_wip=Dieser Pull Request kann nicht zusammengeführt werden, da er als Work In Progress gekennzeichnet ist. pulls.no_merge_wip=Dieser Pull Request kann nicht zusammengeführt werden, da er als Work In Progress gekennzeichnet ist.
pulls.no_merge_status_check=Dieser Pull-Request kann nicht zusammengeführt werden, da nicht alle erforderlichen Statusprüfungen erfolgreich waren.
pulls.merge_pull_request=Pull-Request zusammenführen pulls.merge_pull_request=Pull-Request zusammenführen
pulls.rebase_merge_pull_request=Rebase und Mergen pulls.rebase_merge_pull_request=Rebase und Mergen
pulls.rebase_merge_commit_pull_request=Rebasen und Mergen (--no-ff) pulls.rebase_merge_commit_pull_request=Rebasen und Mergen (--no-ff)

View file

@ -1057,7 +1057,6 @@ pulls.cannot_auto_merge_helper=Combinar manualmente para resolver los conflictos
pulls.no_merge_desc=Este pull request no se puede combinar porque todas las opciones de combinación del repositorio están deshabilitadas. pulls.no_merge_desc=Este pull request no se puede combinar porque todas las opciones de combinación del repositorio están deshabilitadas.
pulls.no_merge_helper=Habilite las opciones de combinación en la configuración del repositorio o fusione el pull request manualmente. pulls.no_merge_helper=Habilite las opciones de combinación en la configuración del repositorio o fusione el pull request manualmente.
pulls.no_merge_wip=Este pull request no se puede combinar porque está marcada como un trabajo en progreso. pulls.no_merge_wip=Este pull request no se puede combinar porque está marcada como un trabajo en progreso.
pulls.no_merge_status_check=No se puede fusionar este pull request porque no todas las comprobaciones de estado requeridas resultaron exitosas.
pulls.merge_pull_request=Fusionar Pull Request pulls.merge_pull_request=Fusionar Pull Request
pulls.rebase_merge_pull_request=Hacer Rebase y Fusionar pulls.rebase_merge_pull_request=Hacer Rebase y Fusionar
pulls.rebase_merge_commit_pull_request=Hacer Rebase y Fusionar (--no-ff) pulls.rebase_merge_commit_pull_request=Hacer Rebase y Fusionar (--no-ff)

View file

@ -1062,7 +1062,6 @@ pulls.cannot_auto_merge_helper=به صورتی دستی ادغام کنید تا
pulls.no_merge_desc=این تقاضای واکشی قابل ادغام نیست لذا تمامی گزینه های ادغام مخزن غیر فعال هستند. pulls.no_merge_desc=این تقاضای واکشی قابل ادغام نیست لذا تمامی گزینه های ادغام مخزن غیر فعال هستند.
pulls.no_merge_helper=گزینه های ادغام را در تنظیمات مخزن فعال کنید یا از تقاضای واکشی به صورت دستی ادغام نمایید. pulls.no_merge_helper=گزینه های ادغام را در تنظیمات مخزن فعال کنید یا از تقاضای واکشی به صورت دستی ادغام نمایید.
pulls.no_merge_wip=این تقاضای واکشی قابل ادغام نیست لذا اکنون به این مخزن درحال پردازش علامت گذاری شده است. pulls.no_merge_wip=این تقاضای واکشی قابل ادغام نیست لذا اکنون به این مخزن درحال پردازش علامت گذاری شده است.
pulls.no_merge_status_check=این تقاضای واکشی نمی‌تواند ادغام شود لذا تمامی حالت های ضروری با موفقیت بررسی نشدند.
pulls.merge_pull_request=ادغام تقاضای واکشی pulls.merge_pull_request=ادغام تقاضای واکشی
pulls.rebase_merge_pull_request=بازگردانی و ادغام pulls.rebase_merge_pull_request=بازگردانی و ادغام
pulls.rebase_merge_commit_pull_request=بازگردانی و ادغام (--no-ff) pulls.rebase_merge_commit_pull_request=بازگردانی و ادغام (--no-ff)

View file

@ -1054,7 +1054,6 @@ pulls.cannot_auto_merge_helper=Fusionner manuellement pour résoudre les conflit
pulls.no_merge_desc=Cette demande de fusion ne peut être appliquée directement car toutes les options de fusion du dépôt sont désactivées. pulls.no_merge_desc=Cette demande de fusion ne peut être appliquée directement car toutes les options de fusion du dépôt sont désactivées.
pulls.no_merge_helper=Activez des options de fusion dans les paramètres du dépôt ou fusionnez la demande manuellement. pulls.no_merge_helper=Activez des options de fusion dans les paramètres du dépôt ou fusionnez la demande manuellement.
pulls.no_merge_wip=Cette demande d'ajout ne peut pas être fusionnée car elle est marquée comme en cours de chantier. pulls.no_merge_wip=Cette demande d'ajout ne peut pas être fusionnée car elle est marquée comme en cours de chantier.
pulls.no_merge_status_check=Cette demande de pull ne peut pas être fusionnée car tous les contrôles de statut requis ne sont pas réussis.
pulls.merge_pull_request=Fusionner la demande d'ajout pulls.merge_pull_request=Fusionner la demande d'ajout
pulls.rebase_merge_pull_request=Rebase et fusionner pulls.rebase_merge_pull_request=Rebase et fusionner
pulls.rebase_merge_commit_pull_request=Rebase et Fusion (--no-ff) pulls.rebase_merge_commit_pull_request=Rebase et Fusion (--no-ff)

View file

@ -10,6 +10,7 @@ link_account=Tautan Akun
register=Daftar register=Daftar
website=Situs Web website=Situs Web
version=Versi version=Versi
powered_by=Didukung oleh %s
page=Halaman page=Halaman
template=Contoh template=Contoh
language=Bahasa language=Bahasa
@ -29,8 +30,16 @@ twofa_scratch=Kode Awal Dua Faktor
passcode=Kode Akses passcode=Kode Akses
u2f_insert_key=Masukkan kunci keamanan anda u2f_insert_key=Masukkan kunci keamanan anda
u2f_sign_in=Tekan tombol pada kunci keamanan anda. Jika kunci keamanan anda tidak memiliki tombol, masukkan kembali.
u2f_press_button=Silahkan tekan tombol pada kunci keamanan anda… u2f_press_button=Silahkan tekan tombol pada kunci keamanan anda…
u2f_use_twofa=Menggunakan kode dua faktor dari telepon anda u2f_use_twofa=Menggunakan kode dua faktor dari telepon anda
u2f_error=Tidak dapat membaca kunci keamanan Anda.
u2f_unsupported_browser=Browser Anda tidak mendukung kunci keamanan U2F.
u2f_error_1=Terdapat kesalahan yang tidak diketahui. Mohon coba lagi.
u2f_error_2=Pastikan menggunakan URL yang benar dan terenkripsi (https://).
u2f_error_3=Server tidak bisa memproses permintaan anda.
u2f_error_4=Kunci keamanan tidak diperbolehkan untuk permintaan ini. Pastikan bahwa kunci ini belum terdaftar sebelumnya.
u2f_error_5=Waktu habis sebelum kunci Anda dapat dibaca. Mohon muat ulang halaman ini dan coba lagi.
u2f_reload=Muat Ulang u2f_reload=Muat Ulang
repository=Repositori repository=Repositori
@ -58,25 +67,48 @@ forks=Garpu
activities=Aktivitas activities=Aktivitas
pull_requests=Tarik Permintaan pull_requests=Tarik Permintaan
issues=Masalah issues=Masalah
milestones=Tonggak
cancel=Batal cancel=Batal
add=Tambah
add_all=Tambah Semua
remove=Buang
remove_all=Buang Semua
write=Tulis
preview=Pratinjau
loading=Memuat…
[startpage] [startpage]
app_desc=Sebuah layanan hosting Git sendiri yang tanpa kesulitan
install=Mudah dipasang
install_desc=Cukup <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/install-from-binary/">jalankan program biner</a> yang sesuai dengan sistem operasi Anda. Atau jalankan Gitea dengan <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a> atau <a target="_blank" rel="noopener noreferrer" href="https://github.com/alvaroaleman/ansible-gitea/blob/master/Vagrantfile">Vagrant</a>, atau install dari <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/install-from-package/">paket</a>.
platform=Lintas platform
platform_desc=Gitea bisa digunakan di mana <a target="_blank" rel="noopener noreferrer" href="http://golang.org/">Go</a> bisa dijalankan: Windows, macOS, Linux, ARM, dll. Silahkan pilih yang Anda suka!
lightweight=Ringan
lightweight_desc=Gitea hanya membutuhkan persyaratan minimal dan bisa berjalan pada Raspberry Pi yang murah. Bisa menghemat listrik!
license=Sumber Terbuka
license_desc="Go get" (Dapatkan kode sumber dari) <a target="_blank" rel="noopener noreferrer" href="https://code.gitea.io/gitea">code.gitea.io/gitea</a>! Mari bergabung dengan <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea">berkontribusi</a> untuk membuat proyek ini lebih baik. Jangan malu untuk menjadi kontributor!
[install] [install]
install=Pemasangan install=Pemasangan
title=Konfigurasi Awal title=Konfigurasi Awal
docker_helper=Jika Anda menjalankan Gitea di dalam Docker, baca <a target="_blank" rel="noopener" href="%s">dokumentasi </a> sebelum mengubah pengaturan.
requite_db_desc=Gitea memerlukan MySQL, PostgreSQL, MSSQL atau SQLite3.
db_title=Pengaturan Basis Data db_title=Pengaturan Basis Data
db_type=Tipe Basis Data db_type=Tipe Basis Data
host=Host host=Host
user=Nama Pengguna user=Nama Pengguna
password=Kata Sandi password=Kata Sandi
db_name=Nama Basis Data db_name=Nama Basis Data
db_helper=Untuk pengguna MySQL: Mohon gunakan mesin penyimpanan InnoDB, dan jika Anda menggunakan enkoding "utf8mb4", versi InnoDB Anda harus diatas 5.6.
ssl_mode=SSL ssl_mode=SSL
charset=Jenis karakter
path=Jalur path=Jalur
sqlite_helper=Jalur berkas untuk basis data SQLite3 atau TiDB.<br>Masukkan path absolut jika anda menjalankan Gitea sebagai layanan.
no_admin_and_disable_registration=Anda tidak dapat menonaktifkan pendaftaran tanpa membuat akun admin. no_admin_and_disable_registration=Anda tidak dapat menonaktifkan pendaftaran tanpa membuat akun admin.
err_empty_admin_password=Sandi administrator tidak boleh kosong. err_empty_admin_password=Sandi administrator tidak boleh kosong.
err_empty_admin_email=Email administrator tidak boleh kosong.
general_title=Pengaturan Umum general_title=Pengaturan Umum
app_name=Judul Situs app_name=Judul Situs
@ -111,13 +143,19 @@ server_service_title=Server dan Pengaturan Layanan Pihak Ketiga
offline_mode=Aktifkan Mode Lokal offline_mode=Aktifkan Mode Lokal
offline_mode_popup=Non-aktifkan jaringan pengiriman konten dari pihak ketiga dan layani semua sumber daya secara lokal. offline_mode_popup=Non-aktifkan jaringan pengiriman konten dari pihak ketiga dan layani semua sumber daya secara lokal.
disable_gravatar=Non-aktifkan Gravatar disable_gravatar=Non-aktifkan Gravatar
federated_avatar_lookup=Aktifkan Avatar Terfederasi
federated_avatar_lookup_popup=Mengaktifkan pencarian avatar federasi menggunakan Libravatar. federated_avatar_lookup_popup=Mengaktifkan pencarian avatar federasi menggunakan Libravatar.
disable_registration_popup=Nonaktifkan pendaftaran oleh pengguna. Hanya admin yang dapat membuat akun pengguna baru.
allow_only_external_registration_popup=Perbolehkan Pendaftaran Hanya Melalui Layanan External
openid_signin=Aktifkan Login OpenID openid_signin=Aktifkan Login OpenID
openid_signin_popup=Aktifkan masuk pengguna lewat OpenID.
openid_signup=Aktifkan Pendaftaran OpenID openid_signup=Aktifkan Pendaftaran OpenID
openid_signup_popup=Aktifkan pendaftaran berdasarkan OpenID. openid_signup_popup=Aktifkan pendaftaran berdasarkan OpenID.
enable_captcha=Aktifkan CAPTCHA enable_captcha=Aktifkan CAPTCHA
enable_captcha_popup=Membutukan CAPTCHA untuk pendaftaran. enable_captcha_popup=Membutukan CAPTCHA untuk pendaftaran.
require_sign_in_view=Anda Harus Login untuk Melihat Halaman require_sign_in_view=Anda Harus Login untuk Melihat Halaman
require_sign_in_view_popup=Batasi akses halaman hanya pada pengguna yang masuk. Pengunjung hanya dapat melihat halaman masuk dan pendaftaran.
admin_setting_desc=Akun administrator tidak wajib dibuat. Pengguna yang pertama kali mendaftar akan secara otomatis menjadi administrator.
admin_title=Pengaturan Akun Admin admin_title=Pengaturan Akun Admin
admin_name=Nama Pengguna Admin admin_name=Nama Pengguna Admin
admin_password=Kata sandi admin_password=Kata sandi
@ -128,6 +166,7 @@ test_git_failed=Tidak dapat menguji perintah 'git': %v
sqlite3_not_available=Gitea versi ini tidak mendukung SQLite3, Silahkan untuh versi biner resmi dari %s (bukan versi 'gobuild'). sqlite3_not_available=Gitea versi ini tidak mendukung SQLite3, Silahkan untuh versi biner resmi dari %s (bukan versi 'gobuild').
invalid_db_setting=Pengaturan basis data tidak valid: %v invalid_db_setting=Pengaturan basis data tidak valid: %v
save_config_failed=Gagal menyimpan konfigurasi: %v save_config_failed=Gagal menyimpan konfigurasi: %v
install_success=Selamat datang! Terimakasih telah memilih Gitea. Selamat bersenang-senang dan hati-hati!
[home] [home]
uname_holder=Nama Pengguna atau Alamat Surel uname_holder=Nama Pengguna atau Alamat Surel
@ -161,30 +200,43 @@ social_register_helper_msg=Sudah memiliki akun? Hubungkan sekarang!
remember_me=Ingat Saya remember_me=Ingat Saya
forgot_password_title=Lupa Kata Sandi forgot_password_title=Lupa Kata Sandi
forgot_password=Lupa kata sandi? forgot_password=Lupa kata sandi?
sign_up_now=Butuh akun? Daftar sekarang.
sign_up_successful=Akun berhasil dibuat.
confirmation_mail_sent_prompt=Surel konfirmasi baru telah dikirim ke <b>%s</b>. Silakan periksa kotak masuk anda dalam %s ke depan untuk menyelesaikan proses pendaftaran. confirmation_mail_sent_prompt=Surel konfirmasi baru telah dikirim ke <b>%s</b>. Silakan periksa kotak masuk anda dalam %s ke depan untuk menyelesaikan proses pendaftaran.
active_your_account=Aktifkan Akun Anda active_your_account=Aktifkan Akun Anda
account_activated=Akun telah diaktifkan
prohibit_login_desc=Akun Anda tidak diperbolehkan untuk masuk, silakan hubungi admin situs.
has_unconfirmed_mail=Hai %s, anda memiliki sebuah alamat surel yang belum dikonfirmasi (<b>%s</b>). Jika anda belum menerima surel konfirmasi atau perlu untuk mengirim ulang yang baru, silakan klik pada tombol di bawah. has_unconfirmed_mail=Hai %s, anda memiliki sebuah alamat surel yang belum dikonfirmasi (<b>%s</b>). Jika anda belum menerima surel konfirmasi atau perlu untuk mengirim ulang yang baru, silakan klik pada tombol di bawah.
resend_mail=Klik di sini untuk mengirim ulang surel aktivasi anda resend_mail=Klik di sini untuk mengirim ulang surel aktivasi anda
email_not_associate=Alamat surel tidak terhubung dengan akun apapun. email_not_associate=Alamat surel tidak terhubung dengan akun apapun.
send_reset_mail=Kirim Surel Pemulihan Akun
reset_password=Pemulihan Akun
password_too_short=Panjang kata sandi tidak boleh kurang dari %d karakter.
verify=Verifikasi verify=Verifikasi
scratch_code=Kode coretan scratch_code=Kode coretan
use_scratch_code=Gunakan kode coretan use_scratch_code=Gunakan kode coretan
twofa_scratch_used=Anda telah menggunakan kode coretan anda. Anda telah dialihkan ke halaman pengaturan dua-faktor jadi anda boleh menghapus pendaftaran perangkat anda atau menghasilkan kode coretan yang baru. twofa_scratch_used=Anda telah menggunakan kode coretan anda. Anda telah dialihkan ke halaman pengaturan dua-faktor jadi anda boleh menghapus pendaftaran perangkat anda atau menghasilkan kode coretan yang baru.
twofa_passcode_incorrect=Kata sandi Anda salah. Jika Anda salah tempatkan perangkat Anda, gunakan kode gosok Anda untuk masuk.
twofa_scratch_token_incorrect=Kode coretan anda tidak tepat. twofa_scratch_token_incorrect=Kode coretan anda tidak tepat.
login_openid=OpenID login_openid=OpenID
openid_connect_submit=Sambungkan openid_connect_submit=Sambungkan
openid_connect_title=Sambungkan ke akun yang sudah ada openid_connect_title=Sambungkan ke akun yang sudah ada
openid_register_title=Buat akun baru openid_register_title=Buat akun baru
openid_signin_desc=Masukkan URI OpenID Anda. Misalnya: https://anne.me, bob.openid.org.cn, atau gnusocial.net/carry.
email_domain_blacklisted=Anda tidak dapat mendaftar dengan alamat email.
authorize_application=Izinkan aplikasi
[mail] [mail]
activate_account=Silakan aktifkan akun anda activate_account=Silakan aktifkan akun anda
activate_email=Verifikasi alamat surel anda activate_email=Verifikasi alamat surel anda
reset_password=Pulihkan akun Anda
register_success=Pendaftaran berhasil register_success=Pendaftaran berhasil
register_notify=Selamat Datang di Gitea register_notify=Selamat Datang di Gitea
[modal] [modal]
yes=Ya yes=Ya
no=Tidak no=Tidak
modify=Perbarui
[form] [form]
UserName=Nama Pengguna UserName=Nama Pengguna
@ -214,43 +266,77 @@ email_error=` bukan alamat surel yang valid. `
url_error=` bukan URL yang valid.` url_error=` bukan URL yang valid.`
include_error=` harus mengandung substring '%s'.` include_error=` harus mengandung substring '%s'.`
unknown_error=Kesalahan yang tidak diketahui: unknown_error=Kesalahan yang tidak diketahui:
lang_select_error=Pilih bahasa dari daftar.
email_been_used=Alamat email sudah digunakan.
openid_been_used=Alamat OpenID '%s' sudah digunakan.
username_password_incorrect=Nama pengguna atau sandi salah.
password_complexity=Kata sandi tidak memenuhi persyaratan kerumitan:
password_lowercase_one=Sekurang-kurangnya satu karakter kecil
password_uppercase_one=Sekurang-kurangnya satu karakter besar
password_digit_one=Sekurang-kurangnya satu angka
password_special_one=Sekurang-kurangnya satu karater khusus (tanda baca, kurung, kutip, dll.)
enterred_invalid_repo_name=Nama repositori yang Anda masukkan salah.
enterred_invalid_owner_name=Nama pemilik baru salah.
enterred_invalid_password=Kata sandi yang Anda masukkan salah.
user_not_exist=Pengguna tidak ada. user_not_exist=Pengguna tidak ada.
last_org_owner=Anda tidak dapat menghapus pengguna terakhir dari tim pemilik. Harus ada setidaknya satu pemilik dalam tim yang diberikan.
cannot_add_org_to_team=Sebuah organisasi tidak dapat ditambahkan sebagai anggota tim.
invalid_ssh_key=Tidak dapat memverifikasi kunci SSH Anda: %s
invalid_gpg_key=Tidak dapat memverifikasi kunci GPG Anda: %s
unable_verify_ssh_key=Tidak dapat memverifikasi kunci SSH; periksa kembali bila ada kesalahan.
auth_failed=Otentikasi gagal: %v auth_failed=Otentikasi gagal: %v
still_own_repo=Akun anda memiliki satu atau lebih repositori, pindahkan atau transfer terlebih dahulu.
still_has_org=Akun Anda adalah anggota dari satu atau lebih organisasi, tinggalkan terlebih dahulu.
org_still_own_repo=Organisasi ini masih memiliki satu atau lebih repositori; hapus atau transfer terlebih dahulu.
target_branch_not_exist=Target cabang tidak ada. target_branch_not_exist=Target cabang tidak ada.
[user] [user]
change_avatar=Ganti avatar anda…
join_on=Telah bergabung di join_on=Telah bergabung di
repositories=Repositori repositories=Repositori
activity=Aktivitas Publik activity=Aktivitas Publik
followers=Pengikut followers=Pengikut
starred=Repositori Terbintang
following=Mengikuti following=Mengikuti
follow=Ikuti follow=Ikuti
unfollow=Berhenti Mengikuti unfollow=Berhenti Mengikuti
heatmap.loading=Memuat Peta Panas…
user_bio=Biografi
form.name_reserved=Nama pengguna '%s' dicadangkan. form.name_reserved=Nama pengguna '%s' dicadangkan.
form.name_pattern_not_allowed=Pola '%s' tidak diperbolehkan dalam nama pengguna.
[settings] [settings]
profile=Profil profile=Profil
account=Akun
password=Kata Sandi password=Kata Sandi
security=Keamanan security=Keamanan
avatar=Avatar avatar=Avatar
ssh_gpg_keys=Kunci SSH / GPG ssh_gpg_keys=Kunci SSH / GPG
social=Akun Sosial social=Akun Sosial
applications=Aplikasi
orgs=Kelola organisasi
repos=Repositori repos=Repositori
delete=Hapus Akun delete=Hapus Akun
twofa=Otentikasi Dua-Faktor twofa=Otentikasi Dua-Faktor
account_link=Akun Tertaut
organization=Organisasi
uid=Uid uid=Uid
u2f=Kunci keamanan
public_profile=Profil Publik public_profile=Profil Publik
profile_desc=Alamat email Anda akan digunakan untuk notifikasi dan operasi lainnya.
password_username_disabled=Pengguna non-lokal tidak diizinkan untuk mengubah nama pengguna mereka. Silakan hubungi administrator sistem anda untuk lebih lanjut.
full_name=Nama Lengkap full_name=Nama Lengkap
website=Situs Web website=Situs Web
location=Lokasi location=Lokasi
update_profile=Perbarui Profil update_profile=Perbarui Profil
update_profile_success=Profil anda telah diperbarui. update_profile_success=Profil anda telah diperbarui.
change_username_prompt=Catatan: Perubahan nama pengguna juga mengubah URL akun Anda.
continue=Lanjutkan continue=Lanjutkan
cancel=Batalkan cancel=Batalkan
@ -318,6 +404,7 @@ confirm_delete_account=Konfirmasi Penghapusan
owner=Pemilik owner=Pemilik
repo_name=Nama Repositori repo_name=Nama Repositori
visibility=Jarak pandang visibility=Jarak pandang
clone_helper=Butuh bantuan kloning? Kunjungi <a target="_blank" rel="noopener noreferrer" href="%s">Bantuan</a>.
fork_repo=Cabang Gudang penyimpanan fork_repo=Cabang Gudang penyimpanan
fork_from=Cabang Dari fork_from=Cabang Dari
repo_desc=Deskripsi repo_desc=Deskripsi
@ -508,6 +595,7 @@ pulls.new=Permintaan Tarik Baru
pulls.filter_branch=Penyaringan cabang pulls.filter_branch=Penyaringan cabang
pulls.no_results=Hasil tidak ditemukan. pulls.no_results=Hasil tidak ditemukan.
pulls.create=Buat Permintaan Tarik pulls.create=Buat Permintaan Tarik
pulls.title_desc=ingin menggabungkan komit %[1]d dari <code>%[2]s</code> menuju <code id="branch_target">%[3]s</code>
pulls.merged_title_desc=commit %[1]d telah digabungkan dari <code>%[2]s</code> menjadi <code>%[3]s</code> %[4]s pulls.merged_title_desc=commit %[1]d telah digabungkan dari <code>%[2]s</code> menjadi <code>%[3]s</code> %[4]s
pulls.tab_conversation=Percakapan pulls.tab_conversation=Percakapan
pulls.tab_commits=Melakukan pulls.tab_commits=Melakukan

View file

@ -1061,7 +1061,8 @@ pulls.cannot_auto_merge_helper=コンフリクトを解消するため手動で
pulls.no_merge_desc=リポジトリのマージオプションがすべて無効になっているため、このプルリクエストをマージすることはできせん。 pulls.no_merge_desc=リポジトリのマージオプションがすべて無効になっているため、このプルリクエストをマージすることはできせん。
pulls.no_merge_helper=リポジトリ設定でマージを有効にするか、手動でマージしてください。 pulls.no_merge_helper=リポジトリ設定でマージを有効にするか、手動でマージしてください。
pulls.no_merge_wip=このプルリクエストはWork in Progressとマークされているため、マージすることはできません。 pulls.no_merge_wip=このプルリクエストはWork in Progressとマークされているため、マージすることはできません。
pulls.no_merge_status_check=すべての必要なステータスチェックが成功していないため、このプルリクエストはマージできません。 pulls.no_merge_not_ready=このプルリクエストはマージする準備ができていません。 レビュー状況とステータスチェックを確認してください。
pulls.no_merge_access=このプルリクエストをマージする権限がありません。
pulls.merge_pull_request=プルリクエストをマージ pulls.merge_pull_request=プルリクエストをマージ
pulls.rebase_merge_pull_request=リベースしてマージ pulls.rebase_merge_pull_request=リベースしてマージ
pulls.rebase_merge_commit_pull_request=リベースしてマージ(--no-ff) pulls.rebase_merge_commit_pull_request=リベースしてマージ(--no-ff)
@ -1412,6 +1413,8 @@ settings.protect_approvals_whitelist_enabled=ホワイトリストに登録し
settings.protect_approvals_whitelist_enabled_desc=ホワイトリストに登録したユーザーやチームによるレビューのみを、必要な承認とみなします。 承認のホワイトリストが無い場合は、書き込み権限がある人によるレビューを必要な承認とみなします。 settings.protect_approvals_whitelist_enabled_desc=ホワイトリストに登録したユーザーやチームによるレビューのみを、必要な承認とみなします。 承認のホワイトリストが無い場合は、書き込み権限がある人によるレビューを必要な承認とみなします。
settings.protect_approvals_whitelist_users=ホワイトリストに含めるレビューア: settings.protect_approvals_whitelist_users=ホワイトリストに含めるレビューア:
settings.protect_approvals_whitelist_teams=ホワイトリストに含めるレビューチーム: settings.protect_approvals_whitelist_teams=ホワイトリストに含めるレビューチーム:
settings.dismiss_stale_approvals=古くなった承認を取り消す
settings.dismiss_stale_approvals_desc=プルリクエストの内容を変える新たなコミットがブランチにプッシュされた場合、以前の承認を取り消します。
settings.add_protected_branch=保護を有効にする settings.add_protected_branch=保護を有効にする
settings.delete_protected_branch=保護を無効にする settings.delete_protected_branch=保護を無効にする
settings.update_protect_branch_success=ブランチ '%s' の保護を更新しました。 settings.update_protect_branch_success=ブランチ '%s' の保護を更新しました。
@ -2025,8 +2028,54 @@ monitor.execute_time=実行時間
monitor.process.cancel=処理をキャンセル monitor.process.cancel=処理をキャンセル
monitor.process.cancel_desc=処理をキャンセルするとデータが失われる可能性があります monitor.process.cancel_desc=処理をキャンセルするとデータが失われる可能性があります
monitor.process.cancel_notices=キャンセル: <strong>%s</strong>? monitor.process.cancel_notices=キャンセル: <strong>%s</strong>?
monitor.queues=キュー
monitor.queue=キュー: %s
monitor.queue.name=キュー名
monitor.queue.type=種類
monitor.queue.exemplar=要素の型
monitor.queue.numberworkers=ワーカー数
monitor.queue.maxnumberworkers=ワーカー数上限
monitor.queue.review=設定確認
monitor.queue.review_add=確認/ワーカー追加
monitor.queue.configuration=初期設定
monitor.queue.nopool.title=ワーカープールはありません
monitor.queue.nopool.desc=このキューは他のキューをラップし、これ自体にはワーカープールがありません。
monitor.queue.wrapped.desc=wrappedキューは、すぐに開始されないキューをラップし、入ってきたリクエストをチャンネルにバッファリングします。 これ自体にはワーカープールがありません。
monitor.queue.persistable-channel.desc=persistable-channelキューは二つのキューをラップします。 一つはchannelキューで、自分のワーカープールを持ちます。もう一つはlevelキューで、前回のシャットダウンからリクエストを引き継ぐためのものです。 これ自体にはワーカープールがありません。
monitor.queue.pool.timeout=タイムアウト
monitor.queue.pool.addworkers.title=ワーカーの追加
monitor.queue.pool.addworkers.submit=ワーカーを追加
monitor.queue.pool.addworkers.desc=このプールに、タイムアウト付きまたはタイムアウト無しでワーカーを追加します。 タイムアウトを指定した場合は、タイムアウト後にそれらのワーカーがこのプールから取り除かれます。
monitor.queue.pool.addworkers.numberworkers.placeholder=ワーカー数
monitor.queue.pool.addworkers.timeout.placeholder=0でタイムアウト無し
monitor.queue.pool.addworkers.mustnumbergreaterzero=追加するワーカー数は1以上にしてください
monitor.queue.pool.addworkers.musttimeoutduration=タイムアウトは 、Go言語の時間差表記(例 5m)、または0にしてください
monitor.queue.settings.title=プール設定
monitor.queue.settings.desc=ワーカーへのキューのブロックが発生すると、それに応じてプール数がブースト分ずつ動的に増えます。 これらの変更は現在のワーカーグループには影響しません。
monitor.queue.settings.timeout=ブースト分のタイムアウト
monitor.queue.settings.timeout.placeholder=現在の設定 %[1]v
monitor.queue.settings.timeout.error=タイムアウトは 、Go言語の時間差表記(例 5m)、または0にしてください
monitor.queue.settings.numberworkers=ブースト分のワーカー数
monitor.queue.settings.numberworkers.placeholder=現在の設定 %[1]d
monitor.queue.settings.numberworkers.error=追加するワーカー数はゼロ以上にしてください
monitor.queue.settings.maxnumberworkers=ワーカー数上限
monitor.queue.settings.maxnumberworkers.placeholder=現在の設定 %[1]d
monitor.queue.settings.maxnumberworkers.error=ワーカー数上限は数値にしてください
monitor.queue.settings.submit=設定を更新
monitor.queue.settings.changed=設定を更新しました
monitor.queue.settings.blocktimeout=現在のブロックタイムアウト
monitor.queue.settings.blocktimeout.value=%[1]v
monitor.queue.pool.none=このキューにはプールがありません
monitor.queue.pool.added=ワーカーグループを追加しました
monitor.queue.pool.max_changed=ワーカー数の上限を変更しました
monitor.queue.pool.workers.title=アクティブなワーカーグループ
monitor.queue.pool.workers.none=ワーカーグループはありません。
monitor.queue.pool.cancel=ワーカーグループのシャットダウン
monitor.queue.pool.cancelling=ワーカーグループをシャットダウンしています
monitor.queue.pool.cancel_notices=このワーカー数 %s のグループをシャットダウンしますか?
monitor.queue.pool.cancel_desc=キューをワーカーグループ無しのままにすると、リクエストがブロックし続ける原因となります。
notices.system_notice_list=システム通知 notices.system_notice_list=システム通知
notices.view_detail_header=通知の詳細を表示 notices.view_detail_header=通知の詳細を表示

View file

@ -1061,7 +1061,6 @@ pulls.cannot_auto_merge_helper=Sapludiniet manuāli, lai atrisinātu konfliktus.
pulls.no_merge_desc=Šo izmaiņu pieprasījumu nav iespējams sapludināt, jo nav atļauts neviens sapludināšanas veids. pulls.no_merge_desc=Šo izmaiņu pieprasījumu nav iespējams sapludināt, jo nav atļauts neviens sapludināšanas veids.
pulls.no_merge_helper=Lai sapludinātu šo izmaiņu pieprasījumu, iespējojiet vismaz vienu sapludināšanas veidu repozitorija iestatījumos vai sapludiniet to manuāli. pulls.no_merge_helper=Lai sapludinātu šo izmaiņu pieprasījumu, iespējojiet vismaz vienu sapludināšanas veidu repozitorija iestatījumos vai sapludiniet to manuāli.
pulls.no_merge_wip=Šo izmaiņu pieprasījumu nav iespējams sapludināt, jo tas ir atzīmēts, ka darbs pie tā vēl nav pabeigts. pulls.no_merge_wip=Šo izmaiņu pieprasījumu nav iespējams sapludināt, jo tas ir atzīmēts, ka darbs pie tā vēl nav pabeigts.
pulls.no_merge_status_check=Šo izmaiņu pieprasījumu nevar saplusināt, jo nav veiksmīgi izildītas visas obligātas statusa pārbaudes.
pulls.merge_pull_request=Izmaiņu pieprasījuma sapludināšana pulls.merge_pull_request=Izmaiņu pieprasījuma sapludināšana
pulls.rebase_merge_pull_request=Pārbāzēt un sapludināt pulls.rebase_merge_pull_request=Pārbāzēt un sapludināt
pulls.rebase_merge_commit_pull_request=Pārbāzēt un sapludināt (--no-ff) pulls.rebase_merge_commit_pull_request=Pārbāzēt un sapludināt (--no-ff)

View file

@ -1055,7 +1055,6 @@ pulls.cannot_auto_merge_helper=Scal ręcznie, aby rozwiązać konflikty.
pulls.no_merge_desc=Ten Pull Request nie może zostać scalony, ponieważ wszystkie opcje scalania dla tego repozytorium są wyłączone. pulls.no_merge_desc=Ten Pull Request nie może zostać scalony, ponieważ wszystkie opcje scalania dla tego repozytorium są wyłączone.
pulls.no_merge_helper=Włącz opcje scalania w ustawieniach repozytorium, lub scal ten Pull Request ręcznie. pulls.no_merge_helper=Włącz opcje scalania w ustawieniach repozytorium, lub scal ten Pull Request ręcznie.
pulls.no_merge_wip=Ten pull request nie może być automatycznie scalony, ponieważ jest oznaczony jako praca w toku. pulls.no_merge_wip=Ten pull request nie może być automatycznie scalony, ponieważ jest oznaczony jako praca w toku.
pulls.no_merge_status_check=Ten Pull Request nie może być scalony, bo nie wszystkie kontrole stanów były pomyślne.
pulls.merge_pull_request=Scal Pull Request pulls.merge_pull_request=Scal Pull Request
pulls.rebase_merge_pull_request=Zmień bazę i scal pulls.rebase_merge_pull_request=Zmień bazę i scal
pulls.rebase_merge_commit_pull_request=Zmień bazę i scal (--no-ff) pulls.rebase_merge_commit_pull_request=Zmień bazę i scal (--no-ff)

View file

@ -1061,7 +1061,6 @@ pulls.cannot_auto_merge_helper=Faça o merge manualmente para resolver os confli
pulls.no_merge_desc=O merge deste pull request não pode ser aplicado porque todas as opções de mesclagem do repositório estão desabilitadas. pulls.no_merge_desc=O merge deste pull request não pode ser aplicado porque todas as opções de mesclagem do repositório estão desabilitadas.
pulls.no_merge_helper=Habilite as opções de merge nas configurações do repositório ou faça o merge do pull request manualmente. pulls.no_merge_helper=Habilite as opções de merge nas configurações do repositório ou faça o merge do pull request manualmente.
pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque está marcado como um trabalho em andamento. pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque está marcado como um trabalho em andamento.
pulls.no_merge_status_check=Este pull request não pode ter seu merge aplicado porque nem todas as verificações de status necessárias foram bem sucedidas.
pulls.merge_pull_request=Aplicar merge do pull request pulls.merge_pull_request=Aplicar merge do pull request
pulls.rebase_merge_pull_request=Aplicar Rebase e Merge pulls.rebase_merge_pull_request=Aplicar Rebase e Merge
pulls.rebase_merge_commit_pull_request=Aplicar Rebase e Merge (--no-ff) pulls.rebase_merge_commit_pull_request=Aplicar Rebase e Merge (--no-ff)

View file

@ -1054,7 +1054,6 @@ pulls.cannot_auto_merge_helper=Çakışmaları çözmek için el ile birleştiri
pulls.no_merge_desc=Tüm depo birleştirme seçenekleri devre dışı bırakıldığından, bu değişiklik isteği birleştirilemez. pulls.no_merge_desc=Tüm depo birleştirme seçenekleri devre dışı bırakıldığından, bu değişiklik isteği birleştirilemez.
pulls.no_merge_helper=Depo ayarlarındaki birleştirme seçeneklerini etkinleştirin veya değişiklik isteğini el ile birleştirin. pulls.no_merge_helper=Depo ayarlarındaki birleştirme seçeneklerini etkinleştirin veya değişiklik isteğini el ile birleştirin.
pulls.no_merge_wip=Bu değişiklik isteği birleştirilemez çünkü devam eden bir çalışma olarak işaretlendi. pulls.no_merge_wip=Bu değişiklik isteği birleştirilemez çünkü devam eden bir çalışma olarak işaretlendi.
pulls.no_merge_status_check=Gerekli olan tüm durum denetimleri başarılı olmadığından bu değişiklik isteği birleştirilemez.
pulls.merge_pull_request=Değişiklik İsteğini Birleştir pulls.merge_pull_request=Değişiklik İsteğini Birleştir
pulls.rebase_merge_pull_request=Rebase ve Merge pulls.rebase_merge_pull_request=Rebase ve Merge
pulls.rebase_merge_commit_pull_request=Rebase ve Merge (--no-ff) pulls.rebase_merge_commit_pull_request=Rebase ve Merge (--no-ff)

View file

@ -1058,7 +1058,6 @@ pulls.cannot_auto_merge_helper=Злийте вручну для вирішенн
pulls.no_merge_desc=Цей запити на злиття неможливо злити, оскільки всі параметри об'єднання репозиторія вимкнено. pulls.no_merge_desc=Цей запити на злиття неможливо злити, оскільки всі параметри об'єднання репозиторія вимкнено.
pulls.no_merge_helper=Увімкніть параметри злиття в налаштуваннях репозиторія або злийте запити на злиття вручну. pulls.no_merge_helper=Увімкніть параметри злиття в налаштуваннях репозиторія або злийте запити на злиття вручну.
pulls.no_merge_wip=Цей пулл-реквест не можливо об'єднати, тому-що він вже виконується. pulls.no_merge_wip=Цей пулл-реквест не можливо об'єднати, тому-що він вже виконується.
pulls.no_merge_status_check=Не вдалося об'єднати цей запит на злиття, оскільки не всі обов'язкові перевірки були успішними.
pulls.merge_pull_request=Об'єднати запит на злиття pulls.merge_pull_request=Об'єднати запит на злиття
pulls.rebase_merge_pull_request=Зробити Rebase і злити pulls.rebase_merge_pull_request=Зробити Rebase і злити
pulls.rebase_merge_commit_pull_request=Rebase та злитя (--no-ff) pulls.rebase_merge_commit_pull_request=Rebase та злитя (--no-ff)

View file

@ -1061,7 +1061,6 @@ pulls.cannot_auto_merge_helper=手动合并解决此冲突
pulls.no_merge_desc=由于未启用合并选项,此合并请求无法被合并。 pulls.no_merge_desc=由于未启用合并选项,此合并请求无法被合并。
pulls.no_merge_helper=在仓库设置中启用合并选项或者手工合并请求。 pulls.no_merge_helper=在仓库设置中启用合并选项或者手工合并请求。
pulls.no_merge_wip=这个合并请求无法合并,因为被标记为尚未完成的工作。 pulls.no_merge_wip=这个合并请求无法合并,因为被标记为尚未完成的工作。
pulls.no_merge_status_check=此合并请求不能合并,因为不是所有的状态检查都是成功的。
pulls.merge_pull_request=合并请求 pulls.merge_pull_request=合并请求
pulls.rebase_merge_pull_request=变基并合并 pulls.rebase_merge_pull_request=变基并合并
pulls.rebase_merge_commit_pull_request=变基合并 (--no-ff) pulls.rebase_merge_commit_pull_request=变基合并 (--no-ff)

View file

@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
) )
@ -25,6 +26,6 @@ func Organizations(ctx *context.Context) {
routers.RenderUserSearch(ctx, &models.SearchUserOptions{ routers.RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
PageSize: setting.UI.Admin.OrgPagingNum, PageSize: setting.UI.Admin.OrgPagingNum,
Private: true, Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
}, tplOrgs) }, tplOrgs)
} }

View file

@ -43,7 +43,7 @@ func DeleteRepo(ctx *context.Context) {
ctx.ServerError("DeleteRepository", err) ctx.ServerError("DeleteRepository", err)
return return
} }
log.Trace("Repository deleted: %s/%s", repo.MustOwner().Name, repo.Name) log.Trace("Repository deleted: %s", repo.FullName())
ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
ctx.JSON(200, map[string]interface{}{ ctx.JSON(200, map[string]interface{}{

View file

@ -104,7 +104,7 @@ func GetAllOrgs(ctx *context.APIContext) {
OrderBy: models.SearchOrderByAlphabetically, OrderBy: models.SearchOrderByAlphabetically,
Page: ctx.QueryInt("page"), Page: ctx.QueryInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")), PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
Private: true, Visible: []api.VisibleType{api.VisibleTypePublic, api.VisibleTypeLimited, api.VisibleTypePrivate},
}) })
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err) ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err)

View file

@ -821,6 +821,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/user/orgs", reqToken(), org.ListMyOrgs) m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
m.Get("/users/:username/orgs", org.ListUserOrgs) m.Get("/users/:username/orgs", org.ListUserOrgs)
m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create) m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Get("/orgs", org.GetAll)
m.Group("/orgs/:orgname", func() { m.Group("/orgs/:orgname", func() {
m.Combo("").Get(org.Get). m.Combo("").Get(org.Get).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit). Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).

View file

@ -66,6 +66,53 @@ func ListUserOrgs(ctx *context.APIContext) {
listUserOrgs(ctx, u, ctx.User.IsAdmin) listUserOrgs(ctx, u, ctx.User.IsAdmin)
} }
// GetAll return list of all public organizations
func GetAll(ctx *context.APIContext) {
// swagger:operation Get /orgs organization orgGetAll
// ---
// summary: Get list of organizations
// produces:
// - application/json
// parameters:
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results, maximum page size is 50
// type: integer
// responses:
// "200":
// "$ref": "#/responses/OrganizationList"
vMode := []api.VisibleType{api.VisibleTypePublic}
if ctx.IsSigned {
vMode = append(vMode, api.VisibleTypeLimited)
if ctx.User.IsAdmin {
vMode = append(vMode, api.VisibleTypePrivate)
}
}
publicOrgs, _, err := models.SearchUsers(&models.SearchUserOptions{
Type: models.UserTypeOrganization,
OrderBy: models.SearchOrderByAlphabetically,
Page: ctx.QueryInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
Visible: vMode,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err)
return
}
orgs := make([]*api.Organization, len(publicOrgs))
for i := range publicOrgs {
orgs[i] = convert.ToOrganization(publicOrgs[i])
}
ctx.JSON(http.StatusOK, &orgs)
}
// Create api for create organization // Create api for create organization
func Create(ctx *context.APIContext, form api.CreateOrgOption) { func Create(ctx *context.APIContext, form api.CreateOrgOption) {
// swagger:operation POST /orgs organization orgCreate // swagger:operation POST /orgs organization orgCreate

View file

@ -67,19 +67,23 @@ func SearchIssues(ctx *context.APIContext) {
// find repos user can access (for issue search) // find repos user can access (for issue search)
repoIDs := make([]int64, 0) repoIDs := make([]int64, 0)
opts := &models.SearchRepoOptions{
PageSize: 15,
Private: false,
AllPublic: true,
TopicOnly: false,
Collaborate: util.OptionalBoolNone,
OrderBy: models.SearchOrderByRecentUpdated,
Actor: ctx.User,
}
if ctx.IsSigned {
opts.Private = true
opts.AllLimited = true
}
issueCount := 0 issueCount := 0
for page := 1; ; page++ { for page := 1; ; page++ {
repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{ opts.Page = page
Page: page, repos, count, err := models.SearchRepositoryByName(opts)
PageSize: 15,
Private: true,
Keyword: "",
OwnerID: ctx.User.ID,
TopicOnly: false,
Collaborate: util.OptionalBoolNone,
Actor: ctx.User,
OrderBy: models.SearchOrderByRecentUpdated,
})
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err) ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err)
return return

View file

@ -22,8 +22,8 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations" "code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/validation"
@ -450,10 +450,10 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
return return
} }
var gitServiceType = structs.PlainGitService var gitServiceType = api.PlainGitService
u, err := url.Parse(remoteAddr) u, err := url.Parse(remoteAddr)
if err == nil && strings.EqualFold(u.Host, "github.com") { if err == nil && strings.EqualFold(u.Host, "github.com") {
gitServiceType = structs.GithubService gitServiceType = api.GithubService
} }
var opts = migrations.MigrateOptions{ var opts = migrations.MigrateOptions{
@ -482,7 +482,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
opts.Releases = false opts.Releases = false
} }
repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ repo, err := repo_module.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
Name: opts.RepoName, Name: opts.RepoName,
Description: opts.Description, Description: opts.Description,
OriginalURL: form.CloneAddr, OriginalURL: form.CloneAddr,

View file

@ -15,6 +15,7 @@ import (
code_indexer "code.gitea.io/gitea/modules/indexer/code" code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/user" "code.gitea.io/gitea/routers/user"
) )
@ -252,7 +253,7 @@ func ExploreUsers(ctx *context.Context) {
Type: models.UserTypeIndividual, Type: models.UserTypeIndividual,
PageSize: setting.UI.ExplorePagingNum, PageSize: setting.UI.ExplorePagingNum,
IsActive: util.OptionalBoolTrue, IsActive: util.OptionalBoolTrue,
Private: true, Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
}, tplExploreUsers) }, tplExploreUsers)
} }
@ -263,10 +264,15 @@ func ExploreOrganizations(ctx *context.Context) {
ctx.Data["PageIsExploreOrganizations"] = true ctx.Data["PageIsExploreOrganizations"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
visibleTypes := []structs.VisibleType{structs.VisibleTypePublic}
if ctx.User != nil {
visibleTypes = append(visibleTypes, structs.VisibleTypeLimited, structs.VisibleTypePrivate)
}
RenderUserSearch(ctx, &models.SearchUserOptions{ RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
PageSize: setting.UI.ExplorePagingNum, PageSize: setting.UI.ExplorePagingNum,
Private: ctx.User != nil, Visible: visibleTypes,
}, tplExploreOrganizations) }, tplExploreOrganizations)
} }

View file

@ -361,10 +361,8 @@ func parseBaseRepoInfo(ctx *context.Context, repo *models.Repository) error {
if err := repo.GetBaseRepo(); err != nil { if err := repo.GetBaseRepo(); err != nil {
return err return err
} }
if err := repo.BaseRepo.GetOwnerName(); err != nil {
return err baseGitRepo, err := git.OpenRepository(repo.BaseRepo.RepoPath())
}
baseGitRepo, err := git.OpenRepository(models.RepoPath(repo.BaseRepo.OwnerName, repo.BaseRepo.Name))
if err != nil { if err != nil {
return err return err
} }

View file

@ -35,7 +35,7 @@ func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) {
return return
} }
if err := models.InitalizeLabels(ctx.Repo.Repository.ID, form.TemplateName); err != nil { if err := models.InitalizeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil {
if models.IsErrIssueLabelTemplateLoad(err) { if models.IsErrIssueLabelTemplateLoad(err) {
originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError
ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))

View file

@ -10,7 +10,6 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"mime" "mime"
"path"
"regexp" "regexp"
"strings" "strings"
texttmpl "text/template" texttmpl "text/template"
@ -142,7 +141,7 @@ func SendRegisterNotifyMail(locale Locale, u *models.User) {
// SendCollaboratorMail sends mail notification to new collaborator. // SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) { func SendCollaboratorMail(u, doer *models.User, repo *models.Repository) {
repoName := path.Join(repo.Owner.Name, repo.Name) repoName := repo.FullName()
subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName) subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repoName)
data := map[string]interface{}{ data := map[string]interface{}{

View file

@ -217,7 +217,7 @@ func runSync(m *models.Mirror) ([]*mirrorSyncResult, bool) {
} }
gitRepo.Close() gitRepo.Close()
if err := m.Repo.UpdateSize(); err != nil { if err := m.Repo.UpdateSize(models.DefaultDBContext()); err != nil {
log.Error("Failed to update size for mirror repository: %v", err) log.Error("Failed to update size for mirror repository: %v", err)
} }

View file

@ -38,7 +38,7 @@ func TestRelease_MirrorDelete(t *testing.T) {
Releases: false, Releases: false,
} }
mirrorRepo, err := models.CreateRepository(user, user, models.CreateRepoOptions{ mirrorRepo, err := repository.CreateRepository(user, user, models.CreateRepoOptions{
Name: opts.RepoName, Name: opts.RepoName,
Description: opts.Description, Description: opts.Description,
IsPrivate: opts.Private, IsPrivate: opts.Private,

View file

@ -55,8 +55,8 @@ func DownloadDiffOrPatch(pr *models.PullRequest, w io.Writer, patch bool) error
} }
pr.MergeBase = strings.TrimSpace(pr.MergeBase) pr.MergeBase = strings.TrimSpace(pr.MergeBase)
if err := gitRepo.GetDiffOrPatch(pr.MergeBase, "tracking", w, patch); err != nil { if err := gitRepo.GetDiffOrPatch(pr.MergeBase, "tracking", w, patch); err != nil {
log.Error("Unable to get patch file from %s to %s in %s/%s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name, err) log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
return fmt.Errorf("Unable to get patch file from %s to %s in %s/%s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name, err) return fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
} }
return nil return nil
} }
@ -108,8 +108,8 @@ func TestPatch(pr *models.PullRequest) error {
if err := gitRepo.GetDiff(pr.MergeBase, "tracking", tmpPatchFile); err != nil { if err := gitRepo.GetDiff(pr.MergeBase, "tracking", tmpPatchFile); err != nil {
tmpPatchFile.Close() tmpPatchFile.Close()
log.Error("Unable to get patch file from %s to %s in %s/%s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name, err) log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
return fmt.Errorf("Unable to get patch file from %s to %s in %s/%s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name, err) return fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
} }
stat, err := tmpPatchFile.Stat() stat, err := tmpPatchFile.Stat()
if err != nil { if err != nil {

View file

@ -256,7 +256,7 @@ func checkIfPRContentChanged(pr *models.PullRequest, oldCommitID, newCommitID st
// Add a temporary remote. // Add a temporary remote.
tmpRemote := "checkIfPRContentChanged-" + com.ToStr(time.Now().UnixNano()) tmpRemote := "checkIfPRContentChanged-" + com.ToStr(time.Now().UnixNano())
if err = headGitRepo.AddRemote(tmpRemote, models.RepoPath(pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name), true); err != nil { if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath(), true); err != nil {
return false, fmt.Errorf("AddRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err) return false, fmt.Errorf("AddRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err)
} }
defer func() { defer func() {

View file

@ -8,20 +8,21 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
) )
// GenerateRepository generates a repository from a template // GenerateRepository generates a repository from a template
func GenerateRepository(doer, owner *models.User, templateRepo *models.Repository, opts models.GenerateRepoOptions) (_ *models.Repository, err error) { func GenerateRepository(doer, owner *models.User, templateRepo *models.Repository, opts models.GenerateRepoOptions) (_ *models.Repository, err error) {
var generateRepo *models.Repository var generateRepo *models.Repository
if err = models.WithTx(func(ctx models.DBContext) error { if err = models.WithTx(func(ctx models.DBContext) error {
generateRepo, err = models.GenerateRepository(ctx, doer, owner, templateRepo, opts) generateRepo, err = repo_module.GenerateRepository(ctx, doer, owner, templateRepo, opts)
if err != nil { if err != nil {
return err return err
} }
// Git Content // Git Content
if opts.GitContent && !templateRepo.IsEmpty { if opts.GitContent && !templateRepo.IsEmpty {
if err = models.GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { if err = repo_module.GenerateGitContent(ctx, templateRepo, generateRepo); err != nil {
return err return err
} }
} }

View file

@ -10,11 +10,12 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
) )
// CreateRepository creates a repository for the user/organization. // CreateRepository creates a repository for the user/organization.
func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) (*models.Repository, error) { func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) (*models.Repository, error) {
repo, err := models.CreateRepository(doer, owner, opts) repo, err := repo_module.CreateRepository(doer, owner, opts)
if err != nil { if err != nil {
if repo != nil { if repo != nil {
if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil { if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil {
@ -31,7 +32,7 @@ func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) (
// ForkRepository forks a repository // ForkRepository forks a repository
func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc string) (*models.Repository, error) { func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc string) (*models.Repository, error) {
repo, err := models.ForkRepository(doer, u, oldRepo, name, desc) repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc)
if err != nil { if err != nil {
if repo != nil { if repo != nil {
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {

View file

@ -606,6 +606,35 @@
} }
}, },
"/orgs": { "/orgs": {
"get": {
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Get list of organizations",
"operationId": "orgGetAll",
"parameters": [
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results, maximum page size is 50",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/OrganizationList"
}
}
},
"post": { "post": {
"consumes": [ "consumes": [
"application/json" "application/json"

View file

@ -22,6 +22,9 @@ if (typeof (Dropzone) !== 'undefined') {
Dropzone.autoDiscover = false; Dropzone.autoDiscover = false;
} }
// Silence fomantic's error logging when tabs are used without a target content element
$.fn.tab.settings.silent = true;
function initCommentPreviewTab($form) { function initCommentPreviewTab($form) {
const $tabMenu = $form.find('.tabular.menu'); const $tabMenu = $form.find('.tabular.menu');
$tabMenu.find('.item').tab(); $tabMenu.find('.item').tab();