Merge branch 'master' of https://github.com/go-gitea/gitea
Signed-off-by: Konrad <konrad@kola-entertainments.de> # Conflicts: # models/migrations/migrations.go # models/migrations/v42.go
This commit is contained in:
commit
8b4a48c4bc
17
.drone.yml
17
.drone.yml
|
|
@ -16,10 +16,13 @@ pipeline:
|
|||
TAGS: bindata sqlite
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- apk -U add nodejs nodejs-npm
|
||||
- npm install
|
||||
- make clean
|
||||
- make generate
|
||||
- make vet
|
||||
- make lint
|
||||
- make fmt-check
|
||||
- make stylesheets-check
|
||||
- make misspell-check
|
||||
- make test-vendor
|
||||
|
|
@ -142,7 +145,6 @@ pipeline:
|
|||
tags: [ '${DRONE_TAG##v}' ]
|
||||
when:
|
||||
event: [ tag ]
|
||||
branch: [ refs/tags/* ]
|
||||
|
||||
docker:
|
||||
image: plugins/docker:17.05
|
||||
|
|
@ -176,7 +178,6 @@ pipeline:
|
|||
target: /gitea/${DRONE_TAG##v}
|
||||
when:
|
||||
event: [ tag ]
|
||||
branch: [ refs/tags/* ]
|
||||
|
||||
release:
|
||||
image: plugins/s3:1
|
||||
|
|
@ -206,6 +207,17 @@ pipeline:
|
|||
event: [ push ]
|
||||
branch: [ master ]
|
||||
|
||||
translations:
|
||||
image: jonasfranz/crowdin
|
||||
pull: true
|
||||
secrets: [ crowdin_key ]
|
||||
project_identifier: gitea
|
||||
files:
|
||||
locale_en-US.ini: options/locale/locale_en-US.ini
|
||||
when:
|
||||
event: [ push ]
|
||||
branch: [ master ]
|
||||
|
||||
github:
|
||||
image: plugins/github-release:1
|
||||
pull: true
|
||||
|
|
@ -213,7 +225,6 @@ pipeline:
|
|||
- dist/release/*
|
||||
when:
|
||||
event: [ tag ]
|
||||
branch: [ refs/tags/* ]
|
||||
|
||||
discord:
|
||||
image: appleboy/drone-discord:1.0.0
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -58,3 +58,4 @@ coverage.all
|
|||
/integrations/indexers-sqlite
|
||||
/integrations/mysql.ini
|
||||
/integrations/pgsql.ini
|
||||
/node_modules
|
||||
|
|
|
|||
|
|
@ -97,6 +97,16 @@ and is synced regularily to Crowdin. Once a translation has reached
|
|||
A SATISFACTORY PERCENTAGE it will be synced back into this repo and
|
||||
included in the next released version.
|
||||
|
||||
## Building Gitea
|
||||
|
||||
Generally, the go build tools are installed as-needed in the `Makefile`.
|
||||
An exception are the tools to build the CSS and images.
|
||||
|
||||
- To build CSS: Install [Node.js](https://nodejs.org/en/download/package-manager)
|
||||
with `npm` and then run `npm install` and `make stylesheets`.
|
||||
- To build Images: ImageMagick, inkscape and zopflipng binaries must be
|
||||
available in your `PATH` to run `make generate-images`.
|
||||
|
||||
## Code review
|
||||
|
||||
Changes to Gitea must be reviewed before they are accepted, no matter who
|
||||
|
|
|
|||
|
|
@ -16,3 +16,4 @@ Patrick G <geek1011@outlook.com> (@geek1011)
|
|||
Antoine Girard <sapk@sapk.fr> (@sapk)
|
||||
Lauris Bukšis-Haberkorns <lauris@nix.lv> (@lafriks)
|
||||
Jonas Östanbäck <jonas.ostanback@gmail.com> (@cez81)
|
||||
David Schneiderbauer <dschneiderbauer@gmail.com> (@daviian)
|
||||
|
|
|
|||
22
Makefile
22
Makefile
|
|
@ -15,7 +15,6 @@ else
|
|||
endif
|
||||
|
||||
BINDATA := modules/{options,public,templates}/bindata.go
|
||||
STYLESHEETS := $(wildcard public/less/index.less public/less/_*.less)
|
||||
DOCKER_TAG := gitea/gitea:latest
|
||||
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
|
||||
GOFMT ?= gofmt -s
|
||||
|
|
@ -69,11 +68,8 @@ clean:
|
|||
integrations/indexers-mysql/ integrations/indexers-pgsql integrations/indexers-sqlite \
|
||||
integrations/mysql.ini integrations/pgsql.ini
|
||||
|
||||
required-gofmt-version:
|
||||
@$(GO) version | grep -q '\(1.7\|1.8\)' || { echo "We require go version 1.7 or 1.8 to format code" >&2 && exit 1; }
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: required-gofmt-version
|
||||
fmt:
|
||||
$(GOFMT) -w $(GOFILES)
|
||||
|
||||
.PHONY: vet
|
||||
|
|
@ -125,7 +121,7 @@ misspell:
|
|||
misspell -w -i unknwon $(GOFILES)
|
||||
|
||||
.PHONY: fmt-check
|
||||
fmt-check: required-gofmt-version
|
||||
fmt-check:
|
||||
# get all go files and run go fmt on them
|
||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
|
|
@ -135,7 +131,7 @@ fmt-check: required-gofmt-version
|
|||
fi;
|
||||
|
||||
.PHONY: test
|
||||
test: fmt-check
|
||||
test:
|
||||
$(GO) test $(PACKAGES)
|
||||
|
||||
.PHONY: coverage
|
||||
|
|
@ -302,14 +298,12 @@ stylesheets-check: stylesheets
|
|||
fi;
|
||||
|
||||
.PHONY: stylesheets
|
||||
stylesheets: public/css/index.css
|
||||
|
||||
.IGNORE: public/css/index.css
|
||||
public/css/index.css: $(STYLESHEETS)
|
||||
@which lessc > /dev/null; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/kib357/less-go/lessc; \
|
||||
stylesheets:
|
||||
@hash minify > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/tdewolff/minify/cmd/minify; \
|
||||
fi
|
||||
lessc -i $< -o $@
|
||||
node_modules/.bin/lessc --no-ie-compat public/less/index.less public/css/index.css
|
||||
minify -o public/css/index.css public/css/index.css
|
||||
|
||||
.PHONY: swagger-ui
|
||||
swagger-ui:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ package integrations
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
|
|
@ -33,11 +32,6 @@ func TestAPIUserReposNotLogin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type searchResponseBody struct {
|
||||
ok bool
|
||||
data []api.Repository
|
||||
}
|
||||
|
||||
func TestAPISearchRepoNotLogin(t *testing.T) {
|
||||
prepareTestEnv(t)
|
||||
const keyword = "test"
|
||||
|
|
@ -45,10 +39,12 @@ func TestAPISearchRepoNotLogin(t *testing.T) {
|
|||
req := NewRequestf(t, "GET", "/api/v1/repos/search?q=%s", keyword)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var body searchResponseBody
|
||||
var body api.SearchResults
|
||||
DecodeJSON(t, resp, &body)
|
||||
for _, repo := range body.data {
|
||||
assert.True(t, strings.Contains(repo.Name, keyword))
|
||||
assert.NotEmpty(t, body.Data)
|
||||
for _, repo := range body.Data {
|
||||
assert.Contains(t, repo.Name, keyword)
|
||||
assert.False(t, repo.Private)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
65f1bf27bc3bf70f64657658635e66094edbcb4d
|
||||
|
|
@ -74,6 +74,10 @@ func initIntegrationTest() {
|
|||
os.Exit(1)
|
||||
}
|
||||
setting.AppPath = path.Join(giteaRoot, "gitea")
|
||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
giteaConf := os.Getenv("GITEA_CONF")
|
||||
if giteaConf == "" {
|
||||
|
|
@ -276,7 +280,7 @@ func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *TestRespo
|
|||
mac.ServeHTTP(respWriter, req)
|
||||
if expectedStatus != NoExpectedStatus {
|
||||
assert.EqualValues(t, expectedStatus, respWriter.HeaderCode,
|
||||
"Request URL: %s %s", req.URL.String(), buffer.String())
|
||||
"Request URL: %s", req.URL.String())
|
||||
}
|
||||
return &TestResponse{
|
||||
HeaderCode: respWriter.HeaderCode,
|
||||
|
|
|
|||
4
main.go
4
main.go
|
|
@ -13,6 +13,10 @@ import (
|
|||
"code.gitea.io/gitea/cmd"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
// register supported doc types
|
||||
_ "code.gitea.io/gitea/modules/markup/markdown"
|
||||
_ "code.gitea.io/gitea/modules/markup/orgmode"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
|
|
|||
100
models/action.go
100
models/action.go
|
|
@ -46,6 +46,8 @@ const (
|
|||
ActionReopenIssue // 13
|
||||
ActionClosePullRequest // 14
|
||||
ActionReopenPullRequest // 15
|
||||
ActionDeleteTag // 16
|
||||
ActionDeleteBranch // 17
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -98,9 +100,8 @@ func (a *Action) AfterSet(colName string, _ xorm.Cell) {
|
|||
}
|
||||
|
||||
// GetOpType gets the ActionType of this action.
|
||||
// TODO: change return type to ActionType ?
|
||||
func (a *Action) GetOpType() int {
|
||||
return int(a.OpType)
|
||||
func (a *Action) GetOpType() ActionType {
|
||||
return a.OpType
|
||||
}
|
||||
|
||||
func (a *Action) loadActUser() {
|
||||
|
|
@ -558,6 +559,12 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
|||
// Check it's tag push or branch.
|
||||
if strings.HasPrefix(opts.RefFullName, git.TagPrefix) {
|
||||
opType = ActionPushTag
|
||||
if opts.NewCommitID == git.EmptySHA {
|
||||
opType = ActionDeleteTag
|
||||
}
|
||||
opts.Commits = &PushCommits{}
|
||||
} else if opts.NewCommitID == git.EmptySHA {
|
||||
opType = ActionDeleteBranch
|
||||
opts.Commits = &PushCommits{}
|
||||
} else {
|
||||
// if not the first commit, set the compare URL.
|
||||
|
|
@ -603,8 +610,60 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
|||
apiRepo := repo.APIFormat(AccessModeNone)
|
||||
|
||||
var shaSum string
|
||||
var isHookEventPush = false
|
||||
switch opType {
|
||||
case ActionCommitRepo: // Push
|
||||
isHookEventPush = true
|
||||
|
||||
if isNewBranch {
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
if err != nil {
|
||||
log.Error(4, "OpenRepository[%s]: %v", repo.RepoPath(), err)
|
||||
}
|
||||
|
||||
shaSum, err = gitRepo.GetBranchCommitID(refName)
|
||||
if err != nil {
|
||||
log.Error(4, "GetBranchCommitID[%s]: %v", opts.RefFullName, err)
|
||||
}
|
||||
if err = PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{
|
||||
Ref: refName,
|
||||
Sha: shaSum,
|
||||
RefType: "branch",
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
case ActionDeleteBranch: // Delete Branch
|
||||
isHookEventPush = true
|
||||
|
||||
case ActionPushTag: // Create
|
||||
isHookEventPush = true
|
||||
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
if err != nil {
|
||||
log.Error(4, "OpenRepository[%s]: %v", repo.RepoPath(), err)
|
||||
}
|
||||
shaSum, err = gitRepo.GetTagCommitID(refName)
|
||||
if err != nil {
|
||||
log.Error(4, "GetTagCommitID[%s]: %v", opts.RefFullName, err)
|
||||
}
|
||||
if err = PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{
|
||||
Ref: refName,
|
||||
Sha: shaSum,
|
||||
RefType: "tag",
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks: %v", err)
|
||||
}
|
||||
case ActionDeleteTag: // Delete Tag
|
||||
isHookEventPush = true
|
||||
}
|
||||
|
||||
if isHookEventPush {
|
||||
if err = PrepareWebhooks(repo, HookEventPush, &api.PushPayload{
|
||||
Ref: opts.RefFullName,
|
||||
Before: opts.OldCommitID,
|
||||
|
|
@ -617,41 +676,6 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
|
|||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks: %v", err)
|
||||
}
|
||||
|
||||
if isNewBranch {
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
if err != nil {
|
||||
log.Error(4, "OpenRepository[%s]: %v", repo.RepoPath(), err)
|
||||
}
|
||||
shaSum, err = gitRepo.GetBranchCommitID(refName)
|
||||
if err != nil {
|
||||
log.Error(4, "GetBranchCommitID[%s]: %v", opts.RefFullName, err)
|
||||
}
|
||||
return PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{
|
||||
Ref: refName,
|
||||
Sha: shaSum,
|
||||
RefType: "branch",
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
})
|
||||
}
|
||||
|
||||
case ActionPushTag: // Create
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
if err != nil {
|
||||
log.Error(4, "OpenRepository[%s]: %v", repo.RepoPath(), err)
|
||||
}
|
||||
shaSum, err = gitRepo.GetTagCommitID(refName)
|
||||
if err != nil {
|
||||
log.Error(4, "GetTagCommitID[%s]: %v", opts.RefFullName, err)
|
||||
}
|
||||
return PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{
|
||||
Ref: refName,
|
||||
Sha: shaSum,
|
||||
RefType: "tag",
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -202,57 +203,118 @@ func TestUpdateIssuesCommit(t *testing.T) {
|
|||
CheckConsistencyFor(t, &Action{})
|
||||
}
|
||||
|
||||
func TestCommitRepoAction(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 2, OwnerID: user.ID}).(*Repository)
|
||||
repo.Owner = user
|
||||
|
||||
pushCommits := NewPushCommits()
|
||||
pushCommits.Commits = []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef1",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user4@example.com",
|
||||
AuthorName: "User Four",
|
||||
Message: "message1",
|
||||
},
|
||||
{
|
||||
Sha1: "abcdef2",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "message2",
|
||||
},
|
||||
}
|
||||
pushCommits.Len = len(pushCommits.Commits)
|
||||
|
||||
actionBean := &Action{
|
||||
OpType: ActionCommitRepo,
|
||||
ActUserID: user.ID,
|
||||
ActUser: user,
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
RefName: "refName",
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}
|
||||
func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) {
|
||||
AssertNotExistsBean(t, actionBean)
|
||||
assert.NoError(t, CommitRepoAction(CommitRepoActionOptions{
|
||||
PusherName: user.Name,
|
||||
RepoOwnerID: user.ID,
|
||||
RepoName: repo.Name,
|
||||
RefFullName: "refName",
|
||||
OldCommitID: "oldCommitID",
|
||||
NewCommitID: "newCommitID",
|
||||
Commits: pushCommits,
|
||||
}))
|
||||
assert.NoError(t, CommitRepoAction(opts))
|
||||
AssertExistsAndLoadBean(t, actionBean)
|
||||
CheckConsistencyFor(t, &Action{})
|
||||
}
|
||||
|
||||
func TestCommitRepoAction(t *testing.T) {
|
||||
samples := []struct {
|
||||
userID int64
|
||||
repositoryID int64
|
||||
commitRepoActionOptions CommitRepoActionOptions
|
||||
action Action
|
||||
}{
|
||||
{
|
||||
userID: 2,
|
||||
repositoryID: 2,
|
||||
commitRepoActionOptions: CommitRepoActionOptions{
|
||||
RefFullName: "refName",
|
||||
OldCommitID: "oldCommitID",
|
||||
NewCommitID: "newCommitID",
|
||||
Commits: &PushCommits{
|
||||
avatars: make(map[string]string),
|
||||
Commits: []*PushCommit{
|
||||
{
|
||||
Sha1: "abcdef1",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user4@example.com",
|
||||
AuthorName: "User Four",
|
||||
Message: "message1",
|
||||
},
|
||||
{
|
||||
Sha1: "abcdef2",
|
||||
CommitterEmail: "user2@example.com",
|
||||
CommitterName: "User Two",
|
||||
AuthorEmail: "user2@example.com",
|
||||
AuthorName: "User Two",
|
||||
Message: "message2",
|
||||
},
|
||||
},
|
||||
Len: 2,
|
||||
},
|
||||
},
|
||||
action: Action{
|
||||
OpType: ActionCommitRepo,
|
||||
RefName: "refName",
|
||||
},
|
||||
},
|
||||
{
|
||||
userID: 2,
|
||||
repositoryID: 1,
|
||||
commitRepoActionOptions: CommitRepoActionOptions{
|
||||
RefFullName: git.TagPrefix + "v1.1",
|
||||
OldCommitID: git.EmptySHA,
|
||||
NewCommitID: "newCommitID",
|
||||
Commits: &PushCommits{},
|
||||
},
|
||||
action: Action{
|
||||
OpType: ActionPushTag,
|
||||
RefName: "v1.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
userID: 2,
|
||||
repositoryID: 1,
|
||||
commitRepoActionOptions: CommitRepoActionOptions{
|
||||
RefFullName: git.TagPrefix + "v1.1",
|
||||
OldCommitID: "oldCommitID",
|
||||
NewCommitID: git.EmptySHA,
|
||||
Commits: &PushCommits{},
|
||||
},
|
||||
action: Action{
|
||||
OpType: ActionDeleteTag,
|
||||
RefName: "v1.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
userID: 2,
|
||||
repositoryID: 1,
|
||||
commitRepoActionOptions: CommitRepoActionOptions{
|
||||
RefFullName: git.BranchPrefix + "feature/1",
|
||||
OldCommitID: "oldCommitID",
|
||||
NewCommitID: git.EmptySHA,
|
||||
Commits: &PushCommits{},
|
||||
},
|
||||
action: Action{
|
||||
OpType: ActionDeleteBranch,
|
||||
RefName: "feature/1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range samples {
|
||||
prepareTestEnv(t)
|
||||
|
||||
user := AssertExistsAndLoadBean(t, &User{ID: s.userID}).(*User)
|
||||
repo := AssertExistsAndLoadBean(t, &Repository{ID: s.repositoryID, OwnerID: user.ID}).(*Repository)
|
||||
repo.Owner = user
|
||||
|
||||
s.commitRepoActionOptions.PusherName = user.Name
|
||||
s.commitRepoActionOptions.RepoOwnerID = user.ID
|
||||
s.commitRepoActionOptions.RepoName = repo.Name
|
||||
|
||||
s.action.ActUserID = user.ID
|
||||
s.action.RepoID = repo.ID
|
||||
s.action.IsPrivate = repo.IsPrivate
|
||||
|
||||
testCorrectRepoAction(t, s.commitRepoActionOptions, &s.action)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransferRepoAction(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ type ProtectedBranch struct {
|
|||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
BranchName string `xorm:"UNIQUE(s)"`
|
||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||
EnableWhitelist bool
|
||||
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
|
||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
|
||||
|
|
|
|||
|
|
@ -15,3 +15,27 @@
|
|||
user_id: 4
|
||||
repo_id: 3
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 4
|
||||
user_id: 15
|
||||
repo_id: 22
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 5
|
||||
user_id: 15
|
||||
repo_id: 21
|
||||
mode: 2 # write
|
||||
|
||||
-
|
||||
id: 6
|
||||
user_id: 15
|
||||
repo_id: 23
|
||||
mode: 4 # owner
|
||||
|
||||
-
|
||||
id: 7
|
||||
user_id: 15
|
||||
repo_id: 24
|
||||
mode: 4 # owner
|
||||
|
|
@ -29,3 +29,11 @@
|
|||
is_public: false
|
||||
is_owner: true
|
||||
num_teams: 1
|
||||
|
||||
-
|
||||
id: 5
|
||||
uid: 15
|
||||
org_id: 17
|
||||
is_public: true
|
||||
is_owner: true
|
||||
num_teams: 1
|
||||
|
|
|
|||
|
|
@ -188,3 +188,100 @@
|
|||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
num_watches: 0
|
||||
|
||||
-
|
||||
id: 17
|
||||
owner_id: 15
|
||||
lower_name: big_test_public_1
|
||||
name: big_test_public_1
|
||||
is_private: false
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
num_watches: 0
|
||||
is_mirror: false
|
||||
|
||||
-
|
||||
id: 18
|
||||
owner_id: 15
|
||||
lower_name: big_test_public_2
|
||||
name: big_test_public_2
|
||||
is_private: false
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
is_mirror: false
|
||||
|
||||
-
|
||||
id: 19
|
||||
owner_id: 15
|
||||
lower_name: big_test_private_1
|
||||
name: big_test_private_1
|
||||
is_private: true
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
is_mirror: false
|
||||
|
||||
-
|
||||
id: 20
|
||||
owner_id: 15
|
||||
lower_name: big_test_private_2
|
||||
name: big_test_private_2
|
||||
is_private: true
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
is_mirror: false
|
||||
|
||||
-
|
||||
id: 21
|
||||
owner_id: 16
|
||||
lower_name: big_test_public_3
|
||||
name: big_test_public_3
|
||||
is_private: false
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
is_mirror: false
|
||||
|
||||
-
|
||||
id: 22
|
||||
owner_id: 16
|
||||
lower_name: big_test_private_3
|
||||
name: big_test_private_3
|
||||
is_private: true
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
is_mirror: false
|
||||
|
||||
-
|
||||
id: 23
|
||||
owner_id: 17
|
||||
lower_name: big_test_public_4
|
||||
name: big_test_public_4
|
||||
is_private: false
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
is_mirror: false
|
||||
|
||||
-
|
||||
id: 24
|
||||
owner_id: 17
|
||||
lower_name: big_test_private_4
|
||||
name: big_test_private_4
|
||||
is_private: true
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
num_pulls: 0
|
||||
num_closed_pulls: 0
|
||||
is_mirror: false
|
||||
|
|
|
|||
|
|
@ -37,3 +37,12 @@
|
|||
num_repos: 0
|
||||
num_members: 1
|
||||
unit_types: '[1,2,3,4,5,6,7]'
|
||||
-
|
||||
id: 5
|
||||
org_id: 17
|
||||
lower_name: owners
|
||||
name: Owners
|
||||
authorize: 4 # owner
|
||||
num_repos: 2
|
||||
num_members: 1
|
||||
unit_types: '[1,2,3,4,5,6,7]'
|
||||
|
|
|
|||
|
|
@ -15,3 +15,15 @@
|
|||
org_id: 3
|
||||
team_id: 1
|
||||
repo_id: 5
|
||||
|
||||
-
|
||||
id: 4
|
||||
org_id: 17
|
||||
team_id: 5
|
||||
repo_id: 23
|
||||
|
||||
-
|
||||
id: 5
|
||||
org_id: 17
|
||||
team_id: 5
|
||||
repo_id: 24
|
||||
|
|
@ -27,3 +27,9 @@
|
|||
org_id: 7
|
||||
team_id: 4
|
||||
uid: 5
|
||||
|
||||
-
|
||||
id: 6
|
||||
org_id: 17
|
||||
team_id: 5
|
||||
uid: 15
|
||||
|
|
@ -218,3 +218,50 @@
|
|||
avatar_email: user13@example.com
|
||||
num_repos: 3
|
||||
is_active: true
|
||||
|
||||
-
|
||||
id: 15
|
||||
lower_name: user15
|
||||
name: user15
|
||||
full_name: User 15
|
||||
email: user15@example.com
|
||||
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
|
||||
type: 0 # individual
|
||||
salt: ZogKvWdyEx
|
||||
is_admin: false
|
||||
avatar: avatar15
|
||||
avatar_email: user15@example.com
|
||||
num_repos: 4
|
||||
is_active: true
|
||||
|
||||
-
|
||||
id: 16
|
||||
lower_name: user16
|
||||
name: user16
|
||||
full_name: User 16
|
||||
email: user16@example.com
|
||||
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
|
||||
type: 0 # individual
|
||||
salt: ZogKvWdyEx
|
||||
is_admin: false
|
||||
avatar: avatar16
|
||||
avatar_email: user16@example.com
|
||||
num_repos: 2
|
||||
is_active: true
|
||||
|
||||
-
|
||||
id: 17
|
||||
lower_name: user17
|
||||
name: user17
|
||||
full_name: User 17
|
||||
email: user17@example.com
|
||||
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
|
||||
type: 1 # organization
|
||||
salt: ZogKvWdyEx
|
||||
is_admin: false
|
||||
avatar: avatar17
|
||||
avatar_email: user17@example.com
|
||||
num_repos: 2
|
||||
is_active: true
|
||||
num_members: 1
|
||||
num_teams: 1
|
||||
|
|
@ -282,11 +282,7 @@ func DeleteGPGKey(doer *User, id int64) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// CommitVerification represents a commit validation of signature
|
||||
|
|
|
|||
|
|
@ -571,11 +571,7 @@ func (issue *Issue) ReadBy(userID int64) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := setNotificationStatusReadIfUnread(x, userID, issue.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return setNotificationStatusReadIfUnread(x, userID, issue.ID)
|
||||
}
|
||||
|
||||
func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
|
||||
|
|
|
|||
|
|
@ -498,10 +498,7 @@ func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
|
|||
}
|
||||
|
||||
if ok, _ := c.Extension("AUTH"); ok {
|
||||
if err = c.Auth(a); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return c.Auth(a)
|
||||
}
|
||||
return ErrUnsupportedLoginType
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/mailer"
|
||||
"code.gitea.io/gitea/modules/markdown"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"gopkg.in/gomail.v2"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
|
@ -167,7 +167,7 @@ func composeIssueCommentMessage(issue *Issue, doer *User, comment *Comment, tplN
|
|||
log.Error(3, "Template: %v", err)
|
||||
}
|
||||
|
||||
msg := mailer.NewMessageFrom(tos, fmt.Sprintf(`"%s" <%s>`, doer.DisplayName(), setting.MailService.FromEmail), subject, content.String())
|
||||
msg := mailer.NewMessageFrom(tos, doer.DisplayName(), setting.MailService.FromEmail, subject, content.String())
|
||||
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
|
||||
return msg
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,6 +133,10 @@ var migrations = []Migration{
|
|||
// v41 -> v42
|
||||
NewMigration("add default value to user prohibit_login", addDefaultValueToUserProhibitLogin),
|
||||
// v42 -> v43
|
||||
NewMigration("add tags to releases and sync existing repositories", releaseAddColumnIsTagAndSyncTags),
|
||||
// v43 -> v44
|
||||
NewMigration("fix protected branch can push value to false", fixProtectedBranchCanPushValue),
|
||||
// v44 -> v45
|
||||
NewMigration("add issue_dependency table", addIssueDependencyTables),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,9 +119,5 @@ func addUnitsToTables(x *xorm.Engine) error {
|
|||
}
|
||||
}
|
||||
|
||||
if err := sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return sess.Commit()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,8 +51,5 @@ func useNewPublickeyFormat(x *xorm.Engine) error {
|
|||
}
|
||||
|
||||
f.Close()
|
||||
if err = os.Rename(tmpPath, fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return os.Rename(tmpPath, fpath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,18 +7,51 @@ package migrations
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
func addIssueDependencyTables(x *xorm.Engine) (err error) {
|
||||
// ReleaseV39 describes the added field for Release
|
||||
type ReleaseV39 struct {
|
||||
IsTag bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
err = x.Sync(new(models.IssueDependency))
|
||||
// TableName will be invoked by XORM to customrize the table name
|
||||
func (*ReleaseV39) TableName() string {
|
||||
return "release"
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating issue_dependency_table column definition: %v", err)
|
||||
func releaseAddColumnIsTagAndSyncTags(x *xorm.Engine) error {
|
||||
if err := x.Sync2(new(ReleaseV39)); err != nil {
|
||||
return fmt.Errorf("Sync2: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
// For the sake of SQLite3, we can't use x.Iterate here.
|
||||
offset := 0
|
||||
pageSize := 20
|
||||
for {
|
||||
repos := make([]*models.Repository, 0, pageSize)
|
||||
if err := x.Table("repository").Asc("id").Limit(pageSize, offset).Find(&repos); err != nil {
|
||||
return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
|
||||
}
|
||||
for _, repo := range repos {
|
||||
gitRepo, err := git.OpenRepository(repo.RepoPath())
|
||||
if err != nil {
|
||||
log.Warn("OpenRepository: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = models.SyncReleasesWithTags(repo, gitRepo); err != nil {
|
||||
log.Warn("SyncReleasesWithTags: %v", err)
|
||||
}
|
||||
}
|
||||
if len(repos) < pageSize {
|
||||
break
|
||||
}
|
||||
offset += pageSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
26
models/migrations/v43.go
Normal file
26
models/migrations/v43.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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 migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
func fixProtectedBranchCanPushValue(x *xorm.Engine) error {
|
||||
type ProtectedBranch struct {
|
||||
CanPush bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
if err := x.Sync2(new(ProtectedBranch)); err != nil {
|
||||
return fmt.Errorf("Sync2: %v", err)
|
||||
}
|
||||
|
||||
_, err := x.Cols("can_push").Update(&ProtectedBranch{
|
||||
CanPush: false,
|
||||
})
|
||||
return err
|
||||
}
|
||||
1
models/migrations/v44.go
Normal file
1
models/migrations/v44.go
Normal file
|
|
@ -0,0 +1 @@
|
|||
package migrations
|
||||
|
|
@ -235,11 +235,7 @@ func DeleteOrganization(org *User) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func deleteOrg(e *xorm.Session, u *User) error {
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ func TestOrganizations(t *testing.T) {
|
|||
[]int64{3, 6})
|
||||
|
||||
testSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 2, PageSize: 2},
|
||||
[]int64{7})
|
||||
[]int64{7, 17})
|
||||
|
||||
testSuccess(&SearchUserOptions{Page: 3, PageSize: 2},
|
||||
[]int64{})
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ type Release struct {
|
|||
NumCommitsBehind int64 `xorm:"-"`
|
||||
Note string `xorm:"TEXT"`
|
||||
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IsPrerelease bool
|
||||
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IsTag bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
||||
Attachments []*Attachment `xorm:"-"`
|
||||
|
||||
|
|
@ -139,17 +140,18 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
commit, err := gitRepo.GetTagCommit(rel.TagName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetTagCommit: %v", err)
|
||||
}
|
||||
rel.LowerTagName = strings.ToLower(rel.TagName)
|
||||
}
|
||||
commit, err := gitRepo.GetTagCommit(rel.TagName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetTagCommit: %v", err)
|
||||
}
|
||||
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.NumCommits, err = commit.CommitsCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("CommitsCount: %v", err)
|
||||
}
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.CreatedUnix = commit.Author.When.Unix()
|
||||
rel.NumCommits, err = commit.CommitsCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("CommitsCount: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -236,6 +238,7 @@ func GetReleaseByID(id int64) (*Release, error) {
|
|||
// FindReleasesOptions describes the conditions to Find releases
|
||||
type FindReleasesOptions struct {
|
||||
IncludeDrafts bool
|
||||
IncludeTags bool
|
||||
TagNames []string
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +249,9 @@ func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
|
|||
if !opts.IncludeDrafts {
|
||||
cond = cond.And(builder.Eq{"is_draft": false})
|
||||
}
|
||||
if !opts.IncludeTags {
|
||||
cond = cond.And(builder.Eq{"is_tag": false})
|
||||
}
|
||||
if len(opts.TagNames) > 0 {
|
||||
cond = cond.And(builder.In("tag_name", opts.TagNames))
|
||||
}
|
||||
|
|
@ -361,6 +367,8 @@ func UpdateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []stri
|
|||
if err = createTag(gitRepo, rel); err != nil {
|
||||
return err
|
||||
}
|
||||
rel.LowerTagName = strings.ToLower(rel.TagName)
|
||||
|
||||
_, err = x.Id(rel.ID).AllCols().Update(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -397,11 +405,64 @@ func DeleteReleaseByID(id int64, u *User, delTag bool) error {
|
|||
if err != nil && !strings.Contains(stderr, "not found") {
|
||||
return fmt.Errorf("git tag -d: %v - %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
}
|
||||
} else {
|
||||
rel.IsTag = true
|
||||
rel.IsDraft = false
|
||||
rel.IsPrerelease = false
|
||||
rel.Title = ""
|
||||
rel.Note = ""
|
||||
|
||||
if _, err = x.Id(rel.ID).AllCols().Update(rel); err != nil {
|
||||
return fmt.Errorf("Update: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncReleasesWithTags synchronizes release table with repository tags
|
||||
func SyncReleasesWithTags(repo *Repository, gitRepo *git.Repository) error {
|
||||
existingRelTags := make(map[string]struct{})
|
||||
opts := FindReleasesOptions{IncludeDrafts: true, IncludeTags: true}
|
||||
for page := 1; ; page++ {
|
||||
rels, err := GetReleasesByRepoID(repo.ID, opts, page, 100)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetReleasesByRepoID: %v", err)
|
||||
}
|
||||
if len(rels) == 0 {
|
||||
break
|
||||
}
|
||||
for _, rel := range rels {
|
||||
if rel.IsDraft {
|
||||
continue
|
||||
}
|
||||
commitID, err := gitRepo.GetTagCommitID(rel.TagName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetTagCommitID: %v", err)
|
||||
}
|
||||
if !gitRepo.IsTagExist(rel.TagName) || commitID != rel.Sha1 {
|
||||
if err := pushUpdateDeleteTag(repo, gitRepo, rel.TagName); err != nil {
|
||||
return fmt.Errorf("pushUpdateDeleteTag: %v", err)
|
||||
}
|
||||
} else {
|
||||
existingRelTags[strings.ToLower(rel.TagName)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
tags, err := gitRepo.GetTags()
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetTags: %v", err)
|
||||
}
|
||||
for _, tagName := range tags {
|
||||
if _, ok := existingRelTags[strings.ToLower(tagName)]; !ok {
|
||||
if err := pushUpdateAddTag(repo, gitRepo, tagName); err != nil {
|
||||
return fmt.Errorf("pushUpdateAddTag: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -639,7 +639,7 @@ func (repo *Repository) UpdateSize() error {
|
|||
|
||||
// CanBeForked returns true if repository meets the requirements of being forked.
|
||||
func (repo *Repository) CanBeForked() bool {
|
||||
return !repo.IsBare
|
||||
return !repo.IsBare && repo.UnitEnabled(UnitTypeCode)
|
||||
}
|
||||
|
||||
// CanEnablePulls returns true if repository meets the requirements of accepting pulls.
|
||||
|
|
@ -951,6 +951,10 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
|
|||
if headBranch != nil {
|
||||
repo.DefaultBranch = headBranch.Name
|
||||
}
|
||||
|
||||
if err = SyncReleasesWithTags(repo, gitRepo); err != nil {
|
||||
log.Error(4, "Failed to synchronize tags to releases for repository: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = repo.UpdateSize(); err != nil {
|
||||
|
|
|
|||
|
|
@ -97,14 +97,14 @@ type SearchRepoOptions struct {
|
|||
// Owner in we search search
|
||||
//
|
||||
// in: query
|
||||
OwnerID int64 `json:"uid"`
|
||||
Searcher *User `json:"-"` //ID of the person who's seeking
|
||||
OrderBy string `json:"-"`
|
||||
Private bool `json:"-"` // Include private repositories in results
|
||||
Collaborate bool `json:"-"` // Include collaborative repositories
|
||||
Starred bool `json:"-"`
|
||||
Page int `json:"-"`
|
||||
IsProfile bool `json:"-"`
|
||||
OwnerID int64 `json:"uid"`
|
||||
Searcher *User `json:"-"` //ID of the person who's seeking
|
||||
OrderBy SearchOrderBy `json:"-"`
|
||||
Private bool `json:"-"` // Include private repositories in results
|
||||
Collaborate bool `json:"-"` // Include collaborative repositories
|
||||
Starred bool `json:"-"`
|
||||
Page int `json:"-"`
|
||||
IsProfile bool `json:"-"`
|
||||
// Limit of result
|
||||
//
|
||||
// maximum: setting.ExplorePagingNum
|
||||
|
|
@ -112,6 +112,25 @@ type SearchRepoOptions struct {
|
|||
PageSize int `json:"limit"` // Can be smaller than or equal to setting.ExplorePagingNum
|
||||
}
|
||||
|
||||
//SearchOrderBy is used to sort the result
|
||||
type SearchOrderBy string
|
||||
|
||||
func (s SearchOrderBy) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// Strings for sorting result
|
||||
const (
|
||||
SearchOrderByAlphabetically SearchOrderBy = "name ASC"
|
||||
SearchOrderByAlphabeticallyReverse = "name DESC"
|
||||
SearchOrderByLeastUpdated = "updated_unix ASC"
|
||||
SearchOrderByRecentUpdated = "updated_unix DESC"
|
||||
SearchOrderByOldest = "created_unix ASC"
|
||||
SearchOrderByNewest = "created_unix DESC"
|
||||
SearchOrderBySize = "size ASC"
|
||||
SearchOrderBySizeReverse = "size DESC"
|
||||
)
|
||||
|
||||
// SearchRepositoryByName takes keyword and part of repository name to search,
|
||||
// it returns results in given range and number of total results.
|
||||
func SearchRepositoryByName(opts *SearchRepoOptions) (repos RepositoryList, count int64, err error) {
|
||||
|
|
@ -164,7 +183,7 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos RepositoryList, coun
|
|||
}
|
||||
|
||||
if len(opts.OrderBy) == 0 {
|
||||
opts.OrderBy = "name ASC"
|
||||
opts.OrderBy = SearchOrderByAlphabetically
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
|
|
@ -193,7 +212,7 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos RepositoryList, coun
|
|||
if err = sess.
|
||||
Where(cond).
|
||||
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
|
||||
OrderBy(opts.OrderBy).
|
||||
OrderBy(opts.OrderBy.String()).
|
||||
Find(&repos); err != nil {
|
||||
return nil, 0, fmt.Errorf("Repo: %v", err)
|
||||
}
|
||||
|
|
@ -217,7 +236,7 @@ func Repositories(opts *SearchRepoOptions) (_ RepositoryList, count int64, err e
|
|||
|
||||
if err = x.
|
||||
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
|
||||
OrderBy(opts.OrderBy).
|
||||
OrderBy(opts.OrderBy.String()).
|
||||
Find(&repos); err != nil {
|
||||
return nil, 0, fmt.Errorf("Repo: %v", err)
|
||||
}
|
||||
|
|
@ -236,7 +255,7 @@ func GetRecentUpdatedRepositories(opts *SearchRepoOptions) (repos RepositoryList
|
|||
var cond = builder.NewCond()
|
||||
|
||||
if len(opts.OrderBy) == 0 {
|
||||
opts.OrderBy = "updated_unix DESC"
|
||||
opts.OrderBy = SearchOrderByRecentUpdated
|
||||
}
|
||||
|
||||
if !opts.Private {
|
||||
|
|
@ -270,7 +289,7 @@ func GetRecentUpdatedRepositories(opts *SearchRepoOptions) (repos RepositoryList
|
|||
if err = x.Where(cond).
|
||||
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
|
||||
Limit(opts.PageSize).
|
||||
OrderBy(opts.OrderBy).
|
||||
OrderBy(opts.OrderBy.String()).
|
||||
Find(&repos); err != nil {
|
||||
return nil, 0, fmt.Errorf("Repo: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/go-xorm/xorm"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
|
@ -156,6 +157,15 @@ func (m *Mirror) runSync() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
log.Error(4, "OpenRepository: %v", err)
|
||||
return false
|
||||
}
|
||||
if err = SyncReleasesWithTags(m.Repo, gitRepo); err != nil {
|
||||
log.Error(4, "Failed to synchronize tags to releases for repository: %v", err)
|
||||
}
|
||||
|
||||
if err := m.Repo.UpdateSize(); err != nil {
|
||||
log.Error(4, "Failed to update size for mirror repository: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -599,11 +599,7 @@ func RewriteAllPublicKeys() error {
|
|||
defer f.Close()
|
||||
}
|
||||
|
||||
if err = os.Rename(tmpPath, fPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return os.Rename(tmpPath, fPath)
|
||||
}
|
||||
|
||||
// ________ .__ ____ __.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -38,6 +42,12 @@ func PrepareTestDatabase() error {
|
|||
return LoadFixtures()
|
||||
}
|
||||
|
||||
func prepareTestEnv(t testing.TB) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
|
||||
assert.NoError(t, com.CopyDir("../integrations/gitea-repositories-meta", setting.RepoRootPath))
|
||||
}
|
||||
|
||||
type testCond struct {
|
||||
query interface{}
|
||||
args []interface{}
|
||||
|
|
|
|||
156
models/update.go
156
models/update.go
|
|
@ -81,6 +81,93 @@ func PushUpdate(branch string, opt PushUpdateOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func pushUpdateDeleteTag(repo *Repository, gitRepo *git.Repository, tagName string) error {
|
||||
rel, err := GetRelease(repo.ID, tagName)
|
||||
if err != nil {
|
||||
if IsErrReleaseNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("GetRelease: %v", err)
|
||||
}
|
||||
if rel.IsTag {
|
||||
if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
}
|
||||
} else {
|
||||
rel.IsDraft = true
|
||||
rel.NumCommits = 0
|
||||
rel.Sha1 = ""
|
||||
if _, err = x.Id(rel.ID).AllCols().Update(rel); err != nil {
|
||||
return fmt.Errorf("Update: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string) error {
|
||||
rel, err := GetRelease(repo.ID, tagName)
|
||||
if err != nil && !IsErrReleaseNotExist(err) {
|
||||
return fmt.Errorf("GetRelease: %v", err)
|
||||
}
|
||||
|
||||
tag, err := gitRepo.GetTag(tagName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetTag: %v", err)
|
||||
}
|
||||
commit, err := tag.Commit()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Commit: %v", err)
|
||||
}
|
||||
tagCreatedUnix := commit.Author.When.Unix()
|
||||
|
||||
author, err := GetUserByEmail(commit.Author.Email)
|
||||
if err != nil && !IsErrUserNotExist(err) {
|
||||
return fmt.Errorf("GetUserByEmail: %v", err)
|
||||
}
|
||||
|
||||
commitsCount, err := commit.CommitsCount()
|
||||
if err != nil {
|
||||
return fmt.Errorf("CommitsCount: %v", err)
|
||||
}
|
||||
|
||||
if rel == nil {
|
||||
rel = &Release{
|
||||
RepoID: repo.ID,
|
||||
Title: "",
|
||||
TagName: tagName,
|
||||
LowerTagName: strings.ToLower(tagName),
|
||||
Target: "",
|
||||
Sha1: commit.ID.String(),
|
||||
NumCommits: commitsCount,
|
||||
Note: "",
|
||||
IsDraft: false,
|
||||
IsPrerelease: false,
|
||||
IsTag: true,
|
||||
CreatedUnix: tagCreatedUnix,
|
||||
}
|
||||
if author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
|
||||
if _, err = x.InsertOne(rel); err != nil {
|
||||
return fmt.Errorf("InsertOne: %v", err)
|
||||
}
|
||||
} else {
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.CreatedUnix = tagCreatedUnix
|
||||
rel.NumCommits = commitsCount
|
||||
rel.IsDraft = false
|
||||
if rel.IsTag && author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
if _, err = x.Id(rel.ID).AllCols().Update(rel); err != nil {
|
||||
return fmt.Errorf("Update: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
|
||||
isNewRef := opts.OldCommitID == git.EmptySHA
|
||||
isDelRef := opts.NewCommitID == git.EmptySHA
|
||||
|
|
@ -106,12 +193,6 @@ func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
|
|||
return nil, fmt.Errorf("GetRepositoryByName: %v", err)
|
||||
}
|
||||
|
||||
if isDelRef {
|
||||
log.GitLogger.Info("Reference '%s' has been deleted from '%s/%s' by %s",
|
||||
opts.RefFullName, opts.RepoUserName, opts.RepoName, opts.PusherName)
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("OpenRepository: %v", err)
|
||||
|
|
@ -121,39 +202,42 @@ func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
|
|||
log.Error(4, "Failed to update size for repository: %v", err)
|
||||
}
|
||||
|
||||
// Push tags.
|
||||
var commits = &PushCommits{}
|
||||
if strings.HasPrefix(opts.RefFullName, git.TagPrefix) {
|
||||
if err := CommitRepoAction(CommitRepoActionOptions{
|
||||
PusherName: opts.PusherName,
|
||||
RepoOwnerID: owner.ID,
|
||||
RepoName: repo.Name,
|
||||
RefFullName: opts.RefFullName,
|
||||
OldCommitID: opts.OldCommitID,
|
||||
NewCommitID: opts.NewCommitID,
|
||||
Commits: &PushCommits{},
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("CommitRepoAction (tag): %v", err)
|
||||
// If is tag reference
|
||||
if isDelRef {
|
||||
err = pushUpdateDeleteTag(repo, gitRepo, opts.RefFullName[len(git.TagPrefix):])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pushUpdateDeleteTag: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = pushUpdateAddTag(repo, gitRepo, opts.RefFullName[len(git.TagPrefix):])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pushUpdateAddTag: %v", err)
|
||||
}
|
||||
}
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gitRepo.GetCommit: %v", err)
|
||||
}
|
||||
|
||||
// Push new branch.
|
||||
var l *list.List
|
||||
if isNewRef {
|
||||
l, err = newCommit.CommitsBeforeLimit(10)
|
||||
} else if !isDelRef {
|
||||
// If is branch reference
|
||||
newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err)
|
||||
return nil, fmt.Errorf("gitRepo.GetCommit: %v", err)
|
||||
}
|
||||
} else {
|
||||
l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err)
|
||||
|
||||
// Push new branch.
|
||||
var l *list.List
|
||||
if isNewRef {
|
||||
l, err = newCommit.CommitsBeforeLimit(10)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err)
|
||||
}
|
||||
} else {
|
||||
l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
commits = ListToPushCommits(l)
|
||||
}
|
||||
|
||||
if err := CommitRepoAction(CommitRepoActionOptions{
|
||||
|
|
@ -163,9 +247,9 @@ func pushUpdate(opts PushUpdateOptions) (repo *Repository, err error) {
|
|||
RefFullName: opts.RefFullName,
|
||||
OldCommitID: opts.OldCommitID,
|
||||
NewCommitID: opts.NewCommitID,
|
||||
Commits: ListToPushCommits(l),
|
||||
Commits: commits,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("CommitRepoAction (branch): %v", err)
|
||||
return nil, fmt.Errorf("CommitRepoAction: %v", err)
|
||||
}
|
||||
return repo, nil
|
||||
}
|
||||
|
|
|
|||
89
modules/base/natural_sort.go
Normal file
89
modules/base/natural_sort.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// 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 base
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
||||
func NaturalSortLess(s1, s2 string) bool {
|
||||
var i1, i2 int
|
||||
for {
|
||||
rune1, j1, end1 := getNextRune(s1, i1)
|
||||
rune2, j2, end2 := getNextRune(s2, i2)
|
||||
if end1 || end2 {
|
||||
return end1 != end2 && end1
|
||||
}
|
||||
dec1 := isDecimal(rune1)
|
||||
dec2 := isDecimal(rune2)
|
||||
var less, equal bool
|
||||
if dec1 && dec2 {
|
||||
i1, i2, less, equal = compareByNumbers(s1, i1, s2, i2)
|
||||
} else if !dec1 && !dec2 {
|
||||
equal = rune1 == rune2
|
||||
less = rune1 < rune2
|
||||
i1 = j1
|
||||
i2 = j2
|
||||
} else {
|
||||
return rune1 < rune2
|
||||
}
|
||||
if !equal {
|
||||
return less
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getNextRune(str string, pos int) (rune, int, bool) {
|
||||
if pos < len(str) {
|
||||
r, w := utf8.DecodeRuneInString(str[pos:])
|
||||
// Fallback to ascii
|
||||
if r == utf8.RuneError {
|
||||
r = rune(str[pos])
|
||||
w = 1
|
||||
}
|
||||
return r, pos + w, false
|
||||
}
|
||||
return 0, pos, true
|
||||
}
|
||||
|
||||
func isDecimal(r rune) bool {
|
||||
return '0' <= r && r <= '9'
|
||||
}
|
||||
|
||||
func compareByNumbers(str1 string, pos1 int, str2 string, pos2 int) (i1, i2 int, less, equal bool) {
|
||||
var d1, d2 bool = true, true
|
||||
var dec1, dec2 string
|
||||
for d1 || d2 {
|
||||
if d1 {
|
||||
r, j, end := getNextRune(str1, pos1)
|
||||
if !end && isDecimal(r) {
|
||||
dec1 += string(r)
|
||||
pos1 = j
|
||||
} else {
|
||||
d1 = false
|
||||
}
|
||||
}
|
||||
if d2 {
|
||||
r, j, end := getNextRune(str2, pos2)
|
||||
if !end && isDecimal(r) {
|
||||
dec2 += string(r)
|
||||
pos2 = j
|
||||
} else {
|
||||
d2 = false
|
||||
}
|
||||
}
|
||||
}
|
||||
less, equal = compareBigNumbers(dec1, dec2)
|
||||
return pos1, pos2, less, equal
|
||||
}
|
||||
|
||||
func compareBigNumbers(dec1, dec2 string) (less, equal bool) {
|
||||
d1, _ := big.NewInt(0).SetString(dec1, 10)
|
||||
d2, _ := big.NewInt(0).SetString(dec2, 10)
|
||||
cmp := d1.Cmp(d2)
|
||||
return cmp < 0, cmp == 0
|
||||
}
|
||||
24
modules/base/natural_sort_test.go
Normal file
24
modules/base/natural_sort_test.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// 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 base
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNaturalSortLess(t *testing.T) {
|
||||
test := func(s1, s2 string, less bool) {
|
||||
assert.Equal(t, less, NaturalSortLess(s1, s2))
|
||||
}
|
||||
test("v1.20.0", "v1.2.0", false)
|
||||
test("v1.20.0", "v1.29.0", true)
|
||||
test("v1.20.0", "v1.20.0", false)
|
||||
test("abc", "bcd", "abc" < "bcd")
|
||||
test("a-1-a", "a-1-b", true)
|
||||
test("2", "12", true)
|
||||
test("a", "ab", "a" < "ab")
|
||||
}
|
||||
|
|
@ -134,21 +134,23 @@ func RetrieveBaseRepo(ctx *Context, repo *models.Repository) {
|
|||
}
|
||||
}
|
||||
|
||||
// composeGoGetImport returns go-get-import meta content.
|
||||
func composeGoGetImport(owner, repo string) string {
|
||||
// ComposeGoGetImport returns go-get-import meta content.
|
||||
func ComposeGoGetImport(owner, repo string) string {
|
||||
return path.Join(setting.Domain, setting.AppSubURL, owner, repo)
|
||||
}
|
||||
|
||||
// earlyResponseForGoGetMeta responses appropriate go-get meta with status 200
|
||||
// EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
|
||||
// if user does not have actual access to the requested repository,
|
||||
// or the owner or repository does not exist at all.
|
||||
// This is particular a workaround for "go get" command which does not respect
|
||||
// .netrc file.
|
||||
func earlyResponseForGoGetMeta(ctx *Context) {
|
||||
func EarlyResponseForGoGetMeta(ctx *Context) {
|
||||
username := ctx.Params(":username")
|
||||
reponame := ctx.Params(":reponame")
|
||||
ctx.PlainText(200, []byte(com.Expand(`<meta name="go-import" content="{GoGetImport} git {CloneLink}">`,
|
||||
map[string]string{
|
||||
"GoGetImport": composeGoGetImport(ctx.Params(":username"), strings.TrimSuffix(ctx.Params(":reponame"), ".git")),
|
||||
"CloneLink": models.ComposeHTTPSCloneURL(ctx.Params(":username"), ctx.Params(":reponame")),
|
||||
"GoGetImport": ComposeGoGetImport(username, strings.TrimSuffix(reponame, ".git")),
|
||||
"CloneLink": models.ComposeHTTPSCloneURL(username, reponame),
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +174,75 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) {
|
|||
ctx.Redirect(redirectPath)
|
||||
}
|
||||
|
||||
// RepoIDAssignment returns an macaron handler which assigns the repo to the context.
|
||||
func RepoIDAssignment() macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
repoID := ctx.ParamsInt64(":repoid")
|
||||
|
||||
// Get repository.
|
||||
repo, err := models.GetRepositoryByID(repoID)
|
||||
if err != nil {
|
||||
if models.IsErrRepoNotExist(err) {
|
||||
ctx.Handle(404, "GetRepositoryByID", nil)
|
||||
} else {
|
||||
ctx.Handle(500, "GetRepositoryByID", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = repo.GetOwner(); err != nil {
|
||||
ctx.Handle(500, "GetOwner", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Admin has super access.
|
||||
if ctx.IsSigned && ctx.User.IsAdmin {
|
||||
ctx.Repo.AccessMode = models.AccessModeOwner
|
||||
} else {
|
||||
var userID int64
|
||||
if ctx.User != nil {
|
||||
userID = ctx.User.ID
|
||||
}
|
||||
mode, err := models.AccessLevel(userID, repo)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "AccessLevel", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.AccessMode = mode
|
||||
}
|
||||
|
||||
// Check access.
|
||||
if ctx.Repo.AccessMode == models.AccessModeNone {
|
||||
if ctx.Query("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
ctx.Handle(404, "no access right", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["HasAccess"] = true
|
||||
|
||||
if repo.IsMirror {
|
||||
ctx.Repo.Mirror, err = models.GetMirrorByRepoID(repo.ID)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetMirror", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune
|
||||
ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
|
||||
ctx.Data["Mirror"] = ctx.Repo.Mirror
|
||||
}
|
||||
|
||||
ctx.Repo.Repository = repo
|
||||
ctx.Data["RepoName"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare
|
||||
}
|
||||
}
|
||||
|
||||
// RepoAssignment returns a macaron to handle repository assignment
|
||||
func RepoAssignment() macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
|
|
@ -191,7 +262,7 @@ func RepoAssignment() macaron.Handler {
|
|||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
if ctx.Query("go-get") == "1" {
|
||||
earlyResponseForGoGetMeta(ctx)
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
ctx.Handle(404, "GetUserByName", nil)
|
||||
|
|
@ -213,7 +284,7 @@ func RepoAssignment() macaron.Handler {
|
|||
RedirectToRepo(ctx, redirectRepoID)
|
||||
} else if models.IsErrRepoRedirectNotExist(err) {
|
||||
if ctx.Query("go-get") == "1" {
|
||||
earlyResponseForGoGetMeta(ctx)
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
ctx.Handle(404, "GetRepositoryByName", nil)
|
||||
|
|
@ -246,7 +317,7 @@ func RepoAssignment() macaron.Handler {
|
|||
// Check access.
|
||||
if ctx.Repo.AccessMode == models.AccessModeNone {
|
||||
if ctx.Query("go-get") == "1" {
|
||||
earlyResponseForGoGetMeta(ctx)
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
ctx.Handle(404, "no access right", err)
|
||||
|
|
@ -288,6 +359,7 @@ func RepoAssignment() macaron.Handler {
|
|||
|
||||
count, err := models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
|
||||
IncludeDrafts: false,
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetReleaseCountByRepoID", err)
|
||||
|
|
@ -373,7 +445,7 @@ func RepoAssignment() macaron.Handler {
|
|||
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
|
||||
|
||||
if ctx.Query("go-get") == "1" {
|
||||
ctx.Data["GoGetImport"] = composeGoGetImport(owner.Name, repo.Name)
|
||||
ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
|
||||
prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", ctx.Repo.BranchName)
|
||||
ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
|
||||
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/blevesearch/bleve/analysis/token/lowercase"
|
||||
"github.com/blevesearch/bleve/analysis/token/unicodenorm"
|
||||
"github.com/blevesearch/bleve/analysis/tokenizer/unicode"
|
||||
"github.com/blevesearch/bleve/index/upsidedown"
|
||||
)
|
||||
|
||||
// issueIndexer (thread-safe) index for searching issues
|
||||
|
|
@ -39,27 +40,25 @@ const issueIndexerAnalyzer = "issueIndexer"
|
|||
// InitIssueIndexer initialize issue indexer
|
||||
func InitIssueIndexer(populateIndexer func() error) {
|
||||
_, err := os.Stat(setting.Indexer.IssuePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err = createIssueIndexer(); err != nil {
|
||||
log.Fatal(4, "CreateIssuesIndexer: %v", err)
|
||||
}
|
||||
if err = populateIndexer(); err != nil {
|
||||
log.Fatal(4, "PopulateIssuesIndex: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Fatal(4, "InitIssuesIndexer: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Fatal(4, "InitIssueIndexer: %v", err)
|
||||
} else if err == nil {
|
||||
issueIndexer, err = bleve.Open(setting.Indexer.IssuePath)
|
||||
if err != nil {
|
||||
log.Error(4, "Unable to open issues indexer (%s)."+
|
||||
" If the error is due to incompatible versions, try deleting the indexer files;"+
|
||||
" gitea will recreate them with the appropriate version the next time it runs."+
|
||||
" Deleting the indexer files will not result in loss of data.",
|
||||
setting.Indexer.IssuePath)
|
||||
log.Fatal(4, "InitIssuesIndexer, open index: %v", err)
|
||||
if err == nil {
|
||||
return
|
||||
} else if err != upsidedown.IncompatibleVersion {
|
||||
log.Fatal(4, "InitIssueIndexer, open index: %v", err)
|
||||
}
|
||||
log.Warn("Incompatible bleve version, deleting and recreating issue indexer")
|
||||
if err = os.RemoveAll(setting.Indexer.IssuePath); err != nil {
|
||||
log.Fatal(4, "InitIssueIndexer: remove index, %v", err)
|
||||
}
|
||||
}
|
||||
if err = createIssueIndexer(); err != nil {
|
||||
log.Fatal(4, "InitIssuesIndexer: create index, %v", err)
|
||||
}
|
||||
if err = populateIndexer(); err != nil {
|
||||
log.Fatal(4, "InitIssueIndexer: populate index, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,10 +70,7 @@ func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
|
|||
return errHashMismatch
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpPath, path); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return os.Rename(tmpPath, path)
|
||||
}
|
||||
|
||||
// Exists returns true if the object exists in the content store.
|
||||
|
|
|
|||
|
|
@ -110,10 +110,7 @@ func (w *FileLogWriter) StartLogger() error {
|
|||
return err
|
||||
}
|
||||
w.mw.SetFd(fd)
|
||||
if err = w.initFd(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return w.initFd()
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) docheck(size int) {
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ type Message struct {
|
|||
}
|
||||
|
||||
// NewMessageFrom creates new mail message object with custom From header.
|
||||
func NewMessageFrom(to []string, from, subject, body string) *Message {
|
||||
func NewMessageFrom(to []string, fromDisplayName, fromAddress, subject, body string) *Message {
|
||||
log.Trace("NewMessageFrom (body):\n%s", body)
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
msg.SetHeader("From", from)
|
||||
msg.SetAddressHeader("From", fromAddress, fromDisplayName)
|
||||
msg.SetHeader("To", to...)
|
||||
msg.SetHeader("Subject", subject)
|
||||
msg.SetDateHeader("Date", time.Now())
|
||||
|
|
@ -58,7 +58,7 @@ func NewMessageFrom(to []string, from, subject, body string) *Message {
|
|||
|
||||
// NewMessage creates new mail message object with default From header.
|
||||
func NewMessage(to []string, subject, body string) *Message {
|
||||
return NewMessageFrom(to, setting.MailService.From, subject, body)
|
||||
return NewMessageFrom(to, setting.MailService.FromName, setting.MailService.FromEmail, subject, body)
|
||||
}
|
||||
|
||||
type loginAuth struct {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "code.gitea.io/gitea/modules/markdown"
|
||||
. "code.gitea.io/gitea/modules/markup"
|
||||
_ "code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ import (
|
|||
// Renderer is a extended version of underlying render object.
|
||||
type Renderer struct {
|
||||
blackfriday.Renderer
|
||||
urlPrefix string
|
||||
isWikiMarkdown bool
|
||||
URLPrefix string
|
||||
IsWiki bool
|
||||
}
|
||||
|
||||
// Link defines how formal links should be processed to produce corresponding HTML elements.
|
||||
|
|
@ -26,10 +26,10 @@ func (r *Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []
|
|||
if len(link) > 0 && !markup.IsLink(link) {
|
||||
if link[0] != '#' {
|
||||
lnk := string(link)
|
||||
if r.isWikiMarkdown {
|
||||
if r.IsWiki {
|
||||
lnk = markup.URLJoin("wiki", lnk)
|
||||
}
|
||||
mLink := markup.URLJoin(r.urlPrefix, lnk)
|
||||
mLink := markup.URLJoin(r.URLPrefix, lnk)
|
||||
link = []byte(mLink)
|
||||
}
|
||||
}
|
||||
|
|
@ -95,8 +95,8 @@ var (
|
|||
|
||||
// Image defines how images should be processed to produce corresponding HTML elements.
|
||||
func (r *Renderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||
prefix := r.urlPrefix
|
||||
if r.isWikiMarkdown {
|
||||
prefix := r.URLPrefix
|
||||
if r.IsWiki {
|
||||
prefix = markup.URLJoin(prefix, "wiki", "src")
|
||||
}
|
||||
prefix = strings.Replace(prefix, "/src/", "/raw/", 1)
|
||||
|
|
@ -129,9 +129,9 @@ func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
|
|||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
renderer := &Renderer{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
urlPrefix: urlPrefix,
|
||||
isWikiMarkdown: wikiMarkdown,
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
URLPrefix: urlPrefix,
|
||||
IsWiki: wikiMarkdown,
|
||||
}
|
||||
|
||||
// set up the parser
|
||||
|
|
@ -5,13 +5,11 @@
|
|||
package markdown_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "code.gitea.io/gitea/modules/markdown"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
. "code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -21,45 +19,6 @@ const AppURL = "http://localhost:3000/"
|
|||
const Repo = "gogits/gogs"
|
||||
const AppSubURL = AppURL + Repo + "/"
|
||||
|
||||
var numericMetas = map[string]string{
|
||||
"format": "https://someurl.com/{user}/{repo}/{index}",
|
||||
"user": "someUser",
|
||||
"repo": "someRepo",
|
||||
"style": markup.IssueNameStyleNumeric,
|
||||
}
|
||||
|
||||
var alphanumericMetas = map[string]string{
|
||||
"format": "https://someurl.com/{user}/{repo}/{index}",
|
||||
"user": "someUser",
|
||||
"repo": "someRepo",
|
||||
"style": markup.IssueNameStyleAlphanumeric,
|
||||
}
|
||||
|
||||
// numericLink an HTML to a numeric-style issue
|
||||
func numericIssueLink(baseURL string, index int) string {
|
||||
return link(markup.URLJoin(baseURL, strconv.Itoa(index)), fmt.Sprintf("#%d", index))
|
||||
}
|
||||
|
||||
// alphanumLink an HTML link to an alphanumeric-style issue
|
||||
func alphanumIssueLink(baseURL string, name string) string {
|
||||
return link(markup.URLJoin(baseURL, name), name)
|
||||
}
|
||||
|
||||
// urlContentsLink an HTML link whose contents is the target URL
|
||||
func urlContentsLink(href string) string {
|
||||
return link(href, href)
|
||||
}
|
||||
|
||||
// link an HTML link
|
||||
func link(href, contents string) string {
|
||||
return fmt.Sprintf("<a href=\"%s\">%s</a>", href, contents)
|
||||
}
|
||||
|
||||
func testRenderIssueIndexPattern(t *testing.T, input, expected string, metas map[string]string) {
|
||||
assert.Equal(t, expected,
|
||||
string(markup.RenderIssueIndexPattern([]byte(input), AppSubURL, metas)))
|
||||
}
|
||||
|
||||
func TestRender_StandardLinks(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
|
@ -7,8 +7,8 @@ package markup_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
_ "code.gitea.io/gitea/modules/markdown"
|
||||
. "code.gitea.io/gitea/modules/markup"
|
||||
_ "code.gitea.io/gitea/modules/markup/markdown"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
|
|||
56
modules/markup/orgmode/orgmode.go
Normal file
56
modules/markup/orgmode/orgmode.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// 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 markup
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
|
||||
"github.com/chaseadamsio/goorgeous"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
func init() {
|
||||
markup.RegisterParser(Parser{})
|
||||
}
|
||||
|
||||
// Parser implements markup.Parser for orgmode
|
||||
type Parser struct {
|
||||
}
|
||||
|
||||
// Name implements markup.Parser
|
||||
func (Parser) Name() string {
|
||||
return "orgmode"
|
||||
}
|
||||
|
||||
// Extensions implements markup.Parser
|
||||
func (Parser) Extensions() []string {
|
||||
return []string{".org"}
|
||||
}
|
||||
|
||||
// Render renders orgmode rawbytes to HTML
|
||||
func Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
|
||||
htmlFlags := blackfriday.HTML_USE_XHTML
|
||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
renderer := &markdown.Renderer{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
URLPrefix: urlPrefix,
|
||||
IsWiki: isWiki,
|
||||
}
|
||||
|
||||
result := goorgeous.Org(rawBytes, renderer)
|
||||
return result
|
||||
}
|
||||
|
||||
// RenderString reners orgmode string to HTML string
|
||||
func RenderString(rawContent string, urlPrefix string, metas map[string]string, isWiki bool) string {
|
||||
return string(Render([]byte(rawContent), urlPrefix, metas, isWiki))
|
||||
}
|
||||
|
||||
// Render implements markup.Parser
|
||||
func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
|
||||
return Render(rawBytes, urlPrefix, metas, isWiki)
|
||||
}
|
||||
54
modules/markup/orgmode/orgmode_test.go
Normal file
54
modules/markup/orgmode/orgmode_test.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// 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 markup
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const AppURL = "http://localhost:3000/"
|
||||
const Repo = "gogits/gogs"
|
||||
const AppSubURL = AppURL + Repo + "/"
|
||||
|
||||
func TestRender_StandardLinks(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer := RenderString(input, setting.AppSubURL, nil, false)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
}
|
||||
|
||||
googleRendered := `<p><a href="https://google.com/" title="https://google.com/">https://google.com/</a></p>`
|
||||
test("[[https://google.com/]]", googleRendered)
|
||||
|
||||
lnk := markup.URLJoin(AppSubURL, "WikiPage")
|
||||
test("[[WikiPage][WikiPage]]",
|
||||
`<p><a href="`+lnk+`" title="WikiPage">WikiPage</a></p>`)
|
||||
}
|
||||
|
||||
func TestRender_Images(t *testing.T) {
|
||||
setting.AppURL = AppURL
|
||||
setting.AppSubURL = AppSubURL
|
||||
|
||||
test := func(input, expected string) {
|
||||
buffer := RenderString(input, setting.AppSubURL, nil, false)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
|
||||
}
|
||||
|
||||
url := "../../.images/src/02/train.jpg"
|
||||
title := "Train"
|
||||
result := markup.URLJoin(AppSubURL, url)
|
||||
|
||||
test(
|
||||
"[[file:"+url+"]["+title+"]]",
|
||||
`<p><a href="`+result+`"><img src="`+result+`" alt="`+title+`" title="`+title+`" /></a></p>`)
|
||||
}
|
||||
|
|
@ -1287,6 +1287,7 @@ type Mailer struct {
|
|||
QueueLength int
|
||||
Name string
|
||||
From string
|
||||
FromName string
|
||||
FromEmail string
|
||||
SendAsPlainText bool
|
||||
|
||||
|
|
@ -1345,6 +1346,7 @@ func newMailService() {
|
|||
if err != nil {
|
||||
log.Fatal(4, "Invalid mailer.FROM (%s): %v", MailService.From, err)
|
||||
}
|
||||
MailService.FromName = parsed.Name
|
||||
MailService.FromEmail = parsed.Address
|
||||
|
||||
log.Info("Mail Service Enabled")
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ func RenderCommitMessage(full bool, msg, urlPrefix string, metas map[string]stri
|
|||
|
||||
// Actioner describes an action
|
||||
type Actioner interface {
|
||||
GetOpType() int
|
||||
GetOpType() models.ActionType
|
||||
GetActUserName() string
|
||||
GetRepoUserName() string
|
||||
GetRepoName() string
|
||||
|
|
@ -289,25 +289,24 @@ type Actioner interface {
|
|||
GetIssueInfos() []string
|
||||
}
|
||||
|
||||
// ActionIcon accepts a int that represents action operation type
|
||||
// and returns a icon class name.
|
||||
func ActionIcon(opType int) string {
|
||||
// ActionIcon accepts an action operation type and returns an icon class name.
|
||||
func ActionIcon(opType models.ActionType) string {
|
||||
switch opType {
|
||||
case 1, 8: // Create and transfer repository
|
||||
case models.ActionCreateRepo, models.ActionTransferRepo:
|
||||
return "repo"
|
||||
case 5, 9: // Commit repository
|
||||
case models.ActionCommitRepo, models.ActionPushTag, models.ActionDeleteTag, models.ActionDeleteBranch:
|
||||
return "git-commit"
|
||||
case 6: // Create issue
|
||||
case models.ActionCreateIssue:
|
||||
return "issue-opened"
|
||||
case 7: // New pull request
|
||||
case models.ActionCreatePullRequest:
|
||||
return "git-pull-request"
|
||||
case 10: // Comment issue
|
||||
case models.ActionCommentIssue:
|
||||
return "comment-discussion"
|
||||
case 11: // Merge pull request
|
||||
case models.ActionMergePullRequest:
|
||||
return "git-merge"
|
||||
case 12, 14: // Close issue or pull request
|
||||
case models.ActionCloseIssue, models.ActionClosePullRequest:
|
||||
return "issue-closed"
|
||||
case 13, 15: // Reopen issue or pull request
|
||||
case models.ActionReopenIssue, models.ActionReopenPullRequest:
|
||||
return "issue-reopened"
|
||||
default:
|
||||
return "invalid type"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ notifications = Notifications
|
|||
create_new = Create...
|
||||
user_profile_and_more = User profile and more
|
||||
signed_in_as = Signed in as
|
||||
enable_javascript = This website works better with JavaScript
|
||||
|
||||
username = Username
|
||||
email = Email
|
||||
|
|
@ -1472,6 +1473,8 @@ comment_issue = `commented on issue <a href="%s/issues/%s">%s#%[2]s</a>`
|
|||
merge_pull_request = `merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
transfer_repo = transferred repository <code>%s</code> to <a href="%s">%s</a>
|
||||
push_tag = pushed tag <a href="%s/src/%s">%[2]s</a> to <a href="%[1]s">%[3]s</a>
|
||||
delete_tag = deleted tag %[2]s from <a href="%[1]s">%[3]s</a>
|
||||
delete_branch = deleted branch %[2]s from <a href="%[1]s">%[3]s</a>
|
||||
compare_commits = Compare %d commits
|
||||
|
||||
[tool]
|
||||
|
|
|
|||
6
package.json
Normal file
6
package.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"less": "^2.7.2"
|
||||
}
|
||||
}
|
||||
3132
public/css/index.css
3132
public/css/index.css
File diff suppressed because one or more lines are too long
|
|
@ -1579,6 +1579,7 @@ $(document).ready(function () {
|
|||
initCodeView();
|
||||
initVueApp();
|
||||
initTeamSettings();
|
||||
initCtrlEnterSubmit();
|
||||
|
||||
// Repo clone url.
|
||||
if ($('#repo-clone-url').length > 0) {
|
||||
|
|
@ -1786,6 +1787,14 @@ function initVueComponents(){
|
|||
})
|
||||
}
|
||||
|
||||
function initCtrlEnterSubmit() {
|
||||
$(".js-quick-submit").keydown(function(e) {
|
||||
if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.keyCode == 13 || e.keyCode == 10)) {
|
||||
$(this).closest("form").submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initVueApp() {
|
||||
var el = document.getElementById('app');
|
||||
if (!el) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import (
|
|||
api "code.gitea.io/sdk/gitea"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/markdown"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
api "code.gitea.io/sdk/gitea"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
|
|
@ -36,6 +34,7 @@ func GetRelease(ctx *context.APIContext) {
|
|||
func ListReleases(ctx *context.APIContext) {
|
||||
releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
|
||||
IncludeDrafts: ctx.Repo.AccessMode >= models.AccessModeWrite,
|
||||
IncludeTags: false,
|
||||
}, 1, 2147483647)
|
||||
if err != nil {
|
||||
ctx.Error(500, "GetReleasesByRepoID", err)
|
||||
|
|
@ -62,43 +61,49 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) {
|
|||
ctx.Status(404)
|
||||
return
|
||||
}
|
||||
tag, err := ctx.Repo.GitRepo.GetTag(form.TagName)
|
||||
rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName)
|
||||
if err != nil {
|
||||
ctx.Error(500, "GetTag", err)
|
||||
return
|
||||
}
|
||||
commit, err := tag.Commit()
|
||||
if err != nil {
|
||||
ctx.Error(500, "Commit", err)
|
||||
return
|
||||
}
|
||||
commitsCount, err := commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Error(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
rel := &models.Release{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
PublisherID: ctx.User.ID,
|
||||
Publisher: ctx.User,
|
||||
TagName: form.TagName,
|
||||
LowerTagName: strings.ToLower(form.TagName),
|
||||
Target: form.Target,
|
||||
Title: form.Title,
|
||||
Sha1: commit.ID.String(),
|
||||
NumCommits: commitsCount,
|
||||
Note: form.Note,
|
||||
IsDraft: form.IsDraft,
|
||||
IsPrerelease: form.IsPrerelease,
|
||||
CreatedUnix: commit.Author.When.Unix(),
|
||||
}
|
||||
if err := models.CreateRelease(ctx.Repo.GitRepo, rel, nil); err != nil {
|
||||
if models.IsErrReleaseAlreadyExist(err) {
|
||||
ctx.Status(409)
|
||||
} else {
|
||||
ctx.Error(500, "CreateRelease", err)
|
||||
if !models.IsErrReleaseNotExist(err) {
|
||||
ctx.Handle(500, "GetRelease", err)
|
||||
return
|
||||
}
|
||||
rel = &models.Release{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
PublisherID: ctx.User.ID,
|
||||
Publisher: ctx.User,
|
||||
TagName: form.TagName,
|
||||
Target: form.Target,
|
||||
Title: form.Title,
|
||||
Note: form.Note,
|
||||
IsDraft: form.IsDraft,
|
||||
IsPrerelease: form.IsPrerelease,
|
||||
IsTag: false,
|
||||
}
|
||||
if err := models.CreateRelease(ctx.Repo.GitRepo, rel, nil); err != nil {
|
||||
if models.IsErrReleaseAlreadyExist(err) {
|
||||
ctx.Status(409)
|
||||
} else {
|
||||
ctx.Error(500, "CreateRelease", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !rel.IsTag {
|
||||
ctx.Status(409)
|
||||
return
|
||||
}
|
||||
|
||||
rel.Title = form.Title
|
||||
rel.Note = form.Note
|
||||
rel.IsDraft = form.IsDraft
|
||||
rel.IsPrerelease = form.IsPrerelease
|
||||
rel.PublisherID = ctx.User.ID
|
||||
rel.IsTag = false
|
||||
|
||||
if err = models.UpdateRelease(ctx.Repo.GitRepo, rel, nil); err != nil {
|
||||
ctx.Handle(500, "UpdateRelease", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.JSON(201, rel.APIFormat())
|
||||
}
|
||||
|
|
@ -111,11 +116,12 @@ func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) {
|
|||
}
|
||||
id := ctx.ParamsInt64(":id")
|
||||
rel, err := models.GetReleaseByID(id)
|
||||
if err != nil {
|
||||
if err != nil && !models.IsErrReleaseNotExist(err) {
|
||||
ctx.Error(500, "GetReleaseByID", err)
|
||||
return
|
||||
}
|
||||
if rel.RepoID != ctx.Repo.Repository.ID {
|
||||
if err != nil && models.IsErrReleaseNotExist(err) ||
|
||||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.Status(404)
|
||||
return
|
||||
}
|
||||
|
|
@ -162,12 +168,13 @@ func DeleteRelease(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
id := ctx.ParamsInt64(":id")
|
||||
release, err := models.GetReleaseByID(id)
|
||||
if err != nil {
|
||||
rel, err := models.GetReleaseByID(id)
|
||||
if err != nil && !models.IsErrReleaseNotExist(err) {
|
||||
ctx.Error(500, "GetReleaseByID", err)
|
||||
return
|
||||
}
|
||||
if release.RepoID != ctx.Repo.Repository.ID {
|
||||
if err != nil && models.IsErrReleaseNotExist(err) ||
|
||||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.Status(404)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,27 +86,27 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
|||
repos []*models.Repository
|
||||
count int64
|
||||
err error
|
||||
orderBy string
|
||||
orderBy models.SearchOrderBy
|
||||
)
|
||||
ctx.Data["SortType"] = ctx.Query("sort")
|
||||
|
||||
switch ctx.Query("sort") {
|
||||
case "oldest":
|
||||
orderBy = "created_unix ASC"
|
||||
orderBy = models.SearchOrderByOldest
|
||||
case "recentupdate":
|
||||
orderBy = "updated_unix DESC"
|
||||
orderBy = models.SearchOrderByRecentUpdated
|
||||
case "leastupdate":
|
||||
orderBy = "updated_unix ASC"
|
||||
orderBy = models.SearchOrderByLeastUpdated
|
||||
case "reversealphabetically":
|
||||
orderBy = "name DESC"
|
||||
orderBy = models.SearchOrderByAlphabeticallyReverse
|
||||
case "alphabetically":
|
||||
orderBy = "name ASC"
|
||||
orderBy = models.SearchOrderByAlphabetically
|
||||
case "reversesize":
|
||||
orderBy = "size DESC"
|
||||
orderBy = models.SearchOrderBySizeReverse
|
||||
case "size":
|
||||
orderBy = "size ASC"
|
||||
orderBy = models.SearchOrderBySize
|
||||
default:
|
||||
orderBy = "created_unix DESC"
|
||||
orderBy = models.SearchOrderByNewest
|
||||
}
|
||||
|
||||
keyword := strings.Trim(ctx.Query("q"), " ")
|
||||
|
|
|
|||
|
|
@ -22,35 +22,15 @@ import (
|
|||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
func composeGoGetImport(owner, repo, sub string) string {
|
||||
return path.Join(setting.Domain, setting.AppSubURL, owner, repo, sub)
|
||||
}
|
||||
|
||||
// earlyResponseForGoGetMeta responses appropriate go-get meta with status 200
|
||||
// if user does not have actual access to the requested repository,
|
||||
// or the owner or repository does not exist at all.
|
||||
// This is particular a workaround for "go get" command which does not respect
|
||||
// .netrc file.
|
||||
func earlyResponseForGoGetMeta(ctx *context.Context, username, reponame, subpath string) {
|
||||
ctx.PlainText(200, []byte(com.Expand(`<meta name="go-import" content="{GoGetImport} git {CloneLink}">`,
|
||||
map[string]string{
|
||||
"GoGetImport": composeGoGetImport(username, reponame, subpath),
|
||||
"CloneLink": models.ComposeHTTPSCloneURL(username, reponame),
|
||||
})))
|
||||
}
|
||||
|
||||
// HTTP implmentation git smart HTTP protocol
|
||||
func HTTP(ctx *context.Context) {
|
||||
username := ctx.Params(":username")
|
||||
reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
|
||||
subpath := ctx.Params("*")
|
||||
|
||||
if ctx.Query("go-get") == "1" {
|
||||
earlyResponseForGoGetMeta(ctx, username, reponame, subpath)
|
||||
context.EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/indexer"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markdown"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/notification"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markdown"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/Unknwon/paginater"
|
||||
|
|
@ -67,6 +67,7 @@ func Releases(ctx *context.Context) {
|
|||
|
||||
opts := models.FindReleasesOptions{
|
||||
IncludeDrafts: ctx.Repo.IsWriter(),
|
||||
IncludeTags: true,
|
||||
}
|
||||
|
||||
releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, opts, page, limit)
|
||||
|
|
@ -145,57 +146,61 @@ func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) {
|
|||
return
|
||||
}
|
||||
|
||||
var tagCreatedUnix int64
|
||||
tag, err := ctx.Repo.GitRepo.GetTag(form.TagName)
|
||||
if err == nil {
|
||||
commit, err := tag.Commit()
|
||||
if err == nil {
|
||||
tagCreatedUnix = commit.Author.When.Unix()
|
||||
}
|
||||
}
|
||||
|
||||
commit, err := ctx.Repo.GitRepo.GetBranchCommit(form.Target)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := commit.CommitsCount()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "CommitsCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
rel := &models.Release{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
PublisherID: ctx.User.ID,
|
||||
Title: form.Title,
|
||||
TagName: form.TagName,
|
||||
Target: form.Target,
|
||||
Sha1: commit.ID.String(),
|
||||
NumCommits: commitsCount,
|
||||
Note: form.Content,
|
||||
IsDraft: len(form.Draft) > 0,
|
||||
IsPrerelease: form.Prerelease,
|
||||
CreatedUnix: tagCreatedUnix,
|
||||
}
|
||||
|
||||
var attachmentUUIDs []string
|
||||
if setting.AttachmentEnabled {
|
||||
attachmentUUIDs = form.Files
|
||||
}
|
||||
|
||||
if err = models.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
|
||||
ctx.Data["Err_TagName"] = true
|
||||
switch {
|
||||
case models.IsErrReleaseAlreadyExist(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
|
||||
case models.IsErrInvalidTagName(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
|
||||
default:
|
||||
ctx.Handle(500, "CreateRelease", err)
|
||||
rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName)
|
||||
if err != nil {
|
||||
if !models.IsErrReleaseNotExist(err) {
|
||||
ctx.Handle(500, "GetRelease", err)
|
||||
return
|
||||
}
|
||||
|
||||
rel := &models.Release{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
PublisherID: ctx.User.ID,
|
||||
Title: form.Title,
|
||||
TagName: form.TagName,
|
||||
Target: form.Target,
|
||||
Note: form.Content,
|
||||
IsDraft: len(form.Draft) > 0,
|
||||
IsPrerelease: form.Prerelease,
|
||||
IsTag: false,
|
||||
}
|
||||
|
||||
if err = models.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
|
||||
ctx.Data["Err_TagName"] = true
|
||||
switch {
|
||||
case models.IsErrReleaseAlreadyExist(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
|
||||
case models.IsErrInvalidTagName(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form)
|
||||
default:
|
||||
ctx.Handle(500, "CreateRelease", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !rel.IsTag {
|
||||
ctx.Data["Err_TagName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form)
|
||||
return
|
||||
}
|
||||
|
||||
rel.Title = form.Title
|
||||
rel.Note = form.Content
|
||||
rel.IsDraft = len(form.Draft) > 0
|
||||
rel.IsPrerelease = form.Prerelease
|
||||
rel.PublisherID = ctx.User.ID
|
||||
rel.IsTag = false
|
||||
|
||||
if err = models.UpdateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs); err != nil {
|
||||
ctx.Data["Err_TagName"] = true
|
||||
ctx.Handle(500, "UpdateRelease", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Release created: %s/%s:%s", ctx.User.LowerName, ctx.Repo.Repository.Name, form.TagName)
|
||||
|
||||
|
|
@ -246,6 +251,10 @@ func EditReleasePost(ctx *context.Context, form auth.EditReleaseForm) {
|
|||
}
|
||||
return
|
||||
}
|
||||
if rel.IsTag {
|
||||
ctx.Handle(404, "GetRelease", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["tag_name"] = rel.TagName
|
||||
ctx.Data["tag_target"] = rel.Target
|
||||
ctx.Data["title"] = rel.Title
|
||||
|
|
@ -275,8 +284,7 @@ func EditReleasePost(ctx *context.Context, form auth.EditReleaseForm) {
|
|||
|
||||
// DeleteRelease delete a release
|
||||
func DeleteRelease(ctx *context.Context) {
|
||||
delTag := ctx.QueryBool("delTag")
|
||||
if err := models.DeleteReleaseByID(ctx.QueryInt64("id"), ctx.User, delTag); err != nil {
|
||||
if err := models.DeleteReleaseByID(ctx.QueryInt64("id"), ctx.User, true); err != nil {
|
||||
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.release.deletion_success"))
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
|||
ctx.Handle(500, "ListEntries", err)
|
||||
return
|
||||
}
|
||||
entries.Sort()
|
||||
entries.CustomSort(base.NaturalSortLess)
|
||||
|
||||
ctx.Data["Files"], err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
|
|
@ -95,11 +95,11 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
|||
buf = append(buf, d...)
|
||||
newbuf := markup.Render(readmeFile.Name(), buf, treeLink, ctx.Repo.Repository.ComposeMetas())
|
||||
if newbuf != nil {
|
||||
ctx.Data["IsMarkdown"] = true
|
||||
ctx.Data["IsMarkup"] = true
|
||||
} else {
|
||||
// FIXME This is the only way to show non-markdown files
|
||||
// instead of a broken "View Raw" link
|
||||
ctx.Data["IsMarkdown"] = true
|
||||
ctx.Data["IsMarkup"] = false
|
||||
newbuf = bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)
|
||||
}
|
||||
ctx.Data["FileContent"] = string(newbuf)
|
||||
|
|
@ -197,13 +197,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
|||
|
||||
tp := markup.Type(blob.Name())
|
||||
isSupportedMarkup := tp != ""
|
||||
// FIXME: currently set IsMarkdown for compatible
|
||||
ctx.Data["IsMarkdown"] = isSupportedMarkup
|
||||
|
||||
readmeExist := isSupportedMarkup || markup.IsReadmeFile(blob.Name())
|
||||
ctx.Data["IsMarkup"] = isSupportedMarkup
|
||||
readmeExist := markup.IsReadmeFile(blob.Name())
|
||||
ctx.Data["ReadmeExist"] = readmeExist
|
||||
if readmeExist && isSupportedMarkup {
|
||||
if isSupportedMarkup {
|
||||
ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
|
||||
} else if readmeExist {
|
||||
ctx.Data["FileContent"] = string(bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1))
|
||||
} else {
|
||||
// Building code view blocks with line number on server side.
|
||||
var fileContent string
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ import (
|
|||
"code.gitea.io/gitea/modules/auth"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/markdown"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -419,8 +419,10 @@ func RegisterRoutes(m *macaron.Macaron) {
|
|||
m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
|
||||
m.Get("/migrate", repo.Migrate)
|
||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
|
||||
m.Combo("/fork/:repoid").Get(repo.Fork).
|
||||
Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
|
||||
m.Group("/fork", func() {
|
||||
m.Combo("/:repoid").Get(repo.Fork).
|
||||
Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
|
||||
}, context.RepoIDAssignment(), context.UnitTypes(), context.LoadRepoUnits(), context.CheckUnit(models.UnitTypeCode))
|
||||
}, reqSignIn)
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
|
|
|
|||
|
|
@ -107,26 +107,26 @@ func Profile(ctx *context.Context) {
|
|||
var (
|
||||
repos []*models.Repository
|
||||
count int64
|
||||
orderBy string
|
||||
orderBy models.SearchOrderBy
|
||||
)
|
||||
|
||||
ctx.Data["SortType"] = ctx.Query("sort")
|
||||
switch ctx.Query("sort") {
|
||||
case "newest":
|
||||
orderBy = "created_unix DESC"
|
||||
orderBy = models.SearchOrderByNewest
|
||||
case "oldest":
|
||||
orderBy = "created_unix ASC"
|
||||
orderBy = models.SearchOrderByOldest
|
||||
case "recentupdate":
|
||||
orderBy = "updated_unix DESC"
|
||||
orderBy = models.SearchOrderByRecentUpdated
|
||||
case "leastupdate":
|
||||
orderBy = "updated_unix ASC"
|
||||
orderBy = models.SearchOrderByLeastUpdated
|
||||
case "reversealphabetically":
|
||||
orderBy = "name DESC"
|
||||
orderBy = models.SearchOrderByAlphabeticallyReverse
|
||||
case "alphabetically":
|
||||
orderBy = "name ASC"
|
||||
orderBy = models.SearchOrderByAlphabetically
|
||||
default:
|
||||
ctx.Data["SortType"] = "recentupdate"
|
||||
orderBy = "updated_unix DESC"
|
||||
orderBy = models.SearchOrderByNewest
|
||||
}
|
||||
|
||||
// set default sort value if sort is empty.
|
||||
|
|
@ -149,7 +149,7 @@ func Profile(ctx *context.Context) {
|
|||
case "stars":
|
||||
ctx.Data["PageIsProfileStarList"] = true
|
||||
if len(keyword) == 0 {
|
||||
repos, err = ctxUser.GetStarredRepos(showPrivate, page, setting.UI.User.RepoPagingNum, orderBy)
|
||||
repos, err = ctxUser.GetStarredRepos(showPrivate, page, setting.UI.User.RepoPagingNum, orderBy.String())
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetStarredRepos", err)
|
||||
return
|
||||
|
|
@ -182,7 +182,7 @@ func Profile(ctx *context.Context) {
|
|||
default:
|
||||
if len(keyword) == 0 {
|
||||
var total int
|
||||
repos, err = models.GetUserRepositories(ctxUser.ID, showPrivate, page, setting.UI.User.RepoPagingNum, orderBy)
|
||||
repos, err = models.GetUserRepositories(ctxUser.ID, showPrivate, page, setting.UI.User.RepoPagingNum, orderBy.String())
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetRepositories", err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@
|
|||
<!-- Stylesheet -->
|
||||
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/semantic/semantic.min.css">
|
||||
<link rel="stylesheet" href="{{AppSubUrl}}/css/index.css?v={{MD5 AppVer}}">
|
||||
<noscript>
|
||||
<style>
|
||||
.dropdown:hover > .menu { display: block; }
|
||||
.ui.secondary.menu .dropdown.item > .menu { margin-top: 0; }
|
||||
</style>
|
||||
</noscript>
|
||||
|
||||
{{if .RequireHighlightJS}}
|
||||
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/highlight/github.css">
|
||||
|
|
@ -118,7 +124,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="full height">
|
||||
<noscript>Please enable JavaScript in your browser!</noscript>
|
||||
<noscript>{{.i18n.Tr "enable_javascript"}}</noscript>
|
||||
|
||||
{{if not .PageIsInstall}}
|
||||
<div class="following bar light">
|
||||
|
|
@ -204,7 +210,7 @@
|
|||
<i class="octicon octicon-settings"></i>
|
||||
{{.i18n.Tr "your_settings"}}<!-- Your settings -->
|
||||
</a>
|
||||
<a class="item" target="_blank" rel="noopener" href="https://docs.gitea.io" rel="noreferrer">
|
||||
<a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io">
|
||||
<i class="octicon octicon-question"></i>
|
||||
{{.i18n.Tr "help"}}<!-- Help -->
|
||||
</a>
|
||||
|
|
@ -228,7 +234,7 @@
|
|||
|
||||
{{else}}
|
||||
|
||||
<a class="item" target="_blank" rel="noopener" href="https://docs.gitea.io" rel="noreferrer">{{.i18n.Tr "help"}}</a>
|
||||
<a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io">{{.i18n.Tr "help"}}</a>
|
||||
<div class="right menu">
|
||||
{{if .ShowRegistrationButton}}
|
||||
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<a class="item" data-tab="preview" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.RepoLink}}">{{.i18n.Tr "repo.release.preview"}}</a>
|
||||
</div>
|
||||
<div class="ui bottom attached active tab segment" data-tab="write">
|
||||
<textarea id="content" class="edit_area" name="content" tabindex="4" data-id="issue-{{.RepoName}}" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.Repo.RepoLink}}">
|
||||
<textarea id="content" class="edit_area js-quick-submit" name="content" tabindex="4" data-id="issue-{{.RepoName}}" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.Repo.RepoLink}}">
|
||||
{{if .IssueTemplate}}{{.IssueTemplate}}{{else if .PullRequestTemplate}}{{.PullRequestTemplate}}{{else}}{{.content}}{{end}}</textarea>
|
||||
</div>
|
||||
<div class="ui bottom attached tab segment markdown" data-tab="preview">
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
{{range .Releases}}
|
||||
<li class="ui grid">
|
||||
<div class="ui four wide column meta">
|
||||
{{if .PublisherID}}
|
||||
{{if .IsTag}}
|
||||
{{if .Created}}<span class="time">{{TimeSince .Created $.Lang}}</span>{{end}}
|
||||
{{else}}
|
||||
{{if .IsDraft}}
|
||||
<span class="ui yellow label">{{$.i18n.Tr "repo.release.draft"}}</span>
|
||||
{{else if .IsPrerelease}}
|
||||
|
|
@ -28,13 +30,22 @@
|
|||
<span class="tag text blue">
|
||||
<a href="{{$.RepoLink}}/src/{{.TagName}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a>
|
||||
</span>
|
||||
<span class="commit">
|
||||
<a href="{{$.RepoLink}}/src/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a>
|
||||
</span>
|
||||
{{end}}
|
||||
<span class="commit">
|
||||
<a href="{{$.RepoLink}}/src/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="ui twelve wide column detail">
|
||||
{{if .PublisherID}}
|
||||
{{if .IsTag}}
|
||||
<h4>
|
||||
<a href="{{$.RepoLink}}/src/{{.TagName}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a>
|
||||
</h4>
|
||||
<div class="download">
|
||||
<a href="{{$.RepoLink}}/src/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a>
|
||||
<a href="{{$.RepoLink}}/archive/{{.TagName}}.zip" rel="nofollow"><i class="octicon octicon-file-zip"></i> ZIP</a>
|
||||
<a href="{{$.RepoLink}}/archive/{{.TagName}}.tar.gz"><i class="octicon octicon-file-zip"></i> TAR.GZ</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<h3>
|
||||
<a href="{{$.RepoLink}}/src/{{.TagName}}">{{.Title}}</a>
|
||||
{{if $.IsRepositoryWriter}}<small>(<a href="{{$.RepoLink}}/releases/edit/{{.TagName}}" rel="nofollow">{{$.i18n.Tr "repo.release.edit"}}</a>)</small>{{end}}
|
||||
|
|
@ -70,14 +81,6 @@
|
|||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
{{else}}
|
||||
<h4>
|
||||
<a href="{{$.RepoLink}}/src/{{.TagName}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a>
|
||||
</h4>
|
||||
<div class="download">
|
||||
<a href="{{$.RepoLink}}/archive/{{.TagName}}.zip" rel="nofollow"><i class="octicon octicon-file-zip"></i> ZIP</a>
|
||||
<a href="{{$.RepoLink}}/archive/{{.TagName}}.tar.gz"><i class="octicon octicon-file-zip"></i> TAR.GZ</a>
|
||||
</div>
|
||||
{{end}}
|
||||
<span class="dot"> </span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@
|
|||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached table segment">
|
||||
<div class="file-view {{if .IsMarkdown}}markdown{{else if .IsTextFile}}code-view{{end}} has-emoji">
|
||||
{{if .IsMarkdown}}
|
||||
<div class="file-view {{if .IsMarkup}}markdown{{else if .IsTextFile}}code-view{{end}} has-emoji">
|
||||
{{if .IsMarkup}}
|
||||
{{if .FileContent}}{{.FileContent | Str2html}}{{end}}
|
||||
{{else if not .IsTextFile}}
|
||||
<div class="view-raw ui center">
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@
|
|||
{{else if eq .GetOpType 15}}
|
||||
{{ $index := index .GetIssueInfos 0}}
|
||||
{{$.i18n.Tr "action.reopen_pull_request" .GetRepoLink $index .ShortRepoPath | Str2html}}
|
||||
{{else if eq .GetOpType 16}}
|
||||
{{ $index := index .GetIssueInfos 0}}
|
||||
{{$.i18n.Tr "action.delete_tag" .GetRepoLink .GetBranch .ShortRepoPath | Str2html}}
|
||||
{{else if eq .GetOpType 17}}
|
||||
{{ $index := index .GetIssueInfos 0}}
|
||||
{{$.i18n.Tr "action.delete_branch" .GetRepoLink .GetBranch .ShortRepoPath | Str2html}}
|
||||
{{end}}
|
||||
</p>
|
||||
{{if eq .GetOpType 5}}
|
||||
|
|
|
|||
40
vendor/code.gitea.io/git/tree_entry.go
generated
vendored
40
vendor/code.gitea.io/git/tree_entry.go
generated
vendored
|
|
@ -116,35 +116,51 @@ func (te *TreeEntry) GetSubJumpablePathName() string {
|
|||
// Entries a list of entry
|
||||
type Entries []*TreeEntry
|
||||
|
||||
var sorter = []func(t1, t2 *TreeEntry) bool{
|
||||
func(t1, t2 *TreeEntry) bool {
|
||||
type customSortableEntries struct {
|
||||
Comparer func(s1, s2 string) bool
|
||||
Entries
|
||||
}
|
||||
|
||||
var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{
|
||||
func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
|
||||
return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
|
||||
},
|
||||
func(t1, t2 *TreeEntry) bool {
|
||||
return t1.name < t2.name
|
||||
func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool {
|
||||
return cmp(t1.name, t2.name)
|
||||
},
|
||||
}
|
||||
|
||||
func (tes Entries) Len() int { return len(tes) }
|
||||
func (tes Entries) Swap(i, j int) { tes[i], tes[j] = tes[j], tes[i] }
|
||||
func (tes Entries) Less(i, j int) bool {
|
||||
t1, t2 := tes[i], tes[j]
|
||||
func (ctes customSortableEntries) Len() int { return len(ctes.Entries) }
|
||||
|
||||
func (ctes customSortableEntries) Swap(i, j int) {
|
||||
ctes.Entries[i], ctes.Entries[j] = ctes.Entries[j], ctes.Entries[i]
|
||||
}
|
||||
|
||||
func (ctes customSortableEntries) Less(i, j int) bool {
|
||||
t1, t2 := ctes.Entries[i], ctes.Entries[j]
|
||||
var k int
|
||||
for k = 0; k < len(sorter)-1; k++ {
|
||||
s := sorter[k]
|
||||
switch {
|
||||
case s(t1, t2):
|
||||
case s(t1, t2, ctes.Comparer):
|
||||
return true
|
||||
case s(t2, t1):
|
||||
case s(t2, t1, ctes.Comparer):
|
||||
return false
|
||||
}
|
||||
}
|
||||
return sorter[k](t1, t2)
|
||||
return sorter[k](t1, t2, ctes.Comparer)
|
||||
}
|
||||
|
||||
// Sort sort the list of entry
|
||||
func (tes Entries) Sort() {
|
||||
sort.Sort(tes)
|
||||
sort.Sort(customSortableEntries{func(s1, s2 string) bool {
|
||||
return s1 < s2
|
||||
}, tes})
|
||||
}
|
||||
|
||||
// CustomSort customizable string comparing sort entry list
|
||||
func (tes Entries) CustomSort(cmp func(s1, s2 string) bool) {
|
||||
sort.Sort(customSortableEntries{cmp, tes})
|
||||
}
|
||||
|
||||
type commitInfo struct {
|
||||
|
|
|
|||
21
vendor/github.com/chaseadamsio/goorgeous/LICENSE
generated
vendored
Normal file
21
vendor/github.com/chaseadamsio/goorgeous/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Chase Adams <realchaseadams@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
66
vendor/github.com/chaseadamsio/goorgeous/README.org
generated
vendored
Normal file
66
vendor/github.com/chaseadamsio/goorgeous/README.org
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#+TITLE: chaseadamsio/goorgeous
|
||||
|
||||
[[https://travis-ci.org/chaseadamsio/goorgeous.svg?branch=master]]
|
||||
[[https://coveralls.io/repos/github/chaseadamsio/goorgeous/badge.svg?branch=master]]
|
||||
|
||||
/goorgeous is a Go Org to HTML Parser./
|
||||
|
||||
[[file:gopher_small.gif]]
|
||||
|
||||
*Pronounced: Go? Org? Yes!*
|
||||
|
||||
#+BEGIN_QUOTE
|
||||
"Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system."
|
||||
|
||||
- [[orgmode.org]]
|
||||
#+END_QUOTE
|
||||
|
||||
The purpose of this package is to come as close as possible as parsing an =*.org= document into HTML, the same way one might publish [[http://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html][with org-publish-html from Emacs]].
|
||||
|
||||
* Installation
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
go get -u github.com/chaseadamsio/goorgeous
|
||||
#+END_SRC
|
||||
|
||||
* Usage
|
||||
|
||||
** Org Headers
|
||||
|
||||
To retrieve the headers from a =[]byte=, call =OrgHeaders= and it will return a =map[string]interface{}=:
|
||||
|
||||
#+BEGIN_SRC go
|
||||
input := "#+title: goorgeous\n* Some Headline\n"
|
||||
out := goorgeous.OrgHeaders(input)
|
||||
#+END_SRC
|
||||
|
||||
#+BEGIN_SRC go
|
||||
map[string]interface{}{
|
||||
"title": "goorgeous"
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** Org Content
|
||||
|
||||
After importing =github.com/chaseadamsio/goorgeous=, you can call =Org= with a =[]byte= and it will return an =html= version of the content as a =[]byte=
|
||||
|
||||
#+BEGIN_SRC go
|
||||
input := "#+TITLE: goorgeous\n* Some Headline\n"
|
||||
out := goorgeous.Org(input)
|
||||
#+END_SRC
|
||||
|
||||
=out= will be:
|
||||
|
||||
#+BEGIN_SRC html
|
||||
<h1>Some Headline</h1>/n
|
||||
#+END_SRC
|
||||
|
||||
* Why?
|
||||
|
||||
First off, I've become an unapologetic user of Emacs & ever since finding =org-mode= I use it for anything having to do with writing content, organizing my life and keeping documentation of my days/weeks/months.
|
||||
|
||||
Although I like Emacs & =emacs-lisp=, I publish all of my html sites with [[https://gohugo.io][Hugo Static Site Generator]] and wanted to be able to write my content in =org-mode= in Emacs rather than markdown.
|
||||
|
||||
Hugo's implementation of templating and speed are unmatched, so the only way I knew for sure I could continue to use Hugo and write in =org-mode= seamlessly was to write a golang parser for org content and submit a PR for Hugo to use it.
|
||||
* Acknowledgements
|
||||
I leaned heavily on russross' [[https://github.com/russross/blackfriday][blackfriday markdown renderer]] as both an example of how to write a parser (with some updates to leverage the go we know today) and reusing the blackfriday HTML Renderer so I didn't have to write my own!
|
||||
803
vendor/github.com/chaseadamsio/goorgeous/goorgeous.go
generated
vendored
Normal file
803
vendor/github.com/chaseadamsio/goorgeous/goorgeous.go
generated
vendored
Normal file
|
|
@ -0,0 +1,803 @@
|
|||
package goorgeous
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"regexp"
|
||||
|
||||
"github.com/russross/blackfriday"
|
||||
"github.com/shurcooL/sanitized_anchor_name"
|
||||
)
|
||||
|
||||
type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int
|
||||
|
||||
type footnotes struct {
|
||||
id string
|
||||
def string
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
r blackfriday.Renderer
|
||||
inlineCallback [256]inlineParser
|
||||
notes []footnotes
|
||||
}
|
||||
|
||||
// NewParser returns a new parser with the inlineCallbacks required for org content
|
||||
func NewParser(renderer blackfriday.Renderer) *parser {
|
||||
p := new(parser)
|
||||
p.r = renderer
|
||||
|
||||
p.inlineCallback['='] = generateVerbatim
|
||||
p.inlineCallback['~'] = generateCode
|
||||
p.inlineCallback['/'] = generateEmphasis
|
||||
p.inlineCallback['_'] = generateUnderline
|
||||
p.inlineCallback['*'] = generateBold
|
||||
p.inlineCallback['+'] = generateStrikethrough
|
||||
p.inlineCallback['['] = generateLinkOrImg
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// OrgCommon is the easiest way to parse a byte slice of org content and makes assumptions
|
||||
// that the caller wants to use blackfriday's HTMLRenderer with XHTML
|
||||
func OrgCommon(input []byte) []byte {
|
||||
renderer := blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML, "", "")
|
||||
return OrgOptions(input, renderer)
|
||||
}
|
||||
|
||||
// Org is a convenience name for OrgOptions
|
||||
func Org(input []byte, renderer blackfriday.Renderer) []byte {
|
||||
return OrgOptions(input, renderer)
|
||||
}
|
||||
|
||||
// OrgOptions takes an org content byte slice and a renderer to use
|
||||
func OrgOptions(input []byte, renderer blackfriday.Renderer) []byte {
|
||||
// in the case that we need to render something in isEmpty but there isn't a new line char
|
||||
input = append(input, '\n')
|
||||
var output bytes.Buffer
|
||||
|
||||
p := NewParser(renderer)
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(input))
|
||||
// used to capture code blocks
|
||||
marker := ""
|
||||
syntax := ""
|
||||
listType := ""
|
||||
inParagraph := false
|
||||
inList := false
|
||||
inTable := false
|
||||
inFixedWidthArea := false
|
||||
var tmpBlock bytes.Buffer
|
||||
|
||||
for scanner.Scan() {
|
||||
data := scanner.Bytes()
|
||||
|
||||
if !isEmpty(data) && isComment(data) || IsKeyword(data) {
|
||||
switch {
|
||||
case inList:
|
||||
if tmpBlock.Len() > 0 {
|
||||
p.generateList(&output, tmpBlock.Bytes(), listType)
|
||||
}
|
||||
inList = false
|
||||
listType = ""
|
||||
tmpBlock.Reset()
|
||||
case inTable:
|
||||
if tmpBlock.Len() > 0 {
|
||||
p.generateTable(&output, tmpBlock.Bytes())
|
||||
}
|
||||
inTable = false
|
||||
tmpBlock.Reset()
|
||||
case inParagraph:
|
||||
if tmpBlock.Len() > 0 {
|
||||
p.generateParagraph(&output, tmpBlock.Bytes()[:len(tmpBlock.Bytes())-1])
|
||||
}
|
||||
inParagraph = false
|
||||
tmpBlock.Reset()
|
||||
case inFixedWidthArea:
|
||||
if tmpBlock.Len() > 0 {
|
||||
tmpBlock.WriteString("</pre>\n")
|
||||
output.Write(tmpBlock.Bytes())
|
||||
}
|
||||
inFixedWidthArea = false
|
||||
tmpBlock.Reset()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch {
|
||||
case isEmpty(data):
|
||||
switch {
|
||||
case inList:
|
||||
if tmpBlock.Len() > 0 {
|
||||
p.generateList(&output, tmpBlock.Bytes(), listType)
|
||||
}
|
||||
inList = false
|
||||
listType = ""
|
||||
tmpBlock.Reset()
|
||||
case inTable:
|
||||
if tmpBlock.Len() > 0 {
|
||||
p.generateTable(&output, tmpBlock.Bytes())
|
||||
}
|
||||
inTable = false
|
||||
tmpBlock.Reset()
|
||||
case inParagraph:
|
||||
if tmpBlock.Len() > 0 {
|
||||
p.generateParagraph(&output, tmpBlock.Bytes()[:len(tmpBlock.Bytes())-1])
|
||||
}
|
||||
inParagraph = false
|
||||
tmpBlock.Reset()
|
||||
case inFixedWidthArea:
|
||||
if tmpBlock.Len() > 0 {
|
||||
tmpBlock.WriteString("</pre>\n")
|
||||
output.Write(tmpBlock.Bytes())
|
||||
}
|
||||
inFixedWidthArea = false
|
||||
tmpBlock.Reset()
|
||||
case marker != "":
|
||||
tmpBlock.WriteByte('\n')
|
||||
default:
|
||||
continue
|
||||
}
|
||||
case isPropertyDrawer(data) || marker == "PROPERTIES":
|
||||
if marker == "" {
|
||||
marker = "PROPERTIES"
|
||||
}
|
||||
if bytes.Equal(data, []byte(":END:")) {
|
||||
marker = ""
|
||||
}
|
||||
continue
|
||||
case isBlock(data) || marker != "":
|
||||
matches := reBlock.FindSubmatch(data)
|
||||
if len(matches) > 0 {
|
||||
if string(matches[1]) == "END" {
|
||||
switch marker {
|
||||
case "QUOTE":
|
||||
var tmpBuf bytes.Buffer
|
||||
p.inline(&tmpBuf, tmpBlock.Bytes())
|
||||
p.r.BlockQuote(&output, tmpBuf.Bytes())
|
||||
case "CENTER":
|
||||
var tmpBuf bytes.Buffer
|
||||
output.WriteString("<center>\n")
|
||||
p.inline(&tmpBuf, tmpBlock.Bytes())
|
||||
output.Write(tmpBuf.Bytes())
|
||||
output.WriteString("</center>\n")
|
||||
default:
|
||||
tmpBlock.WriteByte('\n')
|
||||
p.r.BlockCode(&output, tmpBlock.Bytes(), syntax)
|
||||
}
|
||||
marker = ""
|
||||
tmpBlock.Reset()
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
if marker != "" {
|
||||
if marker != "SRC" && marker != "EXAMPLE" {
|
||||
var tmpBuf bytes.Buffer
|
||||
tmpBuf.Write([]byte("<p>\n"))
|
||||
p.inline(&tmpBuf, data)
|
||||
tmpBuf.WriteByte('\n')
|
||||
tmpBuf.Write([]byte("</p>\n"))
|
||||
tmpBlock.Write(tmpBuf.Bytes())
|
||||
|
||||
} else {
|
||||
tmpBlock.WriteByte('\n')
|
||||
tmpBlock.Write(data)
|
||||
}
|
||||
|
||||
} else {
|
||||
marker = string(matches[2])
|
||||
syntax = string(matches[3])
|
||||
}
|
||||
case isFootnoteDef(data):
|
||||
matches := reFootnoteDef.FindSubmatch(data)
|
||||
for i := range p.notes {
|
||||
if p.notes[i].id == string(matches[1]) {
|
||||
p.notes[i].def = string(matches[2])
|
||||
}
|
||||
}
|
||||
case isTable(data):
|
||||
if inTable != true {
|
||||
inTable = true
|
||||
}
|
||||
tmpBlock.Write(data)
|
||||
tmpBlock.WriteByte('\n')
|
||||
case IsKeyword(data):
|
||||
continue
|
||||
case isComment(data):
|
||||
p.generateComment(&output, data)
|
||||
case isHeadline(data):
|
||||
p.generateHeadline(&output, data)
|
||||
case isDefinitionList(data):
|
||||
if inList != true {
|
||||
listType = "dl"
|
||||
inList = true
|
||||
}
|
||||
var work bytes.Buffer
|
||||
flags := blackfriday.LIST_TYPE_DEFINITION
|
||||
matches := reDefinitionList.FindSubmatch(data)
|
||||
flags |= blackfriday.LIST_TYPE_TERM
|
||||
p.inline(&work, matches[1])
|
||||
p.r.ListItem(&tmpBlock, work.Bytes(), flags)
|
||||
work.Reset()
|
||||
flags &= ^blackfriday.LIST_TYPE_TERM
|
||||
p.inline(&work, matches[2])
|
||||
p.r.ListItem(&tmpBlock, work.Bytes(), flags)
|
||||
case isUnorderedList(data):
|
||||
if inList != true {
|
||||
listType = "ul"
|
||||
inList = true
|
||||
}
|
||||
matches := reUnorderedList.FindSubmatch(data)
|
||||
var work bytes.Buffer
|
||||
p.inline(&work, matches[2])
|
||||
p.r.ListItem(&tmpBlock, work.Bytes(), 0)
|
||||
case isOrderedList(data):
|
||||
if inList != true {
|
||||
listType = "ol"
|
||||
inList = true
|
||||
}
|
||||
matches := reOrderedList.FindSubmatch(data)
|
||||
var work bytes.Buffer
|
||||
tmpBlock.WriteString("<li")
|
||||
if len(matches[2]) > 0 {
|
||||
tmpBlock.WriteString(" value=\"")
|
||||
tmpBlock.Write(matches[2])
|
||||
tmpBlock.WriteString("\"")
|
||||
matches[3] = matches[3][1:]
|
||||
}
|
||||
p.inline(&work, matches[3])
|
||||
tmpBlock.WriteString(">")
|
||||
tmpBlock.Write(work.Bytes())
|
||||
tmpBlock.WriteString("</li>\n")
|
||||
case isHorizontalRule(data):
|
||||
p.r.HRule(&output)
|
||||
case isExampleLine(data):
|
||||
if inParagraph == true {
|
||||
if len(tmpBlock.Bytes()) > 0 {
|
||||
p.generateParagraph(&output, tmpBlock.Bytes()[:len(tmpBlock.Bytes())-1])
|
||||
inParagraph = false
|
||||
}
|
||||
tmpBlock.Reset()
|
||||
}
|
||||
if inFixedWidthArea != true {
|
||||
tmpBlock.WriteString("<pre class=\"example\">\n")
|
||||
inFixedWidthArea = true
|
||||
}
|
||||
matches := reExampleLine.FindSubmatch(data)
|
||||
tmpBlock.Write(matches[1])
|
||||
tmpBlock.WriteString("\n")
|
||||
break
|
||||
default:
|
||||
if inParagraph == false {
|
||||
inParagraph = true
|
||||
if inFixedWidthArea == true {
|
||||
if tmpBlock.Len() > 0 {
|
||||
tmpBlock.WriteString("</pre>")
|
||||
output.Write(tmpBlock.Bytes())
|
||||
}
|
||||
inFixedWidthArea = false
|
||||
tmpBlock.Reset()
|
||||
}
|
||||
}
|
||||
tmpBlock.Write(data)
|
||||
tmpBlock.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmpBlock.Bytes()) > 0 {
|
||||
if inParagraph == true {
|
||||
p.generateParagraph(&output, tmpBlock.Bytes()[:len(tmpBlock.Bytes())-1])
|
||||
} else if inFixedWidthArea == true {
|
||||
tmpBlock.WriteString("</pre>\n")
|
||||
output.Write(tmpBlock.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// Writing footnote def. list
|
||||
if len(p.notes) > 0 {
|
||||
flags := blackfriday.LIST_ITEM_BEGINNING_OF_LIST
|
||||
p.r.Footnotes(&output, func() bool {
|
||||
for i := range p.notes {
|
||||
p.r.FootnoteItem(&output, []byte(p.notes[i].id), []byte(p.notes[i].def), flags)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return output.Bytes()
|
||||
}
|
||||
|
||||
// Org Syntax has been broken up into 4 distinct sections based on
|
||||
// the org-syntax draft (http://orgmode.org/worg/dev/org-syntax.html):
|
||||
// - Headlines
|
||||
// - Greater Elements
|
||||
// - Elements
|
||||
// - Objects
|
||||
|
||||
// Headlines
|
||||
func isHeadline(data []byte) bool {
|
||||
if !charMatches(data[0], '*') {
|
||||
return false
|
||||
}
|
||||
level := 0
|
||||
for level < 6 && charMatches(data[level], '*') {
|
||||
level++
|
||||
}
|
||||
return charMatches(data[level], ' ')
|
||||
}
|
||||
|
||||
func (p *parser) generateHeadline(out *bytes.Buffer, data []byte) {
|
||||
level := 1
|
||||
status := ""
|
||||
priority := ""
|
||||
|
||||
for level < 6 && data[level] == '*' {
|
||||
level++
|
||||
}
|
||||
|
||||
start := skipChar(data, level, ' ')
|
||||
|
||||
data = data[start:]
|
||||
i := 0
|
||||
|
||||
// Check if has a status so it can be rendered as a separate span that can be hidden or
|
||||
// modified with CSS classes
|
||||
if hasStatus(data[i:4]) {
|
||||
status = string(data[i:4])
|
||||
i += 5 // one extra character for the next whitespace
|
||||
}
|
||||
|
||||
// Check if the next byte is a priority marker
|
||||
if data[i] == '[' && hasPriority(data[i+1]) {
|
||||
priority = string(data[i+1])
|
||||
i += 4 // for "[c]" + ' '
|
||||
}
|
||||
|
||||
tags, tagsFound := findTags(data, i)
|
||||
|
||||
headlineID := sanitized_anchor_name.Create(string(data[i:]))
|
||||
|
||||
generate := func() bool {
|
||||
dataEnd := len(data)
|
||||
if tagsFound > 0 {
|
||||
dataEnd = tagsFound
|
||||
}
|
||||
|
||||
headline := bytes.TrimRight(data[i:dataEnd], " \t")
|
||||
|
||||
if status != "" {
|
||||
out.WriteString("<span class=\"todo " + status + "\">" + status + "</span>")
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
|
||||
if priority != "" {
|
||||
out.WriteString("<span class=\"priority " + priority + "\">[" + priority + "]</span>")
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
|
||||
p.inline(out, headline)
|
||||
|
||||
if tagsFound > 0 {
|
||||
for _, tag := range tags {
|
||||
out.WriteByte(' ')
|
||||
out.WriteString("<span class=\"tags " + tag + "\">" + tag + "</span>")
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
p.r.Header(out, generate, level, headlineID)
|
||||
}
|
||||
|
||||
func hasStatus(data []byte) bool {
|
||||
return bytes.Contains(data, []byte("TODO")) || bytes.Contains(data, []byte("DONE"))
|
||||
}
|
||||
|
||||
func hasPriority(char byte) bool {
|
||||
return (charMatches(char, 'A') || charMatches(char, 'B') || charMatches(char, 'C'))
|
||||
}
|
||||
|
||||
func findTags(data []byte, start int) ([]string, int) {
|
||||
tags := []string{}
|
||||
tagOpener := 0
|
||||
tagMarker := tagOpener
|
||||
for tIdx := start; tIdx < len(data); tIdx++ {
|
||||
if tagMarker > 0 && data[tIdx] == ':' {
|
||||
tags = append(tags, string(data[tagMarker+1:tIdx]))
|
||||
tagMarker = tIdx
|
||||
}
|
||||
if data[tIdx] == ':' && tagOpener == 0 && data[tIdx-1] == ' ' {
|
||||
tagMarker = tIdx
|
||||
tagOpener = tIdx
|
||||
}
|
||||
}
|
||||
return tags, tagOpener
|
||||
}
|
||||
|
||||
// Greater Elements
|
||||
// ~~ Definition Lists
|
||||
var reDefinitionList = regexp.MustCompile(`^\s*-\s+(.+?)\s+::\s+(.*)`)
|
||||
|
||||
func isDefinitionList(data []byte) bool {
|
||||
return reDefinitionList.Match(data)
|
||||
}
|
||||
|
||||
// ~~ Example lines
|
||||
var reExampleLine = regexp.MustCompile(`^\s*:\s(\s*.*)|^\s*:$`)
|
||||
|
||||
func isExampleLine(data []byte) bool {
|
||||
return reExampleLine.Match(data)
|
||||
}
|
||||
|
||||
// ~~ Ordered Lists
|
||||
var reOrderedList = regexp.MustCompile(`^(\s*)\d+\.\s+\[?@?(\d*)\]?(.+)`)
|
||||
|
||||
func isOrderedList(data []byte) bool {
|
||||
return reOrderedList.Match(data)
|
||||
}
|
||||
|
||||
// ~~ Unordered Lists
|
||||
var reUnorderedList = regexp.MustCompile(`^(\s*)[-\+]\s+(.+)`)
|
||||
|
||||
func isUnorderedList(data []byte) bool {
|
||||
return reUnorderedList.Match(data)
|
||||
}
|
||||
|
||||
// ~~ Tables
|
||||
var reTableHeaders = regexp.MustCompile(`^[|+-]*$`)
|
||||
|
||||
func isTable(data []byte) bool {
|
||||
return charMatches(data[0], '|')
|
||||
}
|
||||
|
||||
func (p *parser) generateTable(output *bytes.Buffer, data []byte) {
|
||||
var table bytes.Buffer
|
||||
rows := bytes.Split(bytes.Trim(data, "\n"), []byte("\n"))
|
||||
hasTableHeaders := len(rows) > 1
|
||||
if len(rows) > 1 {
|
||||
hasTableHeaders = reTableHeaders.Match(rows[1])
|
||||
}
|
||||
tbodySet := false
|
||||
|
||||
for idx, row := range rows {
|
||||
var rowBuff bytes.Buffer
|
||||
if hasTableHeaders && idx == 0 {
|
||||
table.WriteString("<thead>")
|
||||
for _, cell := range bytes.Split(row[1:len(row)-1], []byte("|")) {
|
||||
p.r.TableHeaderCell(&rowBuff, bytes.Trim(cell, " \t"), 0)
|
||||
}
|
||||
p.r.TableRow(&table, rowBuff.Bytes())
|
||||
table.WriteString("</thead>\n")
|
||||
} else if hasTableHeaders && idx == 1 {
|
||||
continue
|
||||
} else {
|
||||
if !tbodySet {
|
||||
table.WriteString("<tbody>")
|
||||
tbodySet = true
|
||||
}
|
||||
if !reTableHeaders.Match(row) {
|
||||
for _, cell := range bytes.Split(row[1:len(row)-1], []byte("|")) {
|
||||
var cellBuff bytes.Buffer
|
||||
p.inline(&cellBuff, bytes.Trim(cell, " \t"))
|
||||
p.r.TableCell(&rowBuff, cellBuff.Bytes(), 0)
|
||||
}
|
||||
p.r.TableRow(&table, rowBuff.Bytes())
|
||||
}
|
||||
if tbodySet && idx == len(rows)-1 {
|
||||
table.WriteString("</tbody>\n")
|
||||
tbodySet = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("\n<table>\n")
|
||||
output.Write(table.Bytes())
|
||||
output.WriteString("</table>\n")
|
||||
}
|
||||
|
||||
// ~~ Property Drawers
|
||||
|
||||
func isPropertyDrawer(data []byte) bool {
|
||||
return bytes.Equal(data, []byte(":PROPERTIES:"))
|
||||
}
|
||||
|
||||
// ~~ Dynamic Blocks
|
||||
var reBlock = regexp.MustCompile(`^#\+(BEGIN|END)_(\w+)\s*([0-9A-Za-z_\-]*)?`)
|
||||
|
||||
func isBlock(data []byte) bool {
|
||||
return reBlock.Match(data)
|
||||
}
|
||||
|
||||
// ~~ Footnotes
|
||||
var reFootnoteDef = regexp.MustCompile(`^\[fn:([\w]+)\] +(.+)`)
|
||||
|
||||
func isFootnoteDef(data []byte) bool {
|
||||
return reFootnoteDef.Match(data)
|
||||
}
|
||||
|
||||
// Elements
|
||||
// ~~ Keywords
|
||||
func IsKeyword(data []byte) bool {
|
||||
return len(data) > 2 && charMatches(data[0], '#') && charMatches(data[1], '+') && !charMatches(data[2], ' ')
|
||||
}
|
||||
|
||||
// ~~ Comments
|
||||
func isComment(data []byte) bool {
|
||||
return charMatches(data[0], '#') && charMatches(data[1], ' ')
|
||||
}
|
||||
|
||||
func (p *parser) generateComment(out *bytes.Buffer, data []byte) {
|
||||
var work bytes.Buffer
|
||||
work.WriteString("<!-- ")
|
||||
work.Write(data[2:])
|
||||
work.WriteString(" -->")
|
||||
work.WriteByte('\n')
|
||||
out.Write(work.Bytes())
|
||||
}
|
||||
|
||||
// ~~ Horizontal Rules
|
||||
var reHorizontalRule = regexp.MustCompile(`^\s*?-----\s?$`)
|
||||
|
||||
func isHorizontalRule(data []byte) bool {
|
||||
return reHorizontalRule.Match(data)
|
||||
}
|
||||
|
||||
// ~~ Paragraphs
|
||||
func (p *parser) generateParagraph(out *bytes.Buffer, data []byte) {
|
||||
generate := func() bool {
|
||||
p.inline(out, bytes.Trim(data, " "))
|
||||
return true
|
||||
}
|
||||
p.r.Paragraph(out, generate)
|
||||
}
|
||||
|
||||
func (p *parser) generateList(output *bytes.Buffer, data []byte, listType string) {
|
||||
generateList := func() bool {
|
||||
output.WriteByte('\n')
|
||||
p.inline(output, bytes.Trim(data, " "))
|
||||
return true
|
||||
}
|
||||
switch listType {
|
||||
case "ul":
|
||||
p.r.List(output, generateList, 0)
|
||||
case "ol":
|
||||
p.r.List(output, generateList, blackfriday.LIST_TYPE_ORDERED)
|
||||
case "dl":
|
||||
p.r.List(output, generateList, blackfriday.LIST_TYPE_DEFINITION)
|
||||
}
|
||||
}
|
||||
|
||||
// Objects
|
||||
|
||||
func (p *parser) inline(out *bytes.Buffer, data []byte) {
|
||||
i, end := 0, 0
|
||||
|
||||
for i < len(data) {
|
||||
for end < len(data) && p.inlineCallback[data[end]] == nil {
|
||||
end++
|
||||
}
|
||||
|
||||
p.r.Entity(out, data[i:end])
|
||||
|
||||
if end >= len(data) {
|
||||
break
|
||||
}
|
||||
i = end
|
||||
|
||||
handler := p.inlineCallback[data[i]]
|
||||
|
||||
if consumed := handler(p, out, data, i); consumed > 0 {
|
||||
i += consumed
|
||||
end = i
|
||||
continue
|
||||
}
|
||||
|
||||
end = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
func isAcceptablePreOpeningChar(dataIn, data []byte, offset int) bool {
|
||||
if len(dataIn) == len(data) {
|
||||
return true
|
||||
}
|
||||
|
||||
char := dataIn[offset-1]
|
||||
return charMatches(char, ' ') || isPreChar(char)
|
||||
}
|
||||
|
||||
func isPreChar(char byte) bool {
|
||||
return charMatches(char, '>') || charMatches(char, '(') || charMatches(char, '{') || charMatches(char, '[')
|
||||
}
|
||||
|
||||
func isAcceptablePostClosingChar(char byte) bool {
|
||||
return charMatches(char, ' ') || isTerminatingChar(char)
|
||||
}
|
||||
|
||||
func isTerminatingChar(char byte) bool {
|
||||
return charMatches(char, '.') || charMatches(char, ',') || charMatches(char, '?') || charMatches(char, '!') || charMatches(char, ')') || charMatches(char, '}') || charMatches(char, ']')
|
||||
}
|
||||
|
||||
func findLastCharInInline(data []byte, char byte) int {
|
||||
timesFound := 0
|
||||
last := 0
|
||||
// Start from character after the inline indicator
|
||||
for i := 1; i < len(data); i++ {
|
||||
if timesFound == 1 {
|
||||
break
|
||||
}
|
||||
if data[i] == char {
|
||||
if len(data) == i+1 || (len(data) > i+1 && isAcceptablePostClosingChar(data[i+1])) {
|
||||
last = i
|
||||
timesFound += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
func generator(p *parser, out *bytes.Buffer, dataIn []byte, offset int, char byte, doInline bool, renderer func(*bytes.Buffer, []byte)) int {
|
||||
data := dataIn[offset:]
|
||||
c := byte(char)
|
||||
start := 1
|
||||
i := start
|
||||
if len(data) <= 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
lastCharInside := findLastCharInInline(data, c)
|
||||
|
||||
// Org mode spec says a non-whitespace character must immediately follow.
|
||||
// if the current char is the marker, then there's no text between, not a candidate
|
||||
if isSpace(data[i]) || lastCharInside == i || !isAcceptablePreOpeningChar(dataIn, data, offset) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if lastCharInside > 0 {
|
||||
var work bytes.Buffer
|
||||
if doInline {
|
||||
p.inline(&work, data[start:lastCharInside])
|
||||
renderer(out, work.Bytes())
|
||||
} else {
|
||||
renderer(out, data[start:lastCharInside])
|
||||
}
|
||||
next := lastCharInside + 1
|
||||
return next
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// ~~ Text Markup
|
||||
func generateVerbatim(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
return generator(p, out, data, offset, '=', false, p.r.CodeSpan)
|
||||
}
|
||||
|
||||
func generateCode(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
return generator(p, out, data, offset, '~', false, p.r.CodeSpan)
|
||||
}
|
||||
|
||||
func generateEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
return generator(p, out, data, offset, '/', true, p.r.Emphasis)
|
||||
}
|
||||
|
||||
func generateUnderline(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
underline := func(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("<span style=\"text-decoration: underline;\">")
|
||||
out.Write(text)
|
||||
out.WriteString("</span>")
|
||||
}
|
||||
|
||||
return generator(p, out, data, offset, '_', true, underline)
|
||||
}
|
||||
|
||||
func generateBold(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
return generator(p, out, data, offset, '*', true, p.r.DoubleEmphasis)
|
||||
}
|
||||
|
||||
func generateStrikethrough(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
return generator(p, out, data, offset, '+', true, p.r.StrikeThrough)
|
||||
}
|
||||
|
||||
// ~~ Images and Links (inc. Footnote)
|
||||
var reLinkOrImg = regexp.MustCompile(`\[\[(.+?)\]\[?(.*?)\]?\]`)
|
||||
|
||||
func generateLinkOrImg(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
data = data[offset+1:]
|
||||
start := 1
|
||||
i := start
|
||||
var hyperlink []byte
|
||||
isImage := false
|
||||
isFootnote := false
|
||||
closedLink := false
|
||||
hasContent := false
|
||||
|
||||
if bytes.Equal(data[0:3], []byte("fn:")) {
|
||||
isFootnote = true
|
||||
} else if data[0] != '[' {
|
||||
return 0
|
||||
}
|
||||
|
||||
if bytes.Equal(data[1:6], []byte("file:")) {
|
||||
isImage = true
|
||||
}
|
||||
|
||||
for i < len(data) {
|
||||
currChar := data[i]
|
||||
switch {
|
||||
case charMatches(currChar, ']') && closedLink == false:
|
||||
if isImage {
|
||||
hyperlink = data[start+5 : i]
|
||||
} else if isFootnote {
|
||||
refid := data[start+2 : i]
|
||||
if bytes.Equal(refid, bytes.Trim(refid, " ")) {
|
||||
p.notes = append(p.notes, footnotes{string(refid), "DEFINITION NOT FOUND"})
|
||||
p.r.FootnoteRef(out, refid, len(p.notes))
|
||||
return i + 2
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
} else if bytes.Equal(data[i-4:i], []byte(".org")) {
|
||||
orgStart := start
|
||||
if bytes.Equal(data[orgStart:orgStart+2], []byte("./")) {
|
||||
orgStart = orgStart + 1
|
||||
}
|
||||
hyperlink = data[orgStart : i-4]
|
||||
} else {
|
||||
hyperlink = data[start:i]
|
||||
}
|
||||
closedLink = true
|
||||
case charMatches(currChar, '['):
|
||||
start = i + 1
|
||||
hasContent = true
|
||||
case charMatches(currChar, ']') && closedLink == true && hasContent == true && isImage == true:
|
||||
p.r.Image(out, hyperlink, data[start:i], data[start:i])
|
||||
return i + 3
|
||||
case charMatches(currChar, ']') && closedLink == true && hasContent == true:
|
||||
var tmpBuf bytes.Buffer
|
||||
p.inline(&tmpBuf, data[start:i])
|
||||
p.r.Link(out, hyperlink, tmpBuf.Bytes(), tmpBuf.Bytes())
|
||||
return i + 3
|
||||
case charMatches(currChar, ']') && closedLink == true && hasContent == false && isImage == true:
|
||||
p.r.Image(out, hyperlink, hyperlink, hyperlink)
|
||||
return i + 2
|
||||
case charMatches(currChar, ']') && closedLink == true && hasContent == false:
|
||||
p.r.Link(out, hyperlink, hyperlink, hyperlink)
|
||||
return i + 2
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Helpers
|
||||
func skipChar(data []byte, start int, char byte) int {
|
||||
i := start
|
||||
for i < len(data) && charMatches(data[i], char) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func isSpace(char byte) bool {
|
||||
return charMatches(char, ' ')
|
||||
}
|
||||
|
||||
func isEmpty(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := 0; i < len(data) && !charMatches(data[i], '\n'); i++ {
|
||||
if !charMatches(data[i], ' ') && !charMatches(data[i], '\t') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func charMatches(a byte, b byte) bool {
|
||||
return a == b
|
||||
}
|
||||
BIN
vendor/github.com/chaseadamsio/goorgeous/gopher.gif
generated
vendored
Normal file
BIN
vendor/github.com/chaseadamsio/goorgeous/gopher.gif
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif
generated
vendored
Normal file
BIN
vendor/github.com/chaseadamsio/goorgeous/gopher_small.gif
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
70
vendor/github.com/chaseadamsio/goorgeous/header.go
generated
vendored
Normal file
70
vendor/github.com/chaseadamsio/goorgeous/header.go
generated
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package goorgeous
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ExtractOrgHeaders finds and returns all of the headers
|
||||
// from a bufio.Reader and returns them as their own byte slice
|
||||
func ExtractOrgHeaders(r *bufio.Reader) (fm []byte, err error) {
|
||||
var out bytes.Buffer
|
||||
endOfHeaders := true
|
||||
for endOfHeaders {
|
||||
p, err := r.Peek(2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !charMatches(p[0], '#') && !charMatches(p[1], '+') {
|
||||
endOfHeaders = false
|
||||
break
|
||||
}
|
||||
line, _, err := r.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Write(line)
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
var reHeader = regexp.MustCompile(`^#\+(\w+?): (.*)`)
|
||||
|
||||
// OrgHeaders find all of the headers from a byte slice and returns
|
||||
// them as a map of string interface
|
||||
func OrgHeaders(input []byte) (map[string]interface{}, error) {
|
||||
out := make(map[string]interface{})
|
||||
scanner := bufio.NewScanner(bytes.NewReader(input))
|
||||
|
||||
for scanner.Scan() {
|
||||
data := scanner.Bytes()
|
||||
if !charMatches(data[0], '#') && !charMatches(data[1], '+') {
|
||||
return out, nil
|
||||
}
|
||||
matches := reHeader.FindSubmatch(data)
|
||||
|
||||
if len(matches) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := string(matches[1])
|
||||
val := matches[2]
|
||||
switch {
|
||||
case strings.ToLower(key) == "tags" || strings.ToLower(key) == "categories" || strings.ToLower(key) == "aliases":
|
||||
bTags := bytes.Split(val, []byte(" "))
|
||||
tags := make([]string, len(bTags))
|
||||
for idx, tag := range bTags {
|
||||
tags[idx] = string(tag)
|
||||
}
|
||||
out[key] = tags
|
||||
default:
|
||||
out[key] = string(val)
|
||||
}
|
||||
|
||||
}
|
||||
return out, nil
|
||||
|
||||
}
|
||||
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
|
|
@ -3,10 +3,10 @@
|
|||
"ignore": "test appengine",
|
||||
"package": [
|
||||
{
|
||||
"checksumSHA1": "fR5YDSoG7xYv2aLO23rne95gWps=",
|
||||
"checksumSHA1": "9dxw/SGpdhNNm704gt6F02ItYtQ=",
|
||||
"path": "code.gitea.io/git",
|
||||
"revision": "479f87e5d189e7b8f1fd51dbcd25faa32b632cd2",
|
||||
"revisionTime": "2017-08-03T00:53:29Z"
|
||||
"revision": "d7487da878e40ee6c4fac7280b518c0ed0be702c",
|
||||
"revisionTime": "2017-09-16T17:49:37Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Zgp5RqU+20L2p9TNl1rSsUIWEEE=",
|
||||
|
|
@ -299,6 +299,12 @@
|
|||
"revision": "fb1f79c6b65acda83063cbc69f6bba1522558bfc",
|
||||
"revisionTime": "2016-01-17T19:21:50Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "x1svIugw39oEZGU5/HMUHzgRUZM=",
|
||||
"path": "github.com/chaseadamsio/goorgeous",
|
||||
"revision": "098da33fde5f9220736531b3cb26a2dec86a8367",
|
||||
"revisionTime": "2017-09-01T13:22:37Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "agNqSytP0indDCoGizlMyC1L/m4=",
|
||||
"path": "github.com/coreos/etcd/error",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user