This commit is contained in:
Lunny Xiao 2014-03-17 20:04:58 +08:00
commit a30e72323d
40 changed files with 1416 additions and 479 deletions

View File

@ -5,7 +5,7 @@ Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language.
Since we choose to use pure Go implmentation of Git manipulation, Gogs certainly supports **ALL platforms** that Go supports, including Linux, Max OS X, and Windows with **ZERO** dependency.
##### Current version: 0.0.8 Alpha
##### Current version: 0.0.9 Alpha
## Purpose

View File

@ -12,7 +12,8 @@
"models": "",
"others": [
"modules",
"$GOPATH/src/github.com/gogits/binding"
"$GOPATH/src/github.com/gogits/binding",
"$GOPATH/src/github.com/gogits/git"
]
},
"cmd_args": [

View File

@ -7,6 +7,7 @@ LANG_IGNS=Google Go|C|Python|Ruby
LICENSES=Apache v2 License|GPL v2|MIT License|BSD (3-Clause) License
[server]
DOMAIN = gogits.org
HTTP_ADDR =
HTTP_PORT = 3000

View File

@ -20,7 +20,7 @@ import (
// Test that go1.1 tag above is included in builds. main.go refers to this definition.
const go11tag = true
const APP_VER = "0.0.8.0316.1"
const APP_VER = "0.0.9.0317.1"
func init() {
base.AppVer = APP_VER

View File

@ -44,6 +44,10 @@ func (a Action) GetRepoName() string {
return a.RepoName
}
func (a Action) GetContent() string {
return a.Content
}
// CommitRepoAction records action for commit repository.
func CommitRepoAction(userId int64, userName string,
repoId int64, repoName string, commits [][]string) error {

View File

@ -307,6 +307,9 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
}
session := orm.NewSession()
if err = session.Begin(); err != nil {
return err
}
if _, err = session.Delete(&Repository{Id: repoId}); err != nil {
session.Rollback()
return err

View File

@ -5,11 +5,26 @@
package models
import (
"fmt"
"path"
"strings"
"time"
git "github.com/gogits/git"
"github.com/Unknwon/com"
"github.com/gogits/git"
)
type Commit struct {
Author string
Email string
Date time.Time
SHA string
Message string
}
var (
ErrRepoFileNotLoaded = fmt.Errorf("repo file not loaded")
)
type RepoFile struct {
@ -18,6 +33,7 @@ type RepoFile struct {
Message string
Created time.Time
Size int64
Repo *git.Repository
LastCommit string
}
@ -43,10 +59,34 @@ func findTree(repo *git.Repository, tree *git.Tree, rpath string) *git.Tree {
return g
}
func GetReposFiles(userName, reposName, branchName, rpath string) ([]*RepoFile, error) {
f := RepoPath(userName, reposName)
func (file *RepoFile) LookupBlob() (*git.Blob, error) {
if file.Repo == nil {
return nil, ErrRepoFileNotLoaded
}
repo, err := git.OpenRepository(f)
return file.Repo.LookupBlob(file.Id)
}
func GetBranches(userName, reposName string) ([]string, error) {
repo, err := git.OpenRepository(RepoPath(userName, reposName))
if err != nil {
return nil, err
}
refs, err := repo.AllReferences()
if err != nil {
return nil, err
}
brs := make([]string, len(refs))
for i, ref := range refs {
brs[i] = ref.Name
}
return brs, nil
}
func GetReposFiles(userName, reposName, branchName, rpath string) ([]*RepoFile, error) {
repo, err := git.OpenRepository(RepoPath(userName, reposName))
if err != nil {
return nil, err
}
@ -128,6 +168,7 @@ func GetReposFiles(userName, reposName, branchName, rpath string) ([]*RepoFile,
cm.Message(),
cm.Committer.When,
size,
repo,
cm.Id().String(),
}
@ -142,3 +183,33 @@ func GetReposFiles(userName, reposName, branchName, rpath string) ([]*RepoFile,
return append(repodirs, repofiles...), nil
}
func GetLastestCommit(userName, repoName string) (*Commit, error) {
stdout, _, err := com.ExecCmd("git", "--git-dir="+RepoPath(userName, repoName), "log", "-1")
if err != nil {
return nil, err
}
commit := new(Commit)
for _, line := range strings.Split(stdout, "\n") {
if len(line) == 0 {
continue
}
switch {
case line[0] == 'c':
commit.SHA = line[7:]
case line[0] == 'A':
infos := strings.SplitN(line, " ", 3)
commit.Author = infos[1]
commit.Email = infos[2][1 : len(infos[2])-1]
case line[0] == 'D':
commit.Date, err = time.Parse("Mon Jan 02 15:04:05 2006 -0700", line[8:])
if err != nil {
return nil, err
}
case line[:4] == " ":
commit.Message = line[4:]
}
}
return commit, nil
}

View File

@ -17,7 +17,6 @@ import (
)
type CreateRepoForm struct {
UserId int64 `form:"userId"`
RepoName string `form:"repo" binding:"Required;AlphaDash"`
Visibility string `form:"visibility"`
Description string `form:"desc" binding:"MaxSize(100)"`
@ -52,9 +51,3 @@ func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, con
validate(errors, data, f)
}
type DeleteRepoForm struct {
UserId int64 `form:"userId" binding:"Required"`
UserName string `form:"userName" binding:"Required"`
RepoId int64 `form:"repoId" binding:"Required"`
}

View File

@ -18,6 +18,7 @@ import (
var (
AppVer string
AppName string
Domain string
Cfg *goconfig.ConfigFile
)
@ -58,4 +59,5 @@ func init() {
Cfg.BlockMode = false
AppName = Cfg.MustValue("", "APP_NAME")
Domain = Cfg.MustValue("server", "DOMAIN")
}

39
modules/base/markdown.go Normal file
View File

@ -0,0 +1,39 @@
// 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 base
import (
"github.com/slene/blackfriday"
)
func RenderMarkdown(rawBytes []byte) []byte {
htmlFlags := 0
htmlFlags |= blackfriday.HTML_USE_XHTML
// htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
// htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
// htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
htmlFlags |= blackfriday.HTML_SKIP_HTML
htmlFlags |= blackfriday.HTML_SKIP_STYLE
htmlFlags |= blackfriday.HTML_SKIP_SCRIPT
htmlFlags |= blackfriday.HTML_GITHUB_BLOCKCODE
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
htmlFlags |= blackfriday.HTML_COMPLETE_PAGE
renderer := blackfriday.HtmlRenderer(htmlFlags, "", "")
// set up the parser
extensions := 0
extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
extensions |= blackfriday.EXTENSION_TABLES
extensions |= blackfriday.EXTENSION_FENCED_CODE
extensions |= blackfriday.EXTENSION_AUTOLINK
extensions |= blackfriday.EXTENSION_STRIKETHROUGH
extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK
extensions |= blackfriday.EXTENSION_SPACE_HEADERS
extensions |= blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
body := blackfriday.Markdown(rawBytes, renderer, extensions)
return body
}

View File

@ -19,6 +19,10 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"AppVer": func() string {
return AppVer
},
"AppDomain": func() string {
return Domain
},
"AvatarLink": AvatarLink,
"str2html": Str2html,
"TimeSince": TimeSince,
"FileSize": FileSize,

View File

@ -5,8 +5,10 @@
package base
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"math"
"strings"
@ -20,6 +22,11 @@ func EncodeMd5(str string) string {
return hex.EncodeToString(m.Sum(nil))
}
// AvatarLink returns avatar link by given e-mail.
func AvatarLink(email string) string {
return "http://1.gravatar.com/avatar/" + EncodeMd5(email)
}
// Seconds-based time units
const (
Minute = 60
@ -235,6 +242,7 @@ type Actioner interface {
GetOpType() int
GetActUserName() string
GetRepoName() string
GetContent() string
}
// ActionIcon accepts a int that represents action operation type
@ -243,23 +251,39 @@ func ActionIcon(opType int) string {
switch opType {
case 1: // Create repository.
return "plus-circle"
case 5: // Commit repository.
return "arrow-circle-o-right"
default:
return "invalid type"
}
}
const (
CreateRepoTpl = `<a href="/user/%s">%s</a> created repository <a href="/%s/%s">%s</a>`
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s/%s">%s</a>`
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/%s/tree/%s">%s</a> at <a href="/%s/%s">%s/%s</a>%s`
TPL_COMMIT_REPO_LI = `<div><img id="gogs-user-avatar-commit" src="%s?s=16" alt="user-avatar" title="username"/> <a href="/%s/%s/commit/%s">%s</a> %s</div>`
)
// ActionDesc accepts int that represents action operation type
// and returns the description.
func ActionDesc(act Actioner) string {
func ActionDesc(act Actioner, avatarLink string) string {
actUserName := act.GetActUserName()
repoName := act.GetRepoName()
content := act.GetContent()
switch act.GetOpType() {
case 1: // Create repository.
return fmt.Sprintf(CreateRepoTpl, actUserName, actUserName, actUserName, repoName, repoName)
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, actUserName, repoName, repoName)
case 5: // Commit repository.
var commits [][]string
if err := json.Unmarshal([]byte(content), &commits); err != nil {
return err.Error()
}
buf := bytes.NewBuffer([]byte("\n"))
for _, commit := range commits {
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n")
}
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, "master", "master", actUserName, repoName, actUserName, repoName,
buf.String())
default:
return "invalid type"
}

View File

@ -6,6 +6,7 @@ package middleware
import (
"errors"
"strings"
"github.com/codegangsta/martini"
@ -23,8 +24,7 @@ func RepoAssignment(redirect bool) martini.Handler {
)
// get repository owner
ctx.Repo.IsOwner = ctx.IsSigned && ctx.User.LowerName == params["username"]
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner
ctx.Repo.IsOwner = ctx.IsSigned && ctx.User.LowerName == strings.ToLower(params["username"])
if !ctx.Repo.IsOwner {
user, err = models.GetUserByName(params["username"])
@ -70,5 +70,6 @@ func RepoAssignment(redirect bool) martini.Handler {
ctx.Data["Owner"] = user
ctx.Data["Title"] = user.Name + "/" + repo.Name
ctx.Data["RepositoryLink"] = ctx.Data["Title"]
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner
}
}

View File

@ -10,6 +10,7 @@ body {
html, body {
height: 100%;
font-family: Helvetica, Arial, sans-serif;
}
/* override bs3 */
@ -50,7 +51,6 @@ html, body {
.gogs-masthead {
background-color: #428bca;
box-shadow: inset 0 -2px 5px rgba(0, 0, 0, .1);
padding: 0 16px;
margin: 0;
}
@ -65,6 +65,12 @@ html, body {
height: 46px;
}
#gogs-nav-logo{
padding-left: 0;
padding-right: 0;
margin-right: 10px;
}
.gogs-nav-item:hover,
.gogs-nav-item:focus {
color: #fff;
@ -128,6 +134,11 @@ html, body {
padding: 5px 0;
margin-left: 10px;
height: 28px;
float: right;
}
#gogs-nav-signin{
float: right;
}
#gogs-nav-out .fa {
@ -228,6 +239,12 @@ html, body {
border-radius: 6px;
}
#gogs-user-avatar-commit {
width: 16px;
height: 16px;
border-radius: 2px;
}
#gogs-user-name {
margin-top: 20px;
font-size: 1.6em;
@ -338,10 +355,6 @@ html, body {
/* #gogs-feed */
#gogs-feed-left {
padding-left: 0;
}
#gogs-feed-right .repo-panel .panel-heading .btn {
margin-top: -4px;
}
@ -399,18 +412,11 @@ html, body {
.gogs-repo-nav h3 .fa {
color: #BBB;
margin-left: 0;
}
.gogs-repo-btns {
margin-top: 18px;
}
.gogs-repo-btns .btn-group {
margin-left: 1em;
}
.gogs-repo-btns .btn-group .btn {
padding-left: 6px;
.gogs-repo-nav .actions {
padding-top: 20px;
}
#gogs-repo-watching .dropdown-menu {
@ -525,6 +531,10 @@ html, body {
}
/* #gogs-source */
#gogs-source {
margin-top: -20px;
}
#gogs-source .source-toolbar:after {
clear: both;
}
@ -560,7 +570,9 @@ html, body {
.file-list .icon {
font-size: 17px;
padding: 5px 0 4px 10px;
width: 40px;
width: 50px;
color: #999;
text-align: right;
}
.file-list .wrap {
@ -588,6 +600,80 @@ html, body {
text-align: right;
}
.file-content .file-head {
font-size: 18px;
}
.file-content .file-head .icon {
color: #666;
margin: 0 .5em 0 0;
}
.file-content .file-body {
padding: 30px 30px 50px;
}
.branch-list th{
background-color: #FFF;
line-height: 28px !important;
}
.branch-list td{
line-height: 36px !important;
}
.branch-box tr:hover td{
background-color: rgba(19, 95, 215, 0.06) !important;
}
.branch-box .name{
padding-left: 20px;
font-size: 15px;
}
.branch-box .action{
width: 150px;
}
.branch-box td.date,.branch-box td.behind,.branch-box td.ahead{
width: 120px;
font-family: Verdana, Arial, sans-serif;
}
.branch-box .graph{
display: block;
height: 3px;
}
.branch-box .behind{
text-align: right;
direction: rtl;
}
.branch-box .behind .graph{
background-color: #888;
}
.branch-box .ahead .graph{
background-color: #0093c4;
}
.branch-box .branch-main{
background-color: #444;
color: #FFF;
border-color: #444;
}
.branch-box .branch-main a{
color: #FFF;
}
.branch-box .branch-main .name .btn{
margin-left: .5em;
}
/* wrapper and footer */
#wrapper {
min-height: 100%;
height: auto !important;
@ -604,7 +690,7 @@ html, body {
}
#footer .footer-wrap {
padding: 20px 0;
padding: 20px 15px;
}
#footer a {

317
public/css/markdown.css Normal file
View File

@ -0,0 +1,317 @@
.markdown {
font-size: 14px;
}
.markdown a {
color: #4183C4;
}
.markdown h1,
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
line-height: 1.7;
padding: 15px 0 0;
margin: 0 0 15px;
color: #666;
}
.markdown h1,
.markdown h2 {
border-bottom: 1px solid #EEE;
}
.markdown h2 {
border-bottom: 1px solid #EEE;
}
.markdown h1 {
color: #000;
font-size: 33px
}
.markdown h2 {
color: #333;
font-size: 28px
}
.markdown h3 {
font-size: 22px
}
.markdown h4 {
font-size: 18px
}
.markdown h5 {
font-size: 14px
}
.markdown h6 {
font-size: 14px
}
.markdown table {
border-collapse: collapse;
border-spacing: 0;
display: block;
overflow: auto;
width: 100%;
margin: 0 0 9px;
}
.markdown table th {
font-weight: 700
}
.markdown table th,
.markdown table td {
border: 1px solid #DDD;
padding: 6px 13px;
}
.markdown table tr {
background-color: #FFF;
border-top: 1px solid #CCC;
}
.markdown table tr:nth-child(2n) {
background-color: #F8F8F8
}
.markdown li {
line-height: 1.6;
margin-top: 6px;
}
.markdown dl dt {
font-style: italic;
margin-top: 9px;
}
.markdown dl dd {
margin: 0 0 9px;
padding: 0 9px;
}
.markdown blockquote,
.markdown blockquote p {
font-size: 14px;
background-color: #f5f5f5;
}
.markdown > pre {
line-height: 1.6;
overflow: auto;
background: #fff;
padding: 6px 10px;
border: 1px solid #ddd;
}
.markdown > pre.linenums {
padding: 0;
}
.markdown > pre > ol.linenums {
-webkit-box-shadow: inset 40px 0 0 #f5f5f5, inset 41px 0 0 #ccc;
box-shadow: inset 40px 0 0 #f5f5f5, inset 41px 0 0 #ccc;
}
.markdown > pre > code,
.markdown > pre > ol.linenums > li > code {
white-space: pre;
word-wrap: normal;
}
.markdown > pre > ol.linenums > li > code {
padding: 0 10px;
}
.markdown > pre > ol.linenums > li:first-child {
padding-top: 6px;
}
.markdown > pre > ol.linenums > li:last-child {
padding-bottom: 6px;
}
.markdown > pre > ol.linenums > li {
border-left: 1px solid #ddd;
}
.markdown hr {
border: none;
color: #ccc;
height: 4px;
padding: 0;
margin: 15px 0;
border-bottom: 2px solid #EEE;
}
.markdown blockquote:last-child,
.markdown ul:last-child,
.markdown ol:last-child,
.markdown > pre:last-child,
.markdown > pre:last-child,
.markdown p:last-child {
margin-bottom: 0;
}
.markdown .btn {
color: #fff;
}
/* Author: jmblog */
/* Project: https://github.com/jmblog/color-themes-for-google-code-prettify */
/* GitHub Theme */
/* Pretty printing styles. Used with prettify.js. */
/* SPAN elements with the classes below are added by prettyprint. */
/* plain text */
.pln {
color: #333333;
}
@media screen {
/* string content */
.str {
color: #dd1144;
}
/* a keyword */
.kwd {
color: #333333;
}
/* a comment */
.com {
color: #999988;
}
/* a type name */
.typ {
color: #445588;
}
/* a literal value */
.lit {
color: #445588;
}
/* punctuation */
.pun {
color: #333333;
}
/* lisp open bracket */
.opn {
color: #333333;
}
/* lisp close bracket */
.clo {
color: #333333;
}
/* a markup tag name */
.tag {
color: navy;
}
/* a markup attribute name */
.atn {
color: teal;
}
/* a markup attribute value */
.atv {
color: #dd1144;
}
/* a declaration */
.dec {
color: #333333;
}
/* a variable name */
.var {
color: teal;
}
/* a function name */
.fun {
color: #990000;
}
}
/* Use higher contrast and text-weight for printable form. */
@media print, projection {
.str {
color: #006600;
}
.kwd {
color: #006;
font-weight: bold;
}
.com {
color: #600;
font-style: italic;
}
.typ {
color: #404;
font-weight: bold;
}
.lit {
color: #004444;
}
.pun, .opn, .clo {
color: #444400;
}
.tag {
color: #006;
font-weight: bold;
}
.atn {
color: #440044;
}
.atv {
color: #006600;
}
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}
/* IE indents via margin-left */
li.L0,
li.L1,
li.L2,
li.L3,
li.L4,
li.L5,
li.L6,
li.L7,
li.L8,
li.L9 {
/* */
}
/* Alternate shading for lines */
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 {
/* */
}

View File

@ -40,10 +40,37 @@ var Gogits = {
//container: "body"
});
};
Gogits.initPopovers = function () {
var hideAllPopovers = function() {
$('[data-toggle=popover]').each(function() {
$(this).popover('hide');
});
};
$(document).on('click', function(e) {
var $e = $(e.target);
if($e.data('toggle') == 'popover'||$e.parents("[data-toggle=popover], .popover").length > 0){
return;
}
hideAllPopovers();
});
$("body").popover({
selector: "[data-toggle=popover]"
});
};
Gogits.initTabs = function () {
var $tabs = $('[data-init=tabs]');
$tabs.find("li:eq(0) a").tab("show");
};
// render markdown
Gogits.renderMarkdown = function () {
var $pre = $('.markdown').find('pre > code').parent();
$pre.addClass("prettyprint");
prettyPrint();
}
})(jQuery);
// ajax utils
@ -68,8 +95,10 @@ var Gogits = {
function initCore() {
Gogits.initTooltips();
Gogits.initPopovers();
Gogits.initTabs();
Gogits.initModals();
Gogits.renderMarkdown();
}
function initRegister() {
@ -112,3 +141,16 @@ function initUserSetting(){
}
});
}
(function ($) {
$(function () {
initCore();
var body = $("#gogs-body");
if (body.data("page") == "user-signup") {
initRegister();
}
if (body.data("page") == "user") {
initUserSetting();
}
});
})(jQuery);

File diff suppressed because one or more lines are too long

482
public/js/lib.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -20,50 +20,36 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
return
}
if ctx.HasError() {
ctx.Render.HTML(200, "repo/create", ctx.Data)
return
}
// TODO: access check
user, err := models.GetUserById(form.UserId)
if err != nil {
if err.Error() == models.ErrUserNotExist.Error() {
ctx.RenderWithErr("User does not exist", "repo/create", &form)
return
}
}
if err == nil {
if _, err = models.CreateRepository(user,
if _, err := models.CreateRepository(ctx.User,
form.RepoName, form.Description, form.Language, form.License,
form.Visibility == "private", form.InitReadme == "on"); err == nil {
ctx.Render.Redirect("/"+user.Name+"/"+form.RepoName, 302)
ctx.Render.Redirect("/"+ctx.User.Name+"/"+form.RepoName, 302)
return
}
}
if err.Error() == models.ErrRepoAlreadyExist.Error() {
} else if err == models.ErrRepoAlreadyExist {
ctx.RenderWithErr("Repository name has already been used", "repo/create", &form)
return
}
ctx.Handle(200, "repo.Create", err)
}
func Delete(ctx *middleware.Context, form auth.DeleteRepoForm) {
ctx.Data["Title"] = "Delete repository"
if ctx.Req.Method == "GET" {
ctx.Render.HTML(200, "repo/delete", ctx.Data)
func SettingPost(ctx *middleware.Context) {
if !ctx.Repo.IsOwner {
ctx.Render.Error(404)
return
}
if err := models.DeleteRepository(form.UserId, form.RepoId, form.UserName); err != nil {
switch ctx.Query("action") {
case "delete":
if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
ctx.Data["ErrorMsg"] = "Please make sure you entered repository name is correct."
ctx.Render.HTML(200, "repo/setting", ctx.Data)
return
}
if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil {
ctx.Handle(200, "repo.Delete", err)
return
}
}
ctx.Render.Redirect("/", 302)
}

View File

@ -9,10 +9,33 @@ import (
"github.com/codegangsta/martini"
"github.com/gogits/git"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware"
)
func Branches(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsValid {
return
}
ctx.Data["Username"] = params["username"]
ctx.Data["Reponame"] = params["reponame"]
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
ctx.Handle(200, "repo.Branches", err)
return
}
ctx.Data["Branches"] = brs
ctx.Data["IsRepoToolbarBranches"] = true
ctx.Render.HTML(200, "repo/branches", ctx.Data)
}
func Single(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsValid {
return
@ -22,17 +45,28 @@ func Single(ctx *middleware.Context, params martini.Params) {
params["branchname"] = "master"
}
// Get tree path
treename := params["_1"]
// Directory and file list.
files, err := models.GetReposFiles(params["username"], params["reponame"],
params["branchname"], treename)
if err != nil {
ctx.Handle(200, "repo.Single", err)
ctx.Render.Error(404)
return
}
ctx.Data["Username"] = params["username"]
ctx.Data["Reponame"] = params["reponame"]
ctx.Data["Branchname"] = params["branchname"]
// Branches.
brs, err := models.GetBranches(params["username"], params["reponame"])
if err != nil {
ctx.Render.Error(404)
return
}
ctx.Data["Branches"] = brs
var treenames []string
Paths := make([]string, 0)
@ -43,16 +77,52 @@ func Single(ctx *middleware.Context, params martini.Params) {
}
}
// Get latest commit according username and repo name
commit, err := models.GetLastestCommit(params["username"], params["reponame"])
if err != nil {
ctx.Render.Error(404)
return
}
ctx.Data["LatestCommit"] = commit
var readmeFile *models.RepoFile
for _, f := range files {
if !f.IsFile() {
continue
}
if len(f.Name) < 6 {
continue
}
if strings.ToLower(f.Name[:6]) == "readme" {
readmeFile = f
break
}
}
if readmeFile != nil {
// if file large than 1M not show it
if readmeFile.Size > 1024*1024 || readmeFile.Filemode != git.FileModeBlob {
ctx.Data["FileIsLarge"] = true
} else if blob, err := readmeFile.LookupBlob(); err != nil {
ctx.Data["FileIsLarge"] = true
} else {
ctx.Data["ReadmeContent"] = string(base.RenderMarkdown(blob.Contents()))
}
}
ctx.Data["Paths"] = Paths
ctx.Data["Treenames"] = treenames
ctx.Data["IsRepoToolbarSource"] = true
ctx.Data["IsRepositoryOwner"] = strings.ToLower(params["username"]) == ctx.User.LowerName
ctx.Data["Files"] = files
ctx.Render.HTML(200, "repo/single", ctx.Data)
}
func Setting(ctx *middleware.Context, params martini.Params) {
if !ctx.Repo.IsValid {
if !ctx.Repo.IsOwner {
ctx.Render.Error(404)
return
}
@ -63,7 +133,6 @@ func Setting(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = title + " - settings"
ctx.Data["IsRepoToolbarSetting"] = true
ctx.Data["IsRepositoryOwner"] = strings.ToLower(params["username"]) == ctx.User.LowerName
ctx.Render.HTML(200, "repo/setting", ctx.Data)
}

View File

@ -195,7 +195,7 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
feeds := make([]string, len(actions))
for i := range actions {
feeds[i] = fmt.Sprintf(feedTpl, base.ActionIcon(actions[i].OpType),
base.TimeSince(actions[i].Created), base.ActionDesc(actions[i]))
base.TimeSince(actions[i].Created), base.ActionDesc(actions[i], ctx.User.AvatarLink()))
}
ctx.Render.JSON(200, &feeds)
}

View File

@ -1,16 +1,9 @@
<script>
$(function(){
initCore();{{if .PageIsSignUp}}
initRegister();{{end}}{{if .PageIsUserSetting}}
initUserSetting();{{end}}
});
</script>
<div class="wrapper-push"></div>
</div>
<footer id="footer">
<div class="container footer-wrap">
<p>
© 2014 Gogs · ver {{AppVer}} · <i class="fa fa-github"></i><a target="_blank" href="https://github.com/gogits/gogs">GitHub</a>
<p>© 2014 Gogs · ver {{AppVer}} ·
<i class="fa fa-github"></i><a target="_blank" href="https://github.com/gogits/gogs">GitHub</a>
</p>
<p class="desc"></p>
</div>

View File

@ -3,6 +3,8 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="shortcut icon" href="/img/favicon.png" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta name="author" content="Gogs - Go Git Service" />
<meta name="description" content="Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language" />
<meta name="keywords" content="go, git">
@ -11,10 +13,12 @@
<link href="/css/bootstrap.min.css" rel="stylesheet" />
<link href="/css/todc-bootstrap.min.css" rel="stylesheet" />
<link href="/css/font-awesome.min.css" rel="stylesheet" />
<link href="/css/markdown.css" rel="stylesheet" />
<link href="/css/gogs.css" rel="stylesheet" />
<script src="/js/jquery-1.10.1.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/lib.js"></script>
<script src="/js/app.js"></script>
<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>
</head>

View File

@ -0,0 +1,42 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
{{template "repo/nav" .}}
{{template "repo/toolbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-source">
<div class="panel panel-default branch-box info-box">
<div class="panel-heading info-head">
<h4>Branches</h4>
</div>
<table class="panel-footer table branch-list table table-hover">
<thead>
<tr>
<th class="name"></th>
<th class="behind">Behind</th>
<th class="ahead">Ahead</th>
<th class="date">Last Commit</th>
<th class="action"></th>
</tr>
</thead>
<tbody>
<tr class="branch-main">
<td class="name" colspan="3">
<a href="#"><strong>BranchName</strong></a>
<button class="btn btn-primary btn-sm">base branch</button>
</td>
<td class="date">3 years ago</td>
<td class="action"></td>
</tr>
<tr>
<td class="name"><a href="#"><strong>BranchName</strong></a></td>
<td class="behind">102 <span class="graph" style="width: 100%"></span></td>
<td class="ahead"><span class="graph" style="width: 4%"></span>4</td>
<td class="date">3 years ago</td>
<td class="action"><a class="btn btn-info btn-sm" href="#">compare</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -1,12 +0,0 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div class="container">
<form action="/repo/delete" method="post" class="form-horizontal">
<div class="form-group">
<div class="col-md-offset-4 col-md-3">
<button type="submit" class="btn btn-danger">Delete repository</button>
</div>
</div>
</form>
</div>
{{template "base/footer" .}}

View File

@ -1,16 +1,15 @@
<div id="gogs-body-nav" class="gogs-repo-nav">
<div class="container">
<div class="gogs-repo-btns pull-right">
<div class="row">
<div class="col-md-6">
<h3><i class="fa fa-book fa-lg"></i><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / {{.Repository.Name}}</h3>
</div>
<div class="col-md-6 actions text-right">
<div class="btn-group" id="gogs-repo-clone">
<button type="button" class="btn btn-default"><i class="fa fa-download"></i>Clone</button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<button type="button" class="btn btn-default dropdown-toggle" data-container="body" data-toggle="popover" data-placement="bottom" data-content="<label>SSH:</label><div class='input-group'><input type='text' class='form-control' value='git@{{AppDomain}}:{{.Owner.Name}}/{{.Repository.Name}}.git'></div>" data-html="1">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu" role="menu">
<div data-val="down-http">http link</div>
<div data-val="down-git">git link</div>
</div>
</div>
<div class="btn-group" id="gogs-repo-watching">
<button type="button" class="btn btn-default"><i class="fa fa-eye"></i>Watch {x}</button>
@ -37,6 +36,6 @@
<button type="button" class="btn btn-default"><i class="fa fa-code-fork"></i>Fork&nbsp;&nbsp;{{.Repository.NumForks}}</button>
</div>
</div>
<h3><i class="fa fa-book fa-lg"></i><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / {{.Repository.Name}}</h3>
</div>
</div>
</div>

View File

@ -4,31 +4,61 @@
{{template "repo/toolbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Repository Settings</h4>
<ul class="list-group" data-init="tabs">
<li class="list-group-item"><a href="#options" data-toggle="tab">Options</a></li>
<!--<li class="list-group-item" data-toggle="tab"><a href="#">Collaborators</a></li>
<li class="list-group-item" data-toggle="tab"><a href="#">Notifications</a></li>-->
<li class="list-group-item"><a href="#delete" data-toggle="tab">Delete</a></li>
<li class="list-group-item active"><a href="/{{.Owner.Name}}/{{.Repository.Name}}/settings">Options</a></li>
<!--<li class="list-group-item"><a href="#">Collaborators</a></li>
<li class="list-group-item"><a href="#">Notifications</a></li>-->
</ul>
</div>
<div id="gogs-repo-setting-container" class="col-md-9 tab-content">
<div id="options" class="tab-pane">
<h4>Repository Options</h4>
<div id="gogs-repo-setting-container" class="col-md-9">
{{if .ErrorMsg}}<p class="alert alert-danger">{{.ErrorMsg}}</p>{{end}}
<div class="panel panel-default">
<div class="panel-heading">
Repository Options
</div>
<div id="delete" class="tab-pane">
<h4>Delete Repository</h4>
<p class="alert alert-warning">Unexpected bad things will happen if you don't read this!</p>
<p>This action <strong>CANNOT</strong> be undone. This will delete the repository, wiki, issues, and comments permanently. </p>
<div class="panel-body">
<form action="/repo/delete" method="post">
<input type="hidden" name="userId" value="{{.Owner.Id}}"/>
<input type="hidden" name="userName" value="{{.Owner.Name}}"/>
<input type="hidden" name="repoId" value="{{.Repository.Id}}"/>
<hr/>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
Danger Zone
</div>
<div class="panel-body">
<button type="button" class="btn btn-default pull-right" href="#delete-repository-modal" data-toggle="modal">
Delete this repository
</button>
<dd>
<dt>Delete this repository.</dt>
<dl>Once you delete a repository, there is no going back. Please be certain.</dl>
</dd>
<div class="modal fade" id="delete-repository-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form action="/{{.Owner.Name}}/{{.Repository.Name}}/settings" method="post" class="modal-content">
<input type="hidden" name="action" value="delete">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">Delete repository</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label>Please enter your repository name "<strong class="text-danger">{{.Repository.Name}}</strong>"</label>
<input name="repository" class="form-control" type="text" placeholder="Type your repository name" required="required">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button class="btn btn-danger btn-lg">I understand the consequences, delete this repository</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View File

@ -5,39 +5,43 @@
<div id="gogs-body" class="container">
<div id="gogs-source">
<div class="source-toolbar">
<button class="btn btn-default pull-right"><i class="fa fa-plus-square"></i>Add File</button>
<div class="dropdown branch-switch">
<a href="#" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><i class="fa fa-chain"></i>master&nbsp;&nbsp;
<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a class="current" href="/{{.RepositoryLink}}/tree/master">master</a></li>
<li><a href="/{{.RepositoryLink}}/tree/develop">develop</a></li>
</ul>
</div>
{{$paths := .Paths}}
{{ $username := .Username}}
{{ $reponame := .Reponame}}
{{ $branchname := .Branchname}}
{{ $treenames := .Treenames}}
{{ $repoLink := .RepositoryLink}}
{{ $n := len $treenames}}
<button class="btn btn-default pull-right"><i class="fa fa-plus-square"></i>Add File</button>
<div class="dropdown branch-switch">
<a href="#" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><i class="fa fa-chain"></i>{{$branchname}}&nbsp;&nbsp;
<b class="caret"></b></a>
<ul class="dropdown-menu">
{{range .Branches}}
<li><a {{if eq . $branchname}}class="current" {{end}}href="/{{$repoLink}}/tree/{{.}}">{{.}}</a></li>
{{end}}
</ul>
</div>
{{$paths := .Paths}}
{{ $l := Subtract $n 1}}
<ol class="breadcrumb">
<li class="root dir"><a href="/{{$username}}/{{$reponame}}/tree/{{$branchname}}">{{.Repository.Name}}</a></li>
<li class="root dir">
<a href="/{{$username}}/{{$reponame}}/tree/{{$branchname}}">{{.Repository.Name}}</a></li>
{{range $i, $v := $treenames}}
<li class="dir">
{{if eq $i $l}}{{$v}}
{{else}}
<a href="/{{$username}}/{{$reponame}}/tree/{{$branchname}}/{{index $paths $i}}">{{$v}}</a>&nbsp;
{{end}}</li>
{{end}}
</li>
{{end}}
</ol>
</div>
<div class="panel panel-default info-box">
<div class="panel-heading info-head">
Merge branch 'release/1.1.1'
<a href="/{{$username}}/{{$reponame}}/commit/{{.LatestCommit.SHA}}">{{.LatestCommit.Message}}</a>
</div>
<div class="panel-body info-content">
slene authored 4 days ago
<a href="/user/{{.LatestCommit.Author}}">{{.LatestCommit.Author}}</a> <span class="text-muted">{{TimeSince .LatestCommit.Date}}</span>
</div>
<table class="panel-footer table file-list">
<thead class="hidden">
@ -50,7 +54,8 @@
</thead>
<tbody>
{{range .Files}}
<tr {{if .IsDir}}class="is-dir"{{end}}>
<tr
{{if .IsDir}}class="is-dir"{{end}}>
<td class="icon">
<i class="fa {{if .IsDir}}fa-folder{{else}}fa-file-text-o{{end}}"></i>
</td>
@ -79,12 +84,18 @@
</table>
</div>
<div class="panel panel-default file-content">
<div class="panel-heading">
README.md
<div class="panel-heading file-head">
<i class="icon fa fa-book"></i> README.md
</div>
<div class="panel-body markdown">
httplib
{{if .FileIsLarge}}
<div class="panel-footer">
Large file size 1000kb
</div>
{{else}}
<div class="panel-body file-body markdown">
{{.ReadmeContent|str2html}}
</div>
{{end}}
</div>
</div>
</div>

View File

@ -4,9 +4,10 @@
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="/{{.RepositoryLink}}">Source</a></li>
<li><a href="/{{.RepositoryLink}}/commits">Commits</a></li>
<li><a href="/{{.RepositoryLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li>
<li><a href="/{{.RepositoryLink}}/pulls">Pull Requests</a></li>
<li class="{{if .IsRepoToolbarCommits}}active{{end}}"><a href="/{{.RepositoryLink}}/commits">Commits</a></li>
<li class="{{if .IsRepoToolbarBranches}}active{{end}}"><a href="/{{.RepositoryLink}}/branches">Branches</a></li>
<li class="{{if .IsRepoToolbarPulls}}active{{end}}"><a href="/{{.RepositoryLink}}/pulls">Pull Requests</a></li>
<li class="{{if .IsRepoToolbarIssues}}active{{end}}"><a href="/{{.RepositoryLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
<ul class="dropdown-menu">

View File

@ -11,7 +11,7 @@
<h3>News Feed</h3>
</div>
</div>
<div id="gogs-body" class="container">
<div id="gogs-body" class="container" data-page="user">
{{if .HasInfo}}<div class="alert alert-info">{{.InfoMsg}}</div>{{end}}
<div id="gogs-feed-left" class="col-md-8">
<ul class="list-unstyled activity-list">

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-body" class="container" data-page="user">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-body" class="container" data-page="user">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-body" class="container" data-page="user">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-body" class="container" data-page="user">
<div id="gogs-user-profile" class="col-md-3">
<div class="profile-avatar text-center">
<a href="{{.Owner.HomeLink}}" class="center-block" data-toggle="tooltip" data-placement="bottom" title="Change Avatar">
@ -32,10 +32,11 @@
{{if eq .TabName "activity"}}
<div class="tab-pane active">
<ul class="list-unstyled activity-list">
{{$avatarLink := .Owner.AvatarLink}}
{{range .Feeds}}
<li>
<i class="icon fa fa-{{ActionIcon .OpType}}"></i>
<div class="info"><span class="meta">{{TimeSince .Created}}</span><br>{{ActionDesc . | str2html}}</div>
<div class="info"><span class="meta">{{TimeSince .Created}}</span><br>{{ActionDesc . $avatarLink | str2html}}</div>
<span class="clearfix"></span>
</li>
{{else}}

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-body" class="container" data-page="user">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-body" class="container" data-page="user">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="gogs-body" class="container">
<div id="gogs-body" class="container" data-page="user">
<div id="gogs-user-setting-nav" class="col-md-3">
<h4>Account Setting</h4>
<ul class="list-group">

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div class="container" id="gogs-body">
<div class="container" id="gogs-body" data-page="user-signin">
<form action="/user/login" method="post" class="form-horizontal gogs-card" id="gogs-login-card">
<h3>Log in</h3>
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>

View File

@ -1,6 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div class="container" id="gogs-body">
<div class="container" id="gogs-body" data-page="user-signup">
<form action="/user/sign_up" method="post" class="form-horizontal gogs-card" id="gogs-login-card">
<h3>Sign Up</h3>
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>

6
web.go
View File

@ -68,14 +68,16 @@ func runWeb(*cli.Context) {
m.Get("/user/:username", middleware.SignInRequire(false), user.Profile)
m.Any("/repo/create", middleware.SignInRequire(true), binding.BindIgnErr(auth.CreateRepoForm{}), repo.Create)
m.Any("/repo/delete", middleware.SignInRequire(true), binding.Bind(auth.DeleteRepoForm{}), repo.Delete)
m.Get("/help", routers.Help)
m.Get("/:username/:reponame/settings", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Setting)
m.Post("/:username/:reponame/settings", middleware.SignInRequire(true), middleware.RepoAssignment(true), repo.SettingPost)
m.Get("/:username/:reponame/settings", middleware.SignInRequire(true), middleware.RepoAssignment(true), repo.Setting)
m.Get("/:username/:reponame/commits", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Commits)
m.Get("/:username/:reponame/issues", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Issues)
m.Get("/:username/:reponame/pulls", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Pulls)
m.Get("/:username/:reponame/branches", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Branches)
m.Get("/:username/:reponame/tree/:branchname/**",
middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Single)
m.Get("/:username/:reponame/tree/:branchname",