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
					
				| 
						 | 
				
			
			@ -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/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/Unknwon/com"
 | 
			
		||||
| 
						 | 
				
			
			@ -193,3 +194,109 @@ func (repo *Repository) DeleteProtectedBranch(id int64) (err error) {
 | 
			
		|||
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
	// v46 -> v47
 | 
			
		||||
	NewMigration("remove organization watch repositories", removeOrganizationWatchRepo),
 | 
			
		||||
	// v47 -> v48
 | 
			
		||||
	NewMigration("add deleted branches", addDeletedBranch),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Migrate database to current version
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(Stopwatch),
 | 
			
		||||
		new(TrackedTime),
 | 
			
		||||
		new(DeletedBranch),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	gonicNames := []string{"SSL", "UID"}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,6 +77,17 @@ func NewContext() {
 | 
			
		|||
			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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -365,6 +365,12 @@ var (
 | 
			
		|||
			Schedule       string
 | 
			
		||||
			UpdateExisting bool
 | 
			
		||||
		} `ini:"cron.sync_external_users"`
 | 
			
		||||
		DeletedBranchesCleanup struct {
 | 
			
		||||
			Enabled    bool
 | 
			
		||||
			RunAtStart bool
 | 
			
		||||
			Schedule   string
 | 
			
		||||
			OlderThan  time.Duration
 | 
			
		||||
		} `ini:"cron.deleted_branches_cleanup"`
 | 
			
		||||
	}{
 | 
			
		||||
		UpdateMirror: struct {
 | 
			
		||||
			Enabled    bool
 | 
			
		||||
| 
						 | 
				
			
			@ -419,6 +425,17 @@ var (
 | 
			
		|||
			Schedule:       "@every 24h",
 | 
			
		||||
			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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.downloads = Downloads
 | 
			
		||||
 | 
			
		||||
branch.name = Branch name
 | 
			
		||||
branch.search = Search branches
 | 
			
		||||
branch.already_exists = A branch named %s already exists.
 | 
			
		||||
branch.delete_head = Delete
 | 
			
		||||
branch.delete = Delete Branch %s
 | 
			
		||||
branch.delete_html = Delete Branch
 | 
			
		||||
branch.delete_desc = Deleting a branch is permanent. There is no way to undo it.
 | 
			
		||||
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_html = - This operation will permanently delete everything in branch
 | 
			
		||||
branch.deletion_success = %s has been deleted.
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			@ -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_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.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_name_holder = Organization Name
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
				
			
			@ -1423,29 +1423,18 @@ $(document).ready(function () {
 | 
			
		|||
    });
 | 
			
		||||
 | 
			
		||||
    // Helpers.
 | 
			
		||||
    $('.delete-button').click(function () {
 | 
			
		||||
        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-button').click(showDeletePopup);
 | 
			
		||||
 | 
			
		||||
    $('.delete-branch-button').click(showDeletePopup);
 | 
			
		||||
 | 
			
		||||
    $('.undo-button').click(function() {
 | 
			
		||||
        var $this = $(this);
 | 
			
		||||
        $.post($this.data('url'), {
 | 
			
		||||
            "_csrf": csrf,
 | 
			
		||||
            "id": $this.data("id")
 | 
			
		||||
                }).done(function (data) {
 | 
			
		||||
        }).done(function(data) {
 | 
			
		||||
            window.location.href = data.redirect;
 | 
			
		||||
        });
 | 
			
		||||
            }
 | 
			
		||||
        }).modal('show');
 | 
			
		||||
        return false;
 | 
			
		||||
    });
 | 
			
		||||
    $('.show-panel.button').click(function () {
 | 
			
		||||
        $($(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(){
 | 
			
		||||
    var vueDelimeters = ['${', '}'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,6 +50,13 @@
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ui.repository.branches  {
 | 
			
		||||
	.time{
 | 
			
		||||
		font-size: 12px;
 | 
			
		||||
		color: #808080;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ui.user.list {
 | 
			
		||||
	.item {
 | 
			
		||||
		padding-bottom: 25px;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1313,6 +1313,27 @@
 | 
			
		|||
            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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,32 +5,192 @@
 | 
			
		|||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/git"
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/auth"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
func Branches(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Title"] = "Branches"
 | 
			
		||||
	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 {
 | 
			
		||||
		ctx.Handle(500, "repo.Branches(GetBranches)", err)
 | 
			
		||||
		return
 | 
			
		||||
	} else if len(brs) == 0 {
 | 
			
		||||
		ctx.Handle(404, "repo.Branches(GetBranches)", nil)
 | 
			
		||||
		log.Error(4, "DeleteBranch: %v", err)
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", branchName))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["Branches"] = brs
 | 
			
		||||
	ctx.HTML(200, tplBranch)
 | 
			
		||||
	if isProtected {
 | 
			
		||||
		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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,6 +53,7 @@ func Commits(ctx *context.Context) {
 | 
			
		|||
		ctx.Handle(404, "Commit not found", nil)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["PageIsViewCode"] = true
 | 
			
		||||
 | 
			
		||||
	commitsCount, err := ctx.Repo.Commit.CommitsCount()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -88,6 +89,7 @@ func Commits(ctx *context.Context) {
 | 
			
		|||
// Graph render commit graph - show commits from all branches.
 | 
			
		||||
func Graph(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["PageIsCommits"] = true
 | 
			
		||||
	ctx.Data["PageIsViewCode"] = true
 | 
			
		||||
 | 
			
		||||
	commitsCount, err := ctx.Repo.Commit.CommitsCount()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -114,6 +116,7 @@ func Graph(ctx *context.Context) {
 | 
			
		|||
// SearchCommits render commits filtered by keyword
 | 
			
		||||
func SearchCommits(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["PageIsCommits"] = true
 | 
			
		||||
	ctx.Data["PageIsViewCode"] = true
 | 
			
		||||
 | 
			
		||||
	keyword := strings.Trim(ctx.Query("q"), " ")
 | 
			
		||||
	if len(keyword) == 0 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -550,7 +550,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		|||
 | 
			
		||||
		m.Group("/branches", func() {
 | 
			
		||||
			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())
 | 
			
		||||
 | 
			
		||||
	// Releases
 | 
			
		||||
| 
						 | 
				
			
			@ -615,6 +618,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		|||
 | 
			
		||||
		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.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
 | 
			
		||||
			m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewPullFiles)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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">
 | 
			
		||||
	{{template "repo/header" .}}
 | 
			
		||||
	<div class="ui container">
 | 
			
		||||
		{{template "repo/sub_menu" .}}
 | 
			
		||||
		<div class="ui secondary menu">
 | 
			
		||||
		{{template "repo/branch_dropdown" .}}
 | 
			
		||||
			<div class="fitted item">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,12 +73,6 @@
 | 
			
		|||
				</a>
 | 
			
		||||
			{{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) }}
 | 
			
		||||
			<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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}}
 | 
			
		||||
			<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
 | 
			
		||||
		</p>
 | 
			
		||||
		{{template "repo/sub_menu" .}}
 | 
			
		||||
		<div class="ui secondary menu">
 | 
			
		||||
			{{if .PullRequestCtx.Allowed}}
 | 
			
		||||
				<div class="fitted item">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 New Issue