Browse Source

Git installation check, show image in single file view, show short SHA1 instead of 40-length string, complete log module

pull/346/head
Unknwon 11 years ago
parent
commit
5e81383413
  1. 6
      models/repo.go
  2. 2
      models/user.go
  3. 104
      modules/log/conn.go
  4. 6
      modules/log/console.go
  5. 68
      modules/log/database.go
  6. 16
      modules/log/log.go
  7. 87
      modules/log/smtp.go
  8. 21
      public/ng/css/gogs.css
  9. 5
      routers/install.go
  10. 4
      routers/user/home.go
  11. 2
      templates/org/home.tmpl
  12. 6
      templates/repo/home.tmpl
  13. 8
      templates/repo/view_file.tmpl
  14. 48
      templates/user/dashboard/dashboard.tmpl
  15. 2
      templates/user/dashboard/nav.tmpl
  16. 12
      templates/user/dashboard/repo_list.tmpl

6
models/repo.go

@ -11,6 +11,7 @@ import (
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -84,6 +85,11 @@ func LoadRepoConfig() {
func NewRepoContext() { func NewRepoContext() {
zip.Verbose = false zip.Verbose = false
// Check Git installation.
if _, err := exec.LookPath("git"); err != nil {
log.Fatal(4, "Fail to test 'git' command: %v (forgotten install?)", err)
}
// Check Git version. // Check Git version.
ver, err := git.GetVersion() ver, err := git.GetVersion()
if err != nil { if err != nil {

2
models/user.go

@ -81,7 +81,7 @@ type User struct {
// DashboardLink returns the user dashboard page link. // DashboardLink returns the user dashboard page link.
func (u *User) DashboardLink() string { func (u *User) DashboardLink() string {
if u.IsOrganization() { if u.IsOrganization() {
return "/org/" + u.Name + "/dashboard" return "/org/" + u.Name + "/dashboard/"
} }
return "/" return "/"
} }

104
modules/log/conn.go

@ -0,0 +1,104 @@
// Copyright 2014 The Gogs 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 log
import (
"encoding/json"
"io"
"log"
"net"
)
// ConnWriter implements LoggerInterface.
// it writes messages in keep-live tcp connection.
type ConnWriter struct {
lg *log.Logger
innerWriter io.WriteCloser
ReconnectOnMsg bool `json:"reconnectOnMsg"`
Reconnect bool `json:"reconnect"`
Net string `json:"net"`
Addr string `json:"addr"`
Level int `json:"level"`
}
// create new ConnWrite returning as LoggerInterface.
func NewConn() LoggerInterface {
conn := new(ConnWriter)
conn.Level = TRACE
return conn
}
// init connection writer with json config.
// json config only need key "level".
func (cw *ConnWriter) Init(jsonconfig string) error {
return json.Unmarshal([]byte(jsonconfig), cw)
}
// write message in connection.
// if connection is down, try to re-connect.
func (cw *ConnWriter) WriteMsg(msg string, skip, level int) error {
if cw.Level > level {
return nil
}
if cw.neddedConnectOnMsg() {
if err := cw.connect(); err != nil {
return err
}
}
if cw.ReconnectOnMsg {
defer cw.innerWriter.Close()
}
cw.lg.Println(msg)
return nil
}
func (_ *ConnWriter) Flush() {
}
// destroy connection writer and close tcp listener.
func (cw *ConnWriter) Destroy() {
if cw.innerWriter == nil {
return
}
cw.innerWriter.Close()
}
func (cw *ConnWriter) connect() error {
if cw.innerWriter != nil {
cw.innerWriter.Close()
cw.innerWriter = nil
}
conn, err := net.Dial(cw.Net, cw.Addr)
if err != nil {
return err
}
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
}
cw.innerWriter = conn
cw.lg = log.New(conn, "", log.Ldate|log.Ltime)
return nil
}
func (cw *ConnWriter) neddedConnectOnMsg() bool {
if cw.Reconnect {
cw.Reconnect = false
return true
}
if cw.innerWriter == nil {
return true
}
return cw.ReconnectOnMsg
}
func init() {
Register("conn", NewConn)
}

6
modules/log/console.go

@ -61,13 +61,13 @@ func (cw *ConsoleWriter) WriteMsg(msg string, skip, level int) error {
return nil return nil
} }
func (_ *ConsoleWriter) Destroy() {
}
func (_ *ConsoleWriter) Flush() { func (_ *ConsoleWriter) Flush() {
} }
func (_ *ConsoleWriter) Destroy() {
}
func init() { func init() {
Register("console", NewConsole) Register("console", NewConsole)
} }

68
modules/log/database.go

@ -0,0 +1,68 @@
// Copyright 2014 The Gogs 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 log
import (
"encoding/json"
"github.com/go-xorm/xorm"
)
type Log struct {
Id int64
Level int
Msg string `xorm:"TEXT"`
}
// DatabaseWriter implements LoggerInterface and is used to log into database.
type DatabaseWriter struct {
Driver string `json:"driver"`
Conn string `json:"conn"`
Level int `json:"level"`
x *xorm.Engine
}
func NewDatabase() LoggerInterface {
return &DatabaseWriter{Level: TRACE}
}
// init database writer with json config.
// config like:
// {
// "driver": "mysql"
// "conn":"root:root@tcp(127.0.0.1:3306)/gogs?charset=utf8",
// "level": 0
// }
// connection string is based on xorm.
func (d *DatabaseWriter) Init(jsonconfig string) (err error) {
if err = json.Unmarshal([]byte(jsonconfig), d); err != nil {
return err
}
d.x, err = xorm.NewEngine(d.Driver, d.Conn)
if err != nil {
return err
}
return d.x.Sync(new(Log))
}
// write message in database writer.
func (d *DatabaseWriter) WriteMsg(msg string, skip, level int) error {
if level < d.Level {
return nil
}
_, err := d.x.Insert(&Log{Level: level, Msg: msg})
return err
}
func (_ *DatabaseWriter) Flush() {
}
func (_ *DatabaseWriter) Destroy() {
}
func init() {
Register("database", NewDatabase)
}

16
modules/log/log.go

@ -87,12 +87,12 @@ func Fatal(skip int, format string, v ...interface{}) {
os.Exit(1) os.Exit(1)
} }
// .___ _______________________________________________________ _________ ___________ // .___ __ _____
// | |\ \__ ___/\_ _____/\______ \_ _____/ _ \ \_ ___ \\_ _____/ // | | _____/ |_ ____________/ ____\____ ____ ____
// | |/ | \| | | __)_ | _/| __)/ /_\ \/ \ \/ | __)_ // | |/ \ __\/ __ \_ __ \ __\\__ \ _/ ___\/ __ \
// | / | \ | | \ | | \| \/ | \ \____| \ // | | | \ | \ ___/| | \/| | / __ \\ \__\ ___/
// |___\____|__ /____| /_______ / |____|_ /\___ /\____|__ /\______ /_______ / // |___|___| /__| \___ >__| |__| (____ /\___ >___ >
// \/ \/ \/ \/ \/ \/ \/ // \/ \/ \/ \/ \/
type LogLevel int type LogLevel int
@ -168,7 +168,7 @@ func (l *Logger) SetLogger(adapter string, config string) error {
l.outputs[adapter] = lg l.outputs[adapter] = lg
l.adapter = adapter l.adapter = adapter
} else { } else {
panic("log: unknown adapter \"" + adapter + "\" (forgotten Register?)") panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)")
} }
return nil return nil
} }
@ -181,7 +181,7 @@ func (l *Logger) DelLogger(adapter string) error {
lg.Destroy() lg.Destroy()
delete(l.outputs, adapter) delete(l.outputs, adapter)
} else { } else {
panic("log: unknown adapter \"" + adapter + "\" (forgotten Register?)") panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)")
} }
return nil return nil
} }

87
modules/log/smtp.go

@ -0,0 +1,87 @@
// Copyright 2014 The Gogs 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 log
import (
"encoding/json"
"fmt"
"net/smtp"
"strings"
"time"
)
const (
subjectPhrase = "Diagnostic message from server"
)
// smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server.
type SmtpWriter struct {
Username string `json:"Username"`
Password string `json:"password"`
Host string `json:"Host"`
Subject string `json:"subject"`
RecipientAddresses []string `json:"sendTos"`
Level int `json:"level"`
}
// create smtp writer.
func NewSmtpWriter() LoggerInterface {
return &SmtpWriter{Level: TRACE}
}
// init smtp writer with json config.
// config like:
// {
// "Username":"example@gmail.com",
// "password:"password",
// "host":"smtp.gmail.com:465",
// "subject":"email title",
// "sendTos":["email1","email2"],
// "level":LevelError
// }
func (sw *SmtpWriter) Init(jsonconfig string) error {
return json.Unmarshal([]byte(jsonconfig), sw)
}
// write message in smtp writer.
// it will send an email with subject and only this message.
func (s *SmtpWriter) WriteMsg(msg string, skip, level int) error {
if level < s.Level {
return nil
}
hp := strings.Split(s.Host, ":")
// Set up authentication information.
auth := smtp.PlainAuth(
"",
s.Username,
s.Password,
hp[0],
)
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
content_type := "Content-Type: text/plain" + "; charset=UTF-8"
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.Username + "<" + s.Username +
">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg)
return smtp.SendMail(
s.Host,
auth,
s.Username,
s.RecipientAddresses,
mailmsg,
)
}
func (_ *SmtpWriter) Flush() {
}
func (_ *SmtpWriter) Destroy() {
}
func init() {
Register("smtp", NewSmtpWriter)
}

21
public/ng/css/gogs.css

@ -660,32 +660,41 @@ The dashboard page style
#dashboard-sidebar-menu > li.last > a { #dashboard-sidebar-menu > li.last > a {
border-top-right-radius: .3em; border-top-right-radius: .3em;
} }
#dashboard-my-org li,
#dashboard-my-repo li { #dashboard-my-repo li {
border-bottom: 1px solid #EAEAEA; border-bottom: 1px solid #EAEAEA;
} }
#dashboard-my-org li.private,
#dashboard-my-repo li.private { #dashboard-my-repo li.private {
background-color: #fcf8e9; background-color: #fcf8e9;
} }
#dashboard-my-org li:last-child,
#dashboard-my-repo li:last-child { #dashboard-my-repo li:last-child {
border-bottom: none; border-bottom: none;
} }
#dashboard-my-org li a,
#dashboard-my-repo li a { #dashboard-my-repo li a {
padding: 6px 1.2em; padding: 6px 1.2em;
display: block; display: block;
} }
#dashboard-my-org li a .octicon,
#dashboard-my-repo li a .octicon { #dashboard-my-repo li a .octicon {
margin-right: 6px; margin-right: 6px;
color: #888; color: #888;
} }
#dashboard-my-org li a:hover .repo-name,
#dashboard-my-repo li a:hover .repo-name { #dashboard-my-repo li a:hover .repo-name {
text-decoration: underline; text-decoration: underline;
} }
#dashboard-my-org .repo-name,
#dashboard-my-repo .repo-name { #dashboard-my-repo .repo-name {
font-size: 1.1em; font-size: 1.1em;
} }
#dashboard-my-org .repo-star,
#dashboard-my-repo .repo-star { #dashboard-my-repo .repo-star {
color: #888; color: #888;
} }
#dashboard-my-org .repo-contrib-header,
#dashboard-my-repo .repo-contrib-header { #dashboard-my-repo .repo-contrib-header {
border-top: 1px solid #d6d6d6; border-top: 1px solid #d6d6d6;
} }
@ -1139,6 +1148,17 @@ The register and sign-in page style
overflow: auto; overflow: auto;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
background: white;
}
.code-view .view-raw {
min-height: 40px;
text-align: center;
padding-top: 20px;
}
.code-view .view-raw .btn {
font-size: 1.05em;
line-height: 16px;
padding: 6px 8px;
} }
.code-view table { .code-view table {
width: 100%; width: 100%;
@ -1163,7 +1183,6 @@ The register and sign-in page style
} }
.code-view .lines-code > pre { .code-view .lines-code > pre {
border: none; border: none;
background: none;
border-left: 1px solid #ddd; border-left: 1px solid #ddd;
} }
.code-view .lines-code > pre > ol.linenums > li { .code-view .lines-code > pre > ol.linenums > li {

5
routers/install.go

@ -54,11 +54,12 @@ func GlobalInit() {
log.Trace("Log path: %s", setting.LogRootPath) log.Trace("Log path: %s", setting.LogRootPath)
mailer.NewMailerContext() mailer.NewMailerContext()
models.LoadModelsConfig() models.LoadModelsConfig()
models.LoadRepoConfig()
models.NewRepoContext()
NewServices() NewServices()
if setting.InstallLock { if setting.InstallLock {
models.LoadRepoConfig()
models.NewRepoContext()
if err := models.NewEngine(); err != nil { if err := models.NewEngine(); err != nil {
log.Fatal(4, "Fail to initialize ORM engine: %v", err) log.Fatal(4, "Fail to initialize ORM engine: %v", err)
} }

4
routers/user/home.go

@ -32,7 +32,6 @@ func Dashboard(ctx *middleware.Context) {
ctx.Handle(500, "home.Dashboard(GetOrganizations)", err) ctx.Handle(500, "home.Dashboard(GetOrganizations)", err)
return return
} }
ctx.Data["Orgs"] = ctx.User.Orgs
ctx.Data["ContextUser"] = ctx.User ctx.Data["ContextUser"] = ctx.User
repos, err := models.GetRepositories(ctx.User.Id, true) repos, err := models.GetRepositories(ctx.User.Id, true)
@ -40,9 +39,6 @@ func Dashboard(ctx *middleware.Context) {
ctx.Handle(500, "GetRepositories", err) ctx.Handle(500, "GetRepositories", err)
return return
} }
for _, repo := range repos {
repo.Owner = ctx.User
}
ctx.Data["Repos"] = repos ctx.Data["Repos"] = repos
ctx.Data["CollaborativeRepos"], err = models.GetCollaborativeRepos(ctx.User.Name) ctx.Data["CollaborativeRepos"], err = models.GetCollaborativeRepos(ctx.User.Name)

2
templates/org/home.tmpl

@ -34,7 +34,7 @@
</div> </div>
<h3 class="org-repo-name"><a href="/{{$.Org.Name}}/{{.Name}}">{{.Name}}</a></h3> <h3 class="org-repo-name"><a href="/{{$.Org.Name}}/{{.Name}}">{{.Name}}</a></h3>
<p class="org-repo-description">{{.Description}}</p> <p class="org-repo-description">{{.Description}}</p>
<p class="org-repo-update">Updated {{TimeSince .Updated}}</p> <p class="org-repo-update">Updated {{TimeSince .Updated $.Lang}}</p>
</div> </div>
{{end}} {{end}}
</div> </div>

6
templates/repo/home.tmpl

@ -74,7 +74,7 @@
<a href="#"> <a href="#">
<button class="btn btn-gray btn-small btn-radius"> <button class="btn btn-gray btn-small btn-radius">
<i class="octicon octicon-git-branch"></i> Branch : <i class="octicon octicon-git-branch"></i> Branch :
<strong id="repo-branch-current">{{.BranchName}}</strong> <strong id="repo-branch-current">{{if .IsViewBranch}}{{.BranchName}}{{else}}{{ShortSha .BranchName}}{{end}}</strong>
</button> </button>
</a> </a>
<div class="drop-down panel"> <div class="drop-down panel">
@ -87,7 +87,7 @@
</ul> </ul>
<ul class="menu menu-vertical switching-list" id="repo-branch-list"> <ul class="menu menu-vertical switching-list" id="repo-branch-list">
{{range .Branches}} {{range .Branches}}
<li {{if eq . $.BranchName}}class="checked"{{end}}><a href="{{$.RepoLink}}/src/{{.}}"><i class="octicon octicon-check"></i>master</a></li> <li {{if eq . $.BranchName}}class="checked"{{end}}><a href="{{$.RepoLink}}/src/{{.}}"><i class="octicon octicon-check"></i>{{.}}</a></li>
{{end}} {{end}}
</ul> </ul>
<ul class="menu menu-vertical switching-list" id="repo-tag-list"> <ul class="menu menu-vertical switching-list" id="repo-tag-list">
@ -137,7 +137,7 @@
<a class="radius" href="{{.RepoLink}}/pulls"><i class="octicon octicon-git-pull-request"></i>Pull Requests<span class="num right label label-blue label-radius">{{.Repository.NumOpenPulls}}</span></a> <a class="radius" href="{{.RepoLink}}/pulls"><i class="octicon octicon-git-pull-request"></i>Pull Requests<span class="num right label label-blue label-radius">{{.Repository.NumOpenPulls}}</span></a>
</li> </li>
<li class="border-bottom"></li> <li class="border-bottom"></li>
<li class="head">{{.BranchName}}</li> <li class="head">{{if .IsViewBranch}}{{.BranchName}}{{else}}{{ShortSha .BranchName}}{{end}}</li>
<li> <li>
<a class="radius" href="{{.RepoLink}}/commits/{{.BranchName}}"><i class="octicon octicon-history"></i>Commits <span class="num right label label-gray label-radius">{{.CommitsCount}}</span></a> <a class="radius" href="{{.RepoLink}}/commits/{{.BranchName}}"><i class="octicon octicon-history"></i>Commits <span class="num right label label-gray label-radius">{{.CommitsCount}}</span></a>
</li> </li>

8
templates/repo/view_file.tmpl

@ -15,6 +15,14 @@
<div class="{{if .ReadmeExist}}panel-content markdown{{end}} code-view"> <div class="{{if .ReadmeExist}}panel-content markdown{{end}} code-view">
{{if .ReadmeExist}} {{if .ReadmeExist}}
{{.FileContent | Str2html}} {{.FileContent | Str2html}}
{{else if not .IsFileText}}
<div class="view-raw">
{{if .IsImageFile}}
<img src="{{.FileLink}}">
{{else}}
<a href="{{.FileLink}}" rel="nofollow" class="btn btn-gray btn-radius">View Raw</a>
{{end}}
</div>
{{else if .FileSize}} {{else if .FileSize}}
<table> <table>
<tbody> <tbody>

48
templates/user/dashboard/dashboard.tmpl

@ -48,7 +48,9 @@
<div id="dashboard-sidebar" class="right grid-1-3"> <div id="dashboard-sidebar" class="right grid-1-3">
<ul id="dashboard-sidebar-menu" class="menu menu-line"> <ul id="dashboard-sidebar-menu" class="menu menu-line">
<li class="js-tab-nav js-tab-nav-show first" data-tab-target="#dashboard-my-repo"><a href="#">{{.i18n.Tr "repository"}}</a></li> <li class="js-tab-nav js-tab-nav-show first" data-tab-target="#dashboard-my-repo"><a href="#">{{.i18n.Tr "repository"}}</a></li>
{{if not .ContextUser.IsOrganization}}
<li class="js-tab-nav" data-tab-target="#dashboard-my-org"><a href="#">{{.i18n.Tr "organization"}}</a></li> <li class="js-tab-nav" data-tab-target="#dashboard-my-org"><a href="#">{{.i18n.Tr "organization"}}</a></li>
{{end}}
<li class="js-tab-nav last" data-tab-target="#dashboard-my-mirror"><a href="#">{{.i18n.Tr "mirror"}}</a></li> <li class="js-tab-nav last" data-tab-target="#dashboard-my-mirror"><a href="#">{{.i18n.Tr "mirror"}}</a></li>
<li class="drop right"> <li class="drop right">
<button class="btn btn-green text-bold" id="dashboard-new-repo"> <button class="btn btn-green text-bold" id="dashboard-new-repo">
@ -70,26 +72,68 @@
<div class="panel-body"> <div class="panel-body">
<ul class="list-no-style"> <ul class="list-no-style">
{{range .Repos}} {{range .Repos}}
{{template "user/dashboard/repo_list" .}} <li {{if .IsPrivate}}class="private"{{end}}>
<a href="{{$.ContextUser.Name}}/{{.Name}}">
<i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
<span class="repo-name">
<strong class="repo">{{.Name}}</strong>
</span>
<span class="right repo-star">
<i class="octicon octicon-star"></i>{{.NumStars}}
</span>
</a>
</li>
{{end}} {{end}}
</ul> </ul>
</div> </div>
{{if not .ContextUser.IsOrganization}}
<div class="panel-header repo-contrib-header"> <div class="panel-header repo-contrib-header">
<h4 class="text-bold">{{.i18n.Tr "home.collaborative_repos"}}</h4> <h4 class="text-bold">{{.i18n.Tr "home.collaborative_repos"}}</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<ul class="list-no-style"> <ul class="list-no-style">
{{range .CollaborativeRepos}} {{range .CollaborativeRepos}}
{{template "user/dashboard/repo_list" .}} <li {{if .IsPrivate}}class="private"{{end}}>
<a href="{{.Owner.Name}}/{{.Name}}">
<i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
<span class="repo-name">
<span class="repo-name-prefix">{{.Owner.Name}} / </span>
<strong class="repo">{{.Name}}</strong>
</span>
<span class="right repo-star">
<i class="octicon octicon-star"></i>{{.NumStars}}
</span>
</a>
</li>
{{end}} {{end}}
</ul> </ul>
</div> </div>
{{end}}
</div> </div>
{{if not .ContextUser.IsOrganization}}
<div class="panel" id="dashboard-my-org"> <div class="panel" id="dashboard-my-org">
<div class="panel-header"> <div class="panel-header">
<h4 class="text-bold">{{.i18n.Tr "home.my_orgs"}}</h4> <h4 class="text-bold">{{.i18n.Tr "home.my_orgs"}}</h4>
</div> </div>
<div class="panel-body">
<ul class="list-no-style">
{{range .ContextUser.Orgs}}
<li>
<a href="/{{.Name}}">
<i class="octicon octicon-organization"></i>
<span class="repo-name">
<strong class="repo">{{.Name}}</strong>
</span>
<span class="right repo-star">
<i class="octicon octicon-repo"></i>{{.NumRepos}}
</span>
</a>
</li>
{{end}}
</ul>
</div> </div>
</div>
{{end}}
<div class="panel" id="dashboard-my-mirror"> <div class="panel" id="dashboard-my-mirror">
<div class="panel-header"> <div class="panel-header">
<h4 class="text-bold">{{.i18n.Tr "home.my_mirrors"}}</h4> <h4 class="text-bold">{{.i18n.Tr "home.my_mirrors"}}</h4>

2
templates/user/dashboard/nav.tmpl

@ -15,7 +15,7 @@
{{.SignedUser.Name}} {{.SignedUser.Name}}
</a> </a>
</li> </li>
{{range .Orgs}} {{range .ContextUser.Orgs}}
<li class="org {{if eq $.ContextUser.Id .Id}}checked{{end}}"> <li class="org {{if eq $.ContextUser.Id .Id}}checked{{end}}">
<a href="{{.DashboardLink}}"> <a href="{{.DashboardLink}}">
<i class="octicon octicon-check"></i> <i class="octicon octicon-check"></i>

12
templates/user/dashboard/repo_list.tmpl

@ -1,12 +0,0 @@
<li {{if .IsPrivate}}class="private"{{end}}>
<a href="{{.Owner.Name}}/{{.Name}}">
<i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
<span class="repo-name">
<!-- <span class="repo-name-prefix">gogits / </span> -->
<strong class="repo">{{.Name}}</strong>
</span>
<span class="right repo-star">
<i class="octicon octicon-star"></i>{{.NumStars}}
</span>
</a>
</li>
Loading…
Cancel
Save