Add branch overiew page (#2108)
* Add branch overiew page * fix changed method name on sub menu * remove unused code
This commit is contained in:
parent
e86a0bf3fe
commit
3ab580c8d6
21 changed files with 701 additions and 52 deletions
79
integrations/branches_test.go
Normal file
79
integrations/branches_test.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// 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 integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
"github.com/Unknwon/i18n"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestViewBranches(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", "/user2/repo1/branches")
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
_, exists := htmlDoc.doc.Find(".delete-branch-button").Attr("data-url")
|
||||||
|
assert.False(t, exists, "The template has changed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteBranch(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
|
||||||
|
deleteBranch(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUndoDeleteBranch(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
|
||||||
|
deleteBranch(t)
|
||||||
|
htmlDoc, name := branchAction(t, ".undo-button")
|
||||||
|
assert.Contains(t,
|
||||||
|
htmlDoc.doc.Find(".ui.positive.message").Text(),
|
||||||
|
i18n.Tr("en", "repo.branch.restore_success", name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteBranch(t *testing.T) {
|
||||||
|
htmlDoc, name := branchAction(t, ".delete-branch-button")
|
||||||
|
assert.Contains(t,
|
||||||
|
htmlDoc.doc.Find(".ui.positive.message").Text(),
|
||||||
|
i18n.Tr("en", "repo.branch.deletion_success", name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func branchAction(t *testing.T, button string) (*HTMLDoc, string) {
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
req := NewRequest(t, "GET", "/user2/repo1/branches")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
link, exists := htmlDoc.doc.Find(button).Attr("data-url")
|
||||||
|
assert.True(t, exists, "The template has changed")
|
||||||
|
|
||||||
|
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||||
|
req = NewRequestWithValues(t, "POST", link, map[string]string{
|
||||||
|
"_csrf": getCsrf(htmlDoc.doc),
|
||||||
|
})
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
url, err := url.Parse(link)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req = NewRequest(t, "GET", "/user2/repo1/branches")
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
return NewHTMLParser(t, resp.Body), url.Query()["name"][0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCsrf(doc *goquery.Document) string {
|
||||||
|
csrf, _ := doc.Find("meta[name=\"_csrf\"]").Attr("content")
|
||||||
|
return csrf
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/Unknwon/com"
|
"github.com/Unknwon/com"
|
||||||
|
@ -193,3 +194,109 @@ func (repo *Repository) DeleteProtectedBranch(id int64) (err error) {
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeletedBranch struct
|
||||||
|
type DeletedBranch struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||||
|
Name string `xorm:"UNIQUE(s) NOT NULL"`
|
||||||
|
Commit string `xorm:"UNIQUE(s) NOT NULL"`
|
||||||
|
DeletedByID int64 `xorm:"INDEX"`
|
||||||
|
DeletedBy *User `xorm:"-"`
|
||||||
|
Deleted time.Time `xorm:"-"`
|
||||||
|
DeletedUnix int64 `xorm:"INDEX created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
|
||||||
|
func (deletedBranch *DeletedBranch) AfterLoad() {
|
||||||
|
deletedBranch.Deleted = time.Unix(deletedBranch.DeletedUnix, 0).Local()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDeletedBranch adds a deleted branch to the database
|
||||||
|
func (repo *Repository) AddDeletedBranch(branchName, commit string, deletedByID int64) error {
|
||||||
|
deletedBranch := &DeletedBranch{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Name: branchName,
|
||||||
|
Commit: commit,
|
||||||
|
DeletedByID: deletedByID,
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err := sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sess.InsertOne(deletedBranch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeletedBranches returns all the deleted branches
|
||||||
|
func (repo *Repository) GetDeletedBranches() ([]*DeletedBranch, error) {
|
||||||
|
deletedBranches := make([]*DeletedBranch, 0)
|
||||||
|
return deletedBranches, x.Where("repo_id = ?", repo.ID).Desc("deleted_unix").Find(&deletedBranches)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeletedBranchByID get a deleted branch by its ID
|
||||||
|
func (repo *Repository) GetDeletedBranchByID(ID int64) (*DeletedBranch, error) {
|
||||||
|
deletedBranch := &DeletedBranch{ID: ID}
|
||||||
|
has, err := x.Get(deletedBranch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return deletedBranch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDeletedBranch removes a deleted branch from the database
|
||||||
|
func (repo *Repository) RemoveDeletedBranch(id int64) (err error) {
|
||||||
|
deletedBranch := &DeletedBranch{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
ID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sess.Close()
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if affected, err := sess.Delete(deletedBranch); err != nil {
|
||||||
|
return err
|
||||||
|
} else if affected != 1 {
|
||||||
|
return fmt.Errorf("remove deleted branch ID(%v) failed", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadUser loads the user that deleted the branch
|
||||||
|
// When there's no user found it returns a NewGhostUser
|
||||||
|
func (deletedBranch *DeletedBranch) LoadUser() {
|
||||||
|
user, err := GetUserByID(deletedBranch.DeletedByID)
|
||||||
|
if err != nil {
|
||||||
|
user = NewGhostUser()
|
||||||
|
}
|
||||||
|
deletedBranch.DeletedBy = user
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveOldDeletedBranches removes old deleted branches
|
||||||
|
func RemoveOldDeletedBranches() {
|
||||||
|
if !taskStatusTable.StartIfNotRunning(`deleted_branches_cleanup`) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer taskStatusTable.Stop(`deleted_branches_cleanup`)
|
||||||
|
|
||||||
|
log.Trace("Doing: DeletedBranchesCleanup")
|
||||||
|
|
||||||
|
deleteBefore := time.Now().Add(-setting.Cron.DeletedBranchesCleanup.OlderThan)
|
||||||
|
_, err := x.Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(4, "DeletedBranchesCleanup: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
89
models/branches_test.go
Normal file
89
models/branches_test.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 models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var firstBranch = DeletedBranch{
|
||||||
|
ID: 1,
|
||||||
|
Name: "foo",
|
||||||
|
Commit: "1213212312313213213132131",
|
||||||
|
DeletedByID: int64(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
var secondBranch = DeletedBranch{
|
||||||
|
ID: 2,
|
||||||
|
Name: "bar",
|
||||||
|
Commit: "5655464564554545466464655",
|
||||||
|
DeletedByID: int64(99),
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddDeletedBranch(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||||
|
assert.NoError(t, repo.AddDeletedBranch(firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID))
|
||||||
|
assert.Error(t, repo.AddDeletedBranch(firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID))
|
||||||
|
assert.NoError(t, repo.AddDeletedBranch(secondBranch.Name, secondBranch.Commit, secondBranch.DeletedByID))
|
||||||
|
}
|
||||||
|
func TestGetDeletedBranches(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1})
|
||||||
|
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||||
|
|
||||||
|
branches, err := repo.GetDeletedBranches()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, branches, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDeletedBranch(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
assert.NotNil(t, getDeletedBranch(t, firstBranch))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeletedBranchLoadUser(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
branch := getDeletedBranch(t, firstBranch)
|
||||||
|
assert.Nil(t, branch.DeletedBy)
|
||||||
|
branch.LoadUser()
|
||||||
|
assert.NotNil(t, branch.DeletedBy)
|
||||||
|
assert.Equal(t, "user1", branch.DeletedBy.Name)
|
||||||
|
|
||||||
|
branch = getDeletedBranch(t, secondBranch)
|
||||||
|
assert.Nil(t, branch.DeletedBy)
|
||||||
|
branch.LoadUser()
|
||||||
|
assert.NotNil(t, branch.DeletedBy)
|
||||||
|
assert.Equal(t, "Ghost", branch.DeletedBy.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveDeletedBranch(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
|
||||||
|
branch := DeletedBranch{ID: 1}
|
||||||
|
AssertExistsAndLoadBean(t, &branch)
|
||||||
|
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||||
|
|
||||||
|
err := repo.RemoveDeletedBranch(1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
AssertNotExistsBean(t, &branch)
|
||||||
|
AssertExistsAndLoadBean(t, &DeletedBranch{ID: 2})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDeletedBranch(t *testing.T, branch DeletedBranch) *DeletedBranch {
|
||||||
|
AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1})
|
||||||
|
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
|
||||||
|
|
||||||
|
deletedBranch, err := repo.GetDeletedBranchByID(branch.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, branch.ID, deletedBranch.ID)
|
||||||
|
assert.Equal(t, branch.Name, deletedBranch.Name)
|
||||||
|
assert.Equal(t, branch.Commit, deletedBranch.Commit)
|
||||||
|
assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID)
|
||||||
|
|
||||||
|
return deletedBranch
|
||||||
|
}
|
|
@ -142,6 +142,8 @@ var migrations = []Migration{
|
||||||
NewMigration("remove index column from repo_unit table", removeIndexColumnFromRepoUnitTable),
|
NewMigration("remove index column from repo_unit table", removeIndexColumnFromRepoUnitTable),
|
||||||
// v46 -> v47
|
// v46 -> v47
|
||||||
NewMigration("remove organization watch repositories", removeOrganizationWatchRepo),
|
NewMigration("remove organization watch repositories", removeOrganizationWatchRepo),
|
||||||
|
// v47 -> v48
|
||||||
|
NewMigration("add deleted branches", addDeletedBranch),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate database to current version
|
// Migrate database to current version
|
||||||
|
|
29
models/migrations/v47.go
Normal file
29
models/migrations/v47.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// 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 addDeletedBranch(x *xorm.Engine) (err error) {
|
||||||
|
// DeletedBranch contains the deleted branch information
|
||||||
|
type DeletedBranch struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||||
|
Name string `xorm:"UNIQUE(s) NOT NULL"`
|
||||||
|
Commit string `xorm:"UNIQUE(s) NOT NULL"`
|
||||||
|
DeletedByID int64 `xorm:"INDEX NOT NULL"`
|
||||||
|
DeletedUnix int64 `xorm:"INDEX"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = x.Sync2(new(DeletedBranch)); err != nil {
|
||||||
|
return fmt.Errorf("Sync2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -114,6 +114,7 @@ func init() {
|
||||||
new(CommitStatus),
|
new(CommitStatus),
|
||||||
new(Stopwatch),
|
new(Stopwatch),
|
||||||
new(TrackedTime),
|
new(TrackedTime),
|
||||||
|
new(DeletedBranch),
|
||||||
)
|
)
|
||||||
|
|
||||||
gonicNames := []string{"SSL", "UID"}
|
gonicNames := []string{"SSL", "UID"}
|
||||||
|
|
|
@ -77,6 +77,17 @@ func NewContext() {
|
||||||
go models.SyncExternalUsers()
|
go models.SyncExternalUsers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if setting.Cron.DeletedBranchesCleanup.Enabled {
|
||||||
|
entry, err = c.AddFunc("Remove old deleted branches", setting.Cron.DeletedBranchesCleanup.Schedule, models.RemoveOldDeletedBranches)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(4, "Cron[Remove old deleted branches]: %v", err)
|
||||||
|
}
|
||||||
|
if setting.Cron.DeletedBranchesCleanup.RunAtStart {
|
||||||
|
entry.Prev = time.Now()
|
||||||
|
entry.ExecTimes++
|
||||||
|
go models.RemoveOldDeletedBranches()
|
||||||
|
}
|
||||||
|
}
|
||||||
c.Start()
|
c.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -365,6 +365,12 @@ var (
|
||||||
Schedule string
|
Schedule string
|
||||||
UpdateExisting bool
|
UpdateExisting bool
|
||||||
} `ini:"cron.sync_external_users"`
|
} `ini:"cron.sync_external_users"`
|
||||||
|
DeletedBranchesCleanup struct {
|
||||||
|
Enabled bool
|
||||||
|
RunAtStart bool
|
||||||
|
Schedule string
|
||||||
|
OlderThan time.Duration
|
||||||
|
} `ini:"cron.deleted_branches_cleanup"`
|
||||||
}{
|
}{
|
||||||
UpdateMirror: struct {
|
UpdateMirror: struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
@ -419,6 +425,17 @@ var (
|
||||||
Schedule: "@every 24h",
|
Schedule: "@every 24h",
|
||||||
UpdateExisting: true,
|
UpdateExisting: true,
|
||||||
},
|
},
|
||||||
|
DeletedBranchesCleanup: struct {
|
||||||
|
Enabled bool
|
||||||
|
RunAtStart bool
|
||||||
|
Schedule string
|
||||||
|
OlderThan time.Duration
|
||||||
|
}{
|
||||||
|
Enabled: true,
|
||||||
|
RunAtStart: true,
|
||||||
|
Schedule: "@every 24h",
|
||||||
|
OlderThan: 24 * time.Hour,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Git settings
|
// Git settings
|
||||||
|
|
|
@ -1055,10 +1055,16 @@ release.tag_name_already_exist = Release with this tag name already exists.
|
||||||
release.tag_name_invalid = Tag name is not valid.
|
release.tag_name_invalid = Tag name is not valid.
|
||||||
release.downloads = Downloads
|
release.downloads = Downloads
|
||||||
|
|
||||||
|
branch.name = Branch name
|
||||||
|
branch.search = Search branches
|
||||||
|
branch.already_exists = A branch named %s already exists.
|
||||||
|
branch.delete_head = Delete
|
||||||
branch.delete = Delete Branch %s
|
branch.delete = Delete Branch %s
|
||||||
|
branch.delete_html = Delete Branch
|
||||||
branch.delete_desc = Deleting a branch is permanent. There is no way to undo it.
|
branch.delete_desc = Deleting a branch is permanent. There is no way to undo it.
|
||||||
branch.delete_notices_1 = - This operation <strong>CANNOT</strong> be undone.
|
branch.delete_notices_1 = - This operation <strong>CANNOT</strong> be undone.
|
||||||
branch.delete_notices_2 = - This operation will permanently delete everything in branch %s.
|
branch.delete_notices_2 = - This operation will permanently delete everything in branch %s.
|
||||||
|
branch.delete_notices_html = - This operation will permanently delete everything in branch
|
||||||
branch.deletion_success = %s has been deleted.
|
branch.deletion_success = %s has been deleted.
|
||||||
branch.deletion_failed = Failed to delete branch %s.
|
branch.deletion_failed = Failed to delete branch %s.
|
||||||
branch.delete_branch_has_new_commits = %s cannot be deleted because new commits have been added after merging.
|
branch.delete_branch_has_new_commits = %s cannot be deleted because new commits have been added after merging.
|
||||||
|
@ -1068,6 +1074,10 @@ branch.create_success = Branch '%s' has been created successfully!
|
||||||
branch.branch_already_exists = Branch '%s' already exists in this repository.
|
branch.branch_already_exists = Branch '%s' already exists in this repository.
|
||||||
branch.branch_name_conflict = Branch name '%s' conflicts with already existing branch '%s'.
|
branch.branch_name_conflict = Branch name '%s' conflicts with already existing branch '%s'.
|
||||||
branch.tag_collision = Branch '%s' can not be created as tag with same name already exists in this repository.
|
branch.tag_collision = Branch '%s' can not be created as tag with same name already exists in this repository.
|
||||||
|
branch.deleted_by = Deleted by %s
|
||||||
|
branch.restore_success = %s successfully restored
|
||||||
|
branch.restore_failed = Failed to restore branch %s.
|
||||||
|
branch.protected_deletion_failed = It's not possible to delete protected branch %s.
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder = Organization Name
|
org_name_holder = Organization Name
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1423,29 +1423,18 @@ $(document).ready(function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helpers.
|
// Helpers.
|
||||||
$('.delete-button').click(function () {
|
$('.delete-button').click(showDeletePopup);
|
||||||
var $this = $(this);
|
|
||||||
var filter = "";
|
|
||||||
if ($this.attr("id")) {
|
|
||||||
filter += "#"+$this.attr("id")
|
|
||||||
}
|
|
||||||
$('.delete.modal'+filter).modal({
|
|
||||||
closable: false,
|
|
||||||
onApprove: function () {
|
|
||||||
if ($this.data('type') == "form") {
|
|
||||||
$($this.data('form')).submit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$('.delete-branch-button').click(showDeletePopup);
|
||||||
|
|
||||||
|
$('.undo-button').click(function() {
|
||||||
|
var $this = $(this);
|
||||||
$.post($this.data('url'), {
|
$.post($this.data('url'), {
|
||||||
"_csrf": csrf,
|
"_csrf": csrf,
|
||||||
"id": $this.data("id")
|
"id": $this.data("id")
|
||||||
}).done(function(data) {
|
}).done(function(data) {
|
||||||
window.location.href = data.redirect;
|
window.location.href = data.redirect;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}).modal('show');
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
$('.show-panel.button').click(function () {
|
$('.show-panel.button').click(function () {
|
||||||
$($(this).data('panel')).show();
|
$($(this).data('panel')).show();
|
||||||
|
@ -1608,6 +1597,32 @@ $(function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function showDeletePopup() {
|
||||||
|
var $this = $(this);
|
||||||
|
var filter = "";
|
||||||
|
if ($this.attr("id")) {
|
||||||
|
filter += "#" + $this.attr("id")
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.delete.modal' + filter).modal({
|
||||||
|
closable: false,
|
||||||
|
onApprove: function() {
|
||||||
|
if ($this.data('type') == "form") {
|
||||||
|
$($this.data('form')).submit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post($this.data('url'), {
|
||||||
|
"_csrf": csrf,
|
||||||
|
"id": $this.data("id")
|
||||||
|
}).done(function(data) {
|
||||||
|
window.location.href = data.redirect;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).modal('show');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function initVueComponents(){
|
function initVueComponents(){
|
||||||
var vueDelimeters = ['${', '}'];
|
var vueDelimeters = ['${', '}'];
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.repository.branches {
|
||||||
|
.time{
|
||||||
|
font-size: 12px;
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ui.user.list {
|
.ui.user.list {
|
||||||
.item {
|
.item {
|
||||||
padding-bottom: 25px;
|
padding-bottom: 25px;
|
||||||
|
|
|
@ -1313,6 +1313,27 @@
|
||||||
border-bottom: 1px solid #A3C293;
|
border-bottom: 1px solid #A3C293;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ui.segment.sub-menu {
|
||||||
|
padding: 7px;
|
||||||
|
line-height: 0;
|
||||||
|
.list {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
.item {
|
||||||
|
width:100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
a {
|
||||||
|
color: black;
|
||||||
|
&:hover {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background: rgba(0,0,0,.05);;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// End of .repository
|
// End of .repository
|
||||||
|
|
||||||
|
|
|
@ -5,32 +5,192 @@
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/git"
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/auth"
|
"code.gitea.io/gitea/modules/auth"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tplBranch base.TplName = "repo/branch"
|
tplBranch base.TplName = "repo/branch/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Branch contains the branch information
|
||||||
|
type Branch struct {
|
||||||
|
Name string
|
||||||
|
Commit *git.Commit
|
||||||
|
IsProtected bool
|
||||||
|
IsDeleted bool
|
||||||
|
DeletedBranch *models.DeletedBranch
|
||||||
|
}
|
||||||
|
|
||||||
// Branches render repository branch page
|
// Branches render repository branch page
|
||||||
func Branches(ctx *context.Context) {
|
func Branches(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = "Branches"
|
ctx.Data["Title"] = "Branches"
|
||||||
ctx.Data["IsRepoToolbarBranches"] = true
|
ctx.Data["IsRepoToolbarBranches"] = true
|
||||||
|
ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
|
||||||
|
ctx.Data["IsWriter"] = ctx.Repo.IsWriter()
|
||||||
|
ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
|
||||||
|
ctx.Data["PageIsViewCode"] = true
|
||||||
|
ctx.Data["PageIsBranches"] = true
|
||||||
|
|
||||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
ctx.Data["Branches"] = loadBranches(ctx)
|
||||||
|
ctx.HTML(200, tplBranch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBranchPost responses for delete merged branch
|
||||||
|
func DeleteBranchPost(ctx *context.Context) {
|
||||||
|
defer redirect(ctx)
|
||||||
|
|
||||||
|
branchName := ctx.Query("name")
|
||||||
|
isProtected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Handle(500, "repo.Branches(GetBranches)", err)
|
log.Error(4, "DeleteBranch: %v", err)
|
||||||
return
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
|
||||||
} else if len(brs) == 0 {
|
|
||||||
ctx.Handle(404, "repo.Branches(GetBranches)", nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["Branches"] = brs
|
if isProtected {
|
||||||
ctx.HTML(200, tplBranch)
|
ctx.Flash.Error(ctx.Tr("repo.branch.protected_deletion_failed", branchName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.Repo.GitRepo.IsBranchExist(branchName) || branchName == ctx.Repo.Repository.DefaultBranch {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deleteBranch(ctx, branchName); err != nil {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", branchName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreBranchPost responses for delete merged branch
|
||||||
|
func RestoreBranchPost(ctx *context.Context) {
|
||||||
|
defer redirect(ctx)
|
||||||
|
|
||||||
|
branchID := ctx.QueryInt64("branch_id")
|
||||||
|
branchName := ctx.Query("name")
|
||||||
|
|
||||||
|
deletedBranch, err := ctx.Repo.Repository.GetDeletedBranchByID(branchID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(4, "GetDeletedBranchByID: %v", err)
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", branchName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.Repo.GitRepo.CreateBranch(deletedBranch.Name, deletedBranch.Commit); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "already exists") {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.branch.already_exists", deletedBranch.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Error(4, "CreateBranch: %v", err)
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.Repo.Repository.RemoveDeletedBranch(deletedBranch.ID); err != nil {
|
||||||
|
log.Error(4, "RemoveDeletedBranch: %v", err)
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.branch.restore_success", deletedBranch.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirect(ctx *context.Context) {
|
||||||
|
ctx.JSON(200, map[string]interface{}{
|
||||||
|
"redirect": ctx.Repo.RepoLink + "/branches",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteBranch(ctx *context.Context, branchName string) error {
|
||||||
|
commit, err := ctx.Repo.GitRepo.GetBranchCommit(branchName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(4, "GetBranchCommit: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.Repo.GitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
||||||
|
Force: true,
|
||||||
|
}); err != nil {
|
||||||
|
log.Error(4, "DeleteBranch: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't return error here
|
||||||
|
if err := ctx.Repo.Repository.AddDeletedBranch(branchName, commit.ID.String(), ctx.User.ID); err != nil {
|
||||||
|
log.Warn("AddDeletedBranch: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBranches(ctx *context.Context) []*Branch {
|
||||||
|
rawBranches, err := ctx.Repo.Repository.GetBranches()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "GetBranches", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
branches := make([]*Branch, len(rawBranches))
|
||||||
|
for i := range rawBranches {
|
||||||
|
commit, err := rawBranches[i].GetCommit()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "GetCommit", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
isProtected, err := ctx.Repo.Repository.IsProtectedBranch(rawBranches[i].Name, ctx.User)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "IsProtectedBranch", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
branches[i] = &Branch{
|
||||||
|
Name: rawBranches[i].Name,
|
||||||
|
Commit: commit,
|
||||||
|
IsProtected: isProtected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.IsWriter() {
|
||||||
|
deletedBranches, err := getDeletedBranches(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "getDeletedBranches", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
branches = append(branches, deletedBranches...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return branches
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDeletedBranches(ctx *context.Context) ([]*Branch, error) {
|
||||||
|
branches := []*Branch{}
|
||||||
|
|
||||||
|
deletedBranches, err := ctx.Repo.Repository.GetDeletedBranches()
|
||||||
|
if err != nil {
|
||||||
|
return branches, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range deletedBranches {
|
||||||
|
deletedBranches[i].LoadUser()
|
||||||
|
branches = append(branches, &Branch{
|
||||||
|
Name: deletedBranches[i].Name,
|
||||||
|
IsDeleted: true,
|
||||||
|
DeletedBranch: deletedBranches[i],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return branches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBranch creates new branch in repository
|
// CreateBranch creates new branch in repository
|
||||||
|
|
|
@ -53,6 +53,7 @@ func Commits(ctx *context.Context) {
|
||||||
ctx.Handle(404, "Commit not found", nil)
|
ctx.Handle(404, "Commit not found", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.Data["PageIsViewCode"] = true
|
||||||
|
|
||||||
commitsCount, err := ctx.Repo.Commit.CommitsCount()
|
commitsCount, err := ctx.Repo.Commit.CommitsCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -88,6 +89,7 @@ func Commits(ctx *context.Context) {
|
||||||
// Graph render commit graph - show commits from all branches.
|
// Graph render commit graph - show commits from all branches.
|
||||||
func Graph(ctx *context.Context) {
|
func Graph(ctx *context.Context) {
|
||||||
ctx.Data["PageIsCommits"] = true
|
ctx.Data["PageIsCommits"] = true
|
||||||
|
ctx.Data["PageIsViewCode"] = true
|
||||||
|
|
||||||
commitsCount, err := ctx.Repo.Commit.CommitsCount()
|
commitsCount, err := ctx.Repo.Commit.CommitsCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -114,6 +116,7 @@ func Graph(ctx *context.Context) {
|
||||||
// SearchCommits render commits filtered by keyword
|
// SearchCommits render commits filtered by keyword
|
||||||
func SearchCommits(ctx *context.Context) {
|
func SearchCommits(ctx *context.Context) {
|
||||||
ctx.Data["PageIsCommits"] = true
|
ctx.Data["PageIsCommits"] = true
|
||||||
|
ctx.Data["PageIsViewCode"] = true
|
||||||
|
|
||||||
keyword := strings.Trim(ctx.Query("q"), " ")
|
keyword := strings.Trim(ctx.Query("q"), " ")
|
||||||
if len(keyword) == 0 {
|
if len(keyword) == 0 {
|
||||||
|
|
|
@ -550,7 +550,10 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
|
|
||||||
m.Group("/branches", func() {
|
m.Group("/branches", func() {
|
||||||
m.Post("/_new/*", context.RepoRef(), bindIgnErr(auth.NewBranchForm{}), repo.CreateBranch)
|
m.Post("/_new/*", context.RepoRef(), bindIgnErr(auth.NewBranchForm{}), repo.CreateBranch)
|
||||||
}, reqRepoWriter, repo.MustBeNotBare)
|
m.Post("/delete", repo.DeleteBranchPost)
|
||||||
|
m.Post("/restore", repo.RestoreBranchPost)
|
||||||
|
}, reqRepoWriter, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode))
|
||||||
|
|
||||||
}, reqSignIn, context.RepoAssignment(), context.UnitTypes(), context.LoadRepoUnits())
|
}, reqSignIn, context.RepoAssignment(), context.UnitTypes(), context.LoadRepoUnits())
|
||||||
|
|
||||||
// Releases
|
// Releases
|
||||||
|
@ -615,6 +618,10 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
|
|
||||||
m.Get("/archive/*", repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode), repo.Download)
|
m.Get("/archive/*", repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode), repo.Download)
|
||||||
|
|
||||||
|
m.Group("/branches", func() {
|
||||||
|
m.Get("", repo.Branches)
|
||||||
|
}, repo.MustBeNotBare, context.RepoRef(), context.CheckUnit(models.UnitTypeCode))
|
||||||
|
|
||||||
m.Group("/pulls/:index", func() {
|
m.Group("/pulls/:index", func() {
|
||||||
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
||||||
m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles)
|
m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles)
|
||||||
|
|
81
templates/repo/branch/list.tmpl
Normal file
81
templates/repo/branch/list.tmpl
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
<div class="ui repository branches">
|
||||||
|
{{template "repo/header" .}}
|
||||||
|
<div class="ui container">
|
||||||
|
{{template "base/alert" .}}
|
||||||
|
{{template "repo/sub_menu" .}}
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{.i18n.Tr "repo.default_branch"}}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="ui attached table segment">
|
||||||
|
<table class="ui very basic striped fixed table single line">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{.DefaultBranch}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if gt (len .Branches) 1}}
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{.i18n.Tr "repo.branches"}}
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached table segment">
|
||||||
|
<table class="ui very basic striped fixed table single line">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="nine wide">{{.i18n.Tr "repo.branch.name"}}</th>
|
||||||
|
{{if and $.IsWriter (not $.IsMirror)}}
|
||||||
|
<th class="one wide right aligned">{{.i18n.Tr "repo.branch.delete_head"}}</th>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $branch := .Branches}}
|
||||||
|
{{if ne .Name $.DefaultBranch}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{if .IsDeleted}}
|
||||||
|
<s>{{.Name}}</s>
|
||||||
|
<p class="time">{{$.i18n.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSince .DeletedBranch.Deleted $.i18n.Lang}}</p>
|
||||||
|
{{else}}
|
||||||
|
{{.Name}}
|
||||||
|
<p class="time">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}</p>
|
||||||
|
</td>
|
||||||
|
{{end}}
|
||||||
|
{{if and $.IsWriter (not $.IsMirror)}}
|
||||||
|
<td class="right aligned">
|
||||||
|
{{if .IsProtected}}
|
||||||
|
<i class="octicon octicon-shield"></i>
|
||||||
|
{{else if .IsDeleted}}
|
||||||
|
<a class="undo-button" href data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID | urlquery}}&name={{.DeletedBranch.Name | urlquery}}"><i class="octicon octicon-reply"></i></a>
|
||||||
|
{{else}}
|
||||||
|
<a class="delete-branch-button" href data-url="{{$.Link}}/delete?name={{.Name | urlquery}}" data-val="{{.Name}}"><i class="trash icon text red"></i></a>
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
{{end}}
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui small basic delete modal">
|
||||||
|
<div class="ui icon header">
|
||||||
|
<i class="trash icon"></i>
|
||||||
|
{{.i18n.Tr "repo.branch.delete_html"| Safe}} <span class="branch-name"></span>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>{{.i18n.Tr "repo.branch.delete_desc" | Safe}}</p>
|
||||||
|
{{.i18n.Tr "repo.branch.delete_notices_1" | Safe}}<br>
|
||||||
|
{{.i18n.Tr "repo.branch.delete_notices_html" | Safe}} <span class="branch-name"></span><br>
|
||||||
|
</div>
|
||||||
|
{{template "base/delete_modal_actions" .}}
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
|
@ -2,6 +2,7 @@
|
||||||
<div class="repository commits">
|
<div class="repository commits">
|
||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
{{template "repo/sub_menu" .}}
|
||||||
<div class="ui secondary menu">
|
<div class="ui secondary menu">
|
||||||
{{template "repo/branch_dropdown" .}}
|
{{template "repo/branch_dropdown" .}}
|
||||||
<div class="fitted item">
|
<div class="fitted item">
|
||||||
|
|
|
@ -73,12 +73,6 @@
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and (.Repository.UnitEnabled $.UnitTypeCode) (not .IsBareRepo)}}
|
|
||||||
<a class="{{if (or (.PageIsCommits) (.PageIsDiff))}}active{{end}} item" href="{{.RepoLink}}/commits/{{EscapePound .BranchName}}">
|
|
||||||
<i class="octicon octicon-history"></i> {{.i18n.Tr "repo.commits"}} <span class="ui {{if not .CommitsCount}}gray{{else}}blue{{end}} small label">{{.CommitsCount}}</span>
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if and (.Repository.UnitEnabled $.UnitTypeReleases) (not .IsBareRepo) }}
|
{{if and (.Repository.UnitEnabled $.UnitTypeReleases) (not .IsBareRepo) }}
|
||||||
<a class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases">
|
<a class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases">
|
||||||
<i class="octicon octicon-tag"></i> {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .Repository.NumReleases}}gray{{else}}blue{{end}} small label">{{.Repository.NumReleases}}</span>
|
<i class="octicon octicon-tag"></i> {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .Repository.NumReleases}}gray{{else}}blue{{end}} small label">{{.Repository.NumReleases}}</span>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
{{if .Repository.DescriptionHTML}}<span class="description has-emoji">{{.Repository.DescriptionHTML}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>{{end}}
|
{{if .Repository.DescriptionHTML}}<span class="description has-emoji">{{.Repository.DescriptionHTML}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>{{end}}
|
||||||
<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
|
<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
|
||||||
</p>
|
</p>
|
||||||
|
{{template "repo/sub_menu" .}}
|
||||||
<div class="ui secondary menu">
|
<div class="ui secondary menu">
|
||||||
{{if .PullRequestCtx.Allowed}}
|
{{if .PullRequestCtx.Allowed}}
|
||||||
<div class="fitted item">
|
<div class="fitted item">
|
||||||
|
|
14
templates/repo/sub_menu.tmpl
Normal file
14
templates/repo/sub_menu.tmpl
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<div class="ui segment sub-menu">
|
||||||
|
<div class="ui two horizontal center link list">
|
||||||
|
{{if and (.Repository.UnitEnabled $.UnitTypeCode) (not .IsBareRepo)}}
|
||||||
|
<div class="item{{if .PageIsCommits}} active{{end}}">
|
||||||
|
<a href="{{.RepoLink}}/commits/{{EscapePound .BranchName}}"><i class="octicon octicon-history"></i> <b>{{.CommitsCount}}</b> {{.i18n.Tr "repo.commits"}}</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if and (.Repository.UnitEnabled $.UnitTypeCode) (not .IsBareRepo) }}
|
||||||
|
<div class="item{{if .PageIsBranches}} active{{end}}">
|
||||||
|
<a href="{{.RepoLink}}/branches/"><i class="octicon octicon-git-branch"></i> <b>{{.BrancheCount}}</b> {{.i18n.Tr "repo.branches"}}</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in a new issue