Make URL scheme unambiguous (#2408)

* Make URL scheme unambiguous

Redirect old routes to new routes

* Fix redirects to new URL scheme, and update template

* Fix branches/_new endpoints, and update integration test
tokarchuk/v1.17
Ethan Koenig 7 years ago committed by Lunny Xiao
parent 6e98812ecf
commit 513375c429
  1. 4
      integrations/editor_test.go
  2. 18
      integrations/links_test.go
  3. 104
      integrations/repo_branch_test.go
  4. 6
      integrations/repo_commits_test.go
  5. 21
      models/webhook_slack.go
  6. 120
      modules/context/repo.go
  7. 2
      routers/api/v1/api.go
  8. 10
      routers/repo/branch.go
  9. 2
      routers/repo/commit.go
  10. 18
      routers/repo/editor.go
  11. 15
      routers/repo/repo.go
  12. 4
      routers/repo/view.go
  13. 67
      routers/routes/routes.go
  14. 4
      templates/repo/branch_dropdown.tmpl
  15. 8
      templates/repo/commits_table.tmpl
  16. 2
      templates/repo/editor/edit.tmpl
  17. 2
      templates/repo/home.tmpl
  18. 2
      templates/repo/issue/list.tmpl
  19. 10
      templates/repo/release/list.tmpl
  20. 4
      templates/repo/view_file.tmpl

@ -111,7 +111,7 @@ func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePa
resp = session.MakeRequest(t, req, http.StatusFound) resp = session.MakeRequest(t, req, http.StatusFound)
// Verify the change // Verify the change
req = NewRequest(t, "GET", path.Join(user, repo, "raw", branch, filePath)) req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", branch, filePath))
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
assert.EqualValues(t, newContent, string(resp.Body)) assert.EqualValues(t, newContent, string(resp.Body))
@ -142,7 +142,7 @@ func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, bra
resp = session.MakeRequest(t, req, http.StatusFound) resp = session.MakeRequest(t, req, http.StatusFound)
// Verify the change // Verify the change
req = NewRequest(t, "GET", path.Join(user, repo, "raw", targetBranch, filePath)) req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", targetBranch, filePath))
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
assert.EqualValues(t, newContent, string(resp.Body)) assert.EqualValues(t, newContent, string(resp.Body))

@ -10,6 +10,8 @@ import (
"testing" "testing"
api "code.gitea.io/sdk/gitea" api "code.gitea.io/sdk/gitea"
"github.com/stretchr/testify/assert"
) )
func TestLinksNoLogin(t *testing.T) { func TestLinksNoLogin(t *testing.T) {
@ -38,6 +40,20 @@ func TestLinksNoLogin(t *testing.T) {
} }
} }
func TestRedirectsNoLogin(t *testing.T) {
prepareTestEnv(t)
var redirects = map[string]string{
"/user2/repo1/commits/master": "/user2/repo1/commits/branch/master",
"/user2/repo1/src/master": "/user2/repo1/src/branch/master",
}
for link, redirectLink := range redirects {
req := NewRequest(t, "GET", link)
resp := MakeRequest(t, req, http.StatusFound)
assert.EqualValues(t, redirectLink, RedirectURL(t, resp))
}
}
func testLinksAsUser(userName string, t *testing.T) { func testLinksAsUser(userName string, t *testing.T) {
var links = []string{ var links = []string{
"/explore/repos", "/explore/repos",
@ -99,7 +115,7 @@ func testLinksAsUser(userName string, t *testing.T) {
"", "",
"/issues", "/issues",
"/pulls", "/pulls",
"/commits/master", "/commits/branch/master",
"/graph", "/graph",
"/settings", "/settings",
"/settings/collaboration", "/settings/collaboration",

@ -14,14 +14,14 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func testCreateBranch(t *testing.T, session *TestSession, user, repo, oldRefName, newBranchName string, expectedStatus int) string { func testCreateBranch(t *testing.T, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string {
var csrf string var csrf string
if expectedStatus == http.StatusNotFound { if expectedStatus == http.StatusNotFound {
csrf = GetCSRF(t, session, path.Join(user, repo, "src/master")) csrf = GetCSRF(t, session, path.Join(user, repo, "src/branch/master"))
} else { } else {
csrf = GetCSRF(t, session, path.Join(user, repo, "src", oldRefName)) csrf = GetCSRF(t, session, path.Join(user, repo, "src", oldRefSubURL))
} }
req := NewRequestWithValues(t, "POST", path.Join(user, repo, "branches/_new", oldRefName), map[string]string{ req := NewRequestWithValues(t, "POST", path.Join(user, repo, "branches/_new", oldRefSubURL), map[string]string{
"_csrf": csrf, "_csrf": csrf,
"new_branch_name": newBranchName, "new_branch_name": newBranchName,
}) })
@ -34,72 +34,72 @@ func testCreateBranch(t *testing.T, session *TestSession, user, repo, oldRefName
func TestCreateBranch(t *testing.T) { func TestCreateBranch(t *testing.T) {
tests := []struct { tests := []struct {
OldBranchOrCommit string OldRefSubURL string
NewBranch string NewBranch string
CreateRelease string CreateRelease string
FlashMessage string FlashMessage string
ExpectedStatus int ExpectedStatus int
}{ }{
{ {
OldBranchOrCommit: "master", OldRefSubURL: "branch/master",
NewBranch: "feature/test1", NewBranch: "feature/test1",
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test1"), FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test1"),
}, },
{ {
OldBranchOrCommit: "master", OldRefSubURL: "branch/master",
NewBranch: "", NewBranch: "",
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.require_error"), FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.require_error"),
}, },
{ {
OldBranchOrCommit: "master", OldRefSubURL: "branch/master",
NewBranch: "feature=test1", NewBranch: "feature=test1",
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.git_ref_name_error"), FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.git_ref_name_error"),
}, },
{ {
OldBranchOrCommit: "master", OldRefSubURL: "branch/master",
NewBranch: strings.Repeat("b", 101), NewBranch: strings.Repeat("b", 101),
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.max_size_error", "100"), FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.max_size_error", "100"),
}, },
{ {
OldBranchOrCommit: "master", OldRefSubURL: "branch/master",
NewBranch: "master", NewBranch: "master",
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "repo.branch.branch_already_exists", "master"), FlashMessage: i18n.Tr("en", "repo.branch.branch_already_exists", "master"),
}, },
{ {
OldBranchOrCommit: "master", OldRefSubURL: "branch/master",
NewBranch: "master/test", NewBranch: "master/test",
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "repo.branch.branch_name_conflict", "master/test", "master"), FlashMessage: i18n.Tr("en", "repo.branch.branch_name_conflict", "master/test", "master"),
}, },
{ {
OldBranchOrCommit: "acd1d892867872cb47f3993468605b8aa59aa2e0", OldRefSubURL: "commit/acd1d892867872cb47f3993468605b8aa59aa2e0",
NewBranch: "feature/test2", NewBranch: "feature/test2",
ExpectedStatus: http.StatusNotFound, ExpectedStatus: http.StatusNotFound,
}, },
{ {
OldBranchOrCommit: "65f1bf27bc3bf70f64657658635e66094edbcb4d", OldRefSubURL: "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d",
NewBranch: "feature/test3", NewBranch: "feature/test3",
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test3"), FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test3"),
}, },
{ {
OldBranchOrCommit: "master", OldRefSubURL: "branch/master",
NewBranch: "v1.0.0", NewBranch: "v1.0.0",
CreateRelease: "v1.0.0", CreateRelease: "v1.0.0",
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "repo.branch.tag_collision", "v1.0.0"), FlashMessage: i18n.Tr("en", "repo.branch.tag_collision", "v1.0.0"),
}, },
{ {
OldBranchOrCommit: "v1.0.0", OldRefSubURL: "tag/v1.0.0",
NewBranch: "feature/test4", NewBranch: "feature/test4",
CreateRelease: "v1.0.0", CreateRelease: "v1.0.0",
ExpectedStatus: http.StatusFound, ExpectedStatus: http.StatusFound,
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test4"), FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test4"),
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -108,7 +108,7 @@ func TestCreateBranch(t *testing.T) {
if test.CreateRelease != "" { if test.CreateRelease != "" {
createNewRelease(t, session, "/user2/repo1", test.CreateRelease, test.CreateRelease, false, false) createNewRelease(t, session, "/user2/repo1", test.CreateRelease, test.CreateRelease, false, false)
} }
redirectURL := testCreateBranch(t, session, "user2", "repo1", test.OldBranchOrCommit, test.NewBranch, test.ExpectedStatus) redirectURL := testCreateBranch(t, session, "user2", "repo1", test.OldRefSubURL, test.NewBranch, test.ExpectedStatus)
if test.ExpectedStatus == http.StatusFound { if test.ExpectedStatus == http.StatusFound {
req := NewRequest(t, "GET", redirectURL) req := NewRequest(t, "GET", redirectURL)
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
@ -124,7 +124,7 @@ func TestCreateBranch(t *testing.T) {
func TestCreateBranchInvalidCSRF(t *testing.T) { func TestCreateBranchInvalidCSRF(t *testing.T) {
prepareTestEnv(t) prepareTestEnv(t)
session := loginUser(t, "user2") session := loginUser(t, "user2")
req := NewRequestWithValues(t, "POST", "user2/repo1/branches/_new/master", map[string]string{ req := NewRequestWithValues(t, "POST", "user2/repo1/branches/_new/branch/master", map[string]string{
"_csrf": "fake_csrf", "_csrf": "fake_csrf",
"new_branch_name": "test", "new_branch_name": "test",
}) })

@ -20,7 +20,7 @@ func TestRepoCommits(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
// Request repository commits page // Request repository commits page
req := NewRequest(t, "GET", "/user2/repo1/commits/master") req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body) doc := NewHTMLParser(t, resp.Body)
@ -35,7 +35,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
// Request repository commits page // Request repository commits page
req := NewRequest(t, "GET", "/user2/repo1/commits/master") req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body) doc := NewHTMLParser(t, resp.Body)
@ -56,7 +56,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
resp = session.MakeRequest(t, req, http.StatusCreated) resp = session.MakeRequest(t, req, http.StatusCreated)
req = NewRequest(t, "GET", "/user2/repo1/commits/master") req = NewRequest(t, "GET", "/user2/repo1/commits/branch/master")
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
doc = NewHTMLParser(t, resp.Body) doc = NewHTMLParser(t, resp.Body)

@ -80,12 +80,22 @@ func SlackLinkFormatter(url string, text string) string {
return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text)) return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text))
} }
func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) { // SlackLinkToRef slack-formatter link to a repo ref
// created tag/branch func SlackLinkToRef(repoURL, ref string) string {
refName := git.RefEndName(p.Ref) refName := git.RefEndName(ref)
switch {
case strings.HasPrefix(ref, git.BranchPrefix):
return SlackLinkFormatter(repoURL+"/src/branch/"+refName, refName)
case strings.HasPrefix(ref, git.TagPrefix):
return SlackLinkFormatter(repoURL+"/src/tag/"+refName, refName)
default:
return SlackLinkFormatter(repoURL+"/src/commit/"+refName, refName)
}
}
func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) {
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
refLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName) refLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref)
text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName) text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
return &SlackPayload{ return &SlackPayload{
@ -99,7 +109,6 @@ func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayloa
func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) { func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) {
// n new commits // n new commits
var ( var (
branchName = git.RefEndName(p.Ref)
commitDesc string commitDesc string
commitString string commitString string
) )
@ -116,7 +125,7 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
} }
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name) repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
branchLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+branchName, branchName) branchLink := SlackLinkToRef(p.Repo.HTMLURL, p.Ref)
text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.UserName) text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.UserName)
var attachmentText string var attachmentText string

@ -14,6 +14,7 @@ import (
"code.gitea.io/git" "code.gitea.io/git"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com" "github.com/Unknwon/com"
@ -117,6 +118,20 @@ func (r *Repository) GetCommitsCount() (int64, error) {
}) })
} }
// BranchNameSubURL sub-URL for the BranchName field
func (r *Repository) BranchNameSubURL() string {
switch {
case r.IsViewBranch:
return "branch/" + r.BranchName
case r.IsViewTag:
return "tag/" + r.BranchName
case r.IsViewCommit:
return "commit/" + r.BranchName
}
log.Error(4, "Unknown view type for repo: %v", r)
return ""
}
// GetEditorconfig returns the .editorconfig definition if found in the // GetEditorconfig returns the .editorconfig definition if found in the
// HEAD of the default repo branch. // HEAD of the default repo branch.
func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) { func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
@ -444,8 +459,81 @@ func RepoAssignment() macaron.Handler {
} }
} }
// RepoRef handles repository reference name including those contain `/`. // RepoRefType type of repo reference
type RepoRefType int
const (
// RepoRefLegacy unknown type, make educated guess and redirect.
// for backward compatibility with previous URL scheme
RepoRefLegacy RepoRefType = iota
// RepoRefBranch branch
RepoRefBranch
// RepoRefTag tag
RepoRefTag
// RepoRefCommit commit
RepoRefCommit
)
// RepoRef handles repository reference names when the ref name is not
// explicitly given
func RepoRef() macaron.Handler { func RepoRef() macaron.Handler {
// since no ref name is explicitly specified, ok to just use branch
return RepoRefByType(RepoRefBranch)
}
func getRefNameFromPath(ctx *Context, path string, isExist func(string) bool) string {
refName := ""
parts := strings.Split(path, "/")
for i, part := range parts {
refName = strings.TrimPrefix(refName+"/"+part, "/")
if isExist(refName) {
ctx.Repo.TreePath = strings.Join(parts[i+1:], "/")
return refName
}
}
return ""
}
func getRefName(ctx *Context, pathType RepoRefType) string {
path := ctx.Params("*")
switch pathType {
case RepoRefLegacy:
if refName := getRefName(ctx, RepoRefBranch); len(refName) > 0 {
return refName
}
if refName := getRefName(ctx, RepoRefTag); len(refName) > 0 {
return refName
}
return getRefName(ctx, RepoRefCommit)
case RepoRefBranch:
return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsBranchExist)
case RepoRefTag:
return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist)
case RepoRefCommit:
parts := strings.Split(path, "/")
if len(parts) > 0 && len(parts[0]) == 40 {
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
return parts[0]
}
default:
log.Error(4, "Unrecognized path type: %v", path)
}
return ""
}
// URL to redirect to for deprecated URL scheme
func repoRefRedirect(ctx *Context) string {
urlPath := ctx.Req.URL.String()
idx := strings.LastIndex(urlPath, ctx.Params("*"))
if idx < 0 {
idx = len(urlPath)
}
return path.Join(urlPath[:idx], ctx.Repo.BranchNameSubURL())
}
// RepoRefByType handles repository reference name for a specific type
// of repository reference
func RepoRefByType(refType RepoRefType) macaron.Handler {
return func(ctx *Context) { return func(ctx *Context) {
// Empty repository does not have reference information. // Empty repository does not have reference information.
if ctx.Repo.Repository.IsBare { if ctx.Repo.Repository.IsBare {
@ -470,6 +558,7 @@ func RepoRef() macaron.Handler {
// Get default branch. // Get default branch.
if len(ctx.Params("*")) == 0 { if len(ctx.Params("*")) == 0 {
refName = ctx.Repo.Repository.DefaultBranch refName = ctx.Repo.Repository.DefaultBranch
ctx.Repo.BranchName = refName
if !ctx.Repo.GitRepo.IsBranchExist(refName) { if !ctx.Repo.GitRepo.IsBranchExist(refName) {
brs, err := ctx.Repo.GitRepo.GetBranches() brs, err := ctx.Repo.GitRepo.GetBranches()
if err != nil { if err != nil {
@ -492,25 +581,8 @@ func RepoRef() macaron.Handler {
ctx.Repo.IsViewBranch = true ctx.Repo.IsViewBranch = true
} else { } else {
hasMatched := false refName = getRefName(ctx, refType)
parts := strings.Split(ctx.Params("*"), "/") ctx.Repo.BranchName = refName
for i, part := range parts {
refName = strings.TrimPrefix(refName+"/"+part, "/")
if ctx.Repo.GitRepo.IsBranchExist(refName) ||
ctx.Repo.GitRepo.IsTagExist(refName) {
if i < len(parts)-1 {
ctx.Repo.TreePath = strings.Join(parts[i+1:], "/")
}
hasMatched = true
break
}
}
if !hasMatched && len(parts[0]) == 40 {
refName = parts[0]
ctx.Repo.TreePath = strings.Join(parts[1:], "/")
}
if ctx.Repo.GitRepo.IsBranchExist(refName) { if ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.IsViewBranch = true ctx.Repo.IsViewBranch = true
@ -542,10 +614,16 @@ func RepoRef() macaron.Handler {
ctx.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) ctx.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
return return
} }
if refType == RepoRefLegacy {
// redirect from old URL scheme to new URL scheme
ctx.Redirect(repoRefRedirect(ctx))
return
}
} }
ctx.Repo.BranchName = refName
ctx.Data["BranchName"] = ctx.Repo.BranchName ctx.Data["BranchName"] = ctx.Repo.BranchName
ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
ctx.Data["CommitID"] = ctx.Repo.CommitID ctx.Data["CommitID"] = ctx.Repo.CommitID
ctx.Data["TreePath"] = ctx.Repo.TreePath ctx.Data["TreePath"] = ctx.Repo.TreePath
ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch

@ -391,7 +391,7 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(reqToken(), bind(api.CreateForkOption{}), repo.CreateFork) Post(reqToken(), bind(api.CreateForkOption{}), repo.CreateFork)
m.Group("/branches", func() { m.Group("/branches", func() {
m.Get("", repo.ListBranches) m.Get("", repo.ListBranches)
m.Get("/*", context.RepoRef(), repo.GetBranch) m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
}) })
m.Group("/keys", func() { m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys). m.Combo("").Get(repo.ListDeployKeys).

@ -202,7 +202,7 @@ func CreateBranch(ctx *context.Context, form auth.NewBranchForm) {
if ctx.HasError() { if ctx.HasError() {
ctx.Flash.Error(ctx.GetErrMsg()) ctx.Flash.Error(ctx.GetErrMsg())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return return
} }
@ -216,19 +216,19 @@ func CreateBranch(ctx *context.Context, form auth.NewBranchForm) {
if models.IsErrTagAlreadyExists(err) { if models.IsErrTagAlreadyExists(err) {
e := err.(models.ErrTagAlreadyExists) e := err.(models.ErrTagAlreadyExists)
ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName)) ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return return
} }
if models.IsErrBranchAlreadyExists(err) { if models.IsErrBranchAlreadyExists(err) {
e := err.(models.ErrBranchAlreadyExists) e := err.(models.ErrBranchAlreadyExists)
ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", e.BranchName)) ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", e.BranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return return
} }
if models.IsErrBranchNameConflict(err) { if models.IsErrBranchNameConflict(err) {
e := err.(models.ErrBranchNameConflict) e := err.(models.ErrBranchNameConflict)
ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return return
} }
@ -237,5 +237,5 @@ func CreateBranch(ctx *context.Context, form auth.NewBranchForm) {
} }
ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName)) ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + form.NewBranchName) ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + form.NewBranchName)
} }

@ -120,7 +120,7 @@ func SearchCommits(ctx *context.Context) {
keyword := strings.Trim(ctx.Query("q"), " ") keyword := strings.Trim(ctx.Query("q"), " ")
if len(keyword) == 0 { if len(keyword) == 0 {
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchName) ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchNameSubURL())
return return
} }
all := ctx.QueryBool("all") all := ctx.QueryBool("all")

@ -113,7 +113,7 @@ func editFile(ctx *context.Context, isNewFile bool) {
ctx.Data["TreeNames"] = treeNames ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths ctx.Data["TreePaths"] = treePaths
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["commit_summary"] = "" ctx.Data["commit_summary"] = ""
ctx.Data["commit_message"] = "" ctx.Data["commit_message"] = ""
if canCommit { if canCommit {
@ -164,7 +164,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
ctx.Data["TreePath"] = form.TreePath ctx.Data["TreePath"] = form.TreePath
ctx.Data["TreeNames"] = treeNames ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths ctx.Data["TreePaths"] = treePaths
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + branchName ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + branchName
ctx.Data["FileContent"] = form.Content ctx.Data["FileContent"] = form.Content
ctx.Data["commit_summary"] = form.CommitSummary ctx.Data["commit_summary"] = form.CommitSummary
ctx.Data["commit_message"] = form.CommitMessage ctx.Data["commit_message"] = form.CommitMessage
@ -304,7 +304,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + branchName + "/" + strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(form.TreePath)) ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + branchName + "/" + strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(form.TreePath))
} }
// EditFilePost response for editing file // EditFilePost response for editing file
@ -348,7 +348,7 @@ func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
// DeleteFile render delete file page // DeleteFile render delete file page
func DeleteFile(ctx *context.Context) { func DeleteFile(ctx *context.Context) {
ctx.Data["PageIsDelete"] = true ctx.Data["PageIsDelete"] = true
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["TreePath"] = ctx.Repo.TreePath ctx.Data["TreePath"] = ctx.Repo.TreePath
canCommit := renderCommitRights(ctx) canCommit := renderCommitRights(ctx)
@ -367,7 +367,7 @@ func DeleteFile(ctx *context.Context) {
// DeleteFilePost response for deleting file // DeleteFilePost response for deleting file
func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) { func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
ctx.Data["PageIsDelete"] = true ctx.Data["PageIsDelete"] = true
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["TreePath"] = ctx.Repo.TreePath ctx.Data["TreePath"] = ctx.Repo.TreePath
canCommit := renderCommitRights(ctx) canCommit := renderCommitRights(ctx)
@ -422,7 +422,7 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
} }
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath)) ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + branchName) ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + branchName)
} }
func renderUploadSettings(ctx *context.Context) { func renderUploadSettings(ctx *context.Context) {
@ -446,7 +446,7 @@ func UploadFile(ctx *context.Context) {
ctx.Data["TreeNames"] = treeNames ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths ctx.Data["TreePaths"] = treePaths
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["commit_summary"] = "" ctx.Data["commit_summary"] = ""
ctx.Data["commit_message"] = "" ctx.Data["commit_message"] = ""
if canCommit { if canCommit {
@ -482,7 +482,7 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
ctx.Data["TreePath"] = form.TreePath ctx.Data["TreePath"] = form.TreePath
ctx.Data["TreeNames"] = treeNames ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths ctx.Data["TreePaths"] = treePaths
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + branchName ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + branchName
ctx.Data["commit_summary"] = form.CommitSummary ctx.Data["commit_summary"] = form.CommitSummary
ctx.Data["commit_message"] = form.CommitMessage ctx.Data["commit_message"] = form.CommitMessage
ctx.Data["commit_choice"] = form.CommitChoice ctx.Data["commit_choice"] = form.CommitChoice
@ -551,7 +551,7 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + branchName + "/" + form.TreePath) ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + branchName + "/" + form.TreePath)
} }
// UploadFileToServer upload file to server file dir not git // UploadFileToServer upload file to server file dir not git

@ -34,6 +34,21 @@ func MustBeNotBare(ctx *context.Context) {
} }
} }
// MustBeEditable check that repo can be edited
func MustBeEditable(ctx *context.Context) {
if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
ctx.Handle(404, "", nil)
return
}
}
// MustBeAbleToUpload check that repo can be uploaded to
func MustBeAbleToUpload(ctx *context.Context) {
if !setting.Repository.Upload.Enabled {
ctx.Handle(404, "", nil)
}
}
func checkContextUser(ctx *context.Context, uid int64) *models.User { func checkContextUser(ctx *context.Context, uid int64) *models.User {
orgs, err := models.GetOwnedOrgsByUserIDDesc(ctx.User.ID, "updated_unix") orgs, err := models.GetOwnedOrgsByUserIDDesc(ctx.User.ID, "updated_unix")
if err != nil { if err != nil {

@ -297,9 +297,9 @@ func renderCode(ctx *context.Context) {
ctx.Data["Title"] = title ctx.Data["Title"] = title
ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireHighlightJS"] = true
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
treeLink := branchLink treeLink := branchLink
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchName rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
if len(ctx.Repo.TreePath) > 0 { if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + ctx.Repo.TreePath treeLink += "/" + ctx.Repo.TreePath

@ -522,34 +522,30 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost) Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
m.Group("", func() { m.Group("", func() {
m.Combo("/_edit/*").Get(repo.EditFile).
Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
m.Combo("/_new/*").Get(repo.NewFile).
Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
m.Combo("/_delete/*").Get(repo.DeleteFile).
Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
m.Group("", func() { m.Group("", func() {
m.Combo("/_upload/*").Get(repo.UploadFile). m.Combo("/_edit/*").Get(repo.EditFile).
Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
m.Combo("/_new/*").Get(repo.NewFile).
Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
m.Combo("/_delete/*").Get(repo.DeleteFile).
Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
m.Combo("/_upload/*", repo.MustBeAbleToUpload).
Get(repo.UploadFile).
Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost) Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
}, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable)
m.Group("", func() {
m.Post("/upload-file", repo.UploadFileToServer) m.Post("/upload-file", repo.UploadFileToServer)
m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
}, func(ctx *context.Context) { }, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload)
if !setting.Repository.Upload.Enabled { }, repo.MustBeNotBare, reqRepoWriter)
ctx.Handle(404, "", nil)
return
}
})
}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
ctx.Handle(404, "", nil)
return
}
})
m.Group("/branches", func() { m.Group("/branches", func() {
m.Post("/_new/*", context.RepoRef(), bindIgnErr(auth.NewBranchForm{}), repo.CreateBranch) m.Group("/_new/", func() {
m.Post("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.CreateBranch)
m.Post("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.CreateBranch)
m.Post("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.CreateBranch)
}, bindIgnErr(auth.NewBranchForm{}))
m.Post("/delete", repo.DeleteBranchPost) m.Post("/delete", repo.DeleteBranchPost)
m.Post("/restore", repo.RestoreBranchPost) m.Post("/restore", repo.RestoreBranchPost)
}, reqRepoWriter, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode)) }, reqRepoWriter, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode))
@ -629,15 +625,36 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/cleanup", context.RepoRef(), repo.CleanUpPullRequest) m.Post("/cleanup", context.RepoRef(), repo.CleanUpPullRequest)
}, repo.MustAllowPulls) }, repo.MustAllowPulls)
m.Group("/raw", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownload)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownload)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownload)
// "/*" route is deprecated, and kept for backward compatibility
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload)
}, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode))
m.Group("/commits", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefCommits)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits)
// "/*" route is deprecated, and kept for backward compatibility
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.RefCommits)
}, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode))
m.Group("", func() { m.Group("", func() {
m.Get("/raw/*", repo.SingleDownload)
m.Get("/commits/*", repo.RefCommits)
m.Get("/graph", repo.Graph) m.Get("/graph", repo.Graph)
m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff) m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
}, repo.MustBeNotBare, context.RepoRef(), context.CheckUnit(models.UnitTypeCode)) }, repo.MustBeNotBare, context.RepoRef(), context.CheckUnit(models.UnitTypeCode))
m.Group("/src", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home)
// "/*" route is deprecated, and kept for backward compatibility
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Home)
}, repo.SetEditorconfigIfExists)
m.Group("", func() { m.Group("", func() {
m.Get("/src/*", repo.SetEditorconfigIfExists, repo.Home)
m.Get("/forks", repo.Forks) m.Get("/forks", repo.Forks)
}, context.RepoRef(), context.CheckUnit(models.UnitTypeCode)) }, context.RepoRef(), context.CheckUnit(models.UnitTypeCode))
m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)",

@ -10,10 +10,10 @@
</div> </div>
<div class="data" style="display: none" data-mode="{{if .IsViewTag}}tags{{else}}branches{{end}}"> <div class="data" style="display: none" data-mode="{{if .IsViewTag}}tags{{else}}branches{{end}}">
{{range .Branches}} {{range .Branches}}
<div class="item branch {{if eq $.BranchName .}}selected{{end}}" data-url="{{$.RepoLink}}/{{if $.PageIsCommits}}commits{{else}}src{{end}}/{{EscapePound .}}{{if $.TreePath}}/{{EscapePound $.TreePath}}{{end}}">{{.}}</div> <div class="item branch {{if eq $.BranchName .}}selected{{end}}" data-url="{{$.RepoLink}}/{{if $.PageIsCommits}}commits{{else}}src{{end}}/branch/{{EscapePound .}}{{if $.TreePath}}/{{EscapePound $.TreePath}}{{end}}">{{.}}</div>
{{end}} {{end}}
{{range .Tags}} {{range .Tags}}
<div class="item tag {{if eq $.BranchName .}}selected{{end}}" data-url="{{$.RepoLink}}/{{if $.PageIsCommits}}commits{{else}}src{{end}}/{{EscapePound .}}{{if $.TreePath}}/{{EscapePound $.TreePath}}{{end}}">{{.}}</div> <div class="item tag {{if eq $.BranchName .}}selected{{end}}" data-url="{{$.RepoLink}}/{{if $.PageIsCommits}}commits{{else}}src{{end}}/tag/{{EscapePound .}}{{if $.TreePath}}/{{EscapePound $.TreePath}}{{end}}">{{.}}</div>
{{end}} {{end}}
</div> </div>
<div class="menu transition" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak> <div class="menu transition" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>

@ -2,7 +2,7 @@
{{.CommitCount}} {{.i18n.Tr "repo.commits.commits"}} {{if .Branch}}({{.Branch}}){{end}} {{.CommitCount}} {{.i18n.Tr "repo.commits.commits"}} {{if .Branch}}({{.Branch}}){{end}}
{{if .PageIsCommits}} {{if .PageIsCommits}}
<div class="ui right"> <div class="ui right">
<form action="{{.RepoLink}}/commits/{{.BranchName}}/search"> <form action="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/search">
<div class="ui tiny search input"> <div class="ui tiny search input">
<input name="q" placeholder="{{.i18n.Tr "repo.commits.search"}}" value="{{.Keyword}}" autofocus> <input name="q" placeholder="{{.i18n.Tr "repo.commits.search"}}" value="{{.Keyword}}" autofocus>
</div> </div>
@ -75,17 +75,17 @@
{{if gt .TotalPages 1}} {{if gt .TotalPages 1}}
<div class="center page buttons"> <div class="center page buttons">
<div class="ui borderless pagination menu"> <div class="ui borderless pagination menu">
<a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.RepoLink}}/commits/{{$.BranchName}}{{if $.FileName}}/{{$.FileName}}{{end}}?page={{.Previous}}"{{end}}> <a class="{{if not .HasPrevious}}disabled{{end}} item" {{if .HasPrevious}}href="{{$.RepoLink}}/commits/{{$.BranchNameSubURL}}{{if $.FileName}}/{{$.FileName}}{{end}}?page={{.Previous}}"{{end}}>
<i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}} <i class="left arrow icon"></i> {{$.i18n.Tr "repo.issues.previous"}}
</a> </a>
{{range .Pages}} {{range .Pages}}
{{if eq .Num -1}} {{if eq .Num -1}}
<a class="disabled item">...</a> <a class="disabled item">...</a>
{{else}} {{else}}
<a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.RepoLink}}/commits/{{$.BranchName}}{{if $.FileName}}/{{$.FileName}}{{end}}?page={{.Num}}"{{end}}>{{.Num}}</a> <a class="{{if .IsCurrent}}active{{end}} item" {{if not .IsCurrent}}href="{{$.RepoLink}}/commits/{{$.BranchNameSubURL}}{{if $.FileName}}/{{$.FileName}}{{end}}?page={{.Num}}"{{end}}>{{.Num}}</a>
{{end}} {{end}}
{{end}} {{end}}
<a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.RepoLink}}/commits/{{$.BranchName}}{{if $.FileName}}/{{$.FileName}}{{end}}?page={{.Next}}"{{end}}> <a class="{{if not .HasNext}}disabled{{end}} item" {{if .HasNext}}href="{{$.RepoLink}}/commits/{{$.BranchNameSubURL}}{{if $.FileName}}/{{$.FileName}}{{end}}?page={{.Next}}"{{end}}>
{{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i> {{$.i18n.Tr "repo.issues.next"}} <i class="icon right arrow"></i>
</a> </a>
</div> </div>

@ -30,7 +30,7 @@
<div class="ui top attached tabular menu" data-write="write" data-preview="preview" data-diff="diff"> <div class="ui top attached tabular menu" data-write="write" data-preview="preview" data-diff="diff">
<a class="active item" data-tab="write"><i class="octicon octicon-code"></i> {{if .IsNewFile}}{{.i18n.Tr "repo.editor.new_file"}}{{else}}{{.i18n.Tr "repo.editor.edit_file"}}{{end}}</a> <a class="active item" data-tab="write"><i class="octicon octicon-code"></i> {{if .IsNewFile}}{{.i18n.Tr "repo.editor.new_file"}}{{else}}{{.i18n.Tr "repo.editor.edit_file"}}{{end}}</a>
{{if not .IsNewFile}} {{if not .IsNewFile}}
<a class="item" data-tab="preview" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.RepoLink}}/src/{{.BranchName}}" data-preview-file-modes="{{.PreviewableFileModes}}"><i class="octicon octicon-eye"></i> {{.i18n.Tr "repo.release.preview"}}</a> <a class="item" data-tab="preview" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{.RepoLink}}/src/{{.BranchNameSubURL}}" data-preview-file-modes="{{.PreviewableFileModes}}"><i class="octicon octicon-eye"></i> {{.i18n.Tr "repo.release.preview"}}</a>
<a class="item" data-tab="diff" data-url="{{.RepoLink}}/_preview/{{.BranchName}}/{{.TreePath}}" data-context="{{.BranchLink}}"><i class="octicon octicon-diff"></i> {{.i18n.Tr "repo.editor.preview_changes"}}</a> <a class="item" data-tab="diff" data-url="{{.RepoLink}}/_preview/{{.BranchName}}/{{.TreePath}}" data-context="{{.BranchLink}}"><i class="octicon octicon-diff"></i> {{.i18n.Tr "repo.editor.preview_changes"}}</a>
{{end}} {{end}}
</div> </div>

@ -35,7 +35,7 @@
{{template "repo/branch_dropdown" .}} {{template "repo/branch_dropdown" .}}
{{ $n := len .TreeNames}} {{ $n := len .TreeNames}}
{{ $l := Subtract $n 1}} {{ $l := Subtract $n 1}}
<div class="fitted item"><span class="ui breadcrumb repo-path"><a class="section" href="{{.RepoLink}}/src/{{EscapePound .BranchName}}">{{EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span class="divider">/</span>{{if eq $i $l}}<span class="active section">{{EllipsisString $v 30}}</span>{{else}}{{ $p := index $.Paths $i}}<span class="section"><a href="{{EscapePound $.BranchLink}}/{{EscapePound $p}}">{{EllipsisString $v 30}}</a></span>{{end}}{{end}}</span></div> <div class="fitted item"><span class="ui breadcrumb repo-path"><a class="section" href="{{.RepoLink}}/src/{{EscapePound .BranchNameSubURL}}">{{EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span class="divider">/</span>{{if eq $i $l}}<span class="active section">{{EllipsisString $v 30}}</span>{{else}}{{ $p := index $.Paths $i}}<span class="section"><a href="{{EscapePound $.BranchLink}}/{{EscapePound $p}}">{{EllipsisString $v 30}}</a></span>{{end}}{{end}}</span></div>
<div class="right fitted item"> <div class="right fitted item">
{{if .Repository.CanEnableEditor}} {{if .Repository.CanEnableEditor}}
<div id="file-buttons" class="ui tiny blue buttons"> <div id="file-buttons" class="ui tiny blue buttons">

@ -172,7 +172,7 @@
<a class="title has-emoji" href="{{$.Link}}/{{.Index}}">{{.Title}}</a> <a class="title has-emoji" href="{{$.Link}}/{{.Index}}">{{.Title}}</a>
{{if .Ref}} {{if .Ref}}
<a class="ui label" href="{{$.RepoLink}}/src/{{.Ref}}">{{.Ref}}</a> <a class="ui label" href="{{$.RepoLink}}/src/commit/{{.Ref}}">{{.Ref}}</a>
{{end}} {{end}}
{{range .Labels}} {{range .Labels}}
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{.Name | Sanitize}}</a> <a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{.Name | Sanitize}}</a>

@ -28,26 +28,26 @@
<span class="ui green label">{{$.i18n.Tr "repo.release.stable"}}</span> <span class="ui green label">{{$.i18n.Tr "repo.release.stable"}}</span>
{{end}} {{end}}
<span class="tag text blue"> <span class="tag text blue">
<a href="{{$.RepoLink}}/src/{{.TagName}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a> <a href="{{$.RepoLink}}/src/tag/{{.TagName}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a>
</span> </span>
<span class="commit"> <span class="commit">
<a href="{{$.RepoLink}}/src/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a> <a href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a>
</span> </span>
{{end}} {{end}}
</div> </div>
<div class="ui twelve wide column detail"> <div class="ui twelve wide column detail">
{{if .IsTag}} {{if .IsTag}}
<h4> <h4>
<a href="{{$.RepoLink}}/src/{{.TagName}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a> <a href="{{$.RepoLink}}/src/tag/{{.TagName}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a>
</h4> </h4>
<div class="download"> <div class="download">
<a href="{{$.RepoLink}}/src/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a> <a href="{{$.RepoLink}}/src/commit/{{.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}}.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> <a href="{{$.RepoLink}}/archive/{{.TagName}}.tar.gz"><i class="octicon octicon-file-zip"></i> TAR.GZ</a>
</div> </div>
{{else}} {{else}}
<h3> <h3>
<a href="{{$.RepoLink}}/src/{{.TagName}}">{{.Title}}</a> <a href="{{$.RepoLink}}/src/tag/{{.TagName}}">{{.Title}}</a>
{{if $.IsRepositoryWriter}}<small>(<a href="{{$.RepoLink}}/releases/edit/{{.TagName}}" rel="nofollow">{{$.i18n.Tr "repo.release.edit"}}</a>)</small>{{end}} {{if $.IsRepositoryWriter}}<small>(<a href="{{$.RepoLink}}/releases/edit/{{.TagName}}" rel="nofollow">{{$.i18n.Tr "repo.release.edit"}}</a>)</small>{{end}}
</h3> </h3>
<p class="text grey"> <p class="text grey">

@ -15,9 +15,9 @@
<div class="ui right file-actions"> <div class="ui right file-actions">
<div class="ui buttons"> <div class="ui buttons">
{{if not .IsViewCommit}} {{if not .IsViewCommit}}
<a class="ui button" href="{{.RepoLink}}/src/{{.CommitID}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.file_permalink"}}</a> <a class="ui button" href="{{.RepoLink}}/src/commit/{{.CommitID}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.file_permalink"}}</a>
{{end}} {{end}}
<a class="ui button" href="{{.RepoLink}}/commits/{{EscapePound .BranchName}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.file_history"}}</a> <a class="ui button" href="{{.RepoLink}}/commits/{{EscapePound .BranchNameSubURL}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.file_history"}}</a>
<a class="ui button" href="{{EscapePound $.RawFileLink}}">{{.i18n.Tr "repo.file_raw"}}</a> <a class="ui button" href="{{EscapePound $.RawFileLink}}">{{.i18n.Tr "repo.file_raw"}}</a>
</div> </div>
{{if .Repository.CanEnableEditor}} {{if .Repository.CanEnableEditor}}

Loading…
Cancel
Save